deploy.sh rsynced the checkout as it was, so uncommitted edits went to slate with it; with two sessions in one checkout that nearly shipped another's unfinished work. It now exports a commit with git archive, HEAD by default, and wants a verified passing CI attestation for it, as main on the forge does with forge.requireci; --force ships one without. The commit deployed is written to /srv/forge/src/DEPLOYED.
- commit
- 4383583dc93eae0004733fd59207510c2553c8a5
- parent
- 6f5e766c14b0996f54a268d452da1ffe91bc977d
- author
- cat <admin@dev.site>
- ci
- ✓ passed in 67s, signed by cat
- kind
- fix risk score 2.1 of 3, Jev's estimate
- review
- #2 Deploy a commit, not the working tree
ci attestation
forge-attestation 1 type ci commit 4383583dc93eae0004733fd59207510c2553c8a5 tree dd0cad6a3a04f96b563a799f09b430d0fb6cbd34 script .forge/ci 05d612a331a46d602928c28846e036e53299df48 result pass exit 0 started 2026-10-18T22:06:48Z duration 67s runner (not shown here; refs/notes/ci has it) system Darwin 25.5.0 arm64 env host log sha256:04b7c894c00e88db285420c2124fe34f85a96d8f65d1f2a6444332e03528bd93 234 lines |remote: review #4: /dami/greet/reviews/4/ |pushed wip/i18n |b8eac5d running .forge/ci: greet.sh: say what it is | | sh: /bin/sh | | ok greet |b8eac5d pass in 0s, signed |pushed main |remote: review #4: /dami/greet/reviews/4/ |pushed wip/i18n |587d54d running .forge/ci: wave at the world | | sh: /bin/sh | | FAIL greet : got 'hello, world o/', want 'hello, world' |587d54d fail in 1s, signed |forge: 1 commit(s) did not pass |remote: review #6: /dami/greet/reviews/6/ |pushed ana/wave |942ce01 running .forge/ci: update the test for the wave | | sh: /bin/sh | | ok greet |942ce01 pass in 0s, signed |remote: review #6: /dami/greet/reviews/6/ |pushed ana/wave |warning: You appear to have cloned an empty repository. |site home: deployed 132dac6 -> /sites/ana/dotfiles/home/ |created ana/notes |remote: review #1: /ana/notes/reviews/1/ |pushed ana/todo |landing #1 A todo list: ana/todo -> main |f3676c7 running .forge/ci: web, server and Python versions of greet | | sh: /bin/sh | | ok greet |f3676c7 pass in 0s, signed |pushed main |bb4f3e1 running .forge/ci: messages: notes for the later regions | | sh: /bin/sh | | ok greet |bb4f3e1 pass in 0s, signed |remote: review #7: /dami/greet/reviews/7/ |pushed dami/regions |created dami/forge |warning: You appear to have cloned an empty repository. |site guide: deployed 06503cc -> /sites/dami/forge/guide/ |(node:59876) ExperimentalWarning: stripTypeScriptTypes is an experimental feature and might change at any time |(Use `node --trace-warnings ...` to show where the warning was created) |built ana/dotfiles in 0s -> /Users/dami/Library/Caches/forge/ci/35f7a65d0235/demo/root/.forge/www |(node:60000) ExperimentalWarning: stripTypeScriptTypes is an experimental feature and might change at any time |(Use `node --trace-warnings ...` to show where the warning was created) |built ana/notes in 1s -> /Users/dami/Library/Caches/forge/ci/35f7a65d0235/demo/root/.forge/www |(node:60136) ExperimentalWarning: stripTypeScriptTypes is an experimental feature and might change at any time |(Use `node --trace-warnings ...` to show where the warning was created) |built dami/forge in 0s -> /Users/dami/Library/Caches/forge/ci/35f7a65d0235/demo/root/.forge/www |(node:60266) ExperimentalWarning: stripTypeScriptTypes is an experimental feature and might change at any time |(Use `node --trace-warnings ...` to show where the warning was created) |built dami/greet in 2s -> /Users/dami/Library/Caches/forge/ci/35f7a65d0235/demo/root/.forge/www |(node:60930) ExperimentalWarning: stripTypeScriptTypes is an experimental feature and might change at any time |(Use `node --trace-warnings ...` to show where the warning was created) |built - in 0s -> /Users/dami/Library/Caches/forge/ci/35f7a65d0235/demo/root/.forge/www |226 of 226 pages render the same without 11ty |guide: 4 files, links ok |ok
Signature: good, cat
| M | README.md | 8 | +++++++- |
| M | deploy/ | 28 | ++++++++++++++++++++++++++-- |
@@ -759,10 +759,16 @@ without it there is no fallback.
759759 On slate, everything lives in `/srv/forge` (`src/`, `data/`, `forge.env`):760760 761761 ```sh762-deploy/slate/deploy.sh # rsync this checkout, docker compose up -d --build762+deploy/slate/deploy.sh # ship HEAD (git archive), docker compose up -d --build763763 ssh slate docker exec forge forge-admin user add ana 'ssh-ed25519 AAAA...'764764 ```765765 766+`deploy.sh [--force] [<commit>]` ships a commit, `HEAD` by default, exported767+with `git archive`: uncommitted edits in the checkout stay behind. It wants768+a verified passing CI attestation for it (`forge verify`), as `main` on the769+forge does with `forge.requireci`, and `--force` ships one without. The770+commit running on slate is in `/srv/forge/src/DEPLOYED`.771+766772 The edge (`deploy/edge`) needs a dedicated IPv4 on Fly, because raw TCP on767773 port 22 doesn't work on shared addresses. DNS for `src.rest` and768774 `pages.src.rest` points at the edge's addresses, DNS-only on Cloudflare@@ -1,9 +1,33 @@
11 2-# Ship this checkout to slate and rebuild the forge container there.2+# Ship a commit of this repo to slate and rebuild the forge container there.33 # Data in /srv/forge/data is untouched.4+#5+# deploy/slate/deploy.sh [--force] [<commit>] HEAD by default6+#7+# What ships is the commit, exported with git archive, never the working8+# tree: uncommitted edits, whoever made them, stay here. It needs a9+# verified passing CI attestation (forge verify), as main on the forge10+# does; --force ships it anyway. The commit deployed is written to11+# /srv/forge/src/DEPLOYED.412 set -eu513 here=$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)614 host=${FORGE_HOST:-slate}15+force=016+[ "${1:-}" = --force ] && { force=1; shift; }17+rev=$(git -C "$here" rev-parse --verify "${1:-HEAD}^{commit}")18+short=$(git -C "$here" rev-parse --short "$rev")19+if ! git -C "$here" diff --quiet HEAD 2>/dev/null; then20+ echo "deploy: the working tree has uncommitted changes; they stay here, $short ships" >&221+fi22+if ! "$here/bin/forge" verify "$rev" >/dev/null 2>&1; then23+ [ "$force" = 1 ] || { echo "deploy: $short has no verified passing CI attestation (forge attest $short), or pass --force" >&2; exit 1; }24+ echo "deploy: shipping $short without a verified CI pass (--force)" >&225+fi26+tmp=$(mktemp -d "${TMPDIR:-/tmp}/forge-deploy.XXXXXX")27+trap 'rm -rf "$tmp"' EXIT28+git -C "$here" archive "$rev" | tar -x -C "$tmp"29+printf '%s\n' "$rev" >"$tmp/DEPLOYED"730 rsync -a --delete --exclude node_modules --exclude demo/root --exclude demo/work \8- --exclude .git --exclude .env --exclude _site --exclude .cache "$here/" "$host:/srv/forge/src/"31+ --exclude .env --exclude _site --exclude .cache "$tmp/" "$host:/srv/forge/src/"932 ssh "$host" 'cd /srv/forge/src/deploy/slate && docker compose up -d --build'33+echo "deploy: $short is on $host"