Skip to content

v0.3.0

Choose a tag to compare

@github-actions github-actions released this 06 Oct 04:10
· 1769 commits to master since this release
v0.3.0
040e10e

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 capture emptyDir volume 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, no CAP_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 the playit tunnel 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 install onto Gameplane CRDs left behind by an uninstalled release
    runs the crds.autoApply hook to bring them up to date, so air-gapped
    clusters need crds.autoApply.image mirrored 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-time helm 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, default 2; operator.backupJobActiveDeadlineSeconds, default 86400), 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=1 or 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>-agent Service. The capture emptyDir volume 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:manage role. For protocol reverse-engineering and diagnostics (#256).
  • Easy module building & authoring toolkit (feature 010): Scaffolds, validates,
    previews, and packages custom game modules. The gp-module CLI supports three
    starter archetypes (steamcmd, java, generic) with configurable ports,
    storage, and metadata; offline validation with source-line diagnostics; dry-run
    manifest rendering (simulates autoFromMemoryLimit); 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), and beammp
    (BeamMP + non-crashing diagnostic idle). All modules are signed with Cosign
    keyless signatures (#369).
  • Nuclear Option dedicated server support: New nuclearoption remote-command
    protocol implemented in the agent's RCON client set, wired into console /
    players / quiesce; a Gameplane-owned, SteamCMD-based nuclear-option
    container image (no official image exists upstream); generic min / max /
    minLength / maxLength constraints on ConfigField, enforced in
    materializeConfig for 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,
      and api.oidc.roleMappings.{admin,operator,viewer} seed a read-only "helm"
      OIDC provider, so OIDC-only installs need no bootstrap-admin run. Dashboard
      Settings → Authentication can override any role's group list (stored in
      helmOverride.roleMappings); changes take effect on next login (no restart).
      Reset via DELETE /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
      as PVCProvisioningFailed (recoverable) (#282).
  • Share link expiry options (feature 017): Removed the hard 90-day cap that
    outlived long-running servers. Create requests now carry expiresAt (RFC3339
    instant) or neverExpires: 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 deprecated expiresIn
    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_host instead of $host in the nginx config. Added guard
    on send() 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.existingClaim allows 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) and cli (local process
    execution for PTY-only games like Terraria). Both are wired to console, players,
    quiesce, lifecycle, and quick-action endpoints. The rcon.protocol enum is
    now source;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 /metrics with — 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 named metrics containerPort (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 own ServiceMonitor, and a new
    serviceMonitors.scrapeNamespaceSelector value opens the NetworkPolicy
    exceptions (agent metrics port 9090 and the receiver's port) a
    Prometheus outside the release namespace needs to reach either target
    when networkPolicies.enabled is also true (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 only gameplane-games, and the namespace filter
    offers each of them. A new GET /namespaces route 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 current tlog.sigstore.dev endpoint (#287).
  • chart: the games Namespace now carries helm.sh/resource-policy: keep, so helm uninstall no longer deletes it together with every
    GameServer, data PVC and per-server Secret inside it (F-212).
  • chart: helm upgrade now works for releases not named gameplane
    (the pre-upgrade hook targeted the wrong API Deployment) and with
    --reuse-values from a v0.2.0-beta.8 release (values keys added since
    then no longer break rendering), and a helm install over 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 Created responses carry a JSON
    Content-Type, legacy preference timestamps are returned as RFC 3339, and
    api/specs.md matches 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/write and
    mods/upload uploads 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 a DataWipe=False condition 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 /data matches it exactly
    (F-044, F-045, F-048, F-049).
  • operator: a pinned Module no longer flaps between Pulling and Ready,
    a managed GameTemplate deleted with kubectl is recreated, and a
    GameServer's spec.templateRef is now immutable (the API returns 409)
    instead of wedging the StatefulSet reconcile (F-046, F-047, F-050).
  • operator: Module resources in a Failed phase 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's status.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_logs now 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, the bool type, required
    spec.displayName, spec.version and ports[].name), preview applies
    the default version, it warns when gameplaneMinVersion is 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 when networkPolicies.enabled is true; a new
    networkPolicies.backupEgress value (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.externalAddress value, 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
    new api.oidc.displayName value 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.address now sets an
    InvalidAddress reason, an unparseable wake window sets an
    IdleScheduleInvalid condition whether or not the server is asleep, a
    stale TunnelHostnameIgnored condition is cleared, and an expired capture
    whose sidecar is unreachable is deleted after a bounded retry instead of
    lingering (#490).
  • mcp-server: list_events now returns an error for an invalid label
    selector instead of silently ignoring it, and the tool descriptions and
    propose_fix backup advice use labels and names that actually exist
    (#493).

Changed

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-admin break-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/HOSTNAME field 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.