from fastapi import FastAPI, HTTPException, Depends from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from sqlalchemy.orm import Session from pydantic import BaseModel from typing import List, Optional import json from backend.database import get_db, Container, Base, engine from backend.docker_manager import ( get_docker_client, list_containers, create_container, start_container, stop_container, remove_container ) # Create tables Base.metadata.create_all(bind=engine) app = FastAPI(title="Stronghold API") # CORS middleware app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Mount static files (frontend will be served by nginx in production) # app.mount("/", StaticFiles(directory="../frontend", html=True), name="static") # Pydantic models class ContainerConfig(BaseModel): serverName: str serverType: str version: Optional[str] = "LATEST" memory: Optional[str] = "2G" port: Optional[str] = "25565" difficulty: Optional[str] = None gamemode: Optional[str] = None levelType: Optional[str] = None motd: Optional[str] = None maxPlayers: Optional[int] = 20 viewDistance: Optional[int] = 10 acceptEULA: bool = True enableRCON: bool = False rconPort: Optional[str] = "25575" rconPassword: Optional[str] = None pvpEnabled: bool = True allowFlight: bool = False restartPolicy: str = "unless-stopped" class ContainerResponse(BaseModel): id: int container_id: str name: str image: str status: str created_at: str # API Routes @app.get("/api/containers") def get_containers(db: Session = Depends(get_db)): """List all Stronghold-managed containers""" try: client = get_docker_client() docker_containers = list_containers(client) # Get containers from database db_containers = db.query(Container).all() # Merge data result = [] for db_container in db_containers: # Find matching docker container docker_container = next( (dc for dc in docker_containers if dc.id == db_container.container_id), None ) status = docker_container.status if docker_container else "unknown" result.append({ "id": db_container.id, "container_id": db_container.container_id, "name": db_container.name, "image": db_container.image, "status": status, "created_at": db_container.created_at.isoformat() if db_container.created_at else None }) return result except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @app.post("/api/containers") def create_container_endpoint(config: ContainerConfig, db: Session = Depends(get_db)): """Create a new Minecraft server container""" try: client = get_docker_client() # Check if name already exists existing = db.query(Container).filter(Container.name == config.serverName).first() if existing: raise HTTPException(status_code=400, detail="Container name already exists") # Create container docker_container = create_container(client, config.serverName, config.dict()) # Save to database db_container = Container( container_id=docker_container.id, name=config.serverName, image="itzg/minecraft-server", status="created", config_json=json.dumps(config.dict()) ) db.add(db_container) db.commit() db.refresh(db_container) # Start container docker_container.start() # Update status db_container.status = "running" db.commit() return { "id": db_container.id, "container_id": docker_container.id, "name": config.serverName, "status": "running" } except HTTPException: raise except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @app.post("/api/containers/{container_id}/start") def start_container_endpoint(container_id: str, db: Session = Depends(get_db)): """Start a container""" try: client = get_docker_client() container = start_container(client, container_id) # Update database db_container = db.query(Container).filter(Container.container_id == container_id).first() if db_container: db_container.status = "running" db.commit() return {"status": "started", "container_id": container_id} except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @app.post("/api/containers/{container_id}/stop") def stop_container_endpoint(container_id: str, db: Session = Depends(get_db)): """Stop a container""" try: client = get_docker_client() container = stop_container(client, container_id) # Update database db_container = db.query(Container).filter(Container.container_id == container_id).first() if db_container: db_container.status = "stopped" db.commit() return {"status": "stopped", "container_id": container_id} except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @app.delete("/api/containers/{container_id}") def delete_container_endpoint(container_id: str, db: Session = Depends(get_db)): """Delete a container""" try: client = get_docker_client() remove_container(client, container_id) # Remove from database db_container = db.query(Container).filter(Container.container_id == container_id).first() if db_container: db.delete(db_container) db.commit() return {"status": "deleted", "container_id": container_id} except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @app.get("/api/health") def health_check(): """Health check endpoint""" return {"status": "ok"}