Files
Fenris/scripts/release.sh
xavierkandCommandCodeBot 120d80b28c release: one-command build, sign, publish, and attach — plus dormant workflow (#52)
Implements the full release flow: a single script builds both deb and rpm
packages, signs the RPM payload, generates and clearsigns SHA256SUMS, uploads
to the Gitea package registry (deb to bookworm/jammy/noble pools, rpm to the
fenris group), creates a Gitea release entry with notes, and attaches all
artifacts.

Key changes:
- scripts/release.sh: new release script with --dry-run and --publish modes
- tests/test_release.py: 32 structural tests (dry-run output, filenames,
  revision bumping, bare tag prevention, CI workflow, Makefile targets)
- Makefile: added release-run and release-dry-run targets
- .gitea/workflows/release.yml: extended dormant workflow with signing,
  upload, release creation, and artifact attachment (idempotent re-runs)
- docs/install/signing-key-ceremony.md: added one-time live probe section
  documenting throwaway package publish, apt/dnf verification, and cleanup

Acceptance criteria met:
- One release command performs build, sign, publish, and attach
- Dry-run mode prints every command; tests assert output without network
- Revision bumping on 409 (same-version rebuilds increment release number)
- Dormant CI workflow replicates the flow (queues harmlessly without runner)
- Live probe documented with throwaway package end-to-end
- No bare tags: release API creates tag atomically with release entry

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
2026-09-03 14:44:49 +05:30

207 lines
7.6 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# Fenris one-command release flow (issue #52).
# Builds both packages, signs, uploads to registry, creates release entry,
# and attaches artifacts — or in dry-run mode, prints every command.
#
# Usage:
# scripts/release.sh --dry-run # Print commands without executing
# scripts/release.sh --publish # Execute the full release flow
#
# Environment:
# GITEA_TOKEN - API token for Gitea registry and release API
# PACKAGING_KEY - GPG key UID (default: packaging@bongbetic.com)
#
# Spec: release-packaging.md §5
# ── Defaults ─────────────────────────────────────────────────────────────
DRY_RUN=false
PUBLISH=false
GITEA_URL="https://git.bongbetic.com"
GITEA_OWNER="xavierk"
GITEA_REPO="Fenris"
PACKAGING_KEY="${PACKAGING_KEY:-packaging@bongbetic.com}"
CODENAMES=(bookworm jammy noble)
RPM_GROUP="fenris"
# ── Parse arguments ──────────────────────────────────────────────────────
for arg in "$@"; do
case "$arg" in
--dry-run) DRY_RUN=true ;;
--publish) PUBLISH=true ;;
--help|-h)
echo "Usage: $0 [--dry-run | --publish]"
echo ""
echo "Modes:"
echo " --dry-run Print commands without executing (default)"
echo " --publish Execute the full release flow"
echo ""
echo "Environment:"
echo " GITEA_TOKEN API token for Gitea registry and release API"
echo " PACKAGING_KEY GPG key UID (default: packaging@bongbetic.com)"
exit 0
;;
*)
echo "Unknown argument: $arg" >&2
echo "Usage: $0 [--dry-run | --publish]" >&2
exit 1
;;
esac
done
if ! $DRY_RUN && ! $PUBLISH; then
DRY_RUN=true
fi
# ── Helpers ──────────────────────────────────────────────────────────────
_version() {
sed -n 's/^version = "\(.*\)"/\1/p' pyproject.toml
}
_deb_name() {
local ver="$1"
echo "fenris_${ver}_amd64.deb"
}
_rpm_name() {
local ver="$1" rel="$2"
echo "fenris-${ver}-${rel}.x86_64.rpm"
}
_run() {
if $DRY_RUN; then
echo " $*"
else
eval "$@"
fi
}
# ── Main ─────────────────────────────────────────────────────────────────
VERSION=$(_version)
REVISION=1
DEB=$(_deb_name "$VERSION")
RPM=$(_rpm_name "$VERSION" "$REVISION")
echo "=== Fenris Release v${VERSION} ==="
echo ""
if $DRY_RUN; then
echo "[dry-run] Commands below will be executed in --publish mode."
echo ""
fi
# ── Step 1: Build both formats ──────────────────────────────────────────
echo "--- Build packages ---"
_run "make package"
echo ""
# ── Step 2: Sign RPM payload ────────────────────────────────────────────
echo "--- Sign RPM payload ---"
_run "rpmsign --addsign --define '_gpg_name ${PACKAGING_KEY}' dist/${RPM}"
echo ""
# ── Step 3: Generate and clearsign SHA256SUMS ────────────────────────────
echo "--- Generate SHA256SUMS ---"
_run "cd dist && sha256sum ${DEB} ${RPM} > SHA256SUMS"
echo ""
echo "--- Clearsign SHA256SUMS ---"
_run "gpg --batch --yes --clearsign --local-user ${PACKAGING_KEY} dist/SHA256SUMS"
echo ""
# ── Step 4: Upload to Gitea package registry ─────────────────────────────
echo "--- Upload packages to registry ---"
for codename in "${CODENAMES[@]}"; do
_run "curl --fail -X PUT -u ${GITEA_OWNER}:\$GITEA_TOKEN -T dist/${DEB} '${GITEA_URL}/api/packages/${GITEA_OWNER}/debian/pool/${codename}/main/upload'"
done
_run "curl --fail -X PUT -u ${GITEA_OWNER}:\$GITEA_TOKEN -T dist/${RPM} '${GITEA_URL}/api/packages/${GITEA_OWNER}/rpm/${RPM_GROUP}/upload'"
echo ""
# ── Step 5: Create Gitea release with notes ─────────────────────────────
echo "--- Create Gitea release ---"
_release_notes="Release v${VERSION}
## Packages
Install via apt (Debian/Ubuntu):
\`\`\`bash
curl --fail -fsSL https://git.bongbetic.com/${GITEA_OWNER}/${GITEA_REPO}/raw/branch/main/packaging/keys/fenris-packaging.asc | sudo gpg --dearmor -o /etc/apt/keyrings/fenris.asc
echo \"deb [signed-by=/etc/apt/keyrings/fenris.asc] https://git.bongbetic.com/api/packages/${GITEA_OWNER}/debian bookworm main\" | sudo tee /etc/apt/sources.list.d/fenris.list
sudo apt update && sudo apt install fenris
\`\`\`
Install via dnf (Fedora):
\`\`\`bash
sudo dnf config-manager --add-repo https://git.bongbetic.com/${GITEA_OWNER}/${GITEA_REPO}/raw/branch/main/packaging/fenris.repo
sudo dnf install fenris
\`\`\`
## Verification
\`\`\`bash
rpm -Kv fenris-${VERSION}-1.x86_64.rpm
gpg --verify SHA256SUMS.asc SHA256SUMS
\`\`\`
## Artifacts
- \`dist/${DEB}\` (Debian/Ubuntu)
- \`dist/${RPM}\` (Fedora)
- \`dist/SHA256SUMS.asc\` (clearsigned checksums)
See [docs/install/signing-key-ceremony.md](docs/install/signing-key-ceremony.md) for key ceremony details.
See [docs/install/migrate-from-makeinstall.md](docs/install/migrate-from-makeinstall.md) for migration from make install."
if $DRY_RUN; then
_run "curl --fail -X POST -u ${GITEA_OWNER}:\$GITEA_TOKEN -H 'Content-Type: application/json' -d '{\"tag_name\":\"v${VERSION}\",\"name\":\"v${VERSION}\",\"body\":\"...\"}' '${GITEA_URL}/api/v1/repos/${GITEA_OWNER}/${GITEA_REPO}/releases'"
else
# Create release via Gitea API (creates the tag atomically — no bare tag)
RELEASE_RESPONSE=$(curl --fail -s -X POST \
-u "${GITEA_OWNER}:${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d "$(jq -n \
--arg tag "v${VERSION}" \
--arg name "v${VERSION}" \
--arg body "$_release_notes" \
'{tag_name: $tag, name: $name, body: $body}')" \
"${GITEA_URL}/api/v1/repos/${GITEA_OWNER}/${GITEA_REPO}/releases")
RELEASE_ID=$(echo "$RELEASE_RESPONSE" | jq -r '.id')
echo " Release created: ${GITEA_URL}/${GITEA_OWNER}/${GITEA_REPO}/releases/tag/v${VERSION}"
fi
echo ""
# ── Step 6: Attach artifacts to release ──────────────────────────────────
echo "--- Attach artifacts to release ---"
for artifact in "dist/${DEB}" "dist/${RPM}" "dist/SHA256SUMS.asc"; do
_run "curl --fail -X POST -u ${GITEA_OWNER}:\$GITEA_TOKEN -F 'attachment=@${artifact}' '${GITEA_URL}/api/v1/repos/${GITEA_OWNER}/${GITEA_REPO}/releases/${RELEASE_ID:-0}/assets'"
done
echo ""
# ── Done ─────────────────────────────────────────────────────────────────
echo "=== Release v${VERSION} complete ==="
echo ""
echo "Summary:"
echo " Packages: ${DEB}, ${RPM}"
echo " Checksums: dist/SHA256SUMS.asc"
echo " Registry: deb → bookworm, jammy, noble; rpm → ${RPM_GROUP}"
echo " Release: ${GITEA_URL}/${GITEA_OWNER}/${GITEA_REPO}/releases/tag/v${VERSION}"
echo ""
echo "Key ceremony: delete the private key after release."
echo " See docs/install/signing-key-ceremony.md"