- FastAPI backend with REST API endpoints - SQLite database for container metadata - Docker/Podman SDK integration with label filtering - Frontend: Server creation form and management page - Container operations: create, list, start, stop, delete - Single container deployment (nginx + Python + supervisor) - Support for Docker and Podman (rootless) - Volume management for persistent data
195 lines
6.2 KiB
Python
195 lines
6.2 KiB
Python
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"}
|
|
|