Compare commits

...

1 Commits

Author SHA1 Message Date
zhi
4b20444a5e feat: AbstractWizard integration for initialization
- Add init_wizard.py: fetch config from AbstractWizard on startup
- Create admin user if not exists (from wizard config)
- Create default project if configured
- Graceful fallback when wizard is unavailable
2026-03-06 13:15:47 +00:00
2 changed files with 116 additions and 1 deletions

107
app/init_wizard.py Normal file
View File

@@ -0,0 +1,107 @@
"""
HarborForge initialization via AbstractWizard.
On startup, reads config from AbstractWizard and creates:
- Admin user (if not exists)
- Default project (if configured)
"""
import os
import logging
import httpx
from sqlalchemy.orm import Session
from app.models import models
from app.api.deps import get_password_hash
logger = logging.getLogger("harborforge.init")
WIZARD_URL = os.getenv("WIZARD_URL", "http://wizard:8080")
WIZARD_CONFIG = os.getenv("WIZARD_CONFIG", "harborforge.json")
def fetch_wizard_config() -> dict | None:
"""Fetch initialization config from AbstractWizard."""
url = f"{WIZARD_URL}/api/v1/config/{WIZARD_CONFIG}"
try:
resp = httpx.get(url, timeout=10)
if resp.status_code == 200:
data = resp.json()
# AbstractWizard wraps in {"data": ...}
return data.get("data", data)
elif resp.status_code == 404:
logger.info("No wizard config found at %s, skipping initialization", url)
return None
else:
logger.warning("Wizard returned %d: %s", resp.status_code, resp.text)
return None
except httpx.ConnectError:
logger.info("AbstractWizard not available at %s, skipping", WIZARD_URL)
return None
except Exception as e:
logger.warning("Failed to fetch wizard config: %s", e)
return None
def init_admin_user(db: Session, admin_cfg: dict) -> 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
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)
def init_default_project(db: Session, project_cfg: dict, admin_user_id: int) -> None:
"""Create default project if configured and not exists."""
name = project_cfg.get("name", "Default")
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=admin_user_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."""
config = fetch_wizard_config()
if not config:
return
logger.info("Running HarborForge initialization from AbstractWizard")
# Admin user
admin_cfg = config.get("admin")
if admin_cfg:
init_admin_user(db, admin_cfg)
# Default project
project_cfg = config.get("default_project")
if project_cfg:
admin = db.query(models.User).filter(models.User.is_admin == True).first()
if admin:
init_default_project(db, project_cfg, admin.id)
logger.info("Initialization complete")

View File

@@ -46,6 +46,14 @@ app.include_router(misc_router)
# Run database migration on startup
@app.on_event("startup")
def startup():
from app.core.config import Base, engine
from app.core.config import Base, engine, SessionLocal
from app.models import webhook, apikey, activity, milestone, notification, worklog
Base.metadata.create_all(bind=engine)
# Initialize from AbstractWizard (admin user, default project, etc.)
from app.init_wizard import run_init
db = SessionLocal()
try:
run_init(db)
finally:
db.close()