A production-oriented launcher and single-domain gateway for:
The intended public URL is https://game.luisbenedikt.de.
GamePage uses COBOL as its application, registry, and policy layer. Go is intentionally restricted to networking and operating-system responsibilities: HTTP transport, reverse proxying, WebSocket forwarding, concurrent probes, bounded response rewriting, logging, and graceful shutdown.
The system does not silently duplicate COBOL decisions in Go. If the COBOL core cannot execute or returns structurally invalid data, GamePage fails closed: readiness becomes false and all launches are blocked.
All game metadata now lives in one copybook:
src/game_registry.cpy
It is the source of truth for:
- game keys and names;
- launcher card styling and content;
- public route prefixes;
- upstream environment-variable names and defaults;
- health-check paths;
- policy override environment-variable names;
- default enabled and required flags;
- default maximum latency;
- source-repository links.
Three compiled COBOL programs consume the same copybook:
| Program | Responsibility |
|---|---|
src/gamepage.cob |
Generates launcher HTML, CSS, 404 page, favicon, robots.txt, and security.txt; game cards are generated by iterating the registry |
src/gamepage_registry.cob |
Generates the runtime route registry, global policy file, architecture manifest, and browser status client |
src/gamepage_core.cob |
Validates observations and raw overrides, applies policy, decides readiness and launchability, and emits status JSON |
Adding or changing a game therefore starts with one COBOL registry entry instead of coordinated edits across UI, routing, and policy files.
Generated runtime artifacts:
/app/site/runtime/routes.tsv
/app/site/runtime/policy.tsv
/app/site/runtime/architecture.json
| Layer | Language | Responsibilities |
|---|---|---|
| Canonical registry | COBOL copybook | Game identity, routes, UI content, policy defaults, override names |
| Application policy | COBOL | Configuration validation, maintenance, enabled/required rules, latency thresholds, optional degradation, minimum availability, readiness, launchability, reason codes, status JSON |
| Transport adapter | Go | HTTP server, reverse proxy, WebSockets, upstream probes, headers/cookies/path rewriting, security headers, logs, process lifecycle |
| Generated browser artifacts | HTML, CSS, JavaScript, SVG | Launcher presentation and live policy-state display |
| Deployment | Docker, Compose, Bash, Apache or Caddy | Build, isolation, CI, smoke tests, TLS termination |
MAINTENANCE_MODE=false
MINIMUM_LAUNCHABLE_GAMES=1- Maintenance mode blocks all launches and makes
/readyzreturn HTTP 503. MINIMUM_LAUNCHABLE_GAMESsets the minimum number of launchable games required for readiness.- COBOL validates both values. Invalid values produce
configuration-errorand no game may launch.
Each game has three policy controls:
TRUMP_ENABLED=true
TRUMP_REQUIRED=true
TRUMP_MAX_LATENCY_MS=2500
GOLF_ENABLED=true
GOLF_REQUIRED=true
GOLF_MAX_LATENCY_MS=2500
RACE_ENABLED=true
RACE_REQUIRED=true
RACE_MAX_LATENCY_MS=2500The defaults preserve the previous behavior: all three games are enabled and required.
true: COBOL evaluates health and latency and may permit launch.false: the game receivespolicyState: disabledand cannot launch.
- A required game that is down, disabled, or slower than its threshold makes readiness false.
- An optional game may be down, disabled, or slow while readiness remains true, provided the global minimum launchable count is met.
- Optional failures still set
overall: degraded, so reduced functionality remains visible.
- A response above the threshold receives
policyState: slow. - Slow games remain launchable.
- A required slow game makes readiness false.
- An optional slow game keeps readiness true while the overall state is degraded.
| Condition | Launchable | Required readiness impact | Optional readiness impact |
|---|---|---|---|
| Healthy and enabled | Yes | None | None |
| Slow and enabled | Yes | Not ready | Still ready |
| Down and enabled | No | Not ready | Still ready if minimum is met |
| Disabled | No | Not ready | Still ready if minimum is met |
| Maintenance | No | Not ready | Not ready |
| Invalid policy | No | Not ready | Not ready |
| COBOL unavailable or invalid result | No | Fail closed | Fail closed |
Browser / monitoring
|
v
Go HTTP transport
|
| concurrent technical HTTP probes
v
raw observations + raw environment overrides
|
| mode 0600 temporary input
v
compiled GnuCOBOL gamepage-core
|
| canonical registry defaults + policy evaluation
v
COBOL-generated status JSON
|
| structural validation only
v
/api/status, /readyz, and route launch gates
Go does not interpret boolean policy values or latency overrides. It transports them to COBOL exactly as configured.
| URL | Purpose |
|---|---|
/healthz |
Go transport-process liveness; does not claim application readiness |
/readyz |
Forces fresh probes and a COBOL decision; returns 200 only when COBOL reports ready: true |
/api/status |
Cached COBOL decision document used by the launcher and monitoring |
Example:
{
"overall": "degraded",
"ready": true,
"mode": "degraded",
"decisionEngine": "gnucobol",
"checkedAt": "2026-07-14T10:00:00Z",
"policy": {
"minimumLaunchableGames": 1,
"requiredFailures": 0
},
"summary": {
"total": 3,
"up": 2,
"down": 1,
"enabled": 3,
"required": 2,
"launchable": 2,
"healthy": 2,
"slow": 0,
"disabled": 0
},
"games": {
"golf": {
"name": "Crazy Mini Golf",
"path": "/play/golf/",
"status": "down",
"policyState": "down",
"httpStatus": 503,
"latencyMs": 8,
"error": "http-error",
"enabled": true,
"required": false,
"maxLatencyMs": 2500,
"launchable": false,
"reason": "http-error"
}
}
}| Public URL | Service | Internal target |
|---|---|---|
/ |
COBOL registry-generated launcher | GamePage transport |
/play/trump/ |
Trump vs. Shakespeare | http://trump-vs-shakespeare:8000/ |
/play/golf/ |
Crazy Mini Golf | http://crazy-mini-golf:8080/ |
/play/race/ |
Crazy Race | http://crazy-race:8080/ |
/api/status |
COBOL application status | GamePage |
/healthz |
Go transport liveness | GamePage |
/readyz |
COBOL application readiness | GamePage |
Only GamePage publishes a host port. The game containers are reachable only on the internal Docker network.
The games were originally designed to run at the root of an origin. Go performs protocol-level work that does not belong in the COBOL policy layer:
- strips the public
/play/...prefix before forwarding; - restores the prefix in root-relative textual URLs;
- rewrites
Locationresponse headers; - rewrites cookie
Pathattributes; - preserves WebSocket upgrades;
- forwards origin and prefix headers;
- disables upstream compression before bounded rewriting.
A text response larger than 16 MiB is rejected rather than rewritten without bounds.
The GamePage image has three stages:
- COBOL builder
- copies the canonical registry copybook;
- compiles all three COBOL programs with the same include path;
- generates launcher and runtime artifacts;
- builds the runtime
gamepage-coreexecutable; - verifies registry-derived output.
- Go builder
- runs Go tests;
- builds the static transport binary.
- Runtime
- contains the Go transport, compiled COBOL core, generated site,
libcob, and CA certificates; - contains neither compiler nor source tree;
- runs as non-root user
65532:65532.
- contains the Go transport, compiled COBOL core, generated site,
The container has a read-only root filesystem and a bounded noexec /tmp tmpfs for short-lived COBOL decision files.
GamePage/
├── src/
│ ├── game_registry.cpy
│ ├── gamepage.cob
│ ├── gamepage_registry.cob
│ └── gamepage_core.cob
├── cmd/gateway/main.go
├── internal/gateway/
├── scripts/docker-smoke-test.sh
├── deploy/
├── .github/workflows/
│ ├── ci.yml
│ └── full-stack-docker.yml
├── Dockerfile
├── docker-compose.yml
├── .env.example
└── README.md
/srv/games/
├── GamePage/
├── TrumpVsShakespeare/
├── CrazyMiniGolf/
└── CrazyRaceGame/
sudo mkdir -p /srv/games
sudo chown "$USER":"$USER" /srv/games
cd /srv/games
git clone https://github.com/Pepitodrop/GamePage.git
git clone https://github.com/Pepitodrop/TrumpVsShakespeare.git
git clone https://github.com/Pepitodrop/CrazyMiniGolf.git
git clone https://github.com/Pepitodrop/CrazyRaceGame.gitcd /srv/games/GamePage
cp .env.example .env
chmod 600 .env
docker compose config --quiet
docker compose build --pull
docker compose up -d --wait --wait-timeout 240 --remove-orphans
bash scripts/docker-smoke-test.shFor local HTTP testing only:
PUBLIC_ORIGIN=http://127.0.0.1:8090Restore the production HTTPS origin before public deployment.
GOLF_ENABLED=false
GOLF_REQUIRED=falseThe launcher displays DISABLED. Other games remain available and readiness can remain true.
GOLF_ENABLED=true
GOLF_REQUIRED=falseIf Golf is down or slow, the stack is degraded but may remain ready.
MINIMUM_LAUNCHABLE_GAMES=2Even optional outages make readiness false when fewer than two games remain launchable.
MAINTENANCE_MODE=trueThe launcher and status API stay available, all launch links are removed, and /readyz returns 503.
Recreate the GamePage container after changing environment policy:
docker compose up -d --force-recreate gamepageCore CI verifies:
go test -race ./...andgo vet ./...;- compilation of all COBOL programs using
game_registry.cpy; - registry-generated launcher, route, policy, architecture, and status-client artifacts;
- healthy policy behavior;
- optional-down behavior with readiness preserved;
- required-down behavior with readiness rejected;
- slow required-game behavior while launch remains allowed;
- optional disabled-game behavior;
- maintenance mode;
- invalid policy input and fail-closed output;
- Compose validity and the production image build.
Full-stack Docker CI checks out all four repositories, builds and starts all services, runs routed smoke tests, verifies decisionEngine: gnucobol, policy states, launchability, redirects, private ports, and clean shutdown.
- only the gateway is published, and only on
127.0.0.1; - games use an
internal: trueDocker network; - the gateway has fixed upstreams and cannot become an open proxy;
- registry and environment fields are delimiter- and JSON-safe before entering COBOL;
- COBOL validates policy values and canonical game keys;
- Go validates COBOL identity, timestamps, route identities, state vocabulary, and summary consistency;
- missing or malformed COBOL output fails closed;
- temporary files use mode
0600, bounded tmpfs storage, and immediate cleanup; - containers use read-only filesystems, resource limits, log rotation, capability drops where supported, and
no-new-privileges; - TLS terminates at Apache or Caddy.
Create a DNS A record for game pointing to the server. Add AAAA only after IPv6 routing and proxying are verified.
Forward HTTP and WebSocket traffic to 127.0.0.1:8090 and set:
RequestHeader set X-Forwarded-Proto "https"Deployment examples are in deploy/. After TLS is active:
curl --fail https://game.luisbenedikt.de/healthz
curl --fail https://game.luisbenedikt.de/readyz
curl -sS https://game.luisbenedikt.de/api/status | python3 -m json.toolFinally, create a Trump vs. Shakespeare online room and confirm that a second public client reaches Live.
Record all four repository SHAs before updating, then:
git pull --ff-only
git -C ../TrumpVsShakespeare pull --ff-only
git -C ../CrazyMiniGolf pull --ff-only
git -C ../CrazyRaceGame pull --ff-only
docker compose build --pull
docker compose up -d --wait --wait-timeout 240 --remove-orphans
bash scripts/docker-smoke-test.shTo roll back, check out the recorded commits, rebuild, restart, and rerun the smoke test. Active rooms are held in memory and do not survive container replacement.
- The stack targets one server and hobby or small-community traffic.
- The runtime starts one short-lived COBOL process per uncached status decision.
- Horizontal replicas require shared room storage and coordination in the duel and race games.
- Active rooms are not preserved across restarts.
- Final public verification still requires real DNS, TLS, and an external WebSocket test.
GamePage is licensed under the MIT License. Each sibling repository retains its own license and third-party notices.