From fd63d5dce052b4c780cee51aef26d15004385c3e Mon Sep 17 00:00:00 2001 From: goober Date: Fri, 21 Aug 2026 12:43:28 +0300 Subject: [PATCH] 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 --- README.md | 18 ++++++++++++++++++ package.json | 3 ++- tools/deploy.sh | 25 +++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100755 tools/deploy.sh diff --git a/README.md b/README.md index 30bf282..2fd16c0 100644 --- a/README.md +++ b/README.md @@ -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` (IndexedDB → localStorage → memory) and `SolverHost` (worker → inline) — and 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. diff --git a/package.json b/package.json index a7b3aa0..424a3f9 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ "test": "vitest run", "test:watch": "vitest", "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": { "idb": "8.0.3", diff --git a/tools/deploy.sh b/tools/deploy.sh new file mode 100755 index 0000000..2d34673 --- /dev/null +++ b/tools/deploy.sh @@ -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