575 lines
19 KiB
Bash
Executable File
575 lines
19 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
IFS=$'\n\t'
|
|
# Script to manage multitenant deployment
|
|
# Allows adding, removing, and restarting tenants without downtime for others
|
|
|
|
if [ ! -f "docker-compose-multitenant.yml" ]; then
|
|
echo "Error: docker-compose-multitenant.yml not found."
|
|
exit 1
|
|
fi
|
|
|
|
CONFIG_FILE="$PWD/config.json"
|
|
|
|
show_help() {
|
|
echo "Usage: ./manage-tenant.sh [COMMAND] [OPTIONS]"
|
|
echo ""
|
|
echo "Commands:"
|
|
echo " add <tenant_id> [port] Add a new tenant (initializes database)"
|
|
echo " remove <tenant_id> Remove a tenant completely (deletes data!)"
|
|
echo " restart-tenant <id> 'Restart' a single tenant (clears cache/sessions)"
|
|
echo " restart-all Restart all application containers (zero-downtime reload)"
|
|
echo " list List active tenants"
|
|
echo " module <tenant_id> <module>=<on|off>... Enable/disable modules for a tenant"
|
|
echo " -h, --help Show this help message"
|
|
echo ""
|
|
echo "Examples:"
|
|
echo " ./manage-tenant.sh add school_a 10001"
|
|
echo " ./manage-tenant.sh remove test_tenant"
|
|
echo " ./manage-tenant.sh module school_a inventory=off library=on"
|
|
echo " ./manage-tenant.sh restart-all"
|
|
echo " ./manage-tenant.sh -h"
|
|
exit 1
|
|
}
|
|
|
|
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 = {}
|
|
for tid, conf in tenants.items():
|
|
if isinstance(conf, dict) and str(conf.get('port')) == port_str and tid != tenant_id:
|
|
print(f"Error: port {port_str} is already mapped to tenant {tid}", file=sys.stderr)
|
|
sys.exit(2)
|
|
existing = tenants.get(tenant_id)
|
|
if existing is None or not isinstance(existing, dict):
|
|
existing = {}
|
|
existing['port'] = int(port_str)
|
|
tenants[tenant_id] = 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
|
|
}
|
|
|
|
update_runtime_ports() {
|
|
local new_port="$1"
|
|
local env_file="$PWD/.docker-build.env"
|
|
local current_ports=""
|
|
local port_list=()
|
|
local unique_ports=()
|
|
local first_port=""
|
|
if [ -n "$new_port" ]; then
|
|
if [ -f "$env_file" ]; then
|
|
current_ports="$(awk -F= '/^INVENTAR_HTTP_PORTS=/{print $2; exit}' "$env_file" | tr -d ' ' || true)"
|
|
if [ -z "$current_ports" ]; then
|
|
current_ports="$(awk -F= '/^INVENTAR_HTTP_PORT=/{print $2; exit}' "$env_file" | tr -d ' ' || true)"
|
|
fi
|
|
fi
|
|
|
|
if [ -n "$current_ports" ]; then
|
|
IFS=',' read -r -a port_list <<<"${current_ports// /,}"
|
|
fi
|
|
port_list+=("$new_port")
|
|
|
|
for port in "${port_list[@]}"; do
|
|
if [ -n "$port" ] && ! printf '%s\n' "${unique_ports[@]}" | grep -qx "$port"; then
|
|
unique_ports+=("$port")
|
|
fi
|
|
done
|
|
|
|
if [ ${#unique_ports[@]} -eq 0 ]; then
|
|
unique_ports=("$new_port")
|
|
fi
|
|
|
|
first_port="${unique_ports[0]}"
|
|
local ports_csv
|
|
ports_csv="$(IFS=,; echo "${unique_ports[*]}")"
|
|
|
|
if [ ! -f "$env_file" ]; then
|
|
cat > "$env_file" <<EOF
|
|
NUITKA_BUILD=0
|
|
INVENTAR_HTTP_PORT=$first_port
|
|
INVENTAR_HTTP_PORTS=$ports_csv
|
|
EOF
|
|
else
|
|
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"
|
|
else
|
|
printf '\nINVENTAR_HTTP_PORTS=%s\n' "$ports_csv" >> "$env_file"
|
|
fi
|
|
if grep -q '^INVENTAR_HTTP_PORT=' "$env_file" 2>/dev/null; then
|
|
sed -i "s|^INVENTAR_HTTP_PORT=.*|INVENTAR_HTTP_PORT=$first_port|" "$env_file"
|
|
else
|
|
printf '\nINVENTAR_HTTP_PORT=%s\n' "$first_port" >> "$env_file"
|
|
fi
|
|
fi
|
|
|
|
echo "Updated runtime env ports: $ports_csv"
|
|
fi
|
|
}
|
|
|
|
sync_tenant_port_map() {
|
|
local config_path="$CONFIG_FILE"
|
|
local env_file="$PWD/.docker-build.env"
|
|
local tenant_port_map
|
|
|
|
tenant_port_map="$(python3 - <<'PY' "$config_path"
|
|
import json, os, sys
|
|
path = sys.argv[1]
|
|
if not os.path.isfile(path):
|
|
sys.exit(1)
|
|
with open(path, 'r', encoding='utf-8') as f:
|
|
cfg = json.load(f)
|
|
tenants = cfg.get('tenants', {})
|
|
if not isinstance(tenants, dict):
|
|
sys.exit(0)
|
|
entries = []
|
|
for tenant_id, conf in sorted(tenants.items()):
|
|
if isinstance(conf, dict):
|
|
port = conf.get('port')
|
|
if isinstance(port, (int, float)) or (isinstance(port, str) and str(port).strip().isdigit()):
|
|
entries.append(f"{int(port)}={tenant_id}")
|
|
print(','.join(entries))
|
|
PY
|
|
)"
|
|
|
|
if [ -z "$tenant_port_map" ]; then
|
|
if [ -f "$env_file" ]; then
|
|
sed -i '/^INVENTAR_TENANT_PORT_MAP=/d' "$env_file"
|
|
fi
|
|
return 0
|
|
fi
|
|
|
|
if [ ! -f "$env_file" ]; then
|
|
cat > "$env_file" <<EOF
|
|
NUITKA_BUILD=0
|
|
INVENTAR_TENANT_PORT_MAP=$tenant_port_map
|
|
EOF
|
|
return 0
|
|
fi
|
|
|
|
if grep -q '^INVENTAR_TENANT_PORT_MAP=' "$env_file" 2>/dev/null; then
|
|
sed -i "s|^INVENTAR_TENANT_PORT_MAP=.*|INVENTAR_TENANT_PORT_MAP=$tenant_port_map|" "$env_file"
|
|
else
|
|
printf '\nINVENTAR_TENANT_PORT_MAP=%s\n' "$tenant_port_map" >> "$env_file"
|
|
fi
|
|
}
|
|
|
|
restart_app_container() {
|
|
local env_file="$PWD/.docker-build.env"
|
|
local compose_args=()
|
|
|
|
# If HOST_WORKDIR is set (called from container), use absolute paths so docker daemon resolves them correctly
|
|
if [ -n "$HOST_WORKDIR" ]; then
|
|
compose_args+=( -f "$(readlink -f "$HOST_WORKDIR/docker-compose-multitenant.yml")" )
|
|
if [ -f "$HOST_WORKDIR/.docker-compose.runtime.override.yml" ]; then
|
|
compose_args+=( -f "$(readlink -f "$HOST_WORKDIR/.docker-compose.runtime.override.yml")" )
|
|
fi
|
|
if [ -f "$HOST_WORKDIR/.docker-build.env" ]; then
|
|
compose_args+=( --env-file "$(readlink -f "$HOST_WORKDIR/.docker-build.env")" )
|
|
fi
|
|
else
|
|
# Normal case: called directly from host
|
|
compose_args+=( -f "$PWD/docker-compose-multitenant.yml" )
|
|
if [ -f "$PWD/.docker-compose.runtime.override.yml" ]; then
|
|
compose_args+=( -f "$PWD/.docker-compose.runtime.override.yml" )
|
|
fi
|
|
if [ -f "$env_file" ]; then
|
|
compose_args+=( --env-file "$env_file" )
|
|
fi
|
|
fi
|
|
|
|
# Pass along COMPOSE_PROJECT_NAME if set so the internal docker-compose sees it
|
|
if [ -n "$COMPOSE_PROJECT_NAME" ]; then
|
|
compose_args=( -p "$COMPOSE_PROJECT_NAME" "${compose_args[@]}" )
|
|
fi
|
|
|
|
echo "Restarting app container to apply tenant configuration changes..."
|
|
docker compose "${compose_args[@]}" up -d --no-build --force-recreate app
|
|
}
|
|
|
|
remove_tenant_port() {
|
|
local tenant_id="$1"
|
|
local config_path="$CONFIG_FILE"
|
|
local port
|
|
|
|
port="$(python3 - "$config_path" "$tenant_id" <<'PY'
|
|
import json, sys, os
|
|
path, tenant_id = sys.argv[1], sys.argv[2]
|
|
if not os.path.isfile(path):
|
|
sys.exit(1)
|
|
with open(path, 'r', encoding='utf-8') as f:
|
|
cfg = json.load(f)
|
|
tenants = cfg.get('tenants', {})
|
|
if not isinstance(tenants, dict) or tenant_id not in tenants or not isinstance(tenants[tenant_id], dict):
|
|
sys.exit(2)
|
|
removed = tenants.pop(tenant_id, None)
|
|
cfg['tenants'] = tenants
|
|
with open(path, 'w', encoding='utf-8') as f:
|
|
json.dump(cfg, f, indent=4, ensure_ascii=False)
|
|
if removed is not None:
|
|
port = removed.get('port')
|
|
if port is not None:
|
|
print(port)
|
|
PY
|
|
)"
|
|
|
|
local status=$?
|
|
if [ $status -eq 1 ]; then
|
|
echo "Error: config file not found: $config_path"
|
|
return 1
|
|
fi
|
|
if [ $status -eq 2 ]; then
|
|
return 2
|
|
fi
|
|
|
|
if [ -n "$port" ]; then
|
|
echo "$port"
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
remove_runtime_port() {
|
|
local removed_port="$1"
|
|
local env_file="$PWD/.docker-build.env"
|
|
local current_ports=""
|
|
local port_list=()
|
|
local new_ports=()
|
|
local first_port=""
|
|
|
|
if [ ! -f "$env_file" ]; then
|
|
return 0
|
|
fi
|
|
|
|
current_ports="$(awk -F= '/^INVENTAR_HTTP_PORTS=/{print $2; exit}' "$env_file" | tr -d ' ' || true)"
|
|
if [ -z "$current_ports" ]; then
|
|
current_ports="$(awk -F= '/^INVENTAR_HTTP_PORT=/{print $2; exit}' "$env_file" | tr -d ' ' || true)"
|
|
fi
|
|
|
|
if [ -z "$current_ports" ]; then
|
|
return 0
|
|
fi
|
|
|
|
IFS=',' read -r -a port_list <<<"${current_ports// /,}"
|
|
for port in "${port_list[@]}"; do
|
|
if [ -n "$port" ] && [ "$port" != "$removed_port" ]; then
|
|
new_ports+=("$port")
|
|
fi
|
|
done
|
|
|
|
if [ ${#new_ports[@]} -eq 0 ]; then
|
|
sed -i '/^INVENTAR_HTTP_PORTS=/d;/^INVENTAR_HTTP_PORT=/d' "$env_file"
|
|
return 0
|
|
fi
|
|
|
|
first_port="${new_ports[0]}"
|
|
local ports_csv="$(IFS=,; echo "${new_ports[*]}")"
|
|
|
|
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"
|
|
else
|
|
printf '\nINVENTAR_HTTP_PORTS=%s\n' "$ports_csv" >> "$env_file"
|
|
fi
|
|
|
|
if grep -q '^INVENTAR_HTTP_PORT=' "$env_file" 2>/dev/null; then
|
|
sed -i "s|^INVENTAR_HTTP_PORT=.*|INVENTAR_HTTP_PORT=$first_port|" "$env_file"
|
|
else
|
|
printf '\nINVENTAR_HTTP_PORT=%s\n' "$first_port" >> "$env_file"
|
|
fi
|
|
}
|
|
|
|
if [ -z "${1:-}" ]; then
|
|
show_help
|
|
fi
|
|
|
|
COMMAND="$1"
|
|
TENANT_ID="${2:-}"
|
|
|
|
case "$COMMAND" in
|
|
-h|--help)
|
|
show_help
|
|
;;
|
|
add)
|
|
if [ -z "$TENANT_ID" ]; then
|
|
echo "Error: Please provide a tenant_id."
|
|
exit 1
|
|
fi
|
|
|
|
PORT_ARG="$3"
|
|
if [ -n "$PORT_ARG" ]; then
|
|
if ! printf '%s\n' "$PORT_ARG" | grep -qE '^[0-9]+$'; then
|
|
echo "Error: Port must be a numeric value."
|
|
exit 1
|
|
fi
|
|
register_tenant_port "$TENANT_ID" "$PORT_ARG"
|
|
update_runtime_ports "$PORT_ARG"
|
|
sync_tenant_port_map
|
|
if [ -n "$(docker ps -qf 'name=app' | head -n 1)" ]; then
|
|
restart_app_container
|
|
fi
|
|
fi
|
|
|
|
echo "Adding new tenant '$TENANT_ID'..."
|
|
# Initialize tenant database via Python inside container
|
|
echo "Initializing database for $TENANT_ID..."
|
|
APP_CONTAINER=$(docker ps -qf "name=app" | head -n 1)
|
|
if [ -n "$APP_CONTAINER" ]; then
|
|
docker exec $APP_CONTAINER python3 -c "
|
|
import sys, re; sys.path.insert(0, '/app/Web'); 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.sha512('admin123'.encode()).hexdigest()
|
|
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
|
|
;;
|
|
|
|
remove)
|
|
if [ -z "$TENANT_ID" ]; then
|
|
echo "Error: Please provide a tenant_id to remove."
|
|
exit 1
|
|
fi
|
|
echo -n "WARNING: Are you sure you want to permanently delete all data for tenant '$TENANT_ID'? (y/N) "
|
|
read confirm
|
|
if [ "$confirm" = "y" ] || [ "$confirm" = "Y" ]; then
|
|
echo "Removing tenant '$TENANT_ID'..."
|
|
APP_CONTAINER=$(docker ps -qf "name=app" | head -n 1)
|
|
if [ -n "$APP_CONTAINER" ]; then
|
|
docker exec $APP_CONTAINER python3 -c "
|
|
import sys; sys.path.insert(0, '/app/Web'); from tenant import TenantContext; import settings; from pymongo import MongoClient
|
|
ctx = TenantContext()
|
|
db_name = ctx._get_db_name(sys.argv[1])
|
|
client = MongoClient(settings.MONGODB_HOST, int(settings.MONGODB_PORT))
|
|
client.drop_database(db_name)
|
|
print(f'Database for tenant {sys.argv[1]} dropped.')
|
|
" "$TENANT_ID"
|
|
echo "Tenant '$TENANT_ID' database removed."
|
|
else
|
|
echo "Warning: Application container not running. Tenant database may still exist in MongoDB."
|
|
fi
|
|
|
|
port_to_remove=""
|
|
if port_to_remove="$(remove_tenant_port "$TENANT_ID" 2>/dev/null)"; then
|
|
if [ -n "$port_to_remove" ]; then
|
|
remove_runtime_port "$port_to_remove"
|
|
sync_tenant_port_map
|
|
if [ -n "$(docker ps -qf 'name=app' | head -n 1)" ]; then
|
|
restart_app_container
|
|
fi
|
|
echo "Removed tenant '$TENANT_ID' from config.json and cleaned runtime port $port_to_remove."
|
|
else
|
|
sync_tenant_port_map
|
|
if [ -n "$(docker ps -qf 'name=app' | head -n 1)" ]; then
|
|
restart_app_container
|
|
fi
|
|
echo "Removed tenant '$TENANT_ID' from config.json. No port mapping was present."
|
|
fi
|
|
else
|
|
echo "Warning: tenant '$TENANT_ID' was not configured in config.json or could not be removed."
|
|
fi
|
|
else
|
|
echo "Removal canceled."
|
|
fi
|
|
;;
|
|
|
|
restart-tenant)
|
|
if [ -z "$TENANT_ID" ]; then
|
|
echo "Error: Please provide a tenant_id."
|
|
exit 1
|
|
fi
|
|
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)
|
|
if [ -n "$APP_CONTAINER" ]; then
|
|
docker exec $APP_CONTAINER python3 -c "
|
|
import sys; sys.path.insert(0, '/app/Web'); import settings; from pymongo import MongoClient
|
|
client = MongoClient(settings.MONGODB_HOST, int(settings.MONGODB_PORT))
|
|
db = client[f'{settings.MONGODB_DB}_{sys.argv[1]}']
|
|
db.sessions.drop() # Force sign-out / session clear
|
|
print(f'Tenant {sys.argv[1]} session cache cleared. Tenant restarted.')
|
|
" "$TENANT_ID"
|
|
echo "Tenant '$TENANT_ID' has been refreshed without impacting others."
|
|
else
|
|
echo "Error: Application container not running."
|
|
fi
|
|
;;
|
|
|
|
restart-all)
|
|
echo "Restarting all application instances with zero-downtime rolling restart..."
|
|
docker restart $(docker ps -qf "name=app")
|
|
echo "Global restart complete."
|
|
;;
|
|
|
|
list)
|
|
echo "Listing configured tenants (config.json):"
|
|
if [ -f "$CONFIG_FILE" ]; then
|
|
python3 - "$CONFIG_FILE" <<'PY'
|
|
import json, sys
|
|
path = sys.argv[1]
|
|
with open(path, 'r', encoding='utf-8') as f:
|
|
cfg = json.load(f)
|
|
tenants = cfg.get('tenants', {})
|
|
if isinstance(tenants, dict) and tenants:
|
|
for tid, conf in tenants.items():
|
|
port = conf.get('port') if isinstance(conf, dict) else None
|
|
if port is not None:
|
|
print(f'- {tid} (port {port})')
|
|
else:
|
|
print(f'- {tid}')
|
|
else:
|
|
print(' (no tenants configured)')
|
|
PY
|
|
else
|
|
echo " (config.json not found)"
|
|
fi
|
|
|
|
echo ""
|
|
echo "Listing active tenant databases (MongoDB):"
|
|
APP_CONTAINER=$(docker ps -qf "name=app" | head -n 1)
|
|
if [ -n "$APP_CONTAINER" ]; then
|
|
docker exec -i "$APP_CONTAINER" python3 - <<'PY'
|
|
import sys
|
|
sys.path.insert(0, '/app/Web')
|
|
import settings
|
|
from pymongo import MongoClient
|
|
client = MongoClient(settings.MONGODB_HOST, int(settings.MONGODB_PORT))
|
|
prefix = 'inventar_'
|
|
dbs = [d for d in client.list_database_names() if d.startswith(prefix)]
|
|
if dbs:
|
|
for db in dbs:
|
|
print(f'- {db.replace(prefix, "")}')
|
|
else:
|
|
print(' (no tenant databases found)')
|
|
PY
|
|
else
|
|
echo "Error: Application container not running."
|
|
fi
|
|
;;
|
|
|
|
module)
|
|
if [ -z "$TENANT_ID" ] || [ -z "${3:-}" ]; then
|
|
echo "Error: Usage: manage-tenant.sh module <tenant_id> <module_name>=<on|off> [...]"
|
|
exit 1
|
|
fi
|
|
|
|
# Pass all remaining arguments to python script
|
|
shift 2
|
|
|
|
if python3 - "$CONFIG_FILE" "$TENANT_ID" "$@" <<'PY'
|
|
import json, sys, os
|
|
path = sys.argv[1]
|
|
tenant_id = sys.argv[2]
|
|
module_args = 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.setdefault('tenants', {})
|
|
tenant_cfg = tenants.setdefault(tenant_id, {})
|
|
if 'port' not in tenant_cfg:
|
|
print(f"Warning: Tenant {tenant_id} doesn't have a port mapping in config.json.", file=sys.stderr)
|
|
|
|
modules = tenant_cfg.setdefault('modules', {})
|
|
|
|
for arg in module_args:
|
|
if '=' not in arg:
|
|
print(f"Warning: Ignoring invalid argument format '{arg}'. Expected name=on|off.", file=sys.stderr)
|
|
continue
|
|
mod_name, state_str = arg.split('=', 1)
|
|
state = str(state_str).lower() in ('on', '1', 'true', 'yes')
|
|
module_cfg = modules.setdefault(mod_name, {})
|
|
module_cfg['enabled'] = state
|
|
print(f"Module '{mod_name}' set to '{'on' if state else 'off'}' for tenant '{tenant_id}'.")
|
|
|
|
with open(path, 'w', encoding='utf-8') as f:
|
|
json.dump(cfg, f, indent=4, ensure_ascii=False)
|
|
PY
|
|
then
|
|
echo "Module configurations updated successfully."
|
|
if [ -n "$(docker ps -qf 'name=app' | head -n 1)" ]; then
|
|
restart_app_container
|
|
fi
|
|
else
|
|
echo "Failed to update module configuration."
|
|
exit 1
|
|
fi
|
|
;;
|
|
|
|
*)
|
|
echo "Unknown command: $COMMAND"
|
|
show_help
|
|
;;
|
|
esac
|
|
|
|
exit 0
|