Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
0182e16
feat: support standalone panel and API without a local cluster
Baylem Oct 8, 2026
9f698da
fix: harden standalone storage and verify persisted credentials
Baylem Oct 8, 2026
30a194f
fix: route standalone dashboard operations to explicit workloads
Baylem Oct 8, 2026
28dd876
fix: preserve remote gateway credentials across concurrent changes
Baylem Oct 8, 2026
67fd660
fix: satisfy gateway context and expression lint checks
Baylem Oct 8, 2026
2e5e46a
test: align standalone module and notification fixtures
Baylem Oct 8, 2026
834c3ca
fix: preserve share link tokens across server refreshes
Baylem Oct 8, 2026
a60c4d3
docs: complete standalone panel setup guidance
Baylem Oct 8, 2026
6dffc99
fix: make standalone key publication crash durable
Baylem Oct 8, 2026
2414e1a
fix: validate standalone key file ownership and permissions
Baylem Oct 8, 2026
c01e3bb
fix: make standalone cluster registration atomic
Baylem Oct 8, 2026
8d94492
fix: bound standalone remote response and notification memory
Baylem Oct 8, 2026
7e6d247
feat: support separately provisioned standalone panel keys
Baylem Oct 8, 2026
55f1bcf
feat: rotate registered kubeconfigs in place
Baylem Oct 8, 2026
0ece9ae
fix: preserve private key permissions across Helm remounts
Baylem Oct 8, 2026
076fa85
fix: persist key directory publication before retrying
Baylem Oct 8, 2026
3d8a62e
fix: restrict standalone remote credential connections
Baylem Oct 8, 2026
ca65c90
fix: prevent remote response limit bypasses
Baylem Oct 8, 2026
c01a963
feat: rotate standalone master keys transactionally
Baylem Oct 8, 2026
391ca03
fix: allow Helm deployments to select rotated panel keys
Baylem Oct 8, 2026
e0e3595
fix: confine key directory synchronization handles
Baylem Oct 8, 2026
6516613
test: close remote policy responses and propagate contexts
Baylem Oct 8, 2026
7bca550
fix: reject extended access ACLs on standalone encryption keys
Baylem Oct 8, 2026
a42796f
fix: guard standalone interactive Kubernetes transports
Baylem Oct 8, 2026
0828690
test: exercise standalone health over a verified private endpoint
Baylem Oct 8, 2026
cda9658
fix: use supported Kubernetes streaming transport interfaces
Baylem Oct 8, 2026
068eb4c
fix: preserve system-root TLS verification for standalone streams
Baylem Oct 8, 2026
6eb7ac3
design: document standalone panel flows for PR 630
Baylem Oct 8, 2026
3035757
fix: harden panel key publication and rotation diagnostics
Baylem Oct 14, 2026
cc21cfa
fix: preserve remote observers without blocking registry reads
Baylem Oct 14, 2026
6564774
fix: guard gateway destinations and complete credential cleanup
Baylem Oct 14, 2026
a4cb5fe
fix: validate standalone deployments and scope Compose smoke
Baylem Oct 14, 2026
6952098
docs: clarify standalone recovery and deployment guidance
Baylem Oct 14, 2026
3741ae5
fix: preserve dashboard drafts and clarify cluster state
Baylem Oct 14, 2026
b73bbbc
chore: merge current master into standalone review fixes
Baylem Oct 14, 2026
be6f0e6
fix: satisfy review regression compile and lint checks
Baylem Oct 14, 2026
ca5cbac
fix: prevent slow refreshes from starving credential cleanup
Baylem Oct 14, 2026
ca0f49b
fix: preserve unrelated managed credential keys
Baylem Oct 14, 2026
a0dc7c4
test: distinguish credential rotation from status updates
Baylem Oct 14, 2026
117caf3
test: await cached query error rendering
Baylem Oct 14, 2026
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
76 changes: 71 additions & 5 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ jobs:
go: ${{ steps.combine.outputs.go }}
web: ${{ steps.combine.outputs.web }}
charts: ${{ steps.combine.outputs.charts }}
standalone: ${{ steps.combine.outputs.standalone }}
e2e: ${{ steps.combine.outputs.e2e }}
weblive: ${{ steps.combine.outputs.weblive }}
images: ${{ steps.combine.outputs.images }}
Expand Down Expand Up @@ -149,6 +150,18 @@ jobs:
# from these; an edit to either must re-run it.
- 'operator/config/crd/**'
- 'hack/sync-chart-crds.sh'
# Compose builds only API, its local module dependencies, and web.
# Deployment prose and unrelated Go/chart changes need no smoke VM.
standalone:
- 'api/**'
- 'netguard/**'
- 'gameaction/**'
- 'gp-module/**'
- 'telemetryschema/**'
- 'web/**'
- 'deploy/standalone/compose*.yaml'
- 'deploy/standalone/smoke.py'
- '.dockerignore'
e2etree:
- 'test/e2e/**'
- 'deploy/kind/**'
Expand Down Expand Up @@ -294,6 +307,7 @@ jobs:
GO: ${{ steps.scope.outputs.go }}
WEB: ${{ steps.filter.outputs.web }}
CHARTS: ${{ steps.filter.outputs.charts }}
STANDALONE: ${{ steps.filter.outputs.standalone }}
E2ETREE: ${{ steps.filter.outputs.e2etree }}
GAMECOVERAGE: ${{ steps.filter.outputs.gamecoverage }}
CI: ${{ steps.filter.outputs.ci == 'true' || steps.scope.outputs.all == 'true' }}
Expand All @@ -308,6 +322,7 @@ jobs:
gov=false; { [ "$GO" = true ] || [ "$CI" = true ]; } && gov=true
webv=false; { [ "$WEB" = true ] || [ "$CI" = true ]; } && webv=true
chv=false; { [ "$CHARTS" = true ] || [ "$CI" = true ]; } && chv=true
standalonev=false; { [ "$STANDALONE" = true ] || [ "$CI" = true ]; } && standalonev=true
e2ev=false; { [ "$GO" = true ] || [ "$CHARTS" = true ] || [ "$E2ETREE" = true ] || [ "$GAMECOVERAGE" = true ] || [ "$CI" = true ]; } && e2ev=true
wlv=false; { [ "$e2ev" = true ] || [ "$WEB" = true ] || [ "$CI" = true ]; } && wlv=true
ghv=false; { [ "$GITHUB" = true ] || [ "$CI" = true ]; } && ghv=true
Expand All @@ -323,6 +338,7 @@ jobs:
echo "go=$gov"
echo "web=$webv"
echo "charts=$chv"
echo "standalone=$standalonev"
echo "e2e=$e2ev"
echo "weblive=$wlv"
echo "images=$wlv"
Expand Down Expand Up @@ -946,6 +962,52 @@ jobs:
- uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1
- run: helm lint charts/gameplane

