b9684b0107
- Exit with error code 1 if init_database() returns False - Update health check to verify database connectivity - Prevents confusing 'table does not exist' errors later
153 lines
5.0 KiB
Python
153 lines
5.0 KiB
Python
import logging
|
|
import os
|
|
|
|
from fastapi import FastAPI
|
|
from fastapi.staticfiles import StaticFiles
|
|
from sqlalchemy import select, text
|
|
|
|
from src.api.auth import router as auth_router
|
|
from src.api.git_repositories import router as git_repositories_router
|
|
from src.api.projects import router as projects_router
|
|
from src.api.ssh_keys import router as ssh_keys_router
|
|
from src.api.tool_types import router as tool_types_router
|
|
from src.api.user_config import router as user_config_router
|
|
from src.api.users import router as users_router
|
|
from src.database import SessionLocal, init_database
|
|
from src.logging_config import (
|
|
ExceptionLoggingMiddleware,
|
|
RequestLoggingMiddleware,
|
|
configure_logging,
|
|
)
|
|
from src.models.tool_type import ToolType
|
|
|
|
# Configure logging early
|
|
log_level = os.getenv("LOG_LEVEL", "INFO").upper()
|
|
configure_logging(level=getattr(logging, log_level, logging.INFO))
|
|
|
|
logger = logging.getLogger(__name__)
|
|
app = FastAPI(title="Headquarter API")
|
|
app.add_middleware(RequestLoggingMiddleware)
|
|
app.add_middleware(ExceptionLoggingMiddleware)
|
|
|
|
|
|
async def _table_exists(session, table_name: str) -> bool:
|
|
"""Check if a table exists in the database."""
|
|
try:
|
|
result = await session.execute(
|
|
text("""
|
|
SELECT EXISTS (
|
|
SELECT FROM information_schema.tables
|
|
WHERE table_schema = 'public'
|
|
AND table_name = :table_name
|
|
)
|
|
"""),
|
|
{"table_name": table_name},
|
|
)
|
|
return result.scalar() or False
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
async def seed_builtin_tool_types():
|
|
async with SessionLocal() as session:
|
|
# Check if tool_types table exists before attempting to seed
|
|
if not await _table_exists(session, "tool_types"):
|
|
logger.warning(
|
|
"tool_types table does not exist. Skipping seeding. "
|
|
"Migrations may not have run yet."
|
|
)
|
|
return
|
|
|
|
builtin_types = [
|
|
{
|
|
"name": "code-server",
|
|
"display_name": "VS Code Server",
|
|
"description": "VS Code running in the browser via code-server",
|
|
"compose_template": """version: "3.8"
|
|
services:
|
|
code-server:
|
|
image: lscr.io/linuxserver/code-server:latest
|
|
container_name: {{TOOL_NAME}}
|
|
environment:
|
|
- PUID=1000
|
|
- PGID=1000
|
|
- TZ=Europe/London
|
|
volumes:
|
|
- {{REPO_PATH}}:/config/workspace
|
|
ports:
|
|
- "8443:8443"
|
|
restart: unless-stopped""",
|
|
"required_variables": ["REPO_PATH", "TOOL_NAME"],
|
|
},
|
|
{
|
|
"name": "jupyter-notebook",
|
|
"display_name": "Jupyter Notebook",
|
|
"description": "Jupyter Lab for interactive development",
|
|
"compose_template": """version: "3.8"
|
|
services:
|
|
jupyter:
|
|
image: jupyter/scipy-notebook:latest
|
|
container_name: {{TOOL_NAME}}
|
|
environment:
|
|
- JUPYTER_ENABLE_LAB=yes
|
|
volumes:
|
|
- {{REPO_PATH}}:/home/jovyan/work
|
|
ports:
|
|
- "8888:8888"
|
|
restart: unless-stopped""",
|
|
"required_variables": ["REPO_PATH", "TOOL_NAME"],
|
|
},
|
|
]
|
|
|
|
for tool_data in builtin_types:
|
|
existing = await session.scalar(select(ToolType).where(ToolType.name == tool_data["name"]))
|
|
if not existing:
|
|
tool_type = ToolType(
|
|
name=tool_data["name"],
|
|
display_name=tool_data["display_name"],
|
|
description=tool_data["description"],
|
|
compose_template=tool_data["compose_template"],
|
|
required_variables=tool_data["required_variables"],
|
|
is_builtin=True,
|
|
)
|
|
session.add(tool_type)
|
|
|
|
await session.commit()
|
|
logger.info("Built-in tool types seeded successfully.")
|
|
|
|
|
|
@app.on_event("startup")
|
|
async def on_startup():
|
|
logger.info("Starting up Headquarter API...")
|
|
|
|
# Initialize database (run migrations)
|
|
db_ready = await init_database()
|
|
if not db_ready:
|
|
logger.error("Database initialization failed. Shutting down.")
|
|
import sys
|
|
sys.exit(1)
|
|
|
|
# Seed built-in data
|
|
await seed_builtin_tool_types()
|
|
logger.info("Startup complete.")
|
|
|
|
@app.get("/health")
|
|
async def health_check():
|
|
try:
|
|
from sqlalchemy import text
|
|
async with SessionLocal() as session:
|
|
await session.execute(text("SELECT 1"))
|
|
return {"status": "healthy", "database": "connected"}
|
|
except Exception as exc:
|
|
logger.error("Health check failed: %s", exc)
|
|
return {"status": "unhealthy", "database": "disconnected", "error": str(exc)}
|
|
|
|
app.include_router(auth_router)
|
|
app.include_router(projects_router)
|
|
app.include_router(users_router)
|
|
app.include_router(ssh_keys_router)
|
|
app.include_router(git_repositories_router)
|
|
app.include_router(user_config_router)
|
|
app.include_router(tool_types_router)
|
|
app.mount("/uploads", StaticFiles(directory="uploads"), name="uploads")
|