ci(pipeline): replace semver tagging with timestamp tags, drop tag_release.py

- release.yml now generates release-YYYYMMDD-<sha> tags automatically
- changelog.yml requires explicit version input, no auto-increment from tags
- sign.yml normalises extensions to .asc and .b2sum
- build-sign-release.yml neutered to a no-op with descriptive error
- tag_release.py archived to scripts/archived/
- update_changelog.py: version_from_changelog() is now primary version source
- .gitignore: fix export/ tracking to match actual file extensions
- docs/code/develop.md: fully rewritten to reflect new manual four-step flow
This commit is contained in:
nopeitsnothing
2026-05-27 23:26:49 -04:00
parent 91a77ed552
commit ede2a53437
10 changed files with 721 additions and 473 deletions
+17 -210
View File
@@ -1,218 +1,25 @@
name: 🗑️ DEPRECATED — Build & Sign & Release (combined)
# DEPRECATED — replaced by build.yml, sign.yml, and release.yml
# This file is kept temporarily so in-flight runs are not broken.
#
# name: 📖 Build & Sign PDFs
# This workflow is disabled. It is kept only as a reference until the
# split workflows have been confirmed stable in production.
# Do not trigger this workflow.
on:
workflow_dispatch: # manual only — no automatic triggers (deprecated)
permissions:
contents: write
id-token: write
workflow_dispatch:
inputs:
_disabled:
description: 'This workflow is deprecated. Use build.yml → sign.yml → release.yml instead.'
required: false
type: string
jobs:
build-sign-release:
name: Build, Sign & Release PDFs
noop:
name: Deprecated — no-op
runs-on: ubuntu-latest
steps:
- name: 🛠️ Checkout
uses: actions/checkout@v4
- name: 🐍 Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: 📦 Install Python dependencies
run: pip install mkdocs-material pillow numpy
- name: 🖼️ Install poppler (pdftoppm) and qpdf
- name: ❌ Workflow is deprecated
run: |
sudo apt-get update
sudo apt-get install -y poppler-utils qpdf
- name: Setup Chrome
uses: browser-actions/setup-chrome@v2
with:
chrome-version: 120
install-dependencies: true
install-chromedriver: true
- name: 🔑 Install GPG tools
run: sudo apt-get install -y gnupg
# ------------------------------------------------------------------ #
# Build PDFs
# ------------------------------------------------------------------ #
- name: 🖨️ Build PDFs
env:
CI: true
run: python scripts/build_guide_pdf.py --${{ inputs.build_mode || 'both' }}
# ------------------------------------------------------------------ #
# Hash (SHA-256 + BLAKE2b)
# ------------------------------------------------------------------ #
- name: #️⃣ Hash PDFs
id: hashes
run: |
mkdir -p export
sha256sum export/thgtoa.pdf | awk '{print $1}' > export/thgtoa.pdf.sha256
sha256sum export/thgtoa-dark.pdf | awk '{print $1}' > export/thgtoa-dark.pdf.sha256
b2sum export/thgtoa.pdf | awk '{print $1}' > export/thgtoa.pdf.b2
b2sum export/thgtoa-dark.pdf | awk '{print $1}' > export/thgtoa-dark.pdf.b2
# Also write combined human-readable files
sha256sum export/thgtoa.pdf export/thgtoa-dark.pdf > export/sha256sums.txt
b2sum export/thgtoa.pdf export/thgtoa-dark.pdf > export/b2sums.txt
# Expose hashes as step outputs for the release body
echo "light_sha256=$(cat export/thgtoa.pdf.sha256)" >> $GITHUB_OUTPUT
echo "dark_sha256=$(cat export/thgtoa-dark.pdf.sha256)" >> $GITHUB_OUTPUT
echo "light_b2=$(cat export/thgtoa.pdf.b2)" >> $GITHUB_OUTPUT
echo "dark_b2=$(cat export/thgtoa-dark.pdf.b2)" >> $GITHUB_OUTPUT
# ------------------------------------------------------------------ #
# GPG sign (detached .sig for each PDF + each hash file)
# ------------------------------------------------------------------ #
- name: 🔏 Import GPG key
env:
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
run: |
echo "$GPG_PRIVATE_KEY" | gpg --batch --import
# Pre-cache the passphrase so signing doesn't prompt
echo "$GPG_PASSPHRASE" | gpg --batch --yes --passphrase-fd 0 \
--pinentry-mode loopback --list-secret-keys
- name: 🔏 GPG sign PDFs and hash files
env:
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
run: |
sign() {
echo "$GPG_PASSPHRASE" | gpg --batch --yes --passphrase-fd 0 \
--pinentry-mode loopback \
--detach-sign --armor --output "${1}.sig" "$1"
}
sign export/thgtoa.pdf
sign export/thgtoa-dark.pdf
sign export/sha256sums.txt
sign export/b2sums.txt
# ------------------------------------------------------------------ #
# VirusTotal
# ------------------------------------------------------------------ #
- name: 🦠 Upload PDFs to VirusTotal
id: vt
uses: crazy-max/ghaction-virustotal@v5
with:
vt_api_key: ${{ secrets.VT_API_KEY }}
files: |
export/thgtoa.pdf
export/thgtoa-dark.pdf
- name: 🔗 Build VT report URLs
id: vt_urls
run: |
light_hash=$(cat export/thgtoa.pdf.sha256)
dark_hash=$(cat export/thgtoa-dark.pdf.sha256)
echo "light_vt=https://www.virustotal.com/gui/file/${light_hash}" >> $GITHUB_OUTPUT
echo "dark_vt=https://www.virustotal.com/gui/file/${dark_hash}" >> $GITHUB_OUTPUT
# ------------------------------------------------------------------ #
# Create GitHub Release
# ------------------------------------------------------------------ #
- name: 🏷️ Generate release tag
id: tag
run: |
TAG="release-$(date -u +'%Y%m%d-%H%M%S')"
echo "tag=$TAG" >> $GITHUB_OUTPUT
echo "name=Release $(date -u +'%Y-%m-%d %H:%M UTC')" >> $GITHUB_OUTPUT
- name: 🚀 Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.tag.outputs.tag }}
name: ${{ steps.tag.outputs.name }}
body: |
## 📖 The Hitchhiker's Guide to Online Anonymity
Built from commit ${{ github.sha }} on `${{ github.ref_name }}`.
---
### 📄 Files
| File | Description |
|------|-------------|
| `thgtoa.pdf` | Light mode PDF |
| `thgtoa-dark.pdf` | Dark mode PDF (hacker theme) |
| `sha256sums.txt` | SHA-256 checksums |
| `b2sums.txt` | BLAKE2b checksums |
| `*.sig` | GPG detached signatures (ASCII armor) |
---
### #️⃣ Hashes
#### thgtoa.pdf (Light)
```
SHA-256: ${{ steps.hashes.outputs.light_sha256 }}
BLAKE2b: ${{ steps.hashes.outputs.light_b2 }}
```
#### thgtoa-dark.pdf (Dark)
```
SHA-256: ${{ steps.hashes.outputs.dark_sha256 }}
BLAKE2b: ${{ steps.hashes.outputs.dark_b2 }}
```
---
### 🔏 GPG Signatures
Detached signatures (`.sig`) are included in the release assets.
Verify with:
```bash
gpg --verify thgtoa.pdf.sig thgtoa.pdf
gpg --verify thgtoa-dark.pdf.sig thgtoa-dark.pdf
```
The signing key is published at `pgp/anonymousplanet-release.asc`.
---
### 🦠 VirusTotal Scans
| File | Report |
|------|--------|
| `thgtoa.pdf` | ${{ steps.vt_urls.outputs.light_vt }} |
| `thgtoa-dark.pdf` | ${{ steps.vt_urls.outputs.dark_vt }} |
files: |
export/thgtoa.pdf
export/thgtoa-dark.pdf
export/sha256sums.txt
export/b2sums.txt
export/thgtoa.pdf.sha256
export/thgtoa-dark.pdf.sha256
export/thgtoa.pdf.b2
export/thgtoa-dark.pdf.b2
export/thgtoa.pdf.sig
export/thgtoa-dark.pdf.sig
export/sha256sums.txt.sig
export/b2sums.txt.sig
draft: false
prerelease: false
fail_on_unmatched_files: true
# ------------------------------------------------------------------ #
# Upload everything as a workflow artifact (90-day archive)
# ------------------------------------------------------------------ #
- name: 📤 Upload export as workflow artifact
uses: actions/upload-artifact@v4
with:
name: pdf-release-${{ steps.tag.outputs.tag }}
path: export/*
if-no-files-found: error
retention-days: 90
compression-level: 0
echo "This workflow is deprecated."
echo "Use build.yml → sign.yml → release.yml instead."
exit 1
+6 -6
View File
@@ -1,14 +1,14 @@
name: 📝 Update Changelog
# 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.
# Manual only — run after a release is published. Provide the exact version
# string (e.g. v1.2.4) to prepend to the changelog. Version is required to
# prevent silent auto-increment drift from release tags.
on:
workflow_dispatch:
inputs:
version:
description: 'Version string (e.g. v1.2.4) — leave blank to auto-increment'
required: false
description: 'Version string to record (e.g. v1.2.4) — required'
required: true
type: string
dry_run:
description: 'Dry run — print entry without committing'
@@ -40,7 +40,7 @@ jobs:
- name: 📝 Generate and prepend changelog entry
env:
DRY_RUN: ${{ inputs.dry_run || 'false' }}
MANUAL_VERSION: ${{ inputs.version || '' }}
MANUAL_VERSION: ${{ inputs.version }}
GH_SHA: ${{ github.sha }}
GH_REF: ${{ github.ref_name }}
TRIGGERING_SHA: ${{ github.event.workflow_run.head_sha || github.sha }}
+28 -42
View File
@@ -1,14 +1,12 @@
name: 🚀 Release
# 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.
# Provide the sign.yml run ID to pull artifacts from. The release tag is
# generated automatically as release-YYYYMMDD-<short-sha> — no version input
# needed, no semver drift possible.
on:
workflow_dispatch:
inputs:
version:
description: 'Release version tag (e.g. v1.2.4) — must not already exist'
required: true
type: string
sign_run_id:
description: 'sign.yml run ID to pull signatures and PDFs from'
required: true
@@ -29,7 +27,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 🛠️ Checkout (for tags and pgp/)
- name: 🛠️ Checkout (for pgp/)
uses: actions/checkout@v4
with:
fetch-depth: 0
@@ -66,8 +64,8 @@ jobs:
read_hash() { cat "release/$1" 2>/dev/null || echo "(not built)"; }
echo "light_sha256=$(read_hash thgtoa.pdf.sha256)" >> $GITHUB_OUTPUT
echo "dark_sha256=$(read_hash thgtoa-dark.pdf.sha256)" >> $GITHUB_OUTPUT
echo "light_b2=$(read_hash thgtoa.pdf.b2)" >> $GITHUB_OUTPUT
echo "dark_b2=$(read_hash thgtoa-dark.pdf.b2)" >> $GITHUB_OUTPUT
echo "light_b2=$(read_hash thgtoa.pdf.b2sum)" >> $GITHUB_OUTPUT
echo "dark_b2=$(read_hash thgtoa-dark.pdf.b2sum)" >> $GITHUB_OUTPUT
# ------------------------------------------------------------------ #
# VirusTotal
@@ -98,30 +96,18 @@ jobs:
fi
# ------------------------------------------------------------------ #
# Validate explicit version input — refuse to auto-increment or
# overwrite an existing tag
# Generate release tag — timestamp + short SHA, always unique
# ------------------------------------------------------------------ #
- name: 🏷️ Validate release tag
- name: 🏷️ Generate release tag
id: tag
run: |
git fetch --tags --quiet
VERSION="${{ inputs.version }}"
# 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
# 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
echo "tag=$VERSION" >> $GITHUB_OUTPUT
echo "name=$VERSION" >> $GITHUB_OUTPUT
echo "Tag: $VERSION"
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
DATE=$(date -u +'%Y%m%d')
TAG="release-${DATE}-${SHORT_SHA}"
NAME="Release ${DATE} (${SHORT_SHA})"
echo "tag=$TAG" >> $GITHUB_OUTPUT
echo "name=$NAME" >> $GITHUB_OUTPUT
echo "Tag: $TAG"
# ------------------------------------------------------------------ #
# Create GitHub Release
@@ -151,9 +137,9 @@ jobs:
| `b2sums.txt` | BLAKE2b checksums (both files) |
| `thgtoa.pdf.sha256` | SHA-256 — light PDF |
| `thgtoa-dark.pdf.sha256` | SHA-256 — dark PDF |
| `thgtoa.pdf.b2` | BLAKE2b — light PDF |
| `thgtoa-dark.pdf.b2` | BLAKE2b — dark PDF |
| `*.sig` | GPG detached signatures (ASCII armor) |
| `thgtoa.pdf.b2sum` | BLAKE2b — light PDF |
| `thgtoa-dark.pdf.b2sum` | BLAKE2b — dark PDF |
| `*.asc` | GPG detached signatures (ASCII armor) |
---
@@ -180,12 +166,12 @@ jobs:
gpg --import pgp/anonymousplanet-release.asc
# Verify PDFs
gpg --verify thgtoa.pdf.sig thgtoa.pdf
gpg --verify thgtoa-dark.pdf.sig thgtoa-dark.pdf
gpg --verify thgtoa.pdf.asc thgtoa.pdf
gpg --verify thgtoa-dark.pdf.asc thgtoa-dark.pdf
# Verify hash files
gpg --verify sha256sums.txt.sig sha256sums.txt
gpg --verify b2sums.txt.sig b2sums.txt
gpg --verify sha256sums.txt.asc sha256sums.txt
gpg --verify b2sums.txt.asc b2sums.txt
```
---
@@ -204,9 +190,9 @@ jobs:
release/b2sums.txt
release/thgtoa.pdf.sha256
release/thgtoa-dark.pdf.sha256
release/thgtoa.pdf.b2
release/thgtoa-dark.pdf.b2
release/thgtoa.pdf.sig
release/thgtoa-dark.pdf.sig
release/sha256sums.txt.sig
release/b2sums.txt.sig
release/thgtoa.pdf.b2sum
release/thgtoa-dark.pdf.b2sum
release/thgtoa.pdf.asc
release/thgtoa-dark.pdf.asc
release/sha256sums.txt.asc
release/b2sums.txt.asc
+21 -32
View File
@@ -11,8 +11,8 @@ on:
workflow_dispatch:
inputs:
build_run_id:
description: 'build.yml run ID to download PDFs from (leave blank for latest)'
required: false
description: 'build.yml run ID to download PDFs from'
required: true
type: string
permissions:
@@ -39,31 +39,21 @@ jobs:
with:
sparse-checkout: pgp
# Download PDFs from the manually specified run ID (required for manual dispatch)
- name: 📥 Resolve source run ID
id: src
run: |
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
with:
name: pdfs
path: export/
run-id: ${{ steps.src.outputs.run_id }}
name: pdfs
path: export/
run-id: ${{ inputs.build_run_id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: 📋 List downloaded files
run: ls -lh export/
# ------------------------------------------------------------------ #
# Hash
# Hash — extensions match export/ conventions: .sha256, .b2sum
# ------------------------------------------------------------------ #
- name: #️⃣ Hash PDFs
- name: "#️⃣ Hash PDFs"
id: hashes
run: |
cd export
@@ -71,20 +61,19 @@ jobs:
for f in thgtoa.pdf thgtoa-dark.pdf; do
[ -f "$f" ] || continue
sha256sum "$f" | awk '{print $1}' > "${f}.sha256"
b2sum "$f" | awk '{print $1}' > "${f}.b2"
b2sum "$f" | awk '{print $1}' > "${f}.b2sum"
done
# Combined files (only include files that exist)
# Combined summary files
sha256sum thgtoa.pdf thgtoa-dark.pdf 2>/dev/null > sha256sums.txt || \
sha256sum thgtoa.pdf 2>/dev/null > sha256sums.txt
b2sum thgtoa.pdf thgtoa-dark.pdf 2>/dev/null > b2sums.txt || \
b2sum thgtoa.pdf 2>/dev/null > b2sums.txt
# Expose individual hashes as outputs (empty string if file absent)
light_sha256=$(cat thgtoa.pdf.sha256 2>/dev/null || echo "")
dark_sha256=$(cat thgtoa-dark.pdf.sha256 2>/dev/null || echo "")
light_b2=$(cat thgtoa.pdf.b2 2>/dev/null || echo "")
dark_b2=$(cat thgtoa-dark.pdf.b2 2>/dev/null || echo "")
light_b2=$(cat thgtoa.pdf.b2sum 2>/dev/null || echo "")
dark_b2=$(cat thgtoa-dark.pdf.b2sum 2>/dev/null || echo "")
echo "light_sha256=$light_sha256" >> $GITHUB_OUTPUT
echo "dark_sha256=$dark_sha256" >> $GITHUB_OUTPUT
@@ -97,7 +86,7 @@ jobs:
cat b2sums.txt
# ------------------------------------------------------------------ #
# GPG sign (maintainer-verifiable detached signatures for release)
# GPG sign — detached ASCII-armor signatures use .asc extension
# ------------------------------------------------------------------ #
- name: 🔑 Install GPG
run: |
@@ -122,8 +111,8 @@ jobs:
[ -f "$file" ] || return 0
echo "$GPG_PASSPHRASE" | gpg --batch --yes --passphrase-fd 0 \
--pinentry-mode loopback \
--detach-sign --armor --output "${file}.sig" "$file"
echo "Signed: $file"
--detach-sign --armor --output "${file}.asc" "$file"
echo "Signed: $file → ${file}.asc"
}
sign export/thgtoa.pdf
sign export/thgtoa-dark.pdf
@@ -166,7 +155,7 @@ jobs:
fi
# ------------------------------------------------------------------ #
# Upload — PDFs + all signatures and hashes together
# Upload artifacts for release.yml to consume
# ------------------------------------------------------------------ #
- name: 📤 Upload signatures artifact
uses: actions/upload-artifact@v4
@@ -177,12 +166,12 @@ jobs:
export/b2sums.txt
export/thgtoa.pdf.sha256
export/thgtoa-dark.pdf.sha256
export/thgtoa.pdf.b2
export/thgtoa-dark.pdf.b2
export/thgtoa.pdf.sig
export/thgtoa-dark.pdf.sig
export/sha256sums.txt.sig
export/b2sums.txt.sig
export/thgtoa.pdf.b2sum
export/thgtoa-dark.pdf.b2sum
export/thgtoa.pdf.asc
export/thgtoa-dark.pdf.asc
export/sha256sums.txt.asc
export/b2sums.txt.asc
if-no-files-found: error
retention-days: 90
compression-level: 0