clone

a personal forge on plain git: static pages, stacked-diff reviews, signed local ci, repo sites and releases

Let a restore run as root hand the forge to git in scenarios

cat committed 79c9c92
As root on a box with a git user, which is how forge-admin restore runs
in the container, forge restore chowns the restored forge to git and
finishes it (hooks, sites, releases, pages) as git. In scenarios that is
the path the container takes, and it failed: git couldn't get into the
scenario's directory (mktemp -d makes it 0700, root's) or write the
caches root's builds had filled. lib.sh opens the directory to git when
it runs as root and git exists, and restore_into restores into a new
forge with its pages and caches inside it, as a new host has, so git
owns them once forge restore has chowned it. The seven scenarios that
restore use it. Nothing changes for a normal user: restore finishes as
them, as before.

forge restore itself does the right thing here; the scenarios' setup was
what assumed it ran as the user who owns everything.
commit
79c9c92efac95599e87b6605a600b9b2b4ee0e3c
parent
f1bc047964acd1cfe96116a004b776fb797d0824
author
cat <admin@dev.site>
ci
✓ passed in 71s, signed by cat
kind
fix risk score 0.9 of 3, Jev's estimate
review
#5 Pass the scenarios in the container, as root
ci attestation
forge-attestation 1
type ci
commit 79c9c92efac95599e87b6605a600b9b2b4ee0e3c
tree 7a7619742e68bf5ebcce24393c7c65abf266da3b
script .forge/ci e6f038ae810b87f90a4be115a762a9c91dd76a01
result pass
exit 0
started 2026-10-18T23:02:31Z
duration 71s
runner (not shown here; refs/notes/ci has it)
system Darwin 25.5.0 arm64
env host
log sha256:0987a7a9aa556f26eabd0b5706e7646cc22f834271ff57423aee7be94504506a 240 lines
|remote: review #4: /dami/greet/reviews/4/
|pushed wip/i18n
|6d05816 running .forge/ci: greet.sh: say what it is
|  | sh: /bin/sh
|  | ok   greet
|6d05816 pass in 0s, signed
|pushed main
|remote: review #4: /dami/greet/reviews/4/
|pushed wip/i18n
|fcf05d6 running .forge/ci: wave at the world
|  | sh: /bin/sh
|  | FAIL greet : got 'hello, world o/', want 'hello, world'
|fcf05d6 fail in 0s, signed
|forge: 1 commit(s) did not pass
|remote: review #6: /dami/greet/reviews/6/
|pushed ana/wave
|2113433 running .forge/ci: update the test for the wave
|  | sh: /bin/sh
|  | ok   greet
|2113433 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 8c1875b -> /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
|3898fb1 running .forge/ci: web, server and Python versions of greet
|  | sh: /bin/sh
|  | ok   greet
|3898fb1 pass in 0s, signed
|pushed main
|4ecd38d running .forge/ci: messages: notes for the later regions
|  | sh: /bin/sh
|  | ok   greet
|4ecd38d 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 973f9ee -> /sites/dami/forge/guide/
|(node:68291) 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 1s -> /Users/dami/Library/Caches/forge/ci/35f7a65d0235/demo/root/.forge/www
|(node:68421) 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 0s -> /Users/dami/Library/Caches/forge/ci/35f7a65d0235/demo/root/.forge/www
|(node:68558) 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 1s -> /Users/dami/Library/Caches/forge/ci/35f7a65d0235/demo/root/.forge/www
|(node:68694) 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 1s -> /Users/dami/Library/Caches/forge/ci/35f7a65d0235/demo/root/.forge/www
|(node:69359) 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

8 files changed, +22 -8
Mtest/lib.sh14++++++++++++++
Mtest/scenarios/backup-restore.sh2+-
Mtest/scenarios/identity-backup.sh2+-
Mtest/scenarios/identity-litestream.sh4++--
Mtest/scenarios/lake-backup.sh2+-
Mtest/scenarios/private-backup.sh2+-
Mtest/scenarios/release-private-store-backup.sh2+-
Mtest/scenarios/release-restore.sh2+-
M test/lib.sh +14 -0
@@ -17,6 +17,7 @@
1717 # site_commit w "text" a commit of a one-page site, to push to refs/sites/...1818 # release_commit w v1 dir [key] a release commit of dir's files, to push to refs/releases/v11919 # aside dami/secret ... back dami/secret move a repo out of the forge and back20+# restore_into dir --from stage forge restore into a new forge at dir2021 # sync_builds hooks and ssh commands build pages before returning2122 # lq "<sql>" forge lake query: the rows as CSV, no header2223 # private_server lib/private-server.ts for this forge, at $PRIV
@@ -34,6 +35,10 @@ T_HOME=$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)
3435 FORGE=$T_HOME/bin/forge3536 T=$(mktemp -d "${TMPDIR:-/tmp}/forge-scenario.XXXXXX")3637 T=$(CDPATH= cd -- "$T" && pwd -P)38+# As root where there is a git user (the container), forge restore hands39+# the restored forge to git and finishes as git, as it does in production,40+# so git must be able to get to it in here.41+if [ "$(id -u)" = 0 ] && id -u git >/dev/null 2>&1; then chmod 755 "$T"; fi3742 # Processes a scenario starts in the background go in T_PIDS, to be stopped.3843 T_PIDS=3944 trap 'st=$?; [ -z "$T_PIDS" ] || kill $T_PIDS 2>/dev/null || true; [ -n "${FORGE_TEST_KEEP:-}" ] && echo "kept $T" >&2 || rm -rf "$T"; exit $st' EXIT
@@ -231,6 +236,15 @@ release_commit() {
231236 aside() { mv "$FORGE_ROOT/$1.git" "$T/aside.git"; }232237 back() { mv "$T/aside.git" "$FORGE_ROOT/$1.git"; }233238 239+# restore_into dir args...: forge restore into a new forge at dir, with240+# its pages and caches inside it, as on a new host. (As root, git finishes241+# the restore there, and couldn't write this forge's caches.)242+restore_into() {243+ rd=$1244+ shift245+ env FORGE_ROOT="$rd" FORGE_WWW="$rd/.forge/www" FORGE_CACHE="$rd/.forge/cache" "$FORGE" restore "$@"246+}247+234248 # From here on, what hooks and ssh commands rebuild is built before they return.235249 sync_builds() { export FORGE_NO_BUILD=0 FORGE_SYNC_BUILD=1; }236250
M test/scenarios/backup-restore.sh +1 -1
M test/scenarios/identity-backup.sh +1 -1
M test/scenarios/identity-litestream.sh +2 -2
M test/scenarios/lake-backup.sh +1 -1
M test/scenarios/private-backup.sh +1 -1
M test/scenarios/release-private-store-backup.sh +1 -1
M test/scenarios/release-restore.sh +1 -1