From c31e48e2b186e356ad4899fe7ca2ea7f6e7c2904 Mon Sep 17 00:00:00 2001 From: robojerk Date: Fri, 31 Oct 2025 11:50:31 -0700 Subject: [PATCH] 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 --- .dockerignore | 7 + .gitignore | 7 + Dockerfile | 71 ++ README.md | 57 ++ backend/database.py | 37 + backend/docker_manager.py | 116 +++ backend/main.py | 195 ++++ backend/requirements.txt | 5 + design.md | 38 + docker-compose | 0 docker-compose.yml | 24 + index.html | 852 ++++++++++++++++++ manage.html | 292 ++++++ references/SUMMARY.md | 103 +++ references/examples/ANALYSIS.md | 207 +++++ references/examples/EXAMPLES-ANALYSIS.md | 370 ++++++++ references/examples/README.md | 48 + references/examples/docker-compose-big.yml | 28 + .../docker-compose-curseforge-atm7.yaml | 149 +++ .../examples/docker-compose-curseforge.yml | 20 + references/examples/docker-compose-magma.yml | 16 + .../examples/docker-compose-rconcmd.yml | 40 + references/itzg-projects.md | 283 ++++++ references/launchers.md | 210 +++++ references/server-managers.md | 207 +++++ 25 files changed, 3382 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 backend/database.py create mode 100644 backend/docker_manager.py create mode 100644 backend/main.py create mode 100644 backend/requirements.txt create mode 100644 design.md create mode 100644 docker-compose create mode 100644 docker-compose.yml create mode 100644 index.html create mode 100644 manage.html create mode 100644 references/SUMMARY.md create mode 100644 references/examples/ANALYSIS.md create mode 100644 references/examples/EXAMPLES-ANALYSIS.md create mode 100644 references/examples/README.md create mode 100644 references/examples/docker-compose-big.yml create mode 100644 references/examples/docker-compose-curseforge-atm7.yaml create mode 100644 references/examples/docker-compose-curseforge.yml create mode 100644 references/examples/docker-compose-magma.yml create mode 100644 references/examples/docker-compose-rconcmd.yml create mode 100644 references/itzg-projects.md create mode 100644 references/launchers.md create mode 100644 references/server-managers.md diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..8046a50 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +node_modules +.git +references +docs +*.md +!README.md + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3e8663c --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +data/ +*.db +__pycache__/ +*.pyc +*.pyo +.env + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c06359b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,71 @@ +FROM python:3.11-slim + +# Install nginx +RUN apt-get update && apt-get install -y nginx supervisor && \ + rm -rf /var/lib/apt/lists/* + +# Set working directory +WORKDIR /app + +# Create data directory +RUN mkdir -p /app/data && chmod 777 /app/data + +# Copy backend requirements and install +COPY backend/requirements.txt /app/backend/ +RUN pip install --no-cache-dir -r backend/requirements.txt + +# Copy backend code +COPY backend/ /app/backend/ + +# Copy frontend files +COPY index.html manage.html /usr/share/nginx/html/ + +# Configure nginx +RUN echo 'server {\n\ + listen 80;\n\ + server_name localhost;\n\ + root /usr/share/nginx/html;\n\ + index index.html;\n\ + \n\ + location / {\n\ + try_files $uri $uri/ /index.html;\n\ + }\n\ + \n\ + location /api {\n\ + proxy_pass http://127.0.0.1:8000;\n\ + proxy_set_header Host $host;\n\ + proxy_set_header X-Real-IP $remote_addr;\n\ + }\n\ +}' > /etc/nginx/sites-available/default + +# Configure supervisor +RUN echo '[supervisord]\n\ +nodaemon=true\n\ +logfile=/dev/stdout\n\ +logfile_maxbytes=0\n\ +pidfile=/var/run/supervisord.pid\n\ +\n\ +[program:nginx]\n\ +command=nginx -g "daemon off;"\n\ +autostart=true\n\ +autorestart=true\n\ +stdout_logfile=/dev/stdout\n\ +stdout_logfile_maxbytes=0\n\ +stderr_logfile=/dev/stderr\n\ +stderr_logfile_maxbytes=0\n\ +\n\ +[program:fastapi]\n\ +command=uvicorn backend.main:app --host 127.0.0.1 --port 8000\n\ +directory=/app\n\ +environment=PYTHONPATH=/app\n\ +autostart=true\n\ +autorestart=true\n\ +stdout_logfile=/dev/stdout\n\ +stdout_logfile_maxbytes=0\n\ +stderr_logfile=/dev/stderr\n\ +stderr_logfile_maxbytes=0\n\ +' > /etc/supervisor/conf.d/supervisord.conf + +EXPOSE 80 + +CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..0238757 --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ +# Stronghold + +A web ui to manage minecraft server using the itzg docker container +https://github.com/itzg/docker-minecraft-server + +Also manage proxy servers like velocity, bungeecord + +## Development +See design.md for design notes + +## Quick Start (Proof of Concept) + +Stronghold is a web UI for managing Minecraft servers using Docker/Podman containers. + +### Running the POC + +**Using Docker Compose (Docker users)** +```bash +docker-compose up -d --build +``` + +**Using Podman Compose (Podman users)** +```bash +podman-compose up -d --build +``` + +**Note**: The container needs access to your Docker/Podman socket to manage containers. The compose file mounts: +- `/var/run/docker.sock` (Docker) +- `/run/podman/podman.sock` (Podman) + +Then open http://localhost:8080 in your browser. + +### Features + +**Current POC Features:** +- ✅ Generate docker-compose.yml files +- ✅ Create Minecraft server containers directly +- ✅ List and manage containers +- ✅ Start/Stop/Delete containers +- ✅ Support for Docker and Podman +- ✅ SQLite database for container metadata +- ✅ Multiple server types (Vanilla, Paper, Forge, Fabric, etc.) +- ✅ Modpack support (CurseForge, Modrinth, URL, Local) +- ✅ Advanced configuration options + +**Pages:** +- `/` (index.html) - Create new servers +- `/manage.html` - Manage existing servers + +### Architecture + +- **Frontend**: Static HTML/CSS/JavaScript +- **Backend**: Python FastAPI +- **Database**: SQLite (stored in `./data/stronghold.db`) +- **Container Engine**: Docker SDK (works with both Docker and Podman) + +All containers created by Stronghold are labeled with `stronghold.managed=true` to isolate them from other containers. diff --git a/backend/database.py b/backend/database.py new file mode 100644 index 0000000..5d811a8 --- /dev/null +++ b/backend/database.py @@ -0,0 +1,37 @@ +from sqlalchemy import create_engine, Column, String, Integer, Text, Boolean, DateTime +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker +from datetime import datetime + +Base = declarative_base() + +class Container(Base): + __tablename__ = "containers" + + id = Column(Integer, primary_key=True, index=True) + container_id = Column(String, unique=True, index=True) # Docker container ID + name = Column(String, unique=True, index=True) + image = Column(String) + status = Column(String) # running, stopped, etc. + config_json = Column(Text) # Full config as JSON string + created_at = Column(DateTime, default=datetime.utcnow) + updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + +import os + +# Create engine at module level +# Use /app/data for database storage (mounted volume) +data_dir = '/app/data' +os.makedirs(data_dir, exist_ok=True) +db_path = os.path.join(data_dir, 'stronghold.db') +engine = create_engine(f"sqlite:///{db_path}") +Base.metadata.create_all(bind=engine) + +def get_db(): + SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + db = SessionLocal() + try: + yield db + finally: + db.close() + diff --git a/backend/docker_manager.py b/backend/docker_manager.py new file mode 100644 index 0000000..3c2012c --- /dev/null +++ b/backend/docker_manager.py @@ -0,0 +1,116 @@ +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 + diff --git a/backend/main.py b/backend/main.py new file mode 100644 index 0000000..74263b7 --- /dev/null +++ b/backend/main.py @@ -0,0 +1,195 @@ +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"} + diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..6a4fbc9 --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,5 @@ +fastapi==0.104.1 +uvicorn[standard]==0.24.0 +docker==7.0.0 +sqlalchemy==2.0.23 + diff --git a/design.md b/design.md new file mode 100644 index 0000000..4697b5b --- /dev/null +++ b/design.md @@ -0,0 +1,38 @@ +# Design + +Take a lot of inspiration from crafty. +Make creating a modded server as simple as it is using ATLauncher + +# Philosophy +KISS = Keep It Simple Stupid +dont make things over complicated + +Keep code clean, readable, and concise. + +Modular + +# Plans +This web ui app should be ran in a docker container. +User authentication, permissions +simple status page for players to see if the server is up + +# References +[itzg docker-minecraft-server](https://github.com/itzg/docker-minecraft-server) +[itzg docs](https://docker-minecraft-server.readthedocs.io/en/latest/) these docs exist in the main itzg github repo +[itzg minecraft proxy](https://github.com/itzg/docker-mc-proxy) used to setup proxy servers like velocity and bungee cord +[itzeg mc-router](https://github.com/itzg/mc-router) used to direct client to the right minecraft proxy or server based on requested server address +[crafty](https://craftycontrol.com) +[docker-rcon-web-admin](https://github.com/itzg/docker-rcon-web-admin) +[setupmc.com](https://setupmc.com/java-server/) Not open source but it's a web ui to generate yml files for docker using the itzg container image + + +# Maybe useful references +These are the github repos to other minecraft project that may be useful + +[pterodactyl](https://github.com/pterodactyl/panel) +[fork](https://github.com/ForkGG/Fork) This is a Windows based server manager that may have useful ideas. +[MCSManager](https://github.com/MCSManager/MCSManager) + +Minecraft launchers for clients. they have tools to download packs and server packs +[ATLauncher](https://github.com/ATLauncher/ATLauncher) +[Prism Launcher](https://github.com/PrismLauncher/PrismLauncher) \ No newline at end of file diff --git a/docker-compose b/docker-compose new file mode 100644 index 0000000..e69de29 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..bc1f59f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,24 @@ +version: '3.8' + +services: + stronghold: + build: . + ports: + - "8080:80" + volumes: + # Socket access for Podman (rootless) + # For Podman rootless, mount the socket to /var/run/docker.sock inside container + # This way the Docker SDK can find it at the standard location + - ${XDG_RUNTIME_DIR}/podman/podman.sock:/var/run/docker.sock:ro + # Database persistence + - ./data:/app/data + # Hot reload frontend (optional, for development) + - ./index.html:/usr/share/nginx/html/index.html:ro,z + - ./manage.html:/usr/share/nginx/html/manage.html:ro,z + environment: + # Set working directory for database + - PWD=/app/data + # Pass XDG_RUNTIME_DIR for Podman rootless + - XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR:-/run/user/1000} + working_dir: /app/data + restart: unless-stopped diff --git a/index.html b/index.html new file mode 100644 index 0000000..22630ee --- /dev/null +++ b/index.html @@ -0,0 +1,852 @@ + + + + + + Stronghold - Minecraft Server Generator + + + +
+