standalone-compose:
name: standalone Compose smoke
needs: [changes]
if: needs.changes.outputs.standalone == 'true'
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
# Match Compose's image names for its fixed gameplane-panel project.
# Separate cache scopes prevent the API/web exports overwriting each other.
- name: build standalone API
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
with:
context: .
file: api/Dockerfile
platforms: linux/amd64
load: true
push: false
tags: gameplane-panel-gameplane-api:latest
cache-from: type=gha,scope=standalone-api
cache-to: type=gha,mode=max,scope=standalone-api
- name: build standalone dashboard
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
with:
context: .
file: web/Dockerfile
platforms: linux/amd64
load: true
push: false
tags: gameplane-panel-web:latest
cache-from: type=gha,scope=standalone-web
cache-to: type=gha,mode=max,scope=standalone-web
- name: start standalone panel
run: docker compose -f deploy/standalone/compose.yaml up -d --no-build --pull never
- name: bootstrap and verify persistent standalone panel
run: python3 deploy/standalone/smoke.py
- name: container logs
if: failure()
run: docker compose -f deploy/standalone/compose.yaml logs
- name: remove smoke containers and volume
if: always()
run: docker compose -f deploy/standalone/compose.yaml down --volumes

chart-template:
name: chart render
needs: [changes]
Expand All @@ -960,6 +1022,8 @@ jobs:
- run: helm template gameplane charts/gameplane > /tmp/rendered.yaml
- name: gateway isolation and API-disabled chart renders
run: python3 charts/tests/check_gateway.py
- name: standalone panel isolation and persistence chart renders
run: python3 charts/tests/check_standalone.py
- name: sanity-check rendered manifests
run: |
grep -q 'kind: Deployment' /tmp/rendered.yaml
Expand Down Expand Up @@ -1397,8 +1461,8 @@ jobs:

# The api's PostgreSQL backend (--db-driver=postgres) is compiled only
# with -tags postgres, so the `go` matrix and `lint` never build it. This
# job builds and vets api with the tag and runs api/internal/db's tests
# against a real PostgreSQL service; the db_postgres_test.go tests skip
# job builds and vets api with the tag and runs database and standalone
# management storage tests against a real PostgreSQL service; these tests skip
# unless GAMEPLANE_TEST_POSTGRES_DSN is set. The server keeps the image's
# default (non-C) locale on purpose: TestPostgres_TextOrderingIsByteWise
# checks the migrations' COLLATE "C" columns sort byte-wise regardless.
Expand Down Expand Up @@ -1449,11 +1513,11 @@ jobs:
- name: go vet -tags postgres
working-directory: api
run: go vet -tags postgres ./...
- name: test api/internal/db against PostgreSQL
- name: test database and standalone management storage against PostgreSQL
working-directory: api
env:
GAMEPLANE_TEST_POSTGRES_DSN: postgres://postgres@localhost:5432/gameplane_test?sslmode=disable
run: go test -tags postgres -count=1 ./internal/db/...
run: go test -tags postgres -count=1 ./internal/db/... ./internal/controlplane/...

