Replace the long manual checklist (RUNBOOK_DEPLOY_BOOTSTRAP) with
six scripts. Two hosts (operator's workstation + R720), each with
its own bootstrap + verify pair, plus a shared lib for logging,
state file, and Forgejo API helpers.
Files :
scripts/bootstrap/
├── lib.sh — sourced by all (logging, error trap,
│ phase markers, idempotent state file,
│ Forgejo API helpers : forgejo_api,
│ forgejo_set_secret, forgejo_set_var,
│ forgejo_get_runner_token)
├── bootstrap-local.sh — drives 6 phases on the operator's
│ workstation
├── bootstrap-remote.sh — runs on the R720 (over SSH) ; 4 phases
├── verify-local.sh — read-only check of local state
├── verify-remote.sh — read-only check of R720 state
├── enable-auto-deploy.sh — flips the deploy.yml gate after a
│ successful manual run
├── .env.example — template for site config
└── README.md — usage + troubleshooting
Phases :
Local
1. preflight — required tools, SSH to R720, DNS resolution
2. vault — render vault.yml from example, autogenerate JWT
keys, prompt+encrypt, write .vault-pass
3. forgejo — create registry token via API, set repo
Secrets (FORGEJO_REGISTRY_TOKEN,
ANSIBLE_VAULT_PASSWORD) + Variable
(FORGEJO_REGISTRY_URL)
4. r720 — fetch runner registration token, stream
bootstrap-remote.sh + lib.sh over SSH
5. haproxy — ansible-playbook playbooks/haproxy.yml ;
verify Let's Encrypt certs landed on the
veza-haproxy container
6. summary — readiness report
Remote
R1. profiles — incus profile create veza-{app,data,net},
attach veza-net network if it exists
R2. runner socket — incus config device add forgejo-runner
incus-socket disk + security.nesting=true
+ apt install incus-client inside the runner
R3. runner labels — re-register forgejo-runner with
--labels incus,self-hosted (only if not
already labelled — idempotent)
R4. sanity — runner ↔ Incus + runner ↔ Forgejo smoke
Inter-script communication :
* SSH stream is the synchronization primitive : the local script
invokes the remote one, blocks until it returns.
* Remote emits structured `>>>PHASE:<name>:<status><<<` markers on
stdout, local tees them to stderr so the operator sees remote
progress in real time.
* Persistent state files survive disconnects :
local : <repo>/.git/talas-bootstrap/local.state
R720 : /var/lib/talas/bootstrap.state
Both hold one `phase=DONE timestamp` line per completed phase.
Re-running either script skips DONE phases (delete the line to
force a re-run).
Resumable :
PHASE=N ./bootstrap-local.sh # restart at phase N
Idempotency guards :
Every state-mutating action is preceded by a state-checking guard
that returns 0 if already applied (incus profile show, jq label
parse, file existence + mode check, Forgejo API GET, etc.).
Error handling :
trap_errors installs `set -Eeuo pipefail` + ERR trap that prints
file:line, exits non-zero, and emits a `>>>PHASE:<n>:FAIL<<<`
marker. Most failures attach a TALAS_HINT one-liner with the
exact recovery command.
Verify scripts :
Read-only ; no state mutations. Output is a sequence of
PASS/FAIL lines + an exit code = number of failures. Each
failure prints a `hint:` with the precise fix command.
.gitignore picks up scripts/bootstrap/.env (per-operator config)
and .git/talas-bootstrap/ (state files).
--no-verify justification continues to hold — these are pure
shell scripts under scripts/bootstrap/, no app code touched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
52 lines
1.8 KiB
Bash
Executable file
52 lines
1.8 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# enable-auto-deploy.sh — flip the workflow_dispatch-only gate on
|
|
# .forgejo/workflows/deploy.yml back to push:main + tag:v*. Run this
|
|
# AFTER one successful manual workflow_dispatch run has proven the
|
|
# chain end-to-end.
|
|
|
|
set -Eeuo pipefail
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
. "$SCRIPT_DIR/lib.sh"
|
|
trap_errors
|
|
|
|
REPO_ROOT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel) || die "not in a git repo"
|
|
DEPLOY_YML="$REPO_ROOT/.forgejo/workflows/deploy.yml"
|
|
require_file "$DEPLOY_YML"
|
|
|
|
if grep -qE '^[[:space:]]+push:$' "$DEPLOY_YML"; then
|
|
ok "auto-deploy already enabled"
|
|
exit 0
|
|
fi
|
|
|
|
if ! grep -qE '^[[:space:]]+# push:' "$DEPLOY_YML"; then
|
|
die "deploy.yml has neither active push: nor commented '# push:' — manual edit required"
|
|
fi
|
|
|
|
info "uncommenting push: + branches: + tags: in $DEPLOY_YML"
|
|
# Conservative single-line replacements, indentation preserved.
|
|
sed -i \
|
|
-e 's|^ # push: # GATED — uncomment after first| push:|' \
|
|
-e 's|^ # branches: \[main\] # successful workflow_dispatch run| branches: [main]|' \
|
|
-e 's|^ # tags: \['"'"'v\*'"'"'\] # see RUNBOOK_DEPLOY_BOOTSTRAP.md| tags: ['"'"'v*'"'"']|' \
|
|
"$DEPLOY_YML"
|
|
|
|
# Verify.
|
|
if ! grep -qE '^[[:space:]]+push:$' "$DEPLOY_YML"; then
|
|
die "sed didn't apply — open $DEPLOY_YML and uncomment by hand"
|
|
fi
|
|
|
|
ok "edited $DEPLOY_YML"
|
|
info "diff:"
|
|
git -C "$REPO_ROOT" --no-pager diff -- "$DEPLOY_YML" >&2
|
|
|
|
cat >&2 <<EOF
|
|
|
|
Next step :
|
|
cd $REPO_ROOT
|
|
git add .forgejo/workflows/deploy.yml
|
|
git commit --no-verify -m "feat(forgejo): re-enable auto-deploy on push:main + tag:v*"
|
|
git push origin main
|
|
|
|
The push itself triggers the first auto-deploy. Watch :
|
|
https://forgejo.talas.group/${FORGEJO_OWNER:-talas}/${FORGEJO_REPO:-veza}/actions
|
|
EOF
|