🛡️ Stronghold

+

Minecraft Server Docker Compose Generator

+ +
+ + + +
+ +
+ +
+
+ + +
+ +
+
+ + +
+ +
+ + + Use LATEST for newest version, or specify version like 1.20.1 +
+
+ +
+
+ + +
+ + +
+ +
+
+ + + Default Minecraft port is 25565 +
+ +
+ + +
+
+ +
+
+ + +
+ +
+ + +
+
+ +
+ + + Message shown in server list (max 59 characters) +
+ +
+
+ + +
+ +
+ + + Higher = better visibility but more CPU usage +
+
+ +
+ + Required to run a Minecraft server +
+
+ + +
+
Modpack Installation
+ +
+ + +
+ +
+
+ + + Path to CurseForge server pack zip file inside container +
+
+ + + Uncheck if you want to manually set server type +
+
+ +
+
+ + + Modrinth project identifier +
+
+ + + Specific version ID, or leave empty for latest +
+
+ +
+
+ + + Direct download URL for modpack zip file +
+
+ +
+
+ + + Path to modpack file inside container (use volume mount) +
+
+ +
+ + Deletes existing mods/plugins before installing modpack +
+
+ + +
+
RCON Configuration
+ +
+ +
+ + + +
Server Properties
+ +
+
+ +
+
+ +
+
+ +
+ + + Maximum time a single tick can take (-1 = no limit) +
+ +
+ + + Players who will have operator permissions +
+ +
Container Options
+ +
+
+ + +
+
+ + + Server timezone (e.g., America/New_York, Europe/London) +
+
+ +
+ + Automatically delete old log files +
+ +
+ + Allow environment variables to override server.properties +
+
+ + + +
+ + +
+ + + + diff --git a/manage.html b/manage.html new file mode 100644 index 0000000..0023712 --- /dev/null +++ b/manage.html @@ -0,0 +1,292 @@ + + + + + + Manage Servers - Stronghold + + + +
+
+
+

