- entrypoint.sh: wait for config file before starting uvicorn - config.py: resolve DB URL from wizard config volume - init_wizard.py: read config from file instead of HTTP - Dockerfile: use entrypoint.sh
114 lines
3.6 KiB
Python
114 lines
3.6 KiB
Python
"""
|
|
HarborForge initialization from AbstractWizard config volume.
|
|
|
|
Reads config from shared volume (written by AbstractWizard).
|
|
On startup, creates admin user and default project if not exists.
|
|
"""
|
|
import os
|
|
import json
|
|
import logging
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.models import models
|
|
from app.api.deps import get_password_hash
|
|
|
|
logger = logging.getLogger("harborforge.init")
|
|
|
|
CONFIG_DIR = os.getenv("CONFIG_DIR", "/config")
|
|
CONFIG_FILE = os.getenv("CONFIG_FILE", "harborforge.json")
|
|
|
|
|
|
def load_config() -> dict | None:
|
|
"""Load initialization config from shared volume."""
|
|
config_path = os.path.join(CONFIG_DIR, CONFIG_FILE)
|
|
if not os.path.exists(config_path):
|
|
logger.info("No config file at %s, skipping initialization", config_path)
|
|
return None
|
|
try:
|
|
with open(config_path, "r") as f:
|
|
return json.load(f)
|
|
except Exception as e:
|
|
logger.warning("Failed to read config %s: %s", config_path, e)
|
|
return None
|
|
|
|
|
|
def get_db_url(config: dict) -> str | None:
|
|
"""Build DATABASE_URL from wizard config, or fall back to env."""
|
|
db_cfg = config.get("database")
|
|
if not db_cfg:
|
|
return os.getenv("DATABASE_URL")
|
|
|
|
host = db_cfg.get("host", "mysql")
|
|
port = db_cfg.get("port", 3306)
|
|
user = db_cfg.get("user", "harborforge")
|
|
password = db_cfg.get("password", "harborforge_pass")
|
|
database = db_cfg.get("database", "harborforge")
|
|
return f"mysql+pymysql://{user}:{password}@{host}:{port}/{database}"
|
|
|
|
|
|
def init_admin_user(db: Session, admin_cfg: dict) -> models.User | None:
|
|
"""Create admin user if not exists."""
|
|
username = admin_cfg.get("username", "admin")
|
|
existing = db.query(models.User).filter(models.User.username == username).first()
|
|
if existing:
|
|
logger.info("Admin user '%s' already exists (id=%d), skipping", username, existing.id)
|
|
return existing
|
|
|
|
password = admin_cfg.get("password", "changeme")
|
|
user = models.User(
|
|
username=username,
|
|
email=admin_cfg.get("email", f"{username}@harborforge.local"),
|
|
full_name=admin_cfg.get("full_name", "Admin"),
|
|
hashed_password=get_password_hash(password),
|
|
is_admin=True,
|
|
is_active=True,
|
|
)
|
|
db.add(user)
|
|
db.commit()
|
|
db.refresh(user)
|
|
logger.info("Created admin user '%s' (id=%d)", username, user.id)
|
|
return user
|
|
|
|
|
|
def init_default_project(db: Session, project_cfg: dict, owner_id: int) -> None:
|
|
"""Create default project if configured and not exists."""
|
|
name = project_cfg.get("name")
|
|
if not name:
|
|
return
|
|
existing = db.query(models.Project).filter(models.Project.name == name).first()
|
|
if existing:
|
|
logger.info("Project '%s' already exists (id=%d), skipping", name, existing.id)
|
|
return
|
|
|
|
project = models.Project(
|
|
name=name,
|
|
description=project_cfg.get("description", ""),
|
|
owner_id=owner_id,
|
|
)
|
|
db.add(project)
|
|
db.commit()
|
|
db.refresh(project)
|
|
logger.info("Created default project '%s' (id=%d)", name, project.id)
|
|
|
|
|
|
def run_init(db: Session) -> None:
|
|
"""Main initialization entry point. Reads config from shared volume."""
|
|
config = load_config()
|
|
if not config:
|
|
return
|
|
|
|
logger.info("Running HarborForge initialization from wizard config")
|
|
|
|
# Admin user
|
|
admin_cfg = config.get("admin")
|
|
admin_user = None
|
|
if admin_cfg:
|
|
admin_user = init_admin_user(db, admin_cfg)
|
|
|
|
# Default project
|
|
project_cfg = config.get("default_project")
|
|
if project_cfg and admin_user:
|
|
init_default_project(db, project_cfg, admin_user.id)
|
|
|
|
logger.info("Initialization complete")
|