feat: extract reforge engine into a standalone consumable flake

The forgejo-sandbox / reforge harness, lifted out of the machine config
into a host-agnostic, generic engine anyone can consume with Nix.

Two layers:
- engine (this repo) — nixosModules.reforge stands up the sandbox forge,
  provisions role accounts + tokens, enforces branch protection, and puts
  the reforge-* CLI + forgejo-mcp on PATH. Carries no project specifics.
- run config — per-project manifest/charter/agenda/issues an adopter fills
  in; scaffold one with the `reforge` flake template.

Portability fixes vs the in-config version:
- forgejo-mcp resolved from $REFORGE_MCP_BIN or PATH, never a named host
  (kills the nixosConfigurations.omni hardcode).
- all instance data + paths parameterized via REFORGE_* env, baked into the
  reforge-scripts wrappers from module options (configDir, agentsDir,
  refsDir, org, port, tokenOwner, ...).
- option namespace neutral (reforge.* not omni.packs.*); settings policies
  carry no absolute /etc/nixos paths.
- role briefs + orchestrator playbook genericized: all project specifics
  point at the charter; refs corpus optional.

Validated: nix flake check (eval) + builds of forgejo-mcp, reforge-scripts,
and a module-eval check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Padreug 2026-07-18 22:50:49 +02:00
commit df0fd9a9ba
32 changed files with 2698 additions and 0 deletions

View file

@ -0,0 +1,73 @@
#!/usr/bin/env bash
# Reforge acceptance check (docs/reforge.md): byte-for-byte TREE diff of
# each sandbox repo's `main` against its declared target — the known
# working state — from $REFORGE_CONFIG_DIR/manifest.txt.
#
# Targets are fetched shallow from their real remotes (may require your ssh
# key, depending on the target URL); nothing is pushed or written anywhere.
# History is deliberately ignored: the run re-derives the tree through its
# own PR history, so only content parity counts.
#
# Exit 0 = every repo with a declared target is IDENTICAL.
#
# reforge-compare
set -euo pipefail
FORGE_URL=${REFORGE_FORGE_URL:-http://localhost:3030}
ORG=${REFORGE_ORG:-sandbox-team}
ADMIN_USER=${REFORGE_ADMIN_USER:-sandbox-admin}
TOKENS_DIR=${REFORGE_TOKENS_DIR:-/var/lib/forgejo-sandbox/tokens}
CONFIG_DIR=${REFORGE_CONFIG_DIR:?set REFORGE_CONFIG_DIR to your run config dir}
TOKEN_FILE=${REFORGE_ADMIN_TOKEN_FILE:-$TOKENS_DIR/${ADMIN_USER}.token}
TOKEN=$(cat "$TOKEN_FILE")
MANIFEST="$CONFIG_DIR/manifest.txt"
[ -r "$MANIFEST" ] || { echo "compare: no readable manifest at $MANIFEST" >&2; exit 1; }
WORK=$(mktemp -d)
trap 'rm -rf "$WORK"' EXIT
auth_url() { # name
echo "http://$ADMIN_USER:$TOKEN@${FORGE_URL#http://}/$ORG/$1.git"
}
fail=0
mapfile -t ROWS < <(grep -Ev '^[[:space:]]*(#|$)' "$MANIFEST")
for row in "${ROWS[@]}"; do
IFS='|' read -r name _kind _upstream _base_ref target_url target_ref <<<"$row"
if [ "$target_url" = "-" ] || [ -z "$target_url" ]; then
echo "SKIP $name — no target declared in the manifest"
continue
fi
d="$WORK/$name"
git init --quiet "$d"
if ! git -C "$d" fetch --quiet --depth 1 "$(auth_url "$name")" main 2>/dev/null; then
echo "MISSING $name — no main in the sandbox (not seeded / not scaffolded yet)"
fail=1
continue
fi
sandbox_sha=$(git -C "$d" rev-parse FETCH_HEAD)
git -C "$d" fetch --quiet --depth 1 "$target_url" "$target_ref"
target_sha=$(git -C "$d" rev-parse FETCH_HEAD)
if git -C "$d" diff --quiet "$sandbox_sha" "$target_sha"; then
echo "IDENTICAL $name — matches $target_url @ $target_ref"
else
n=$(git -C "$d" diff --name-only "$sandbox_sha" "$target_sha" | wc -l)
summary=$(git -C "$d" diff --shortstat "$sandbox_sha" "$target_sha")
echo "DIFFERS $name$n files vs $target_ref:$summary"
fail=1
fi
done
echo
if [ "$fail" = 0 ]; then
echo "compare: every targeted repo is byte-for-byte identical to its target."
else
echo "compare: divergence remains (DIFFERS/MISSING above)."
fi
exit "$fail"