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 moving to Gitea.
Its .gitea/workflows/container.yml is intended to build and verify 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.
No Gitea runner has been registered and no Gitea image build has succeeded yet.
A source push is therefore not a verified container image or a deployment.
Register and configure an ubuntu-latest runner whose job environment has a
working Docker CLI, daemon and Buildx, plus network access to actions, npm and
container images. Configure registry access, then verify the
workflow and its published image before considering a manual NAS update. The
existing Compose configuration continues to reference the GHCR image until a
separately verified migration is applied at a safe point.
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
- 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.
- 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.
- Update while retaining the same app identity and
/datavolume. Never delete the volume, reinstall with empty storage or reset a database to resolve an error. Restart and confirm the saved seat restores. - 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.