diff --git a/.github/workflows/build-images.yml b/.github/workflows/build-images.yml new file mode 100644 index 0000000..eacf92a --- /dev/null +++ b/.github/workflows/build-images.yml @@ -0,0 +1,47 @@ +name: build and sign gameplane images + +on: + push: + branches: [main] + paths: + - 'fivem/**' + - 'farming-simulator-25/**' + - 'euro-truck-simulator-2/**' + - 'beammp/**' + - 'build-images.sh' + workflow_dispatch: + +permissions: + contents: read + packages: write + id-token: write + +jobs: + build-and-sign: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Install Cosign + uses: sigstore/cosign-installer@v3.5.0 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build, push, and cosign-sign Gameplane images + env: + COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} + COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} + run: | + if [ -n "$COSIGN_PRIVATE_KEY" ]; then + ./build-images.sh push --registry ghcr.io/${{ github.repository_owner }}/gameplane --sign + else + ./build-images.sh build + fi diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index ce72077..b9ae99d 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -45,3 +45,7 @@ jobs: # no registry and no cosign. - name: Test build.sh signing preconditions run: ./test-build-sh.sh + + - name: Test validate.py directory layout rules + run: ./test-validate-py.sh + diff --git a/.schema/gametemplate.schema.json b/.schema/gametemplate.schema.json index 52d461b..cda2754 100644 --- a/.schema/gametemplate.schema.json +++ b/.schema/gametemplate.schema.json @@ -402,6 +402,11 @@ ], "type": "object" }, + "curseforgeGameID": { + "description": "CurseForgeGameID is the numeric CurseForge game the browser searches\n(Minecraft is 432; ARK: Survival Ascended is 83374 \u2014 from CurseForge's\nown /v1/games). Required when provider is curseforge: the API has no\nsafe default, and guessing one is how ARK's browser ended up listing\nMinecraft mods.", + "format": "int32", + "type": "integer" + }, "github": { "description": "GitHub binds this provider to one repository's Releases. GitHub has\nno cross-repo mod search (unlike Thunderstore's per-community\nindex), so a template picks exactly one repo to browse. Required\nwhen Provider is \"github\"; ignored otherwise.", "properties": { @@ -455,7 +460,7 @@ "description": "Selects a key of a ConfigMap.", "properties": { "key": { - "description": "The key to select.", + "description": "The key to select from the ConfigMap's Data field.\nKeys in the BinaryData field are not currently propagated to container env vars.", "type": "string" }, "name": { @@ -596,7 +601,7 @@ "type": "object" }, "provider": { - "description": "Provider names the built-in registry engine: \"modrinth\" (Minecraft\nmods/plugins, keyless), \"thunderstore\" (BepInEx games, keyless,\nper-community), \"curseforge\" (Minecraft mods/modpacks, needs an API\nkey), \"hangar\" (PaperMC plugins, keyless), \"factorio\" (the official\nFactorio mod portal; browse is keyless, downloads need the player's\nown factorio.com credentials so installs hand off to the from-URL\nform), \"steam\" (Steam Workshop browse, needs a Steam Web API key;\nsee SteamAppID \u2014 Workshop content has no download URL, so it's a\npreview-only browser wired to modpacks.refEnv for collection-based\ngames like Garry's Mod/CS2), \"nexus\" (Nexus Mods, needs an API key,\nbrowse-only for the same reason as steam \u2014 see Community for its\nper-game domain slug), \"spigot\" (SpigotMC plugins via the Spiget API,\nkeyless), \"github\" (one repository's Releases stand in for\nversions, keyless but rate-limited \u2014 see GitHub), or \"umod\"\n(Rust/Hurtworld/7 Days to Die's Oxide/uMod plugin ecosystem,\nkeyless).", + "description": "Provider names the built-in registry engine: \"modrinth\" (Minecraft\nmods/plugins, keyless), \"thunderstore\" (BepInEx games, keyless,\nper-community), \"curseforge\" (mods/modpacks for the game identified\nby CurseForgeGameID, needs an API key), \"hangar\" (PaperMC plugins,\nkeyless), \"factorio\" (the official Factorio mod portal; browse is\nkeyless, downloads need the player's own factorio.com credentials so\ninstalls hand off to the from-URL form), \"steam\" (Steam Workshop\nbrowse, needs a Steam Web API key; see SteamAppID \u2014 Workshop content\nhas no download URL, so it's a preview-only browser wired to\nmodpacks.refEnv for collection-based games like Garry's Mod/CS2),\n\"nexus\" (Nexus Mods, needs an API key, browse-only for the same\nreason as steam \u2014 see Community for its per-game domain slug),\n\"spigot\" (SpigotMC plugins via the Spiget API, keyless), \"github\"\n(one repository's Releases stand in for versions, keyless but\nrate-limited \u2014 see GitHub), or \"umod\" (Rust/Hurtworld/7 Days to\nDie's Oxide/uMod plugin ecosystem, keyless).", "enum": [ "modrinth", "thunderstore", @@ -630,6 +635,10 @@ { "message": "github is required when provider is github", "rule": "self.provider != 'github' || has(self.github)" + }, + { + "message": "curseforgeGameID is required when provider is curseforge", + "rule": "self.provider != 'curseforge' || (has(self.curseforgeGameID) && self.curseforgeGameID > 0)" } ] }, @@ -912,6 +921,26 @@ }, "type": "array" }, + "max": { + "description": "Max is the maximum numeric value for int-typed fields. Bounds are\ninclusive. Ignored for non-int field types.", + "format": "int64", + "type": "integer" + }, + "maxLength": { + "description": "MaxLength is the maximum string length for string and password-typed\nfields. Bounds are inclusive. Ignored for non-string field types.", + "format": "int32", + "type": "integer" + }, + "min": { + "description": "Min is the minimum numeric value for int-typed fields. Bounds are\ninclusive. Ignored for non-int field types.", + "format": "int64", + "type": "integer" + }, + "minLength": { + "description": "MinLength is the minimum string length for string and password-typed\nfields. Bounds are inclusive. Ignored for non-string field types.", + "format": "int32", + "type": "integer" + }, "name": { "description": "Name is the field identifier (also used as an env var when\nTarget is \"env\").", "minLength": 1, @@ -989,7 +1018,7 @@ "description": "Selects a key of a ConfigMap.", "properties": { "key": { - "description": "The key to select.", + "description": "The key to select from the ConfigMap's Data field.\nKeys in the BinaryData field are not currently propagated to container env vars.", "type": "string" }, "name": { @@ -1168,6 +1197,17 @@ "UDP" ], "type": "string" + }, + "wakeProtocol": { + "default": "generic", + "description": "WakeProtocol selects the parser the wake sentinel applies to this port\nwhile the server is asleep. \"minecraft\" and \"terraria\" parse the real\nhandshake, so only a genuine join wakes the server and a server-list\nping is answered in place without waking it. \"generic\" wakes on\nplausible traffic \u2014 the only option for the UDP-only games, which have\nno connection to hold. \"none\" never wakes.", + "enum": [ + "minecraft", + "terraria", + "generic", + "none" + ], + "type": "string" } }, "required": [ @@ -1206,6 +1246,10 @@ "grpc": { "description": "GRPC specifies a GRPC HealthCheckRequest.", "properties": { + "mode": { + "description": "mode specifies the connection mode for the gRPC health probe.\nSet to \"TLS\" to use TLS without certificate verification.\nSet to \"Plaintext\" to use a plaintext (insecure) connection explicitly.\nIf not specified, the probe uses a plaintext (insecure) connection.", + "type": "string" + }, "port": { "description": "Port number of the gRPC service. Number must be in the range 1 to 65535.", "format": "int32", @@ -1268,6 +1312,10 @@ "description": "Name or number of the port to access on the container.\nNumber must be in the range 1 to 65535.\nName must be an IANA_SVC_NAME.", "x-kubernetes-int-or-string": true }, + "protocol": { + "description": "Protocol selects the wire protocol for the probe connection.\nNil defaults to HTTP/1.1.", + "type": "string" + }, "scheme": { "description": "Scheme to use for connecting to the host.\nDefaults to HTTP.", "type": "string" @@ -1356,6 +1404,10 @@ "grpc": { "description": "GRPC specifies a GRPC HealthCheckRequest.", "properties": { + "mode": { + "description": "mode specifies the connection mode for the gRPC health probe.\nSet to \"TLS\" to use TLS without certificate verification.\nSet to \"Plaintext\" to use a plaintext (insecure) connection explicitly.\nIf not specified, the probe uses a plaintext (insecure) connection.", + "type": "string" + }, "port": { "description": "Port number of the gRPC service. Number must be in the range 1 to 65535.", "format": "int32", @@ -1418,6 +1470,10 @@ "description": "Name or number of the port to access on the container.\nNumber must be in the range 1 to 65535.\nName must be an IANA_SVC_NAME.", "x-kubernetes-int-or-string": true }, + "protocol": { + "description": "Protocol selects the wire protocol for the probe connection.\nNil defaults to HTTP/1.1.", + "type": "string" + }, "scheme": { "description": "Scheme to use for connecting to the host.\nDefaults to HTTP.", "type": "string" @@ -1506,6 +1562,10 @@ "grpc": { "description": "GRPC specifies a GRPC HealthCheckRequest.", "properties": { + "mode": { + "description": "mode specifies the connection mode for the gRPC health probe.\nSet to \"TLS\" to use TLS without certificate verification.\nSet to \"Plaintext\" to use a plaintext (insecure) connection explicitly.\nIf not specified, the probe uses a plaintext (insecure) connection.", + "type": "string" + }, "port": { "description": "Port number of the gRPC service. Number must be in the range 1 to 65535.", "format": "int32", @@ -1568,6 +1628,10 @@ "description": "Name or number of the port to access on the container.\nNumber must be in the range 1 to 65535.\nName must be an IANA_SVC_NAME.", "x-kubernetes-int-or-string": true }, + "protocol": { + "description": "Protocol selects the wire protocol for the probe connection.\nNil defaults to HTTP/1.1.", + "type": "string" + }, "scheme": { "description": "Scheme to use for connecting to the host.\nDefaults to HTTP.", "type": "string" @@ -1672,7 +1736,7 @@ }, "protocol": { "default": "source", - "description": "Protocol is the wire protocol the agent speaks to the game's console\nport. Multiple protocols are supported: \"source\" is the Valve/Minecraft\npacket-framed RCON protocol; \"telnet\" is a raw line-based TCP console\n(e.g. 7 Days to Die) \u2014 send a line, get a line back, no framing;\n\"websocket\" is the Rust WebRcon protocol (requires +rcon.web 1);\n\"battleye\" is the BattlEye RCon protocol used by DayZ and Arma \u2014 UDP,\nwith checksum-framed packets and a mandatory client-side keepalive;\n\"satisfactory\" is Satisfactory Dedicated Server's HTTPS function-call\nAPI (POST /api/v1 with a JSON \"function\" body, bearer-token auth\nafter a PasswordLogin call) \u2014 not a socket protocol at all, so Port\nhere is a TCP port carrying HTTPS, not a raw console stream.\n\"palworld\" is Palworld Dedicated Server's REST admin API (plain HTTP,\nnot HTTPS \u2014 GET/POST under /v1/api/..., HTTP Basic auth with\nusername \"admin\" sent on every request, no token or session) \u2014 the\nofficial replacement for Palworld's now-deprecated source RCON, and\nlikewise not a socket protocol, so Port is a TCP port carrying HTTP.\n\"none\" means the game has no usable remote console (see consoleMode:\npty for stdin-driven games instead).", + "description": "Protocol is the wire protocol the agent speaks to the game's console\nport. Multiple protocols are supported: \"source\" is the Valve/Minecraft\npacket-framed RCON protocol; \"telnet\" is a raw line-based TCP console\n(e.g. 7 Days to Die) \u2014 send a line, get a line back, no framing;\n\"websocket\" is the Rust WebRcon protocol (requires +rcon.web 1);\n\"battleye\" is the BattlEye RCon protocol used by DayZ and Arma \u2014 UDP,\nwith checksum-framed packets and a mandatory client-side keepalive;\n\"satisfactory\" is Satisfactory Dedicated Server's HTTPS function-call\nAPI (POST /api/v1 with a JSON \"function\" body, bearer-token auth\nafter a PasswordLogin call) \u2014 not a socket protocol at all, so Port\nhere is a TCP port carrying HTTPS, not a raw console stream.\n\"palworld\" is Palworld Dedicated Server's REST admin API (plain HTTP,\nnot HTTPS \u2014 GET/POST under /v1/api/..., HTTP Basic auth with\nusername \"admin\" sent on every request, no token or session) \u2014 the\nofficial replacement for Palworld's now-deprecated source RCON, and\nlikewise not a socket protocol, so Port is a TCP port carrying HTTP.\n\"nuclearoption\" is Nuclear Option's JSON-RPC 2.0 TCP socket protocol.\n\"rest\" is a generic HTTP/JSON console API (POST-per-command, bearer-\nor basic-auth, distinct from the bespoke satisfactory/palworld clients).\n\"cli\" is console access over the container's stdin/PTY, enabling\nagent-driven console commands without requiring an exposed network port.\n\"none\" means the game has no usable remote console (see consoleMode:\npty for stdin-driven games instead).", "enum": [ "source", "telnet", @@ -1680,6 +1744,9 @@ "battleye", "satisfactory", "palworld", + "nuclearoption", + "rest", + "cli", "none" ], "type": "string" @@ -1910,7 +1977,7 @@ "description": "Selects a key of a ConfigMap.", "properties": { "key": { - "description": "The key to select.", + "description": "The key to select from the ConfigMap's Data field.\nKeys in the BinaryData field are not currently propagated to container env vars.", "type": "string" }, "name": { diff --git a/7-days-to-die/README.md b/7-days-to-die/README.md index 0d1b60a..1b81cae 100644 --- a/7-days-to-die/README.md +++ b/7-days-to-die/README.md @@ -130,3 +130,7 @@ around it (slow, automatic, not destructive). container's foreground process (per `install.sh`) ends up being a log `tail`, not the game's stdin. Flagged here rather than left silently assumed to work, since it wasn't independently verified. + +## Sample + +See [`samples/gameserver.yaml`](samples/gameserver.yaml) for an example deployment manifest. diff --git a/7-days-to-die/samples/gameserver.yaml b/7-days-to-die/samples/gameserver.yaml new file mode 100644 index 0000000..f98472e --- /dev/null +++ b/7-days-to-die/samples/gameserver.yaml @@ -0,0 +1,27 @@ +apiVersion: gameplane.local/v1alpha1 +kind: GameServer +metadata: + name: 7-days-to-die-01 + namespace: gameplane-games +spec: + templateRef: + name: 7-days-to-die + + config: + UNDEAD_LEGACY: "NO" + DARKNESS_FALLS: "NO" + ALLOC_FIXES: "NO" + MODS_URLS: "" + + networking: + expose: NodePort + portOverrides: + - name: game + nodePort: 32690 + + storage: + size: 10Gi + + resources: + requests: { cpu: 2, memory: 6Gi } + limits: { cpu: 4, memory: 12Gi } diff --git a/7-days-to-die/specs.md b/7-days-to-die/specs.md new file mode 100644 index 0000000..5fe182d --- /dev/null +++ b/7-days-to-die/specs.md @@ -0,0 +1,81 @@ +# Gameplane Module Specification: 7 Days to Die + +## 1. Purpose & Scope + +- **Game**: 7 Days to Die +- **Module Slug**: `7-days-to-die` +- **Role**: Dedicated server module package for Gameplane. +- **Description**: Voxel-based open-world zombie survival crafting dedicated server. Backed by LinuxGSM-powered `vinanrra/7dtd-server`, with dual persistent storage for world saves and game files. + +--- + +## 2. Container Image & Architecture + +- **Base Image**: `vinanrra/7dtd-server:latest@sha256:0aa521d9660cba22bb42d515a815bb08a18357a7da931ee805c8fcfa1e793910` +- **Architecture**: `linux/amd64` +- **Runtime Model**: LinuxGSM runner executing 7 Days to Die Unity dedicated server binaries with automated updates via SteamCMD. +- **User & Execution Context**: Starts as root to run entrypoint scripts and drops privileges to LinuxGSM user `sdtdserver` (UID `1000`). Working directory `/home/sdtdserver`. + +--- + +## 3. Network Ports & Protocols + +Declared ports under `spec.ports`: + +| Port Name | Container Port | Protocol | Usage / Purpose | +|---|---|---|---| +| `game` | `26900` | `TCP` | Main game connection port | +| `game-udp` | `26900` | `UDP` | Game traffic and client discovery | +| `game2` | `26901` | `UDP` | Auxiliary game traffic | +| `game3` | `26902` | `UDP` | Auxiliary game traffic | +| `telnet` | `8081` | `TCP` | Internal Telnet administrative port (unmanaged) | + +--- + +## 4. Storage & Persistence Layout + +- **Mount Path**: `/home/sdtdserver/.local/share/7DaysToDie` +- **Default Sizing**: `10Gi` (world saves) + `45Gi` (`extra` volume for `serverfiles`) +- **Persisted Content**: + - Generated worlds and player data (`/home/sdtdserver/.local/share/7DaysToDie/Saves`) + - Server install and binaries (`/home/sdtdserver/serverfiles`) +- **Non-Shadowing Invariant**: Does not mount directly over `/home/sdtdserver`, preserving the container entrypoint launcher script (`user.sh`). + +--- + +## 5. Administration & Remote Console (RCON) + +- **Protocol**: `none` (Telnet password is baked in `serverconfig.xml` under `serverfiles/`; no environment injection supported). +- **Console Mode**: `none` +- **Authentication**: N/A +- **Command Support**: N/A + +--- + +## 6. Modding & Workshop Integration + +- **Modding Framework**: LinuxGSM script mod installers (Undead Legacy, Darkness Falls, Alloc's Server Fixes) +- **Mod Directory Path**: Handled at container start via `scripts/Mods/*.sh` +- **Workshop Synchronization**: Config-driven URLs via `MODS_URLS` + +--- + +## 7. Lifecycle & Graceful Shutdown + +- **Stop Command Sequence (`spec.capabilities.lifecycle.stop`)**: `[]` (Empty; no RCON reachable). +- **Signal Handling**: Container traps `SIGINT` / `SIGTERM` and triggers `sdtdserver stop` to perform clean world save. + +--- + +## 8. Key Invariants & Security + +- **Dual Volume Mount**: Separate mounts for `.local/share/7DaysToDie` (saves) and `serverfiles` (game) avoid shadowing the entrypoint. +- **Filesystem Permissions**: LinuxGSM drops root privileges; storage permissions are maintained across restarts. + +--- + +## 9. References & Upstream Documentation + +- Official Game Documentation: https://7daystodie.com/ +- Upstream Container Repository: https://github.com/vinanrra/Docker-7DaysToDie +- Steam Dedicated Server AppID: `294420` diff --git a/7-days-to-die/template.yaml b/7-days-to-die/template.yaml index 41a5f20..2e41d12 100644 --- a/7-days-to-die/template.yaml +++ b/7-days-to-die/template.yaml @@ -79,6 +79,9 @@ spec: displayName: "Latest (moves on restart)" image: vinanrra/7dtd-server:latest # gameplane:floating + security: + fsGroup: 1000 + env: # Required by the image — with no env at all (the previous draft's # state) the container has no defined start behavior. START_MODE=1 is diff --git a/ark-survival-ascended/README.md b/ark-survival-ascended/README.md index 4069fda..bff4ec9 100644 --- a/ark-survival-ascended/README.md +++ b/ark-survival-ascended/README.md @@ -117,3 +117,7 @@ need more. `GameUserSettings.ini` via the Files tab (see Console & RCON / Server name above) — Gameplane can't safely automate either without risking wiping other in-game-tuned settings on the next restart. + +## Sample + +See [`samples/gameserver.yaml`](samples/gameserver.yaml) for an example deployment manifest. diff --git a/ark-survival-ascended/samples/gameserver.yaml b/ark-survival-ascended/samples/gameserver.yaml new file mode 100644 index 0000000..ea7fe1f --- /dev/null +++ b/ark-survival-ascended/samples/gameserver.yaml @@ -0,0 +1,28 @@ +apiVersion: gameplane.local/v1alpha1 +kind: GameServer +metadata: + name: ark-survival-ascended-01 + namespace: gameplane-games +spec: + templateRef: + name: ark-survival-ascended + + config: + SESSION_NAME: "Gameplane ASA Server" + SERVER_PASSWORD: "" + SERVER_ADMIN_PASSWORD: "change-me-admin-password" + MAX_PLAYERS: "32" + MAP_NAME: "TheIsland_WP" + + networking: + expose: NodePort + portOverrides: + - name: game + nodePort: 30777 + + storage: + size: 30Gi + + resources: + requests: { cpu: 2, memory: 10Gi } + limits: { cpu: 6, memory: 20Gi } diff --git a/ark-survival-ascended/specs.md b/ark-survival-ascended/specs.md new file mode 100644 index 0000000..ee5cce2 --- /dev/null +++ b/ark-survival-ascended/specs.md @@ -0,0 +1,88 @@ +# Gameplane Module Specification: ARK: Survival Ascended + +## 1. Purpose & Scope + +- **Game**: ARK: Survival Ascended +- **Module Slug**: `ark-survival-ascended` +- **Role**: Dedicated server module package for Gameplane. +- **Description**: Unreal Engine 5 dinosaur survival multiplayer dedicated server. Powered by `mschnitzer/asa-linux-server`, supporting crossplay, CurseForge modding, cluster travel, and Source RCON administration. + +--- + +## 2. Container Image & Architecture + +- **Base Image**: `mschnitzer/asa-linux-server:latest@sha256:0d69614f24da77e208b0ad453e1f5791fe2786fb8860269f8df5c26b527848f9` +- **Architecture**: `linux/amd64` +- **Runtime Model**: Wine/Proton execution of the Windows/Linux UE5 dedicated server binary with SteamCMD synchronization at container startup. +- **User & Execution Context**: Image user `gameserver` (UID `25000`), working directory `/home/gameserver`. + +--- + +## 3. Network Ports & Protocols + +Declared ports under `spec.ports`: + +| Port Name | Container Port | Protocol | Usage / Purpose | +|---|---|---|---| +| `game` | `7777` | `UDP` | Primary client game traffic | +| `peer` | `7778` | `UDP` | Peer / raw UDP communication | +| `rcon` | `27020` | `TCP` | Source RCON administrative console | + +--- + +## 4. Storage & Persistence Layout + +- **Mount Path**: `/home/gameserver` +- **Default Sizing**: `30Gi` +- **Persisted Content**: + - Saved worlds and tribe data (`server-files/ShooterGame/Saved/`) + - Server configuration files (`server-files/ShooterGame/Saved/Config/WindowsServer/GameUserSettings.ini`) + - Cluster shared travel data (`cluster-shared/`) + - Steam and SteamCMD caches (`steam-cache/`, `steamcmd-cache/`) +- **Non-Shadowing Invariant**: The volume at `/home/gameserver` hosts user data and steamcmd files; entrypoint binary `/usr/bin/start_server` resides in system path `/usr/bin`. + +--- + +## 5. Administration & Remote Console (RCON) + +- **Protocol**: `source` +- **Console Mode**: `rcon` +- **Authentication**: Password supplied via file `rcon-password.txt` or password secret. +- **Command Support**: Standard ARK RCON commands (`SaveWorld`, `DoExit`, `ServerChat`, `KickPlayer`, `BanPlayer`, `DestroyWildDinos`, `SetTimeOfDay`). + +--- + +## 6. Modding & Workshop Integration + +- **Modding Framework**: CurseForge ARK mods +- **Mod Directory Path**: Handled via launch argument `-mods=,...` appended to `ASA_START_PARAMS`. +- **Workshop Synchronization**: Automatic download by game client at boot via CurseForge API. + +--- + +## 7. Lifecycle & Graceful Shutdown + +- **Stop Command Sequence (`spec.capabilities.lifecycle.stop`)**: + ```yaml + capabilities: + lifecycle: + stop: + - "SaveWorld" + - "DoExit" + ``` +- **Signal Handling**: Issues `SaveWorld` and `DoExit` via Source RCON before container terminates, preventing world corruption and rollbacks. + +--- + +## 8. Key Invariants & Security + +- **User Matching**: Image runs strictly as UID `25000`. `spec.security.runAsUser: 25000` and `fsGroup: 25000` ensure non-root volume permissions for Proton. +- **Explicit Command**: `spec.command: ["/usr/bin/start_server"]` is declared to avoid non-interactive shell EOF termination. + +--- + +## 9. References & Upstream Documentation + +- Official Game Documentation: https://survivetheark.com/ +- Upstream Container Repository: https://github.com/mschnitzer/asa-linux-server +- Steam Dedicated Server AppID: `2430930` diff --git a/ark-survival-ascended/template.yaml b/ark-survival-ascended/template.yaml index b6bc488..2bcf339 100644 --- a/ark-survival-ascended/template.yaml +++ b/ark-survival-ascended/template.yaml @@ -109,6 +109,8 @@ spec: fsGroup: 25000 env: + - name: HOME + value: /home/gameserver - name: ENABLE_DEBUG value: "0" diff --git a/ark-survival-evolved/README.md b/ark-survival-evolved/README.md new file mode 100644 index 0000000..6a0c89f --- /dev/null +++ b/ark-survival-evolved/README.md @@ -0,0 +1,33 @@ +# ARK: Survival Evolved + +ARK: Survival Evolved dedicated server package for Gameplane. Runs on Linux with persistent world state, Steam A2S server browser discovery, Source RCON console, and multi-server cluster transfer support. + +## Install + +```sh +kubectl apply -f modules/ark-survival-evolved/template.yaml +``` + +## Console & RCON + +Remote management uses standard Source RCON on port 27020 TCP. The operator injects the password via `RCON_PASSWORD`. Console actions include `ServerChat` (broadcast), `SaveWorld` (world flush), and `KickPlayer` (moderation). + +The server stops cleanly by issuing `SaveWorld` and `DoExit` prior to container termination. + +## Ports + +| Name | Port | Protocol | Advertised | Purpose | +| ---- | ---- | -------- | ---------- | ------- | +| `game` | 7777 | UDP | yes | Primary gameplay traffic | +| `query` | 27015 | UDP | yes | Steam A2S browser query | +| `rcon` | 27020 | TCP | no | Source RCON administration | + +## Storage & Cluster Travel + +Storage is mounted at `/serverdata/ShooterGame/Saved` (35 GiB default). All map saves, tribe data, player profiles, and cross-shard cluster transfers (`clusters/` subfolder) persist across container restarts. + +To enable cluster travel between multiple ARK servers, supply the same `CLUSTER_ID` in each server's configuration and configure a shared volume or synchronize the cluster folder. + +## Sample + +See [`samples/gameserver.yaml`](samples/gameserver.yaml) for a deployment example. diff --git a/ark-survival-evolved/module.yaml b/ark-survival-evolved/module.yaml new file mode 100644 index 0000000..0cc6140 --- /dev/null +++ b/ark-survival-evolved/module.yaml @@ -0,0 +1,11 @@ +# yaml-language-server: $schema=../.schema/module.schema.json +apiVersion: gameplane.local/module/v1 +name: ark-survival-evolved +displayName: ARK: Survival Evolved +version: 1.0.0 +game: ark-survival-evolved +categories: [Survival, Open World, Multiplayer, Dinosaurs] +summary: ARK: Survival Evolved dedicated server with Source RCON console, cluster travel support, and persistent world state. +homepage: https://survivetheark.com/ +license: MIT +gameplaneMinVersion: 0.2.0-beta.7 diff --git a/ark-survival-evolved/samples/gameserver.yaml b/ark-survival-evolved/samples/gameserver.yaml new file mode 100644 index 0000000..618618d --- /dev/null +++ b/ark-survival-evolved/samples/gameserver.yaml @@ -0,0 +1,31 @@ +apiVersion: gameplane.local/v1alpha1 +kind: GameServer +metadata: + name: ark-survival-evolved-01 + namespace: gameplane-games +spec: + templateRef: + name: ark-survival-evolved + + config: + SESSION_NAME: "Gameplane ARK Server" + SERVER_PASSWORD: "" + RCON_PASSWORD: "secret-rcon-password" + MAX_PLAYERS: 70 + MAP_NAME: "TheIsland" + CLUSTER_ID: "cluster-alpha" + + networking: + expose: NodePort + portOverrides: + - name: game + nodePort: 30777 + - name: query + nodePort: 32015 + + storage: + size: 35Gi + + resources: + requests: { cpu: 2, memory: 8Gi } + limits: { cpu: 6, memory: 16Gi } diff --git a/ark-survival-evolved/specs.md b/ark-survival-evolved/specs.md new file mode 100644 index 0000000..13293c0 --- /dev/null +++ b/ark-survival-evolved/specs.md @@ -0,0 +1,86 @@ +# Gameplane Module Specification: ARK: Survival Evolved + +## 1. Purpose & Scope + +- **Game**: ARK: Survival Evolved +- **Module Slug**: `ark-survival-evolved` +- **Role**: Dedicated server module package for Gameplane. +- **Description**: Dedicated multiplayer server for Studio Wildcard's ARK: Survival Evolved. Manages persistent world saves, multi-shard cluster transfers, and Source RCON administration. + +--- + +## 2. Container Image & Architecture + +- **Base Image**: `ghcr.io/valgulnecron/gameplane/ark-survival-evolved:latest@sha256:0000000000000000000000000000000000000000000000000000000000000000` +- **Architecture**: `linux/amd64` +- **Runtime Model**: SteamCMD Linux dedicated server (`ShooterGameServer`). +- **User & Execution Context**: UID 1000, GID 1000, working directory `/serverdata`. + +--- + +## 3. Network Ports & Protocols + +Declared ports under `spec.ports`: + +| Port Name | Container Port | Protocol | Usage / Purpose | +|---|---|---|---| +| `game` | 7777 | UDP | Primary gameplay traffic | +| `query` | 27015 | UDP | Steam A2S browser discovery | +| `rcon` | 27020 | TCP | Remote administrative console (Source RCON) | + +--- + +## 4. Storage & Persistence Layout + +- **Mount Path**: `/serverdata/ShooterGame/Saved` +- **Default Sizing**: `35Gi` +- **Persisted Content**: + - Saved worlds, tribe data, and dinosaur entities (`SavedArks/`) + - Server configuration files (`Config/LinuxServer/GameUserSettings.ini`) + - Cross-server cluster transfers (`clusters/`) +- **Non-Shadowing Invariant**: The mount path isolates the `Saved/` directory without shadowing the server binaries in `/serverdata`. + +--- + +## 5. Administration & Remote Console (RCON) + +- **Protocol**: `source` +- **Console Mode**: `rcon` +- **Authentication**: Password supplied via `RCON_PASSWORD`. +- **Command Support**: In-game moderation (`KickPlayer`, `BanPlayer`), world saving (`SaveWorld`), broadcasts (`ServerChat`). + +--- + +## 6. Modding & Workshop Integration + +- **Modding Framework**: Steam Workshop (`-automanagedmods`). +- **Mod Directory Path**: Managed within image install. + +--- + +## 7. Lifecycle & Graceful Shutdown + +- **Stop Command Sequence (`spec.capabilities.lifecycle.stop`)**: + ```yaml + capabilities: + lifecycle: + stop: + - "SaveWorld" + - "DoExit" + ``` +- **Signal Handling**: Server cleanly saves before terminating on `SIGINT`/`SIGTERM`. + +--- + +## 8. Key Invariants & Security + +- **User Matching**: `spec.security.runAsUser: 1000` matches image user. +- **Environment**: `spec.env` contains `HOME=/serverdata`. +- **Filesystem Permissions**: `spec.security.fsGroup: 1000` ensures volume read/write permissions. + +--- + +## 9. References & Upstream Documentation + +- ARK: Survival Evolved Dedicated Server: https://ark.wiki.gg/wiki/Dedicated_server_setup +- Steam Dedicated Server AppID: 376030 diff --git a/ark-survival-evolved/template.yaml b/ark-survival-evolved/template.yaml new file mode 100644 index 0000000..87055a5 --- /dev/null +++ b/ark-survival-evolved/template.yaml @@ -0,0 +1,146 @@ +# yaml-language-server: $schema=../.schema/gametemplate.schema.json +# Gameplane GameTemplate for ARK: Survival Evolved (dedicated server). +# +# Dedicated server for ARK: Survival Evolved running on Linux. +# Persistent world state and cluster directory live under /serverdata/ShooterGame/Saved. +# +# Cluster-scoped; apply once per cluster: +# kubectl apply -f modules/ark-survival-evolved/template.yaml + +apiVersion: gameplane.local/v1alpha1 +kind: GameTemplate +metadata: + name: ark-survival-evolved + labels: + gameplane.local/module: ark-survival-evolved +spec: + displayName: "ARK: Survival Evolved" + game: ark-survival-evolved + version: 1.0.0 + categories: [Survival, Open World, Multiplayer, Dinosaurs] + accentColor: "#00b2e2" + description: | + ARK: Survival Evolved dedicated server. Source RCON powers remote administration + and server actions. Persistent world state and cross-shard cluster transfers live + under `/serverdata/ShooterGame/Saved`. + + image: ghcr.io/valgulnecron/gameplane/ark-survival-evolved:latest@sha256:0000000000000000000000000000000000000000000000000000000000000000 + + versions: + - id: latest + displayName: "Latest" + image: ghcr.io/valgulnecron/gameplane/ark-survival-evolved:latest@sha256:0000000000000000000000000000000000000000000000000000000000000000 + default: true + + env: + - name: HOME + value: /serverdata + + security: + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + + ports: + - name: game + containerPort: 7777 + protocol: UDP + advertise: true + - name: query + containerPort: 27015 + protocol: UDP + advertise: true + - name: rcon + containerPort: 27020 + protocol: TCP + advertise: false + + storage: + size: 35Gi + mountPath: /serverdata/ShooterGame/Saved + + resources: + requests: {cpu: "2", memory: 8Gi} + limits: {cpu: "6", memory: 16Gi} + + rcon: + protocol: source + port: 27020 + passwordEnv: RCON_PASSWORD + + consoleMode: rcon + + probes: + startup: + tcpSocket: + port: rcon + initialDelaySeconds: 60 + periodSeconds: 15 + failureThreshold: 60 + readiness: + tcpSocket: + port: rcon + periodSeconds: 10 + failureThreshold: 6 + liveness: + tcpSocket: + port: rcon + periodSeconds: 30 + failureThreshold: 5 + + capabilities: + lifecycle: + stop: ["SaveWorld", "DoExit"] + actions: + - id: broadcast + displayName: Broadcast + icon: megaphone + group: Server + command: 'ServerChat "{{.Params.message}}"' + params: + - name: message + displayName: Message + type: string + required: true + - id: save-world + displayName: Save World + icon: save + group: World + command: "SaveWorld" + - id: kick-player + displayName: Kick Player + icon: user-x + group: Moderation + command: "KickPlayer {{.Params.user}}" + params: + - name: user + displayName: Player SteamID + type: string + required: true + + configSchema: + - name: SESSION_NAME + displayName: Session Name + type: string + default: "Gameplane ARK Server" + - name: SERVER_PASSWORD + displayName: Server Password + type: password + default: "" + - name: RCON_PASSWORD + displayName: RCON Password + type: password + default: "" + - name: MAX_PLAYERS + displayName: Max Players + type: int + default: 70 + - name: MAP_NAME + displayName: Map Name + type: string + default: "TheIsland" + - name: CLUSTER_ID + displayName: Cluster ID + description: Shared cluster identifier for cross-ark transfers. + type: string + default: "" diff --git a/arma-reforger/README.md b/arma-reforger/README.md new file mode 100644 index 0000000..3764447 --- /dev/null +++ b/arma-reforger/README.md @@ -0,0 +1,32 @@ +# Arma Reforger + +Arma Reforger dedicated server package for Gameplane. Runs on Bohemia Interactive's Enfusion engine with interactive PTY stdin console, persistent profile and saves, and Steam Workshop modding support. + +## Install + +```sh +kubectl apply -f modules/arma-reforger/template.yaml +``` + +## Console + +No RCON protocol. The **Console** tab attaches directly to container stdin/stdout (pty) for administrative commands. Server stop issues `save` prior to pod termination. + +## SteamCMD Login + +Most dedicated servers allow anonymous SteamCMD download. If an authenticated Steam login is required to pull specific game builds or workshop dependencies, provide credentials in `STEAM_USER` and `STEAM_PASSWORD`. + +## Ports + +| Name | Port | Protocol | Advertised | Purpose | +| ---- | ---- | -------- | ---------- | ------- | +| `game` | 2001 | UDP | yes | Client gameplay | +| `query` | 17777 | UDP | yes | Steam / A2S query | + +## Storage + +Persistent storage is mounted at `/home/steam/.local/share/ArmaReforgerServer` (30 GiB default). All world state, player profiles, and downloaded Workshop mods persist across container restarts. + +## Sample + +See [`samples/gameserver.yaml`](samples/gameserver.yaml) for a deployment example. diff --git a/arma-reforger/module.yaml b/arma-reforger/module.yaml new file mode 100644 index 0000000..5dd6757 --- /dev/null +++ b/arma-reforger/module.yaml @@ -0,0 +1,11 @@ +# yaml-language-server: $schema=../.schema/module.schema.json +apiVersion: gameplane.local/module/v1 +name: arma-reforger +displayName: Arma Reforger +version: 1.0.0 +game: arma-reforger +categories: [Shooter, Tactical, Military, Simulation] +summary: Arma Reforger dedicated server (Enfusion Engine) with stdin PTY console, persistent profile/world saves, and Steam Workshop support. +homepage: https://reforger.armaplatform.com/ +license: MIT +gameplaneMinVersion: 0.2.0-beta.7 diff --git a/arma-reforger/samples/gameserver.yaml b/arma-reforger/samples/gameserver.yaml new file mode 100644 index 0000000..deb13c2 --- /dev/null +++ b/arma-reforger/samples/gameserver.yaml @@ -0,0 +1,29 @@ +apiVersion: gameplane.local/v1alpha1 +kind: GameServer +metadata: + name: arma-reforger-01 + namespace: gameplane-games +spec: + templateRef: + name: arma-reforger + + config: + SERVER_NAME: "Gameplane Arma Reforger Server" + SERVER_PASSWORD: "" + ADMIN_PASSWORD: "secret-admin-pass" + MAX_PLAYERS: 64 + + networking: + expose: NodePort + portOverrides: + - name: game + nodePort: 32001 + - name: query + nodePort: 31777 + + storage: + size: 30Gi + + resources: + requests: { cpu: 2, memory: 8Gi } + limits: { cpu: 6, memory: 16Gi } diff --git a/arma-reforger/specs.md b/arma-reforger/specs.md new file mode 100644 index 0000000..8e12446 --- /dev/null +++ b/arma-reforger/specs.md @@ -0,0 +1,84 @@ +# Gameplane Module Specification: Arma Reforger + +## 1. Purpose & Scope + +- **Game**: Arma Reforger +- **Module Slug**: `arma-reforger` +- **Role**: Dedicated server module package for Gameplane. +- **Description**: Dedicated multiplayer server for Bohemia Interactive's Arma Reforger, running on the Enfusion engine with interactive PTY console and Steam Workshop support. + +--- + +## 2. Container Image & Architecture + +- **Base Image**: `ghcr.io/valgulnecron/gameplane/arma-reforger:latest@sha256:0000000000000000000000000000000000000000000000000000000000000000` +- **Architecture**: `linux/amd64` +- **Runtime Model**: SteamCMD Linux dedicated server (`ArmaReforgerServer`). +- **User & Execution Context**: UID 1000, GID 1000, working directory `/home/steam`. + +--- + +## 3. Network Ports & Protocols + +Declared ports under `spec.ports`: + +| Port Name | Container Port | Protocol | Usage / Purpose | +|---|---|---|---| +| `game` | 2001 | UDP | Primary client gameplay traffic | +| `query` | 17777 | UDP | Steam A2S query discovery | + +--- + +## 4. Storage & Persistence Layout + +- **Mount Path**: `/home/steam/.local/share/ArmaReforgerServer` +- **Default Sizing**: `30Gi` +- **Persisted Content**: + - Profile state and saved game sessions + - Server configs (`ArmaReforgerServer.json`) + - Downloaded Workshop addons (`addons/`) +- **Non-Shadowing Invariant**: The mount path isolates server state under `.local/share/ArmaReforgerServer`. + +--- + +## 5. Administration & Remote Console (RCON) + +- **Protocol**: `none` +- **Console Mode**: `pty` (attaches to container stdin) +- **Authentication**: Admin password in server configuration. +- **Command Support**: In-engine CLI commands (`save`, `say`). + +--- + +## 6. Modding & Workshop Integration + +- **Modding Framework**: Bohemia Interactive Workshop / addons. +- **Mod Directory Path**: `addons` + +--- + +## 7. Lifecycle & Graceful Shutdown + +- **Stop Command Sequence (`spec.capabilities.lifecycle.stop`)**: + ```yaml + capabilities: + lifecycle: + stop: + - "save" + ``` +- **Signal Handling**: Dedicated server initiates state flush on `SIGINT`/`SIGTERM`. + +--- + +## 8. Key Invariants & Security + +- **User Matching**: `spec.security.runAsUser: 1000` matches image user (`steam`). +- **Environment**: `spec.env` contains `HOME=/home/steam`. +- **Filesystem Permissions**: `spec.security.fsGroup: 1000` configured for volume ownership. + +--- + +## 9. References & Upstream Documentation + +- Bohemia Interactive Community Wiki - Arma Reforger Server Hosting: https://community.bistudio.com/wiki/Arma_Reforger:Server_Hosting +- Steam Dedicated Server AppID: 1874900 diff --git a/arma-reforger/template.yaml b/arma-reforger/template.yaml new file mode 100644 index 0000000..4dd0479 --- /dev/null +++ b/arma-reforger/template.yaml @@ -0,0 +1,127 @@ +# yaml-language-server: $schema=../.schema/gametemplate.schema.json +# Gameplane GameTemplate for Arma Reforger (dedicated server). +# +# Runs Bohemia Interactive's Enfusion-engine dedicated server. Console access +# is provided via container PTY (stdin/stdout). World saves, profile settings, +# and downloaded Workshop mods persist under /home/steam/.local/share/ArmaReforgerServer. +# +# Cluster-scoped; apply once per cluster: +# kubectl apply -f modules/arma-reforger/template.yaml + +apiVersion: gameplane.local/v1alpha1 +kind: GameTemplate +metadata: + name: arma-reforger + labels: + gameplane.local/module: arma-reforger +spec: + displayName: Arma Reforger + game: arma-reforger + version: 1.0.0 + categories: [Shooter, Tactical, Military, Simulation] + accentColor: "#3a5a40" + description: | + Arma Reforger dedicated server running Bohemia Interactive's Enfusion engine. + Console access is available via container PTY stdin/stdout. Persistent server + saves and Workshop mods reside under `/home/steam/.local/share/ArmaReforgerServer`. + + image: ghcr.io/valgulnecron/gameplane/arma-reforger:latest@sha256:0000000000000000000000000000000000000000000000000000000000000000 + + versions: + - id: latest + displayName: "Latest (Enfusion)" + image: ghcr.io/valgulnecron/gameplane/arma-reforger:latest@sha256:0000000000000000000000000000000000000000000000000000000000000000 + default: true + + env: + - name: HOME + value: /home/steam + + security: + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + + ports: + - name: game + containerPort: 2001 + protocol: UDP + advertise: true + - name: query + containerPort: 17777 + protocol: UDP + advertise: true + + storage: + size: 30Gi + mountPath: /home/steam/.local/share/ArmaReforgerServer + + resources: + requests: {cpu: "2", memory: 8Gi} + limits: {cpu: "6", memory: 16Gi} + + rcon: + protocol: none + + consoleMode: pty + + capabilities: + lifecycle: + stop: ["save"] + actions: + - id: broadcast + displayName: Broadcast + icon: megaphone + group: Server + transport: stdin + command: 'say "{{.Params.message}}"' + params: + - name: message + displayName: Message + type: string + required: true + - id: save-world + displayName: Save World + icon: save + group: World + transport: stdin + command: "save" + mods: + path: addons + extensions: [".pak", ".bin"] + install: + allowedHosts: + - github.com + - .githubusercontent.com + maxSizeMB: 512 + registry: + providers: + - provider: steam + steamAppID: 1874880 + + configSchema: + - name: SERVER_NAME + displayName: Server Name + type: string + default: "Gameplane Arma Reforger Server" + - name: SERVER_PASSWORD + displayName: Server Password + type: password + default: "" + - name: ADMIN_PASSWORD + displayName: Admin Password + type: password + default: "" + - name: MAX_PLAYERS + displayName: Max Players + type: int + default: 64 + - name: STEAM_USER + displayName: Steam Username + description: Steam account with Arma Reforger license if authenticated SteamCMD login is needed. Leave blank for anonymous. + type: string + default: "" + - name: STEAM_PASSWORD + displayName: Steam Password + type: password + default: "" diff --git a/beammp/Dockerfile b/beammp/Dockerfile new file mode 100644 index 0000000..f78a59d --- /dev/null +++ b/beammp/Dockerfile @@ -0,0 +1,39 @@ +FROM alpine:3.20 + +LABEL org.opencontainers.image.title="Gameplane BeamMP Dedicated Server" \ + org.opencontainers.image.description="Dedicated server container for BeamNG.drive (BeamMP) with diagnostic idle support for missing auth keys" \ + org.opencontainers.image.authors="Gameplane Maintainers" \ + org.opencontainers.image.source="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/ValgulNecron/gameplane" + +RUN apk add --no-cache \ + bash \ + curl \ + ca-certificates \ + tini \ + procps \ + libstdc++ \ + libgcc \ + openssl \ + zlib + +# Create gameplane runtime user with fixed UID/GID 1000 +RUN addgroup -g 1000 gameplane && \ + adduser -u 1000 -G gameplane -h /home/gameplane -s /bin/bash -D gameplane + +# Prepare directories +RUN mkdir -p /serverdata /opt/beammp /server && \ + ln -s /serverdata /server/Root && \ + chown -R gameplane:gameplane /serverdata /opt/beammp /home/gameplane /server + +COPY --chown=gameplane:gameplane entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +USER 1000:1000 +WORKDIR /serverdata +ENV HOME=/home/gameplane \ + USER=gameplane \ + PORT=30814 + +EXPOSE 30814/udp 30814/tcp + +ENTRYPOINT ["/sbin/tini", "--", "/entrypoint.sh"] diff --git a/beammp/README.md b/beammp/README.md new file mode 100644 index 0000000..bac40e8 --- /dev/null +++ b/beammp/README.md @@ -0,0 +1,40 @@ +# BeamMP + +BeamMP dedicated server for BeamNG.drive multiplayer soft-body physics simulation, powered by a Gameplane-owned container with diagnostic idle support (FR-013). + +## Install + +```sh +kubectl apply -f modules/beammp/template.yaml +``` + +## Server AuthKey + +A BeamMP Server AuthKey is required to advertise your server on the BeamMP master list. Register a key at [beammp.com](https://beammp.com) and supply it in `BEAMMP_AUTH_KEY`. + +If omitted, the server enters a non-crashing graceful diagnostic idle state (FR-013) that prints setup instructions in the pod log. + +## Mods (Vehicles & Maps) + +BeamNG vehicles, tracks, and map mods (`.zip` packages) can be dropped or uploaded into the `Resources/` folder managed through the **Mods** tab. + +## Console (PTY) + +BeamMP dedicated servers interact via standard input/output. The Gameplane Console tab connects via container PTY (`consoleMode: pty`). + +## Ports + +| Name | Port | Protocol | Advertised | Purpose | +| ---- | ---- | -------- | ---------- | ------- | +| `game` | 30814 | UDP | yes | BeamMP client vehicle physics sync | +| `auth` | 30814 | TCP | yes | BeamMP server authentication and handshake | + +## Storage + +Persistent storage is mounted at `/server/Root` (5 GiB default), holding: +- `ServerConfig.toml` +- Downloaded and uploaded vehicle and map packages (`Resources/`) + +## Sample + +See [`samples/gameserver.yaml`](samples/gameserver.yaml) for a deployment manifest example. diff --git a/beammp/entrypoint.sh b/beammp/entrypoint.sh new file mode 100644 index 0000000..f1da602 --- /dev/null +++ b/beammp/entrypoint.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash +set -eo pipefail + +PID_GAME="" + +term_handler() { + echo "[gameplane-beammp] SIGTERM/SIGINT received, initiating graceful shutdown..." + if [ -n "$PID_GAME" ]; then + echo "[gameplane-beammp] Stopping BeamMP-Server process $PID_GAME..." + kill -TERM "$PID_GAME" 2>/dev/null || true + fi + wait + echo "[gameplane-beammp] Clean shutdown complete." + exit 0 +} + +trap term_handler SIGTERM SIGINT + +# Resolve auth key from environment +BEAM_KEY="${BEAMMP_AUTH_KEY:-${AUTH_KEY:-}}" + +# FR-013: Non-crashing diagnostic idle for missing auth key +if [ -z "$BEAM_KEY" ]; then + echo "========================================================================" + echo "DIAGNOSTIC: BeamMP Server AuthKey is not configured!" + echo "The BeamMP dedicated server requires a valid authentication key from" + echo "the BeamMP community portal to register on the master list." + echo "" + echo "Step-by-step instructions to obtain and configure your key:" + echo "1. Sign in to the BeamMP server portal at https://beammp.com" + echo "2. Navigate to the 'Keys' management section." + echo "3. Generate a new server authentication key." + echo "4. Copy the generated key." + echo "5. Configure the key in your GameServer specification:" + echo " spec:" + echo " env:" + echo " - name: BEAMMP_AUTH_KEY" + echo " value: \"your_beammp_key_here\"" + echo "" + echo "Entering graceful idle mode. The server will not crash-loop." + echo "Update your manifest and restart the server once configured." + echo "========================================================================" + + while true; do + sleep 3600 & + wait $! + done +fi + +CONFIG_FILE="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/serverdata/ServerConfig.toml" + +# Generate or update ServerConfig.toml with auth key and port +if [ ! -f "$CONFIG_FILE" ]; then + cat < "$CONFIG_FILE" +[General] +AuthKey = "$BEAM_KEY" +Port = ${PORT:-30814} +Name = "Gameplane BeamMP Server" +Description = "BeamMP Server powered by Gameplane" +MaxPlayers = 10 +Private = false +Debug = false +Map = "/levels/gridmap_v2/info.json" +ResourceFolder = "Resources" +EOF +else + # Update AuthKey in existing configuration + sed -i "s/^AuthKey = .*/AuthKey = \"$BEAM_KEY\"/" "$CONFIG_FILE" +fi + +SERVER_BIN="/opt/beammp/BeamMP-Server" +if [ ! -f "$SERVER_BIN" ] && [ -f "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/serverdata/BeamMP-Server" ]; then + SERVER_BIN="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/serverdata/BeamMP-Server" +fi + +echo "[gameplane-beammp] Starting BeamMP dedicated server on port ${PORT:-30814}..." + +if [ -f "$SERVER_BIN" ]; then + "$SERVER_BIN" "$@" & + PID_GAME=$! + wait "$PID_GAME" 2>/dev/null || true +else + echo "[gameplane-beammp] Note: BeamMP-Server binary not found at $SERVER_BIN." + echo "[gameplane-beammp] Entering diagnostic wait mode..." + while true; do + sleep 60 & + wait $! + done +fi diff --git a/beammp/module.yaml b/beammp/module.yaml new file mode 100644 index 0000000..3fa5560 --- /dev/null +++ b/beammp/module.yaml @@ -0,0 +1,11 @@ +# yaml-language-server: $schema=../.schema/module.schema.json +apiVersion: gameplane.local/module/v1 +name: beammp +displayName: BeamMP +version: 1.0.0 +game: beammp +categories: [Simulation, Driving, Physics, Multiplayer] +summary: BeamMP dedicated server for BeamNG.drive multiplayer with PTY console, custom vehicle/map mods, and diagnostic idle. +homepage: https://beammp.com/ +license: MIT +gameplaneMinVersion: 0.2.0-beta.7 diff --git a/beammp/samples/gameserver.yaml b/beammp/samples/gameserver.yaml new file mode 100644 index 0000000..2fa1cca --- /dev/null +++ b/beammp/samples/gameserver.yaml @@ -0,0 +1,27 @@ +apiVersion: gameplane.local/v1alpha1 +kind: GameServer +metadata: + name: beammp-01 + namespace: gameplane-games +spec: + templateRef: + name: beammp + + config: + BEAMMP_AUTH_KEY: "change-me-auth-key" + SERVER_NAME: "Gameplane BeamMP Server" + MAP: "/levels/gridmap_v2/info.json" + MAX_PLAYERS: 10 + + networking: + expose: NodePort + portOverrides: + - name: game + nodePort: 30814 + + storage: + size: 5Gi + + resources: + requests: { cpu: 1, memory: 2Gi } + limits: { cpu: 4, memory: 4Gi } diff --git a/beammp/specs.md b/beammp/specs.md new file mode 100644 index 0000000..b68a0c5 --- /dev/null +++ b/beammp/specs.md @@ -0,0 +1,79 @@ +# Gameplane Module Specification: BeamMP + +## 1. Purpose & Scope + +- **Game**: BeamNG.drive (BeamMP Multiplayer) +- **Module Slug**: `beammp` +- **Role**: Dedicated server module package for Gameplane. +- **Description**: Dedicated multiplayer server for BeamNG.drive soft-body vehicle physics simulation. Features custom vehicle/map mod loading and non-crashing diagnostic idle when the auth key is missing. + +--- + +## 2. Container Image & Architecture + +- **Base Image**: `ghcr.io/valgulnecron/gameplane/beammp:latest@sha256:0000000000000000000000000000000000000000000000000000000000000000` +- **Architecture**: `linux/amd64` +- **Runtime Model**: Standalone C++ binary (`BeamMP-Server`) executing natively under Alpine Linux. +- **User & Execution Context**: UID `1000`, GID `1000`, working directory `/serverdata`. + +--- + +## 3. Network Ports & Protocols + +Declared ports under `spec.ports`: + +| Port Name | Container Port | Protocol | Usage / Purpose | +|---|---|---|---| +| `game` | `30814` | `UDP` | Primary client physics packet stream | +| `auth` | `30814` | `TCP` | Client authentication and TCP sync | + +--- + +## 4. Storage & Persistence Layout + +- **Mount Path**: `/server/Root` +- **Default Sizing**: `5Gi` +- **Persisted Content**: + - `ServerConfig.toml` configuration + - Custom vehicles, levels, and track mods (`Resources/`) +- **Non-Shadowing Invariant**: The mount path `/server/Root` is symlinked to `/serverdata` and does not shadow the application binary in `/opt/beammp`. + +--- + +## 5. Administration & Remote Console (RCON) + +- **Protocol**: `none` +- **Console Mode**: `pty` +- **Authentication**: N/A (interactive terminal console). +- **Command Support**: Standard BeamMP CLI commands issued via stdin. + +--- + +## 6. Modding & Workshop Integration + +- **Modding Framework**: Custom vehicle and map `.zip` archives +- **Mod Directory Path**: `Resources` +- **Workshop Synchronization**: Manual file drop or archive upload via Gameplane Mods tab. + +--- + +## 7. Lifecycle & Graceful Shutdown + +- **Stop Command Sequence (`spec.capabilities.lifecycle.stop`)**: `[]` (Stateless vehicle session; processes terminate cleanly on SIGTERM). +- **Signal Handling**: Container intercepts `SIGTERM` / `SIGINT` and halts `BeamMP-Server` process cleanly. + +--- + +## 8. Key Invariants & Security + +- **User Matching**: `spec.security.runAsUser: 1000` matches image user. +- **Environment**: `spec.env` defines `HOME: /home/gameplane`. +- **Filesystem Permissions**: `spec.security.fsGroup: 1000` guarantees write permission on `/server/Root`. +- **Diagnostic Idle**: Graceful idle without crash-looping if `BEAMMP_AUTH_KEY` is missing (FR-013). + +--- + +## 9. References & Upstream Documentation + +- Official Website: https://beammp.com/ +- Server Documentation: https://wiki.beammp.com/en/home/server-installation diff --git a/beammp/template.yaml b/beammp/template.yaml new file mode 100644 index 0000000..97ece40 --- /dev/null +++ b/beammp/template.yaml @@ -0,0 +1,99 @@ +# yaml-language-server: $schema=../.schema/gametemplate.schema.json +# Gameplane GameTemplate for BeamMP (BeamNG.drive dedicated server). +# +# Backed by Gameplane-owned BeamMP container with non-crashing diagnostic idle +# when the auth key is missing (FR-013). +# +# Cluster-scoped; apply once per cluster: +# kubectl apply -f modules/beammp/template.yaml + +apiVersion: gameplane.local/v1alpha1 +kind: GameTemplate +metadata: + name: beammp + labels: + gameplane.local/module: beammp +spec: + displayName: BeamMP + game: beammp + version: 1.0.0 + categories: [Simulation, Driving, Physics, Multiplayer] + accentColor: "#f97316" + description: | + BeamMP dedicated server for BeamNG.drive multiplayer physics simulation. + Interactive console connects via container PTY stdin/stdout. Mods and + custom vehicle/map packages install into the Resources directory. + + image: ghcr.io/valgulnecron/gameplane/beammp:latest@sha256:0000000000000000000000000000000000000000000000000000000000000000 + + env: + - name: HOME + value: /home/gameplane + + security: + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + + ports: + - name: game + containerPort: 30814 + protocol: UDP + advertise: true + - name: auth + containerPort: 30814 + protocol: TCP + advertise: true + + storage: + size: 5Gi + mountPath: /server/Root + + resources: + requests: {cpu: "1", memory: 2Gi} + limits: {cpu: "4", memory: 4Gi} + + rcon: + protocol: none + + consoleMode: pty + + probes: + readiness: + tcpSocket: + port: auth + initialDelaySeconds: 15 + periodSeconds: 10 + failureThreshold: 10 + + capabilities: + lifecycle: + stop: [] + mods: + path: Resources + extensions: [".zip"] + install: + allowedHosts: + - github.com + - .githubusercontent.com + maxSizeMB: 512 + + configSchema: + - name: BEAMMP_AUTH_KEY + displayName: Server AuthKey + description: BeamMP server authentication key from beammp.com. + type: password + required: true + default: "" + - name: SERVER_NAME + displayName: Server Name + type: string + default: "Gameplane BeamMP Server" + - name: MAP + displayName: Map Path + type: string + default: "/levels/gridmap_v2/info.json" + - name: MAX_PLAYERS + displayName: Max Players + type: int + default: 10 diff --git a/build-images.sh b/build-images.sh new file mode 100755 index 0000000..f46aebd --- /dev/null +++ b/build-images.sh @@ -0,0 +1,134 @@ +#!/usr/bin/env bash +# build-images.sh — build, push, and sign Gameplane-owned container images. +# +# Builds purpose-built Dockerfiles for games requiring auxiliary service +# supervision (FR-012) or non-crashing diagnostic idle for missing tokens (FR-013): +# - fivem (txAdmin + embedded database) +# - farming-simulator-25 (headless Wine + dummy X11 + web admin portal) +# - euro-truck-simulator-2 (ETS2 server logon token diagnostic idle) +# - beammp (BeamMP auth key diagnostic idle) +# +# Usage: +# modules/build-images.sh build # build all 4 images locally +# modules/build-images.sh build --name fivem # build only fivem +# modules/build-images.sh push --registry ghcr.io/valgulnecron/gameplane --sign + +set -euo pipefail + +KNOWN_IMAGES=("fivem" "farming-simulator-25" "euro-truck-simulator-2" "beammp") + +usage() { + cat < [flags] + +Commands: + build Build container images locally + push Build and push container images to an OCI registry + +Flags: + --registry Registry prefix (e.g. ghcr.io/valgulnecron/gameplane) [required for push] + --name Target a specific image (fivem, farming-simulator-25, euro-truck-simulator-2, beammp) + --tag Override image tag (default: latest or module version) + --sign cosign-sign each pushed image by manifest digest + --plain-http Use plain HTTP (for local testing registries) + --insecure Skip TLS verification + --tlog-upload Record signature in public Rekor transparency log (default: offline) +USAGE +} + +ACTION="${1:-}" +[[ -n "$ACTION" ]] || { usage; exit 1; } +shift + +REGISTRY="" +TARGET="" +OVERRIDE_TAG="" +SIGN=0 +PLAIN_HTTP="" +INSECURE="" +TLOG_UPLOAD=0 + +while [[ $# -gt 0 ]]; do + case "$1" in + --registry) REGISTRY="$2"; shift 2 ;; + --name) TARGET="$2"; shift 2 ;; + --tag) OVERRIDE_TAG="$2"; shift 2 ;; + --sign) SIGN=1; shift ;; + --plain-http) PLAIN_HTTP=1; shift ;; + --insecure) INSECURE=1; shift ;; + --tlog-upload) TLOG_UPLOAD=1; shift ;; + -h|--help) usage; exit 0 ;; + *) echo "Unknown flag: $1" >&2; exit 1 ;; + esac +done + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +if [[ "$ACTION" == "push" ]] && [[ -z "$REGISTRY" ]]; then + echo "Error: --registry is required for push" >&2 + exit 1 +fi + +build_and_push() { + local name="$1" + local dir="$SCRIPT_DIR/$name" + + if [[ ! -f "$dir/Dockerfile" ]]; then + echo "Skipping $name: no Dockerfile found at $dir/Dockerfile" >&2 + return 0 + fi + + local version="latest" + if [[ -n "$OVERRIDE_TAG" ]]; then + version="$OVERRIDE_TAG" + elif [[ -f "$dir/module.yaml" ]] && command -v python3 >/dev/null 2>&1; then + local v + v="$(python3 -c "import yaml; print(yaml.safe_load(open('$dir/module.yaml'))['version'])" 2>/dev/null || true)" + [[ -n "$v" ]] && version="$v" + fi + + local image_tag="gameplane-$name:$version" + if [[ -n "$REGISTRY" ]]; then + image_tag="$REGISTRY/$name:$version" + fi + + echo "==> Building $image_tag from $dir/Dockerfile" + docker build -t "$image_tag" -f "$dir/Dockerfile" "$dir" + + if [[ "$ACTION" == "push" ]]; then + echo "==> Pushing $image_tag" + docker push "$image_tag" + + if (( SIGN )); then + local digest + digest="$(docker inspect --format='{{index .RepoDigests 0}}' "$image_tag" 2>/dev/null | grep -o 'sha256:[a-f0-9]*' || true)" + if [[ -z "$digest" ]]; then + echo "Error: could not determine digest for $image_tag" >&2 + return 1 + fi + + echo "==> Signing $REGISTRY/$name@$digest" + local sargs=( sign --key env://COSIGN_PRIVATE_KEY --yes + --new-bundle-format=false --use-signing-config=false ) + if (( TLOG_UPLOAD )); then + echo ">> (recording signature in public Rekor transparency log)" + else + sargs+=( --tlog-upload=false ) + fi + [[ -n "$PLAIN_HTTP" ]] && sargs+=( --allow-http-registry ) + [[ -n "$INSECURE" ]] && sargs+=( --allow-insecure-registry ) + cosign "${sargs[@]}" "$REGISTRY/$name@$digest" + fi + fi +} + +TARGETS=("${KNOWN_IMAGES[@]}") +if [[ -n "$TARGET" ]]; then + TARGETS=("$TARGET") +fi + +for img in "${TARGETS[@]}"; do + build_and_push "$img" +done + +echo "==> Done." diff --git a/cs2/specs.md b/cs2/specs.md new file mode 100644 index 0000000..19e8201 --- /dev/null +++ b/cs2/specs.md @@ -0,0 +1,88 @@ +# Gameplane Module Specification: Counter-Strike 2 + +## 1. Purpose & Scope + +- **Game**: Counter-Strike 2 +- **Module Slug**: `cs2` +- **Role**: Dedicated server module package for Gameplane. +- **Description**: Tactical competitive first-person shooter dedicated server powered by Valve's Source 2 engine. Matches are session-based with optional Metamod / CounterStrikeSharp plugin support and Steam Workshop map rotation. + +--- + +## 2. Container Image & Architecture + +- **Base Image**: `joedwards32/cs2:latest@sha256:41b826d6280d1aa9e41c866a6d88cc7f523e027f16c47a313b20c2d7a17f2680` +- **Architecture**: `linux/amd64` +- **Runtime Model**: Native Source 2 Linux dedicated server binary fetched and updated via SteamCMD. +- **User & Execution Context**: UID `1000`, GID `1000`, working directory `/home/steam/cs2-dedicated`. + +--- + +## 3. Network Ports & Protocols + +Declared ports under `spec.ports`: + +| Port Name | Container Port | Protocol | Usage / Purpose | +|---|---|---|---| +| `game` | `27015` | `UDP` | Primary client game traffic & Steam server queries | +| `rcon` | `27015` | `TCP` | Source RCON administrative console | +| `gotv` | `27020` | `UDP` | SourceTV / GOTV spectator relay broadcast | + +--- + +## 4. Storage & Persistence Layout + +- **Mount Path**: `/home/steam/cs2-dedicated` +- **Default Sizing**: `60Gi` (minimum installation requirement for base game + updates) +- **Persisted Content**: + - Downloaded Source 2 dedicated server binaries and assets + - Server configurations (`game/csgo/cfg/server.cfg`) + - Workshop map cache and downloaded addons + - Metamod / CounterStrikeSharp plugins (`game/csgo/addons`) +- **Non-Shadowing Invariant**: Mounted volume contains the dedicated server root install directory created at runtime; does not shadow container base OS or entrypoint. + +--- + +## 5. Administration & Remote Console (RCON) + +- **Protocol**: `source` +- **Console Mode**: `rcon` +- **Authentication**: Password supplied via `spec.rcon.passwordEnv: CS2_RCONPW`. +- **Command Support**: Standard Valve Source RCON console commands (e.g., `say`, `mp_restartgame`, `changelevel`, `exec`, `bot_add`, `bot_kick`). + +--- + +## 6. Modding & Workshop Integration + +- **Modding Framework**: Metamod:Source and CounterStrikeSharp (CoreCLR) +- **Mod Directory Path**: `game/csgo/addons` (extensions: `.dll`, `.so`) +- **Workshop Synchronization**: Steam Workshop collections configured via `CS2_HOST_WORKSHOP_COLLECTION` and downloaded natively by `srcds` at startup. + +--- + +## 7. Lifecycle & Graceful Shutdown + +- **Stop Command Sequence (`spec.capabilities.lifecycle.stop`)**: + ```yaml + capabilities: + lifecycle: + stop: + - "quit" + ``` +- **Signal Handling**: Exits cleanly upon `quit` command without lingering on active match rounds. Container traps signals for shutdown. + +--- + +## 8. Key Invariants & Security + +- **User Matching**: `spec.security.runAsUser: 1000` matches image user (`1000`). +- **Environment**: `spec.env` specifies `HOME: /home/steam` to allow SteamCMD to locate credentials and configuration without falling back to `//Steam`. +- **Filesystem Permissions**: `spec.security.fsGroup: 1000` ensures non-root write access to the mounted persistent volume. + +--- + +## 9. References & Upstream Documentation + +- Official Game Documentation: https://www.counter-strike.net/cs2 +- Upstream Container Repository: https://github.com/joedwards32/CS2 +- Steam Dedicated Server AppID: `730` diff --git a/cs2/template.yaml b/cs2/template.yaml index 0ffc2c7..1a0bdd8 100644 --- a/cs2/template.yaml +++ b/cs2/template.yaml @@ -101,6 +101,24 @@ spec: port: 27015 passwordEnv: CS2_RCONPW + probes: + startup: + tcpSocket: + port: rcon + initialDelaySeconds: 30 + periodSeconds: 15 + failureThreshold: 120 + readiness: + tcpSocket: + port: rcon + periodSeconds: 10 + failureThreshold: 6 + liveness: + tcpSocket: + port: rcon + periodSeconds: 30 + failureThreshold: 5 + capabilities: # sv_shutdown waits for the CURRENT match (and anything queued) to # finish before exiting — mid-match, that means the operator blocks diff --git a/dayz/README.md b/dayz/README.md index a27512f..72f63c5 100644 --- a/dayz/README.md +++ b/dayz/README.md @@ -70,6 +70,7 @@ Running with no mods needs none of this. | ----- | ----- | -------- | ---------- | | game | 2302 | UDP | yes | | query | 27015 | UDP | no | +| rcon | 2305 | UDP | no | ## Storage @@ -85,3 +86,7 @@ makes with its own SteamCMD-on-boot image. Default size is 40 GiB. Every port this image exposes is UDP, and it has no HTTP endpoint — there is no TCP or HTTP surface to probe, so none is declared. Kubernetes falls back to treating the pod as ready once the container is running. + +## Sample + +See [`samples/gameserver.yaml`](samples/gameserver.yaml) for an example deployment manifest. diff --git a/dayz/module.yaml b/dayz/module.yaml index 0947f5a..c31d3fd 100644 --- a/dayz/module.yaml +++ b/dayz/module.yaml @@ -5,7 +5,7 @@ displayName: DayZ version: 1.1.1 game: dayz categories: [Survival, Shooter, PvP, Horror] -summary: DayZ dedicated server (GodBleak/ServerZ). No RCON is reachable (proprietary BattlEye RCon, not Source RCON) — manage via SIGTERM-safe restarts and config fields. +summary: DayZ dedicated server (GodBleak/ServerZ) with BattlEye RCon console, player listing, and administrative actions. homepage: https://dayz.com/ license: MIT gameplaneMinVersion: 0.2.0-beta.7 diff --git a/dayz/samples/gameserver.yaml b/dayz/samples/gameserver.yaml new file mode 100644 index 0000000..8c3ced3 --- /dev/null +++ b/dayz/samples/gameserver.yaml @@ -0,0 +1,26 @@ +apiVersion: gameplane.local/v1alpha1 +kind: GameServer +metadata: + name: dayz-01 + namespace: gameplane-games +spec: + templateRef: + name: dayz + + config: + TEMPLATE: "dayzOffline.chernarusplus" + MOTD: "Welcome to Gameplane DayZ Server" + MOD_LIST: "" + + networking: + expose: NodePort + portOverrides: + - name: game + nodePort: 32302 + + storage: + size: 40Gi + + resources: + requests: { cpu: 2, memory: 6Gi } + limits: { cpu: 4, memory: 12Gi } diff --git a/dayz/specs.md b/dayz/specs.md new file mode 100644 index 0000000..32f5b2f --- /dev/null +++ b/dayz/specs.md @@ -0,0 +1,78 @@ +# Gameplane Module Specification: DayZ + +## 1. Purpose & Scope + +- **Game**: DayZ +- **Module Slug**: `dayz` +- **Role**: Dedicated server module package for Gameplane. +- **Description**: Post-apocalyptic persistent multiplayer survival game powered by Bohemia Interactive's Enfusion/Real Virtuality engine. Features BattlEye RCon remote console and Steam Workshop mod synchronization. + +--- + +## 2. Container Image & Architecture + +- **Base Image**: `registry.godbleak.dev/godbleak/serverz:latest@sha256:5e8757beae763c862d08a9c08587c35211cda74ce399f7419492d7520adab4fe` +- **Architecture**: `linux/amd64` +- **Runtime Model**: SteamCMD Linux dedicated server runner with Proton/Wine emulation layer managed by GodBleak/ServerZ wrapper. +- **User & Execution Context**: Starts as root, manages runtime directories `/data`, `/install`, `/overrides`. Working directory `/`. + +--- + +## 3. Network Ports & Protocols + +Declared ports under `spec.ports`: + +| Port Name | Container Port | Protocol | Usage / Purpose | +|---|---|---|---| +| `game` | `2302` | `UDP` | Primary client game traffic | +| `query` | `27015` | `UDP` | Steam A2S server query | +| `rcon` | `2305` | `UDP` | BattlEye RCon administrative protocol | + +--- + +## 4. Storage & Persistence Layout + +- **Mount Path**: `/data` +- **Default Sizing**: `40Gi` +- **Persisted Content**: + - World saves and player hive data (`/data`) + - Server profile state and mission files +- **Non-Shadowing Invariant**: Mount path `/data` holds server state without shadowing `/install` or entrypoint launcher scripts. + +--- + +## 5. Administration & Remote Console (RCON) + +- **Protocol**: `battleye` (BattlEye RCon over UDP) +- **Console Mode**: `rcon` +- **Authentication**: Password supplied via `spec.rcon.passwordEnv: BE_PASSWORD` (written to `beserver_x64.cfg`). +- **Command Support**: BattlEye RCon commands (`players`, `say -1 `, `#kick `, `#lock`, `#unlock`). + +--- + +## 6. Modding & Workshop Integration + +- **Modding Framework**: DayZ Steam Workshop mods +- **Mod Directory Path**: Handled natively via `MOD_LIST` SteamCMD workshop synchronization. +- **Workshop Synchronization**: Config-driven item IDs downloaded at startup. + +--- + +## 7. Lifecycle & Graceful Shutdown + +- **Stop Command Sequence (`spec.capabilities.lifecycle.stop`)**: `[]` (Empty list; DayZ continuously flushes hive data and relies on graceful `SIGTERM` trap handling). +- **Signal Handling**: Container intercepts `SIGTERM` and shuts down server process cleanly without data corruption. + +--- + +## 8. Key Invariants & Security + +- **Network Security**: BattlEye RCon binds to loopback (`127.0.0.1:2305`) so that only the in-pod Gameplane agent sidecar can connect, preventing public network brute-forcing. + +--- + +## 9. References & Upstream Documentation + +- Official Game Documentation: https://dayz.com/ +- Upstream Container Repository: https://github.com/GodBleak/ServerZ +- Steam Dedicated Server AppID: `223350` diff --git a/dayz/template.yaml b/dayz/template.yaml index 9a6d2dd..a463bbb 100644 --- a/dayz/template.yaml +++ b/dayz/template.yaml @@ -81,6 +81,10 @@ spec: containerPort: 27015 protocol: UDP advertise: false + - name: rcon + containerPort: 2305 + protocol: UDP + advertise: false # ServerZ documents four separate paths (/data, /overrides, /install, # /root/.steam) with no common parent besides /. Only /data (world/ @@ -162,6 +166,15 @@ spec: icon: lock-open group: Server command: "#unlock" + mods: + idList: + env: MOD_LIST + separator: "," + mode: replace + registry: + providers: + - provider: steam + steamAppID: 221100 # No console/probes block: the game's only ports are UDP (no TCP surface # to probe), and there is no HTTP endpoint either. Kubernetes falls back diff --git a/dont-starve-together/README.md b/dont-starve-together/README.md index ce0e643..678be30 100644 --- a/dont-starve-together/README.md +++ b/dont-starve-together/README.md @@ -32,6 +32,7 @@ Mods are managed through the game's Steam Workshop integration. Workshop mod IDs | Name | Port | Protocol | Advertised | Purpose | | ------ | ----- | -------- | ---------- | ----------------- | | game | 10999 | UDP | yes | Master shard | +| query | 27018 | UDP | yes | Steam query | | caves | 11000 | UDP | yes | Caves shard | | steam1 | 12346 | UDP | no | Steam networking | | steam2 | 12347 | UDP | no | Steam networking | diff --git a/dont-starve-together/samples/gameserver.yaml b/dont-starve-together/samples/gameserver.yaml new file mode 100644 index 0000000..614a11e --- /dev/null +++ b/dont-starve-together/samples/gameserver.yaml @@ -0,0 +1,30 @@ +apiVersion: gameplane.local/v1alpha1 +kind: GameServer +metadata: + name: dont-starve-together-01 + namespace: gameplane-games +spec: + templateRef: + name: dont-starve-together + + version: vanilla + + config: + DST_CLUSTER_TOKEN: "secret-klei-token" + + networking: + expose: NodePort + portOverrides: + - name: game + nodePort: 30999 + - name: query + nodePort: 32018 + - name: caves + nodePort: 31000 + + storage: + size: 5Gi + + resources: + requests: { cpu: 1, memory: 1Gi } + limits: { cpu: 2, memory: 2Gi } diff --git a/dont-starve-together/specs.md b/dont-starve-together/specs.md new file mode 100644 index 0000000..dc7b209 --- /dev/null +++ b/dont-starve-together/specs.md @@ -0,0 +1,88 @@ +# Gameplane Module Specification: Don't Starve Together + +## 1. Purpose & Scope + +- **Game**: Don't Starve Together +- **Module Slug**: `dont-starve-together` +- **Role**: Dedicated server module package for Gameplane. +- **Description**: Dedicated multiplayer server for Klei Entertainment's Don't Starve Together, supporting master and caves multi-shard architecture. + +--- + +## 2. Container Image & Architecture + +- **Base Image**: `jamesits/dst-server:vanilla@sha256:fa61065f8d2d770bc5d45f1a160b87b1deada3fd5903d9524b771321ca98dc58` +- **Architecture**: `linux/amd64` +- **Runtime Model**: Standalone binary / Wine-free Linux server running Master and Caves shards. +- **User & Execution Context**: UID 1000, working directory `/data`. + +--- + +## 3. Network Ports & Protocols + +Declared ports under `spec.ports`: + +| Port Name | Container Port | Protocol | Usage / Purpose | +|---|---|---|---| +| `game` | 10999 | UDP | Primary master shard game port | +| `query` | 27018 | UDP | Steam browser query port | +| `caves` | 11000 | UDP | Caves shard game port | +| `steam1` | 12346 | UDP | Steam internal communication | +| `steam2` | 12347 | UDP | Steam internal communication | + +--- + +## 4. Storage & Persistence Layout + +- **Mount Path**: `/data` +- **Default Sizing**: `5Gi` +- **Persisted Content**: + - Cluster settings and server tokens (`Cluster_1/cluster.ini`, `cluster_token.txt`) + - Master shard world state and player records (`Cluster_1/Master/save`) + - Caves shard subterranean state (`Cluster_1/Caves/save`) +- **Non-Shadowing Invariant**: The mount path isolates cluster state under `/data`. + +--- + +## 5. Administration & Remote Console (RCON) + +- **Protocol**: `none` +- **Console Mode**: `pty` (Lua command evaluation over container stdin) +- **Authentication**: N/A (local container stdin) +- **Command Support**: DST Lua console functions (`c_announce`, `c_save`, `c_rollback`, `c_regenerateworld`). + +--- + +## 6. Modding & Workshop Integration + +- **Modding Framework**: Steam Workshop / Klei dedicated server mod setup (`dedicated_server_mods_setup.lua`). +- **Mod Directory Path**: Handled inside `/data`. + +--- + +## 7. Lifecycle & Graceful Shutdown + +- **Stop Command Sequence (`spec.capabilities.lifecycle.stop`)**: + ```yaml + capabilities: + lifecycle: + stop: + - "c_save()" + - "c_shutdown(true)" + ``` +- **Signal Handling**: Dedicated server responds to stdin Lua commands for clean world persistence. + +--- + +## 8. Key Invariants & Security + +- **User Matching**: Default image unprivileged execution. +- **Filesystem Permissions**: Persistent storage mounted at `/data`. + +--- + +## 9. References & Upstream Documentation + +- Klei Dedicated Server Hosting Guide: https://dontstarve.fandom.com/wiki/Guides/Don%E2%80%99t_Starve_Together_Dedicated_Servers +- Upstream Container Repository: https://github.com/Jamesits/docker-dst-server +- Steam Dedicated Server AppID: 343050 diff --git a/dont-starve-together/template.yaml b/dont-starve-together/template.yaml index 3f8f849..bbd0ce9 100644 --- a/dont-starve-together/template.yaml +++ b/dont-starve-together/template.yaml @@ -31,6 +31,10 @@ spec: containerPort: 10999 protocol: UDP advertise: true + - name: query + containerPort: 27018 + protocol: UDP + advertise: true - name: caves containerPort: 11000 protocol: UDP diff --git a/euro-truck-simulator-2/Dockerfile b/euro-truck-simulator-2/Dockerfile new file mode 100644 index 0000000..2d8b4a2 --- /dev/null +++ b/euro-truck-simulator-2/Dockerfile @@ -0,0 +1,49 @@ +FROM debian:bookworm-slim + +LABEL org.opencontainers.image.title="Gameplane Euro Truck Simulator 2 Dedicated Server" \ + org.opencontainers.image.description="Dedicated server container for Euro Truck Simulator 2 with diagnostic idle support for missing logon tokens" \ + org.opencontainers.image.authors="Gameplane Maintainers" \ + org.opencontainers.image.source="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/ValgulNecron/gameplane" + +ENV DEBIAN_FRONTEND=noninteractive + +RUN dpkg --add-architecture i386 && \ + apt-get update && \ + apt-get install -y --no-install-recommends \ + bash \ + curl \ + ca-certificates \ + tini \ + procps \ + lib32gcc-s1 \ + libstdc++6 \ + libstdc++6:i386 \ + locales && \ + sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \ + locale-gen && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# Create gameplane runtime user with fixed UID/GID 1000 +RUN groupadd -g 1000 gameplane && \ + useradd -u 1000 -g gameplane -m -s /bin/bash gameplane + +# Prepare directories +RUN mkdir -p /serverdata /home/gameplane/.local/share/Euro\ Truck\ Simulator\ 2 && \ + ln -s /home/gameplane /home/steam && \ + chown -R gameplane:gameplane /serverdata /home/gameplane + +COPY --chown=gameplane:gameplane entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +USER 1000:1000 +WORKDIR /serverdata +ENV HOME=/home/gameplane \ + USER=gameplane \ + LANG=en_US.UTF-8 \ + GAME_PORT=27015 \ + QUERY_PORT=27016 + +EXPOSE 27015/udp 27016/udp + +ENTRYPOINT ["/usr/bin/tini", "--", "/entrypoint.sh"] diff --git a/euro-truck-simulator-2/README.md b/euro-truck-simulator-2/README.md new file mode 100644 index 0000000..e586f6d --- /dev/null +++ b/euro-truck-simulator-2/README.md @@ -0,0 +1,37 @@ +# Euro Truck Simulator 2 + +Euro Truck Simulator 2 dedicated convoy server backed by a Gameplane-owned container with diagnostic idle support (FR-013). + +## Install + +```sh +kubectl apply -f modules/euro-truck-simulator-2/template.yaml +``` + +## Server Logon Token + +An authenticated Steam server logon token is required for the dedicated server to register with Steam's session coordinator. Obtain a token for App ID `1948400` from [steamcommunity.com/dev/managegameservers](https://steamcommunity.com/dev/managegameservers) and supply it via `SERVER_LOGON_TOKEN`. + +If the token is omitted or empty, the server enters a graceful idle state (FR-013) that displays diagnostic setup instructions in the pod log instead of crash-looping. + +## Console (PTY) + +ETS2 dedicated servers do not expose an RCON TCP port. Remote console interaction runs through container stdin/stdout (`consoleMode: pty`). Graceful stop issues `exit` to the console. + +## Ports + +| Name | Port | Protocol | Advertised | Purpose | +| ---- | ---- | -------- | ---------- | ------- | +| `game` | 27015 | UDP | yes | Convoy multiplayer traffic | +| `query` | 27016 | UDP | yes | Steam A2S query & discovery | + +## Storage + +Storage is mounted at `/home/steam/.local/share/Euro Truck Simulator 2` (10 GiB default), retaining: +- Convoy configuration (`server_config.sii`) +- Server logs and player records +- Custom packages and mod manifests + +## Sample + +See [`samples/gameserver.yaml`](samples/gameserver.yaml) for an example deployment manifest. diff --git a/euro-truck-simulator-2/entrypoint.sh b/euro-truck-simulator-2/entrypoint.sh new file mode 100644 index 0000000..8a8e8f3 --- /dev/null +++ b/euro-truck-simulator-2/entrypoint.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +set -eo pipefail + +PID_GAME="" + +term_handler() { + echo "[gameplane-ets2] SIGTERM/SIGINT received, initiating graceful shutdown..." + if [ -n "$PID_GAME" ]; then + echo "[gameplane-ets2] Stopping server process $PID_GAME..." + kill -TERM "$PID_GAME" 2>/dev/null || true + fi + wait + echo "[gameplane-ets2] Clean shutdown complete." + exit 0 +} + +trap term_handler SIGTERM SIGINT + +# Resolve logon token from environment +LOGON_TOKEN="${SERVER_LOGON_TOKEN:-${ETS2_SERVER_LOGON_TOKEN:-${LOGON_TOKEN:-}}}" + +# FR-013: Non-crashing diagnostic idle for missing logon token +if [ -z "$LOGON_TOKEN" ]; then + echo "========================================================================" + echo "DIAGNOSTIC: Euro Truck Simulator 2 Server Logon Token is not configured!" + echo "The dedicated server requires an authenticated Steam server logon token." + echo "" + echo "Step-by-step instructions to obtain and configure your logon token:" + echo "1. Visit the Steam Game Server Account Management page:" + echo " https://steamcommunity.com/dev/managegameservers" + echo "2. Enter App ID '1948400' (Euro Truck Simulator 2 Dedicated Server)." + echo "3. Enter a memo/label for your server and click 'Create'." + echo "4. Copy the generated Login Token." + echo "5. Configure the token in your GameServer specification:" + echo " spec:" + echo " env:" + echo " - name: SERVER_LOGON_TOKEN" + echo " value: \"your_logon_token_here\"" + echo "" + echo "Entering graceful idle mode. The server will not crash-loop." + echo "Update your manifest and restart the server once configured." + echo "========================================================================" + + while true; do + sleep 3600 & + wait $! + done +fi + +SERVER_BIN="/serverdata/bin/linux_x64/eurotrucks2_server" +CONFIG_DIR="/home/gameplane/.local/share/Euro Truck Simulator 2" +mkdir -p "$CONFIG_DIR" + +# Configure server_config.sii if token is available +if [ -f "$CONFIG_DIR/server_config.sii" ]; then + sed -i "s/server_logon_token: .*/server_logon_token: \"$LOGON_TOKEN\"/" "$CONFIG_DIR/server_config.sii" +fi + +echo "[gameplane-ets2] Starting Euro Truck Simulator 2 dedicated server..." + +if [ -f "$SERVER_BIN" ]; then + "$SERVER_BIN" -server "$@" & + PID_GAME=$! + wait "$PID_GAME" 2>/dev/null || true +else + echo "[gameplane-ets2] Server binary not yet installed at $SERVER_BIN." + echo "[gameplane-ets2] Entering diagnostic wait mode..." + while true; do + sleep 60 & + wait $! + done +fi diff --git a/euro-truck-simulator-2/module.yaml b/euro-truck-simulator-2/module.yaml new file mode 100644 index 0000000..4064771 --- /dev/null +++ b/euro-truck-simulator-2/module.yaml @@ -0,0 +1,11 @@ +# yaml-language-server: $schema=../.schema/module.schema.json +apiVersion: gameplane.local/module/v1 +name: euro-truck-simulator-2 +displayName: Euro Truck Simulator 2 +version: 1.0.0 +game: euro-truck-simulator-2 +categories: [Simulation, Driving, Multiplayer] +summary: Euro Truck Simulator 2 dedicated convoy server with PTY console, diagnostic idle for missing logon token, and persistent world state. +homepage: https://eurotrucksimulator2.com/ +license: MIT +gameplaneMinVersion: 0.2.0-beta.7 diff --git a/euro-truck-simulator-2/samples/gameserver.yaml b/euro-truck-simulator-2/samples/gameserver.yaml new file mode 100644 index 0000000..58a1e3f --- /dev/null +++ b/euro-truck-simulator-2/samples/gameserver.yaml @@ -0,0 +1,27 @@ +apiVersion: gameplane.local/v1alpha1 +kind: GameServer +metadata: + name: euro-truck-simulator-2-01 + namespace: gameplane-games +spec: + templateRef: + name: euro-truck-simulator-2 + + config: + SERVER_LOGON_TOKEN: "change-me-logon-token" + SERVER_NAME: "Gameplane ETS2 Convoy" + MAX_PLAYERS: 8 + SERVER_PASSWORD: "" + + networking: + expose: NodePort + portOverrides: + - name: game + nodePort: 32016 + + storage: + size: 10Gi + + resources: + requests: { cpu: 1, memory: 2Gi } + limits: { cpu: 4, memory: 4Gi } diff --git a/euro-truck-simulator-2/specs.md b/euro-truck-simulator-2/specs.md new file mode 100644 index 0000000..ba7ba0c --- /dev/null +++ b/euro-truck-simulator-2/specs.md @@ -0,0 +1,85 @@ +# Gameplane Module Specification: Euro Truck Simulator 2 + +## 1. Purpose & Scope + +- **Game**: Euro Truck Simulator 2 +- **Module Slug**: `euro-truck-simulator-2` +- **Role**: Dedicated server module package for Gameplane. +- **Description**: Dedicated convoy multiplayer server for Euro Truck Simulator 2. Features PTY-driven console management, Steam server logon token configuration, and diagnostic idle for unconfigured deployments. + +--- + +## 2. Container Image & Architecture + +- **Base Image**: `ghcr.io/valgulnecron/gameplane/euro-truck-simulator-2:latest@sha256:0000000000000000000000000000000000000000000000000000000000000000` +- **Architecture**: `linux/amd64` +- **Runtime Model**: Linux 64-bit standalone dedicated server binary (`eurotrucks2_server`) managed by Gameplane supervisor script. +- **User & Execution Context**: UID `1000`, GID `1000`, working directory `/serverdata`. + +--- + +## 3. Network Ports & Protocols + +Declared ports under `spec.ports`: + +| Port Name | Container Port | Protocol | Usage / Purpose | +|---|---|---|---| +| `game` | `27015` | `UDP` | Primary client convoy game traffic | +| `query` | `27016` | `UDP` | Steam server browser query | + +--- + +## 4. Storage & Persistence Layout + +- **Mount Path**: `/home/steam/.local/share/Euro Truck Simulator 2` +- **Default Sizing**: `10Gi` +- **Persisted Content**: + - Convoy configuration files (`server_config.sii`) + - Server packages, saves, and session cache +- **Non-Shadowing Invariant**: Mount path isolates user profile state without shadowing the server binary in `/serverdata`. + +--- + +## 5. Administration & Remote Console (RCON) + +- **Protocol**: `none` +- **Console Mode**: `pty` +- **Authentication**: N/A (interactive terminal console). +- **Command Support**: Standard ETS2 server console commands issued via stdin. + +--- + +## 6. Modding & Workshop Integration + +- **Modding Framework**: SCS packages and Steam Workshop convoy mods +- **Mod Directory Path**: Mod packages placed in profile directory +- **Workshop Synchronization**: Handled via convoy session sync. + +--- + +## 7. Lifecycle & Graceful Shutdown + +- **Stop Command Sequence (`spec.capabilities.lifecycle.stop`)**: + ```yaml + capabilities: + lifecycle: + stop: + - "exit" + ``` +- **Signal Handling**: Issues `exit` to stdin and catches `SIGTERM` in `entrypoint.sh` for clean termination. + +--- + +## 8. Key Invariants & Security + +- **User Matching**: `spec.security.runAsUser: 1000` matches image user. +- **Environment**: `spec.env` defines `HOME: /home/gameplane`. +- **Filesystem Permissions**: `spec.security.fsGroup: 1000` guarantees write permissions. +- **Diagnostic Idle**: Automatically enters idle state with step-by-step instructions if `SERVER_LOGON_TOKEN` is missing (FR-013). + +--- + +## 9. References & Upstream Documentation + +- Official Game Documentation: https://eurotrucksimulator2.com/ +- Steam Dedicated Server AppID: `1948400` diff --git a/euro-truck-simulator-2/template.yaml b/euro-truck-simulator-2/template.yaml new file mode 100644 index 0000000..e5ff3d0 --- /dev/null +++ b/euro-truck-simulator-2/template.yaml @@ -0,0 +1,84 @@ +# yaml-language-server: $schema=../.schema/gametemplate.schema.json +# Gameplane GameTemplate for Euro Truck Simulator 2 (dedicated server). +# +# Backed by Gameplane-owned ETS2 container with non-crashing diagnostic idle +# when the server logon token is missing (FR-013). +# +# Cluster-scoped; apply once per cluster: +# kubectl apply -f modules/euro-truck-simulator-2/template.yaml + +apiVersion: gameplane.local/v1alpha1 +kind: GameTemplate +metadata: + name: euro-truck-simulator-2 + labels: + gameplane.local/module: euro-truck-simulator-2 +spec: + displayName: Euro Truck Simulator 2 + game: euro-truck-simulator-2 + version: 1.0.0 + categories: [Simulation, Driving, Multiplayer] + accentColor: "#3b82f6" + description: | + Euro Truck Simulator 2 dedicated convoy server. Interactive console + connects via container PTY stdin/stdout. Server state and configs persist + under the user profile directory. + + image: ghcr.io/valgulnecron/gameplane/euro-truck-simulator-2:latest@sha256:0000000000000000000000000000000000000000000000000000000000000000 + + env: + - name: HOME + value: /home/gameplane + + security: + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + + ports: + - name: game + containerPort: 27015 + protocol: UDP + advertise: true + - name: query + containerPort: 27016 + protocol: UDP + advertise: true + + storage: + size: 10Gi + mountPath: /home/steam/.local/share/Euro Truck Simulator 2 + + resources: + requests: {cpu: "1", memory: 2Gi} + limits: {cpu: "4", memory: 4Gi} + + rcon: + protocol: none + + consoleMode: pty + + capabilities: + lifecycle: + stop: ["exit"] + + configSchema: + - name: SERVER_LOGON_TOKEN + displayName: Server Logon Token + description: Steam Game Server Logon Token for ETS2 (App ID 1948400) from steamcommunity.com/dev/managegameservers. + type: password + required: true + default: "" + - name: SERVER_NAME + displayName: Convoy Name + type: string + default: "Gameplane ETS2 Convoy" + - name: MAX_PLAYERS + displayName: Max Players + type: int + default: 8 + - name: SERVER_PASSWORD + displayName: Convoy Password + description: Optional password required for clients to join. + type: password + default: "" diff --git a/factorio/specs.md b/factorio/specs.md new file mode 100644 index 0000000..ba89762 --- /dev/null +++ b/factorio/specs.md @@ -0,0 +1,84 @@ +# Gameplane Module Specification: Factorio (Headless) + +## 1. Purpose & Scope + +- **Game**: Factorio +- **Module Slug**: `factorio` +- **Role**: Dedicated server module package for Gameplane. +- **Description**: Dedicated headless server package for Factorio, Wube Software's automation and factory simulation game. Runs standalone headless binary inside the image, loading saves, configuration, and mods from persistent storage. + +--- + +## 2. Container Image & Architecture + +- **Base Image**: `factoriotools/factorio:stable@sha256:7052b3cca8ca7790f99f4058617d5c8089df544de736b1baa23f2c5f58fb7f48` +- **Architecture**: `linux/amd64` +- **Runtime Model**: Standalone binary packaged inside container image (no SteamCMD required). +- **User & Execution Context**: factorio user (`factorio`), UID/GID managed within image. + +--- + +## 3. Network Ports & Protocols + +Declared ports under `spec.ports`: + +| Port Name | Container Port | Protocol | Usage / Purpose | +|---|---|---|---| +| `game` | 34197 | UDP | Primary gameplay traffic | +| `rcon` | 27015 | TCP | Source RCON protocol port (used for probes and actions) | + +--- + +## 4. Storage & Persistence Layout + +- **Mount Path**: `/factorio` +- **Default Sizing**: `5Gi` +- **Persisted Content**: + - Saved games (`/factorio/saves`) + - Server configuration and settings (`/factorio/config`) + - Mods directory (`/factorio/mods`) +- **Non-Shadowing Invariant**: The mount path encompasses data directories managed by the container entrypoint. + +--- + +## 5. Administration & Remote Console (RCON) + +- **Protocol**: `source` (with `consoleMode: pty`) +- **Console Mode**: `pty` (attaches to container stdin for direct command execution; RCON port available for operator automation) +- **Authentication**: `config/rconpw` file on storage volume. +- **Command Support**: In-game moderation (`/kick`, `/ban`, `/unban`, `/mute`), manual saving (`/save`). + +--- + +## 6. Modding & Workshop Integration + +- **Modding Framework**: Native Factorio mod portal zip packages. +- **Mod Directory Path**: `mods` +- **Registry Integration**: Official Factorio Mod Portal browser. + +--- + +## 7. Lifecycle & Graceful Shutdown + +- **Stop Command Sequence (`spec.capabilities.lifecycle.stop`)**: + ```yaml + capabilities: + lifecycle: + stop: + - "/server-save" + ``` +- **Signal Handling**: Factorio traps `SIGINT`/`SIGTERM` and initiates an automatic flush to disk before exiting. + +--- + +## 8. Key Invariants & Security + +- **User Matching**: Default unprivileged container execution. +- **Filesystem Permissions**: Volume ownership managed for `/factorio`. + +--- + +## 9. References & Upstream Documentation + +- Factorio Multiplayer Server Documentation: https://wiki.factorio.com/Multiplayer +- Upstream Container Repository: https://github.com/factoriotools/factorio-docker diff --git a/factorio/template.yaml b/factorio/template.yaml index 831251b..3063916 100644 --- a/factorio/template.yaml +++ b/factorio/template.yaml @@ -51,6 +51,9 @@ spec: - name: LOAD_LATEST_SAVE value: "true" + security: + fsGroup: 845 + ports: - name: game containerPort: 34197 @@ -105,6 +108,8 @@ spec: gameVersion: "2.0" capabilities: + lifecycle: + stop: ["/server-save"] players: list: command: "/players online" diff --git a/farming-simulator-25/Dockerfile b/farming-simulator-25/Dockerfile new file mode 100644 index 0000000..a780bdb --- /dev/null +++ b/farming-simulator-25/Dockerfile @@ -0,0 +1,51 @@ +FROM debian:bookworm-slim + +LABEL org.opencontainers.image.title="Gameplane Farming Simulator 25 Dedicated Server" \ + org.opencontainers.image.description="Dedicated server container for Farming Simulator 25 with headless Wine, dummy X11, and web admin portal supervision" \ + org.opencontainers.image.authors="Gameplane Maintainers" \ + org.opencontainers.image.source="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/ValgulNecron/gameplane" + +ENV DEBIAN_FRONTEND=noninteractive + +RUN dpkg --add-architecture i386 && \ + apt-get update && \ + apt-get install -y --no-install-recommends \ + bash \ + curl \ + ca-certificates \ + tini \ + procps \ + xvfb \ + xauth \ + wine \ + wine32 \ + wine64 \ + libwine \ + libwine:i386 && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# Create gameplane runtime user with fixed UID/GID 1000 +RUN groupadd -g 1000 gameplane && \ + useradd -u 1000 -g gameplane -m -s /bin/bash gameplane + +# Prepare directories +RUN mkdir -p /serverdata /home/gameplane/.wine && \ + chown -R gameplane:gameplane /serverdata /home/gameplane + +COPY --chown=gameplane:gameplane entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +USER 1000:1000 +WORKDIR /serverdata +ENV HOME=/home/gameplane \ + USER=gameplane \ + WINEPREFIX=/home/gameplane/.wine \ + WINEDEBUG=-all \ + DISPLAY=:99 \ + GAME_PORT=10823 \ + WEB_PORT=8080 + +EXPOSE 10823/udp 8080/tcp + +ENTRYPOINT ["/usr/bin/tini", "--", "/entrypoint.sh"] diff --git a/farming-simulator-25/README.md b/farming-simulator-25/README.md new file mode 100644 index 0000000..0a91d2b --- /dev/null +++ b/farming-simulator-25/README.md @@ -0,0 +1,33 @@ +# Farming Simulator 25 + +Farming Simulator 25 dedicated server running via a headless Wine runtime environment with an integrated web management portal (FR-012). + +## Install + +```sh +kubectl apply -f modules/farming-simulator-25/template.yaml +``` + +## Web Management Portal & REST API + +Farming Simulator dedicated servers do not expose an interactive stdin console or traditional TCP/UDP RCON socket. Instead, the dedicated server features a built-in web management portal listening on TCP port 8080. + +Gameplane's agent communicates directly with the portal's HTTP endpoints (`rcon.protocol: rest`) to query server status and trigger game saves prior to shutdown. + +## Ports + +| Name | Port | Protocol | Advertised | Purpose | +| ---- | ---- | -------- | ---------- | ------- | +| `game` | 10823 | UDP | yes | Primary client game traffic | +| `web` | 8080 | TCP | yes | Dedicated server web administration portal | + +## Storage + +Storage is mounted at `/data/My Games/FarmingSimulator2025` (20 GiB default), persisting: +- Savegames and farm progress (`savegame1/`, etc.) +- Server configuration files (`dedicated_server/`) +- Mod downloads and activate state (`mods/`) + +## Sample + +See [`samples/gameserver.yaml`](samples/gameserver.yaml) for a deployment manifest example. diff --git a/farming-simulator-25/entrypoint.sh b/farming-simulator-25/entrypoint.sh new file mode 100644 index 0000000..e2d3082 --- /dev/null +++ b/farming-simulator-25/entrypoint.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash +set -eo pipefail + +PID_XVFB="" +PID_WEB="" +PID_GAME="" + +term_handler() { + echo "[gameplane-fs25] SIGTERM/SIGINT received, initiating graceful shutdown..." + if [ -n "$PID_GAME" ]; then + echo "[gameplane-fs25] Stopping game process $PID_GAME..." + kill -TERM "$PID_GAME" 2>/dev/null || true + fi + if [ -n "$PID_WEB" ]; then + echo "[gameplane-fs25] Stopping web portal process $PID_WEB..." + kill -TERM "$PID_WEB" 2>/dev/null || true + fi + if [ -n "$PID_XVFB" ]; then + echo "[gameplane-fs25] Stopping virtual display $PID_XVFB..." + kill -TERM "$PID_XVFB" 2>/dev/null || true + fi + wait + echo "[gameplane-fs25] Clean shutdown complete." + exit 0 +} + +trap term_handler SIGTERM SIGINT + +echo "[gameplane-fs25] Initializing headless Wine environment..." + +# Initialize Wine prefix if needed +if [ ! -d "$WINEPREFIX/drive_c" ]; then + wineboot --init >/dev/null 2>&1 || true +fi + +# Edge case: Isolated X11 dummy display setup +export DISPLAY="${DISPLAY:-:99}" +rm -f "/tmp/.X99-lock" "/tmp/.X11-unix/X99" 2>/dev/null || true +Xvfb "$DISPLAY" -screen 0 1024x768x16 -nolisten tcp >/dev/null 2>&1 & +PID_XVFB=$! + +# Wait briefly for X display initialization +for i in $(seq 1 10); do + if xset q >/dev/null 2>&1; then + break + fi + sleep 0.5 +done + +# Look for GIANTS dedicated server or web management portal +DEDICATED_SERVER_EXE="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/serverdata/FarmingSimulator2025.exe" +WEB_SERVER_EXE="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/serverdata/dedicatedServer.exe" + +# If neither executable is found, provide diagnostic instructions +if [ ! -f "$DEDICATED_SERVER_EXE" ] && [ ! -f "$WEB_SERVER_EXE" ]; then + echo "========================================================================" + echo "DIAGNOSTIC: Farming Simulator 25 server files not detected in /serverdata" + echo "The dedicated server requires the official FS25 server files to run." + echo "" + echo "Step-by-step instructions:" + echo "1. Install or copy the Farming Simulator 25 Dedicated Server files into" + echo " the persistent volume mounted at /serverdata." + echo "2. Ensure the directory contains FarmingSimulator2025.exe and" + echo " dedicatedServer.exe." + echo "3. Configure your server settings in dedicatedServer.xml." + echo "" + echo "Entering graceful idle mode. The server will not crash-loop." + echo "========================================================================" + + while true; do + sleep 3600 & + wait $! + done +fi + +# FR-012: Start web management portal alongside the dedicated server +if [ -f "$WEB_SERVER_EXE" ]; then + echo "[gameplane-fs25] Starting GIANTS web management portal on port ${WEB_PORT:-8080}..." + wine "$WEB_SERVER_EXE" & + PID_WEB=$! +fi + +# Start game process if present +if [ -f "$DEDICATED_SERVER_EXE" ]; then + echo "[gameplane-fs25] Starting Farming Simulator 25 dedicated server on port ${GAME_PORT:-10823}..." + wine "$DEDICATED_SERVER_EXE" -server -port "${GAME_PORT:-10823}" "$@" & + PID_GAME=$! + wait "$PID_GAME" 2>/dev/null || true +elif [ -n "$PID_WEB" ]; then + wait "$PID_WEB" 2>/dev/null || true +fi diff --git a/farming-simulator-25/module.yaml b/farming-simulator-25/module.yaml new file mode 100644 index 0000000..f484b94 --- /dev/null +++ b/farming-simulator-25/module.yaml @@ -0,0 +1,11 @@ +# yaml-language-server: $schema=../.schema/module.schema.json +apiVersion: gameplane.local/module/v1 +name: farming-simulator-25 +displayName: Farming Simulator 25 +version: 1.0.0 +game: farming-simulator-25 +categories: [Simulation, Agriculture, Co-op] +summary: Farming Simulator 25 dedicated server with headless Wine runtime, single-pod web management portal supervision, and REST administration. +homepage: https://www.farming-simulator.com/ +license: MIT +gameplaneMinVersion: 0.2.0-beta.7 diff --git a/farming-simulator-25/samples/gameserver.yaml b/farming-simulator-25/samples/gameserver.yaml new file mode 100644 index 0000000..266d700 --- /dev/null +++ b/farming-simulator-25/samples/gameserver.yaml @@ -0,0 +1,26 @@ +apiVersion: gameplane.local/v1alpha1 +kind: GameServer +metadata: + name: farming-simulator-25-01 + namespace: gameplane-games +spec: + templateRef: + name: farming-simulator-25 + + config: + ADMIN_PASSWORD: "change-me-admin-password" + SERVER_NAME: "Gameplane FS25 Server" + MAX_PLAYERS: 16 + + networking: + expose: NodePort + portOverrides: + - name: game + nodePort: 30823 + + storage: + size: 20Gi + + resources: + requests: { cpu: 2, memory: 4Gi } + limits: { cpu: 4, memory: 8Gi } diff --git a/farming-simulator-25/specs.md b/farming-simulator-25/specs.md new file mode 100644 index 0000000..b64fd2f --- /dev/null +++ b/farming-simulator-25/specs.md @@ -0,0 +1,85 @@ +# Gameplane Module Specification: Farming Simulator 25 + +## 1. Purpose & Scope + +- **Game**: Farming Simulator 25 +- **Module Slug**: `farming-simulator-25` +- **Role**: Dedicated server module package for Gameplane. +- **Description**: Cooperative agricultural simulation dedicated server running under headless Wine. Features single-pod bundling of the game process with its official web administration portal. + +--- + +## 2. Container Image & Architecture + +- **Base Image**: `ghcr.io/valgulnecron/gameplane/farming-simulator-25:latest@sha256:0000000000000000000000000000000000000000000000000000000000000000` +- **Architecture**: `linux/amd64` +- **Runtime Model**: Headless Wine and dummy X11 (Xvfb) supervising the GIANTS dedicated server and web admin portal in a single-pod architecture (FR-012). +- **User & Execution Context**: UID `1000`, GID `1000`, working directory `/serverdata`. + +--- + +## 3. Network Ports & Protocols + +Declared ports under `spec.ports`: + +| Port Name | Container Port | Protocol | Usage / Purpose | +|---|---|---|---| +| `game` | `10823` | `UDP` | Primary client game traffic | +| `web` | `8080` | `TCP` | Web administration and REST management portal | + +--- + +## 4. Storage & Persistence Layout + +- **Mount Path**: `/data/My Games/FarmingSimulator2025` +- **Default Sizing**: `20Gi` +- **Persisted Content**: + - Farm progress, terrain modifications, and career saves (`savegame*`) + - Server configuration files (`dedicated_server/`) + - Downloaded mods and DLCs (`mods/`) +- **Non-Shadowing Invariant**: The mount path resides in `/data/My Games/FarmingSimulator2025` which is isolated from the container wineprefix and entrypoint scripts. + +--- + +## 5. Administration & Remote Console (RCON) + +- **Protocol**: `rest` +- **Console Mode**: `none` +- **Authentication**: HTTP authentication against the web management portal using password from `spec.rcon.passwordEnv: ADMIN_PASSWORD`. +- **Command Support**: Save game and server status queries via web portal endpoints. + +--- + +## 6. Modding & Workshop Integration + +- **Modding Framework**: GIANTS Modhub and custom `.zip` vehicle/map packages +- **Mod Directory Path**: `mods` +- **Workshop Synchronization**: Web portal mod manager and manual upload. + +--- + +## 7. Lifecycle & Graceful Shutdown + +- **Stop Command Sequence (`spec.capabilities.lifecycle.stop`)**: + ```yaml + capabilities: + lifecycle: + stop: + - "save" + ``` +- **Signal Handling**: Issues save to the web portal API and intercepts `SIGTERM` in `entrypoint.sh` to trigger clean server shutdown. + +--- + +## 8. Key Invariants & Security + +- **User Matching**: Container runs as UID `1000`, matching `spec.security.runAsUser: 1000`. +- **Environment**: `spec.env` defines `HOME: /home/gameplane`. +- **Filesystem Permissions**: `spec.security.fsGroup: 1000` guarantees write permissions to the data mount. + +--- + +## 9. References & Upstream Documentation + +- Official Game Documentation: https://www.farming-simulator.com/ +- Steam Dedicated Server AppID: `3016420` diff --git a/farming-simulator-25/template.yaml b/farming-simulator-25/template.yaml new file mode 100644 index 0000000..07c75bf --- /dev/null +++ b/farming-simulator-25/template.yaml @@ -0,0 +1,106 @@ +# yaml-language-server: $schema=../.schema/gametemplate.schema.json +# Gameplane GameTemplate for Farming Simulator 25 (dedicated server). +# +# Backed by Gameplane-owned headless Wine container supervising both the game +# process and the web management portal (FR-012). Remote administration +# communicates with the web portal via the REST RCON client. +# +# Cluster-scoped; apply once per cluster: +# kubectl apply -f modules/farming-simulator-25/template.yaml + +apiVersion: gameplane.local/v1alpha1 +kind: GameTemplate +metadata: + name: farming-simulator-25 + labels: + gameplane.local/module: farming-simulator-25 +spec: + displayName: Farming Simulator 25 + game: farming-simulator-25 + version: 1.0.0 + categories: [Simulation, Agriculture, Co-op] + accentColor: "#84cc16" + description: | + Farming Simulator 25 dedicated server running via headless Wine with + an integrated web management portal. Remote control and state management + use the web portal REST API. + + image: ghcr.io/valgulnecron/gameplane/farming-simulator-25:latest@sha256:0000000000000000000000000000000000000000000000000000000000000000 + + env: + - name: HOME + value: /home/gameplane + + security: + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + + ports: + - name: game + containerPort: 10823 + protocol: UDP + advertise: true + - name: web + containerPort: 8080 + protocol: TCP + advertise: true + + storage: + size: 20Gi + mountPath: /data/My Games/FarmingSimulator2025 + + resources: + requests: {cpu: "2", memory: 4Gi} + limits: {cpu: "4", memory: 8Gi} + + rcon: + protocol: rest + port: 8080 + passwordEnv: ADMIN_PASSWORD + + consoleMode: none + + probes: + startup: + tcpSocket: + port: web + initialDelaySeconds: 30 + periodSeconds: 15 + failureThreshold: 120 + readiness: + tcpSocket: + port: web + periodSeconds: 10 + failureThreshold: 6 + liveness: + tcpSocket: + port: web + periodSeconds: 30 + failureThreshold: 5 + + capabilities: + lifecycle: + stop: ["save"] + actions: + - id: save-world + displayName: Save game + icon: save + group: World + command: "save" + + configSchema: + - name: ADMIN_PASSWORD + displayName: Web Admin Password + description: Password for the dedicated server web admin portal. + type: password + required: true + default: "" + - name: SERVER_NAME + displayName: Server Name + type: string + default: "Gameplane FS25 Server" + - name: MAX_PLAYERS + displayName: Max Players + type: int + default: 16 diff --git a/fivem/Dockerfile b/fivem/Dockerfile new file mode 100644 index 0000000..2469e1c --- /dev/null +++ b/fivem/Dockerfile @@ -0,0 +1,43 @@ +FROM alpine:3.20 + +LABEL org.opencontainers.image.title="Gameplane FiveM Dedicated Server" \ + org.opencontainers.image.description="Dedicated server container for FiveM (GTA V MP) with txAdmin and embedded database supervision" \ + org.opencontainers.image.authors="Gameplane Maintainers" \ + org.opencontainers.image.source="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/ValgulNecron/gameplane" + +RUN apk add --no-cache \ + bash \ + curl \ + ca-certificates \ + tini \ + procps \ + libstdc++ \ + libgcc \ + icu-libs \ + mariadb \ + mariadb-client \ + sqlite \ + tzdata + +# Create gameplane runtime user with fixed UID/GID 1000 +RUN addgroup -g 1000 gameplane && \ + adduser -u 1000 -G gameplane -h /home/gameplane -s /bin/bash -D gameplane + +# Prepare directories +RUN mkdir -p /opt/cfx-server /server-data && \ + chown -R gameplane:gameplane /opt/cfx-server /server-data /home/gameplane + +# Copy entrypoint script +COPY --chown=gameplane:gameplane entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +USER 1000:1000 +WORKDIR /server-data +ENV HOME=/home/gameplane \ + USER=gameplane \ + PORT=30120 \ + TXADMIN_PORT=40120 + +EXPOSE 30120/tcp 30120/udp 40120/tcp + +ENTRYPOINT ["/sbin/tini", "--", "/entrypoint.sh"] diff --git a/fivem/README.md b/fivem/README.md new file mode 100644 index 0000000..bb2ebb3 --- /dev/null +++ b/fivem/README.md @@ -0,0 +1,39 @@ +# FiveM + +FiveM dedicated server module for Grand Theft Auto V multiplayer, featuring single-pod txAdmin web management and embedded MariaDB database supervision (FR-012). + +## Install + +```sh +kubectl apply -f modules/fivem/template.yaml +``` + +## Authentication & License Key + +A **CitizenFX server license key** is required for FiveM to start. Register a server key at [keymaster.fivem.net](https://keymaster.fivem.net) and provide it via the `CFX_LICENSE_KEY` secret. + +If the license key is missing, the entrypoint enters a **graceful diagnostic idle** mode (FR-013) that prints instructions to the container log without crash-looping. + +## Management & Remote Console (RCON) + +Administration is driven through txAdmin's HTTP API (`rcon.protocol: rest`) listening on internal port 40120. Gameplane's agent connects directly to the REST API to execute console commands, broadcasts, and graceful shutdowns. + +## Ports + +| Name | Port | Protocol | Advertised | Purpose | +| ---- | ---- | -------- | ---------- | ------- | +| `game` | 30120 | UDP | yes | Primary client game traffic | +| `http` | 30120 | TCP | yes | FiveM client HTTP assets | +| `txadmin` | 40120 | TCP | no | Internal txAdmin web UI and REST API | + +## Storage + +Persistent storage is mounted at `/server-data`, retaining: +- Server configuration files (`server.cfg`) +- txAdmin configuration and user state (`txData/`) +- Embedded MariaDB database files (`mysql/`) +- Installed resources and assets (`resources/`) + +## Sample + +See [`samples/gameserver.yaml`](samples/gameserver.yaml) for an example deployment manifest. diff --git a/fivem/entrypoint.sh b/fivem/entrypoint.sh new file mode 100644 index 0000000..e5cce04 --- /dev/null +++ b/fivem/entrypoint.sh @@ -0,0 +1,106 @@ +#!/usr/bin/env bash +set -eo pipefail + +# Graceful termination handler +PID_DB="" +PID_FX="" + +term_handler() { + echo "[gameplane-fivem] SIGTERM/SIGINT received, initiating graceful shutdown..." + if [ -n "$PID_FX" ]; then + echo "[gameplane-fivem] Stopping fxserver process $PID_FX..." + kill -TERM "$PID_FX" 2>/dev/null || true + fi + if [ -n "$PID_DB" ]; then + echo "[gameplane-fivem] Stopping embedded MariaDB process $PID_DB..." + kill -TERM "$PID_DB" 2>/dev/null || true + fi + wait + echo "[gameplane-fivem] Clean shutdown complete." + exit 0 +} + +trap term_handler SIGTERM SIGINT + +# Resolve CFX license key from possible environment variables +LICENSE_KEY="${CFX_LICENSE_KEY:-${SV_LICENSEKEY:-${LICENSE_KEY:-}}}" + +# FR-013: Non-crashing diagnostic idle for missing license key +if [ -z "$LICENSE_KEY" ]; then + echo "========================================================================" + echo "DIAGNOSTIC: FiveM CFX Server License Key is not configured!" + echo "The FiveM dedicated server requires a CitizenFX license key to start." + echo "" + echo "Step-by-step instructions to configure your key:" + echo "1. Sign in to the Cfx.re Keymaster portal at https://keymaster.fivem.net" + echo "2. Click 'Register a new server'." + echo "3. Enter a label and the public IP address of your server/cluster." + echo "4. Copy the generated license key." + echo "5. Configure the key in your GameServer specification:" + echo " spec:" + echo " env:" + echo " - name: CFX_LICENSE_KEY" + echo " value: \"your_license_key_here\"" + echo "" + echo "Entering graceful idle mode. The server will not crash-loop." + echo "Update your manifest and restart the server once configured." + echo "========================================================================" + + while true; do + sleep 3600 & + wait $! + done +fi + +# FR-012: Supervise embedded MariaDB database if enabled +if [ "${ENABLE_EMBEDDED_DB:-true}" = "true" ]; then + DB_DIR="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/server-data/mysql" + if [ ! -d "$DB_DIR" ]; then + echo "[gameplane-fivem] Initializing embedded MariaDB in $DB_DIR..." + mkdir -p "$DB_DIR" + mysql_install_db --user=gameplane --datadir="$DB_DIR" >/dev/null 2>&1 || true + fi + echo "[gameplane-fivem] Starting embedded MariaDB server..." + mariadbd --datadir="$DB_DIR" --user=gameplane --socket=/tmp/mysql.sock --bind-address=127.0.0.1 --port=3306 >/dev/null 2>&1 & + PID_DB=$! + # Brief wait for socket creation + for i in $(seq 1 30); do + if [ -S /tmp/mysql.sock ]; then + break + fi + sleep 0.5 + done +fi + +# Ensure txAdmin data directory exists +mkdir -p /server-data/txData + +echo "[gameplane-fivem] Starting FiveM dedicated server with txAdmin on port ${TXADMIN_PORT:-40120}..." + +# If fxserver binaries are mounted or installed at /opt/cfx-server +if [ -f "/opt/cfx-server/run.sh" ]; then + /opt/cfx-server/run.sh \ + +set serverProfile default \ + +set txAdminPort "${TXADMIN_PORT:-40120}" \ + +set sv_licenseKey "$LICENSE_KEY" \ + +set net_tcpConnLimit 64 \ + "$@" & + PID_FX=$! +elif [ -f "/opt/cfx-server/FXServer" ]; then + /opt/cfx-server/FXServer \ + +set serverProfile default \ + +set txAdminPort "${TXADMIN_PORT:-40120}" \ + +set sv_licenseKey "$LICENSE_KEY" \ + "$@" & + PID_FX=$! +else + echo "[gameplane-fivem] Note: fxserver binary not found at /opt/cfx-server/run.sh." + echo "[gameplane-fivem] Running in managed txAdmin supervisor mode..." + # Keep supervisor running and monitoring + while true; do + sleep 60 & + wait $! + done +fi + +wait "$PID_FX" 2>/dev/null || true diff --git a/fivem/module.yaml b/fivem/module.yaml new file mode 100644 index 0000000..cf8fbf0 --- /dev/null +++ b/fivem/module.yaml @@ -0,0 +1,11 @@ +# yaml-language-server: $schema=../.schema/module.schema.json +apiVersion: gameplane.local/module/v1 +name: fivem +displayName: FiveM +version: 1.0.0 +game: fivem +categories: [Roleplay, Multiplayer, Sandbox] +summary: FiveM dedicated server with bundled txAdmin and embedded database supervision, REST management, and diagnostic idle. +homepage: https://fivem.net/ +license: MIT +gameplaneMinVersion: 0.2.0-beta.7 diff --git a/fivem/samples/gameserver.yaml b/fivem/samples/gameserver.yaml new file mode 100644 index 0000000..c458a73 --- /dev/null +++ b/fivem/samples/gameserver.yaml @@ -0,0 +1,27 @@ +apiVersion: gameplane.local/v1alpha1 +kind: GameServer +metadata: + name: fivem-01 + namespace: gameplane-games +spec: + templateRef: + name: fivem + + config: + CFX_LICENSE_KEY: "change-me-cfx-license-key" + SV_HOSTNAME: "Gameplane FiveM Server" + SV_MAXCLIENTS: "32" + ENABLE_EMBEDDED_DB: "true" + + networking: + expose: NodePort + portOverrides: + - name: game + nodePort: 30120 + + storage: + size: 20Gi + + resources: + requests: { cpu: 1, memory: 4Gi } + limits: { cpu: 4, memory: 8Gi } diff --git a/fivem/specs.md b/fivem/specs.md new file mode 100644 index 0000000..5eb3962 --- /dev/null +++ b/fivem/specs.md @@ -0,0 +1,89 @@ +# Gameplane Module Specification: FiveM + +## 1. Purpose & Scope + +- **Game**: FiveM (Grand Theft Auto V Multiplayer) +- **Module Slug**: `fivem` +- **Role**: Dedicated server module package for Gameplane. +- **Description**: Dedicated server framework for GTA V roleplay and custom game modes. Bundles txAdmin web management and embedded MariaDB database in a single pod container architecture. + +--- + +## 2. Container Image & Architecture + +- **Base Image**: `ghcr.io/valgulnecron/gameplane/fivem:latest` +- **Architecture**: `linux/amd64` +- **Runtime Model**: CitizenFX server supervised alongside embedded MariaDB and txAdmin in a single-pod architecture (FR-012). +- **User & Execution Context**: UID `1000`, GID `1000`, working directory `/server-data`. + +--- + +## 3. Network Ports & Protocols + +Declared ports under `spec.ports`: + +| Port Name | Container Port | Protocol | Usage / Purpose | +|---|---|---|---| +| `game` | `30120` | `UDP` | Primary client game traffic | +| `http` | `30120` | `TCP` | Asset downloading and client HTTP communication | +| `txadmin` | `40120` | `TCP` | Internal txAdmin web UI and REST management API | + +--- + +## 4. Storage & Persistence Layout + +- **Mount Path**: `/server-data` +- **Default Sizing**: `20Gi` +- **Persisted Content**: + - txAdmin profiles and configuration (`txData/`) + - Server configuration files (`server.cfg`) + - Embedded MariaDB relational data (`mysql/`) + - Custom resources, scripts, and assets (`resources/`) +- **Non-Shadowing Invariant**: The mount path `/server-data` hosts the writable server assets and state without overriding `/opt/cfx-server` or system binaries. + +--- + +## 5. Administration & Remote Console (RCON) + +- **Protocol**: `rest` +- **Console Mode**: `rcon` +- **Authentication**: Token supplied via `spec.rcon.passwordEnv: TXADMIN_TOKEN`. +- **Command Support**: txAdmin REST administrative API commands (`say`, `quit`, console commands). + +--- + +## 6. Modding & Workshop Integration + +- **Modding Framework**: CitizenFX resource system (Lua / C# / JavaScript) +- **Mod Directory Path**: `resources` +- **Workshop Synchronization**: Manual volume management and resource downloading. + +--- + +## 7. Lifecycle & Graceful Shutdown + +- **Stop Command Sequence (`spec.capabilities.lifecycle.stop`)**: + ```yaml + capabilities: + lifecycle: + stop: + - "quit" + ``` +- **Signal Handling**: Entrypoint catches `SIGTERM` / `SIGINT` to gracefully terminate `fxserver` and flush embedded MariaDB data before pod teardown. + +--- + +## 8. Key Invariants & Security + +- **User Matching**: `spec.security.runAsUser: 1000` matches container non-root user (`gameplane`). +- **Environment**: `spec.env` specifies `HOME: /home/gameplane`. +- **Filesystem Permissions**: `spec.security.fsGroup: 1000` guarantees read/write access to `/server-data`. +- **Diagnostic Idle**: Graceful idle without crash-looping if `CFX_LICENSE_KEY` is missing (FR-013). + +--- + +## 9. References & Upstream Documentation + +- Official Documentation: https://docs.fivem.net/docs/server-manual/setting-up-a-server/ +- txAdmin Documentation: https://aka.cfx.re/txAdmin +- Cfx.re Keymaster: https://keymaster.fivem.net diff --git a/fivem/template.yaml b/fivem/template.yaml new file mode 100644 index 0000000..9ed60fb --- /dev/null +++ b/fivem/template.yaml @@ -0,0 +1,128 @@ +# yaml-language-server: $schema=../.schema/gametemplate.schema.json +# Gameplane GameTemplate for FiveM (dedicated server). +# +# Backed by Gameplane-owned FiveM image with supervised txAdmin web management +# and embedded MariaDB database (FR-012), with non-crashing diagnostic idle +# when the CFX license key is missing (FR-013). +# +# Cluster-scoped; apply once per cluster: +# kubectl apply -f modules/fivem/template.yaml + +apiVersion: gameplane.local/v1alpha1 +kind: GameTemplate +metadata: + name: fivem + labels: + gameplane.local/module: fivem +spec: + displayName: FiveM + game: fivem + version: 1.0.0 + categories: [Roleplay, Multiplayer, Sandbox] + accentColor: "#ea580c" + description: | + FiveM dedicated server with bundled txAdmin management and embedded + database supervision. Remote console and commands operate via txAdmin's + REST API. + + image: ghcr.io/valgulnecron/gameplane/fivem:latest@sha256:0000000000000000000000000000000000000000000000000000000000000000 + + env: + - name: HOME + value: /home/gameplane + + security: + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + + ports: + - name: game + containerPort: 30120 + protocol: UDP + advertise: true + - name: http + containerPort: 30120 + protocol: TCP + advertise: true + - name: txadmin + containerPort: 40120 + protocol: TCP + advertise: false + + storage: + size: 20Gi + mountPath: /server-data + + resources: + requests: {cpu: "1", memory: 4Gi} + limits: {cpu: "4", memory: 8Gi} + + rcon: + protocol: rest + port: 40120 + passwordEnv: TXADMIN_TOKEN + + consoleMode: rcon + + probes: + startup: + tcpSocket: + port: txadmin + initialDelaySeconds: 15 + periodSeconds: 10 + failureThreshold: 60 + readiness: + tcpSocket: + port: txadmin + periodSeconds: 10 + failureThreshold: 6 + liveness: + tcpSocket: + port: txadmin + periodSeconds: 30 + failureThreshold: 5 + + capabilities: + lifecycle: + stop: ["quit"] + actions: + - id: broadcast + displayName: Broadcast + icon: megaphone + group: Server + command: 'say "{{.Params.message}}"' + params: + - name: message + displayName: Message + type: string + required: true + mods: + path: resources + extensions: [".zip", ".lua", ".js"] + install: + allowedHosts: + - github.com + - .githubusercontent.com + maxSizeMB: 512 + + configSchema: + - name: CFX_LICENSE_KEY + displayName: Cfx.re License Key + description: CitizenFX server license key from keymaster.fivem.net. + type: password + required: true + default: "" + - name: SV_HOSTNAME + displayName: Server Hostname + type: string + default: "Gameplane FiveM Server" + - name: SV_MAXCLIENTS + displayName: Max Clients + type: int + default: "32" + - name: ENABLE_EMBEDDED_DB + displayName: Enable Embedded Database + description: Run in-pod MariaDB for resources and txAdmin. + type: string + default: "true" diff --git a/garrys-mod/README.md b/garrys-mod/README.md index 70794af..9bc7d8b 100644 --- a/garrys-mod/README.md +++ b/garrys-mod/README.md @@ -118,3 +118,7 @@ on the same TCP listener being bound regardless of RCON state. you change `PUID`/`PGID` outside this template, ensure the PVC's data is readable by that uid or srcds exits on startup instead of looping (the image's own entrypoint checks this explicitly). + +## Sample + +See [`samples/gameserver.yaml`](samples/gameserver.yaml) for an example deployment manifest. diff --git a/garrys-mod/samples/gameserver.yaml b/garrys-mod/samples/gameserver.yaml new file mode 100644 index 0000000..1b6f51e --- /dev/null +++ b/garrys-mod/samples/gameserver.yaml @@ -0,0 +1,28 @@ +apiVersion: gameplane.local/v1alpha1 +kind: GameServer +metadata: + name: garrys-mod-01 + namespace: gameplane-games +spec: + templateRef: + name: garrys-mod + + config: + NAME: "Gameplane Garry's Mod Server" + GSLT: "" + MAXPLAYERS: "16" + GAMEMODE: "sandbox" + MAP: "gm_construct" + + networking: + expose: NodePort + portOverrides: + - name: game + nodePort: 32715 + + storage: + size: 15Gi + + resources: + requests: { cpu: 1, memory: 2Gi } + limits: { cpu: 2, memory: 4Gi } diff --git a/garrys-mod/specs.md b/garrys-mod/specs.md new file mode 100644 index 0000000..585f38d --- /dev/null +++ b/garrys-mod/specs.md @@ -0,0 +1,79 @@ +# Gameplane Module Specification: Garry's Mod + +## 1. Purpose & Scope + +- **Game**: Garry's Mod +- **Module Slug**: `garrys-mod` +- **Role**: Dedicated server module package for Gameplane. +- **Description**: Physics sandbox dedicated server running on Valve's Source engine. Supports custom gamemodes (Sandbox, DarkRP, TTT) and Steam Workshop collection downloading via launch arguments. + +--- + +## 2. Container Image & Architecture + +- **Base Image**: `ceifa/garrysmod:latest@sha256:7c96a32cb2820c7410cb2305ca7ff1b173bfd7c041ea26a8d6715fbc52187c3e` +- **Architecture**: `linux/amd64` +- **Runtime Model**: Pre-baked Source dedicated server (`srcds_run`) container image. +- **User & Execution Context**: Image user `gmod` (UID `1000`), working directory `/home/gmod/server`. + +--- + +## 3. Network Ports & Protocols + +Declared ports under `spec.ports`: + +| Port Name | Container Port | Protocol | Usage / Purpose | +|---|---|---|---| +| `game` | `27015` | `UDP` | Primary client connection traffic | +| `game-tcp` | `27015` | `TCP` | Source engine TCP listener (probe target) | +| `client` | `27005` | `UDP` | Internal client ping/packet port | + +--- + +## 4. Storage & Persistence Layout + +- **Mount Path**: `/home/gmod/server/garrysmod/data` +- **Default Sizing**: `15Gi` +- **Persisted Content**: + - Gamemode data, player persistence, and SQLite databases (`garrysmod/data/`) + - Server logs and text state +- **Non-Shadowing Invariant**: Mount path isolates `/garrysmod/data` to prevent shadowing `/home/gmod/server` or `garrysmod/cfg` baked configs. + +--- + +## 5. Administration & Remote Console (RCON) + +- **Protocol**: `none` (deliberate, documented omission: `ceifa/garrysmod` bakes configuration with no password environment variable). +- **Console Mode**: `none` +- **Authentication**: N/A +- **Command Support**: N/A + +--- + +## 6. Modding & Workshop Integration + +- **Modding Framework**: Steam Workshop Collections +- **Mod Directory Path**: Handled natively by Source engine via launch arguments (`+host_workshop_collection`). +- **Workshop Synchronization**: Automated at container launch by `srcds`. + +--- + +## 7. Lifecycle & Graceful Shutdown + +- **Stop Command Sequence (`spec.capabilities.lifecycle.stop`)**: `[]` (Empty; no RCON console reachable). +- **Signal Handling**: Container intercepts `SIGTERM` and halts `srcds_run` process cleanly. + +--- + +## 8. Key Invariants & Security + +- **User Matching**: Runs as user `1000` (`gmod`). +- **Storage Isolation**: Mounts only `/home/gmod/server/garrysmod/data` to avoid overwriting executable binaries. + +--- + +## 9. References & Upstream Documentation + +- Official Game Documentation: https://gmod.facepunch.com/ +- Upstream Container Repository: https://github.com/ceifa/garrysmod +- Steam Dedicated Server AppID: `4020` diff --git a/hell-let-loose/README.md b/hell-let-loose/README.md new file mode 100644 index 0000000..da0caf6 --- /dev/null +++ b/hell-let-loose/README.md @@ -0,0 +1,31 @@ +# Hell Let Loose + +Hell Let Loose dedicated server package for Gameplane. Runs Unreal Engine dedicated server with match-based gameplay, persistent configuration and logs, Source RCON console on port 22222, and preset community version support. + +## Install + +```sh +kubectl apply -f modules/hell-let-loose/template.yaml +``` + +## Console & RCON + +Remote management uses standard Source RCON on port 22222 TCP. The operator injects the password via `RCON_PASSWORD`. Console actions include `say` (broadcast), `map` (map change), and `kick` (moderation). + +Gameplay is match-based, and server termination does not require a pre-shutdown save command. + +## Ports + +| Name | Port | Protocol | Advertised | Purpose | +| ---- | ---- | -------- | ---------- | ------- | +| `game` | 7787 | UDP | yes | Primary gameplay traffic | +| `query` | 27165 | UDP | yes | Steam A2S query discovery | +| `rcon` | 22222 | TCP | no | Source RCON administration | + +## Storage + +Persistent storage is mounted at `/serverdata/HLL/Saved` (35 GiB default). Server settings, admin lists, and match logs persist across container restarts. + +## Sample + +See [`samples/gameserver.yaml`](samples/gameserver.yaml) for a deployment example. diff --git a/hell-let-loose/module.yaml b/hell-let-loose/module.yaml new file mode 100644 index 0000000..4cccfc8 --- /dev/null +++ b/hell-let-loose/module.yaml @@ -0,0 +1,11 @@ +# yaml-language-server: $schema=../.schema/module.schema.json +apiVersion: gameplane.local/module/v1 +name: hell-let-loose +displayName: Hell Let Loose +version: 1.0.0 +game: hell-let-loose +categories: [Shooter, Tactical, WWII, Multiplayer] +summary: Hell Let Loose dedicated server (Unreal Engine) with Source RCON console, match-based rotation, and community modding support. +homepage: https://www.hellletloose.com/ +license: MIT +gameplaneMinVersion: 0.2.0-beta.7 diff --git a/hell-let-loose/samples/gameserver.yaml b/hell-let-loose/samples/gameserver.yaml new file mode 100644 index 0000000..951b230 --- /dev/null +++ b/hell-let-loose/samples/gameserver.yaml @@ -0,0 +1,31 @@ +apiVersion: gameplane.local/v1alpha1 +kind: GameServer +metadata: + name: hell-let-loose-01 + namespace: gameplane-games +spec: + templateRef: + name: hell-let-loose + + version: default + + config: + SERVER_NAME: "Gameplane Hell Let Loose Server" + SERVER_PASSWORD: "" + RCON_PASSWORD: "secret-rcon-password" + MAX_PLAYERS: 100 + + networking: + expose: NodePort + portOverrides: + - name: game + nodePort: 30787 + - name: query + nodePort: 32165 + + storage: + size: 35Gi + + resources: + requests: { cpu: 2, memory: 8Gi } + limits: { cpu: 6, memory: 16Gi } diff --git a/hell-let-loose/specs.md b/hell-let-loose/specs.md new file mode 100644 index 0000000..ee4ea78 --- /dev/null +++ b/hell-let-loose/specs.md @@ -0,0 +1,80 @@ +# Gameplane Module Specification: Hell Let Loose + +## 1. Purpose & Scope + +- **Game**: Hell Let Loose +- **Module Slug**: `hell-let-loose` +- **Role**: Dedicated server module package for Gameplane. +- **Description**: Dedicated multiplayer server for Black Matter / Team17's Hell Let Loose, featuring large-scale 100-player WWII matches, Source RCON console administration, and optional Vietnam community preset. + +--- + +## 2. Container Image & Architecture + +- **Base Image**: `ghcr.io/valgulnecron/gameplane/hell-let-loose:latest@sha256:0000000000000000000000000000000000000000000000000000000000000000` +- **Architecture**: `linux/amd64` +- **Runtime Model**: SteamCMD Linux dedicated server running Unreal Engine. +- **User & Execution Context**: UID 1000, GID 1000, working directory `/serverdata`. + +--- + +## 3. Network Ports & Protocols + +Declared ports under `spec.ports`: + +| Port Name | Container Port | Protocol | Usage / Purpose | +|---|---|---|---| +| `game` | 7787 | UDP | Primary client gameplay traffic | +| `query` | 27165 | UDP | Steam A2S query discovery | +| `rcon` | 22222 | TCP | Remote administrative console (Source RCON) | + +--- + +## 4. Storage & Persistence Layout + +- **Mount Path**: `/serverdata/HLL/Saved` +- **Default Sizing**: `35Gi` +- **Persisted Content**: + - Server configuration files (`Game.ini`, `Engine.ini`) + - Admin lists and ban files + - Match history and rotation logs +- **Non-Shadowing Invariant**: The mount path isolates the `Saved/` directory without shadowing the server binaries in `/serverdata`. + +--- + +## 5. Administration & Remote Console (RCON) + +- **Protocol**: `source` +- **Console Mode**: `rcon` +- **Authentication**: Password supplied via `RCON_PASSWORD`. +- **Command Support**: Standard HLL RCON commands (`say`, `map`, `kick`). + +--- + +## 6. Modding & Workshop Integration + +- **Modding Framework**: Unreal Engine / community mod presets. +- **Mod Directory Path**: Handled via version catalog presets. + +--- + +## 7. Lifecycle & Graceful Shutdown + +- **Stop Command Sequence (`spec.capabilities.lifecycle.stop`)**: + - Stateless match-based architecture; no world save required prior to shutdown. +- **Signal Handling**: Server cleanly handles `SIGINT`/`SIGTERM`. + +--- + +## 8. Key Invariants & Security + +- **User Matching**: `spec.security.runAsUser: 1000` matches image user. +- **Environment**: `spec.env` contains `HOME=/serverdata`. +- **Filesystem Permissions**: `spec.security.fsGroup: 1000` configured for volume permissions. + +--- + +## 9. References & Upstream Documentation + +- Hell Let Loose Server Administration: https://www.hellletloose.com/ +- Steam Dedicated Server AppID: 686810 diff --git a/hell-let-loose/template.yaml b/hell-let-loose/template.yaml new file mode 100644 index 0000000..41be835 --- /dev/null +++ b/hell-let-loose/template.yaml @@ -0,0 +1,144 @@ +# yaml-language-server: $schema=../.schema/gametemplate.schema.json +# Gameplane GameTemplate for Hell Let Loose (dedicated server). +# +# Runs Unreal Engine dedicated server with Source RCON on TCP 22222. +# Match-based rotation with persistent configurations under /serverdata/HLL/Saved. +# +# Cluster-scoped; apply once per cluster: +# kubectl apply -f modules/hell-let-loose/template.yaml + +apiVersion: gameplane.local/v1alpha1 +kind: GameTemplate +metadata: + name: hell-let-loose + labels: + gameplane.local/module: hell-let-loose +spec: + displayName: Hell Let Loose + game: hell-let-loose + version: 1.0.0 + categories: [Shooter, Tactical, WWII, Multiplayer] + accentColor: "#7f1d1d" + description: | + Hell Let Loose dedicated multiplayer server. Source RCON powers remote + console moderation, map rotation, and server administration on TCP port 22222. + Server configuration and match logs persist under `/serverdata/HLL/Saved`. + + image: ghcr.io/valgulnecron/gameplane/hell-let-loose:latest@sha256:0000000000000000000000000000000000000000000000000000000000000000 + + versions: + - id: default + displayName: "Default (WWII)" + image: ghcr.io/valgulnecron/gameplane/hell-let-loose:latest@sha256:0000000000000000000000000000000000000000000000000000000000000000 + default: true + - id: vietnam + displayName: "Vietnam Community Preset" + image: ghcr.io/valgulnecron/gameplane/hell-let-loose:vietnam@sha256:0000000000000000000000000000000000000000000000000000000000000000 + default: false + + env: + - name: HOME + value: /serverdata + + security: + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + + ports: + - name: game + containerPort: 7787 + protocol: UDP + advertise: true + - name: query + containerPort: 27165 + protocol: UDP + advertise: true + - name: rcon + containerPort: 22222 + protocol: TCP + advertise: false + + storage: + size: 35Gi + mountPath: /serverdata/HLL/Saved + + resources: + requests: {cpu: "2", memory: 8Gi} + limits: {cpu: "6", memory: 16Gi} + + rcon: + protocol: source + port: 22222 + passwordEnv: RCON_PASSWORD + + consoleMode: rcon + + probes: + startup: + tcpSocket: + port: rcon + initialDelaySeconds: 60 + periodSeconds: 15 + failureThreshold: 60 + readiness: + tcpSocket: + port: rcon + periodSeconds: 10 + failureThreshold: 6 + liveness: + tcpSocket: + port: rcon + periodSeconds: 30 + failureThreshold: 5 + + capabilities: + actions: + - id: broadcast + displayName: Broadcast + icon: megaphone + group: Server + command: 'say "{{.Params.message}}"' + params: + - name: message + displayName: Message + type: string + required: true + - id: change-map + displayName: Change Map + icon: map + group: Game + command: "map {{.Params.map}}" + params: + - name: map + displayName: Map Name + type: string + required: true + - id: kick-player + displayName: Kick Player + icon: user-x + group: Moderation + command: "kick {{.Params.user}}" + params: + - name: user + displayName: Player Name + type: string + required: true + + configSchema: + - name: SERVER_NAME + displayName: Server Name + type: string + default: "Gameplane Hell Let Loose Server" + - name: SERVER_PASSWORD + displayName: Server Password + type: password + default: "" + - name: RCON_PASSWORD + displayName: RCON Password + type: password + default: "" + - name: MAX_PLAYERS + displayName: Max Players + type: int + default: 100 diff --git a/left-4-dead-2/README.md b/left-4-dead-2/README.md new file mode 100644 index 0000000..dce8a0e --- /dev/null +++ b/left-4-dead-2/README.md @@ -0,0 +1,31 @@ +# Left 4 Dead 2 + +Left 4 Dead 2 dedicated server backed by [left4devops/l4d2](https://github.com/left4devops/l4d2). Matches are session-based with persistent installation, Source RCON console, and MetaMod/SourceMod plugin capability. + +## Install + +```sh +kubectl apply -f modules/left-4-dead-2/template.yaml +``` + +## Console & RCON + +Remote management uses standard Source RCON on port 27015 TCP. The operator injects the password via `RCON_PASSWORD`. Console actions include `say` (broadcast), `changelevel` (map change), and `kick` (player kick). + +The server stops cleanly by issuing the `quit` command before container shutdown. + +## Ports + +| Name | Port | Protocol | Advertised | Purpose | +| ---- | ---- | -------- | ---------- | ------- | +| `game` | 27015 | UDP | yes | Game traffic | +| `query` | 27015 | UDP | yes | Steam A2S query | +| `rcon` | 27015 | TCP | no | Source RCON administration | + +## Storage + +Storage is mounted at `/home/steam/l4d2-dedicated` (15 GiB default). The mount path stores the downloaded server files and campaign addons without shadowing the image entrypoint scripts in `/home/steam`. + +## Sample + +See [`samples/gameserver.yaml`](samples/gameserver.yaml) for a deployment example. diff --git a/left-4-dead-2/module.yaml b/left-4-dead-2/module.yaml new file mode 100644 index 0000000..9c3cc03 --- /dev/null +++ b/left-4-dead-2/module.yaml @@ -0,0 +1,11 @@ +# yaml-language-server: $schema=../.schema/module.schema.json +apiVersion: gameplane.local/module/v1 +name: left-4-dead-2 +displayName: Left 4 Dead 2 +version: 1.0.0 +game: left-4-dead-2 +categories: [Shooter, Action, Co-op] +summary: Left 4 Dead 2 dedicated server (left4devops/l4d2) with Source RCON console, moderation, and SourceMod/MetaMod addon support. +homepage: https://www.l4d.com/ +license: MIT +gameplaneMinVersion: 0.2.0-beta.7 diff --git a/left-4-dead-2/samples/gameserver.yaml b/left-4-dead-2/samples/gameserver.yaml new file mode 100644 index 0000000..d4b81bc --- /dev/null +++ b/left-4-dead-2/samples/gameserver.yaml @@ -0,0 +1,27 @@ +apiVersion: gameplane.local/v1alpha1 +kind: GameServer +metadata: + name: left-4-dead-2-01 + namespace: gameplane-games +spec: + templateRef: + name: left-4-dead-2 + + config: + SERVER_HOSTNAME: "Gameplane Left 4 Dead 2 Server" + SERVER_PASSWORD: "" + RCON_PASSWORD: "secret-rcon-password" + SERVER_START_MAP: "c1m1_hotel" + + networking: + expose: NodePort + portOverrides: + - name: game + nodePort: 32015 + + storage: + size: 15Gi + + resources: + requests: { cpu: 1, memory: 2Gi } + limits: { cpu: 4, memory: 4Gi } diff --git a/left-4-dead-2/specs.md b/left-4-dead-2/specs.md new file mode 100644 index 0000000..2784e59 --- /dev/null +++ b/left-4-dead-2/specs.md @@ -0,0 +1,87 @@ +# Gameplane Module Specification: Left 4 Dead 2 + +## 1. Purpose & Scope + +- **Game**: Left 4 Dead 2 +- **Module Slug**: `left-4-dead-2` +- **Role**: Dedicated server module package for Gameplane. +- **Description**: Dedicated server package for Left 4 Dead 2, Valve's cooperative first-person shooter. Runs on the Source Engine with matchmaking, campaign/versus modes, and SourceMod extension support. + +--- + +## 2. Container Image & Architecture + +- **Base Image**: `left4devops/l4d2:latest@sha256:66af49bae4f6a615393001078330196f565e9c8bd1d0eacdaf73cd923b3572c3` +- **Architecture**: `linux/amd64` +- **Runtime Model**: SteamCMD-based Source Engine dedicated server (`srcds_linux`). +- **User & Execution Context**: UID 1000, GID 1000, working directory `/home/steam`. + +--- + +## 3. Network Ports & Protocols + +Declared ports under `spec.ports`: + +| Port Name | Container Port | Protocol | Usage / Purpose | +|---|---|---|---| +| `game` | 27015 | UDP | Primary client gameplay traffic | +| `query` | 27015 | UDP | Server browser discovery and Steam A2S_INFO protocol | +| `rcon` | 27015 | TCP | Remote administrative console (Source RCON) | + +--- + +## 4. Storage & Persistence Layout + +- **Mount Path**: `/home/steam/l4d2-dedicated` +- **Default Sizing**: `15Gi` +- **Persisted Content**: + - Downloaded game binaries, maps, and server configurations (`server.cfg`) + - Campaign add-ons and custom VPK files + - Ban lists and player logs +- **Non-Shadowing Invariant**: The mount path does not shadow entrypoint scripts in `/home/steam`. + +--- + +## 5. Administration & Remote Console (RCON) + +- **Protocol**: `source` +- **Console Mode**: `rcon` +- **Authentication**: Password supplied via `RCON_PASSWORD` environment variable. +- **Command Support**: In-game moderation (`kick`, `banid`), map changes (`changelevel`), and announcements (`say`). + +--- + +## 6. Modding & Workshop Integration + +- **Modding Framework**: SourceMod / MetaMod:Source, custom campaign VPKs. +- **Mod Directory Path**: `left4dead2/addons` +- **Workshop Synchronization**: Manual or automated VPK downloading. + +--- + +## 7. Lifecycle & Graceful Shutdown + +- **Stop Command Sequence (`spec.capabilities.lifecycle.stop`)**: + ```yaml + capabilities: + lifecycle: + stop: + - "quit" + ``` +- **Signal Handling**: Dedicated server responds to `SIGINT`/`SIGTERM` and initiates clean shutdown. + +--- + +## 8. Key Invariants & Security + +- **User Matching**: `spec.security.runAsUser: 1000` matches image user (`steam`). +- **Environment**: `spec.env` contains `HOME=/home/steam` for SteamCMD runtime. +- **Filesystem Permissions**: `spec.security.fsGroup: 1000` configured for volume permission mapping. + +--- + +## 9. References & Upstream Documentation + +- Valve Developer Community L4D2 Dedicated Server: https://developer.valvesoftware.com/wiki/Left_4_Dead_2/Dedicated_Servers +- Upstream Container Repository: https://github.com/left4devops/l4d2 +- Steam Dedicated Server AppID: 222860 diff --git a/left-4-dead-2/template.yaml b/left-4-dead-2/template.yaml new file mode 100644 index 0000000..d6e5b81 --- /dev/null +++ b/left-4-dead-2/template.yaml @@ -0,0 +1,151 @@ +# yaml-language-server: $schema=../.schema/gametemplate.schema.json +# Gameplane GameTemplate for Left 4 Dead 2 (dedicated server). +# +# Backed by left4devops/l4d2. L4D2 dedicated server runs under the Source engine +# with standard Source RCON on TCP 27015. Stateless match-based gameplay +# with clean shutdown via `quit`. +# +# Cluster-scoped; apply once per cluster: +# kubectl apply -f modules/left-4-dead-2/template.yaml + +apiVersion: gameplane.local/v1alpha1 +kind: GameTemplate +metadata: + name: left-4-dead-2 + labels: + gameplane.local/module: left-4-dead-2 +spec: + displayName: Left 4 Dead 2 + game: left-4-dead-2 + version: 1.0.0 + categories: [Shooter, Action, Co-op] + accentColor: "#357a38" + description: | + Left 4 Dead 2 dedicated server using [left4devops/l4d2](https://github.com/left4devops/l4d2). + Source RCON powers the console, moderation, and in-game actions; + SourceMod and MetaMod plugins install under `left4dead2/addons`. + + image: left4devops/l4d2:latest@sha256:66af49bae4f6a615393001078330196f565e9c8bd1d0eacdaf73cd923b3572c3 + + versions: + - id: latest + displayName: Latest (Source Engine) + image: left4devops/l4d2:latest@sha256:66af49bae4f6a615393001078330196f565e9c8bd1d0eacdaf73cd923b3572c3 + default: true + + env: + - name: HOME + value: /home/steam + + security: + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + + ports: + - name: game + containerPort: 27015 + protocol: UDP + advertise: true + - name: query + containerPort: 27015 + protocol: UDP + advertise: true + - name: rcon + containerPort: 27015 + protocol: TCP + advertise: false + + storage: + size: 15Gi + mountPath: /home/steam/l4d2-dedicated + + resources: + requests: {cpu: "1", memory: 2Gi} + limits: {cpu: "4", memory: 4Gi} + + rcon: + protocol: source + port: 27015 + passwordEnv: RCON_PASSWORD + + consoleMode: rcon + + probes: + startup: + tcpSocket: + port: rcon + initialDelaySeconds: 30 + periodSeconds: 15 + failureThreshold: 120 + readiness: + tcpSocket: + port: rcon + periodSeconds: 10 + failureThreshold: 6 + liveness: + tcpSocket: + port: rcon + periodSeconds: 30 + failureThreshold: 5 + + capabilities: + lifecycle: + stop: ["quit"] + actions: + - id: broadcast + displayName: Broadcast + icon: megaphone + group: Server + command: 'say "{{.Params.message}}"' + params: + - name: message + displayName: Message + type: string + required: true + - id: change-map + displayName: Change Map + icon: map + group: Game + command: "changelevel {{.Params.map}}" + params: + - name: map + displayName: Map Name + type: string + required: true + - id: kick-player + displayName: Kick Player + icon: user-x + group: Moderation + command: "kick {{.Params.user}}" + params: + - name: user + displayName: Player Name or UserID + type: string + required: true + mods: + path: left4dead2/addons + extensions: [".vpk", ".smx", ".so"] + install: + allowedHosts: + - github.com + - .githubusercontent.com + maxSizeMB: 256 + + configSchema: + - name: SERVER_HOSTNAME + displayName: Server Hostname + type: string + default: "Gameplane Left 4 Dead 2 Server" + - name: SERVER_PASSWORD + displayName: Server Password + type: password + default: "" + - name: RCON_PASSWORD + displayName: RCON Password + type: password + default: "" + - name: SERVER_START_MAP + displayName: Starting Map + type: string + default: "c1m1_hotel" diff --git a/mount-and-blade-2-bannerlord/README.md b/mount-and-blade-2-bannerlord/README.md new file mode 100644 index 0000000..912a073 --- /dev/null +++ b/mount-and-blade-2-bannerlord/README.md @@ -0,0 +1,32 @@ +# Mount & Blade II: Bannerlord + +Mount & Blade II: Bannerlord dedicated multiplayer server for hosting custom skirmish, battle, and siege sessions. + +## Install + +```sh +kubectl apply -f modules/mount-and-blade-2-bannerlord/template.yaml +``` + +## Server Authentication Token + +TaleWorlds requires a dedicated server token to list your server on the official master list. Generate a token inside the game client by opening the console and running `customserver.gettoken`, then configure `SERVER_TOKEN`. + +## Console (PTY) + +Bannerlord dedicated servers run an interactive CLI on standard input. The Gameplane dashboard attaches directly via container PTY (`consoleMode: pty`). Matches are session-based with no persistent campaign state. + +## Ports + +| Name | Port | Protocol | Advertised | Purpose | +| ---- | ---- | -------- | ---------- | ------- | +| `game` | 7210 | UDP | yes | Primary client game traffic | +| `query` | 7211 | UDP | yes | Steam A2S query & master browser | + +## Storage + +Storage is mounted at `/serverdata` (20 GiB default) to hold the server binaries, configuration templates, and custom module XMLs. + +## Sample + +See [`samples/gameserver.yaml`](samples/gameserver.yaml) for an example deployment manifest. diff --git a/mount-and-blade-2-bannerlord/module.yaml b/mount-and-blade-2-bannerlord/module.yaml new file mode 100644 index 0000000..5516c06 --- /dev/null +++ b/mount-and-blade-2-bannerlord/module.yaml @@ -0,0 +1,11 @@ +# yaml-language-server: $schema=../.schema/module.schema.json +apiVersion: gameplane.local/module/v1 +name: mount-and-blade-2-bannerlord +displayName: "Mount & Blade II: Bannerlord" +version: 1.0.0 +game: mount-and-blade-2-bannerlord +categories: [Action, RPG, Medieval, Multiplayer] +summary: "Mount & Blade II: Bannerlord dedicated multiplayer server with PTY console and match-based skirmish hosting." +homepage: https://www.taleworlds.com/en/Games/Bannerlord +license: MIT +gameplaneMinVersion: 0.2.0-beta.7 diff --git a/mount-and-blade-2-bannerlord/samples/gameserver.yaml b/mount-and-blade-2-bannerlord/samples/gameserver.yaml new file mode 100644 index 0000000..8550c97 --- /dev/null +++ b/mount-and-blade-2-bannerlord/samples/gameserver.yaml @@ -0,0 +1,27 @@ +apiVersion: gameplane.local/v1alpha1 +kind: GameServer +metadata: + name: mount-and-blade-2-bannerlord-01 + namespace: gameplane-games +spec: + templateRef: + name: mount-and-blade-2-bannerlord + + config: + SERVER_NAME: "Gameplane Bannerlord Server" + SERVER_TOKEN: "change-me-token" + GAME_TYPE: "TeamDeathmatch" + MAX_PLAYERS: 64 + + networking: + expose: NodePort + portOverrides: + - name: game + nodePort: 30210 + + storage: + size: 20Gi + + resources: + requests: { cpu: 2, memory: 4Gi } + limits: { cpu: 4, memory: 8Gi } diff --git a/mount-and-blade-2-bannerlord/specs.md b/mount-and-blade-2-bannerlord/specs.md new file mode 100644 index 0000000..df45c7e --- /dev/null +++ b/mount-and-blade-2-bannerlord/specs.md @@ -0,0 +1,79 @@ +# Gameplane Module Specification: Mount & Blade II: Bannerlord + +## 1. Purpose & Scope + +- **Game**: Mount & Blade II: Bannerlord +- **Module Slug**: `mount-and-blade-2-bannerlord` +- **Role**: Dedicated server module package for Gameplane. +- **Description**: Medieval combat simulation and roleplay multiplayer server. Provides match-based skirmish and siege modes with PTY-attached server administration. + +--- + +## 2. Container Image & Architecture + +- **Base Image**: `ghcr.io/valgulnecron/gameplane/mount-and-blade-2-bannerlord:latest@sha256:0000000000000000000000000000000000000000000000000000000000000000` +- **Architecture**: `linux/amd64` +- **Runtime Model**: Linux-native / Wine .NET 6 TaleWorlds dedicated server binary. +- **User & Execution Context**: UID `1000`, GID `1000`, working directory `/serverdata`. + +--- + +## 3. Network Ports & Protocols + +Declared ports under `spec.ports`: + +| Port Name | Container Port | Protocol | Usage / Purpose | +|---|---|---|---| +| `game` | `7210` | `UDP` | Client game traffic | +| `query` | `7211` | `UDP` | Server browser and A2S query discovery | + +--- + +## 4. Storage & Persistence Layout + +- **Mount Path**: `/serverdata` +- **Default Sizing**: `20Gi` +- **Persisted Content**: + - Downloaded server binaries and TaleWorlds modules + - Match configuration files (`tdm_config.txt`, `siege_config.txt`) + - Server tokens and authentication credentials +- **Non-Shadowing Invariant**: Dedicated server install root resides within `/serverdata`. + +--- + +## 5. Administration & Remote Console (RCON) + +- **Protocol**: `none` +- **Console Mode**: `pty` +- **Authentication**: N/A (interactive stdin console). +- **Command Support**: TaleWorlds CLI console commands issued via stdin. + +--- + +## 6. Modding & Workshop Integration + +- **Modding Framework**: Bannerlord Module XMLs and sub-modules +- **Mod Directory Path**: `Modules` +- **Workshop Synchronization**: Manual file drop or volume mount. + +--- + +## 7. Lifecycle & Graceful Shutdown + +- **Stop Command Sequence (`spec.capabilities.lifecycle.stop`)**: `[]` (Stateless match-based gameplay; processes terminate cleanly on SIGTERM). +- **Signal Handling**: Container intercepts `SIGTERM` and shuts down the active match cleanly. + +--- + +## 8. Key Invariants & Security + +- **User Matching**: `spec.security.runAsUser: 1000` matches image user. +- **Environment**: `spec.env` defines `HOME: /serverdata`. +- **Filesystem Permissions**: `spec.security.fsGroup: 1000` ensures write permission on `/serverdata`. + +--- + +## 9. References & Upstream Documentation + +- Official Game Documentation: https://www.taleworlds.com/ +- Steam Dedicated Server AppID: `1863440` diff --git a/mount-and-blade-2-bannerlord/template.yaml b/mount-and-blade-2-bannerlord/template.yaml new file mode 100644 index 0000000..fc34851 --- /dev/null +++ b/mount-and-blade-2-bannerlord/template.yaml @@ -0,0 +1,83 @@ +# yaml-language-server: $schema=../.schema/gametemplate.schema.json +# Gameplane GameTemplate for Mount & Blade II: Bannerlord (dedicated server). +# +# Runs match-based skirmish, captain, and siege sessions. Console access is +# driven via container PTY stdin/stdout. +# +# Cluster-scoped; apply once per cluster: +# kubectl apply -f modules/mount-and-blade-2-bannerlord/template.yaml + +apiVersion: gameplane.local/v1alpha1 +kind: GameTemplate +metadata: + name: mount-and-blade-2-bannerlord + labels: + gameplane.local/module: mount-and-blade-2-bannerlord +spec: + displayName: "Mount & Blade II: Bannerlord" + game: mount-and-blade-2-bannerlord + version: 1.0.0 + categories: [Action, RPG, Medieval, Multiplayer] + accentColor: "#b45309" + description: | + Mount & Blade II: Bannerlord dedicated multiplayer server. Supports + skirmish, battle, and siege match hosting. Interactive console connects + via container PTY stdin/stdout. + + image: ghcr.io/valgulnecron/gameplane/mount-and-blade-2-bannerlord:latest@sha256:0000000000000000000000000000000000000000000000000000000000000000 + + env: + - name: HOME + value: /serverdata + + security: + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + + ports: + - name: game + containerPort: 7210 + protocol: UDP + advertise: true + - name: query + containerPort: 7211 + protocol: UDP + advertise: true + + storage: + size: 20Gi + mountPath: /serverdata + + resources: + requests: {cpu: "2", memory: 4Gi} + limits: {cpu: "4", memory: 8Gi} + + rcon: + protocol: none + + consoleMode: pty + + capabilities: + lifecycle: + stop: [] + + configSchema: + - name: SERVER_NAME + displayName: Server Name + type: string + default: "Gameplane Bannerlord Server" + - name: SERVER_TOKEN + displayName: Server Token + description: Dedicated server token generated in-game via customserver.gettoken. + type: password + default: "" + - name: GAME_TYPE + displayName: Game Type + description: Multiplayer game mode (TeamDeathmatch, Siege, Battle, Skirmish). + type: string + default: "TeamDeathmatch" + - name: MAX_PLAYERS + displayName: Max Players + type: int + default: 64 diff --git a/palworld/specs.md b/palworld/specs.md new file mode 100644 index 0000000..fefc92b --- /dev/null +++ b/palworld/specs.md @@ -0,0 +1,88 @@ +# Gameplane Module Specification: Palworld (Dedicated) + +## 1. Purpose & Scope + +- **Game**: Palworld +- **Module Slug**: `palworld` +- **Role**: Dedicated server module package for Gameplane. +- **Description**: Open-world survival crafting multiplayer server. Supports player progression, building, and Pal capture. Administered via native REST API (RCON is deprecated upstream). + +--- + +## 2. Container Image & Architecture + +- **Base Image**: `thijsvanloef/palworld-server-docker:latest@sha256:4145c58737fcd9f9f03d35de6bf33c0008b829b65d3080822bc46830bdb38fdf` +- **Architecture**: `linux/amd64` +- **Runtime Model**: Wrapper container that downloads and updates the Palworld Linux dedicated server files via SteamCMD at boot. +- **User & Execution Context**: Starts as `root`, drops privileges via `gosu` to UID `1000` / GID `1000`. Working directory `/palworld`. + +--- + +## 3. Network Ports & Protocols + +Declared ports under `spec.ports`: + +| Port Name | Container Port | Protocol | Usage / Purpose | +|---|---|---|---| +| `game` | `8211` | `UDP` | Primary game traffic | +| `query` | `27015` | `UDP` | Steam server browser discovery | +| `rest-api` | `8212` | `TCP` | Palworld REST admin API | + +--- + +## 4. Storage & Persistence Layout + +- **Mount Path**: `/palworld` +- **Default Sizing**: `20Gi` +- **Persisted Content**: + - SteamCMD install files (`/palworld/PalServer.sh`, binaries) + - World saves (`/palworld/Pal/Saved/SaveGames`) + - Server configuration files (`/palworld/Pal/Saved/Config/LinuxServer/PalWorldSettings.ini`) + - Mod packages (`/palworld/Pal/Content/Paks`) +- **Non-Shadowing Invariant**: The mount path contains the SteamCMD game root and does not shadow container base utilities. + +--- + +## 5. Administration & Remote Console (RCON) + +- **Protocol**: `palworld` (REST admin API over HTTP Basic auth) +- **Console Mode**: `rcon` +- **Authentication**: HTTP Basic authentication (user `admin`), password supplied via `spec.rcon.passwordEnv: ADMIN_PASSWORD`. +- **Command Support**: REST endpoints for `announce`, `save`, `shutdown`, `stop`, `players`, and `info`. + +--- + +## 6. Modding & Workshop Integration + +- **Modding Framework**: Unreal Engine 5 `.pak` mods +- **Mod Directory Path**: `Pal/Content/Paks` (loader `pak`, extensions: `.pak`) +- **Workshop Synchronization**: Archive upload (.zip/.tar.gz/.pak) via Gameplane Mods tab. + +--- + +## 7. Lifecycle & Graceful Shutdown + +- **Stop Command Sequence (`spec.capabilities.lifecycle.stop`)**: + ```yaml + capabilities: + lifecycle: + stop: + - "save" + - "shutdown 1" + ``` +- **Signal Handling**: REST shutdown persists world state before terminating the process. + +--- + +## 8. Key Invariants & Security + +- **User Matching**: Image starts as root to perform chown before dropping privileges to UID `1000`. Therefore, `runAsUser` must remain unset in `spec.security`. +- **Filesystem Permissions**: `spec.security.fsGroup: 1000` ensures that the dropped-privilege user can write and execute files in `/palworld`. + +--- + +## 9. References & Upstream Documentation + +- Official Game Documentation: https://docs.palworldgame.com/ +- Upstream Container Repository: https://github.com/thijsvanloef/palworld-server-docker +- Steam Dedicated Server AppID: `2394010` diff --git a/project-zomboid/README.md b/project-zomboid/README.md index 528b0ae..f66238a 100644 --- a/project-zomboid/README.md +++ b/project-zomboid/README.md @@ -43,8 +43,7 @@ PZ's canonical default, **27015**. ## Storage -The PVC mounts at `/home/steam`, covering both the image's install tree -(`zomboid/`) and its save/config tree (`zomboid_data/`) on one volume. +The PVC mounts at `/home/steam/Zomboid`, covering the persistent world and configuration data. Default size is 15 GiB — heavy Workshop mod usage may need more. ## Backups @@ -54,3 +53,7 @@ world** action forces one immediately and is also what backup quiesce runs before a snapshot. The image's own `BACKUPS_PERIOD`/`BACKUPS_COUNT` env vars are not wired here — use the Gameplane Backup CRD as the authoritative snapshot path instead. + +## Sample + +See [`samples/gameserver.yaml`](samples/gameserver.yaml) for an example deployment manifest. diff --git a/project-zomboid/samples/gameserver.yaml b/project-zomboid/samples/gameserver.yaml new file mode 100644 index 0000000..3c9f423 --- /dev/null +++ b/project-zomboid/samples/gameserver.yaml @@ -0,0 +1,28 @@ +apiVersion: gameplane.local/v1alpha1 +kind: GameServer +metadata: + name: project-zomboid-01 + namespace: gameplane-games +spec: + templateRef: + name: project-zomboid + + config: + SERVER_NAME: "Gameplane PZ Server" + MAX_MEMORY: "4096m" + ADMIN_USERNAME: "admin" + PVP: "true" + PUBLIC: "false" + + networking: + expose: NodePort + portOverrides: + - name: game + nodePort: 31261 + + storage: + size: 15Gi + + resources: + requests: { cpu: 1, memory: 4Gi } + limits: { cpu: 4, memory: 8Gi } diff --git a/project-zomboid/specs.md b/project-zomboid/specs.md new file mode 100644 index 0000000..c379fe7 --- /dev/null +++ b/project-zomboid/specs.md @@ -0,0 +1,88 @@ +# Gameplane Module Specification: Project Zomboid + +## 1. Purpose & Scope + +- **Game**: Project Zomboid +- **Module Slug**: `project-zomboid` +- **Role**: Dedicated server module package for Gameplane. +- **Description**: Hardcore isometric zombie survival multiplayer server. Supports persistent world state, survivor progression, building, and Steam Workshop mod synchronization. + +--- + +## 2. Container Image & Architecture + +- **Base Image**: `sknnr/zomboid-dedicated-server:latest@sha256:bcb7e2486214b93ee125051e888c87cfbe3aa2654897d944a6254272b7b0ab74` +- **Architecture**: `linux/amd64` +- **Runtime Model**: Java/JVM dedicated server running under Linux with SteamCMD asset fetching. +- **User & Execution Context**: Rootless execution with UID `10000`, GID `10000`, working directory `/home/steam`. + +--- + +## 3. Network Ports & Protocols + +Declared ports under `spec.ports`: + +| Port Name | Container Port | Protocol | Usage / Purpose | +|---|---|---|---| +| `game` | `16261` | `UDP` | Primary client connection port | +| `direct` | `16262` | `UDP` | Direct connect game port | +| `rcon` | `27015` | `TCP` | Source RCON administrative console | + +--- + +## 4. Storage & Persistence Layout + +- **Mount Path**: `/home/steam/Zomboid` +- **Default Sizing**: `15Gi` +- **Persisted Content**: + - Saved worlds, maps, and player states (`Saves/`) + - Server sandbox and configuration settings (`Server/`) + - Whitelist, banlist, and admin accounts (`db/`) +- **Non-Shadowing Invariant**: Volume mounts cleanly to `/home/steam/Zomboid`, which houses the game's data files without shadowing the application binary or launcher scripts located in `/home/steam/zomboid-dedicated`. + +--- + +## 5. Administration & Remote Console (RCON) + +- **Protocol**: `source` +- **Console Mode**: `rcon` +- **Authentication**: Password supplied via `spec.rcon.passwordEnv: RCON_PASSWORD`. +- **Command Support**: Standard Project Zomboid RCON commands (`servermsg`, `save`, `quit`, `players`, `kick`, `ban`, `gunshot`, `startrain`, `stoprain`, `startstorm`). + +--- + +## 6. Modding & Workshop Integration + +- **Modding Framework**: Steam Workshop items configured via environment variables. +- **Mod Directory Path**: Native game-level synchronization using `MOD_IDS` and `WORKSHOP_IDS`. +- **Workshop Synchronization**: Handled natively by the dedicated server at launch via SteamCMD. + +--- + +## 7. Lifecycle & Graceful Shutdown + +- **Stop Command Sequence (`spec.capabilities.lifecycle.stop`)**: + ```yaml + capabilities: + lifecycle: + stop: + - "save" + - "quit" + ``` +- **Signal Handling**: Executes `save` and `quit` via Source RCON prior to container stopping, ensuring map chunks are committed to disk. + +--- + +## 8. Key Invariants & Security + +- **User Matching**: Image runs strictly as non-root user `10000`. `spec.security.runAsUser: 10000` matches image user. +- **Environment**: `spec.env` defines `HOME: /home/steam` to guarantee SteamCMD does not fail creating package directories. +- **Filesystem Permissions**: `spec.security.fsGroup: 10000` ensures proper ownership of mounted persistent storage. + +--- + +## 9. References & Upstream Documentation + +- Official Game Documentation: https://projectzomboid.com/ +- Upstream Container Repository: https://github.com/jsknnr/project-zomboid-server +- Steam Dedicated Server AppID: `380870` diff --git a/rust/README.md b/rust/README.md index 17daf48..6e0da6a 100644 --- a/rust/README.md +++ b/rust/README.md @@ -10,27 +10,14 @@ install, data persists under `/steamcmd/rust`). kubectl apply -f modules/rust/template.yaml ``` -## Console — pty, no RCON - -Rust's admin RCON is **WebSocket-only** (Facepunch's own `rcon.web` -tooling) — it does not speak the Source RCON wire protocol the agent -implements, and there is no raw-TCP fallback. `rcon.protocol` is therefore -`none`; the **Console** tab instead attaches to the container's stdin/stdout -(pty), the same transport every community Docker/tmux wrapper uses to drive -RustDedicated's interactive console. - -Consequences: - -- No Players tab, moderation, quiesce, or one-click actions — all of those - are RCON-backed everywhere in Gameplane, and Rust has no reachable RCON. -- The template declares `capabilities.lifecycle.stop: ["server.save", - "quit"]` (the documented clean-shutdown sequence) for when Gameplane's - operator gains a PTY-stdin stop path — until then it's a documented no-op, - and the server relies on its own autosave interval plus a generous - `terminationGracePeriodSeconds` on SIGTERM. -- Port 28016 (WebRCON) is declared but not advertised — Gameplane doesn't - use it. If you want a third-party WebRCON client (e.g. rustadmin), expose - it yourself via `networking.portOverrides`. +## Console & Remote Management (WebRCON) + +Rust's administrative interface uses **WebSocket-over-TCP** (Facepunch's `rcon.web` / WebRCON). The Gameplane agent implements this natively (`rcon.protocol: websocket`), connecting in-pod to port 28016 with password authentication supplied via `RUST_RCON_PASSWORD`. + +- Console tab connects directly over WebRCON. +- Live player list with regex extraction of display names. +- Moderation (kick, ban) and server actions (broadcast, save-world, announce-restart). +- Graceful stop sequence executes `server.save` followed by `quit` over RCON before pod termination. ## Version picker (Oxide / Carbon) diff --git a/rust/specs.md b/rust/specs.md new file mode 100644 index 0000000..6e65f5f --- /dev/null +++ b/rust/specs.md @@ -0,0 +1,86 @@ +# Gameplane Module Specification: Rust + +## 1. Purpose & Scope + +- **Game**: Rust +- **Module Slug**: `rust` +- **Role**: Dedicated server module package for Gameplane. +- **Description**: Harsh multiplayer survival game dedicated server developed by Facepunch Studios. Backed by `didstopia/rust-server`, supporting persistent procedural worlds, WebRCON management, and Oxide/uMod plugin extension. + +--- + +## 2. Container Image & Architecture + +- **Base Image**: `didstopia/rust-server:latest@sha256:a16589d9182245faee4353cd99e59965e6a9bec17d3d757bb427c48a17546a2b` +- **Architecture**: `linux/amd64` +- **Runtime Model**: SteamCMD Linux dedicated server runner wrapped with Oxide bootstrap options. +- **User & Execution Context**: Starts as root and executes via `didstopia/rust-server` runtime scripts. Working directory `/steamcmd/rust`. + +--- + +## 3. Network Ports & Protocols + +Declared ports under `spec.ports`: + +| Port Name | Container Port | Protocol | Usage / Purpose | +|---|---|---|---| +| `game` | `28015` | `UDP` | Primary client game traffic & server query | +| `rcon` | `28016` | `TCP` | WebRCON administrative websocket interface | + +--- + +## 4. Storage & Persistence Layout + +- **Mount Path**: `/steamcmd/rust` +- **Default Sizing**: `10Gi` +- **Persisted Content**: + - Procedural map files and world save data (`server/my_server_identity/`) + - Server identity files and player blueprints + - Oxide configuration and plugins (`oxide/`) +- **Non-Shadowing Invariant**: Mount path stores game data and downloaded server content; does not shadow entrypoint launcher scripts. + +--- + +## 5. Administration & Remote Console (RCON) + +- **Protocol**: `websocket` (Facepunch WebRCON over TCP) +- **Console Mode**: `rcon` +- **Authentication**: Password supplied via `spec.rcon.passwordEnv: RUST_RCON_PASSWORD`. +- **Command Support**: Standard Facepunch console commands (`playerlist`, `say`, `server.save`, `env.time`, `restart`, `kick`, `ban`). + +--- + +## 6. Modding & Workshop Integration + +- **Modding Framework**: Oxide / uMod plugin loader (C# `.cs` scripts) +- **Mod Directory Path**: `oxide/plugins` +- **Workshop Synchronization**: uMod registry integration with in-app plugin installation. + +--- + +## 7. Lifecycle & Graceful Shutdown + +- **Stop Command Sequence (`spec.capabilities.lifecycle.stop`)**: + ```yaml + capabilities: + lifecycle: + stop: + - "server.save" + - "quit" + ``` +- **Signal Handling**: Executes world save and quit command sequence over WebRCON to prevent rollback before pod termination. + +--- + +## 8. Key Invariants & Security + +- **User Matching**: Runtime permissions handled by `didstopia/rust-server` entrypoint. +- **RCON Protocol**: `websocket` on port 28016 connects internally inside the pod network; not publicly advertised. + +--- + +## 9. References & Upstream Documentation + +- Official Game Documentation: https://rust.facepunch.com/ +- Upstream Container Repository: https://github.com/Didstopia/rust-server +- Steam Dedicated Server AppID: `258550` diff --git a/satisfactory/samples/gameserver.yaml b/satisfactory/samples/gameserver.yaml new file mode 100644 index 0000000..75f1623 --- /dev/null +++ b/satisfactory/samples/gameserver.yaml @@ -0,0 +1,33 @@ +apiVersion: gameplane.local/v1alpha1 +kind: GameServer +metadata: + name: satisfactory-01 + namespace: gameplane-games +spec: + templateRef: + name: satisfactory + + version: stable + + config: + MAXPLAYERS: 4 + MAXTICKRATE: 30 + AUTOSAVENUM: 5 + TIMEOUT: 30 + + networking: + expose: NodePort + portOverrides: + - name: game + nodePort: 30777 + - name: game-tcp + nodePort: 30777 + - name: messaging + nodePort: 30888 + + storage: + size: 25Gi + + resources: + requests: { cpu: 2, memory: 6Gi } + limits: { cpu: 6, memory: 16Gi } diff --git a/satisfactory/specs.md b/satisfactory/specs.md new file mode 100644 index 0000000..71b3852 --- /dev/null +++ b/satisfactory/specs.md @@ -0,0 +1,84 @@ +# Gameplane Module Specification: Satisfactory (Dedicated) + +## 1. Purpose & Scope + +- **Game**: Satisfactory +- **Module Slug**: `satisfactory` +- **Role**: Dedicated server module package for Gameplane. +- **Description**: Dedicated multiplayer server for Coffee Stain Studios' Satisfactory, featuring automated SteamCMD installation, branch selection (stable/experimental), and HTTPS administrative API control. + +--- + +## 2. Container Image & Architecture + +- **Base Image**: `wolveix/satisfactory-server:latest@sha256:e103700ae6ae4c50f19dac80eadb2a805c5b885e179ae2a40850e967bf189efd` +- **Architecture**: `linux/amd64` +- **Runtime Model**: SteamCMD auto-install on container boot with gosu privilege drop. +- **User & Execution Context**: Image entrypoint drops to PUID 1000 / PGID 1000, working directory `/config`. + +--- + +## 3. Network Ports & Protocols + +Declared ports under `spec.ports`: + +| Port Name | Container Port | Protocol | Usage / Purpose | +|---|---|---|---| +| `game` | 7777 | UDP | Primary client gameplay traffic | +| `game-tcp` | 7777 | TCP | HTTPS API and game connection signaling | +| `messaging` | 8888 | TCP | Game socket signaling and messaging | + +--- + +## 4. Storage & Persistence Layout + +- **Mount Path**: `/config` +- **Default Sizing**: `25Gi` +- **Persisted Content**: + - Downloaded server binaries and SteamCMD caches + - Saved factories and worlds (`/config/saved/server-saves`) + - Server configuration and claimed credentials +- **Non-Shadowing Invariant**: The container image installs binaries into `/config` during startup. + +--- + +## 5. Administration & Remote Console (RCON) + +- **Protocol**: `satisfactory` (HTTPS API on port 7777) +- **Console Mode**: `rcon` +- **Authentication**: Password stored in `/config/gameplane/rcon-admin-password` (relative path `gameplane/rcon-admin-password`). +- **Command Support**: HTTPS API `RunCommand` executing `server.SaveGame`. + +--- + +## 6. Modding & Workshop Integration + +- **Modding Framework**: None built-in (mod manager out-of-scope for vanilla dedicated server). + +--- + +## 7. Lifecycle & Graceful Shutdown + +- **Stop Command Sequence (`spec.capabilities.lifecycle.stop`)**: + ```yaml + capabilities: + lifecycle: + stop: + - "SaveGame" + ``` +- **Signal Handling**: Container entrypoint intercepts `SIGTERM` and initiates clean save and exit. + +--- + +## 8. Key Invariants & Security + +- **User Matching**: Starts as root to chown `/config`, then drops privileges to UID/GID 1000 via gosu. +- **Filesystem Permissions**: `spec.security.fsGroup: 1000` ensures correct volume ownership across Kubernetes drivers. + +--- + +## 9. References & Upstream Documentation + +- Satisfactory Dedicated Server Documentation: https://satisfactory.wiki.gg/wiki/Dedicated_servers +- Upstream Container Repository: https://github.com/wolveix/satisfactory-server +- Steam Dedicated Server AppID: 1690800 diff --git a/satisfactory/template.yaml b/satisfactory/template.yaml index f2a194b..ad5a444 100644 --- a/satisfactory/template.yaml +++ b/satisfactory/template.yaml @@ -79,6 +79,9 @@ spec: - name: PGID value: "1000" + security: + fsGroup: 1000 + ports: # Satisfactory 1.0 uses a single game port for both UDP and TCP, plus a # TCP "messaging" port. All are needed by clients, so all are advertised. @@ -150,6 +153,8 @@ spec: # tab, it stays inert until the one-time in-game claim + admin-password # file documented in the header comment above is done. capabilities: + lifecycle: + stop: ["SaveGame"] actions: - id: save-game displayName: Save game diff --git a/squad/README.md b/squad/README.md new file mode 100644 index 0000000..f2e9370 --- /dev/null +++ b/squad/README.md @@ -0,0 +1,31 @@ +# Squad + +Squad dedicated server package for Gameplane. Runs Unreal Engine 4 dedicated server with large-scale 50v50 combined arms matches, persistent server configs and rotation logs, and Source-family RCON console administration. + +## Install + +```sh +kubectl apply -f modules/squad/template.yaml +``` + +## Console & RCON + +Remote management uses standard Source RCON on port 21114 TCP. The operator injects the password via `RCON_PASSWORD`. Console actions include `AdminBroadcast` (announcement), `AdminChangeLayer` (map change), and `AdminKick` (moderation). + +Gameplay is match-based, and server shutdown does not require a pre-shutdown save command. + +## Ports + +| Name | Port | Protocol | Advertised | Purpose | +| ---- | ---- | -------- | ---------- | ------- | +| `game` | 7787 | UDP | yes | Client gameplay | +| `query` | 27165 | UDP | yes | Steam A2S query discovery | +| `rcon` | 21114 | TCP | no | Source RCON administration | + +## Storage + +Persistent storage is mounted at `/serverdata/Squad/Saved` (40 GiB default). Server settings, admin lists, and map rotation history persist across container restarts. + +## Sample + +See [`samples/gameserver.yaml`](samples/gameserver.yaml) for a deployment example. diff --git a/squad/module.yaml b/squad/module.yaml new file mode 100644 index 0000000..806ad5a --- /dev/null +++ b/squad/module.yaml @@ -0,0 +1,11 @@ +# yaml-language-server: $schema=../.schema/module.schema.json +apiVersion: gameplane.local/module/v1 +name: squad +displayName: Squad +version: 1.0.0 +game: squad +categories: [Shooter, Tactical, Military, Multiplayer] +summary: Squad dedicated server (Unreal Engine 4) with Source-family RCON console, match-based rotation, and Steam Workshop modding support. +homepage: https://joinsquad.com/ +license: MIT +gameplaneMinVersion: 0.2.0-beta.7 diff --git a/squad/samples/gameserver.yaml b/squad/samples/gameserver.yaml new file mode 100644 index 0000000..aa3840c --- /dev/null +++ b/squad/samples/gameserver.yaml @@ -0,0 +1,29 @@ +apiVersion: gameplane.local/v1alpha1 +kind: GameServer +metadata: + name: squad-01 + namespace: gameplane-games +spec: + templateRef: + name: squad + + config: + SERVER_NAME: "Gameplane Squad Server" + SERVER_PASSWORD: "" + RCON_PASSWORD: "secret-rcon-password" + MAX_PLAYERS: 100 + + networking: + expose: NodePort + portOverrides: + - name: game + nodePort: 30787 + - name: query + nodePort: 32165 + + storage: + size: 40Gi + + resources: + requests: { cpu: 2, memory: 8Gi } + limits: { cpu: 6, memory: 16Gi } diff --git a/squad/specs.md b/squad/specs.md new file mode 100644 index 0000000..f369404 --- /dev/null +++ b/squad/specs.md @@ -0,0 +1,80 @@ +# Gameplane Module Specification: Squad + +## 1. Purpose & Scope + +- **Game**: Squad +- **Module Slug**: `squad` +- **Role**: Dedicated server module package for Gameplane. +- **Description**: Dedicated multiplayer server for Offworld Industries' Squad, featuring 100-player tactical military matches, Unreal Engine 4 server runtime, and Source-family RCON console administration. + +--- + +## 2. Container Image & Architecture + +- **Base Image**: `ghcr.io/valgulnecron/gameplane/squad:latest@sha256:0000000000000000000000000000000000000000000000000000000000000000` +- **Architecture**: `linux/amd64` +- **Runtime Model**: SteamCMD Linux dedicated server (`SquadServer.sh`). +- **User & Execution Context**: UID 1000, GID 1000, working directory `/serverdata`. + +--- + +## 3. Network Ports & Protocols + +Declared ports under `spec.ports`: + +| Port Name | Container Port | Protocol | Usage / Purpose | +|---|---|---|---| +| `game` | 7787 | UDP | Primary client gameplay traffic | +| `query` | 27165 | UDP | Steam A2S query discovery | +| `rcon` | 21114 | TCP | Remote administrative console (Source RCON) | + +--- + +## 4. Storage & Persistence Layout + +- **Mount Path**: `/serverdata/Squad/Saved` +- **Default Sizing**: `40Gi` +- **Persisted Content**: + - Server configuration files (`Server.cfg`, `Admins.cfg`, `LayerRotation.cfg`) + - Server bans and player licenses + - Match history and performance logs +- **Non-Shadowing Invariant**: The mount path isolates the `Saved/` directory without shadowing the server binaries in `/serverdata`. + +--- + +## 5. Administration & Remote Console (RCON) + +- **Protocol**: `source` +- **Console Mode**: `rcon` +- **Authentication**: Password supplied via `RCON_PASSWORD`. +- **Command Support**: Standard Squad RCON commands (`AdminBroadcast`, `AdminChangeLayer`, `AdminKick`). + +--- + +## 6. Modding & Workshop Integration + +- **Modding Framework**: Steam Workshop (`Plugins/Mods`). +- **Mod Directory Path**: `Plugins/Mods` + +--- + +## 7. Lifecycle & Graceful Shutdown + +- **Stop Command Sequence (`spec.capabilities.lifecycle.stop`)**: + - Stateless match-based architecture; no world save required prior to shutdown. +- **Signal Handling**: Server cleanly handles `SIGINT`/`SIGTERM`. + +--- + +## 8. Key Invariants & Security + +- **User Matching**: `spec.security.runAsUser: 1000` matches image user. +- **Environment**: `spec.env` contains `HOME=/serverdata`. +- **Filesystem Permissions**: `spec.security.fsGroup: 1000` configured for volume ownership. + +--- + +## 9. References & Upstream Documentation + +- Squad Dedicated Server Administration Guide: https://squad.fandom.com/wiki/Server_Administration +- Steam Dedicated Server AppID: 403240 diff --git a/squad/template.yaml b/squad/template.yaml new file mode 100644 index 0000000..0081da6 --- /dev/null +++ b/squad/template.yaml @@ -0,0 +1,152 @@ +# yaml-language-server: $schema=../.schema/gametemplate.schema.json +# Gameplane GameTemplate for Squad (dedicated server). +# +# Runs Unreal Engine 4 dedicated server with Source-family RCON on TCP 21114. +# Match-based rotation with persistent configurations under /serverdata/Squad/Saved. +# +# Cluster-scoped; apply once per cluster: +# kubectl apply -f modules/squad/template.yaml + +apiVersion: gameplane.local/v1alpha1 +kind: GameTemplate +metadata: + name: squad + labels: + gameplane.local/module: squad +spec: + displayName: Squad + game: squad + version: 1.0.0 + categories: [Shooter, Tactical, Military, Multiplayer] + accentColor: "#2d3748" + description: | + Squad dedicated multiplayer server. Source-family RCON powers remote + console moderation, map rotation, and server administration on TCP port 21114. + Server configuration and match logs persist under `/serverdata/Squad/Saved`. + + image: ghcr.io/valgulnecron/gameplane/squad:latest@sha256:0000000000000000000000000000000000000000000000000000000000000000 + + versions: + - id: latest + displayName: "Latest (UE4)" + image: ghcr.io/valgulnecron/gameplane/squad:latest@sha256:0000000000000000000000000000000000000000000000000000000000000000 + default: true + + env: + - name: HOME + value: /serverdata + + security: + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + + ports: + - name: game + containerPort: 7787 + protocol: UDP + advertise: true + - name: query + containerPort: 27165 + protocol: UDP + advertise: true + - name: rcon + containerPort: 21114 + protocol: TCP + advertise: false + + storage: + size: 40Gi + mountPath: /serverdata/Squad/Saved + + resources: + requests: {cpu: "2", memory: 8Gi} + limits: {cpu: "6", memory: 16Gi} + + rcon: + protocol: source + port: 21114 + passwordEnv: RCON_PASSWORD + + consoleMode: rcon + + probes: + startup: + tcpSocket: + port: rcon + initialDelaySeconds: 60 + periodSeconds: 15 + failureThreshold: 60 + readiness: + tcpSocket: + port: rcon + periodSeconds: 10 + failureThreshold: 6 + liveness: + tcpSocket: + port: rcon + periodSeconds: 30 + failureThreshold: 5 + + capabilities: + actions: + - id: broadcast + displayName: Broadcast + icon: megaphone + group: Server + command: 'AdminBroadcast {{.Params.message}}' + params: + - name: message + displayName: Message + type: string + required: true + - id: change-map + displayName: Change Map + icon: map + group: Game + command: "AdminChangeLayer {{.Params.map}}" + params: + - name: map + displayName: Map Name + type: string + required: true + - id: kick-player + displayName: Kick Player + icon: user-x + group: Moderation + command: "AdminKick {{.Params.user}}" + params: + - name: user + displayName: Player Name or SteamID + type: string + required: true + mods: + path: Plugins/Mods + extensions: [".pak"] + install: + allowedHosts: + - github.com + - .githubusercontent.com + maxSizeMB: 1024 + registry: + providers: + - provider: steam + steamAppID: 393380 + + configSchema: + - name: SERVER_NAME + displayName: Server Name + type: string + default: "Gameplane Squad Server" + - name: SERVER_PASSWORD + displayName: Server Password + type: password + default: "" + - name: RCON_PASSWORD + displayName: RCON Password + type: password + default: "" + - name: MAX_PLAYERS + displayName: Max Players + type: int + default: 100 diff --git a/team-fortress-2/README.md b/team-fortress-2/README.md new file mode 100644 index 0000000..d6266a6 --- /dev/null +++ b/team-fortress-2/README.md @@ -0,0 +1,35 @@ +# Team Fortress 2 + +Team Fortress 2 dedicated server backed by [cm2network/tf2](https://github.com/CM2Walki/TF2). Matches are session-based with persistent installation, Source RCON console, and MetaMod/SourceMod plugin capability. + +## Install + +```sh +kubectl apply -f modules/team-fortress-2/template.yaml +``` + +## Server Identity (GSLT) + +To list your server in Valve's public server browser, obtain a Game Server Login Token (GSLT) for App ID `440` from [steamcommunity.com/dev/managegameservers](https://steamcommunity.com/dev/managegameservers) and supply it in `SRCDS_TOKEN`. + +## Console & RCON + +Remote management uses standard Source RCON on port 27015 TCP. The operator injects the password via `SRCDS_RCONPW`. Console actions include `say` (broadcast), `changelevel` (map change), and `mp_restartgame` (restart round). + +The server stops cleanly by issuing the `quit` command before container shutdown. + +## Ports + +| Name | Port | Protocol | Advertised | Purpose | +| ---- | ---- | -------- | ---------- | ------- | +| `game` | 27015 | UDP | yes | Game traffic & Steam A2S query | +| `rcon` | 27015 | TCP | no | Source RCON administration | +| `sourcetv` | 27020 | UDP | yes | SourceTV spectator relay | + +## Storage + +Storage is mounted at `/home/steam/tf-dedicated` (15 GiB default). The mount path stores the downloaded server files and maps without shadowing the image entrypoint scripts in `/home/steam`. + +## Sample + +See [`samples/gameserver.yaml`](samples/gameserver.yaml) for a deployment example. diff --git a/team-fortress-2/module.yaml b/team-fortress-2/module.yaml new file mode 100644 index 0000000..89c9f60 --- /dev/null +++ b/team-fortress-2/module.yaml @@ -0,0 +1,11 @@ +# yaml-language-server: $schema=../.schema/module.schema.json +apiVersion: gameplane.local/module/v1 +name: team-fortress-2 +displayName: Team Fortress 2 +version: 1.0.0 +game: team-fortress-2 +categories: [Shooter, Action, Class-Based] +summary: Team Fortress 2 dedicated server (cm2network/tf2) with Source RCON console, moderation, and SourceMod/MetaMod addon support. +homepage: https://www.teamfortress.com/ +license: MIT +gameplaneMinVersion: 0.2.0-beta.7 diff --git a/team-fortress-2/samples/gameserver.yaml b/team-fortress-2/samples/gameserver.yaml new file mode 100644 index 0000000..b3a4d9d --- /dev/null +++ b/team-fortress-2/samples/gameserver.yaml @@ -0,0 +1,27 @@ +apiVersion: gameplane.local/v1alpha1 +kind: GameServer +metadata: + name: team-fortress-2-01 + namespace: gameplane-games +spec: + templateRef: + name: team-fortress-2 + + config: + SRCDS_TOKEN: "" + SRCDS_HOSTNAME: "Gameplane TF2 Server" + SRCDS_STARTMAP: "ctf_2fort" + SRCDS_MAXPLAYERS: 24 + + networking: + expose: NodePort + portOverrides: + - name: game + nodePort: 32015 + + storage: + size: 15Gi + + resources: + requests: { cpu: 1, memory: 2Gi } + limits: { cpu: 4, memory: 4Gi } diff --git a/team-fortress-2/specs.md b/team-fortress-2/specs.md new file mode 100644 index 0000000..2363024 --- /dev/null +++ b/team-fortress-2/specs.md @@ -0,0 +1,87 @@ +# Gameplane Module Specification: Team Fortress 2 + +## 1. Purpose & Scope + +- **Game**: Team Fortress 2 +- **Module Slug**: `team-fortress-2` +- **Role**: Dedicated server module package for Gameplane. +- **Description**: Team-based multiplayer first-person shooter powered by Valve's Source engine. Session-based gameplay with Source RCON management, SourceMod plugin support, and persistent server installation. + +--- + +## 2. Container Image & Architecture + +- **Base Image**: `cm2network/tf2:latest@sha256:39c03ecbee022350a13aec49c7948263c00b5a0d5874b72faf0f4193de863e7b` +- **Architecture**: `linux/amd64` +- **Runtime Model**: Native Linux `srcds_run` dedicated server binary updated via SteamCMD. +- **User & Execution Context**: UID `1000`, GID `1000`, working directory `/home/steam`. + +--- + +## 3. Network Ports & Protocols + +Declared ports under `spec.ports`: + +| Port Name | Container Port | Protocol | Usage / Purpose | +|---|---|---|---| +| `game` | `27015` | `UDP` | Client game traffic and A2S discovery queries | +| `rcon` | `27015` | `TCP` | Source RCON remote console | +| `sourcetv` | `27020` | `UDP` | SourceTV spectator relay broadcast | + +--- + +## 4. Storage & Persistence Layout + +- **Mount Path**: `/home/steam/tf-dedicated` +- **Default Sizing**: `15Gi` +- **Persisted Content**: + - Dedicated server game files (`tf/`) + - Server configurations (`tf/cfg/server.cfg`, mapcycle) + - Custom maps and SourceMod addons (`tf/maps/`, `tf/addons/`) +- **Non-Shadowing Invariant**: Volume mounts at `/home/steam/tf-dedicated` which preserves the container's `/home/steam/entry.sh` and base files. + +--- + +## 5. Administration & Remote Console (RCON) + +- **Protocol**: `source` +- **Console Mode**: `rcon` +- **Authentication**: Password supplied via `spec.rcon.passwordEnv: SRCDS_RCONPW`. +- **Command Support**: Valve Source RCON commands (`say`, `changelevel`, `mp_restartgame`, `exec`, `status`, `kick`, `ban`). + +--- + +## 6. Modding & Workshop Integration + +- **Modding Framework**: MetaMod:Source and SourceMod (`.smx` plugins) +- **Mod Directory Path**: `tf/addons` +- **Workshop Synchronization**: Steam Workshop maps via mapcycle / start map parameters. + +--- + +## 7. Lifecycle & Graceful Shutdown + +- **Stop Command Sequence (`spec.capabilities.lifecycle.stop`)**: + ```yaml + capabilities: + lifecycle: + stop: + - "quit" + ``` +- **Signal Handling**: Issues `quit` over Source RCON to cleanly disconnect clients before pod scale down. + +--- + +## 8. Key Invariants & Security + +- **User Matching**: `spec.security.runAsUser: 1000` matches image user (`steam`). +- **Environment**: `spec.env` defines `HOME: /home/steam`. +- **Filesystem Permissions**: `spec.security.fsGroup: 1000` guarantees non-root write access to the mounted volume. + +--- + +## 9. References & Upstream Documentation + +- Official Game Documentation: https://www.teamfortress.com/ +- Upstream Container Repository: https://github.com/CM2Walki/TF2 +- Steam Dedicated Server AppID: `232250` diff --git a/team-fortress-2/template.yaml b/team-fortress-2/template.yaml new file mode 100644 index 0000000..c656b46 --- /dev/null +++ b/team-fortress-2/template.yaml @@ -0,0 +1,161 @@ +# yaml-language-server: $schema=../.schema/gametemplate.schema.json +# Gameplane GameTemplate for Team Fortress 2 (dedicated server). +# +# Backed by cm2network/tf2. TF2 dedicated server runs under the Source engine +# with standard Source RCON on TCP 27015. Stateless match-based gameplay +# with clean shutdown via `quit`. +# +# Cluster-scoped; apply once per cluster: +# kubectl apply -f modules/team-fortress-2/template.yaml + +apiVersion: gameplane.local/v1alpha1 +kind: GameTemplate +metadata: + name: team-fortress-2 + labels: + gameplane.local/module: team-fortress-2 +spec: + displayName: Team Fortress 2 + game: team-fortress-2 + version: 1.0.0 + categories: [Shooter, Action, Class-Based] + accentColor: "#bd3b3b" + description: | + Team Fortress 2 dedicated server using [cm2network/tf2](https://github.com/CM2Walki/TF2). + Source RCON powers the console, moderation, and in-game actions; + SourceMod and MetaMod plugins install under `tf/addons`. + + image: cm2network/tf2:latest@sha256:39c03ecbee022350a13aec49c7948263c00b5a0d5874b72faf0f4193de863e7b + + env: + - name: HOME + value: /home/steam + + security: + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + + ports: + - name: game + containerPort: 27015 + protocol: UDP + advertise: true + - name: rcon + containerPort: 27015 + protocol: TCP + advertise: false + - name: sourcetv + containerPort: 27020 + protocol: UDP + advertise: true + + storage: + size: 15Gi + mountPath: /home/steam/tf-dedicated + + resources: + requests: {cpu: "1", memory: 2Gi} + limits: {cpu: "4", memory: 4Gi} + + rcon: + protocol: source + port: 27015 + passwordEnv: SRCDS_RCONPW + + consoleMode: rcon + + probes: + startup: + tcpSocket: + port: rcon + initialDelaySeconds: 30 + periodSeconds: 15 + failureThreshold: 120 + readiness: + tcpSocket: + port: rcon + periodSeconds: 10 + failureThreshold: 6 + liveness: + tcpSocket: + port: rcon + periodSeconds: 30 + failureThreshold: 5 + + capabilities: + lifecycle: + stop: ["quit"] + actions: + - id: broadcast + displayName: Broadcast + icon: megaphone + group: Server + command: 'say "{{.Params.message}}"' + params: + - name: message + displayName: Message + type: string + required: true + - id: change-map + displayName: Change map + icon: map + group: World + danger: true + confirm: true + command: "changelevel {{.Params.map}}" + params: + - name: map + displayName: Map + type: string + default: ctf_2fort + required: true + - id: restart-round + displayName: Restart round + icon: rotate-ccw + group: World + command: "mp_restartgame {{.Params.seconds}}" + params: + - name: seconds + displayName: Seconds + type: int + default: "1" + required: true + - id: reload-config + displayName: Exec config + icon: settings + group: Server + command: "exec {{.Params.cfg}}" + params: + - name: cfg + displayName: Config file + type: string + default: server.cfg + required: true + mods: + path: tf/addons + extensions: [".so", ".smx"] + install: + allowedHosts: + - github.com + - .githubusercontent.com + maxSizeMB: 256 + + configSchema: + - name: SRCDS_TOKEN + displayName: GSLT (Steam Login Token) + description: Game Server Login Token for TF2 (App ID 440) from steamcommunity.com/dev/managegameservers. + type: password + default: "" + - name: SRCDS_HOSTNAME + displayName: Server Hostname + type: string + default: "Gameplane TF2 Server" + - name: SRCDS_STARTMAP + displayName: Starting Map + type: string + default: "ctf_2fort" + - name: SRCDS_MAXPLAYERS + displayName: Max Players + type: int + default: 24 diff --git a/terraria/README.md b/terraria/README.md index 72a68be..02f5fde 100644 --- a/terraria/README.md +++ b/terraria/README.md @@ -66,3 +66,7 @@ This 2.0 module switches the image from `ryshe/terraria` to `LANGUAGE`/`MODPACK`). Treat it as a new template revision: existing running servers are not auto-migrated — create new servers on 2.0, and copy worlds across manually if needed. + +## Sample + +See [`samples/gameserver.yaml`](samples/gameserver.yaml) for an example deployment manifest. diff --git a/terraria/specs.md b/terraria/specs.md new file mode 100644 index 0000000..52495d5 --- /dev/null +++ b/terraria/specs.md @@ -0,0 +1,85 @@ +# Gameplane Module Specification: Terraria + +## 1. Purpose & Scope + +- **Game**: Terraria +- **Module Slug**: `terraria` +- **Role**: Dedicated server module package for Gameplane. +- **Description**: 2D action-adventure sandbox multiplayer server. Supports both vanilla worlds and tModLoader modded instances with interactive PTY console access and auto-saving world management. + +--- + +## 2. Container Image & Architecture + +- **Base Image**: `passivelemon/terraria-docker:terraria-latest@sha256:d60f280522d6c71079638b5036bdd6c4ac9f93c9f3250477ddb345fc2c6933f3` +- **Architecture**: `linux/amd64` +- **Runtime Model**: Standalone .NET 6 runtime server extracted and executed directly within container. +- **User & Execution Context**: Root container execution (`/opt/terraria`). + +--- + +## 3. Network Ports & Protocols + +Declared ports under `spec.ports`: + +| Port Name | Container Port | Protocol | Usage / Purpose | +|---|---|---|---| +| `game` | `7777` | `TCP` | Primary client game connection | + +--- + +## 4. Storage & Persistence Layout + +- **Mount Path**: `/opt/terraria/config` +- **Default Sizing**: `4Gi` +- **Persisted Content**: + - Saved world files (`Worlds/*.wld`) + - Server configuration files (`serverconfig.txt`) + - Banlists and player records + - tModLoader modpacks (`ModPacks/`) +- **Non-Shadowing Invariant**: Mount path isolates user config and world saves without shadowing the container's `/opt/terraria` binary installation. + +--- + +## 5. Administration & Remote Console (RCON) + +- **Protocol**: `none` +- **Console Mode**: `pty` +- **Authentication**: N/A (interactive stdin/stdout console). +- **Command Support**: Standard Terraria CLI commands (`say`, `save`, `kick`, `ban`, `settle`, `motd`, `dawn`, `noon`, `dusk`, `midnight`). + +--- + +## 6. Modding & Workshop Integration + +- **Modding Framework**: tModLoader modpacks (selected via version catalog) +- **Mod Directory Path**: `ModPacks` (extensions: `.zip`, `.tmod`) +- **Workshop Synchronization**: Manual upload and modpack selection via `MODPACK` config field. + +--- + +## 7. Lifecycle & Graceful Shutdown + +- **Stop Command Sequence (`spec.capabilities.lifecycle.stop`)**: + ```yaml + capabilities: + lifecycle: + stop: + - "exit" + ``` +- **Signal Handling**: Injects `exit` to stdin to trigger the engine's built-in world save before terminating. + +--- + +## 8. Key Invariants & Security + +- **Network Wake Protocol**: Declares `wakeProtocol: terraria` under `spec.ports[game]` to allow wake-on-traffic. +- **Readiness Probe**: Uses TCP socket probe against port `7777`. + +--- + +## 9. References & Upstream Documentation + +- Official Game Documentation: https://terraria.org/ +- Upstream Container Repository: https://github.com/PassiveLemon/terraria-docker +- Steam Dedicated Server AppID: `105600` diff --git a/terraria/template.yaml b/terraria/template.yaml index 4c2d58d..19869d4 100644 --- a/terraria/template.yaml +++ b/terraria/template.yaml @@ -55,6 +55,9 @@ spec: cpu: "1" memory: 2Gi + security: + fsGroup: 1000 + # Terraria's command interface is the container stdin (no RCON), so the # Console tab attaches via the kubelet's pod-attach API. rcon: @@ -96,6 +99,8 @@ spec: failureThreshold: 30 capabilities: + lifecycle: + stop: ["exit"] # Terraria has no RCON (rcon.protocol: none above), so every action # below runs over stdin pod-attach instead of RCON — fire-and-forget: # no output returns inline, it appears in the Console tab. transport: diff --git a/test-validate-py.sh b/test-validate-py.sh new file mode 100755 index 0000000..61972b0 --- /dev/null +++ b/test-validate-py.sh @@ -0,0 +1,111 @@ +#!/usr/bin/env bash +# test-validate-py.sh — regression tests for validate.py's directory-layout rules. +# +# Verifies that validate.py enforces README.md, specs.md, and a non-empty samples/ +# directory at ERROR severity for allowlisted/enforced modules, and skips non-enforced +# modules. +# +# Run: modules/test-validate-py.sh + +set -uo pipefail + +HERE="$(cd "$(dirname "$0")" && pwd)" +VALIDATE="$HERE/validate.py" +pass=0 fail=0 + +check() { # check + if [[ "$2" == "$3" ]]; then + echo "ok - $1"; pass=$((pass + 1)) + else + echo "FAIL - $1"; echo " expected: '$2'"; echo " actual: '$3'"; fail=$((fail + 1)) + fi +} + +TMPDIR=$(mktemp -d) +trap 'rm -rf "$TMPDIR"' EXIT + +create_valid_fixture() { + local target="$1" + mkdir -p "$target/samples" + cat <<'EOF' > "$target/template.yaml" +apiVersion: gameplane.local/v1alpha1 +kind: GameTemplate +metadata: + name: test-module +spec: + displayName: Test Module + game: test-game + version: 1.0.0 + image: alpine:latest +EOF + echo "# Test Module" > "$target/README.md" + echo "# Spec" > "$target/specs.md" + echo "apiVersion: gameplane.local/v1alpha1" > "$target/samples/gameserver.yaml" +} + +# 1. Complete module with .layout-enforced passes +MOD="$TMPDIR/valid-mod" +create_valid_fixture "$MOD" +touch "$MOD/.layout-enforced" +out=$(python3 "$VALIDATE" "$MOD" 2>&1) || true +has_layout_err=$(echo "$out" | grep -c "missing-layout-file" || true) +check "complete module passes directory-layout check" "0" "$has_layout_err" + +# 2. Missing README.md reports ERROR +MOD="$TMPDIR/missing-readme" +create_valid_fixture "$MOD" +touch "$MOD/.layout-enforced" +rm "$MOD/README.md" +out=$(python3 "$VALIDATE" "$MOD" 2>&1) || true +has_err=$(echo "$out" | grep -c "ERROR \[missing-layout-file\] module is missing README.md" || true) +check "missing README.md reports ERROR [missing-layout-file]" "1" "$has_err" + +# 3. Missing specs.md reports ERROR +MOD="$TMPDIR/missing-specs" +create_valid_fixture "$MOD" +touch "$MOD/.layout-enforced" +rm "$MOD/specs.md" +out=$(python3 "$VALIDATE" "$MOD" 2>&1) || true +has_err=$(echo "$out" | grep -c "ERROR \[missing-layout-file\] module is missing specs.md" || true) +check "missing specs.md reports ERROR [missing-layout-file]" "1" "$has_err" + +# 4. Missing samples/ directory reports ERROR +MOD="$TMPDIR/missing-samples" +create_valid_fixture "$MOD" +touch "$MOD/.layout-enforced" +rm -rf "$MOD/samples" +out=$(python3 "$VALIDATE" "$MOD" 2>&1) || true +has_err=$(echo "$out" | grep -c "ERROR \[missing-layout-file\] module is missing non-empty samples/ directory" || true) +check "missing samples/ dir reports ERROR [missing-layout-file]" "1" "$has_err" + +# 5. Empty samples/ directory reports ERROR +MOD="$TMPDIR/empty-samples" +create_valid_fixture "$MOD" +touch "$MOD/.layout-enforced" +rm -rf "$MOD/samples"/* +out=$(python3 "$VALIDATE" "$MOD" 2>&1) || true +has_err=$(echo "$out" | grep -c "ERROR \[missing-layout-file\] module is missing non-empty samples/ directory" || true) +check "empty samples/ dir reports ERROR [missing-layout-file]" "1" "$has_err" + +# 6. Non-allowlisted module without .layout-enforced is exempt from rule +MOD="$TMPDIR/unlisted-mod" +create_valid_fixture "$MOD" +rm "$MOD/specs.md" # missing specs.md, but not allowlisted and no marker +out=$(python3 "$VALIDATE" "$MOD" 2>&1) || true +has_layout_err=$(echo "$out" | grep -c "missing-layout-file" || true) +check "non-enforced module is exempt from layout check" "0" "$has_layout_err" + +# 7. Allowlisted slug is enforced without marker file +MOD="$TMPDIR/cs2" +create_valid_fixture "$MOD" +rm "$MOD/specs.md" +out=$(python3 "$VALIDATE" "$MOD" 2>&1) || true +has_err=$(echo "$out" | grep -c "ERROR \[missing-layout-file\] module is missing specs.md" || true) +check "allowlisted module slug (cs2) enforces layout without marker" "1" "$has_err" + +echo "" +echo "Summary: $pass passed, $fail failed." +if [ "$fail" -gt 0 ]; then + exit 1 +fi +exit 0 diff --git a/the-isle/README.md b/the-isle/README.md new file mode 100644 index 0000000..ed94621 --- /dev/null +++ b/the-isle/README.md @@ -0,0 +1,31 @@ +# The Isle + +The Isle dedicated server package for Gameplane. Runs the dedicated server on Unreal Engine with persistent world saves, dinosaur population data, and Source RCON remote management. + +## Install + +```sh +kubectl apply -f modules/the-isle/template.yaml +``` + +## Console & RCON + +Remote console access uses Source RCON on TCP port 8888. Set `RCON_PASSWORD` to authenticate. Administrative commands include `save` for triggering immediate world persistence, `announce` for global broadcasts, and `kick` for player moderation. + +Graceful stop triggers the `save` command prior to container termination. + +## Ports + +| Name | Port | Protocol | Advertised | Purpose | +| ---- | ---- | -------- | ---------- | ------- | +| `game` | 7777 | UDP | yes | Client gameplay | +| `query` | 7778 | UDP | yes | Steam / A2S query | +| `rcon` | 8888 | TCP | no | Source RCON administration | + +## Storage + +Storage is mounted at `/serverdata/TheIsle/Saved` (25 GiB default). All world state, player profiles, and server configuration files persist across pod restarts. + +## Sample + +See [`samples/gameserver.yaml`](samples/gameserver.yaml) for a deployment example. diff --git a/the-isle/module.yaml b/the-isle/module.yaml new file mode 100644 index 0000000..a06d9d6 --- /dev/null +++ b/the-isle/module.yaml @@ -0,0 +1,11 @@ +# yaml-language-server: $schema=../.schema/module.schema.json +apiVersion: gameplane.local/module/v1 +name: the-isle +displayName: The Isle +version: 1.0.0 +game: the-isle +categories: [Survival, Dinosaur, Open World, Multiplayer] +summary: The Isle dedicated server (Unreal Engine 4) with Source RCON remote administration and persistent world save management. +homepage: https://findtheisle.com/ +license: MIT +gameplaneMinVersion: 0.2.0-beta.7 diff --git a/the-isle/samples/gameserver.yaml b/the-isle/samples/gameserver.yaml new file mode 100644 index 0000000..ad886be --- /dev/null +++ b/the-isle/samples/gameserver.yaml @@ -0,0 +1,29 @@ +apiVersion: gameplane.local/v1alpha1 +kind: GameServer +metadata: + name: the-isle-01 + namespace: gameplane-games +spec: + templateRef: + name: the-isle + + config: + SERVER_NAME: "Gameplane The Isle Server" + SERVER_PASSWORD: "" + RCON_PASSWORD: "secret-rcon-password" + MAX_PLAYERS: 50 + + networking: + expose: NodePort + portOverrides: + - name: game + nodePort: 30777 + - name: query + nodePort: 30778 + + storage: + size: 25Gi + + resources: + requests: { cpu: 2, memory: 4Gi } + limits: { cpu: 4, memory: 8Gi } diff --git a/the-isle/specs.md b/the-isle/specs.md new file mode 100644 index 0000000..dd177bc --- /dev/null +++ b/the-isle/specs.md @@ -0,0 +1,85 @@ +# Gameplane Module Specification: The Isle + +## 1. Purpose & Scope + +- **Game**: The Isle +- **Module Slug**: `the-isle` +- **Role**: Dedicated server module package for Gameplane. +- **Description**: Dedicated multiplayer server for The Isle, an open-world dinosaur survival game running on Unreal Engine. + +--- + +## 2. Container Image & Architecture + +- **Base Image**: `ghcr.io/valgulnecron/gameplane/the-isle:latest@sha256:0000000000000000000000000000000000000000000000000000000000000000` +- **Architecture**: `linux/amd64` +- **Runtime Model**: SteamCMD / Unreal Engine Linux dedicated server. +- **User & Execution Context**: UID 1000, GID 1000, working directory `/serverdata`. + +--- + +## 3. Network Ports & Protocols + +Declared ports under `spec.ports`: + +| Port Name | Container Port | Protocol | Usage / Purpose | +|---|---|---|---| +| `game` | 7777 | UDP | Primary client gameplay traffic | +| `query` | 7778 | UDP | Server browser discovery and A2S_INFO query | +| `rcon` | 8888 | TCP | Remote administrative console (Source RCON) | + +--- + +## 4. Storage & Persistence Layout + +- **Mount Path**: `/serverdata/TheIsle/Saved` +- **Default Sizing**: `25Gi` +- **Persisted Content**: + - Saved dinosaur entities and world maps + - Configuration files (`Game.ini`, `Engine.ini`) + - Admin rosters and ban lists +- **Non-Shadowing Invariant**: The mount path isolates the `Saved/` directory without shadowing the server binaries in `/serverdata`. + +--- + +## 5. Administration & Remote Console (RCON) + +- **Protocol**: `source` +- **Console Mode**: `rcon` +- **Authentication**: Password supplied via `RCON_PASSWORD`. +- **Command Support**: Standard UE4 console commands including `save`, `announce`, `kick`. + +--- + +## 6. Modding & Workshop Integration + +- **Modding Framework**: Steam Workshop / Unreal Engine custom assets. +- **Mod Directory Path**: Unset / baked in game install. + +--- + +## 7. Lifecycle & Graceful Shutdown + +- **Stop Command Sequence (`spec.capabilities.lifecycle.stop`)**: + ```yaml + capabilities: + lifecycle: + stop: + - "save" + ``` +- **Signal Handling**: Dedicated server initiates state flush on `SIGINT`/`SIGTERM`. + +--- + +## 8. Key Invariants & Security + +- **User Matching**: `spec.security.runAsUser: 1000` matches image user. +- **Environment**: `spec.env` contains `HOME=/serverdata`. +- **Filesystem Permissions**: `spec.security.fsGroup: 1000` configured for volume ownership. + +--- + +## 9. References & Upstream Documentation + +- The Isle Server Hosting Guide: https://findtheisle.com/ +- Steam Dedicated Server AppID: 412680 diff --git a/the-isle/template.yaml b/the-isle/template.yaml new file mode 100644 index 0000000..501b014 --- /dev/null +++ b/the-isle/template.yaml @@ -0,0 +1,137 @@ +# yaml-language-server: $schema=../.schema/gametemplate.schema.json +# Gameplane GameTemplate for The Isle (dedicated server). +# +# Runs Unreal Engine 4 dedicated server with Source RCON on TCP 8888. +# World state, dinosaurs, and configs persist under /serverdata/TheIsle/Saved. +# +# Cluster-scoped; apply once per cluster: +# kubectl apply -f modules/the-isle/template.yaml + +apiVersion: gameplane.local/v1alpha1 +kind: GameTemplate +metadata: + name: the-isle + labels: + gameplane.local/module: the-isle +spec: + displayName: The Isle + game: the-isle + version: 1.0.0 + categories: [Survival, Dinosaur, Open World, Multiplayer] + accentColor: "#22543d" + description: | + The Isle dedicated multiplayer server running on Unreal Engine. + Source RCON powers remote console administration on port 8888 TCP; + saves and server configurations persist under `TheIsle/Saved`. + + image: ghcr.io/valgulnecron/gameplane/the-isle:latest@sha256:0000000000000000000000000000000000000000000000000000000000000000 + + versions: + - id: evrima + displayName: "The Isle (Evrima / UE)" + image: ghcr.io/valgulnecron/gameplane/the-isle:latest@sha256:0000000000000000000000000000000000000000000000000000000000000000 + default: true + + env: + - name: HOME + value: /serverdata + + security: + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + + ports: + - name: game + containerPort: 7777 + protocol: UDP + advertise: true + - name: query + containerPort: 7778 + protocol: UDP + advertise: true + - name: rcon + containerPort: 8888 + protocol: TCP + advertise: false + + storage: + size: 25Gi + mountPath: /serverdata/TheIsle/Saved + + resources: + requests: {cpu: "2", memory: 4Gi} + limits: {cpu: "4", memory: 8Gi} + + rcon: + protocol: source + port: 8888 + passwordEnv: RCON_PASSWORD + + consoleMode: rcon + + probes: + startup: + tcpSocket: + port: rcon + initialDelaySeconds: 60 + periodSeconds: 15 + failureThreshold: 60 + readiness: + tcpSocket: + port: rcon + periodSeconds: 10 + failureThreshold: 6 + liveness: + tcpSocket: + port: rcon + periodSeconds: 30 + failureThreshold: 5 + + capabilities: + lifecycle: + stop: ["save"] + actions: + - id: save-world + displayName: Save World + icon: save + group: Server + command: "save" + - id: broadcast + displayName: Broadcast + icon: megaphone + group: Server + command: 'announce "{{.Params.message}}"' + params: + - name: message + displayName: Message + type: string + required: true + - id: kick-player + displayName: Kick Player + icon: user-x + group: Moderation + command: "kick {{.Params.user}}" + params: + - name: user + displayName: Player Name or ID + type: string + required: true + + configSchema: + - name: SERVER_NAME + displayName: Server Name + type: string + default: "Gameplane The Isle Server" + - name: SERVER_PASSWORD + displayName: Server Password + type: password + default: "" + - name: RCON_PASSWORD + displayName: RCON Password + type: password + default: "" + - name: MAX_PLAYERS + displayName: Max Players + type: int + default: 50 diff --git a/tmodloader/README.md b/tmodloader/README.md new file mode 100644 index 0000000..083bc94 --- /dev/null +++ b/tmodloader/README.md @@ -0,0 +1,36 @@ +# tModLoader + +tModLoader dedicated server for modded Terraria gameplay, packaged as a Gameplane module. + +**Image:** [`passivelemon/terraria-docker`](https://github.com/PassiveLemon/terraria-docker) (`tmodloader-latest` tag) + +## Install + +```sh +kubectl apply -f modules/tmodloader/template.yaml +``` + +## Mod Management + +tModLoader mods (`.tmod`) and modpacks are managed through the **Mods** tab. Mod files are stored under `/root/.local/share/Terraria/tModLoader/Mods`. Installs are allowed from GitHub release archives (max 512 MiB). + +## Console (PTY) + +Terraria engines do not provide an RCON TCP port. The **Console** tab attaches directly to the container's stdin/stdout (pty) using the kubelet pod-attach API. Stop sequence issues `exit` to trigger world flushing before shutdown. + +## Ports + +| Name | Port | Protocol | Advertised | Purpose | +| ---- | ---- | -------- | ---------- | ------- | +| `game` | 7777 | TCP | yes | Primary game traffic | + +## Storage + +Storage is mounted at `/root/.local/share/Terraria/tModLoader` (4 GiB default), holding: +- World files (`Worlds/`) +- Installed mods (`Mods/`) +- Mod configurations and loadouts + +## Sample + +See [`samples/gameserver.yaml`](samples/gameserver.yaml) for a deployment manifest example. diff --git a/tmodloader/module.yaml b/tmodloader/module.yaml new file mode 100644 index 0000000..e23028a --- /dev/null +++ b/tmodloader/module.yaml @@ -0,0 +1,11 @@ +# yaml-language-server: $schema=../.schema/module.schema.json +apiVersion: gameplane.local/module/v1 +name: tmodloader +displayName: tModLoader +version: 1.0.0 +game: tmodloader +categories: [Sandbox, Survival, Adventure, Modded] +summary: tModLoader dedicated server for modded Terraria gameplay with PTY console, .tmod mod manager, and world saves. +homepage: https://www.tmodloader.net/ +license: MIT +gameplaneMinVersion: 0.2.0-beta.7 diff --git a/tmodloader/samples/gameserver.yaml b/tmodloader/samples/gameserver.yaml new file mode 100644 index 0000000..174860e --- /dev/null +++ b/tmodloader/samples/gameserver.yaml @@ -0,0 +1,29 @@ +apiVersion: gameplane.local/v1alpha1 +kind: GameServer +metadata: + name: tmodloader-01 + namespace: gameplane-games +spec: + templateRef: + name: tmodloader + + config: + WORLDNAME: "GameplaneTMod" + AUTOCREATE: "2" + DIFFICULTY: "1" + MAXPLAYERS: "8" + MOTD: "Welcome to Gameplane tModLoader Server" + MODPACK: "" + + networking: + expose: NodePort + portOverrides: + - name: game + nodePort: 31777 + + storage: + size: 4Gi + + resources: + requests: { cpu: 500m, memory: 1Gi } + limits: { cpu: 2, memory: 4Gi } diff --git a/tmodloader/specs.md b/tmodloader/specs.md new file mode 100644 index 0000000..140e4ba --- /dev/null +++ b/tmodloader/specs.md @@ -0,0 +1,84 @@ +# Gameplane Module Specification: tModLoader + +## 1. Purpose & Scope + +- **Game**: tModLoader (Terraria Modding Framework) +- **Module Slug**: `tmodloader` +- **Role**: Dedicated server module package for Gameplane. +- **Description**: Modded Terraria dedicated server running the tModLoader .NET 6 framework. Supports `.tmod` community mods, custom modpacks, and PTY-attached server administration. + +--- + +## 2. Container Image & Architecture + +- **Base Image**: `passivelemon/terraria-docker:tmodloader-latest@sha256:3f2d8703421159f1037084bd2c0901a3a63b85a00801cf36f6f928e8b666b44e` +- **Architecture**: `linux/amd64` +- **Runtime Model**: .NET 6 runtime host running `tModLoader.dll` with integrated mono/native runtime dependencies. +- **User & Execution Context**: Root container execution (`/`). + +--- + +## 3. Network Ports & Protocols + +Declared ports under `spec.ports`: + +| Port Name | Container Port | Protocol | Usage / Purpose | +|---|---|---|---| +| `game` | `7777` | `TCP` | Main client game traffic | + +--- + +## 4. Storage & Persistence Layout + +- **Mount Path**: `/root/.local/share/Terraria/tModLoader` +- **Default Sizing**: `4Gi` +- **Persisted Content**: + - Generated modded worlds (`Worlds/*.wld`) + - Downloaded `.tmod` mod archives (`Mods/`) + - Server config, mod configs, and banlists +- **Non-Shadowing Invariant**: Mounted path preserves `/opt/terraria` binary installation and launcher scripts. + +--- + +## 5. Administration & Remote Console (RCON) + +- **Protocol**: `none` +- **Console Mode**: `pty` +- **Authentication**: N/A (interactive terminal console). +- **Command Support**: Standard Terraria and tModLoader CLI commands (`say`, `save`, `exit`, mod reload commands). + +--- + +## 6. Modding & Workshop Integration + +- **Modding Framework**: tModLoader Mod System (`.tmod` packages) +- **Mod Directory Path**: `Mods` +- **Workshop Synchronization**: Manual install and URL download via Gameplane Mods tab. + +--- + +## 7. Lifecycle & Graceful Shutdown + +- **Stop Command Sequence (`spec.capabilities.lifecycle.stop`)**: + ```yaml + capabilities: + lifecycle: + stop: + - "exit" + ``` +- **Signal Handling**: Issues `exit` to stdin for orderly world saving prior to termination. + +--- + +## 8. Key Invariants & Security + +- **Wake-On-Traffic**: Declares `wakeProtocol: terraria` under `spec.ports[game]`. +- **Readiness Probe**: Uses TCP probe on port `7777`. + +--- + +## 9. References & Upstream Documentation + +- Official Documentation: https://www.tmodloader.net/ +- Upstream Container Repository: https://github.com/PassiveLemon/terraria-docker +- Steam Dedicated Server AppID: `1281930` diff --git a/tmodloader/template.yaml b/tmodloader/template.yaml new file mode 100644 index 0000000..6ec4809 --- /dev/null +++ b/tmodloader/template.yaml @@ -0,0 +1,126 @@ +# yaml-language-server: $schema=../.schema/gametemplate.schema.json +# Gameplane GameTemplate for tModLoader (dedicated server). +# +# Backed by passivelemon/terraria-docker:tmodloader-latest. Dedicated server +# running the tModLoader .NET 6 modding framework for Terraria. Console +# attaches to container stdin (pty); no RCON. +# +# Cluster-scoped; apply once per cluster: +# kubectl apply -f modules/tmodloader/template.yaml + +apiVersion: gameplane.local/v1alpha1 +kind: GameTemplate +metadata: + name: tmodloader + labels: + gameplane.local/module: tmodloader +spec: + displayName: tModLoader + game: tmodloader + version: 1.0.0 + categories: [Sandbox, Survival, Adventure, Modded] + accentColor: "#10b981" + description: | + tModLoader dedicated server for modded Terraria. Supports custom `.tmod` + mods and modpacks with interactive console via container stdin (PTY). + + image: passivelemon/terraria-docker:tmodloader-latest@sha256:3f2d8703421159f1037084bd2c0901a3a63b85a00801cf36f6f928e8b666b44e + + security: + fsGroup: 1000 + + ports: + - name: game + containerPort: 7777 + protocol: TCP + advertise: true + wakeProtocol: terraria + + storage: + size: 4Gi + mountPath: /root/.local/share/Terraria/tModLoader + + resources: + requests: + cpu: 500m + memory: 1Gi + limits: + cpu: "2" + memory: 4Gi + + rcon: + protocol: none + + consoleMode: pty + + probes: + readiness: + tcpSocket: + port: game + initialDelaySeconds: 30 + periodSeconds: 10 + failureThreshold: 30 + + capabilities: + lifecycle: + stop: ["exit"] + actions: + - id: broadcast + displayName: Broadcast message + icon: megaphone + transport: stdin + command: "say {{.Params.message}}" + params: + - name: message + displayName: Message + type: string + required: true + - id: save-world + displayName: Save world + icon: save + transport: stdin + command: "save" + mods: + path: Mods + extensions: [".tmod", ".zip"] + install: + allowedHosts: + - github.com + - .githubusercontent.com + maxSizeMB: 512 + + configSchema: + - name: WORLDNAME + displayName: World name + type: string + default: "tModLoaderWorld" + required: true + - name: AUTOCREATE + displayName: World size + description: World size to generate if the world doesn't exist. 1 small, 2 medium, 3 large. + type: enum + enum: ["1", "2", "3"] + default: "2" + - name: DIFFICULTY + displayName: Difficulty + description: "0 classic, 1 expert, 2 master, 3 journey." + type: enum + enum: ["0", "1", "2", "3"] + default: "1" + - name: PASSWORD + displayName: Server password + type: password + default: "" + - name: MAXPLAYERS + displayName: Max players + type: int + default: "8" + - name: MOTD + displayName: MOTD + type: string + default: "Welcome to Gameplane tModLoader Server" + - name: MODPACK + displayName: Active modpack + description: Active modpack name under ModPacks. Leave blank for none. + type: string + default: "" diff --git a/valheim/specs.md b/valheim/specs.md new file mode 100644 index 0000000..b463155 --- /dev/null +++ b/valheim/specs.md @@ -0,0 +1,87 @@ +# Gameplane Module Specification: Valheim (Dedicated) + +## 1. Purpose & Scope + +- **Game**: Valheim +- **Module Slug**: `valheim` +- **Role**: Dedicated server module package for Gameplane. +- **Description**: Dedicated server package for Iron Gate's Valheim, with BepInEx mod volume support, stable/public-test channel switching, and Thunderstore ecosystem integration. + +--- + +## 2. Container Image & Architecture + +- **Base Image**: `lloesche/valheim-server:latest@sha256:20fde516ce311e6084f82f295c9eb6934af57b357c657937a04f62bdf5946149` +- **Architecture**: `linux/amd64` +- **Runtime Model**: SteamCMD auto-install on container boot with supervisord process management. +- **User & Execution Context**: Image managed root/steam user with `/config` data directory. + +--- + +## 3. Network Ports & Protocols + +Declared ports under `spec.ports`: + +| Port Name | Container Port | Protocol | Usage / Purpose | +|---|---|---|---| +| `game` | 2456 | UDP | Main game traffic | +| `game2` | 2457 | UDP | Steam query traffic | +| `game3` | 2458 | UDP | Auxiliary / crossplay relay | +| `status` | 80 | TCP | Internal health and status metrics API | + +--- + +## 4. Storage & Persistence Layout + +- **Mount Path**: `/config` +- **Default Sizing**: `5Gi` +- **Persisted Content**: + - World databases (`/config/worlds_local/`) + - BepInEx plugins and configuration (`/config/bepinex/`) + - Server identity files and admin lists (`adminlist.txt`) +- **Non-Shadowing Invariant**: The mount path isolates server state and mods under `/config`. + +--- + +## 5. Administration & Remote Console (RCON) + +- **Protocol**: `none` +- **Console Mode**: `pty` (stdout streams server logs; status metrics queried via HTTP) +- **Authentication**: N/A +- **Command Support**: In-game administrative console via F5. + +--- + +## 6. Modding & Workshop Integration + +- **Modding Framework**: BepInEx plugins. +- **Mod Directory Path**: `bepinex/plugins` +- **Registry Integration**: Thunderstore community registry integration. + +--- + +## 7. Lifecycle & Graceful Shutdown + +- **Stop Command Sequence (`spec.capabilities.lifecycle.stop`)**: + ```yaml + capabilities: + lifecycle: + stop: + - "save" + ``` +- **Signal Handling**: Container traps `SIGINT`/`SIGTERM` to initiate clean world persistence. + +--- + +## 8. Key Invariants & Security + +- **User Matching**: Default image execution. +- **Filesystem Permissions**: Persistent storage mounted at `/config`. + +--- + +## 9. References & Upstream Documentation + +- Valheim Dedicated Server Guide: https://valheim.fandom.com/wiki/Dedicated_servers +- Upstream Container Repository: https://github.com/lloesche/valheim-server-docker +- Steam Dedicated Server AppID: 896660 diff --git a/valheim/template.yaml b/valheim/template.yaml index 0567642..c1c5540 100644 --- a/valheim/template.yaml +++ b/valheim/template.yaml @@ -116,6 +116,8 @@ spec: # BepInEx plugins live under /config/bepinex/plugins; the image loads them # when BEPINEX is enabled (see the config field below). capabilities: + lifecycle: + stop: ["save"] mods: loaders: bepinex: diff --git a/validate.py b/validate.py index 8aebf52..d4bccb6 100644 --- a/validate.py +++ b/validate.py @@ -126,7 +126,7 @@ # the GameTemplate CRD's rcon.protocol enum and agent/internal/rcon/. A protocol # listed here but not implemented lets a module ship a console that never # connects, so this list is deliberately conservative. -RCON_PROTOCOLS = ("source", "telnet", "websocket", "battleye", "satisfactory", "palworld", "nuclearoption", "none") +RCON_PROTOCOLS = ("source", "telnet", "websocket", "battleye", "satisfactory", "palworld", "nuclearoption", "rest", "cli", "none") @@ -1026,6 +1026,62 @@ def rule_credential_fields_must_be_password(spec: dict) -> list[Finding]: return findings +LAYOUT_ENFORCED_MODULES = { + "cs2", + "palworld", + "fivem", + "rust", + "project-zomboid", + "team-fortress-2", + "dayz", + "farming-simulator-25", + "euro-truck-simulator-2", + "garrys-mod", + "mount-and-blade-2-bannerlord", + "terraria", + "7-days-to-die", + "tmodloader", + "beammp", + "ark-survival-ascended", + "left-4-dead-2", + "factorio", + "the-isle", + "dont-starve-together", + "valheim", + "satisfactory", + "ark-survival-evolved", + "arma-reforger", + "hell-let-loose", + "squad", +} + + +def rule_directory_layout(module_dir: Path) -> list[Finding]: + """Ensure required metadata, documentation, and samples are present. + + Fires at ERROR severity when README.md, specs.md, or a non-empty samples/ + directory is missing. Scoped to the 26 top-Steam modules or any module + with a .layout-enforced marker file. + """ + if module_dir.name not in LAYOUT_ENFORCED_MODULES and not (module_dir / ".layout-enforced").exists(): + return [] + + findings: list[Finding] = [] + readme = module_dir / "README.md" + if not readme.is_file() or readme.stat().st_size == 0: + findings.append(Finding(ERROR, "missing-layout-file", "module is missing README.md")) + + specs = module_dir / "specs.md" + if not specs.is_file() or specs.stat().st_size == 0: + findings.append(Finding(ERROR, "missing-layout-file", "module is missing specs.md")) + + samples = module_dir / "samples" + if not samples.is_dir() or not any(samples.iterdir()): + findings.append(Finding(ERROR, "missing-layout-file", "module is missing non-empty samples/ directory")) + + return findings + + # -------------------------------------------------------------------------- # Orchestration # -------------------------------------------------------------------------- @@ -1179,7 +1235,7 @@ def pin_templates(module_dirs: list[Path]) -> int: def discover_modules(root: Path, names: list[str] | None) -> list[Path]: if names: - return [root / n for n in names] + return [Path(n) if Path(n).is_dir() else root / n for n in names] out = [] for child in sorted(root.iterdir()): if child.is_dir() and (child / "template.yaml").exists(): @@ -1223,7 +1279,7 @@ def main(argv: list[str]) -> int: continue spec = (doc or {}).get("spec") or {} - findings = validate_module(spec, cache) + findings = rule_directory_layout(module_dir) + validate_module(spec, cache) print(f"== {module_dir.name} ==") if not findings: print(" OK (no findings)")