stronghold/backend/docker_manager.py
robojerk c31e48e2b1 Proof of concept: Container management with FastAPI backend
- 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
2025-10-31 11:50:31 -07:00

116 lines
4 KiB
Python

import docker
import os
import json
LABEL_MANAGED = "stronghold.managed"
LABEL_VALUE = "true"
def get_docker_client():
"""Get Docker client, automatically detecting Docker or Podman"""
try:
# Try Docker socket first
if os.path.exists('/var/run/docker.sock'):
return docker.DockerClient(base_url='unix://var/run/docker.sock')
# Try Podman rootful
if os.path.exists('/run/podman/podman.sock'):
return docker.DockerClient(base_url='unix://run/podman/podman.sock')
# Try Podman rootless
xdg_runtime = os.environ.get('XDG_RUNTIME_DIR', '')
podman_sock = f'{xdg_runtime}/podman/podman.sock'
if xdg_runtime and os.path.exists(podman_sock):
return docker.DockerClient(base_url=f'unix://{podman_sock}')
# Fallback to default
return docker.DockerClient(base_url='unix://var/run/docker.sock')
except Exception as e:
raise Exception(f"Failed to connect to Docker/Podman: {e}")
def list_containers(client):
"""List all Stronghold-managed containers"""
filters = {"label": f"{LABEL_MANAGED}={LABEL_VALUE}"}
return client.containers.list(all=True, filters=filters)
def create_container(client, name, config):
"""Create a new container with Stronghold label"""
# Build environment variables from config
environment = {
"EULA": "TRUE" if config.get("acceptEULA") else "FALSE",
"TYPE": config.get("serverType", "PAPER"),
}
# Add optional environment variables
if config.get("version") and config.get("version") != "LATEST":
environment["VERSION"] = config["version"]
if config.get("memory"):
environment["MEMORY"] = config["memory"]
if config.get("difficulty"):
environment["DIFFICULTY"] = config["difficulty"]
if config.get("gamemode"):
environment["MODE"] = config["gamemode"]
if config.get("levelType"):
environment["LEVEL_TYPE"] = config["levelType"]
if config.get("motd"):
environment["MOTD"] = config["motd"]
if config.get("maxPlayers"):
environment["MAX_PLAYERS"] = str(config["maxPlayers"])
if config.get("viewDistance"):
environment["VIEW_DISTANCE"] = str(config["viewDistance"])
if not config.get("pvpEnabled", True):
environment["PVP"] = "false"
if config.get("allowFlight"):
environment["ALLOW_FLIGHT"] = "true"
if config.get("enableRCON"):
environment["ENABLE_RCON"] = "true"
if config.get("rconPassword"):
environment["RCON_PASSWORD"] = config["rconPassword"]
# Port bindings (host_port:container_port)
port = config.get("port", "25565")
port_bindings = {f"{25565}/tcp": int(port)}
if config.get("enableRCON"):
rcon_port = config.get("rconPort", "25575")
port_bindings[f"{25575}/tcp"] = int(rcon_port)
# Create or get volume
volume_name = f"{name}_data"
try:
client.volumes.get(volume_name)
except:
client.volumes.create(name=volume_name, labels={LABEL_MANAGED: LABEL_VALUE})
# Create container with proper volume mount
restart_policy = config.get("restartPolicy", "unless-stopped")
container = client.containers.create(
image="itzg/minecraft-server",
name=name,
environment=environment,
ports=port_bindings,
volumes=[f"{volume_name}:/data"],
labels={LABEL_MANAGED: LABEL_VALUE},
restart_policy={"Name": restart_policy} if restart_policy != "no" else None,
detach=True
)
return container
def start_container(client, container_id):
"""Start a container"""
container = client.containers.get(container_id)
container.start()
return container
def stop_container(client, container_id):
"""Stop a container"""
container = client.containers.get(container_id)
container.stop()
return container
def remove_container(client, container_id):
"""Remove a container"""
container = client.containers.get(container_id)
container.remove(force=True)
return container