Some more fixes of the manage-tenants.sh file
This commit is contained in:
+155
-254
@@ -1,92 +1,39 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
IFS=$'\n\t'
|
IFS=$'\n\t'
|
||||||
# Script to manage multitenant deployment
|
|
||||||
# Allows adding, removing, and restarting tenants without downtime for others
|
# Skript zur Verwaltung von Multitenant-Deployments
|
||||||
|
|
||||||
if [ ! -f "docker-compose-multitenant.yml" ]; then
|
if [ ! -f "docker-compose-multitenant.yml" ]; then
|
||||||
echo "Error: docker-compose-multitenant.yml not found."
|
echo "Error: docker-compose-multitenant.yml not found."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Resolve script directory so config paths are deterministic even when called via sudo
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
CONFIG_FILE="$SCRIPT_DIR/config.json"
|
CONFIG_FILE="$SCRIPT_DIR/config.json"
|
||||||
|
|
||||||
ensure_runtime_config_json() {
|
# === HILFSFUNKTIONEN ===
|
||||||
local config_path backup_path
|
|
||||||
config_path="$CONFIG_FILE"
|
|
||||||
|
|
||||||
|
ensure_runtime_config_json() {
|
||||||
|
local config_path="$CONFIG_FILE"
|
||||||
|
local backup_path
|
||||||
if [ -d "$config_path" ]; then
|
if [ -d "$config_path" ]; then
|
||||||
backup_path="${config_path}.dir.$(date +%Y%m%d-%H%M%S).bak"
|
backup_path="${config_path}.dir.$(date +%Y%m%d-%H%M%S).bak"
|
||||||
mv "$config_path" "$backup_path"
|
mv "$config_path" "$backup_path"
|
||||||
echo "Warning: moved unexpected directory $config_path to $backup_path"
|
echo "Warning: moved unexpected directory $config_path to $backup_path"
|
||||||
fi
|
fi
|
||||||
|
if [ ! -f "$config_path" ]; then
|
||||||
FORCE_REMOVE=false
|
|
||||||
if [ "${2:-}" = "--yes" ] || [ "${2:-}" = "-y" ]; then
|
|
||||||
FORCE_REMOVE=true
|
|
||||||
TENANT_ID="${3:-}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$TENANT_ID" ]; then
|
|
||||||
cat > "$config_path" <<'EOF'
|
cat > "$config_path" <<'EOF'
|
||||||
{
|
{
|
||||||
"ver": "2.6.5",
|
"ver": "2.6.5",
|
||||||
|
"tenants": {}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
if [ "$FORCE_REMOVE" != true ]; then
|
get_tenant_aliases() {
|
||||||
echo -n "WARNING: Are you sure you want to permanently delete all data for tenant '$TENANT_ID'? (y/N) "
|
local tenant_id="$1"
|
||||||
read confirm
|
|
||||||
if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then
|
|
||||||
echo "Removal canceled."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Removing tenant '$TENANT_ID'..."
|
|
||||||
APP_CONTAINER=$(docker ps -qf "name=app" | head -n 1)
|
|
||||||
port_to_remove=""
|
|
||||||
if [ -n "$APP_CONTAINER" ]; then
|
|
||||||
port_to_remove="$({ docker exec "$APP_CONTAINER" python3 - "$TENANT_ID" <<'PY'
|
|
||||||
import sys
|
|
||||||
sys.path.insert(0, '/app')
|
|
||||||
sys.path.insert(0, '/app/Web')
|
|
||||||
from tenant import delete_tenant, get_tenant_config
|
|
||||||
|
|
||||||
tenant_id = sys.argv[1]
|
|
||||||
tenant_cfg = get_tenant_config(tenant_id)
|
|
||||||
port = tenant_cfg.get('port')
|
|
||||||
|
|
||||||
if not delete_tenant(tenant_id):
|
|
||||||
print(f'Error: failed to delete tenant {tenant_id}', file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if port is not None:
|
|
||||||
print(port)
|
|
||||||
PY
|
|
||||||
} 2>/dev/null)"
|
|
||||||
echo "Tenant '$TENANT_ID' database and config removed."
|
|
||||||
else
|
|
||||||
echo "Warning: Application container not running. Tenant database may still exist in MongoDB."
|
|
||||||
if port_to_remove="$(remove_tenant_port "$TENANT_ID" 2>/dev/null)"; then
|
|
||||||
:
|
|
||||||
else
|
|
||||||
echo "Warning: tenant '$TENANT_ID' was not configured in config.json or could not be removed."
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$port_to_remove" ]; then
|
|
||||||
remove_runtime_port "$port_to_remove"
|
|
||||||
fi
|
|
||||||
sync_tenant_port_map
|
|
||||||
if [ -n "$(docker ps -qf 'name=app' | head -n 1)" ]; then
|
|
||||||
restart_app_container
|
|
||||||
fi
|
|
||||||
if [ -n "$port_to_remove" ]; then
|
|
||||||
echo "Removed tenant '$TENANT_ID' and cleaned runtime port $port_to_remove."
|
|
||||||
else
|
|
||||||
echo "Removed tenant '$TENANT_ID'. No port mapping was present."
|
|
||||||
fi
|
|
||||||
local normalized alias
|
local normalized alias
|
||||||
normalized="$(printf '%s' "$tenant_id" | tr '[:upper:]' '[:lower:]')"
|
normalized="$(printf '%s' "$tenant_id" | tr '[:upper:]' '[:lower:]')"
|
||||||
printf '%s\n' "$tenant_id"
|
printf '%s\n' "$tenant_id"
|
||||||
@@ -103,6 +50,53 @@ PY
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
register_tenant_port() {
|
||||||
|
local tenant_id="$1"
|
||||||
|
local port="$2"
|
||||||
|
|
||||||
|
if python3 - <<'PY' "$CONFIG_FILE" "$tenant_id" "$port"
|
||||||
|
import json, sys, os
|
||||||
|
path, tenant_id, port_str = sys.argv[1], sys.argv[2], sys.argv[3]
|
||||||
|
if not os.path.isfile(path):
|
||||||
|
print(f"Error: config file not found: {path}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
with open(path, 'r', encoding='utf-8') as f:
|
||||||
|
cfg = json.load(f)
|
||||||
|
tenants = cfg.get('tenants')
|
||||||
|
if tenants is None or not isinstance(tenants, dict):
|
||||||
|
tenants = {}
|
||||||
|
aliases = {tenant_id}
|
||||||
|
normalized = tenant_id.lower()
|
||||||
|
if normalized.startswith('schule'):
|
||||||
|
aliases.add('school' + normalized[len('schule'):])
|
||||||
|
elif normalized.startswith('school'):
|
||||||
|
aliases.add('schule' + normalized[len('school'):])
|
||||||
|
for tid, conf in tenants.items():
|
||||||
|
if isinstance(conf, dict) and str(conf.get('port')) == port_str and tid not in aliases:
|
||||||
|
print(f"Error: port {port_str} is already mapped to tenant {tid}", file=sys.stderr)
|
||||||
|
sys.exit(2)
|
||||||
|
existing = {}
|
||||||
|
for alias in aliases:
|
||||||
|
alias_cfg = tenants.get(alias)
|
||||||
|
if isinstance(alias_cfg, dict):
|
||||||
|
existing = alias_cfg
|
||||||
|
break
|
||||||
|
existing['port'] = int(port_str)
|
||||||
|
for alias in aliases:
|
||||||
|
tenants[alias] = dict(existing)
|
||||||
|
cfg['tenants'] = tenants
|
||||||
|
with open(path, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(cfg, f, indent=4, ensure_ascii=False)
|
||||||
|
print(f"Registered tenant port {port_str} for {tenant_id}")
|
||||||
|
PY
|
||||||
|
then
|
||||||
|
echo "Tenant $tenant_id port $port registered in config.json"
|
||||||
|
else
|
||||||
|
echo "Failed to register tenant port $port for $tenant_id"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
write_trial_tenant_config() {
|
write_trial_tenant_config() {
|
||||||
local tenant_id="$1"
|
local tenant_id="$1"
|
||||||
local port="$2"
|
local port="$2"
|
||||||
@@ -165,7 +159,7 @@ existing['modules'] = {
|
|||||||
'inventory': {'enabled': True},
|
'inventory': {'enabled': True},
|
||||||
'library': {'enabled': True},
|
'library': {'enabled': True},
|
||||||
'student_cards': {'enabled': True, 'default_borrow_days': 14, 'max_borrow_days': 365},
|
'student_cards': {'enabled': True, 'default_borrow_days': 14, 'max_borrow_days': 365},
|
||||||
'terminplan': {'enabled': True},
|
'terminplan': {'enabled': True},
|
||||||
'mail': {'enabled': True},
|
'mail': {'enabled': True},
|
||||||
}
|
}
|
||||||
existing['trial'] = trial_config
|
existing['trial'] = trial_config
|
||||||
@@ -211,7 +205,7 @@ sanitized = "".join(c for c in tenant_id if c.isalnum() or c == "_")
|
|||||||
db_name = f"inventar_{sanitized}"
|
db_name = f"inventar_{sanitized}"
|
||||||
client = MongoClient(settings.MONGODB_HOST, int(settings.MONGODB_PORT))
|
client = MongoClient(settings.MONGODB_HOST, int(settings.MONGODB_PORT))
|
||||||
db = client[db_name]
|
db = client[db_name]
|
||||||
hashed_pw = hashlib.scrypt('admin123'.encode(), salt=b'some_salt', n=16384, r=8, p=1).hex()
|
hashed_pw = hashlib.scrypt("admin123".encode(), salt=b"some_salt", n=16384, r=8, p=1).hex()
|
||||||
|
|
||||||
action_permissions = {
|
action_permissions = {
|
||||||
"can_borrow": True,
|
"can_borrow": True,
|
||||||
@@ -388,7 +382,6 @@ restart_app_container() {
|
|||||||
|
|
||||||
ensure_runtime_config_json
|
ensure_runtime_config_json
|
||||||
|
|
||||||
# If HOST_WORKDIR is set (called from container), use absolute paths so docker daemon resolves them correctly
|
|
||||||
if [ -n "${HOST_WORKDIR:-}" ]; then
|
if [ -n "${HOST_WORKDIR:-}" ]; then
|
||||||
workdir="$HOST_WORKDIR"
|
workdir="$HOST_WORKDIR"
|
||||||
compose_args+=( -f "$(readlink -f "$HOST_WORKDIR/docker-compose-multitenant.yml")" )
|
compose_args+=( -f "$(readlink -f "$HOST_WORKDIR/docker-compose-multitenant.yml")" )
|
||||||
@@ -399,7 +392,6 @@ restart_app_container() {
|
|||||||
compose_args+=( --env-file "$(readlink -f "$HOST_WORKDIR/.docker-build.env")" )
|
compose_args+=( --env-file "$(readlink -f "$HOST_WORKDIR/.docker-build.env")" )
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
# Normal case: called directly from host
|
|
||||||
compose_args+=( -f "$workdir/docker-compose-multitenant.yml" )
|
compose_args+=( -f "$workdir/docker-compose-multitenant.yml" )
|
||||||
if [ -f "$workdir/.docker-compose.runtime.override.yml" ]; then
|
if [ -f "$workdir/.docker-compose.runtime.override.yml" ]; then
|
||||||
compose_args+=( -f "$workdir/.docker-compose.runtime.override.yml" )
|
compose_args+=( -f "$workdir/.docker-compose.runtime.override.yml" )
|
||||||
@@ -409,7 +401,6 @@ restart_app_container() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Pass along COMPOSE_PROJECT_NAME if set so the internal docker-compose sees it
|
|
||||||
if [ -n "${COMPOSE_PROJECT_NAME:-}" ]; then
|
if [ -n "${COMPOSE_PROJECT_NAME:-}" ]; then
|
||||||
compose_args=( -p "$COMPOSE_PROJECT_NAME" "${compose_args[@]}" )
|
compose_args=( -p "$COMPOSE_PROJECT_NAME" "${compose_args[@]}" )
|
||||||
fi
|
fi
|
||||||
@@ -477,8 +468,6 @@ remove_runtime_port() {
|
|||||||
local port_list=()
|
local port_list=()
|
||||||
local new_ports=()
|
local new_ports=()
|
||||||
local first_port=""
|
local first_port=""
|
||||||
local ports_csv=""
|
|
||||||
|
|
||||||
|
|
||||||
if [ ! -f "$env_file" ]; then
|
if [ ! -f "$env_file" ]; then
|
||||||
return 0
|
return 0
|
||||||
@@ -506,8 +495,7 @@ remove_runtime_port() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
first_port="${new_ports[0]}"
|
first_port="${new_ports[0]}"
|
||||||
# Das 'local' hier entfernen, da wir es oben deklariert haben:
|
local ports_csv="$(IFS=,; echo "${new_ports[*]}")"
|
||||||
ports_csv="$(IFS=,; echo "${new_ports[*]}")"
|
|
||||||
|
|
||||||
if grep -q '^INVENTAR_HTTP_PORTS=' "$env_file" 2>/dev/null; then
|
if grep -q '^INVENTAR_HTTP_PORTS=' "$env_file" 2>/dev/null; then
|
||||||
sed -i "s|^INVENTAR_HTTP_PORTS=.*|INVENTAR_HTTP_PORTS=$ports_csv|" "$env_file"
|
sed -i "s|^INVENTAR_HTTP_PORTS=.*|INVENTAR_HTTP_PORTS=$ports_csv|" "$env_file"
|
||||||
@@ -522,120 +510,83 @@ remove_runtime_port() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
register_tenant_port() {
|
|
||||||
local tenant_id="$1"
|
|
||||||
local port="$2"
|
|
||||||
|
|
||||||
if python3 - <<'PY' "$CONFIG_FILE" "$tenant_id" "$port"
|
|
||||||
import json, sys, os
|
|
||||||
path, tenant_id, port_str = sys.argv[1], sys.argv[2], sys.argv[3]
|
|
||||||
if not os.path.isfile(path):
|
|
||||||
print(f"Error: config file not found: {path}", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
with open(path, 'r', encoding='utf-8') as f:
|
|
||||||
cfg = json.load(f)
|
|
||||||
tenants = cfg.get('tenants')
|
|
||||||
if tenants is None or not isinstance(tenants, dict):
|
|
||||||
tenants = {}
|
|
||||||
aliases = {tenant_id}
|
|
||||||
normalized = tenant_id.lower()
|
|
||||||
if normalized.startswith('schule'):
|
|
||||||
aliases.add('school' + normalized[len('schule'):])
|
|
||||||
elif normalized.startswith('school'):
|
|
||||||
aliases.add('schule' + normalized[len('school'):])
|
|
||||||
for tid, conf in tenants.items():
|
|
||||||
if isinstance(conf, dict) and str(conf.get('port')) == port_str and tid not in aliases:
|
|
||||||
print(f"Error: port {port_str} is already mapped to tenant {tid}", file=sys.stderr)
|
|
||||||
sys.exit(2)
|
|
||||||
existing = {}
|
|
||||||
for alias in aliases:
|
|
||||||
alias_cfg = tenants.get(alias)
|
|
||||||
if isinstance(alias_cfg, dict):
|
|
||||||
existing = alias_cfg
|
|
||||||
break
|
|
||||||
existing['port'] = int(port_str)
|
|
||||||
for alias in aliases:
|
|
||||||
tenants[alias] = dict(existing)
|
|
||||||
cfg['tenants'] = tenants
|
|
||||||
with open(path, 'w', encoding='utf-8') as f:
|
|
||||||
json.dump(cfg, f, indent=4, ensure_ascii=False)
|
|
||||||
print(f"Registered tenant port {port_str} for {tenant_id}")
|
|
||||||
PY
|
|
||||||
then
|
|
||||||
echo "Tenant $tenant_id port $port registered in config.json"
|
|
||||||
else
|
|
||||||
echo "Failed to register tenant port $port for $tenant_id"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
show_help() {
|
show_help() {
|
||||||
local script_name
|
local HEADER='\033[95m'
|
||||||
script_name="$(basename "$0")"
|
local BLUE='\033[94m'
|
||||||
|
local GREEN='\033[92m'
|
||||||
|
local YELLOW='\033[93m'
|
||||||
|
local RED='\033[91m'
|
||||||
|
local BOLD='\033[1m'
|
||||||
|
local RESET='\033[0m'
|
||||||
|
|
||||||
echo "=== MULTI-TENANT INVENTAR MANAGER ==="
|
cat << EOF
|
||||||
echo ""
|
|
||||||
echo "NUTZUNG:"
|
${HEADER}${BOLD}=== MULTI-TENANT INVENTAR MANAGER ===${RESET}
|
||||||
echo " ./$script_name <befehl> [tenant_id] [optionen]"
|
${YELLOW}Nutzung:${RESET} $0 <befehl> [tenant_id] [optionen]
|
||||||
echo ""
|
|
||||||
echo "VERFÜGBARE BEFEHLE:"
|
${BLUE}${BOLD}VERFÜGBARE BEFEHLE:${RESET}
|
||||||
echo " add <tenant_id> [port]"
|
${GREEN}add${RESET} <tenant_id> [port]
|
||||||
echo " Legt einen neuen Tenant an, registriert den Port und initialisiert"
|
Legt einen neuen Tenant an, registriert den Port und initialisiert
|
||||||
echo " die MongoDB-Datenbank mit einem Standard-Admin (admin / admin123)."
|
die MongoDB-Datenbank mit einem Standard-Admin (${YELLOW}admin / admin123${RESET}).
|
||||||
echo ""
|
|
||||||
echo " trial <tenant_id> [port] [tage]"
|
${GREEN}trial${RESET} <tenant_id> [port] [tage]
|
||||||
echo " Erstellt einen temporären Test-Tenant (Standardlaufzeit: 7 Tage)."
|
Erstellt einen temporären Test-Tenant. Standardlaufzeit: 7 Tage.
|
||||||
echo " Dieser läuft nach der festgelegten Zeit ab und wird automatisch gelöscht."
|
Läuft automatisch ab und löscht sich selbst.
|
||||||
echo ""
|
|
||||||
echo " remove [-y|--yes] <tenant_id>"
|
${GREEN}remove${RESET} [-y|--yes] <tenant_id>
|
||||||
echo " Löscht einen Tenant komplett (Konfiguration, Ports und Datenbank)."
|
Löscht einen Tenant, seine Konfiguration, Ports und die Datenbank.
|
||||||
echo " Nutze -y oder --yes, um die Bestätigungsabfrage zu überspringen."
|
Nutze ${RED}-y${RESET}, um die Bestätigungsabfrage zu überspringen.
|
||||||
echo ""
|
|
||||||
echo " restart-tenant <tenant_id>"
|
${GREEN}restart-tenant${RESET} <tenant_id>
|
||||||
echo " Startet einen spezifischen Tenant neu, indem alle aktiven Sessions"
|
Startet einen spezifischen Tenant neu, indem alle aktiven Sessions
|
||||||
echo " und der Cache in der MongoDB geleert werden (Sitzungs-Reset)."
|
und der Cache in der MongoDB geleert werden (Sitzungs-Reset).
|
||||||
echo ""
|
|
||||||
echo " restart-all"
|
${GREEN}restart-all${RESET}
|
||||||
echo " Führt einen Zero-Downtime Rolling-Restart für alle App-Container durch."
|
Führt einen Zero-Downtime Rolling-Restart für alle App-Container durch.
|
||||||
echo ""
|
|
||||||
echo " list"
|
${GREEN}list${RESET}
|
||||||
echo " Listet alle registrierten Tenants (aus der config.json) sowie alle"
|
Listet alle registrierten Tenants aus der 'config.json' sowie alle
|
||||||
echo " aktiven Tenant-Datenbanken (aus der MongoDB) übersichtlich auf."
|
aktiven Tenant-Datenbanken aus der MongoDB auf.
|
||||||
echo ""
|
|
||||||
echo " module <tenant_id> <modulname>=<on|off> [...]"
|
${GREEN}module${RESET} <tenant_id> <modulname>=<on|off> ...
|
||||||
echo " Aktiviert oder deaktiviert bestimmte Features/Module für einen Tenant."
|
Aktiviert oder deaktiviert bestimmte Features/Module für einen Tenant.
|
||||||
echo " Es können mehrere Module gleichzeitig konfiguriert werden."
|
Es können mehrere Module gleichzeitig übergeben werden.
|
||||||
echo ""
|
|
||||||
echo "GLOBALE OPTIONEN:"
|
${BLUE}${BOLD}GLOBALE OPTIONEN:${RESET}
|
||||||
echo " -h, --help"
|
${GREEN}-h, --help${RESET}
|
||||||
echo " Zeigt dieses Hilfemenü an."
|
Zeigt diese Hilfe an.
|
||||||
echo ""
|
|
||||||
echo "BEISPIELE:"
|
${YELLOW}Beispiele:${RESET}
|
||||||
echo " ./$script_name add schule_muenchen 8081"
|
$0 add schule_muenchen 8081
|
||||||
echo " ./$script_name trial test_user 8082 14"
|
$0 trial test_user 8082 14
|
||||||
echo " ./$script_name module schule_muenchen inventory=on mail=off"
|
$0 module schule_muenchen barre_code=on leih_historie=off
|
||||||
echo " ./$script_name remove --yes test_user"
|
$0 remove -y test_user
|
||||||
echo ""
|
|
||||||
|
-----------------------------------------------------------------
|
||||||
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
if [ -z "${1:-}" ]; then
|
if [ -z "${1:-}" ]; then
|
||||||
show_help
|
show_help
|
||||||
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
COMMAND="$1"
|
COMMAND="$1"
|
||||||
TENANT_ID="${2:-}"
|
|
||||||
|
# === MAIN ROUTING BLOCK ===
|
||||||
|
|
||||||
case "$COMMAND" in
|
case "$COMMAND" in
|
||||||
-h|--help)
|
-h|--help)
|
||||||
show_help
|
show_help
|
||||||
;;
|
;;
|
||||||
add)
|
add)
|
||||||
|
TENANT_ID="${2:-}"
|
||||||
if [ -z "$TENANT_ID" ]; then
|
if [ -z "$TENANT_ID" ]; then
|
||||||
echo "Error: Please provide a tenant_id."
|
echo "Error: Please provide a tenant_id."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
PORT_ARG="$3"
|
PORT_ARG="${3:-}"
|
||||||
if [ -n "$PORT_ARG" ]; then
|
if [ -n "$PORT_ARG" ]; then
|
||||||
if ! printf '%s\n' "$PORT_ARG" | grep -qE '^[0-9]+$'; then
|
if ! printf '%s\n' "$PORT_ARG" | grep -qE '^[0-9]+$'; then
|
||||||
echo "Error: Port must be a numeric value."
|
echo "Error: Port must be a numeric value."
|
||||||
@@ -650,68 +601,13 @@ case "$COMMAND" in
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Adding new tenant '$TENANT_ID'..."
|
echo "Adding new tenant '$TENANT_ID'..."
|
||||||
# Initialize tenant database via Python inside container
|
|
||||||
echo "Initializing database for $TENANT_ID..."
|
echo "Initializing database for $TENANT_ID..."
|
||||||
APP_CONTAINER=$(docker ps -qf "name=app" | head -n 1)
|
initialize_tenant_database "$TENANT_ID" "standard"
|
||||||
if [ -n "$APP_CONTAINER" ]; then
|
echo "Tenant '$TENANT_ID' successfully added. Ready to use."
|
||||||
docker exec $APP_CONTAINER python3 -c "
|
|
||||||
import sys, re; sys.path.insert(0, '/app'); sys.path.insert(0, '/app/Web'); from Web.modules.database import settings; from pymongo import MongoClient; import hashlib
|
|
||||||
tenant_id = sys.argv[1].lower()
|
|
||||||
sanitized = ''.join(c for c in tenant_id if c.isalnum() or c == '_')
|
|
||||||
db_name = f'inventar_{sanitized}'
|
|
||||||
client = MongoClient(settings.MONGODB_HOST, int(settings.MONGODB_PORT))
|
|
||||||
db = client[db_name]
|
|
||||||
hashed_pw = hashlib.scrypt('admin123'.encode(), salt=b'some_salt', n=16384, r=8, p=1).hex()
|
|
||||||
if db.users.count_documents({'Username': 'admin'}) == 0:
|
|
||||||
db.users.insert_one({
|
|
||||||
'Username': 'admin',
|
|
||||||
'Password': hashed_pw,
|
|
||||||
'Admin': True,
|
|
||||||
'active_ausleihung': None,
|
|
||||||
'name': 'Admin',
|
|
||||||
'last_name': 'User',
|
|
||||||
'IsStudent': False,
|
|
||||||
'PermissionPreset': 'full_access',
|
|
||||||
'ActionPermissions': {
|
|
||||||
'can_borrow': True,
|
|
||||||
'can_insert': True,
|
|
||||||
'can_edit': True,
|
|
||||||
'can_delete': True,
|
|
||||||
'can_manage_users': True,
|
|
||||||
'can_manage_settings': True,
|
|
||||||
'can_view_logs': True,
|
|
||||||
},
|
|
||||||
'PagePermissions': {
|
|
||||||
'home': True,
|
|
||||||
'tutorial_page': True,
|
|
||||||
'my_borrowed_items': True,
|
|
||||||
'notifications_view': True,
|
|
||||||
'impressum': True,
|
|
||||||
'license': True,
|
|
||||||
'library_view': True,
|
|
||||||
'terminplan': True,
|
|
||||||
'home_admin': True,
|
|
||||||
'upload_admin': True,
|
|
||||||
'library_admin': True,
|
|
||||||
'admin_borrowings': True,
|
|
||||||
'library_loans_admin': True,
|
|
||||||
'admin_damaged_items': True,
|
|
||||||
'admin_audit_dashboard': True,
|
|
||||||
'logs': True,
|
|
||||||
'manage_filters': True,
|
|
||||||
'manage_locations': True,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
print(f'Tenant {sys.argv[1]} database initialized. Default admin: admin / admin123')
|
|
||||||
" "$TENANT_ID"
|
|
||||||
echo "Tenant '$TENANT_ID' successfully added. Ready to use."
|
|
||||||
else
|
|
||||||
echo "Warning: Application container is not running. Please start the multi-tenant system first."
|
|
||||||
echo "Data will be initialized upon first access by the tenant."
|
|
||||||
fi
|
|
||||||
;;
|
;;
|
||||||
|
|
||||||
trial)
|
trial)
|
||||||
|
TENANT_ID="${2:-}"
|
||||||
if [ -z "$TENANT_ID" ]; then
|
if [ -z "$TENANT_ID" ]; then
|
||||||
echo "Error: Please provide a tenant_id."
|
echo "Error: Please provide a tenant_id."
|
||||||
exit 1
|
exit 1
|
||||||
@@ -742,10 +638,14 @@ print(f'Tenant {sys.argv[1]} database initialized. Default admin: admin / admin1
|
|||||||
;;
|
;;
|
||||||
|
|
||||||
remove)
|
remove)
|
||||||
FORCE_REMOVE=true
|
FORCE_REMOVE=false
|
||||||
if [ "${2:-}" = "--yes" ] || [ "${2:-}" = "-y" ]; then
|
TENANT_ARG="${2:-}"
|
||||||
|
|
||||||
|
if [ "$TENANT_ARG" = "--yes" ] || [ "$TENANT_ARG" = "-y" ]; then
|
||||||
FORCE_REMOVE=true
|
FORCE_REMOVE=true
|
||||||
TENANT_ID="${3:-}"
|
TENANT_ID="${3:-}"
|
||||||
|
else
|
||||||
|
TENANT_ID="$TENANT_ARG"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "$TENANT_ID" ]; then
|
if [ -z "$TENANT_ID" ]; then
|
||||||
@@ -766,44 +666,47 @@ print(f'Tenant {sys.argv[1]} database initialized. Default admin: admin / admin1
|
|||||||
APP_CONTAINER=$(docker ps -qf "name=app" | head -n 1)
|
APP_CONTAINER=$(docker ps -qf "name=app" | head -n 1)
|
||||||
port_to_remove=""
|
port_to_remove=""
|
||||||
|
|
||||||
if port_to_remove="$(remove_tenant_port "$TENANT_ID" 2>/dev/null)"; then
|
|
||||||
:
|
|
||||||
else
|
|
||||||
echo "Warning: tenant '$TENANT_ID' was not found in config.json."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 2. Dann die Datenbank via Container hart über PyMongo löschen
|
|
||||||
if [ -n "$APP_CONTAINER" ]; then
|
if [ -n "$APP_CONTAINER" ]; then
|
||||||
docker exec "$APP_CONTAINER" python3 - "$TENANT_ID" <<'PY'
|
# Zuerst die Datenbank via PyMongo hart droppen (Erzwungenes Löschen)
|
||||||
|
port_to_remove="$(docker exec "$APP_CONTAINER" python3 - "$TENANT_ID" <<'PY'
|
||||||
import sys, re
|
import sys, re
|
||||||
sys.path.insert(0, '/app')
|
sys.path.insert(0, '/app')
|
||||||
sys.path.insert(0, '/app/Web')
|
sys.path.insert(0, '/app/Web')
|
||||||
|
from tenant import delete_tenant, get_tenant_config
|
||||||
from Web.modules.database import settings
|
from Web.modules.database import settings
|
||||||
from pymongo import MongoClient
|
from pymongo import MongoClient
|
||||||
|
|
||||||
tenant_id = sys.argv[1].lower()
|
tenant_id = sys.argv[1]
|
||||||
# Den genauen Datenbanknamen rekonstruieren (wie beim 'add' Befehl)
|
tenant_cfg = get_tenant_config(tenant_id)
|
||||||
|
port = tenant_cfg.get('port')
|
||||||
|
|
||||||
|
# Datenbanknamen exakt rekonstruieren
|
||||||
sanitized = "".join(c for c in tenant_id if c.isalnum() or c == "_")
|
sanitized = "".join(c for c in tenant_id if c.isalnum() or c == "_")
|
||||||
db_name = f"inventar_{sanitized}"
|
db_name = f"inventar_{sanitized}"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Direkte Verbindung zur Datenbank herstellen und komplett löschen
|
|
||||||
client = MongoClient(settings.MONGODB_HOST, int(settings.MONGODB_PORT))
|
client = MongoClient(settings.MONGODB_HOST, int(settings.MONGODB_PORT))
|
||||||
client.drop_database(db_name)
|
client.drop_database(db_name)
|
||||||
print(f"MongoDB database '{db_name}' dropped successfully.")
|
print(f"MongoDB database '{db_name}' dropped successfully.", file=sys.stderr)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Warning: Could not drop database '{db_name}': {e}", file=sys.stderr)
|
print(f"Warning: Could not drop database '{db_name}': {e}", file=sys.stderr)
|
||||||
|
|
||||||
# Fallback: Die interne Funktion trotzdem aufrufen, falls sie noch Ordner/Dateien bereinigt
|
if not delete_tenant(tenant_id):
|
||||||
try:
|
print(f'Error: failed to delete tenant {tenant_id}', file=sys.stderr)
|
||||||
from tenant import delete_tenant
|
sys.exit(1)
|
||||||
delete_tenant(sys.argv[1])
|
|
||||||
except Exception:
|
if port is not None:
|
||||||
pass
|
print(port)
|
||||||
PY
|
PY
|
||||||
|
)"
|
||||||
echo "Tenant '$TENANT_ID' database and config removed."
|
echo "Tenant '$TENANT_ID' database and config removed."
|
||||||
else
|
else
|
||||||
echo "Warning: Application container not running. Tenant database may still exist in MongoDB."
|
echo "Warning: Application container not running. Tenant database may still exist in MongoDB."
|
||||||
|
if port_to_remove="$(remove_tenant_port "$TENANT_ID" 2>/dev/null)"; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
echo "Warning: tenant '$TENANT_ID' was not configured in config.json or could not be removed."
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -n "$port_to_remove" ]; then
|
if [ -n "$port_to_remove" ]; then
|
||||||
@@ -813,7 +716,6 @@ PY
|
|||||||
if [ -n "$(docker ps -qf 'name=app' | head -n 1)" ]; then
|
if [ -n "$(docker ps -qf 'name=app' | head -n 1)" ]; then
|
||||||
restart_app_container
|
restart_app_container
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -n "$port_to_remove" ]; then
|
if [ -n "$port_to_remove" ]; then
|
||||||
echo "Removed tenant '$TENANT_ID' and cleaned runtime port $port_to_remove."
|
echo "Removed tenant '$TENANT_ID' and cleaned runtime port $port_to_remove."
|
||||||
else
|
else
|
||||||
@@ -822,20 +724,19 @@ PY
|
|||||||
;;
|
;;
|
||||||
|
|
||||||
restart-tenant)
|
restart-tenant)
|
||||||
|
TENANT_ID="${2:-}"
|
||||||
if [ -z "$TENANT_ID" ]; then
|
if [ -z "$TENANT_ID" ]; then
|
||||||
echo "Error: Please provide a tenant_id."
|
echo "Error: Please provide a tenant_id."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "Restarting tenant '$TENANT_ID' (clearing session/cache)..."
|
echo "Restarting tenant '$TENANT_ID' (clearing session/cache)..."
|
||||||
# To restart a single tenant without restarting the global python processes,
|
|
||||||
# we can invalidate their cache or drop their sessions collection to sign everyone out
|
|
||||||
APP_CONTAINER=$(docker ps -qf "name=app" | head -n 1)
|
APP_CONTAINER=$(docker ps -qf "name=app" | head -n 1)
|
||||||
if [ -n "$APP_CONTAINER" ]; then
|
if [ -n "$APP_CONTAINER" ]; then
|
||||||
docker exec $APP_CONTAINER python3 -c "
|
docker exec $APP_CONTAINER python3 -c "
|
||||||
import sys; sys.path.insert(0, '/app'); sys.path.insert(0, '/app/Web'); from Web.modules.database import settings; from pymongo import MongoClient
|
import sys; sys.path.insert(0, '/app'); sys.path.insert(0, '/app/Web'); from Web.modules.database import settings; from pymongo import MongoClient
|
||||||
client = MongoClient(settings.MONGODB_HOST, int(settings.MONGODB_PORT))
|
client = MongoClient(settings.MONGODB_HOST, int(settings.MONGODB_PORT))
|
||||||
db = client[f'{settings.MONGODB_DB}_{sys.argv[1]}']
|
db = client[f'{settings.MONGODB_DB}_{sys.argv[1]}']
|
||||||
db.sessions.drop() # Force sign-out / session clear
|
db.sessions.drop()
|
||||||
print(f'Tenant {sys.argv[1]} session cache cleared. Tenant restarted.')
|
print(f'Tenant {sys.argv[1]} session cache cleared. Tenant restarted.')
|
||||||
" "$TENANT_ID"
|
" "$TENANT_ID"
|
||||||
echo "Tenant '$TENANT_ID' has been refreshed without impacting others."
|
echo "Tenant '$TENANT_ID' has been refreshed without impacting others."
|
||||||
@@ -898,12 +799,12 @@ PY
|
|||||||
;;
|
;;
|
||||||
|
|
||||||
module)
|
module)
|
||||||
|
TENANT_ID="${2:-}"
|
||||||
if [ -z "$TENANT_ID" ] || [ -z "${3:-}" ]; then
|
if [ -z "$TENANT_ID" ] || [ -z "${3:-}" ]; then
|
||||||
echo "Error: Usage: manage-tenant.sh module <tenant_id> <module_name>=<on|off> [...]"
|
echo "Error: Usage: manage-tenant.sh module <tenant_id> <module_name>=<on|off> [...]"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Pass all remaining arguments to python script
|
|
||||||
shift 2
|
shift 2
|
||||||
|
|
||||||
if python3 - "$CONFIG_FILE" "$TENANT_ID" "$@" <<'PY'
|
if python3 - "$CONFIG_FILE" "$TENANT_ID" "$@" <<'PY'
|
||||||
@@ -979,4 +880,4 @@ PY
|
|||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
exit 0
|
exit 0
|
||||||
Reference in New Issue
Block a user