diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2945b64..8aace63 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,3 +1,8 @@ +# 1. Push to main → build.yml runs automatically → note the run ID +# 2. Manually trigger sign.yml with that build run ID → note the sign run ID +# 3. Manually trigger release.yml with: version=v1.2.5, sign_run_id= +# 4. Manually trigger changelog.yml with: version=v1.2.5 + name: 📖 Build PDFs on: diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index d09b40a..eb56442 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -1,12 +1,9 @@ name: 📝 Update Changelog -# Runs after build.yml completes on main — at that point we know what changed. -# Can also be triggered manually to backfill a missing entry. +# Manual only — automatic triggering is disabled to prevent version mismatches +# between the generated PDF and the GitHub release. Run this manually after +# a release is published and the version is confirmed. on: - workflow_run: - workflows: ["📖 Build PDFs"] - types: [completed] - branches: [main] workflow_dispatch: inputs: version: @@ -25,9 +22,6 @@ permissions: jobs: changelog: name: Prepend changelog entry - if: > - github.event_name == 'workflow_dispatch' || - github.event.workflow_run.conclusion == 'success' runs-on: ubuntu-latest steps: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 87ee40b..8e46c84 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,22 +1,17 @@ name: 🚀 Release -# Can be triggered: -# 1. Automatically after sign.yml completes on main -# 2. Manually, pointing at specific build/sign runs to pull artifacts from +# Manual only — run this deliberately after build and sign are confirmed good. +# Provide the exact version tag and the sign.yml run ID to pull artifacts from. on: - workflow_run: - workflows: ["🔏 Sign PDFs"] - types: [completed] - branches: [main] workflow_dispatch: inputs: - sign_run_id: - description: 'sign.yml run ID to pull signatures from' + version: + description: 'Release version tag (e.g. v1.2.4) — must not already exist' required: true type: string - build_run_id: - description: 'build.yml run ID to pull PDFs from (leave blank to use pdfs-signed from sign run)' - required: false + sign_run_id: + description: 'sign.yml run ID to pull signatures and PDFs from' + required: true type: string prerelease: description: 'Mark as pre-release?' @@ -31,9 +26,6 @@ permissions: jobs: release: name: Publish GitHub Release - if: > - github.event_name == 'workflow_dispatch' || - github.event.workflow_run.conclusion == 'success' runs-on: ubuntu-latest steps: @@ -44,40 +36,22 @@ jobs: sparse-checkout: pgp # ------------------------------------------------------------------ # - # Resolve which run IDs to pull artifacts from - # ------------------------------------------------------------------ # - - name: 🔍 Resolve run IDs - id: runs - run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - SIGN_RUN="${{ inputs.sign_run_id }}" - BUILD_RUN="${{ inputs.build_run_id }}" - else - SIGN_RUN="${{ github.event.workflow_run.id }}" - BUILD_RUN="" - fi - echo "sign_run=$SIGN_RUN" >> $GITHUB_OUTPUT - echo "build_run=$BUILD_RUN" >> $GITHUB_OUTPUT - echo "Sign run: $SIGN_RUN" - echo "Build run: ${BUILD_RUN:-'(using pdfs-signed from sign run)'}" - - # ------------------------------------------------------------------ # - # Download artifacts + # Download artifacts from the specified sign run # ------------------------------------------------------------------ # - name: 📥 Download signatures artifact uses: actions/download-artifact@v4 with: name: signatures path: release/ - run-id: ${{ steps.runs.outputs.sign_run }} + run-id: ${{ inputs.sign_run_id }} github-token: ${{ secrets.GITHUB_TOKEN }} - - name: 📥 Download PDFs (from sign run) + - name: 📥 Download signed PDFs artifact uses: actions/download-artifact@v4 with: name: pdfs-signed path: release/ - run-id: ${{ steps.runs.outputs.sign_run }} + run-id: ${{ inputs.sign_run_id }} github-token: ${{ secrets.GITHUB_TOKEN }} - name: 📋 List release assets @@ -96,7 +70,7 @@ jobs: echo "dark_b2=$(read_hash thgtoa-dark.pdf.b2)" >> $GITHUB_OUTPUT # ------------------------------------------------------------------ # - # VirusTotal — upload whichever PDFs are present + # VirusTotal # ------------------------------------------------------------------ # - name: 🦠 Upload PDFs to VirusTotal id: vt @@ -124,28 +98,34 @@ jobs: fi # ------------------------------------------------------------------ # - # Tag + Release — auto-increment vX.Y.Z from latest semver tag + # Validate explicit version input — refuse to auto-increment or + # overwrite an existing tag # ------------------------------------------------------------------ # - - name: 🏷️ Generate release tag + - name: 🏷️ Validate release tag id: tag run: | git fetch --tags --quiet + VERSION="${{ inputs.version }}" - LATEST=$(git tag --list 'v*' --sort=-version:refname \ - | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' \ - | head -1) - LATEST=${LATEST:-v0.0.0} + # Enforce vX.Y.Z format + if ! echo "$VERSION" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+$'; then + echo "::error::Version '$VERSION' is not valid semver. Use format: v1.2.3" + exit 1 + fi - MAJOR=$(echo "$LATEST" | cut -d. -f1 | tr -d 'v') - MINOR=$(echo "$LATEST" | cut -d. -f2) - PATCH=$(echo "$LATEST" | cut -d. -f3) - PATCH=$((PATCH + 1)) + # Refuse to overwrite an existing tag + if git tag --list | grep -qx "$VERSION"; then + echo "::error::Tag '$VERSION' already exists. Bump the version." + exit 1 + fi - TAG="v${MAJOR}.${MINOR}.${PATCH}" - echo "Previous tag: $LATEST → New tag: $TAG" - echo "tag=$TAG" >> $GITHUB_OUTPUT - echo "name=$TAG" >> $GITHUB_OUTPUT + echo "tag=$VERSION" >> $GITHUB_OUTPUT + echo "name=$VERSION" >> $GITHUB_OUTPUT + echo "Tag: $VERSION" + # ------------------------------------------------------------------ # + # Create GitHub Release + # ------------------------------------------------------------------ # - name: 🚀 Create GitHub Release uses: softprops/action-gh-release@v2 with: @@ -180,13 +160,13 @@ jobs: ### #️⃣ Hashes **thgtoa.pdf** (light) - ``` + ```text SHA-256 ${{ steps.hashes.outputs.light_sha256 }} BLAKE2b ${{ steps.hashes.outputs.light_b2 }} ``` **thgtoa-dark.pdf** (dark) - ``` + ```text SHA-256 ${{ steps.hashes.outputs.dark_sha256 }} BLAKE2b ${{ steps.hashes.outputs.dark_b2 }} ``` diff --git a/.github/workflows/sign.yml b/.github/workflows/sign.yml index c41ec2d..3d1fac0 100644 --- a/.github/workflows/sign.yml +++ b/.github/workflows/sign.yml @@ -4,10 +4,10 @@ name: 🔏 Sign PDFs # 1. Automatically after build.yml completes on main # 2. Manually, pointing at a specific build run to pull PDFs from on: - workflow_run: - workflows: ["📖 Build PDFs"] - types: [completed] - branches: [main] + # workflow_run: + # workflows: ["📖 Build PDFs"] + # types: [completed] + # branches: [main] workflow_dispatch: inputs: build_run_id: @@ -17,15 +17,15 @@ on: permissions: actions: read # download artifacts from other runs - contents: read + contents: write # needed to commit export/ files back to the repo jobs: sign: name: Hash & Sign PDFs # On workflow_run, only proceed if the build actually succeeded - if: > - github.event_name == 'workflow_dispatch' || - github.event.workflow_run.conclusion == 'success' + # if: > + # github.event_name == 'workflow_dispatch' || + # github.event.workflow_run.conclusion == 'success' runs-on: ubuntu-latest outputs: light_sha256: ${{ steps.hashes.outputs.light_sha256 }} @@ -39,20 +39,15 @@ jobs: with: sparse-checkout: pgp - - name: 🔑 Install GPG - run: | - sudo apt-get update -qq - sudo apt-get install -y gnupg - - # Download PDFs from the triggering build run, or a manually specified one + # Download PDFs from the manually specified run ID (required for manual dispatch) - name: 📥 Resolve source run ID id: src run: | - if [ -n "${{ inputs.build_run_id }}" ]; then - echo "run_id=${{ inputs.build_run_id }}" >> $GITHUB_OUTPUT - else - echo "run_id=${{ github.event.workflow_run.id }}" >> $GITHUB_OUTPUT + if [ -z "${{ inputs.build_run_id }}" ]; then + echo "::error::build_run_id is required — provide the build.yml run ID to pull PDFs from." + exit 1 fi + echo "run_id=${{ inputs.build_run_id }}" >> $GITHUB_OUTPUT - name: 📥 Download PDF artifacts uses: actions/download-artifact@v4 @@ -102,15 +97,19 @@ jobs: cat b2sums.txt # ------------------------------------------------------------------ # - # GPG sign + # GPG sign (maintainer-verifiable detached signatures for release) # ------------------------------------------------------------------ # + - name: 🔑 Install GPG + run: | + sudo apt-get update -qq + sudo apt-get install -y gnupg + - name: 🔏 Import GPG signing key env: GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} run: | echo "$GPG_PRIVATE_KEY" | gpg --batch --import - # Pre-cache passphrase to prevent interactive prompt echo "$GPG_PASSPHRASE" | gpg --batch --yes --passphrase-fd 0 \ --pinentry-mode loopback --list-secret-keys @@ -131,6 +130,41 @@ jobs: sign export/sha256sums.txt sign export/b2sums.txt + # ------------------------------------------------------------------ # + # Commit export/ back to main + # ------------------------------------------------------------------ # + - name: 📦 Checkout full repo for commit + uses: actions/checkout@v4 + with: + ref: main + fetch-depth: 0 + path: repo + + - name: 📂 Copy export files into repo + run: cp -v export/* repo/export/ + + - name: 🔏 Configure SSH commit signing + run: | + mkdir -p ~/.ssh + echo "${{ secrets.ACTIONS_SSH_SIGNING_KEY }}" > ~/.ssh/signing_key + chmod 600 ~/.ssh/signing_key + git config --global gpg.format ssh + git config --global user.signingKey ~/.ssh/signing_key + git config --global commit.gpgSign true + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + + - name: 📤 Commit and push export/ to main + working-directory: repo + run: | + git add export/ + if git diff --cached --quiet; then + echo "Nothing to commit — export/ is already up to date." + else + git commit -S -m "chore(export): update PDFs, hashes and signatures [skip ci]" + git push origin main + fi + # ------------------------------------------------------------------ # # Upload — PDFs + all signatures and hashes together # ------------------------------------------------------------------ # diff --git a/docs/changelog/index.md b/docs/changelog/index.md index d5c2c51..1b067e0 100644 --- a/docs/changelog/index.md +++ b/docs/changelog/index.md @@ -20,30 +20,6 @@ Notable changes to the guide and its tooling. Follows [Keep a Changelog](https:/ --- -## [v2026.5.25] - -!!! Note "Meta" - - - Released 2026-05-24 from [`3b55011`](https://github.com/Anon-Planet/thgtoa/commit/3b550119a8f70129096774b3303278b50fed1529) - -!!! Note "Added" - - - Explain missing v1.2.2 tag - -!!! Note "Changed" - - - Commitizen passes - - V1.2.3 - - Auto-increment using [vX.X.X] - - Only use "vX.X.X" in version tags - -!!! Note "Fixed" - - - We use the Anonymous Planet RSK for releases - - Sign using RSK instead - - Prevent history dump and filter noise commits - - Actually save per-page PDFs for qpdf, not PNGs - ## [v1.2.3] CI/CD pipeline split into independent stages, dark PDF quality improved, release signing automated, and the changelog now updates itself on every build. Skipping v1.2.2 which was a placeholder and contained broken Python unsuitable for a tag/release.