#!/usr/bin/env bash # # Builds and deploys site/ to Wisp (wisp.place), which serves it at # atfs.dev. The full sequence both `.tangled/workflows/deploy-site.yml` # (push to main) and `publish-sbc-images.yml` (a tagged release) now drive — # pulled out here so the two callers share one script instead of two # copies that could drift (ATFS-ouy7). # # site/ is pure build output — nothing under it is committed. web/ is one # SvelteKit app: every page is a route under web/src/routes, prerendered to a # static file here, and everything in web/static/ (oauth-client-metadata.json, # .well-known/, the setup flow's old-URL redirect stub, atfs.css, logo.png) # is copied in verbatim. On top of that, # hack/bake-site-downloads.sh bakes the current release's raw downloads in # (site/setup/images.json, site/downloads/raw/). # # Step order is the safety property: the build step runs before wisp-cli # is even downloaded, so a broken build — or, now, an atfs instance that # can't produce a release index (see bake-site-downloads.sh's own header) # — stops the whole deploy right here, with the previous site still being # served. There is no path that uploads a half-assembled tree. The bake # step must run after the Vite build, not before: Vite empties site/ and # copies web/static/ into it verbatim, which would otherwise silently # overwrite a freshly-baked images.json. # # Requires (env): # ATFS_DOMAIN the instance hack/bake-site-downloads.sh queries # SITE_URL where this site is served, read back for the # release index when the instance is unreachable # (default https://atfs.dev — see the bake's own # header). A deploy that can reach neither still # fails rather than publish an empty page. # for the current release's index (or ATFS_SERVER — # see that script's own header) # WISP_HANDLE the atproto handle deploying to Wisp # WISP_APP_PASSWORD that handle's atproto app password # SITE_PATH the assembled tree to deploy (default ./site) # SITE_NAME the Wisp site bound to atfs.dev (default atfs) # # Set up outside this repo, once: a Wisp site named SITE_NAME under # WISP_HANDLE, with atfs.dev bound to it — the name must match or the # deploy lands on the wrong site. # # Usage: hack/deploy-site.sh set -euo pipefail SITE_PATH="${SITE_PATH:-./site}" SITE_NAME="${SITE_NAME:-atfs}" WISP_HANDLE="${WISP_HANDLE:?WISP_HANDLE must be set}" WISP_APP_PASSWORD="${WISP_APP_PASSWORD:?WISP_APP_PASSWORD must be set}" fail() { echo "error: $*" >&2 exit 1 } # web/svelte.config.js prerenders the whole app into ../site — the home page, # docs page, setup flow, and the manage pages' 200.html fallback — plus # web/static/ copied in verbatim. echo "=== building the site ===" (cd web && corepack pnpm install --frozen-lockfile && corepack pnpm run build) # Bakes the current release's index — queried live, see that script's own # header — into site/setup/images.json (the setup flow's catalogue) and # site/downloads/raw/ (the human download links). Fails the whole deploy, # before wisp-cli is ever fetched, when no valid index can be found. echo "=== baking the release index into the site ===" # Strict deliberately — no --allow-unpublished. This is the caller that # would overwrite a good page, and it runs before wisp-cli is downloaded, # so failing here leaves the previous site serving. ./hack/bake-site-downloads.sh "$SITE_PATH" # Indexes the docs pages just built (data-pagefind-body in DocLayout.svelte, # TypeIndex.svelte, etc.) for the sidebar's search overlay # (web/src/lib/docs/Search.svelte) — after the bake step, so anything it # writes is covered too. Resolved to an absolute path first since the # pagefind CLI below runs from web/, where a relative $SITE_PATH would # resolve against the wrong directory. echo "=== indexing search (pagefind) ===" site_abs="$(cd "$SITE_PATH" && pwd)" (cd web && corepack pnpm exec pagefind --site "$site_abs") # Cheap insurance against uploading a tree that is missing something # load-bearing: .well-known must be at the top level or did:web:atfs.dev # stops resolving, and an empty site/setup or site/downloads/raw would # mean a build step silently wrote somewhere else. 200.html and _redirects are # the manage pages' fallback — /manage/ takes an arbitrary domain, so # nothing can be prerendered for one, and the rewrite rule pointing at that # shell is what answers a cold load. echo "=== checking the assembled tree ===" for required in \ "$SITE_PATH/index.html" \ "$SITE_PATH/docs/index.html" \ "$SITE_PATH/.well-known/did.json" \ "$SITE_PATH/.well-known/atproto-did" \ "$SITE_PATH/oauth-client-metadata.json" \ "$SITE_PATH/atfs.css" \ "$SITE_PATH/setup/index.html" \ "$SITE_PATH/setup/images.json" \ "$SITE_PATH/setup/index.json" \ "$SITE_PATH/downloads/index.html" \ "$SITE_PATH/downloads/raw/index.html" \ "$SITE_PATH/200.html" \ "$SITE_PATH/_redirects"; do [ -s "$required" ] || fail "missing or empty: $required" done echo "=== downloading wisp-cli ===" curl -fsS https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-x86_64-linux -o wisp-cli chmod +x wisp-cli echo "=== uploading site ===" # wisp-cli asks for /lib64/ld-linux-x86-64.so.2, a path no nix image has. # Patch the binary's own interpreter field to the real store path rather # than symlinking it into place at that fixed system path: the earlier # approach (mkdir -p /lib64 && ln -sfn) needs write access to /, which the # v0.2.4 release's publish-sbc-images.yml run didn't have (ln: Permission # denied) even though the same code had worked in earlier releases and # still works in deploy-site.yml's own run the same day — so this is # runner/engine-dependent, not something this script can assume either way. # patchelf edits wisp-cli itself, entirely inside the workspace, sidestepping # the question of whether / is writable. wisp-cli still has to be invoked # directly as `./wisp-cli` rather than via the loader as an argument: it's a # `bun build --compile` binary that finds its embedded payload through # /proc/self/exe, which only resolves correctly when the kernel itself execs # the file. loader="" for candidate in /nix/store/*-glibc-*/lib/ld-linux-x86-64.so.2; do loader="$candidate" break done [ -n "$loader" ] && [ -x "$loader" ] || fail "no glibc loader found in the image store; is glibc still a dependency?" patchelf --set-interpreter "$loader" wisp-cli ./wisp-cli deploy \ "$WISP_HANDLE" \ --path "$SITE_PATH" \ --site "$SITE_NAME" \ --password "$WISP_APP_PASSWORD" \ --yes