Repository navigation
v0.3.0
The first Gameplane release without a pre-release suffix, a pre-v1 release. It
contains every change made since v0.2.0-beta.8, including the changes first
previewed in v0.3.0-rc.1 and the pre-release audit fixes summarized under
0.3.0-rc.2 below. It does not claim v1 or production support; see
docs/roadmap.md.
Upgrade Notes
-
Network capture feature adds an emptyDir volume to every game pod: The
network capture feature (protocol reverse-engineering and admin-only traffic
diagnostics) requires a pre-provisioned volume on every game pod, whether or
not that server will ever opt into capture. This is a structural requirement:
Kubernetes ephemeral containers can add containers to a running pod without pod
recreation, but cannot add volumes. The captureemptyDirvolume must exist in
the pod template before the ephemeral sidecar is injected, so it is provisioned
unconditionally at cluster install time.
Consequence: every existing GameServer will roll once on upgrade to this
release. Each pod will be recreated with the new template (containing the
empty capture volume), interrupting any active player sessions. This is a
one-time event at upgrade time and does not repeat on each server start. Plan
your upgrade window for a time when you can tolerate a rolling restart of all
game servers. The upgrade note for FR-001 and SC-007 (specification items
tracking pod immutability) is updated to reflect this: non-opted-in game servers
are no longer byte-identical between captures disabled and enabled; they carry
an empty, unused capture volume. What they do NOT carry is the capture sidecar
container itself (no ephemeral container, noCAP_NET_RAW, no network access
change). -
Owner-only server operations need the owner or an admin: transferring
ownership, editing collaborators, wiping data and deleting a server now need
the server's owner or an admin. Operator-role users keep their other server
permissions, but can no longer run these four operations on servers they
don't own. Servers without a recorded owner (for example ones created with
kubectl or GitOps) can be transferred, wiped or deleted only by an admin. -
Playit tunnel NetworkPolicy egress widened: the operator-managed
NetworkPolicy for a GameServer using theplayittunnel provider now adds
an unrestricted egress rule (all ports and protocols, any destination)
alongside the DNS/advertised-ports rule every provider gets. Previously
playit got only that rule, so it could not reach its relay (F-262). -
Helm 3 reinstalling over leftover CRDs runs the CRD-apply hook: a
helm installonto Gameplane CRDs left behind by an uninstalled release
runs thecrds.autoApplyhook to bring them up to date, so air-gapped
clusters needcrds.autoApply.imagemirrored for that case. Under Helm 4,
existing CRDs are applied natively; CRDs last applied by v0.2.0-beta.8 or
earlier need a one-timehelm install --force-conflicts(F-218).
Added
- Configurable backup/restore Job limits: the restic backup and restore Jobs' retry limit and deadline are now Helm values (
operator.backupJobBackoffLimit, default2;operator.backupJobActiveDeadlineSeconds, default86400), passed to the operator as--backup-job-backoff-limit/--backup-job-active-deadline-seconds. - Multicluster gateways and unified server management: an optional
private mTLS agent gateway (with its own Helm deployment) lets one dashboard
and API manage authorized game servers across independent clusters.
Dashboard, Servers, Backups and Search combine every site with optional
location filters, and console, files, players, mods and captures are routed
to the selected server's cluster (#542). - User theme customization (feature 016): Settings → Theme & Appearance
offers preset themes (Modern Pink, Legacy Orange) and custom color selection
with live WCAG AA contrast validation. Custom accent and surface colors are
persisted per user; appearance mode (light/dark) can follow the surface color.
Custom CSS overlay allows per-user style tweaks; all customizations support
import/export. Safe mode (?safe-mode=1or keyboard shortcut) suspends custom
CSS and shows a banner without reloading. Theme bootstrap on login prevents
flash; preferences are read/write controlled via RBAC (every user sees only
their own) (#415). - Network capture sidecar (feature 003): Admin-only, on-demand packet capture
from running GameServer pods. Enabled per server via a Dashboard toggle, capture
runs as an ephemeral sidecar container (no pod restart), is recorded as PCAPNG
(Wireshark-compatible), and auto-expires after 7 days or a configurable size/duration
limit. Download streams directly from the capture sidecar via the existing
<gs>-agentService. The captureemptyDirvolume is pre-provisioned on every
pod at cluster install time (the upgrade consequence above). Every capture
session is recorded in the audit log with admin-only playback to the
captures:managerole. For protocol reverse-engineering and diagnostics (#256). - Easy module building & authoring toolkit (feature 010): Scaffolds, validates,
previews, and packages custom game modules. Thegp-moduleCLI supports three
starter archetypes (steamcmd,java,generic) with configurable ports,
storage, and metadata; offline validation with source-line diagnostics; dry-run
manifest rendering (simulatesautoFromMemoryLimit); and OCI bundle packaging
with media-type and size-limit enforcement. The web dashboard (Modules page)
offers a BuildModuleDialog with live validation, YAML editor, bundle download,
and cluster installation. API endpoints:/modules/builder/{scaffold,validate,preview,export}(#370). - 13 new top-steam-game modules (feature 015): FiveM, Farming Simulator 25,
Euro Truck Simulator 2, Mount & Blade Bannerlord, tModLoader, Team Fortress 2,
BeamMP, Left 4 Dead 2, The Isle, ARK: Survival Evolved, Arma Reforger, Hell
Let Loose, and Squad. Gameplane publishes four Gameplane-owned container
images:ghcr.io/valgulnecron/gameplane/fivem(FiveM + txAdmin),farming-simulator-25
(Wine/Xvfb runner),euro-truck-simulator-2(Wine runner), andbeammp
(BeamMP + non-crashing diagnostic idle). All modules are signed with Cosign
keyless signatures (#369). - Nuclear Option dedicated server support: New
nuclearoptionremote-command
protocol implemented in the agent's RCON client set, wired into console /
players / quiesce; a Gameplane-owned, SteamCMD-basednuclear-option
container image (no official image exists upstream); genericmin/max/
minLength/maxLengthconstraints onConfigField, enforced in
materializeConfigfor every module, landed alongside it (#252, #255, #260). - Install-time configuration (feature 006): Two Helm values for zero post-deploy
configuration:- Helm-seeded OIDC role mappings:
api.oidc.groupsClaim,api.oidc.defaultRole,
andapi.oidc.roleMappings.{admin,operator,viewer}seed a read-only "helm"
OIDC provider, so OIDC-only installs need nobootstrap-adminrun. Dashboard
Settings → Authentication can override any role's group list (stored in
helmOverride.roleMappings); changes take effect on next login (no restart).
Reset viaDELETE /admin/config/auth/role-mappings/{role}. - Default StorageClass for game-data PVCs:
operator.gameDataStorage.storageClassName
(Helm value) sets a cluster-wide default with precedence: GameServer >
GameTemplate > install-time > cluster default. Missing StorageClass surfaces
asPVCProvisioningFailed(recoverable) (#282).
- Helm-seeded OIDC role mappings:
- Share link expiry options (feature 017): Removed the hard 90-day cap that
outlived long-running servers. Create requests now carryexpiresAt(RFC3339
instant) orneverExpires: true(not both, not neither = 400 error). Dashboard
offers presets (15/30/60/90 days, No expiry) and custom date picker; 30 days
is pre-selected. Past instant dates are rejected. The deprecatedexpiresIn
field still works for one release (#392, #396). - Console WebSocket robustness: Fixed 403 errors when dashboard is reached
on non-default ports (e.g.,kubectl port-forward svc/gameplane-web 8088:80)
by forwarding$http_hostinstead of$hostin the nginx config. Added guard
onsend()before socket open: input sent while CONNECTING is queued (max 500
items) and flushed on open; input after reconnect is dropped (no silent replay);
input after close is no-op. Queue tests verify behavior with fake sockets that
throw like real browsers (#416). - Existing PVC for SQLite: Helm
api.storage.existingClaimallows installs
to point the API database to a pre-existing PersistentVolumeClaim, enabling
storage migration and lifecycle separation from the main Helm release (#390). - REST and CLI console protocols: Agent implementation for
rest(generic HTTP
for games with HTTP admin APIs like FiveM txAdmin) andcli(local process
execution for PTY-only games like Terraria). Both are wired to console, players,
quiesce, lifecycle, and quick-action endpoints. Thercon.protocolenum is
nowsource;telnet;websocket;battleye;satisfactory;palworld;nuclearoption;rest;cli;none(#369). - Security audit remediation: Multiple fixes from a comprehensive security
audit: share links are isolated by cluster; GameServer setting/image/service
account/environment/tunnel changes are permission-checked; Secret and ConfigMap
references must belong to the target server; optional network policies restrict
audit and telemetry services to API traffic; SteamCMD image pinned to a stable
version (#350). - Mod archive path confinement: Symlink-escape and zip-slip guards hardened
across every extraction and non-extract upload call site in the agent's mods
handler (ConfinePath/ConfineRelPath, resolving symlinks on the deepest
existing ancestor); an unconditional TLS-verification bypass in an e2e test
helper was also fixed (#285, #290, #291). - CI hardening: GitHub Actions workflow pins (100% to 40-hex SHAs with version
comments), least-privilege permissions, measured timeouts, Dependabot expansion
(Go modules, Dockerfiles, Actions), workflow-lint gates (actionlint + zizmor),
and an AI-powered two-workflow reviewer with split trust boundaries (#292). - Design & visual polish: HeroUI foundation and brand re-theme; Servers list
and Server Detail; console, logs, files, and mods tabs; backups and settings
pages; design alignment and visual regression testing (test/e2e/design-vs-browser)
against Pencil baselines (#348, #349, #364, #371, #374, #378).
Fixed
- api: SQLite DSN now automatically includes a
_pragma=busy_timeout(5000)pragma if one is not already specified, allowing concurrent processes (API and bootstrap-admin) sharing the same database file to wait for locks instead of failing with SQLITE_BUSY immediately. - agent, chart: the agent now serves Prometheus metrics on a separate,
unauthenticated listener (:9090,--metrics-addr) instead of the mTLS
control port (:8090) it used to share/metricswith — every agent
scrape target was previously "down" because the control port requires a
client cert for every route (F-216). The operator declares that listener
as a namedmetricscontainerPort (9090) on the agent sidecar, and the
PodMonitor(serviceMonitors.enabled) now scrapes it by that name
instead of needing a Prometheus client cert. The
telemetry-receiver now gets its ownServiceMonitor, and a new
serviceMonitors.scrapeNamespaceSelectorvalue opens theNetworkPolicy
exceptions (agent metrics port9090and the receiver's port) a
Prometheus outside the release namespace needs to reach either target
whennetworkPolicies.enabledis alsotrue(F-217). - api: the experimental PostgreSQL backend (
db.driver=postgres, an api
image built with-tags postgres) now works end to end. It has its own
migration set, queries are rebound to Postgres placeholders, and a CI job
runs the database tests against PostgreSQL. SQLite installs are unchanged.
New migrations must now be portable SQL that runs on both databases
(#519, closes #518). - web, api: the Servers page now lists servers from every namespace the
viewer may read, not onlygameplane-games, and the namespace filter
offers each of them. A newGET /namespacesroute returns those
namespaces (#498). - web: catalog tag filters now intersect instead of union (AND logic), so
selecting multiple tags shows only games matching all tags (#411). - web: vertical tab list no longer renders as an oval shape (#410).
- web: console terminal no longer grows without bound horizontally when
receiving long lines (#409). - operator: migrated off the deprecated Fulcio certificate API (
fulcioroots.pb.go),
which was sunset by sigstore; image signing now uses the currenttlog.sigstore.devendpoint (#287). - chart: the games
Namespacenow carrieshelm.sh/resource-policy: keep, sohelm uninstallno longer deletes it together with every
GameServer, data PVC and per-server Secret inside it (F-212). - chart:
helm upgradenow works for releases not namedgameplane
(the pre-upgrade hook targeted the wrong API Deployment) and with
--reuse-valuesfrom a v0.2.0-beta.8 release (values keys added since
then no longer break rendering), and ahelm installover CRDs left by an
uninstalled release now updates their schema (F-213, F-214, F-218). - api: hand-written validation errors in the destinations and modules
handlers now return 400 (bad input) or 409 (name clash) instead of 500, a
missing share link returns 404,201 Createdresponses carry a JSON
Content-Type, legacy preference timestamps are returned as RFC 3339, and
api/specs.mdmatches the handlers (F-076, F-077, F-082, F-083, F-085,
F-086, F-087, F-088, F-089). - gameaction: a required console-action parameter sent as an empty
string is now rejected (400) instead of silently taking its declared
default, and the 512-character cap now counts characters rather than
bytes (F-149, F-150, F-151). - api: file, log and capture downloads and
files/writeand
mods/uploaduploads are now exempt from the 60 s request timeout and the
1 MiB body-size cap that cut them off (F-074, F-075). - operator: a data wipe that fails (for example on a permission error)
now fails its Job and sets aDataWipe=Falsecondition on the GameServer
instead of being acknowledged as successful (F-054). - operator: fixed the backup/restore quiesce lifecycle: a failed
unquiesce is retried, scheduled backups quiesce by default again, deleting
a quiesced Backup waits (bounded) for the unquiesce, and a restore now
removes files created after the snapshot so/datamatches it exactly
(F-044, F-045, F-048, F-049). - operator: a pinned
Moduleno longer flaps between Pulling and Ready,
a managedGameTemplatedeleted with kubectl is recreated, and a
GameServer'sspec.templateRefis now immutable (the API returns 409)
instead of wedging the StatefulSet reconcile (F-046, F-047, F-050). - operator:
Moduleresources in aFailedphase no longer rewrite
their status on every reconcile (F-258). - web: the server detail page (mods, backups, schedules, restores, quick
actions, log download, clone) now passes the server's namespace, so
servers outside the default games namespace no longer 404 or hit a
same-named server there; "Shared with you" rows regain Start/Stop/Restart
(F-116, F-130). - web: errors on previously-silent mutations and unhandled promises are
now surfaced to the user instead of failing quietly (F-126, F-137). - web: closing a WebSocket now cancels a pending reconnect timer, so no
socket is reopened after the component unmounts (F-121). - web: URL safe mode (
?safe-mode=1) now stays active across in-app
navigation; a full reload without the parameter still turns it off
(F-125). - agent, web: an undeterminable player count is now always reported as
unknown (previously sometimes 0) and shown as "—" instead of-1, and the
agent no longer reports the game id as the game version (F-105, F-106). - agent: console and log WebSockets and mod installs are no longer cut
off at 30 s by the router timeout, and the RCON reply cap was raised from
4096 to 16394 bytes so full multi-byte replies are accepted (F-103,
F-104). - tunnel: fixed a retry-counter overflow that made the supervisor retry
with no backoff, and frp now proxies to the Service's own port and
protocol, so UDP games and a remote port different from the Service port
work (F-172, F-052). - tunnel: the address playit assigns is now read from playitd and
reported into the GameServer'sstatus.endpoints, which previously stayed
empty for playit tunnels (F-174). - sentinel: a connection handed through to the game pod is no longer cut
when the game pod becomes Ready and the sentinel shuts down; it drains
instead. A failed listener now exits the sentinel at once instead of
waiting for shutdown (F-179, F-180). - mcp-server:
get_pod_logsnow keeps the newest tail bytes when a log
is truncated and prefixes a truncation notice, instead of silently
returning the oldest part (F-204). - api, operator: downloading a user-stopped network capture now waits for
the sidecar to finalize it instead of failing with 409, and a stopped
capture that never reached a sidecar completes instead of staying Pending
and holding the capture lock (F-259). - api, capture-sidecar: deleting a capture now frees its file from the
capture volume budget immediately instead of at pod restart, and a
rejected concurrent start's 409 names the running capture (F-192, F-261). - gp-module: the validator used by the CLI and the dashboard's module
builder now checks the real CRD shape (enum, thebooltype, required
spec.displayName,spec.versionandports[].name),previewapplies
the default version, it warns whengameplaneMinVersionis newer than the
tool, and its docs match the CLI (F-159, F-160, F-161, F-162, F-163,
F-164, F-165, F-166, F-167, F-168). - web: the dashboard's nginx now accepts request bodies up to 64 MiB,
matching the ingress and API, so file and mod uploads over 1 MiB are no
longer rejected with 413 (#426). - chart, operator: Backup and Restore Jobs can now reach their restic
repository whennetworkPolicies.enabledistrue; a new
networkPolicies.backupEgressvalue (on by default, TCP 443 and 22) opens
that egress (#432). - release: Go component images are now cross-compiled instead of built
under emulation, so the multi-arch release build no longer times out
(#435). - api, chart: the Add node join command and the downloaded kubeconfig
can now use a node-routable API server address set with the new
clusterOps.externalAddressvalue, instead of the in-cluster ClusterIP
(#464). - chart: OIDC flags are now quoted, so issuer, client ID and role-mapping
group names containing YAML-special characters render correctly, and the
newapi.oidc.displayNamevalue sets the OIDC login button label (#485). - web: Admin Logs now shows the timestamp of log lines that use a
time
field, the audit log labels module-source changes correctly, and the ban
confirmation reads "Banning…" (#480). - operator: an unparseable
spec.networking.addressnow sets an
InvalidAddressreason, an unparseable wake window sets an
IdleScheduleInvalidcondition whether or not the server is asleep, a
staleTunnelHostnameIgnoredcondition is cleared, and an expired capture
whose sidecar is unreachable is deleted after a bounded retry instead of
lingering (#490). - mcp-server:
list_eventsnow returns an error for an invalid label
selector instead of silently ignoring it, and the tool descriptions and
propose_fixbackup advice use labels and names that actually exist
(#493).
Changed
- Dependency updates: Go modules (direct + indirect), web npm packages
(React, Vite, Playwright, TanStack Router/Query, Tailwind, HeroUI, Lucide,
Vitest, TypeScript ESLint), GitHub Actions, and container base images
(Node 24→26 bookworm-slim, Go 1.26→1.27 alpine, nginx 1.30→1.31, restic
0.18→0.19) bumped to latest stable releases; actionlint and zizmor pinned
for workflow linting (#262, #263, #264, #265, #266, #267, #268, #270, #271,
#273, #274, #275, #276, #277, #278, #279, #280, #281, #283, #298, #301, #307,
#308, #309, #310, #311, #312, #317, #318, #320, #321, #323, #324, #328, #329,
#334, #344, #354, #355, #356, #357, #358, #359, #379, #380, #381, #382, #387,
#399, #400, #401, #402, #403, #404, #405, #406, #412). - deps: further web npm patch updates and a refreshed Alpine base image
digest for the tunnel images (#456, #457). - docs: corrected drift found by the pre-release audit across the API
reference (OpenAPI), agent, tunnel, svcutil, module-authoring, dependency
and security docs (#442, #465, #468, #481, #486, #494, #495).
Security hardening
- api: hardened multi-cluster request scoping.
- operator: hardened module bundle integrity checks.
- web: hardened the Admin Settings draft and managed-secret lifecycle.
- api: hardened ownership checks on owner-only server operations.
- operator: hardened network captures to apply the template-port default filter when none is given.
- api: hardened account removal and share-link revocation.
- api: hardened client IP derivation behind trusted proxies.
- api: hardened OIDC role re-evaluation, role-assignment auditing for every OIDC provider, and the
bootstrap-adminbreak-glass reset. - api: hardened role-edit guards and event-stream authorization.
- api: hardened cluster registration removal.
- operator: hardened ownership checks on restore and cleanup.
- api: hardened audit export fields by including reason in webhook, S3 and CSV output.
- ci: hardened the release signing order and the scope of the signing key.
- api: hardened Prometheus metrics serving with a dedicated in-cluster listener.
- agent: hardened file write and delete handling in the files endpoints.
- capture-sidecar: hardened TLS configuration defaults and capture size limits.
- audit-syslog-bridge: hardened
APP-NAME/HOSTNAMEfield validation. - gameproto: hardened handshake string-length and status-response validation.
- agent: hardened RCON error handling, TLS certificate validation, and dotfile access in file operations.
- netguard: hardened SSRF dial-guard policies to block cloud metadata addresses and refine reserved ranges.
- operator: hardened RBAC scope with cluster-wide reads and namespace-scoped writes.
- chart: hardened agent mTLS Secret configuration to support bring-your-own CA paths; referenced Secrets that do not exist now fail live installs or upgrades, and the API and operator mTLS checksum now covers custom Secret data to roll both on Secret rotation.
- agent: confined the final upload path to the data root.
- web: hardened dashboard permission gates for capture and mod controls.
- operator, api, web: hardened the module "verified" badge to reflect a recorded signature check instead of a source's current policy.
- tunnel: hardened relay config rendering with escaping and validation.
- audit-syslog-bridge: hardened collector delivery reporting and intake time bounds.
- api: hardened tunnel credential storage when the tunnel provider changes.
- api: hardened module archive extraction limits and mod-registry outbound connections.
- deploy: hardened the kind dev cluster bootstrap by pinning the ingress-nginx manifest to a release tag.
- gp-module: hardened the steamcmd preset so scaffolded templates run the game as a non-root user.
- docs: hardened the accuracy of the game-container posture and OIDC group-claim documentation.
- telemetry-receiver: hardened ingest payload validation.
- gameaction: hardened console action parameter validation against shell and RCON metacharacters.
- operator: hardened tunnel credential handling: a credentials Secret not owned by its GameServer is now refused, the tunnel is scaled to zero, and TunnelReady=False (reason TunnelCredentialRefused) reports why.