Some more fixes of the manage-tenants.sh file

This commit is contained in:
2026-06-24 15:55:21 +02:00
parent 717e3ce15e
commit 69ac6eca63
+155 -254
View File
@@ -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