chore: one-command deploy to nte.goober.moe

The homelab has no rsync, so the tree goes over as a tar stream, and the
destination is wiped first - a stale content-hashed chunk left behind would be
served forever, since /assets/* is cached as immutable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
goober 2026-08-21 12:43:28 +03:00
parent f6466881f4
commit fd63d5dce0
3 changed files with 45 additions and 1 deletions

View file

@ -56,3 +56,21 @@ IndexedDB, workers, `fetch` and module scripts are all unavailable. The app
handles this behind two interfaces it never sees through — `PersistenceAdapter` handles this behind two interfaces it never sees through — `PersistenceAdapter`
(IndexedDB → localStorage → memory) and `SolverHost` (worker → inline) — and (IndexedDB → localStorage → memory) and `SolverHost` (worker → inline) — and
shows a banner when storage is not durable. Artwork is not carried in that build. shows a banner when storage is not durable. Artwork is not carried in that build.
## Deploying
`npm run deploy` builds both targets and publishes to
**https://nte.goober.moe**.
The homelab has no `rsync`, so the tree goes over as a tar stream, and the
destination is wiped first: a stale content-hashed chunk left behind would be
served forever, since `/assets/*` is cached as immutable. Caddy serves
`/mnt/docker/appdata/caddy/sites/nte.goober.moe` as `/srv/nte.goober.moe` via a
read-only bind mount, which is why a deploy needs no container restart — but
adding a *new* site does, and recreating that container briefly drops every
other site behind Caddy.
`https://nte.goober.moe/nte.html` is the single-file offline build, shipped
alongside the app.
`dist/` is not in git. The deploy builds from source.

View file

@ -18,7 +18,8 @@
"test": "vitest run", "test": "vitest run",
"test:watch": "vitest", "test:watch": "vitest",
"typecheck": "tsc -b --noEmit", "typecheck": "tsc -b --noEmit",
"check:guides": "node --experimental-strip-types tools/check-guides.ts" "check:guides": "node --experimental-strip-types tools/check-guides.ts",
"deploy": "sh tools/deploy.sh"
}, },
"dependencies": { "dependencies": {
"idb": "8.0.3", "idb": "8.0.3",

25
tools/deploy.sh Executable file
View file

@ -0,0 +1,25 @@
#!/usr/bin/env sh
# Build and publish to nte.goober.moe.
#
# The homelab has no rsync, so the tree goes over as a tar stream. Caddy serves
# /mnt/docker/appdata/caddy/sites/nte.goober.moe as /srv/nte.goober.moe (a
# read-only bind mount declared in /mnt/docker/compose/caddy/docker-compose.yml),
# so a deploy is a file copy - no container restart, no config reload.
set -eu
HOST="${DEPLOY_HOST:-hl}"
DEST="${DEPLOY_DEST:-/mnt/docker/appdata/caddy/sites/nte.goober.moe}"
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"
rm -rf dist
# build:single runs the hosted build first, so this produces both targets.
npm run build:single
# Replace rather than merge: a stale content-hashed chunk left behind would be
# served forever, since /assets/* is cached as immutable.
tar -C dist -cz . | ssh "$HOST" "rm -rf '$DEST'/* && tar -C '$DEST' -xz"
printf '\ndeployed. checking...\n'
curl -fsS -o /dev/null -w 'https://nte.goober.moe/ -> %{http_code}\n' https://nte.goober.moe/
curl -fsS -o /dev/null -w 'https://nte.goober.moe/nte.html -> %{http_code} (%{size_download} bytes)\n' https://nte.goober.moe/nte.html