furtka/scripts/build-release-tarball.sh

50 lines
1.5 KiB
Bash
Raw Normal View History

feat(furtka): release CI + \`furtka update\` / \`furtka rollback\` CLI Slice 2 of the self-update story. Tagging a release on main now produces a downloadable self-update payload on the Forgejo releases page, and a running box can pull it down, verify it, atomically swap to the new version, and health-check the result. New pieces: - scripts/build-release-tarball.sh <version> — packages the furtka/ package + bundled apps/ + a root-level VERSION file as dist/furtka-<version>.tar.gz, plus a .sha256 sidecar and a release.json metadata blob. - scripts/publish-release.sh <version> — uses the Forgejo v1 API to create a release (body pulled from the CHANGELOG section for this tag, pre-release auto-flagged on -alpha/-beta/-rc) and upload the three assets sequentially. Needs \$FORGEJO_TOKEN. - .forgejo/workflows/release.yml — tag-triggered, runs both scripts with the new \$FORGEJO_RELEASE_TOKEN repo secret. - furtka/updater.py — check_update, prepare_update, apply_update, run_update, rollback. Atomic symlink swap, sha256 verify (TOCTOU- safe: re-hashes on-disk file), health-check post-restart with auto-rollback on failure, stage-by-stage progress persisted to /var/lib/furtka/update-state.json so the UI can poll independent of the (restarting) API process. Path overrides via FURTKA_ROOT / FURTKA_STATE_DIR / FURTKA_LOCK_PATH so tests pin a tmpdir. - furtka/cli.py — \`furtka update [--check] [--json]\` and \`furtka rollback\`. - tests/test_updater.py — 15 tests: version compare, sha256 verify, tarball extract (including traversal refusal), lockfile, apply happy + rollback paths, rollback CLI, check_update with stubbed Forgejo. - iso/build.sh — writes VERSION at the tarball root so the install path matches the self-update path (previously assumed only the release script did this). RELEASING.md now points at the automated flow — no more manually clicking "Create release" on the Forgejo UI. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 13:30:45 +02:00
#!/usr/bin/env bash
# Build a Furtka release tarball + sha256 sidecar + release.json metadata.
#
# Usage: ./scripts/build-release-tarball.sh <version>
#
# Produces (in ./dist/):
# furtka-<version>.tar.gz contents extract to /opt/furtka/versions/<version>/
# furtka-<version>.tar.gz.sha256 single-line sha256 (<hash> <name>)
# release.json {"version","sha256","size","created_at"}
#
# The tarball shape matches what iso/build.sh ships in the live ISO: a
# VERSION file at the root, plus furtka/ and apps/ trees. Self-update on a
# running box downloads this tarball, verifies the sha256, stages to
# /opt/furtka/versions/<version>/, and flips /opt/furtka/current to it.
set -euo pipefail
VERSION="${1:?usage: $0 <version>}"
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
DIST_DIR="$REPO_ROOT/dist"
STAGE="$(mktemp -d)"
trap 'rm -rf "$STAGE"' EXIT
cp -a "$REPO_ROOT/furtka" "$STAGE/"
cp -a "$REPO_ROOT/apps" "$STAGE/"
find "$STAGE" -type d -name __pycache__ -exec rm -rf {} +
echo "$VERSION" > "$STAGE/VERSION"
mkdir -p "$DIST_DIR"
TARBALL="$DIST_DIR/furtka-$VERSION.tar.gz"
tar -czf "$TARBALL" -C "$STAGE" .
SHA=$(sha256sum "$TARBALL" | awk '{print $1}')
SIZE=$(stat -c%s "$TARBALL")
printf '%s %s\n' "$SHA" "$(basename "$TARBALL")" > "$TARBALL.sha256"
cat > "$DIST_DIR/release.json" <<EOF
{
"version": "$VERSION",
"sha256": "$SHA",
"size": $SIZE,
"created_at": "$(date -Iseconds)"
}
EOF
echo "Built $TARBALL"
echo " sha256: $SHA"
echo " size: $SIZE bytes"