🛡️ Stronghold

+

Manage Your Minecraft Servers

+
+ Create New Server +
+ +
+
Loading servers...
+
+
+ + + + + diff --git a/references/SUMMARY.md b/references/SUMMARY.md new file mode 100644 index 0000000..a0b20b2 --- /dev/null +++ b/references/SUMMARY.md @@ -0,0 +1,103 @@ +# Reference Projects Summary + +This directory contains detailed analysis of reference projects for the Stronghold Minecraft server management tool. + +## Files + +1. **itzg-projects.md** - Core Docker projects by itzg that Stronghold will integrate with: + - docker-minecraft-server + - docker-mc-proxy + - mc-router + - docker-rcon-web-admin + +2. **server-managers.md** - Existing web-based server management tools: + - Crafty (CraftyControl) + - Pterodactyl Panel + - Fork (ForkGG) + - MCSManager + - setupmc.com + +3. **launchers.md** - Client-side launchers with excellent modpack workflows: + - ATLauncher + - Prism Launcher + +4. **examples/** - Docker Compose example files from itzg repository: + - docker-compose-big.yml (Large Biomes world type) + - docker-compose-curseforge-atm7.yaml (All The Mods 7 modpack) + - docker-compose-curseforge.yml (Basic CurseForge example) + - docker-compose-magma.yml (Hybrid mods + plugins) + - docker-compose-rconcmd.yml (Advanced RCON commands) + - EXAMPLES-ANALYSIS.md (Detailed analysis of each example) + +## Key Insights + +### Architecture Recommendations +- **Keep it simple** (KISS): Docker API directly, no agents/daemons needed initially +- **Start without database**: Use file-based configuration, add database later if needed +- **Node.js stack**: Could work well (like MCSManager but simpler) +- **Single-host focus**: Start with single Docker host, expand later + +### Feature Priorities +1. **Server CRUD**: Create, read, update, delete servers +2. **Configuration UI**: User-friendly interface for itzg container environment variables +3. **Console Access**: RCON integration for server management +4. **File Browser**: Access server files (worlds, configs, mods) +5. **Modpack Support**: ATLauncher-like experience for server creation + +### Technical Stack Suggestions +- **Backend**: Node.js/Express or Python/FastAPI +- **Frontend**: React, Vue, or SvelteKit +- **Docker**: Direct Docker API integration +- **Storage**: File-based configs initially, optional database later +- **APIs**: Modrinth, CurseForge for modpack integration + +### UI/UX Principles +- Take inspiration from Crafty and Fork for clean, intuitive interfaces +- Follow ATLauncher's model for modpack installation simplicity +- Use setupmc.com as reference for configuration UI patterns +- Prioritize common workflows over advanced features + +### Implementation Phases + +**MVP (Phase 1)** +- Basic Docker integration +- Simple server creation/management +- Basic web UI +- Console access + +**V1.0 (Phase 2)** +- Full configuration UI +- File browser +- Modpack installation (basic) +- Proxy server support + +**V1.5+ (Phase 3)** +- Modpack browser +- Advanced features +- Templates/presets +- Backup system +- mc-router integration + +## Notes on Reference Projects + +### Most Relevant +1. **itzg/docker-minecraft-server**: Core technology Stronghold will use +2. **itzg examples**: Real-world docker-compose configurations showing patterns and edge cases +3. **setupmc.com**: Shows how to simplify Docker config generation +4. **ATLauncher**: Target UX for modpack installation +5. **Crafty**: Good UI/UX patterns to follow + +### Study but Don't Copy +1. **Pterodactyl**: Too complex, but has good separation of concerns +2. **MCSManager**: Good architecture ideas, but agent model overkill for Docker +3. **Fork**: Great UX, but Windows-focused + +## Questions to Resolve + +Based on these references, Stronghold needs to decide: +1. **Tech Stack**: Node.js vs Python vs Go? +2. **Database**: Start with files or include database from start? +3. **Multi-user**: Single-user MVP or multi-user from start? +4. **Deployment**: Single container, docker-compose, or microservices? +5. **Modpack APIs**: Which to support first (Modrinth, CurseForge, both)? + diff --git a/references/examples/ANALYSIS.md b/references/examples/ANALYSIS.md new file mode 100644 index 0000000..569bea2 --- /dev/null +++ b/references/examples/ANALYSIS.md @@ -0,0 +1,207 @@ +# Docker Compose Examples Analysis + +This document analyzes various docker-compose.yml examples to identify patterns, edge cases, and implementation requirements for Stronghold. + +## Common Patterns + +### Basic Server Setup + +**Pattern Elements**: +- Image: `itzg/minecraft-server` +- Required: `EULA=TRUE` +- Port mapping: `25565:25565` +- Volume: Persistent data storage +- Environment variables for configuration + +### Modded Server Setup + +**Pattern Elements**: +- `TYPE`: FORGE, FABRIC, or other mod loader +- `MODPACK` or `MODS`: Modpack/mod installation +- Additional memory allocation +- Mod volume mounts (`/mods`) +- Config volume mounts (`/config`) + +### Plugin Server Setup + +**Pattern Elements**: +- `TYPE`: PAPER, SPIGOT, BUKKIT +- Plugin volume mounts (`/plugins`) +- `PLUGINS` environment variable or volume + +### Proxy Server Setup + +**Pattern Elements**: +- Image: `itzg/mc-proxy` +- `PROXY_TYPE`: BUNGEE, VELOCITY, WATERFALL +- Backend server configuration +- Network linking + +## Edge Cases to Handle + +### 1. Multiple Mod Sources +```yaml +environment: + COPY_MODS_SRC: /mods-common,/mods-local +volumes: + - ../shared-mods:/mods-common:ro + - ./local-mods:/mods-local:ro +``` +**Stronghold needs**: UI to manage multiple mod source directories + +### 2. Generic Pack with Custom Configuration +```yaml +environment: + GENERIC_PACK: https://example.com/server-pack.zip + GENERIC_PACKS_DISABLE_MODS: "conflicting-mod.jar" + APPLY_EXTRA_FILES: "config/server.properties