Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/components/Nav.astro
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const links = [
<LogoMark size="md" />
<span class="font-primary text-lg font-semibold text-foreground">gameplane</span>
</a>
<Badge class="hidden md:inline-flex">Beta</Badge>
<Badge class="hidden md:inline-flex">Pre-v1</Badge>
</div>

<div class="hidden items-center gap-6 md:flex">
Expand Down
3 changes: 2 additions & 1 deletion src/components/docs/DocsArticleMeta.astro
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
---
import { VERSION } from "../../config";
import { GROUPS } from "../../lib/docs-nav";

interface Props {
Expand All @@ -7,7 +8,7 @@ interface Props {
version?: string;
}

const { groupId, readTime, version = "v0.2" } = Astro.props;
const { groupId, readTime, version = VERSION.split(".").slice(0, 2).join(".") } = Astro.props;
const group = groupId ? GROUPS.find((g) => g.id === groupId) : undefined;
---

Expand Down
2 changes: 1 addition & 1 deletion src/content/docs/agent-game-protocols.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ Adapters normalize RCON, PTY, log files, moderation, status, and graceful stop a
items={[
{ icon: "activity", label: "RCON Protocols", text: "Source (Valve/Minecraft), Telnet (7DtD), WebSocket (Rust), BattlEye (DayZ/Arma), Satisfactory, Palworld, NuclearOption, REST (generic HTTP/JSON), CLI (container stdin/PTY)." },
{ icon: "history", label: "Authoritative Logs", text: "Separate authoritative game logs from container stdout. Log streaming supports live-tail or full backlog replay." },
{ icon: "shield", label: "Console Mode & Secrets", text: "Model console mode and credentials without hard-coding secrets. Each game declares its RCON protocol in the module's capabilities." },
{ icon: "shield", label: "Console Mode & Secrets", text: "Model console mode and credentials without hard-coding secrets. Each game declares its RCON protocol in the module's capabilities. A console with no password sets spec.rcon.authentication to false (v0.4.0), and the operator then mounts no password for the agent." },
{ icon: "lock", label: "Capability Gates", text: "Hide unsupported status, players, actions, and mods. Every game-specific handler reads its config from the module's declared capabilities; new games require no agent code changes." },
]}
/>
Expand Down
8 changes: 4 additions & 4 deletions src/content/docs/air-gapped-installation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: Air-gapped and private-registry installation
description: Mirror every required artifact and trust root, then install and upgrade without hidden public-network dependencies.
section: platform
group: cluster-storage
order: 9
order: 10
icon: shield-off
eyebrow: INSTALL
readTime: 20 MIN
Expand Down Expand Up @@ -68,7 +68,7 @@ Collect **all of the following by digest** (immutable hash references, not mutab
- **Game modules** — all bundles from `ghcr.io/gameplanepanel/gameplane-modules`, versioned per your module source config
- **CRD manifests** — the CRD schemas (bundled in the chart)
- **CRD-apply hook image** — the `kubectl` image behind `crds.autoApply.image` (default `registry.k8s.io/kubectl:v1.36.3`). The hook runs on every `helm upgrade`, and also on a `helm install` over Gameplane CRDs that an earlier, uninstalled release left behind. A genuinely fresh first install does not run it. Any image with `kubectl` on its `PATH` works.
- **Cosign public keys** — `cosign.pub` (current key, ECDSA P-256) and `cosign-legacy.pub` (pre-rotation, Ed25519)
- **Cosign public keys** — `cosign.pub` (current key, ECDSA P-256) and `2026-07-24-cosign-legacy.pub` (pre-rotation, Ed25519, from `signing/`)
- **Signatures** — Cosign signatures (recorded in Rekor for v0.2.0-beta.8 onward) and SBOMs, available as separate OCI artifacts in the same registry namespace

### Verify by digest
Expand All @@ -89,7 +89,7 @@ cosign verify --key cosign.pub \
ghcr.io/gameplanepanel/gameplane-modules/minecraft:1.20.4
```

Extract the digest from the verified signature and record it. On pre-rotation releases (v0.2.0-beta.7 and earlier), use `cosign-legacy.pub` with `--insecure-ignore-tlog=true` instead.
Extract the digest from the verified signature and record it. On pre-rotation releases (v0.2.0-beta.7 and earlier), use `2026-07-24-cosign-legacy.pub` with `--insecure-ignore-tlog=true` instead.

### Mirror to internal registry

Expand Down Expand Up @@ -332,7 +332,7 @@ Confirm the operator and API pods roll with the new image digest, and re-run Gam

### Disable usage telemetry

New installs send anonymous usage reports to the project's provider at `telemetry.gameplane.net` unless told otherwise. An air-gapped cluster cannot reach it, and the reporter would retry for nothing. Turn telemetry off at install time so the API never attempts the connection, whatever an admin later toggles:
New installs send anonymous usage reports to the project's provider at `telemetry.gameplane.net` unless told otherwise. An air-gapped cluster cannot reach it, and the reporter would retry for nothing. An empty `api.telemetry.endpoint` also means that default receiver, so leaving the endpoint empty does not keep reports inside the cluster. Turn telemetry off at install time so the API never attempts the connection, whatever an admin later toggles:

```yaml
# values-offline.yaml
Expand Down
8 changes: 5 additions & 3 deletions src/content/docs/api-realtime-development.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,17 @@ Handlers stay close to Kubernetes resources while enforcing session, CSRF, permi

### Authentication and authorization

The API supports two authentication flows: **local password** (argon2id hashing) and **OIDC** (via `coreos/go-oidc/v3`). Sessions are stored in the database with CSRF tokens and configurable expiry. Every API request runs through the `Session` middleware, which validates the session cookie and populates the request context.
The API supports two authentication flows: **local password** (argon2id hashing) and **OIDC** (via `coreos/go-oidc/v3`). Sessions are stored in the database with CSRF tokens and configurable expiry. A password reset ends every session of that account (v0.4.0). Every API request runs through the `Session` middleware, which validates the session cookie and populates the request context.

Authorization is role-based with three built-in roles (admin, operator, viewer) plus custom roles. The RBAC middleware (`internal/rbac/`) checks the user's permission against a rule table (method + path → permission), and falls back to per-GameServer owner/collaborator checks for fine-grained access.
Authorization is role-based with three built-in roles (admin, operator, viewer) plus custom roles. The RBAC middleware (`internal/rbac/`) checks the user's permission against a rule table (method + path → permission), and falls back to per-GameServer owner/collaborator checks for fine-grained access. Since v0.4.0 (breaking), a holder of `users:manage`, `roles:manage`, `config:manage` or `cluster:manage` without `*` cannot grant `*` or another admin-level permission, edit or reset an administrator, or edit auth providers. `/admin/config/auth` and `/admin/auth` need `*`.

**Permission discovery:** The `GET /roles` endpoint lists all permissions and role assignments (read by the dashboard settings UI).

### Cluster and namespace scope

Every request has a logical cluster and namespace. The API resolves these via:

- **Cluster:** extracted from `?cluster={name}` query param (defaults to `local` — the install cluster), validated against the Cluster CRD registry. Collection reads under `/fleet/*` (v0.3.0) instead combine every authorized scope, with optional cluster and namespace filters; each item carries an explicit `{cluster, namespace, name, uid}` target, and unavailable or truncated scopes are reported rather than silently dropped.
- **Cluster:** extracted from `?cluster={name}` query param (defaults to `local` — the install cluster), validated against the Cluster CRD registry. Module and template catalog access follows the selected cluster: it needs a cluster-wide grant on that cluster or the wildcard cluster, so a grant on `local` no longer authorizes a remote catalog (v0.4.0, breaking). Collection reads under `/fleet/*` (v0.3.0) instead combine every authorized scope, with optional cluster and namespace filters; each item carries an explicit `{cluster, namespace, name, uid}` target, and unavailable or truncated scopes are reported rather than silently dropped.
- **Namespace:** extracted from the URL path (`/servers/{name}` → namespace from server lookup) or defaulted to the user's home namespace

Scope middleware (`internal/scope/`) extracts both before the handler runs. If a user tries to access a namespace they lack permission for, the handler returns `403 Forbidden` with a safe message (no namespace-existence leakage).
Expand All @@ -64,6 +64,8 @@ Scope middleware (`internal/scope/`) extracts both before the handler runs. If a

The `httperr` package classifies errors into safe HTTP responses. Internal errors (e.g., "database connection lost") are logged server-side but returned to the client as generic `500 Internal Server Error`. Permission errors return `403 Forbidden` without revealing whether the resource exists. Not-found errors return `404`, but the safe message never reveals whether the 404 is because the resource doesn't exist or the user lacks permission to see it.

Clients that list `application/problem+json` in `Accept` get an RFC 9457 body with a stable `messageId`, `args` and the English text in `detail` (v0.4.0). Every other client keeps the English plain-text body.

### Audit logging

Every non-read request (POST, PATCH, DELETE) is logged to the audit table with:
Expand Down
6 changes: 3 additions & 3 deletions src/content/docs/architecture.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ A dashboard action cascades: API → Operator → StatefulSet → Agent → hear

## Module system

ModuleSource CRs declare where templates come from — OCI registry, git, HTTP, local, or dashboard upload.
ModuleSource CRs declare where templates come from — OCI registry, git, HTTP, local, or dashboard upload. Combined panels manage the local module catalog; standalone panels require an explicit registered cluster for module and source operations.

## Multiple clusters and the gateway

One central API and dashboard can manage independent clusters registered as `Cluster` resources. Each target cluster runs its own operator, and optionally a private gateway built from the API image. The gateway has no database, no user login and no browser or admin routes. Users authenticate only to the central API, which stays the authorization authority.
One central API and dashboard can manage independent clusters registered with the panel: as `Cluster` resources in a combined installation, or in the database of a [standalone panel](../standalone-panel/), which runs without a local cluster. A combined installation runs the panel and a local operator in one cluster. Each target cluster runs its own operator, and optionally a private gateway built from the API image. The gateway has no database, no user login and no browser or admin routes. Users authenticate only to the central API, which stays the authorization authority.

<ArchFlow
nodes={[
Expand All @@ -52,7 +52,7 @@ One central API and dashboard can manage independent clusters registered as `Clu
- **Capture downloads and cleanup** use a separate gateway route bound to both the GameServer and NetworkCapture UIDs; the sidecar verifies a persisted file identity.
- **Existing local installs** keep direct API-to-agent connections. Remote gateway access needs both private gateway reachability and direct Kubernetes API access from the central API; it does not tunnel Kubernetes operations or replicate game storage.

The `Cluster` health phase reports Kubernetes connectivity and does not probe the gateway. See the [remote agent gateway guide](../remote-agent-gateway/) for installation and the [multi-cluster guide](../multi-cluster/) for what works across clusters. The gateway ships in v0.3.0.
In a combined installation, the `Cluster` health phase reports Kubernetes connectivity and does not probe the gateway; a standalone panel polls remote Kubernetes health from the API every 30 seconds. See the [remote agent gateway guide](../remote-agent-gateway/) for installation and the [multi-cluster guide](../multi-cluster/) for what works across clusters. The gateway ships in v0.3.0.

## Optional components

Expand Down
2 changes: 1 addition & 1 deletion src/content/docs/audit-observability-admin.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Use fleet metrics for symptom detection, Kubernetes events for control-plane cau

- **Keep API and operator streams separate when following live logs.** Both services emit structured logs; keep them in separate streams to avoid losing context when debugging a single component's behavior.
- **Use structured-level filters and download raw evidence for correlation.** The Dashboard Logs page (Admin Settings → Observability) supports filtering by level; download the full log window as JSON for offline analysis across multiple components.
- **Document telemetry settings and privacy expectations for operators.** Usage telemetry is on by default for new installs; admins can turn it off in Admin Settings > Telemetry. When enabled, the telemetry-receiver collects anonymous cluster usage (server counts, resource requests, uptime) but does not capture game data, player information, or audit events. Audit telemetry settings in Platform Settings so all cluster operators are aware of what is being transmitted.
- **Document telemetry settings and privacy expectations for operators.** Usage telemetry is on by default for new installs, after a first-login notice; admins can turn each tier off in Admin Settings > Telemetry, and `api.telemetry.enabled=false` turns it off at install time. Reports go to the project's receiver at telemetry.gameplane.net unless `api.telemetry.endpoint` names another. The basic tier sends only the Gameplane version and the GameServer and GameTemplate counts. The extended tier adds environment, per-module server counts and feature flags, and never sends names, namespaces, hostnames, addresses or player data. The full field list is on [platform settings, telemetry, and build info](../platform-settings-telemetry/). Audit telemetry settings in Platform Settings so all cluster operators are aware of what is being transmitted.

## Notifications and platform settings

Expand Down
4 changes: 2 additions & 2 deletions src/content/docs/backup-destinations.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,10 @@ Backup and Restore jobs run in the `gameplane-games` namespace. If you use Kuber
- Read the backup destination Secret (already available since it's in the same namespace)
- Write to the configured repository (via egress networking)

By default, the backup egress policy (`operator.backupEgress` in Helm values) opens outbound TCP 443 (HTTPS) and TCP 22 (SFTP) to allow restic to reach common repository backends. If your repository uses a non-standard port, update the Helm values during installation:
By default, the backup egress policy (`networkPolicies.backupEgress` in Helm values) opens outbound TCP 443 (HTTPS) and TCP 22 (SFTP) to allow restic to reach common repository backends. If your repository uses a non-standard port, update the Helm values during installation:

```yaml
operator:
networkPolicies:
backupEgress:
enabled: true
ports:
Expand Down
4 changes: 4 additions & 0 deletions src/content/docs/backup-repository-operations.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ spec:
restartPolicy: OnFailure
```

### Multi-volume consistency (v0.4.0)

A copy that includes more than one volume (`volumePolicy: all-data`) captures a single snapshot, but a live copy is not a consistent recovery point across those volumes. Its Backup reports `Consistency=False` with reason `LiveMultiVolumeCopy`, even when the game's quiesce succeeded. Only a stopped, fully drained source (`powerPolicy: stop-copy-restart`) gives consistency across the included roots. Quiesce still depends on the module's commands and cannot prove application state. A volume marked `backupRole: rebuildable` is excluded and reported as a recovery prerequisite, not copied. CSI volume snapshots stay primary-only.

### Pruning and retention

Restic's retention policies are defined in `BackupSchedule.spec.retention`. Gameplane supports restic's `keep-*` vocabulary:
Expand Down
4 changes: 3 additions & 1 deletion src/content/docs/backup-restore-crds.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Backup specifies the source server and repository; BackupSchedule adds a cron sc

## Restore and status

Restore specifies the source Backup and target GameServer. Status fields track progress, snapshot references, and readiness.
Restore specifies the source Backup and target GameServer. Status fields track progress, snapshot references, and readiness. Backups, restores and wipes of the same server share one lifecycle ownership (v0.4.0), so a backup never reads a filesystem that a restore or wipe is changing.

| Field / Kind | Type | Default | Description |
|---|---|---|---|
Expand Down Expand Up @@ -90,6 +90,8 @@ Restic runs inside the backup pod and incrementally uploads encrypted snapshots
- Restore is slower (downloads and re-writes data via restic).
- Requires agent support for quiesce (game save-all before snapshot).

A stopped server with several data volumes can be recovered from its Restic backups (v0.4.0).

### Volume-snapshot (CSI)

Uses the cluster's CSI storage provider to create native snapshots at the hypervisor/storage layer. Much faster than restic and no external repository needed, but limited to cluster-scoped infrastructure.
Expand Down
5 changes: 3 additions & 2 deletions src/content/docs/backups-recovery.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ Backups combine a cron schedule with a restic repository, credentials, and expli
Gameplane supports two backup strategies:

**Restic-snapshot** (default):
- Runs `restic backup` directly against the data volume
- Runs `restic backup` directly against the server's data volumes. By default only `<server>-data` is captured (`volumePolicy: primary-only`). Opt in with `volumePolicy: all-data` (primary data, classified `data` extra volumes and active loader or mod volumes) and/or `powerPolicy: stop-copy-restart` (copies a stopped, drained server). Both are set on Backup, BackupSchedule or the GameServer's inline backup policy. (v0.4.0)
- Stores snapshots in a restic repository (S3, GCS, SFTP, or local backend)
- Supports both per-server and global snapshot browsing
- Requires explicit restic repository credentials in a Secret
Expand All @@ -102,7 +102,7 @@ Retention rules apply after the backup completes. Restic supports overlapping po
- **keep-monthly N** — keep N monthly backups (1st day)
- **keep-yearly N** — keep N yearly backups (1st day)

Leave all empty to keep everything (not recommended in production). The operator prunes snapshots when trim conditions are met.
Leave all empty to keep everything (not recommended in production). A schedule with no retention policy trims nothing, so backups with retention disabled are kept. When a policy is set, the operator deletes `Backup` resources that fall outside every bucket. It does not run `restic forget` or `prune` against the repository, so repository storage is not reclaimed automatically; see [Backup Repository Operations](../backup-repository-operations/). (v0.4.0)

## Restore safely

Expand All @@ -123,6 +123,7 @@ Restore into a new server for verification, or overwrite only after stopping wri
2. **Choose a target server:**
- For restic-snapshot backups: specify an existing GameServer (will be suspended and overwritten) or a new one.
- For volume-snapshot backups: the target must not exist; the operator creates a new GameServer with the snapshot data.
- For all-data restic captures (v0.4.0): the saved volume layout is checked against an existing target before the target is stopped. Same-server recovery needs the original claim UIDs. A mismatch fails visibly without suspending the target or writing any data.
3. **Check available storage** on the target node or cluster to ensure the restore can complete.
4. **Start the restore** and monitor the Restore resource's phase:
- **Pending** — validating source backup and target server
Expand Down
Loading
Loading