Bootstrap initial admin via env and add compose profiles

This commit is contained in:
2026-03-17 19:16:25 +03:00
parent 3b22133d7a
commit 78f1e6803f
4 changed files with 88 additions and 9 deletions

View File

@@ -12,6 +12,7 @@
# @SIDE_EFFECT: Starts background scheduler and binds network ports for HTTP/WS traffic.
# @DATA_CONTRACT: [HTTP Request | WS Message] -> [HTTP Response | JSON Log Stream]
import os
from pathlib import Path
# project_root is used for static files mounting
@@ -28,6 +29,9 @@ from .dependencies import get_task_manager, get_scheduler_service
from .core.encryption_key import ensure_encryption_key
from .core.utils.network import NetworkError
from .core.logger import logger, belief_scope
from .core.database import AuthSessionLocal
from .core.auth.security import get_password_hash
from .models.auth import User, Role
from .api.routes import plugins, tasks, settings, environments, mappings, migration, connections, git, storage, admin, llm, dashboards, datasets, reports, assistant, clean_release, clean_release_v2, profile, health, dataset_review
from .api import auth
@@ -42,6 +46,54 @@ app = FastAPI(
)
# [/DEF:App:Global]
# [DEF:ensure_initial_admin_user:Function]
# @COMPLEXITY: 3
# @PURPOSE: Ensures initial admin user exists when bootstrap env flags are enabled.
def ensure_initial_admin_user() -> None:
raw_flag = os.getenv("INITIAL_ADMIN_CREATE", "false").strip().lower()
if raw_flag not in {"1", "true", "yes", "on"}:
return
username = os.getenv("INITIAL_ADMIN_USERNAME", "").strip()
password = os.getenv("INITIAL_ADMIN_PASSWORD", "").strip()
if not username or not password:
logger.warning(
"INITIAL_ADMIN_CREATE is enabled but INITIAL_ADMIN_USERNAME/INITIAL_ADMIN_PASSWORD is missing; skipping bootstrap."
)
return
db = AuthSessionLocal()
try:
admin_role = db.query(Role).filter(Role.name == "Admin").first()
if not admin_role:
admin_role = Role(name="Admin", description="System Administrator")
db.add(admin_role)
db.commit()
db.refresh(admin_role)
existing_user = db.query(User).filter(User.username == username).first()
if existing_user:
logger.info("Initial admin bootstrap skipped: user '%s' already exists.", username)
return
new_user = User(
username=username,
email=None,
password_hash=get_password_hash(password),
auth_source="LOCAL",
is_active=True,
)
new_user.roles.append(admin_role)
db.add(new_user)
db.commit()
logger.info("Initial admin user '%s' created from environment bootstrap.", username)
except Exception as exc:
db.rollback()
logger.error("Failed to bootstrap initial admin user: %s", exc)
raise
finally:
db.close()
# [/DEF:ensure_initial_admin_user:Function]
# [DEF:startup_event:Function]
# @COMPLEXITY: 3
# @PURPOSE: Handles application startup tasks, such as starting the scheduler.
@@ -53,6 +105,7 @@ app = FastAPI(
async def startup_event():
with belief_scope("startup_event"):
ensure_encryption_key()
ensure_initial_admin_user()
scheduler = get_scheduler_service()
scheduler.start()
# [/DEF:startup_event:Function]

View File

@@ -7,6 +7,23 @@ cd "$SCRIPT_DIR"
BACKEND_ENV_FILE="$SCRIPT_DIR/backend/.env"
PROFILE="${1:-current}"
case "$PROFILE" in
master)
PROFILE_ENV_FILE="$SCRIPT_DIR/.env.master"
PROJECT_NAME="ss-tools-master"
;;
current)
PROFILE_ENV_FILE="$SCRIPT_DIR/.env.current"
PROJECT_NAME="ss-tools-current"
;;
*)
echo "Error: unknown profile '$PROFILE'. Use one of: master, current."
exit 1
;;
esac
if ! command -v docker >/dev/null 2>&1; then
echo "Error: docker is not installed or not in PATH."
exit 1
@@ -80,11 +97,23 @@ PY
ensure_backend_encryption_key
COMPOSE_ARGS=(-p "$PROJECT_NAME")
if [[ -f "$PROFILE_ENV_FILE" ]]; then
COMPOSE_ARGS+=(--env-file "$PROFILE_ENV_FILE")
else
echo "[build] Warning: profile env file not found at $PROFILE_ENV_FILE, using compose defaults."
fi
echo "[build] Profile: $PROFILE (project: $PROJECT_NAME)"
if [[ -f "$PROFILE_ENV_FILE" ]]; then
echo "[build] Env file: $PROFILE_ENV_FILE"
fi
echo "[1/2] Building project images..."
"${COMPOSE_CMD[@]}" build
"${COMPOSE_CMD[@]}" "${COMPOSE_ARGS[@]}" build
echo "[2/2] Starting Docker services..."
"${COMPOSE_CMD[@]}" up -d
"${COMPOSE_CMD[@]}" "${COMPOSE_ARGS[@]}" up -d
echo "Done. Services are running."
echo "Use '${COMPOSE_CMD[*]} ps' to check status and '${COMPOSE_CMD[*]} logs -f' to stream logs."
echo "Use '${COMPOSE_CMD[*]} ${COMPOSE_ARGS[*]} ps' to check status and '${COMPOSE_CMD[*]} ${COMPOSE_ARGS[*]} logs -f' to stream logs."

View File

@@ -2,7 +2,6 @@ services:
db:
image: ${POSTGRES_IMAGE:?Set POSTGRES_IMAGE in .env.enterprise-clean}
pull_policy: never
container_name: ss_tools_db
restart: unless-stopped
environment:
POSTGRES_DB: ${POSTGRES_DB:-ss_tools}
@@ -21,7 +20,6 @@ services:
backend:
image: ${BACKEND_IMAGE:?Set BACKEND_IMAGE in .env.enterprise-clean}
pull_policy: never
container_name: ss_tools_backend
restart: unless-stopped
depends_on:
db:
@@ -50,7 +48,6 @@ services:
frontend:
image: ${FRONTEND_IMAGE:?Set FRONTEND_IMAGE in .env.enterprise-clean}
pull_policy: never
container_name: ss_tools_frontend
restart: unless-stopped
depends_on:
- backend

View File

@@ -1,7 +1,6 @@
services:
db:
image: ${POSTGRES_IMAGE:-postgres:16-alpine}
container_name: ss_tools_db
restart: unless-stopped
environment:
POSTGRES_DB: ss_tools
@@ -21,7 +20,6 @@ services:
build:
context: .
dockerfile: docker/backend.Dockerfile
container_name: ss_tools_backend
restart: unless-stopped
env_file:
- ./backend/.env
@@ -34,6 +32,9 @@ services:
TASKS_DATABASE_URL: postgresql+psycopg2://postgres:postgres@db:5432/ss_tools
AUTH_DATABASE_URL: postgresql+psycopg2://postgres:postgres@db:5432/ss_tools
BACKEND_PORT: 8000
INITIAL_ADMIN_CREATE: ${INITIAL_ADMIN_CREATE:-false}
INITIAL_ADMIN_USERNAME: ${INITIAL_ADMIN_USERNAME:-admin}
INITIAL_ADMIN_PASSWORD: ${INITIAL_ADMIN_PASSWORD:-}
ports:
- "${BACKEND_HOST_PORT:-8001}:8000"
volumes:
@@ -46,7 +47,6 @@ services:
build:
context: .
dockerfile: docker/frontend.Dockerfile
container_name: ss_tools_frontend
restart: unless-stopped
depends_on:
- backend