""" 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, owner_name: str = "") -> 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_name=project_cfg.get("owner") or owner_name or "", 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, admin_user.username) logger.info("Initialization complete")