14 Commits

Author SHA1 Message Date
nopeitsnothing 1bb0acc3e8 Sign local copy
Signed-off-by: nopeitsnothing <no@anonymousplanet.org>
2026-04-20 03:50:31 -04:00
nopeitsnothing 25bc901ece Tweaking some of the build to function
Signed-off-by: nopeitsnothing <no@anonymousplanet.org>
2026-04-20 03:43:52 -04:00
nopeitsnothing 78a0a37ee8 Tweaking some of the build to function
Signed-off-by: nopeitsnothing <no@anonymousplanet.org>
2026-04-20 03:38:27 -04:00
nopeitsnothing aeb63cd7ba Tweaking some of the build to function
Signed-off-by: nopeitsnothing <no@anonymousplanet.org>
2026-04-20 03:34:47 -04:00
nopeitsnothing 64ddd18535 Tweaking some of the build to function
Signed-off-by: nopeitsnothing <no@anonymousplanet.org>
2026-04-20 03:29:54 -04:00
nopeitsnothing 7c9847e7d1 Tweaking some of the build to function
Signed-off-by: nopeitsnothing <no@anonymousplanet.org>
2026-04-20 03:23:15 -04:00
nopeitsnothing 1e8c90513f Tweaking some of the build to function
Signed-off-by: nopeitsnothing <no@anonymousplanet.org>
2026-04-20 03:21:00 -04:00
nopeitsnothing 2d09d7c01c Tweaking some of the build to function pt6
Signed-off-by: nopeitsnothing <no@anonymousplanet.org>
2026-04-20 03:15:19 -04:00
nopeitsnothing 1938e031ee Tweaking some of the build to function pt5
Signed-off-by: nopeitsnothing <no@anonymousplanet.org>
2026-04-20 03:13:28 -04:00
nopeitsnothing 8483d6336b Tweaking some of the build to function pt4
Signed-off-by: nopeitsnothing <no@anonymousplanet.org>
2026-04-20 03:08:28 -04:00
nopeitsnothing 1c168691c5 Tweaking some of the build to function pt3
Signed-off-by: nopeitsnothing <no@anonymousplanet.org>
2026-04-20 03:04:39 -04:00
nopeitsnothing ae50911375 Tweaking some of the build to function pt2
Signed-off-by: nopeitsnothing <no@anonymousplanet.org>
2026-04-20 02:58:56 -04:00
nopeitsnothing df2dd61676 Tweaking some of the build to function
Signed-off-by: nopeitsnothing <no@anonymousplanet.org>
2026-04-20 02:50:05 -04:00
nopeitsnothing 904fa24478 Moving some things around
Signed-off-by: nopeitsnothing <no@anonymousplanet.org>
2026-04-20 02:45:06 -04:00
23 changed files with 273 additions and 638 deletions
+11 -52
View File
@@ -53,61 +53,23 @@ jobs:
sudo apt-get update sudo apt-get update
sudo apt-get install gnupg sudo apt-get install gnupg
- name: 🖨️ Build PDFs - name: 🖨️ Build & Hash PDFs
env: env:
CI: true 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 }}
run: | run: |
cd ${{ github.workspace }} python scripts/build_guide_pdf.py --${{ inputs.build_mode || 'both' }}
for f in ./export/*.pdf; do
# Import GPG key echo "sha256sums: $f"; sha256sum "$f" >> export/sha256sums.txt; done
export GPG_TTY=$(tty) for f in ./export/*.pdf; do
echo "$GPG_KEY" | gpg --batch --import 2>/dev/null || true echo "b2sums: $f"; b2sum "$f" >> export/b2sums.txt; done
- 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/"
- name: 🦠 Upload PDFs to VirusTotal - name: 🦠 Upload PDFs to VirusTotal
uses: crazy-max/ghaction-virustotal@v5 uses: crazy-max/ghaction-virustotal@v5
with: with:
vt_api_key: ${{ secrets.VT_API_KEY }} vt_api_key: ${{ secrets.VT_API_KEY }}
files: | files: |
./export/thgtoa.pdf export/thgtoa.pdf
./export/thgtoa-dark.pdf export/thgtoa-dark.pdf
- name: 📊 Extract VT scan results - name: 📊 Extract VT scan results
id: vt-scan id: vt-scan
@@ -125,7 +87,7 @@ jobs:
### thgtoa.pdf (Light Mode) ### thgtoa.pdf (Light Mode)
- **VT Report:** https://www.virustotal.com/gui/file/\$(sha256sum export/thgtoa.pdf | cut -d' ' -f1) - **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) - **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 - name: 📤 Upload export directory as artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: pdf-export-${{ inputs.build_mode || 'both' }} name: export
path: | path: |
export/*.pdf export/*
export/*.sig
export/*.sha256
export/virus-total-results.md
if-no-files-found: error if-no-files-found: error
retention-days: 90 retention-days: 90
compression-level: 0 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 # Changelog
All notable changes to this project will be documented in this file. 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] ## [Unreleased]
### Added ### Added
- This changelog page
- Add ways to verify the files - Add ways to verify the files
### Changed ### 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). - 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 ## 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 [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 [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 -160
View File
@@ -1,81 +1,36 @@
# Development # 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" ??? Note "How the pipeline works"
1. **Automatic PDF Generation** - Builds both light and dark mode PDFs from MkDocs source **Automatic PDF Generation:** - Builds both light and dark mode PDFs from MkDocs source
2. **SHA256 Hash Generation** - Creates hash files for integrity verification **SHA256 Hash Generation:** - Creates hash files for integrity verification
3. **GPG Signature Signing** - Signs all PDFs and hash files with repository GPG key **GPG Signature Signing:** - Signs all PDFs and hash files with repository GPG key
4. **VirusTotal Scanning** - Automatically scans PDFs and updates release notes **VirusTotal Scanning:** - Automatically scans PDFs and updates release notes
5. **Release Automation** - Packages everything into GitHub releases **Release Automation:** - Packages everything into GitHub releases
## Workflow Architecture ## Workflow Architecture
### 1. Build PDF Workflow (`build-pdf.yml`) ### Build PDF Workflow (`build-sign-release.yml`)
**Trigger:** Push to main, pull requests, or manual dispatch
??? Note "Steps" ??? Note "Steps"
- Checkout repository - Checkout repository
- Set up Python 3.13 and MkDocs Material - Set up Python and MkDocs Material
- Install Chromium browser - Install Chromium browser
- Generate both light and dark mode PDFs - Generate both light and dark mode PDFs with `scripts\build_guide_pdf.py`
- Create SHA256 hash files - Create SHA256 and blake2 hash files in `export/`
- Sign all files with GPG - Sign all files with GPG in `export/`
- Upload artifacts to GitHub Actions - Upload artifacts to GitHub Actions **manually**
- Publish release
### 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" - 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
- Download PDF artifacts from build workflow ### GPG Signature Verification
- 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
**Purpose:** Verify authenticity and prevent tampering **Purpose:** Verify authenticity and prevent tampering
@@ -90,104 +45,6 @@ gpg --import pgp/anonymousplanet-master.asc
gpg --verify export/thgtoa.pdf.sig export/thgtoa.pdf 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.* *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** **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)
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.
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 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" ??? 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: Expected output for successful verification:
``` ```
gpg: Signature made [date] gpg: Signature made Mon 20 Apr 2026 01:46:40 AM EDT
gpg: using RSA key [key-id] gpg: using EDDSA key 9FA5436D0EE360985157382517ECA05F768DEDF6
gpg: Good signature from "[owner]" 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 #### 3. Check VirusTotal Status
@@ -114,7 +117,7 @@ The GitHub Actions workflows automatically:
## Key Information ## 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 **Key ID:** See `pgp/anonymousplanet-master.asc` for details
**Fingerprint:** Verify from the repository's official documentation **Fingerprint:** Verify from the repository's official documentation
Binary file not shown.
+8
View File
@@ -0,0 +1,8 @@
-----BEGIN PGP SIGNATURE-----
iJEEABYKADkWIQSfpUNtDuNgmFFXOCUX7KBfdo3t9gUCaeXaqxsUgAAAAAAEAA5t
YW51MiwyLjUrMS4xMiwyLDIACgkQF+ygX3aN7fY6QAD/YCGJqs9HiRllFrF9EluE
Ga4XUEQ/R6Q2zc+X6lX856sBAJIpxeMxUmMUXyr3xBAHxUf5eV+nQYkQQMKI81L1
x8gL
=VX6l
-----END PGP SIGNATURE-----
+1
View File
@@ -0,0 +1 @@
f212d0425b38d5cd10da6dc804b60f143da23d4b07051aae31d0966082519b300af0e1c423683e0223738b33b138c687232b1c8bd68cf643777bbc5b588152bd ./export/thgtoa-dark.pdf
+8
View File
@@ -0,0 +1,8 @@
-----BEGIN PGP SIGNATURE-----
iJEEABYKADkWIQSfpUNtDuNgmFFXOCUX7KBfdo3t9gUCaeXaqxsUgAAAAAAEAA5t
YW51MiwyLjUrMS4xMiwyLDIACgkQF+ygX3aN7fbdDgEAoSslLR47ydW/3r1wJOPY
X/waLkVbkGZpHqwd4RjywwcA/3B7Ci+jUg+yP5TRsuChagEhwyO5vw2DxSlUGoB4
+ksH
=2ja9
-----END PGP SIGNATURE-----
+8
View File
@@ -0,0 +1,8 @@
-----BEGIN PGP SIGNATURE-----
iJEEABYKADkWIQSfpUNtDuNgmFFXOCUX7KBfdo3t9gUCaeXaqxsUgAAAAAAEAA5t
YW51MiwyLjUrMS4xMiwyLDIACgkQF+ygX3aN7faErgD/Svj1G+B7gmrZQ6AsLZ5J
HfeldxjmrXE99dig1iHtl5IBAMndZZb+95TO03IZ9eLGfYuyTz4GCUanmftsY9yv
LAIN
=MEd0
-----END PGP SIGNATURE-----
BIN
View File
Binary file not shown.
+8
View File
@@ -0,0 +1,8 @@
-----BEGIN PGP SIGNATURE-----
iJEEABYKADkWIQSfpUNtDuNgmFFXOCUX7KBfdo3t9gUCaeXaqxsUgAAAAAAEAA5t
YW51MiwyLjUrMS4xMiwyLDIACgkQF+ygX3aN7favvgEAvFFSB5NrsrKMYvGG5ZYB
iLIyt8Sn1rZmlVkibssMPq0BAImpZe8S7hWNkbukyEC4sLbKiOYvjbVipQHnrIUV
xPMH
=0hnj
-----END PGP SIGNATURE-----
+1
View File
@@ -0,0 +1 @@
436ed0df78c299f95b8d5ff94f43f26ec2e7825d92d843fc15419630d55ed5e0c98485e738c12715a2b6242633faae38e8a98935b361d44ddde97a1692cb01a1 ./export/thgtoa.pdf
+8
View File
@@ -0,0 +1,8 @@
-----BEGIN PGP SIGNATURE-----
iJEEABYKADkWIQSfpUNtDuNgmFFXOCUX7KBfdo3t9gUCaeXaqxsUgAAAAAAEAA5t
YW51MiwyLjUrMS4xMiwyLDIACgkQF+ygX3aN7fatsgEAixDzH+zTnKYMEx3sikWp
dsNTiHTU6wJY/brVJIU879UBAJntBIq72vqwKtMb/ZlVvomdDvKVllZw8ZsYBz1n
aTkM
=vkgy
-----END PGP SIGNATURE-----
+8
View File
@@ -0,0 +1,8 @@
-----BEGIN PGP SIGNATURE-----
iJEEABYKADkWIQSfpUNtDuNgmFFXOCUX7KBfdo3t9gUCaeXaqxsUgAAAAAAEAA5t
YW51MiwyLjUrMS4xMiwyLDIACgkQF+ygX3aN7faAGQEAyEhVKrRoXIsV3E5f1FZg
8fcsmbxCnKBqxichCkf0dWYBAIvbI146mQLHaNqLDaTIqCUQbkq1aE/YMFDGykUG
ngsJ
=/0RY
-----END PGP SIGNATURE-----
+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*
+8
View File
@@ -0,0 +1,8 @@
-----BEGIN PGP SIGNATURE-----
iJEEABYKADkWIQSfpUNtDuNgmFFXOCUX7KBfdo3t9gUCaeXaqxsUgAAAAAAEAA5t
YW51MiwyLjUrMS4xMiwyLDIACgkQF+ygX3aN7fYpCgEA209U3QewChp7mdrrFjH1
CaBMIk2sCHwRMCcmbMDkNTAA/RIchAKex13ZjZWC9xsJpZEktvBENFsQLsNPReqR
UZ8C
=TYsa
-----END PGP SIGNATURE-----
+1
View File
@@ -133,6 +133,7 @@ nav:
- Constitution: constitution/index.md - Constitution: constitution/index.md
- Mirrors: mirrors/index.md - Mirrors: mirrors/index.md
- Twitter: twitter/index.md - Twitter: twitter/index.md
- TBA: changelog/index.md
copyright: | copyright: |
&copy; 2023-2026 <a href="https://anonymousplanet.org/" target="_blank" rel="noopener">Anonymous Planet</a> &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", "--pdf-light",
type=Path, type=Path,
default=root / "export" / "thgtoa.pdf", 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( ap.add_argument(
"--pdf-dark", "--pdf-dark",
type=Path, type=Path,
default=root / "export" / "thgtoa-dark.pdf", 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("--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") 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) 3. VT_API_KEY (optional but recommended)
- VirusTotal API key for malware scanning - 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: TROUBLESHOOTING:
- If GPG signing fails: Check that your key has signing capability ('s' flag) - 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 passphrase is wrong: Verify you're using the correct passphrase
- If VT scan fails: Ensure API key is valid and within rate limits - 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("⚠ WARNING: GPG is not installed or not in PATH")
print("Please install GPG before continuing:") print("Please install GPG before continuing:")
print(" - Linux: sudo apt install gnupg") print(" - Linux: sudo apt install gnupg")
print(" - macOS: brew install gnupg")
print(" - Windows: https://www.gpg4win.org/")
print("\nContinuing anyway...") print("\nContinuing anyway...")
# List available keys # 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("1. Export your private key (see instructions above)")
print("2. Add all three secrets to GitHub repository settings") print("2. Add all three secrets to GitHub repository settings")
print("3. Test the workflow by triggering a manual build") 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 return 0
+1 -9
View File
@@ -26,11 +26,9 @@ import subprocess
import sys import sys
from pathlib import Path from pathlib import Path
def repo_root() -> Path: def repo_root() -> Path:
return Path(__file__).resolve().parent.parent return Path(__file__).resolve().parent.parent
def calculate_sha256(file_path: Path) -> str: def calculate_sha256(file_path: Path) -> str:
"""Calculate SHA256 hash of a file.""" """Calculate SHA256 hash of a file."""
sha256_hash = hashlib.sha256() sha256_hash = hashlib.sha256()
@@ -39,7 +37,6 @@ def calculate_sha256(file_path: Path) -> str:
sha256_hash.update(byte_block) sha256_hash.update(byte_block)
return sha256_hash.hexdigest() return sha256_hash.hexdigest()
def verify_hash(file_path: Path, expected_hash: str) -> bool: def verify_hash(file_path: Path, expected_hash: str) -> bool:
"""Verify file hash against expected value.""" """Verify file hash against expected value."""
actual_hash = calculate_sha256(file_path) 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}") print(f" Actual: {actual_hash}")
return is_valid return is_valid
def verify_signature(file_path: Path, sig_file: Path) -> bool: def verify_signature(file_path: Path, sig_file: Path) -> bool:
"""Verify GPG signature of a file.""" """Verify GPG signature of a file."""
if not sig_file.exists(): 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.") print("⚠ WARNING: GPG not installed. Skipping signature verification.")
return None return None
def verify_from_hash_file(file_path: Path, hash_file: Path) -> bool: def verify_from_hash_file(file_path: Path, hash_file: Path) -> bool:
"""Verify file hash from a hash file.""" """Verify file hash from a hash file."""
if not hash_file.exists(): 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) return verify_hash(file_path, expected_hash)
def check_virustotal(file_hash: str, api_key: str | None = None) -> dict | None: def check_virustotal(file_hash: str, api_key: str | None = None) -> dict | None:
"""Check VirusTotal scan status for a file hash.""" """Check VirusTotal scan status for a file hash."""
if not api_key: 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}") print(f"⚠ ERROR checking VirusTotal: {e}")
return None return None
def main() -> int: def main() -> int:
root = repo_root() root = repo_root()
ap = argparse.ArgumentParser(description="Verify PDF files (hashes, signatures, VT).") ap = argparse.ArgumentParser(description="Verify PDF files (hashes, signatures, VT).")
@@ -158,7 +151,7 @@ def main() -> int:
ap.add_argument( ap.add_argument(
"--hash-file", "--hash-file",
type=Path, type=Path,
default=root / "sha256sum-light.txt", default=root / "export" / "thgtoa.pdf.sha256",
help="Hash file to verify against", help="Hash file to verify against",
) )
@@ -217,6 +210,5 @@ def main() -> int:
print("✗ Some verifications FAILED") print("✗ Some verifications FAILED")
return 1 return 1
if __name__ == "__main__": if __name__ == "__main__":
raise SystemExit(main()) raise SystemExit(main())