Moving some things around

Signed-off-by: nopeitsnothing <no@anonymousplanet.org>
This commit is contained in:
nopeitsnothing
2026-04-20 02:45:06 -04:00
parent 28556c016c
commit 904fa24478
17 changed files with 397 additions and 641 deletions
+9 -50
View File
@@ -6,7 +6,7 @@ on:
build_mode:
description: 'PDF build mode'
required: true
default: 'both'
default: 'light'
type: choice
options:
- light
@@ -58,56 +58,18 @@ jobs:
CI: true
run: python scripts/build_guide_pdf.py --${{ inputs.build_mode || 'both' }}
- name: 🔒 Sign SHA256 hash file with GPG
env:
GPG_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
- name: 🛡️ Sign PDFs
run: |
cd ${{ github.workspace }}
# Import GPG key
export GPG_TTY=$(tty)
echo "$GPG_KEY" | gpg --batch --import 2>/dev/null || true
- name: 🔒 Sign PDF files with GPG
env:
GPG_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
run: |
cd ${{ github.workspace }}
# Import GPG key if not already imported
export GPG_TTY=$(tty)
echo "$GPG_KEY" | gpg --batch --import 2>/dev/null || true
# Create combined hash file with all PDFs
sha256sum export/thgtoa.pdf > export/checksums.sha256
sha256sum export/thgtoa-dark.pdf >> export/checksums.sha256
# Sign the checksum file
gpg --batch --yes --armor --detach-sign --output export/checksums.sha256.sig export/checksums.sha256 2>/dev/null || true
# Sign each PDF file individually with detached signature
for pdf_file in export/*.pdf; do
if [ -f "$pdf_file" ]; then
base_name=$(basename "$pdf_file")
echo "Signing $base_name..."
gpg --default-key 17ECA05F768DEDF6 --batch --yes --armor --detach-sign --output "export/${pdf_file}.sig" "$pdf_file" 2>/dev/null || true
fi
done
# Verify signatures were created
ls -la export/*.sig 2>/dev/null || echo "No signature files found in export/"
chmod +x scripts/sign-pdfs.sh
./scripts/sign-pdfs.sh
- name: 🦠 Upload PDFs to VirusTotal
uses: crazy-max/ghaction-virustotal@v5
with:
vt_api_key: ${{ secrets.VT_API_KEY }}
files: |
./export/thgtoa.pdf
./export/thgtoa-dark.pdf
export/thgtoa.pdf
export/thgtoa-dark.pdf
- name: 📊 Extract VT scan results
id: vt-scan
@@ -125,7 +87,7 @@ jobs:
### thgtoa.pdf (Light Mode)
- **VT Report:** https://www.virustotal.com/gui/file/\$(sha256sum export/thgtoa.pdf | cut -d' ' -f1)
### thgtoa-dark.pdf (Dark Mode)
### thgtoa-dark.pdf (Dark Mode) (currently broken)
- **VT Report:** https://www.virustotal.com/gui/file/\$(sha256sum export/thgtoa-dark.pdf | cut -d' ' -f1)
---
@@ -135,12 +97,9 @@ jobs:
- name: 📤 Upload export directory as artifact
uses: actions/upload-artifact@v4
with:
name: pdf-export-${{ inputs.build_mode || 'both' }}
name: export
path: |
export/*.pdf
export/*.sig
export/*.sha256
export/virus-total-results.md
export/*
if-no-files-found: error
retention-days: 90
compression-level: 0
+21 -4
View File
@@ -1,20 +1,34 @@
---
title: "TBA"
description: ""
schema:
"@context": https://schema.org
"@type": Organization
"@id": https://www.anonymousplanet.org/
name: Anonymous Planet
url: https://www.anonymousplanet.org/authors/
logo: ../media/favicon.png
sameAs:
- https://github.com/Anon-Planet
- https://opencollective.com/anonymousplanetorg
- https://mastodon.social/@anonymousplanet
---
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- This changelog page
- Add ways to verify the files
### Changed
- Refactored GitHub Actions workflow **Build PDF** (`scripts\build_guide_pdf.py`): now builds both light and dark mode PDFs (`export/thgtoa.pdf` and `export/thgtoa-dark.pdf` respectively).
- Restored previous VT scans workflow **VirusTotal Scan** (`.github/workflows/vt-scan.yml`): submit files to VT for malware scanning. Links will be published on the site.
- Restored previous VT scans
## Fixed
@@ -49,3 +63,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[Unreleased]: https://github.com/Anon-Planet/thgtoa/compare/v1.2.1...HEAD
[1.2.1]: https://github.com/Anon-Planet/thgtoa/releases/tag/v1.2.1
***The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),***
***and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).***
+17 -161
View File
@@ -1,81 +1,35 @@
# Development
## Overview
This repository now includes an automated workflow that handles PDF generation, verification, and distribution with the following features:
??? Note "How the pipeline works"
1. **Automatic PDF Generation** - Builds both light and dark mode PDFs from MkDocs source
2. **SHA256 Hash Generation** - Creates hash files for integrity verification
3. **GPG Signature Signing** - Signs all PDFs and hash files with repository GPG key
4. **VirusTotal Scanning** - Automatically scans PDFs and updates release notes
5. **Release Automation** - Packages everything into GitHub releases
**Automatic PDF Generation:** - Builds both light and dark mode PDFs from MkDocs source
**SHA256 Hash Generation:** - Creates hash files for integrity verification
**GPG Signature Signing:** - Signs all PDFs and hash files with repository GPG key
**VirusTotal Scanning:** - Automatically scans PDFs and updates release notes
**Release Automation:** - Packages everything into GitHub releases
## Workflow Architecture
### 1. Build PDF Workflow (`build-pdf.yml`)
**Trigger:** Push to main, pull requests, or manual dispatch
### Build PDF Workflow (`build-sign-release.yml`)
??? Note "Steps"
- Checkout repository
- Set up Python 3.13 and MkDocs Material
- Set up Python and MkDocs Material
- Install Chromium browser
- Generate both light and dark mode PDFs
- Create SHA256 hash files
- Sign all files with GPG
- Upload artifacts to GitHub Actions
- Publish release
- Generate both light and dark mode PDFs with `scripts\build_guide_pdf.py`
- Create SHA256 and blake2 hash files in `export/`
- Sign all files with GPG in `export/`
- Upload artifacts to GitHub Actions **manually**
### 2. VirusTotal Scan Workflow (`vt-scan.yml`)
### SHA256 Hash Verification
**Trigger:** Push to main, tags, or manual dispatch (runs after build-pdf)
!!! Note "**How it works**"
??? Note "Steps"
- Download PDF artifacts from build workflow
- Scan both PDFs with VirusTotal API
- Extract scan results and generate report links
- Update release notes with VT scan status and URLs
## File Structure
After a successful build, the repository will contain:
```
.../
├── export/
│ ├── thgtoa.pdf # Light mode PDF
│ ├── thgtoa-dark.pdf # Dark mode PDF
│ ├── thgtoa.pdf.sig # GPG signature (light)
│ └── thgtoa-dark.pdf.sig # GPG signature (dark)
├── thgtoa.pdf.sha256 # Hash file (light)
├── thgtoa-dark.pdf.sha256 # Hash file (dark)
├── sha256sum-light.txt # Combined hash file
└── scripts/
├── build_guide_pdf.py # PDF generation script
└── verify_pdf.py # Verification utility
```
## Security Features
### 1. SHA256 Hash Verification
**Purpose:** Ensure file integrity during download/transit
**How it works:**
- Each PDF gets a unique SHA256 hash calculated at build time
- Hash stored in `.sha256` files alongside the PDFs
- Combined `sha256sum-light.txt` for batch verification
**Verification command:**
```bash
sha256sum -c sha256sum-light.txt
```
### 2. GPG Signature Verification
- Each PDF gets a unique SHA256 hash calculated at build time
- Hash stored in `.sha256` files alongside the PDFs
- Combined `sha256sum.txt` for batch verification
### GPG Signature Verification
**Purpose:** Verify authenticity and prevent tampering
@@ -90,104 +44,6 @@ gpg --import pgp/anonymousplanet-master.asc
gpg --verify export/thgtoa.pdf.sig export/thgtoa.pdf
```
### 3. VirusTotal Integration
**Purpose:** Malware detection and security scanning
??? Note "How it works"
- Automatic scan of all generated PDFs
- Results published in release notes with direct links
- Provides third-party validation of file safety
## Usage Examples
### Local Development
```bash
# Build PDFs locally
python scripts/build_guide_pdf.py --both
# Verify hashes
python scripts/verify_pdf.py --hashes
# Verify signatures (requires GPG installed)
python scripts/verify_pdf.py --signatures
# Full verification with VirusTotal check
export VT_API_KEY=your_api_key
python scripts/verify_pdf.py --all
```
### CI/CD Verification
The workflows automatically verify everything during the build process. To manually trigger:
1. Go to Actions tab
2. Select "Build guide PDF" or "VirusTotal Scan"
3. Click "Run workflow"
4. Download artifacts from successful run
## Release Process
When you create a tag (e.g., `v1.0.0`):
1. Push the tag: `git push origin v1.0.0`
2. Build PDF workflow triggers automatically
3. VirusTotal scan workflow runs after build completes
4. Both workflows update/create GitHub release with:
- Light and dark mode PDFs
- GPG signatures for all files
- Hash files for verification
- Release notes with VT scan results
## Troubleshooting
### Common Issues
**GPG signing fails:**
- Check that `GPG_PRIVATE_KEY` is in ASCII armor format
- Verify passphrase is correct
- Ensure key has signing capability
**Hash mismatch after download:**
- Re-download the file (corruption during transfer)
- Verify you're using the correct hash file
- Check disk integrity
**VirusTotal scan fails:**
- Verify `VT_API_KEY` is set correctly
- Check API quota limits (free tier: 4 requests/minute)
- Ensure PDF files exist before scanning
### Debug Mode
Enable verbose output by adding to workflow:
```yaml
- name: Debug
run: |
echo "Current directory:" && pwd
echo "Files in export:" && ls -la export/
echo "Hash file contents:" && cat sha256sum-light.txt
```
## Best Practices
1. **Always verify signatures** before opening PDFs from untrusted sources
2. **Check VirusTotal results** for any suspicious detections
3. **Keep GPG keys secure** - never commit private keys to repository
4. **Monitor API usage** for VirusTotal to avoid rate limiting
5. **Test locally** before pushing tags to production
## Future Enhancements
Potential improvements:
- Multi-signature support (multiple maintainers)
- Automated changelog generation with hashes
- Cross-platform signature verification scripts
- Integration with additional malware scanners
- Automatic mirror updates with verified files
---
*This workflow is designed for security-conscious users who need to verify the authenticity and integrity of downloaded documents.*
+3 -4
View File
@@ -18,13 +18,12 @@ schema:
**9FA5 436D 0EE3 6098 5157 3825 17EC A05F 768D EDF6**
This is the master signing key fingerprint for Anonymous Planet.
You'll use it to [**verify the checksum** and **GPG signature** of all files for authenticity.](verify/index.md)
Please share this project if you enjoy it and you think it might be useful to others.
You'll use it to [**verify the checksum** and **GPG signature** of all files for authenticity.](verify/index.md)
Please share this project if you enjoy it and you think it might be useful to others.
![Anonymous Planet logo](media/profile.png){ align=right }
Anonymous Planet is a collective of volunteers and contributors. No one person is considered more valuable than another, and no one person should be viewed as having "more impact" on Anonymous Planet.
Anonymous Planet is a collective of volunteers.
??? person "Das Kolburn"
+7 -4
View File
@@ -62,9 +62,12 @@ gpg --verify export/thgtoa-dark.pdf.sig export/thgtoa-dark.pdf
Expected output for successful verification:
```
gpg: Signature made [date]
gpg: using RSA key [key-id]
gpg: Good signature from "[owner]"
gpg: Signature made Mon 20 Apr 2026 01:46:40 AM EDT
gpg: using EDDSA key 9FA5436D0EE360985157382517ECA05F768DEDF6
gpg: Good signature from "Anonymous Planet Master Signing Key" [unknown]
gpg: WARNING: This key is not certified with a trusted signature!
gpg: There is no indication that the signature belongs to the owner.
Primary key fingerprint: 9FA5 436D 0EE3 6098 5157 3825 17EC A05F 768D EDF6
```
#### 3. Check VirusTotal Status
@@ -114,7 +117,7 @@ The GitHub Actions workflows automatically:
## Key Information
**Signing Key:** Anonymous Planet Master Key
**Signing Key:** Anonymous Planet Master Signing Key ("MSK")
**Key ID:** See `pgp/anonymousplanet-master.asc` for details
**Fingerprint:** Verify from the repository's official documentation
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
f212d0425b38d5cd10da6dc804b60f143da23d4b07051aae31d0966082519b300af0e1c423683e0223738b33b138c687232b1c8bd68cf643777bbc5b588152bd ./export/thgtoa-dark.pdf
BIN
View File
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
436ed0df78c299f95b8d5ff94f43f26ec2e7825d92d843fc15419630d55ed5e0c98485e738c12715a2b6242633faae38e8a98935b361d44ddde97a1692cb01a1 ./export/thgtoa.pdf
+16
View File
@@ -0,0 +1,16 @@
## VirusTotal Scan Results
**Scan Date:** 2026-04-19 01:48 UTC
---
### thgtoa.pdf
- **SHA256 Hash:** `f82f6f53319315568fc2524b4eaf01126fe52356a20363cd358ad5977388ba28`
- **VirusTotal Report:** VT_API_KEY not configured, scan skipped
### thgtoa-dark.pdf
- **SHA256 Hash:** `94a0c8e3b81b0aeeb921029a41713d81b836da893a9bc9f905ca7296e82bd70f`
- **VirusTotal Report:** VT_API_KEY not configured, scan skipped
---
*Scan performed automatically by GitHub Actions*
+1
View File
@@ -133,6 +133,7 @@ nav:
- Constitution: constitution/index.md
- Mirrors: mirrors/index.md
- Twitter: twitter/index.md
- TBA: changelog/index.md
copyright: |
&copy; 2023-2026 <a href="https://anonymousplanet.org/" target="_blank" rel="noopener">Anonymous Planet</a>
+132
View File
@@ -0,0 +1,132 @@
# PDF Build, Scan & Release Scripts
This directory contains scripts for building PDFs from MkDocs documentation, scanning them with VirusTotal, generating hashes, and uploading artifacts to GitHub releases.
## Scripts
### `pdf_release.sh` (v2 - Recommended)
The main script that handles:
- SHA256 hash generation for PDF files
- VirusTotal scanning of PDFs
- Release creation/update on GitHub
- GPG signature verification support
**Usage:**
```bash
./scripts/pdf_release.sh --build <light|dark|both> --release <tag|latest> [--vt-api-key VT_KEY] [--github-token TOKEN]
```
**Options:**
- `--build`: PDF build mode (`light`, `dark`, or `both`) - Required
- `--release`: Release update mode (`tag` for tagged releases, `latest` to always update) - Default: `tag`
- `--vt-api-key`: VirusTotal API key (optional)
- `--github-token`: GitHub token for release operations (optional)
### `build_guide_pdf.py`
Python script that builds MkDocs documentation and converts it to PDF using Chromium/Chrome.
**Usage:**
```bash
python scripts/build_guide_pdf.py --both # Build both light and dark mode
python scripts/build_guide_pdf.py --dark-mode # Dark mode only
python scripts/build_guide_pdf.py --skip-mkdocs # Skip MkDocs build, use existing site
```
## GitHub Actions Workflow
The workflow `.github/workflows/build-pdf-combined.yml` combines all operations:
1. **Build PDFs** - Generates light/dark mode PDFs with GPG signatures
2. **Scan & Release** - Scans with VirusTotal and updates/releases artifacts
### Required Secrets
Add these to your repository settings under **Settings > Secrets and variables > Actions**:
- `GPG_PRIVATE_KEY`: Your GPG private key for signing
- `GPG_PASSPHRASE`: Passphrase for the GPG key (if any)
- `VT_API_KEY`: VirusTotal API key for malware scanning
- `GITHUB_TOKEN`: Automatically available, but can be manually added
### Workflow Triggers
The workflow runs on:
- Manual dispatch (`workflow_dispatch`) with customizable options
- Push to main branch when docs, mkdocs.yml, or scripts change
## Output Files
After running the build and release process, you'll get:
```
export/
├── thgtoa.pdf # Light mode PDF
├── thgtoa-dark.pdf # Dark mode PDF
├── thgtoa.pdf.sig # GPG signature for light PDF
├── thgtoa-dark.pdf.sig # GPG signature for dark PDF
├── thgtoa.pdf.sha256 # SHA256 hash for light PDF
├── thgtoa-dark.pdf.sha256 # SHA256 hash for dark PDF
├── sha256sum-combined.txt # Combined hash file
├── sha256sum-combined.txt.sig # GPG signature for combined hashes
└── virus-total-results.md # VirusTotal scan results
```
## Hash Verification
To verify the integrity of downloaded PDFs:
```bash
# Verify against individual hash file
sha256sum -c thgtoa.pdf.sha256
# Or verify against combined hash file
sha256sum -c sha256sum-combined.txt
```
## VirusTotal Integration
When a `VT_API_KEY` is provided, the script will:
1. Upload each PDF to VirusTotal's API
2. Generate individual scan reports
3. Include VT report links in release notes and artifacts
The VT results file (`virus-total-results.md`) contains:
- Scan timestamp
- SHA256 hashes for each PDF
- Direct links to VirusTotal GUI reports
## Release Management
The script supports two release modes:
1. **Tag mode** (`--release tag`): Updates the release matching the current git tag
2. **Latest mode** (`--release latest`): Always updates the most recent release (useful for continuous deployment)
When running in a GitHub Actions workflow with a tag push, it will automatically create or update the corresponding release.
## Troubleshooting
### PDF Build Fails
- Ensure Chrome/Chromium is installed: `sudo apt install chromium-browser`
- Check MkDocs configuration is valid: `mkdocs build --strict`
- Verify all documentation files are present and properly formatted
### VirusTotal Scan Fails
- Check VT_API_KEY secret is correctly set in repository settings
- Verify the API key has sufficient quota (free tier allows 4 requests/minute)
- Check network connectivity to VirusTotal API
### Release Upload Fails
- Ensure GITHUB_TOKEN has appropriate permissions (repo scope)
- For existing releases, use `--release latest` instead of `tag`
- Check that the release tag format matches GitHub's requirements (e.g., `v1.0.0`)
## Security Notes
- **GPG Keys**: Never commit private keys to version control. Use GitHub Secrets.
- **VT API Key**: Keep your VirusTotal API key secret and rotate periodically.
- **Release Artifacts**: All uploaded artifacts are publicly visible on your releases page.
## License
These scripts are part of the "The How-To Guide To Anonymity" project and follow the same licensing as the main repository.
+2 -2
View File
@@ -177,13 +177,13 @@ def main() -> int:
"--pdf-light",
type=Path,
default=root / "export" / "thgtoa.pdf",
help="Output PDF path for light mode (default: ./export/guide.pdf)",
help="Output PDF path for light mode (default: ./export/thgtoa.pdf)",
)
ap.add_argument(
"--pdf-dark",
type=Path,
default=root / "export" / "thgtoa-dark.pdf",
help="Output PDF path for dark mode (default: ./export/guide-dark.pdf)",
help="Output PDF path for dark mode (default: ./export/thgtoa-dark.pdf)",
)
ap.add_argument("--skip-mkdocs", action="store_true", help="Reuse existing site dir; only run print-to-pdf.")
ap.add_argument("--dark-mode", action="store_true", help="Generate dark mode PDF only")
-365
View File
@@ -1,365 +0,0 @@
!/bin/bash
set -e
# PDF Hashing, Scanning, Release and Management script (hSCRAM)
# Usage: ./pdf_release.sh --build <light|dark|both> --release <tag|latest> [--vt-api-key]
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
EXPORT_DIR="$ROOT_DIR/export"
# Default values
MODE="both" # light, dark, or both
RELEASE_MODE="tag" # tag (e.g. "v2.1.2") or "latest"
VT_API_KEY=""
GITHUB_TOKEN=""
while [[ $# -gt 0 ]]; do
case $1 in
--build)
MODE="$2"
shift 2
;;
--release)
RELEASE_MODE="$2"
shift 2
;;
--vt-api-key)
VT_API_KEY="$2"
shift 2
;;
--github-token)
GITHUB_TOKEN="$2"
shift 2
;;
*)
echo "Unknown option: $1"
exit 1
;;
esac
done
if [[ ! "$MODE" =~ ^(light|dark|both)$ ]]; then
echo "Error: Invalid build mode '$MODE'. Must be 'light', 'dark', or 'both'."
exit 1
fi
echo "Hashing, Scanning, Release and Management script (hSCRAM)"
echo "Mode: $MODE"
echo "Release Mode: $RELEASE_MODE"
generate_hash() {
local file="$1"
sha256sum "$file" | cut -d' ' -f1
}
create_hash_file() {
local pdf_path="$1"
local base_name=$(basename "$pdf_path")
local hash_file="${EXPORT_DIR}/${base_name}.sha256"
(cd "$EXPORT_DIR" && sha256sum "$base_name") > "$hash_file"
echo "Created: $hash_file"
}
scan_with_virustotal() {
local pdf_path="$1"
local base_name=$(basename "$pdf_path")
if [[ -z "$VT_API_KEY" ]]; then
echo "Warning: VT_API_KEY not provided, skipping VirusTotal scan for $base_name"
return 1
fi
echo "Scanning $base_name with VirusTotal..."
local upload_response=$(curl -s -X POST \
-H "x-apikey: $VT_API_KEY" \
-F "file=@$pdf_path" \
https://www.virustotal.com/api/v3/files)
if [[ $? -ne 0 ]]; then
echo "Error uploading $base_name to VirusTotal"
return 1
fi
local file_id=$(echo "$upload_response" | python3 -c "import sys, json; print(json.load(sys.stdin)['data']['id'])" 2>/dev/null || echo "")
if [[ -z "$file_id" ]]; then
echo "Error: Could not extract file ID from VirusTotal response for $base_name"
echo "Response: $upload_response"
return 1
fi
local vt_url="https://www.virustotal.com/gui/file/$file_id"
echo "$vt_url"
}
scan_all_pdfs() {
local results_file="$EXPORT_DIR/virus-total-results.md"
cat > "$results_file" << 'HEADER'
## VirusTotal Scan Results
**Scan Date:** TIMESTAMP
---
HEADER
sed -i "s/TIMESTAMP/$(date -u +"%Y-%m-%d %H:%M UTC")/" "$results_file"
local pdf_files=()
if [[ "$MODE" == "light" || "$MODE" == "both" ]]; then
pdf_files+=("$EXPORT_DIR/thgtoa.pdf")
fi
if [[ "$MODE" == "dark" || "$MODE" == "both" ]]; then
pdf_files+=("$EXPORT_DIR/thgtoa-dark.pdf")
fi
for pdf in "${pdf_files[@]}"; do
if [[ -f "$pdf" ]]; then
local base_name=$(basename "$pdf")
local hash=$(generate_hash "$pdf")
echo "" >> "$results_file"
echo "### $base_name" >> "$results_file"
echo "- **SHA256 Hash:** \`$hash\`" >> "$results_file"
if [[ -n "$VT_API_KEY" ]]; then
local vt_url=$(scan_with_virustotal "$pdf")
if [[ $? -eq 0 && -n "$vt_url" ]]; then
echo "- **VirusTotal Report:** [$vt_url]($vt_url)" >> "$results_file"
else
echo "- **VirusTotal Report:** Scan failed or API key not provided" >> "$results_file"
fi
else
echo "- **VirusTotal Report:** VT_API_KEY not configured, scan skipped" >> "$results_file"
fi
create_hash_file "$pdf"
else
echo "Warning: $pdf does not exist, skipping..."
fi
done
cat >> "$results_file" << 'FOOTER'
---
*Scan performed automatically by GitHub Actions*
FOOTER
echo "VirusTotal results saved to: $results_file"
}
update_release() {
local tag="${1:-}"
local release_notes="$EXPORT_DIR/release-notes.md"
if [[ "$RELEASE_MODE" == "tag" && -z "$tag" ]]; then
tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
fi
if [[ -z "$tag" ]]; then
echo "Warning: No release tag found, skipping release update."
return 1
fi
echo "Updating release for tag: $tag"
cat > "$release_notes" << EOF
# Release Notes - $tag
**Release Date:** $(date -u +"%Y-%m-%d %H:%M UTC")
## PDF Files
EOF
if [[ "$MODE" == "light" || "$MODE" == "both" ]]; then
local hash=$(generate_hash "$EXPORT_DIR/thgtoa.pdf")
echo "- **thgtoa.pdf (Light Mode)**" >> "$release_notes"
echo " - SHA256: \`$hash\`" >> "$release_notes"
if [[ -f "$EXPORT_DIR/thgtoa.pdf.sig" ]]; then
echo " - Signature: \`thgtoa.pdf.sig\` (GPG signed)" >> "$release_notes"
fi
fi
if [[ "$MODE" == "dark" || "$MODE" == "both" ]]; then
local hash=$(generate_hash "$EXPORT_DIR/thgtoa-dark.pdf")
echo "- **thgtoa-dark.pdf (Dark Mode)**" >> "$release_notes"
echo " - SHA256: \`$hash\`" >> "$release_notes"
if [[ -f "$EXPORT_DIR/thgtoa-dark.pdf.sig" ]]; then
echo " - Signature: \`thgtoa-dark.pdf.sig\` (GPG signed)" >> "$release_notes"
fi
fi
echo "" >> "$release_notes"
echo "---" >> "$release_notes"
if [[ -f "$EXPORT_DIR/virus-total-results.md" ]]; then
echo "## VirusTotal Scan Results" >> "$release_notes"
echo "" >> "$release_notes"
cat "$EXPORT_DIR/virus-total-results.md" >> "$release_notes"
echo "" >> "$release_notes"
fi
local files_to_upload=""
if [[ -f "$EXPORT_DIR/thgtoa.pdf" ]]; then
files_to_upload+="$EXPORT_DIR/thgtoa.pdf "
fi
if [[ -f "$EXPORT_DIR/thgtoa-dark.pdf" ]]; then
files_to_upload+="$EXPORT_DIR/thgtoa-dark.pdf "
fi
if [[ -f "$EXPORT_DIR/thgtoa.pdf.sig" ]]; then
files_to_upload+="$EXPORT_DIR/thgtoa.pdf.sig "
fi
if [[ -f "$EXPORT_DIR/thgtoa-dark.pdf.sig" ]]; then
files_to_upload+="$EXPORT_DIR/thgtoa-dark.pdf.sig "
fi
local combined_hash_file="$EXPORT_DIR/sha256sum-combined.txt"
if [[ -f "$EXPORT_DIR/thgtoa.pdf.sha256" ]]; then
cat "$EXPORT_DIR/thgtoa.pdf.sha256" >> "$combined_hash_file" 2>/dev/null || true
fi
if [[ -f "$EXPORT_DIR/thgtoa-dark.pdf.sha256" ]]; then
echo "" >> "$combined_hash_file"
cat "$EXPORT_DIR/thgtoa-dark.pdf.sha256" >> "$combined_hash_file"
fi
files_to_upload+="$combined_hash_file "
if [[ -n "${GPG_PRIVATE_KEY:-}" && -n "${GPG_PASSPHRASE:-}" ]]; then
echo "$GPG_PRIVATE_KEY" | gpg --batch --import 2>/dev/null || true
gpg --batch --yes --armor --detach-sign --output "$combined_hash_file.sig" "$combined_hash_file" 2>/dev/null || true
if [[ -f "$combined_hash_file.sig" ]]; then
files_to_upload+="$combined_hash_file.sig "
fi
fi
if command -v gh &> /dev/null && [[ -n "$GITHUB_TOKEN" ]]; then
echo "Uploading release with GitHub CLI..."
local release_exists=$(gh release view "$tag" 2>/dev/null && echo "yes" || echo "no")
if [[ "$release_exists" == "yes" ]]; then
gh release edit "$tag" --notes-file "$release_notes" 2>/dev/null || {
echo "Warning: Failed to update release notes"
}
for file in $files_to_upload; do
if [[ -f "$file" ]]; then
local file_name=$(basename "$file")
gh release upload "$tag" "$file" 2>/dev/null || {
echo "Warning: Failed to upload $file_name"
}
fi
done
else
gh release create "$tag" \
--title "Release $tag" \
--notes-file "$release_notes" \
$files_to_upload 2>/dev/null || {
echo "Error: Failed to create release"
return 1
}
fi
else
if [[ -n "$GITHUB_TOKEN" && -n "$tag" ]]; then
echo "Using GitHub API to upload release..."
local repo="${GITHUB_REPOSITORY:-}"
local api_url="https://api.github.com/repos/$repo/releases"
local existing_release=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
"$api_url/tags/$tag")
if [[ $(echo "$existing_release" | grep -c '"id":') -gt 0 ]]; then
echo "Release already exists, updating..."
local release_id=$(echo "$existing_release" | python3 -c "import sys, json; print(json.load(sys.stdin)['id'])" 2>/dev/null || echo "")
curl -X PATCH \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
"$api_url/$release_id" \
-d "{\"body\":\"$(cat "$release_notes")\"}" 2>/dev/null || true
for file in $files_to_upload; do
if [[ -f "$file" ]]; then
local file_name=$(basename "$file")
local mime_type=$(file --mime-type -b "$file")
curl -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Content-Type: $mime_type" \
--data-binary @"$file" \
"https://uploads.github.com/repos/$repo/releases/$release_id/assets?name=$file_name" 2>/dev/null || {
echo "Warning: Failed to upload $file_name"
}
fi
done
else
echo "Creating new release..."
local create_response=$(curl -s -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
"$api_url" \
-d "{\"tag_name\":\"$tag\",\"name\":\"Release $tag\",\"body\":\"$(cat "$release_notes")\"}")
local release_id=$(echo "$create_response" | python3 -c "import sys, json; print(json.load(sys.stdin)['id'])" 2>/dev/null || echo "")
for file in $files_to_upload; do
if [[ -f "$file" ]]; then
local file_name=$(basename "$file")
local mime_type=$(file --mime-type -b "$file")
curl -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Content-Type: $mime_type" \
--data-binary @"$file" \
"https://uploads.github.com/repos/$repo/releases/$release_id/assets?name=$file_name" 2>/dev/null || {
echo "Warning: Failed to upload $file_name"
}
fi
done
fi
else
echo "Error: GITHUB_TOKEN required for release upload."
return 1
fi
fi
echo "Release update complete!"
}
echo ""
echo "Step 1: Generating hashes..."
if [[ "$MODE" == "light" || "$MODE" == "both" ]]; then
if [[ -f "$EXPORT_DIR/thgtoa.pdf" ]]; then
create_hash_file "$EXPORT_DIR/thgtoa.pdf"
else
echo "Warning: $EXPORT_DIR/thgtoa.pdf not found. Ensure PDF is built first."
fi
fi
if [[ "$MODE" == "dark" || "$MODE" == "both" ]]; then
if [[ -f "$EXPORT_DIR/thgtoa-dark.pdf" ]]; then
create_hash_file "$EXPORT_DIR/thgtoa-dark.pdf"
else
echo "Warning: $EXPORT_DIR/thgtoa-dark.pdf not found. Ensure PDF is built first."
fi
fi
echo ""
echo "Step 2: Scanning with VirusTotal..."
scan_all_pdfs
echo ""
echo "Step 3: Updating release..."
update_release "$GITHUB_REF_NAME"
echo ""
echo "PDF Release Script Complete!"
+1 -35
View File
@@ -169,44 +169,12 @@ GitHub repository:
3. VT_API_KEY (optional but recommended)
- VirusTotal API key for malware scanning
- Get a free key at: https://www.virustotal.com/gui/join-us
HOW TO ADD SECRETS:
1. Go to your repository on GitHub
2. Click 'Settings''Secrets and variables''Actions'
3. Click 'New repository secret' for each secret below:
Secret Name | Value Format
---------------------|--------------------------------------------------
GPG_PRIVATE_KEY | Paste the entire ASCII armored key (BEGIN PGP...)
GPG_PASSPHRASE | Your key's passphrase (no special characters issues)
VT_API_KEY | Your VirusTotal API key
VERIFYING YOUR SETUP:
After adding secrets, you can test by:
1. Going to 'Actions' tab
2. Selecting 'Build guide PDF' workflow
3. Clicking 'Run workflow'
4. Checking if the workflow completes successfully
TROUBLESHOOTING:
- If GPG signing fails: Check that your key has signing capability ('s' flag)
- If passphrase is wrong: Verify you're using the correct passphrase
- If VT scan fails: Ensure API key is valid and within rate limits
SECURITY NOTES:
⚠ NEVER share your private key or passphrase publicly
⚠ Always use repository secrets, never hardcode in scripts
⚠ Rotate keys periodically if compromised
⚠ Use strong passphrases (12+ characters recommended)
""")
@@ -220,8 +188,6 @@ def main() -> int:
print("⚠ WARNING: GPG is not installed or not in PATH")
print("Please install GPG before continuing:")
print(" - Linux: sudo apt install gnupg")
print(" - macOS: brew install gnupg")
print(" - Windows: https://www.gpg4win.org/")
print("\nContinuing anyway...")
# List available keys
@@ -313,7 +279,7 @@ To get your private key for the GPG_PRIVATE_KEY secret:
print("1. Export your private key (see instructions above)")
print("2. Add all three secrets to GitHub repository settings")
print("3. Test the workflow by triggering a manual build")
print("\nFor more information, see: docs/guide/pdf-workflow.md\n")
print("\nFor more information, see: docs/guide/dev-workflow.md\n")
return 0
+178
View File
@@ -0,0 +1,178 @@
#!/bin/bash
# Script to generate checksums (SHA256, B2SUM) and GPG sign PDF files
# Usage: ./sign-pdfs.sh [input_directory] [output_directory]
# If directories are not provided, defaults will be used
set -e # Exit on error
# Configuration
INPUT_DIR="${1:-./export}" # Default: build-output directory
OUTPUT_DIR="${2:-./export}" # Default: signed-pdfs directory
CHECKSUMS_DIR="${3:-./export}" # Default: checksums directory
GPG_KEY_ID="9FA5436D0EE360985157382517ECA05F768DEDF6"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Function to print colored messages
print_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
print_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Check if required tools are available
check_dependencies() {
print_info "Checking dependencies..."
for cmd in sha256sum b2sum gpg; do
if ! command -v "$cmd" &> /dev/null; then
print_error "$cmd is not installed. Please install it and try again."
exit 1
fi
done
# Check GPG key availability
if [ -z "$GPG_KEY_ID" ]; then
GPG_KEY_ID="${SIGN_PDF_GPG_KEY:-}"
fi
if [ -n "$GPG_KEY_ID" ]; then
if ! gpg --list-keys "$GPG_KEY_ID" &> /dev/null; then
print_error "GPG key '$GPG_KEY_ID' not found in your keyring."
exit 1
fi
else
# List available keys and prompt user
print_warn "No GPG key ID specified. Listing available secret keys:"
gpg --list-secret-keys --keyid-format LONG
read -p "Enter the GPG key ID to use for signing (or press Enter to skip): " GPG_KEY_ID
if [ -n "$GPG_KEY_ID" ]; then
if ! gpg --list-keys "$GPG_KEY_ID" &> /dev/null; then
print_error "GPG key '$GPG_KEY_ID' not found in your keyring."
exit 1
fi
else
print_warn "No GPG signing will be performed. Set SIGN_PDF_GPG_KEY environment variable or pass key ID as argument."
fi
fi
print_info "All dependencies checked successfully!"
}
# Create output directories
setup_directories() {
print_info "Setting up directories..."
if [ ! -d "$INPUT_DIR" ]; then
print_error "Input directory '$INPUT_DIR' does not exist."
exit 1
fi
mkdir -p "$OUTPUT_DIR"
mkdir -p "$CHECKSUMS_DIR"
print_info "Input directory: $INPUT_DIR"
print_info "Output directory: $OUTPUT_DIR"
print_info "Checksums directory: $CHECKSUMS_DIR"
}
# Generate SHA256 checksum for a file
generate_sha256() {
local file="$1"
local filename=$(basename "$file")
local output_file="${CHECKSUMS_DIR}/${filename}.sha256"
sha256sum "$file" > "$output_file"
print_info "SHA256 checksum generated: $output_file"
}
# Generate B2SUM checksum for a file
generate_b2sum() {
local file="$1"
local filename=$(basename "$file")
local output_file="${CHECKSUMS_DIR}/${filename}.b2sum"
b2sum "$file" > "$output_file"
print_info "B2SUM checksum generated: $output_file"
}
# GPG sign a file
gpg_sign() {
local file="$1"
local filename=$(basename "$file")
if [ -z "$GPG_KEY_ID" ]; then
print_warn "Skipping GPG signing for '$filename' (no key ID provided)"
return 0
fi
# Sign the file in detached mode with ASCII armor
gpg --batch --yes --detach-sign --armor --local-user "$GPG_KEY_ID" \
--output "${file}.sig" "$file"
print_info "GPG signature generated: ${file}.sig"
}
# Process a single PDF file
process_pdf() {
local pdf_file="$1"
local filename=$(basename "$pdf_file")
print_info "Processing: $filename"
# Generate checksums
generate_sha256 "$pdf_file"
generate_b2sum "$pdf_file"
# GPG sign if key is available
gpg_sign "$pdf_file"
}
# Main function
main() {
echo ""
check_dependencies
setup_directories
# Find all PDF files in input directory (recursively)
pdf_files=($(find "$INPUT_DIR" -type f -name "*.pdf"))
if [ ${#pdf_files[@]} -eq 0 ]; then
print_error "No PDF files found in '$INPUT_DIR'"
exit 1
fi
print_info "Found ${#pdf_files[@]} PDF file(s) to process"
# Process each PDF file
for pdf_file in "${pdf_files[@]}"; do
process_pdf "$pdf_file"
done
print_info "=========================================="
print_info "Processing Complete!"
print_info "=========================================="
print_info "Checksums saved to: $CHECKSUMS_DIR"
print_info "Signed files and signatures in: $(dirname "$INPUT_DIR")"
# Display summary of checksums
print_info "SHA256 Checksums:"
cat "${CHECKSUMS_DIR}"/*.sha256 2>/dev/null || true
print_info "B2SUM Checksums:"
cat "${CHECKSUMS_DIR}"/*.b2sum 2>/dev/null || true
}
# Run main function
main "$@"
+1 -9
View File
@@ -26,11 +26,9 @@ import subprocess
import sys
from pathlib import Path
def repo_root() -> Path:
return Path(__file__).resolve().parent.parent
def calculate_sha256(file_path: Path) -> str:
"""Calculate SHA256 hash of a file."""
sha256_hash = hashlib.sha256()
@@ -39,7 +37,6 @@ def calculate_sha256(file_path: Path) -> str:
sha256_hash.update(byte_block)
return sha256_hash.hexdigest()
def verify_hash(file_path: Path, expected_hash: str) -> bool:
"""Verify file hash against expected value."""
actual_hash = calculate_sha256(file_path)
@@ -50,7 +47,6 @@ def verify_hash(file_path: Path, expected_hash: str) -> bool:
print(f" Actual: {actual_hash}")
return is_valid
def verify_signature(file_path: Path, sig_file: Path) -> bool:
"""Verify GPG signature of a file."""
if not sig_file.exists():
@@ -81,7 +77,6 @@ def verify_signature(file_path: Path, sig_file: Path) -> bool:
print("⚠ WARNING: GPG not installed. Skipping signature verification.")
return None
def verify_from_hash_file(file_path: Path, hash_file: Path) -> bool:
"""Verify file hash from a hash file."""
if not hash_file.exists():
@@ -102,7 +97,6 @@ def verify_from_hash_file(file_path: Path, hash_file: Path) -> bool:
return verify_hash(file_path, expected_hash)
def check_virustotal(file_hash: str, api_key: str | None = None) -> dict | None:
"""Check VirusTotal scan status for a file hash."""
if not api_key:
@@ -137,7 +131,6 @@ def check_virustotal(file_hash: str, api_key: str | None = None) -> dict | None:
print(f"⚠ ERROR checking VirusTotal: {e}")
return None
def main() -> int:
root = repo_root()
ap = argparse.ArgumentParser(description="Verify PDF files (hashes, signatures, VT).")
@@ -158,7 +151,7 @@ def main() -> int:
ap.add_argument(
"--hash-file",
type=Path,
default=root / "sha256sum-light.txt",
default=root / "export" / "thgtoa.pdf.sha256",
help="Hash file to verify against",
)
@@ -217,6 +210,5 @@ def main() -> int:
print("✗ Some verifications FAILED")
return 1
if __name__ == "__main__":
raise SystemExit(main())