Files
Dune/docs/TRUENAS.md
T
Quintine 0681529e8e
Verify and publish checkpoint container / container (push) Canceled after 0s
Clarify Gitea verification failure diagnostics
2026-10-29 05:48:30 +11:00

8.6 KiB

Private TrueNAS container

The requested instance starts with new games, serving HTTP at http://192.168.2.251:33046 behind the existing NGINX HTTPS endpoint https://dune.procrastination.games. Development games remain in their original .wrangler/state; they are never copied into an image. This private local runtime prototype does not change public release or rules-mode gates.

Checkpoint images

The source repository is hosted on Gitea. Its .gitea/workflows/container.yml builds and verifies checkpoint images on pushes to main. Configure the Gitea Actions variables DUNE_CONTAINER_IMAGE for the full registry image name and DUNE_REGISTRY_USERNAME for the registry account; supply the registry token as the CONTAINER_REGISTRY_TOKEN Actions secret. The default image name is git.juiced.computer/admin/dune. Registry access and image visibility depend on the actual Gitea package settings; do not assume anonymous pulls work.

An ubuntu-latest Gitea runner is configured. On 29 October 2026, run #83 verified and published commit b18a7674702b92b6988b00cbde6212fa3b66b74a. The registry shows its immutable sha-b18a7674702b92b6988b00cbde6212fa3b66b74a tag and checkpoint with the same digest. Run #85 failed before publication, so a source push still does not by itself establish a verified image or a deployment. For each checkpoint, verify the relevant workflow run and its published revision before considering a manual NAS update. The existing TrueNAS deployment and Compose configuration continue to reference the GHCR image; a Gitea publication does not migrate them.

The previous GitHub workflow, .github/workflows/container.yml, builds a production image using the lockfile on GitHub pushes. Types, lint and offline tests remain part of source checkpoint verification rather than being repeated in the container build. Before publishing, CI creates an isolated container and volume, checks accepted and rejected HTTP origins, verifies saved-seat restoration through restart and container replacement, then runs the HTTP integration suite.

Only a passing GitHub image is pushed to ghcr.io/quintine/dune:checkpoint, alongside ghcr.io/quintine/dune:sha-<full-commit>. TrueNAS currently pulls that image. Failed checks leave the previous update tag untouched. This workflow targets Linux AMD64, the architecture of ordinary x86 TrueNAS systems.

The user selected the existing GHCR image as public, matching the public source repository, so TrueNAS needs no registry login for that image. GitHub initially creates packages privately; after the first successful publication, set this package's visibility to public in GitHub package settings. Images contain application code and assets, never saved games or credentials. Publishing uses the workflow's built-in token. A source push alone does not prove image publication; repository permissions or package policy may prevent the workflow from publishing.

Install

On a Docker-based TrueNAS release, use Apps → Discover Apps → Install via YAML and paste the Compose configuration, naming the app dune. It maps host port 33046 to container port 3000. A persistent named volume mounts at /data, starts empty and inherits writable ownership from the image. The container runs as UID/GID 1000. If using a dedicated host dataset instead, mount it at /data and give UID/GID 1000 write access before starting.

Startup applies pending additive migrations before serving. Both commands use the same database ID and storage root. A failed migration stops startup without deleting games. The inspector remains on container loopback and is not published.

On a database without administrator accounts, startup creates one owner and prints its personal access key once in the container log. Save it privately before replacing the container; its original log may be lost. Anyone with log access can use that key, so restrict log access and do not publish diagnostics containing it. Existing accounts never trigger a new key on restart or update. See admin access recovery.

The Docker build context explicitly allows deploy/bootstrap-admin.mjs; keep that allowlist in .dockerignore when changing the startup entrypoint. A CI image-build failure does not replace the running NAS image or provision a key.

The container serves the built Worker directly through pinned Miniflare/workerd, with persisted D1-compatible SQLite. Wrangler only applies startup migrations. The development proxy produced intermittent POST failures after rejected request bodies; the direct listener avoids that proxy. This preserves the application without a database rewrite. It does not supply managed D1 backups, replication, high availability or durable unattended bot scheduling. Keep this prototype on a trusted LAN; public hosting remains separately gated. HTTP does not encrypt cookies or seat recovery kits. Browser operation IDs now use cryptographic getRandomValues, which also works at LAN HTTP origins where randomUUID is unavailable.

Production navigation

Use native anchors for app page links. The pinned Vinext/Vite production build renames navigation exports while its Link runtime dynamically looks up their original names, breaking clicks and prefetch despite successful HTTP responses. Native navigation keeps query strings, rule anchors, browser history and saved seat cookies. The Next.js lint preference for client-side Link is disabled for this intentional choice. Verify page links in the production browser when changing framework versions; development navigation alone does not cover it.

Update and preserve games

Existing NGINX HTTPS termination

Keep the NGINX upstream HTTP, 192.168.2.251:33046. Set the app environment variable DUNE_PUBLIC_ORIGIN to the exact external HTTPS origin (scheme and hostname, optional port, no path). This keeps strict origin validation and marks seat cookies Secure, even though the proxy-to-app connection is HTTP. Dune does not blindly trust client-supplied forwarding headers. When configured, use the HTTPS address to play; direct HTTP browser POSTs are intentionally rejected. The supplied Compose file already sets https://dune.procrastination.games.

Keep NGINX's usual original Host and forwarded-protocol headers. Enable WebSocket support in NGINX Proxy Manager, or forward Upgrade/Connection headers with HTTP/1.1 in an existing NGINX location. Dune currently uses HTTP polling, not game WebSockets; enabling proxy support does not change its transport. See NGINX WebSocket forwarding.

Applying an update

  1. Wait for the checkpoint workflow for the configured image registry to succeed and confirm the intended revision was published. A Gitea source push alone is insufficient, and the GHCR Compose image does not automatically switch registries. Check for image updates in TrueNAS and apply the update manually at a safe point between decisions.
  2. Before updating, stop the app and back up or snapshot its persistent storage. Include the named volume in the Apps backup procedure; a dedicated host dataset provides a more explicit ZFS snapshot boundary.
  3. Update while retaining the same app identity and /data volume. Never delete the volume, reinstall with empty storage or reset a database to resolve an error. Restart and confirm the saved seat restores.
  4. Retain the prior immutable image tag. Rollback may require its matching storage snapshot if a subsequent migration is incompatible; switching the image alone is not a guaranteed database rollback.

No unattended updater is installed. The health probe checks HTTP availability; the CI integration and saved-seat checks separately exercise persistence.

Verification and installation boundary

On a machine with Docker:

docker build -t dune:verify .
node tools/verify-container.mjs dune:verify

The verifier only removes containers and volumes with names it randomly creates. It never mounts development games or an existing TrueNAS volume.

Container execution and NAS installation require a successful CI run and an authenticated TrueNAS session. Final acceptance includes create/join at the NAS URL, saved-seat restoration, restart and update retaining storage. Source checks alone do not certify these steps.

Sources: TrueNAS custom apps, GitHub container registry, Cloudflare local runtime.