Compare commits
1 Commits
4b20444a5e
...
c1288b5fa9
| Author | SHA1 | Date | |
|---|---|---|---|
| c1288b5fa9 |
@@ -15,9 +15,10 @@ RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copy application code
|
||||
COPY . .
|
||||
RUN chmod +x entrypoint.sh
|
||||
|
||||
# Expose port
|
||||
EXPOSE 8000
|
||||
|
||||
# Run the application
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
# Wait for wizard config, then start uvicorn
|
||||
ENTRYPOINT ["./entrypoint.sh"]
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import os
|
||||
import json
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
@@ -5,6 +7,30 @@ from pydantic_settings import BaseSettings
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def _resolve_db_url(env_url: str) -> str:
|
||||
"""Read DB config from wizard config volume if available, else use env."""
|
||||
config_dir = os.getenv("CONFIG_DIR", "/config")
|
||||
config_file = os.getenv("CONFIG_FILE", "harborforge.json")
|
||||
config_path = os.path.join(config_dir, config_file)
|
||||
|
||||
if os.path.exists(config_path):
|
||||
try:
|
||||
with open(config_path, "r") as f:
|
||||
cfg = json.load(f)
|
||||
db_cfg = cfg.get("database")
|
||||
if db_cfg:
|
||||
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}"
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return env_url
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
DATABASE_URL: str = "mysql+pymysql://harborforge:harborforge_pass@mysql:3306/harborforge"
|
||||
SECRET_KEY: str = "change-me-in-production"
|
||||
@@ -18,7 +44,9 @@ class Settings(BaseSettings):
|
||||
|
||||
settings = Settings()
|
||||
|
||||
engine = create_engine(settings.DATABASE_URL, pool_pre_ping=True)
|
||||
# Resolve DB URL: wizard config volume > env > default
|
||||
_db_url = _resolve_db_url(settings.DATABASE_URL)
|
||||
engine = create_engine(_db_url, pool_pre_ping=True)
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
"""
|
||||
HarborForge initialization via AbstractWizard.
|
||||
HarborForge initialization from AbstractWizard config volume.
|
||||
|
||||
On startup, reads config from AbstractWizard and creates:
|
||||
- Admin user (if not exists)
|
||||
- Default project (if configured)
|
||||
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
|
||||
import httpx
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.models import models
|
||||
@@ -15,40 +14,45 @@ 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")
|
||||
CONFIG_DIR = os.getenv("CONFIG_DIR", "/config")
|
||||
CONFIG_FILE = os.getenv("CONFIG_FILE", "harborforge.json")
|
||||
|
||||
|
||||
def fetch_wizard_config() -> dict | None:
|
||||
"""Fetch initialization config from AbstractWizard."""
|
||||
url = f"{WIZARD_URL}/api/v1/config/{WIZARD_CONFIG}"
|
||||
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:
|
||||
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
|
||||
with open(config_path, "r") as f:
|
||||
return json.load(f)
|
||||
except Exception as e:
|
||||
logger.warning("Failed to fetch wizard config: %s", e)
|
||||
logger.warning("Failed to read config %s: %s", config_path, e)
|
||||
return None
|
||||
|
||||
|
||||
def init_admin_user(db: Session, admin_cfg: dict) -> 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
|
||||
return existing
|
||||
|
||||
password = admin_cfg.get("password", "changeme")
|
||||
user = models.User(
|
||||
@@ -63,11 +67,14 @@ def init_admin_user(db: Session, admin_cfg: dict) -> None:
|
||||
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, admin_user_id: int) -> None:
|
||||
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", "Default")
|
||||
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)
|
||||
@@ -76,7 +83,7 @@ def init_default_project(db: Session, project_cfg: dict, admin_user_id: int) ->
|
||||
project = models.Project(
|
||||
name=name,
|
||||
description=project_cfg.get("description", ""),
|
||||
owner_id=admin_user_id,
|
||||
owner_id=owner_id,
|
||||
)
|
||||
db.add(project)
|
||||
db.commit()
|
||||
@@ -85,23 +92,22 @@ def init_default_project(db: Session, project_cfg: dict, admin_user_id: int) ->
|
||||
|
||||
|
||||
def run_init(db: Session) -> None:
|
||||
"""Main initialization entry point."""
|
||||
config = fetch_wizard_config()
|
||||
"""Main initialization entry point. Reads config from shared volume."""
|
||||
config = load_config()
|
||||
if not config:
|
||||
return
|
||||
|
||||
logger.info("Running HarborForge initialization from AbstractWizard")
|
||||
logger.info("Running HarborForge initialization from wizard config")
|
||||
|
||||
# Admin user
|
||||
admin_cfg = config.get("admin")
|
||||
admin_user = None
|
||||
if admin_cfg:
|
||||
init_admin_user(db, admin_cfg)
|
||||
admin_user = 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)
|
||||
if project_cfg and admin_user:
|
||||
init_default_project(db, project_cfg, admin_user.id)
|
||||
|
||||
logger.info("Initialization complete")
|
||||
|
||||
19
entrypoint.sh
Normal file
19
entrypoint.sh
Normal file
@@ -0,0 +1,19 @@
|
||||
#!/bin/sh
|
||||
# Wait for wizard config before starting uvicorn
|
||||
CONFIG_DIR="${CONFIG_DIR:-/config}"
|
||||
CONFIG_FILE="${CONFIG_FILE:-harborforge.json}"
|
||||
CONFIG_PATH="$CONFIG_DIR/$CONFIG_FILE"
|
||||
|
||||
echo "HarborForge Backend - waiting for config..."
|
||||
echo " Config path: $CONFIG_PATH"
|
||||
|
||||
while true; do
|
||||
if [ -f "$CONFIG_PATH" ]; then
|
||||
echo " Config found! Starting backend..."
|
||||
break
|
||||
fi
|
||||
echo " Config not ready, waiting 5s... (run setup wizard via SSH tunnel)"
|
||||
sleep 5
|
||||
done
|
||||
|
||||
exec uvicorn app.main:app --host 0.0.0.0 --port 8000
|
||||
Reference in New Issue
Block a user