# ---------------------------------------------------------------------
# kind e2e — one job per suite, each with its own throwaway cluster on
Expand Down Expand Up @@ -2125,6 +2189,7 @@ jobs:
web-e2e-mock,
helm,
chart-template,
standalone-compose,
workflow-lint,
go-e2e-unit,
api-postgres,
Expand Down Expand Up @@ -2231,7 +2296,7 @@ jobs:
const NEEDS_ORDER = [
'changes', 'build-images', 'build-images-arm64', 'vuln-images',
'capture-sidecar-setcap-proof', 'docs', 'codegen-drift', 'submodule-freshness', 'dependency-review', 'vuln-go', 'lint', 'go',
'web', 'web-e2e-mock', 'helm', 'chart-template', 'workflow-lint', 'go-e2e-unit',
'web', 'web-e2e-mock', 'helm', 'chart-template', 'standalone-compose', 'workflow-lint', 'go-e2e-unit',
'api-postgres',
'e2e-buckets', 'e2e-go', 'e2e-go-arm64', 'e2e-multicluster', 'e2e-multicluster-arm64', 'e2e-upgrade', 'e2e-upgrade-arm64',
'e2e-web-live', 'e2e-web-live-arm64', 'e2e-game-bot', 'e2e-capture-overhead', 'e2e-kind-ingress-smoke',
Expand All @@ -2254,6 +2319,7 @@ jobs:
'web-e2e-mock': (n) => n === 'web e2e (mock)',
helm: (n) => n === 'helm lint',
'chart-template': (n) => n === 'chart render',
'standalone-compose': (n) => n === 'standalone Compose smoke',
'workflow-lint': (n) => n === 'workflow lint',
'go-e2e-unit': (n) => n === 'go (test/e2e)',
'api-postgres': (n) => n === 'api (postgres)',
Expand Down
43 changes: 43 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,23 @@ reaches `1.0.0`. Pre-1.0 minor versions may contain breaking changes.

### Upgrade Notes

- **Standalone panel/API:** run the central panel with Docker Compose or the
standalone Helm profile, without a local game cluster or operator. Compose
needs no Kubernetes installation on the panel host.
Register remote clusters explicitly. Back up the database and matching
credential encryption key separately, with restricted access and encrypted
storage for key backups. Supplied profiles use key storage outside the database
volume. Preserve the original key when migrating legacy `/data/panel.key`
installations. See [standalone installation](docs/standalone-panel.md).
- **Breaking: selected-cluster module/template grants:** in combined and
standalone installations, catalog permissions now require a cluster-wide
grant on the selected cluster or wildcard cluster. A grant on `local` retains
local access but no longer authorizes a remote catalog. Before upgrading,
identify users who rely on remote catalog access and grant a role containing
`modules:read`, `modules:manage`, `templates:read`, and/or `templates:write`
on each intended remote cluster, selecting **All namespaces**. Grant only the
operations each user needs; namespace-only grants do not authorize catalogs.
See [grant workload access](docs/standalone-panel.md#grant-workload-access).
- **Telemetry is on by default for new installs, after a first-login
notice:** a new install with a telemetry destination in effect now shares
basic and extended data by default; the stock destination is the project's
Expand Down Expand Up @@ -69,6 +86,17 @@ reaches `1.0.0`. Pre-1.0 minor versions may contain breaking changes.

### Added

- **Independent central panel:** Docker Compose runs the API/dashboard without
host Kubernetes; a standalone Helm profile runs them without a central game
operator or workloads. SQL stores registrations, settings, and encrypted
credentials; explicitly selected remote clusters host games through their own
operators and agents.
- **Credential maintenance:** standalone keys have separate generated storage
or read-only provisioned custody, the offline transactional `rotate-panel-key`
command, and `PUT /clusters/{name}/kubeconfig` preserving registration identity
and grants. `PUT`/`DELETE /clusters/{name}/gateway` manage remote gateway
credentials; `api.remoteAllowedCIDRs` restricts workload API and gateway
destinations. See [standalone setup](docs/standalone-panel.md).
- **Extended telemetry tier:** a second, separately consented tier with a
random install ID, environment, game-usage and feature-adoption
categories. Every value comes from a fixed set, a band or a number; no
Expand Down Expand Up @@ -135,6 +163,21 @@ reaches `1.0.0`. Pre-1.0 minor versions may contain breaking changes.
Team17's licensed hosting partners, so no public image can install it.
Its hand-run e2e test and probe package are removed with it.

### Security

- **Standalone credential protection:** encryption keys are kept outside
database volumes, with durable publication, fail-closed file-access checks,
read-only provisioned custody, and authenticated transactional rotation.
Database and key backups need separate access policies; recovery requires
the matching key. See [storage and recovery](docs/standalone-panel.md#storage-and-recovery).
- **Remote workload boundaries:** standalone connections require verified TLS,
reject unsafe destinations at dial time, and bound finite responses, watch
frames, and retained observations. Private networks remain reachable unless
destination CIDRs restrict them; configure `GAMEPLANE_REMOTE_ALLOWED_CIDRS`
or `api.remoteAllowedCIDRs` for the workload APIs and gateways. Module and
template authorization follows the selected cluster in every deployment mode.
See [security](docs/security.md#kubeconfig-secret-handling).

## [0.3.0] — 2026-10-05

The first Gameplane release without a pre-release suffix, a pre-v1 release. It
Expand Down
Loading
Loading