mirror of
https://github.com/Anon-Planet/thgtoa.git
synced 2026-05-06 19:44:19 +02:00
Compare commits
44 Commits
v1.2.1
..
f3cb57230f
| Author | SHA1 | Date | |
|---|---|---|---|
| f3cb57230f | |||
| 5e8057bb1f | |||
| ac3d2ceb37 | |||
| 5eded0af38 | |||
| 1bb0acc3e8 | |||
| 25bc901ece | |||
| 78a0a37ee8 | |||
| aeb63cd7ba | |||
| 64ddd18535 | |||
| 7c9847e7d1 | |||
| 1e8c90513f | |||
| 2d09d7c01c | |||
| 1938e031ee | |||
| 8483d6336b | |||
| 1c168691c5 | |||
| ae50911375 | |||
| df2dd61676 | |||
| 904fa24478 | |||
| 28556c016c | |||
| 7bc3ed6bb6 | |||
| 9a58ca1b7c | |||
| 655e47fb8d | |||
| c0eb8aa6f3 | |||
| 90aa8b5442 | |||
| 85912692d2 | |||
| 6305e1fbbb | |||
| 0b71c3f49a | |||
| 468ff8f4a1 | |||
| 11c2882ba5 | |||
| 4c3ca7bfd7 | |||
| 5636291c8a | |||
| 41ac52de0a | |||
| f100633632 | |||
| 062128732e | |||
| e0d16797ed | |||
| d5659af3f7 | |||
| a2dbdd10e9 | |||
| cd00fa79fd | |||
| c49cc87390 | |||
| a14191bc7b | |||
| a47e02939d | |||
| 52a38f5deb | |||
| 206a6ff6b7 | |||
| 0e8de6ccc0 |
@@ -1,55 +0,0 @@
|
||||
name: 📖 Build PDF
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
paths:
|
||||
- "docs/**"
|
||||
- "mkdocs.yml"
|
||||
- "scripts/build_guide_pdf.py"
|
||||
- ".github/workflows/build-pdf.yml"
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "docs/**"
|
||||
- "mkdocs.yml"
|
||||
- "scripts/build_guide_pdf.py"
|
||||
- ".github/workflows/build-pdf.yml"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
pdf:
|
||||
name: MkDocs + print to PDF
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install MkDocs Material
|
||||
run: pip install mkdocs-material
|
||||
|
||||
- name: Install Chromium
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends chromium
|
||||
|
||||
- name: Build PDF
|
||||
env:
|
||||
CI: true
|
||||
run: python scripts/build_guide_pdf.py
|
||||
|
||||
- name: Upload PDF artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: guide-pdf
|
||||
path: export/guide.pdf
|
||||
if-no-files-found: error
|
||||
retention-days: 90
|
||||
@@ -0,0 +1,105 @@
|
||||
name: 📖 Build & Sign PDFs
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
build_mode:
|
||||
description: 'PDF build mode'
|
||||
required: true
|
||||
default: 'both'
|
||||
type: choice
|
||||
options:
|
||||
- light
|
||||
- dark
|
||||
- both
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "docs/**"
|
||||
- "mkdocs.yml"
|
||||
- "scripts/**"
|
||||
- ".github/workflows/**"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
build-sign-release:
|
||||
name: Build, Sign & Release PDFs
|
||||
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 MkDocs Material
|
||||
run: pip install mkdocs-material
|
||||
|
||||
- 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 update
|
||||
sudo apt-get install gnupg
|
||||
|
||||
- name: 🖨️ Build & Hash PDFs
|
||||
env:
|
||||
CI: true
|
||||
run: |
|
||||
python scripts/build_guide_pdf.py --${{ inputs.build_mode || 'both' }}
|
||||
for f in ./export/*.pdf; do
|
||||
echo "sha256sums: $f"; sha256sum "$f" >> export/sha256sums.txt; done
|
||||
for f in ./export/*.pdf; do
|
||||
echo "b2sums: $f"; b2sum "$f" >> export/b2sums.txt; done
|
||||
|
||||
- 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
|
||||
|
||||
- name: 📊 Extract VT scan results
|
||||
id: vt-scan
|
||||
run: |
|
||||
echo "status=completed" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: 🔗 Generate VT report links
|
||||
run: |
|
||||
# Create a markdown file with VT scan results and links
|
||||
cat > export/virus-total-results.md << EOF
|
||||
## VirusTotal Scan Results
|
||||
|
||||
**Scan Date:** \$(date -u +"%Y-%m-%d %H:%M UTC")
|
||||
|
||||
### thgtoa.pdf (Light Mode)
|
||||
- **VT Report:** https://www.virustotal.com/gui/file/\$(sha256sum export/thgtoa.pdf | cut -d' ' -f1)
|
||||
|
||||
### thgtoa-dark.pdf (Dark Mode) (currently broken)
|
||||
- **VT Report:** https://www.virustotal.com/gui/file/\$(sha256sum export/thgtoa-dark.pdf | cut -d' ' -f1)
|
||||
|
||||
---
|
||||
*Scan performed automatically by GitHub Actions*
|
||||
EOF
|
||||
|
||||
- name: 📤 Upload export directory as artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: upload pdf artifact
|
||||
path: |
|
||||
export/*
|
||||
if-no-files-found: error
|
||||
retention-days: 90
|
||||
compression-level: 0
|
||||
+6
-1
@@ -19,4 +19,9 @@ ENV/
|
||||
site/
|
||||
_site/
|
||||
_site_test/
|
||||
export/
|
||||
build/
|
||||
|
||||
# Export directory - but track hash files and signatures
|
||||
export/thgtoa.pdf.sha256
|
||||
export/thgtoa-dark.pdf.sha256
|
||||
*.sig
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
# 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]
|
||||
|
||||
## [1.2.1] - 2026-04-11
|
||||
|
||||
### Added
|
||||
|
||||
- GitHub Actions workflow **Build guide PDF** (`.github/workflows/build-pdf.yml`): installs Chromium on `ubuntu-latest`, runs `scripts/build_guide_pdf.py`, uploads `export/guide.pdf` as the `guide-pdf` artifact. Runs on `workflow_dispatch`, on pushes to `main` that touch docs or build inputs, and on matching pull requests.
|
||||
|
||||
- `scripts/build_guide_pdf.py` to build the MkDocs site and render the guide to a single PDF (`export/guide.pdf` by default) using a Chromium-based browser (Chrome or Edge) headless print-to-PDF.
|
||||
- `docs/stylesheets/extra.css` and `extra_css` in `mkdocs.yml` for shared site styling.
|
||||
- This `CHANGELOG.md`.
|
||||
|
||||
### Changed
|
||||
|
||||
- `README.md` “Ways to read or export the guide”: hosted link, local `mkdocs serve`, PDF build via the script, ODT note, raw Markdown link.
|
||||
- Guide landing layout: wrap the opening block in `docs/guide/index.md` with a `guide-intro-lead` container so the logo and first sections share one layout context for web and print.
|
||||
- `.gitignore` to exclude local build outputs `export/`, `site/`, and `_site_test/`.
|
||||
- `scripts/build_guide_pdf.py`: when the `CI` environment variable is set, pass Chromium flags (`--no-sandbox`, `--disable-setuid-sandbox`, `--disable-dev-shm-usage`) so headless print works on typical CI images.
|
||||
- `README.md`: note the **Build guide PDF** GitHub Actions workflow and the `guide-pdf` artifact.
|
||||
|
||||
### Fixed
|
||||
|
||||
- `docs/guide/index.md`: replace broken reference-style internal links (`[label][label:]`) with working same-page fragment links to the correct headings; correct the mismatched “Real-Name System” cross-reference; fix a broken footnote marker on the “free (unallocated) space of your hard drive” list item.
|
||||
|
||||
[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,8 +1,6 @@
|
||||
Welcome.
|
||||
|
||||
**[IMPORTANT RECOMMENDATION FOR UKRAINIANS. ВАЖЛИВА РЕКОМЕНДАЦІЯ ДЛЯ УКРАЇНЦІВ](briar.html)**
|
||||
|
||||
This is a maintained guide with the aim of providing an introduction to various online tracking techniques, online ID verification techniques, and detailed guidance to creating and maintaining (truly) anonymous online identities. <span style="color: red">**It is written with hope for activists, journalists, scientists, lawyers, whistle-blowers, and good people being oppressed, censored, harassed anywhere!**</span> This guide has no affiliation with the [Anonymous](https://en.wikipedia.org/wiki/Anonymous_(hacker_group)) <sup>[[Wikiless]](https://wikiless.com/wiki/Anonymous_(hacker_group))</sup> <sup>[[Archive.org]](https://web.archive.org/web/https://en.wikipedia.org/wiki/Anonymous_(hacker_group))</sup> collective/movement.
|
||||
This is a guide with the aim of providing an introduction to various online tracking techniques, online ID verification techniques, and detailed guidance to creating and maintaining (truly) anonymous online identities. <span style="color: red">**It is written with hope for activists, journalists, scientists, lawyers, whistle-blowers, and good people being oppressed, censored, harassed anywhere!**</span> This guide has no affiliation with the [Anonymous](https://en.wikipedia.org/wiki/Anonymous_(hacker_group)) <sup>[[Wikiless]](https://wikiless.com/wiki/Anonymous_(hacker_group))</sup> <sup>[[Archive.org]](https://web.archive.org/web/https://en.wikipedia.org/wiki/Anonymous_(hacker_group))</sup> collective/movement.
|
||||
|
||||
This guide is an open-source non-profit initiative, [licensed](LICENSE.html) under **Creative Commons Attribution-NonCommercial 4.0 International** ([cc-by-nc-4.0](https://creativecommons.org/licenses/by-nc/4.0/) <sup>[[Archive.org]](https://web.archive.org/web/https://creativecommons.org/licenses/by-nc/4.0/)</sup>) and is **not sponsored/endorsed by any commercial/governmental entity**. This means that you are free to use our guide for pretty much any purpose **excluding commercially** as long as you do attribute it. There are no ads or any affiliate links.
|
||||
|
||||
@@ -12,27 +10,3 @@ This guide is an open-source non-profit initiative, [licensed](LICENSE.html) und
|
||||
|
||||
- **In your browser:** [Hitchhiker's Guide](https://www.anonymousplanet.org/guide/) (hosted site). After a local build you can also open `site/guide/index.html` directly.
|
||||
- **Local HTML preview:** from the repository root, with Python 3 and [MkDocs Material](https://squidfunk.github.io/mkdocs-material/getting-started/) installed (`pip install mkdocs-material`), run `mkdocs serve` and open the URL printed in the terminal (for example `http://127.0.0.1:8000`).
|
||||
- **PDF (local build):** from the repository root, using the same environment, run:
|
||||
|
||||
```bash
|
||||
python scripts/build_guide_pdf.py
|
||||
```
|
||||
|
||||
This runs `mkdocs build` (output defaults to `./site`), then uses **Google Chrome** or **Microsoft Edge** in headless mode to print `site/guide/index.html` to **`export/guide.pdf`** (images and styling preserved). If the site is already built: `python scripts/build_guide_pdf.py --skip-mkdocs`. Other options: `--site-dir`, `--pdf`, and `python scripts/build_guide_pdf.py --help`.
|
||||
|
||||
On **GitHub Actions**, the [Build guide PDF](https://github.com/Anon-Planet/thgtoa/actions/workflows/build-pdf.yml) workflow does the same using Chromium on Ubuntu when you push to `main` or open a pull request that touches the guide or build inputs; download the **`guide-pdf`** artifact from a successful run. You can also run it manually (**Actions** → **Build guide PDF** → **Run workflow**).
|
||||
- **OpenDocument (ODT):** not produced by this repository (previous hosted export removed).
|
||||
- **Raw Markdown (very large):** [docs/guide/index.md on GitHub](https://raw.githubusercontent.com/Anon-Planet/thgtoa/refs/heads/main/docs/guide/index.md)
|
||||
|
||||
**Mirrors:**
|
||||
- <del>Hidden service: <http://thgtoa3jzy3doku7hkna32htpghjijefscwvh4dyjgfydbbjkeiohgid.onion/></del> **Host down**
|
||||
|
||||
Feel free to submit issues using Github Issues with the repository link above. Criticism, opinions, and ideas are welcome!
|
||||
|
||||
**Follow or contact us on:**
|
||||
|
||||
Discussion Channels:
|
||||
- Matrix room: <https://matrix.to/#/#anonymity:anonymousplanet.net>
|
||||
- Matrix space: <https://matrix.to/#/#psa:anonymousplanet.net>
|
||||
|
||||
Have a good read and feel free to share and/or recommend it!
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: "About Anonymous Planet"
|
||||
title: "Anonymous Planet"
|
||||
description: We are the maintainers of the Hitchhiker's Guide and the PSA Matrix space.
|
||||
schema:
|
||||
"@context": https://schema.org
|
||||
@@ -7,7 +7,7 @@ schema:
|
||||
"@id": https://www.anonymousplanet.org/
|
||||
name: Anonymous Planet
|
||||
url: https://www.anonymousplanet.org/about/
|
||||
logo: ../media/favicon.png
|
||||
logo: ../media/profile.png
|
||||
sameAs:
|
||||
- https://github.com/Anon-Planet
|
||||
- https://opencollective.com/anonymousplanetorg
|
||||
@@ -15,7 +15,7 @@ schema:
|
||||
---
|
||||
{ align=right }
|
||||
|
||||
**Anonymous Planet** are the maintainers of the [_Hitchhiker's Guide_](https://anonymousplanet.org/guide.html) and the [_PSA Community_](https://psa.anonymousplanet.org). It is responsible for maintaining the projects and code repositories.
|
||||
**Anonymous Planet** are the maintainers of the [_Hitchhiker's Guide_](../guide/index.md) and the [_PSA Community_](https://psa.anonymousplanet.org). It is responsible for maintaining the projects and code repositories. This project is part of our ongoing efforts to provide open-source tools and resources for the community, with regular updates and improvements added to the changelog.
|
||||
|
||||
The purpose: providing an introduction to various online tracking techniques, online ID verification techniques, and detailed guidance to creating and maintaining (truly) anonymous online identities. It is written with the hopes that good people (e.g., activists, journalists, scientists, lawyers, whistle-blowers, etc.) will be able to fight oppression, censorship and harassment! The website and projects are free (as in freedom) and not affiliated with any donor or projects discussed.
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
title: "Release Notes"
|
||||
description: "Release Notes"
|
||||
schema:
|
||||
"@context": https://schema.org
|
||||
"@type": Organization
|
||||
"@id": https://www.anonymousplanet.org/
|
||||
name: Anonymous Planet
|
||||
url: https://www.anonymousplanet.org/authors/
|
||||
logo: ../media/profile.png
|
||||
sameAs:
|
||||
- https://github.com/Anon-Planet
|
||||
- https://opencollective.com/anonymousplanetorg
|
||||
- https://mastodon.social/@anonymousplanet
|
||||
---
|
||||
|
||||
# Release Notes
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
!!! Note "Added"
|
||||
|
||||
- This changelog page
|
||||
- Add ways to verify the files
|
||||
|
||||
!!! Note "Changed"
|
||||
|
||||
- Refactored GitHub Actions workflow **Build PDF** (`scripts\build_guide_pdf.py`): now builds both light and dark mode PDFs
|
||||
- Restored previous VT scans
|
||||
|
||||
!!! Note "Fixed"
|
||||
|
||||
- `docs/about/index.md`: replace broken reference-style internal links
|
||||
- `docs/guide/index.md`: Appendix A6: comment out deprecated ODT information because we don't and probably won't use it in the future
|
||||
|
||||
!!! Note "Feature"
|
||||
|
||||
- Updated `scripts/build_guide_pdf.py` to use `--print-to-pdf` instead of `--save-as` for PDF generation
|
||||
- Added a new `--dark-mode` flag to generate dark mode PDFs. Save your eyes - you only get one pair.
|
||||
|
||||
## [v1.2.1]
|
||||
|
||||
!!! Note "Added"
|
||||
|
||||
- GitHub Actions workflow **Build PDF** (`.github/workflows/build-pdf.yml`): installs Chromium on `ubuntu-latest`, runs `scripts/build_guide_pdf.py`, uploads `export/guide.pdf` as the `guide-pdf` artifact. Runs on `workflow_dispatch`, on pushes to `main` that touch docs or build inputs, and on matching pull requests.
|
||||
|
||||
- `scripts/build_guide_pdf.py` to build the MkDocs site and render the guide to a single PDF (`export/guide.pdf` by default) using a Chromium-based browser (Chrome or Edge) headless print-to-PDF.
|
||||
- `docs/stylesheets/extra.css` and `extra_css` in `mkdocs.yml` for shared site styling.
|
||||
- This `CHANGELOG.md`.
|
||||
|
||||
!!! Note "Changed"
|
||||
|
||||
- `README.md` “Ways to read or export the guide”: hosted link, local `mkdocs serve`, PDF build via the script, ODT note, raw Markdown link.
|
||||
- Guide landing layout: wrap the opening block in `docs/guide/index.md` with a `guide-intro-lead` container so the logo and first sections share one layout context for web and print.
|
||||
- `.gitignore` to exclude local build outputs `export/`, `site/`, and `_site_test/`.
|
||||
- `scripts/build_guide_pdf.py`: when the `CI` environment variable is set, pass Chromium flags (`--no-sandbox`, `--disable-setuid-sandbox`, `--disable-dev-shm-usage`) so headless print works on typical CI images.
|
||||
- `README.md`: note the **Build PDF** GitHub Actions workflow and the `guide-pdf` artifact.
|
||||
|
||||
!!! Note "Fixed"
|
||||
|
||||
- `docs/guide/index.md`: replace broken reference-style internal links (`[label][label:]`) with working same-page fragment links to the correct headings; correct the mismatched “Real-Name System” cross-reference; fix a broken footnote marker on the “free (unallocated) space of your hard drive” list item.
|
||||
|
||||
[Unreleased]: https://github.com/Anon-Planet/thgtoa/compare/v1.2.1...HEAD
|
||||
[v1.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).***
|
||||
@@ -0,0 +1,50 @@
|
||||
# Development
|
||||
|
||||
??? Note "How the pipeline works"
|
||||
|
||||
**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
|
||||
|
||||
## Architecture
|
||||
|
||||
### Build PDF Workflow (`build-sign-release.yml`)
|
||||
|
||||
!!! Note "Steps"
|
||||
|
||||
- Checkout repository
|
||||
- Set up Python and MkDocs Material
|
||||
- Install Chromium browser
|
||||
- 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**
|
||||
|
||||
### SHA256 Hash Verification
|
||||
|
||||
!!! Note "**How it works**"
|
||||
|
||||
- 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
|
||||
|
||||
!!! Note "How it works"
|
||||
|
||||
- Detached signatures created for each PDF and hash file
|
||||
- Public keys available in `/pgp/` directory
|
||||
|
||||
**Verification command:**
|
||||
```bash
|
||||
gpg --import pgp/anonymousplanet-master.asc
|
||||
gpg --verify export/thgtoa.pdf.sig export/thgtoa.pdf
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*This workflow is designed for security-conscious users who need to verify the authenticity and integrity of downloaded documents.*
|
||||
+2
-2
@@ -7,7 +7,7 @@ schema:
|
||||
"@id": https://www.anonymousplanet.org/
|
||||
name: Anonymous Planet
|
||||
url: https://www.anonymousplanet.org/guide/
|
||||
logo: ../media/favicon.ico
|
||||
logo: ../media/profile.png
|
||||
sameAs:
|
||||
- https://github.com/Anon-Planet
|
||||
- https://opencollective.com/anonymousplanetorg
|
||||
@@ -11344,7 +11344,7 @@ If you want to compare an older version of the PDF with a newer version, conside
|
||||
|
||||
- <https://draftable.com/compare>
|
||||
|
||||
If you want to compare the older version of the ODT format with a newer version, use the LibreWriter compare features as explained here: <https://help.libreoffice.org/7.1/en-US/text/shared/guide/redlining_doccompare.html> <sup>[[Archive.org]](https://web.archive.org/web/https://help.libreoffice.org/7.1/en-US/text/shared/guide/redlining_doccompare.html)</sup>
|
||||
<!-- If you want to compare the older version of the ODT format with a newer version, use the LibreWriter compare features as explained here: <https://help.libreoffice.org/7.1/en-US/text/shared/guide/redlining_doccompare.html> <sup>[[Archive.org]](https://web.archive.org/web/https://help.libreoffice.org/7.1/en-US/text/shared/guide/redlining_doccompare.html)</sup> -->
|
||||
|
||||
# Appendix A7: Crypto Swapping Services without Registration and KYC
|
||||
|
||||
|
||||
+8
-4
@@ -7,19 +7,23 @@ schema:
|
||||
"@id": https://www.anonymousplanet.org/
|
||||
name: Anonymous Planet
|
||||
url: https://www.anonymousplanet.org/authors/
|
||||
logo: ../media/favicon.png
|
||||
logo: ../media/profile.png
|
||||
sameAs:
|
||||
- https://github.com/Anon-Planet
|
||||
- https://opencollective.com/anonymousplanetorg
|
||||
- https://mastodon.social/@anonymousplanet
|
||||
---
|
||||
{ align=right }
|
||||
|
||||
**Welcome to the Hitchhiker's Guide.**
|
||||
# **Hello, and welcome to the Hitchhiker's Guide.**
|
||||
|
||||
**9FA5 436D 0EE3 6098 5157 3825 17EC A05F 768D EDF6**
|
||||
|
||||
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 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.
|
||||
{ align=right }
|
||||
|
||||
Anonymous Planet is a collective of volunteers.
|
||||
|
||||
??? person "Das Kolburn"
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ schema:
|
||||
"@id": https://www.anonymousplanet.org/
|
||||
name: Anonymous Planet
|
||||
url: https://www.anonymousplanet.org/mirrors/
|
||||
logo: ../media/favicon.png
|
||||
logo: ../media/profile.png
|
||||
sameAs:
|
||||
- https://github.com/Anon-Planet
|
||||
- https://opencollective.com/anonymousplanetorg
|
||||
@@ -27,9 +27,9 @@ schema:
|
||||
|
||||
!!! Note "PDF export (single file)"
|
||||
|
||||
The guide is also available as a **PDF** (images and layout preserved). It is built automatically in GitHub Actions: open [**Build guide PDF**](https://github.com/Anon-Planet/thgtoa/actions/workflows/build-pdf.yml) on the [**thgtoa** source repository](https://github.com/Anon-Planet/thgtoa), pick a successful run, and download the **`guide-pdf`** artifact. You can start a fresh build anytime (**Actions** → **Build guide PDF** → **Run workflow**).
|
||||
The guide is also available as a **PDF** (images and layout preserved). It is built automatically in GitHub Actions: open [**Build guide PDF**](https://github.com/Anon-Planet/thgtoa/actions/workflows/build-sign-release.yml) on the [**source repository**](https://github.com/Anon-Planet/thgtoa), pick a successful run, and download the **`thgtoa`** and **`thgtoa-dark`** artifacts. You can start a fresh build anytime (**Actions** → **Build guide PDF** → **Run workflow**).
|
||||
|
||||
To produce the same file locally, clone the repository and run `python scripts/build_guide_pdf.py` (Python, [MkDocs Material](https://squidfunk.github.io/mkdocs-material/getting-started/), and **Google Chrome** or **Microsoft Edge** required). More detail is in the [repository README](https://github.com/Anon-Planet/thgtoa#ways-to-read-or-export-the-guide).
|
||||
To produce the same file locally, clone the repository and run `python3 scripts/build_guide_pdf.py --both` (Python, [MkDocs Material](https://squidfunk.github.io/mkdocs-material/getting-started/), and **Google Chrome** or **Microsoft Edge** required). More detail is in the [repository README](https://github.com/Anon-Planet/thgtoa#ways-to-read-or-export-the-guide).
|
||||
|
||||
!!! Note "Our official git mirrors"
|
||||
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
/* Generate dark mode PDF of the HTML at guide/index.html */
|
||||
|
||||
/*
|
||||
DARK_MODE_PDF.CSS
|
||||
Use this stylesheet when generating a PDF from HTML.
|
||||
*/
|
||||
|
||||
:root {
|
||||
/* Color Palette */
|
||||
--bg-color: #121212; /* Deep dark grey (easier on eyes than pure black) */
|
||||
--text-primary: #e0e0e0; /* Off-white for readability */
|
||||
--text-secondary: #a0a0a0; /* Grey for captions/metadata */
|
||||
--accent-color: #bb86fc; /* Light purple accent (optional) */
|
||||
--border-color: #333333; /* Subtle borders */
|
||||
|
||||
/* Fonts - System fonts ensure best rendering across PDF engines */
|
||||
--font-main: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
/* --- RESET & BASE STYLES --- */
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-primary);
|
||||
font-family: var(--font-main);
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* --- TYPOGRAPHY & HEADINGS --- */
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
color: var(--text-primary);
|
||||
font-weight: 700;
|
||||
margin-top: 1.5em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 1rem;
|
||||
color: var(--text-secondary); /* Slightly dimmer text for body copy */
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--accent-color);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* --- CONTAINER & LAYOUT --- */
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 40px auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* Cards / Sections with dark backgrounds */
|
||||
.card {
|
||||
background-color: #1e1e1e;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* --- TABLES (Common in PDFs) --- */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 20px 0;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #2c2c2c;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* --- IMAGES --- */
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
margin: 20px 0;
|
||||
/* This ensures high contrast images don't get washed out */
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
|
||||
/* --- CRITICAL FOR PDF GENERATORS --- */
|
||||
/* Forces the browser/PDF engine to print background colors and graphics */
|
||||
@media print {
|
||||
@page {
|
||||
size: A4; /* Change to 'Letter' if preferred */
|
||||
margin: 20mm;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg-color) !important;
|
||||
color: var(--text-primary) !important;
|
||||
}
|
||||
|
||||
.card, table th {
|
||||
-webkit-print-color-adjust: exact !important; /* Chrome/Safari */
|
||||
print-color-adjust: exact !important; /* Firefox/Standard */
|
||||
background-color: #1e1e1e !important;
|
||||
color: var(--text-primary) !important;
|
||||
}
|
||||
|
||||
/* Prevent page breaks in the middle of a sentence or card if possible */
|
||||
.card {
|
||||
break-inside: avoid;
|
||||
}
|
||||
|
||||
/* Hide elements you don't want in PDF (like navigation bars) */
|
||||
nav, footer, button {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
---
|
||||
title: "Verify"
|
||||
description: How to verify the authenticity of our files and check virus scans
|
||||
---
|
||||
|
||||
# PDF Verification Guide
|
||||
|
||||
## Files Provided
|
||||
|
||||
For each PDF release, you'll receive:
|
||||
|
||||
- **PDF file** (`thgtoa.pdf` or `thgtoa-dark.pdf`) - The actual document
|
||||
- **Signature file** (`.sig`) - GPG detached signature for authenticity verification
|
||||
- **Hash file** (`.sha256`) - SHA256 checksum for integrity verification
|
||||
|
||||
## Quick Verification
|
||||
|
||||
### Using Python Script (Recommended)
|
||||
|
||||
```bash
|
||||
# Verify everything (hashes, signatures, and optionally VirusTotal)
|
||||
python scripts/verify_pdf.py --all
|
||||
|
||||
# Only verify hashes
|
||||
python scripts/verify_pdf.py --hashes
|
||||
|
||||
# Only verify GPG signatures
|
||||
python scripts/verify_pdf.py --signatures
|
||||
|
||||
# Check VirusTotal scan status (requires VT_API_KEY environment variable)
|
||||
python scripts/verify_pdf.py --vt
|
||||
```
|
||||
|
||||
### Manual Verification
|
||||
|
||||
#### 1. Verify SHA256 Hash
|
||||
|
||||
**Linux/macOS:**
|
||||
```bash
|
||||
cd /path/to/repo
|
||||
sha256sum -c sha256sum-light.txt
|
||||
```
|
||||
|
||||
**Windows (PowerShell):**
|
||||
```powershell
|
||||
Get-FileHash -Algorithm SHA256 export\thgtoa.pdf | Select-Object Hash
|
||||
# Compare with the hash in thgtoa.pdf.sha256
|
||||
```
|
||||
|
||||
#### 2. Verify GPG Signature
|
||||
|
||||
First, import the public key:
|
||||
```bash
|
||||
gpg --import pgp/anonymousplanet-master.asc
|
||||
```
|
||||
|
||||
Then verify the signature:
|
||||
```bash
|
||||
gpg --verify export/thgtoa.pdf.sig export/thgtoa.pdf
|
||||
gpg --verify export/thgtoa-dark.pdf.sig export/thgtoa-dark.pdf
|
||||
```
|
||||
|
||||
Expected output for successful verification:
|
||||
```
|
||||
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
|
||||
|
||||
Visit the VirusTotal report links (automatically generated in release notes):
|
||||
- Light mode: `https://www.virustotal.com/gui/file/[hash]`
|
||||
- Dark mode: `https://www.virustotal.com/gui/file/[hash]`
|
||||
|
||||
Or use the Python script with API key:
|
||||
```bash
|
||||
export VT_API_KEY=your_vt_api_key
|
||||
python scripts/verify_pdf.py --vt
|
||||
```
|
||||
|
||||
## Automated Verification in CI/CD
|
||||
|
||||
The GitHub Actions workflows automatically:
|
||||
|
||||
1. **Build PDFs** from MkDocs source
|
||||
2. **Generate SHA256 hashes** and save to root directory
|
||||
3. **Sign files with GPG** using the repository's private key
|
||||
4. **Scan with VirusTotal** and update release notes
|
||||
5. **Create releases** with all verification artifacts
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
1. **Always verify signatures** before opening PDFs from untrusted sources
|
||||
2. **Check hashes** to ensure files weren't corrupted during download
|
||||
3. **Review VirusTotal results** for any suspicious detections
|
||||
4. **Import keys securely** - verify key fingerprints with the project maintainers
|
||||
5. **Keep verification scripts updated** to match current security standards
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Good signature" but wrong owner?
|
||||
- Ensure you imported the correct public key
|
||||
- Check the key fingerprint matches the official one from the repository
|
||||
|
||||
### Hash mismatch?
|
||||
- Re-download the file (corruption during transfer)
|
||||
- Verify you're checking against the correct hash file
|
||||
- Check for disk errors on your system
|
||||
|
||||
### GPG not found?
|
||||
- Install GPG: `sudo apt install gnupg` (Debian/Ubuntu) or `brew install gnupg` (macOS)
|
||||
- On Windows, use [Gpg4win](https://www.gpg4win.org/)
|
||||
|
||||
## Key Information
|
||||
|
||||
**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
|
||||
|
||||
---
|
||||
|
||||
*For questions or issues with verification, please open an issue on GitHub.*
|
||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iJEEABYKADkWIQSfpUNtDuNgmFFXOCUX7KBfdo3t9gUCaeXaqxsUgAAAAAAEAA5t
|
||||
YW51MiwyLjUrMS4xMiwyLDIACgkQF+ygX3aN7fY6QAD/YCGJqs9HiRllFrF9EluE
|
||||
Ga4XUEQ/R6Q2zc+X6lX856sBAJIpxeMxUmMUXyr3xBAHxUf5eV+nQYkQQMKI81L1
|
||||
x8gL
|
||||
=VX6l
|
||||
-----END PGP SIGNATURE-----
|
||||
@@ -0,0 +1 @@
|
||||
f212d0425b38d5cd10da6dc804b60f143da23d4b07051aae31d0966082519b300af0e1c423683e0223738b33b138c687232b1c8bd68cf643777bbc5b588152bd ./export/thgtoa-dark.pdf
|
||||
@@ -0,0 +1,8 @@
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iJEEABYKADkWIQSfpUNtDuNgmFFXOCUX7KBfdo3t9gUCaeXaqxsUgAAAAAAEAA5t
|
||||
YW51MiwyLjUrMS4xMiwyLDIACgkQF+ygX3aN7fbdDgEAoSslLR47ydW/3r1wJOPY
|
||||
X/waLkVbkGZpHqwd4RjywwcA/3B7Ci+jUg+yP5TRsuChagEhwyO5vw2DxSlUGoB4
|
||||
+ksH
|
||||
=2ja9
|
||||
-----END PGP SIGNATURE-----
|
||||
@@ -0,0 +1,8 @@
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iJEEABYKADkWIQSfpUNtDuNgmFFXOCUX7KBfdo3t9gUCaeXaqxsUgAAAAAAEAA5t
|
||||
YW51MiwyLjUrMS4xMiwyLDIACgkQF+ygX3aN7faErgD/Svj1G+B7gmrZQ6AsLZ5J
|
||||
HfeldxjmrXE99dig1iHtl5IBAMndZZb+95TO03IZ9eLGfYuyTz4GCUanmftsY9yv
|
||||
LAIN
|
||||
=MEd0
|
||||
-----END PGP SIGNATURE-----
|
||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iJEEABYKADkWIQSfpUNtDuNgmFFXOCUX7KBfdo3t9gUCaeXaqxsUgAAAAAAEAA5t
|
||||
YW51MiwyLjUrMS4xMiwyLDIACgkQF+ygX3aN7favvgEAvFFSB5NrsrKMYvGG5ZYB
|
||||
iLIyt8Sn1rZmlVkibssMPq0BAImpZe8S7hWNkbukyEC4sLbKiOYvjbVipQHnrIUV
|
||||
xPMH
|
||||
=0hnj
|
||||
-----END PGP SIGNATURE-----
|
||||
@@ -0,0 +1 @@
|
||||
436ed0df78c299f95b8d5ff94f43f26ec2e7825d92d843fc15419630d55ed5e0c98485e738c12715a2b6242633faae38e8a98935b361d44ddde97a1692cb01a1 ./export/thgtoa.pdf
|
||||
@@ -0,0 +1,8 @@
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iJEEABYKADkWIQSfpUNtDuNgmFFXOCUX7KBfdo3t9gUCaeXaqxsUgAAAAAAEAA5t
|
||||
YW51MiwyLjUrMS4xMiwyLDIACgkQF+ygX3aN7fatsgEAixDzH+zTnKYMEx3sikWp
|
||||
dsNTiHTU6wJY/brVJIU879UBAJntBIq72vqwKtMb/ZlVvomdDvKVllZw8ZsYBz1n
|
||||
aTkM
|
||||
=vkgy
|
||||
-----END PGP SIGNATURE-----
|
||||
@@ -0,0 +1,8 @@
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iJEEABYKADkWIQSfpUNtDuNgmFFXOCUX7KBfdo3t9gUCaeXaqxsUgAAAAAAEAA5t
|
||||
YW51MiwyLjUrMS4xMiwyLDIACgkQF+ygX3aN7faAGQEAyEhVKrRoXIsV3E5f1FZg
|
||||
8fcsmbxCnKBqxichCkf0dWYBAIvbI146mQLHaNqLDaTIqCUQbkq1aE/YMFDGykUG
|
||||
ngsJ
|
||||
=/0RY
|
||||
-----END PGP SIGNATURE-----
|
||||
@@ -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*
|
||||
@@ -0,0 +1,8 @@
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iJEEABYKADkWIQSfpUNtDuNgmFFXOCUX7KBfdo3t9gUCaeXaqxsUgAAAAAAEAA5t
|
||||
YW51MiwyLjUrMS4xMiwyLDIACgkQF+ygX3aN7fYpCgEA209U3QewChp7mdrrFjH1
|
||||
CaBMIk2sCHwRMCcmbMDkNTAA/RIchAKex13ZjZWC9xsJpZEktvBENFsQLsNPReqR
|
||||
UZ8C
|
||||
=TYsa
|
||||
-----END PGP SIGNATURE-----
|
||||
+19
-1
@@ -9,6 +9,7 @@ repo_name: ""
|
||||
#edit_uri: ""
|
||||
theme:
|
||||
name: material
|
||||
locale: en
|
||||
favicon: media/profile.png
|
||||
icon:
|
||||
logo: material/bird
|
||||
@@ -17,6 +18,8 @@ theme:
|
||||
text: Public Sans
|
||||
code: Liberation Mono
|
||||
features:
|
||||
- navigation.instant
|
||||
- navigation.instant.prefetch
|
||||
- navigation.tabs
|
||||
- navigation.sections
|
||||
- toc.integrate
|
||||
@@ -121,5 +124,20 @@ markdown_extensions:
|
||||
permalink: true
|
||||
toc_depth: 3
|
||||
|
||||
nav:
|
||||
- Welcome: index.md
|
||||
- About: about/index.md
|
||||
- Verify: verify/index.md
|
||||
- Guide:
|
||||
- guide/index.md
|
||||
- Code:
|
||||
- code/index.md
|
||||
- Develop: code/develop.md
|
||||
- Contribute: contribute/index.md
|
||||
- Constitution: constitution/index.md
|
||||
- Mirrors: mirrors/index.md
|
||||
- Twitter: twitter/index.md
|
||||
- Releases: changelog/index.md
|
||||
|
||||
copyright: |
|
||||
© 2023-2025 <a href="https://anonymousplanet.org/" target="_blank" rel="noopener">Anonymous Planet</a>
|
||||
<a href="https://anonymousplanet.org/">The Hitchhiker's Guide</a> ©2023-2026 by <a href="https://psa.anonymousplanet.org/">Anonymous Planet</a> is licensed under <a href="https://creativecommons.org/licenses/by-nc/4.0/">CC BY-NC 4.0</a><img src="https://mirrors.creativecommons.org/presskit/icons/cc.svg" alt="" style="max-width: 1em;max-height:1em;margin-left: .2em;"><img src="https://mirrors.creativecommons.org/presskit/icons/by.svg" alt="" style="max-width: 1em;max-height:1em;margin-left: .2em;"><img src="https://mirrors.creativecommons.org/presskit/icons/nc.svg" alt="" style="max-width: 1em;max-height:1em;margin-left: .2em;"></a>
|
||||
|
||||
+105
-19
@@ -1,12 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build the MkDocs site, then render docs/guide/ to a single PDF via a Chromium-based browser.
|
||||
"""Experimental dark mode support.
|
||||
|
||||
Uses headless Chrome/Edge print-to-PDF (embeds images). WeasyPrint-based mkdocs-with-pdf is
|
||||
omitted here because it needs GTK/Pango (awkward on Windows).
|
||||
This script builds both light and dark mode MkDocs site, then renders docs/guide/ to single PDFs via Chromium.
|
||||
|
||||
Usage (from repo root):
|
||||
python scripts/build_guide_pdf.py
|
||||
python scripts/build_guide_pdf.py --site-dir build/html --pdf export/guide.pdf
|
||||
Usage:
|
||||
python scripts/build_guide_pdf.py # Generate light mode PDF only
|
||||
python scripts/build_guide_pdf.py --dark-mode # Generate dark mode PDF only
|
||||
python scripts/build_guide_pdf.py --both # Generate both light and dark mode PDFs
|
||||
|
||||
Examples:
|
||||
python scripts/build_guide_pdf.py --site-dir build/html --pdf-light export/thgtoa.pdf
|
||||
python scripts/build_guide_pdf.py --dark-mode --pdf-dark export/thgtoa-dark.pdf
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -64,15 +68,22 @@ def run_mkdocs(site_dir: Path) -> None:
|
||||
)
|
||||
|
||||
|
||||
def print_to_pdf(browser: Path, html_file: Path, pdf_out: Path) -> Path:
|
||||
def print_to_pdf(browser: Path, html_file: Path, pdf_out: Path, dark_mode: bool = False) -> Path:
|
||||
"""Write PDF to ``pdf_out``. Uses a temp file first so an open ``guide.pdf`` on Windows
|
||||
does not block the build: if the final path is locked, writes ``guide-new.pdf`` instead.
|
||||
|
||||
Args:
|
||||
browser: Path to Chromium executable
|
||||
html_file: Path to HTML file to convert
|
||||
pdf_out: Output PDF path
|
||||
dark_mode: If True, use dark mode color scheme via --prefers-color-scheme flag
|
||||
"""
|
||||
pdf_out.parent.mkdir(parents=True, exist_ok=True)
|
||||
partial = pdf_out.parent / f".{pdf_out.name}.writing"
|
||||
partial.unlink(missing_ok=True)
|
||||
|
||||
uri = html_file.resolve().as_uri()
|
||||
|
||||
# Chromium headless print; allow time for fonts/images on very large pages.
|
||||
cmd = [str(browser)]
|
||||
if os.environ.get("CI"):
|
||||
@@ -82,13 +93,22 @@ def print_to_pdf(browser: Path, html_file: Path, pdf_out: Path) -> Path:
|
||||
"--disable-setuid-sandbox",
|
||||
"--disable-dev-shm-usage",
|
||||
]
|
||||
|
||||
cmd += [
|
||||
"--headless=new",
|
||||
"--disable-gpu",
|
||||
"--no-pdf-header-footer",
|
||||
]
|
||||
|
||||
# Add dark mode preference if requested
|
||||
if dark_mode:
|
||||
cmd.append("--prefers-color-scheme=dark")
|
||||
|
||||
cmd += [
|
||||
f"--print-to-pdf={partial.resolve()}",
|
||||
uri,
|
||||
]
|
||||
|
||||
subprocess.run(cmd, check=True, timeout=600)
|
||||
deadline = time.time() + 120
|
||||
while time.time() < deadline:
|
||||
@@ -112,27 +132,77 @@ def print_to_pdf(browser: Path, html_file: Path, pdf_out: Path) -> Path:
|
||||
return pdf_out
|
||||
|
||||
|
||||
def generate_dark_mode_html(html_file: Path, output_file: Path, dark_css_path: Path) -> None:
|
||||
"""Create a temporary HTML file with dark mode stylesheet applied.
|
||||
|
||||
This is used when we need to force dark mode rendering via CSS rather than browser flags.
|
||||
"""
|
||||
try:
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
# Read the original HTML
|
||||
html_content = html_file.read_text(encoding='utf-8')
|
||||
soup = BeautifulSoup(html_content, 'html.parser')
|
||||
|
||||
# Add dark mode stylesheet link if not present
|
||||
existing_links = [link.get('href', '') for link in soup.find_all('link', rel='stylesheet')]
|
||||
if not any(dark_css_path.name in link for link in existing_links):
|
||||
head = soup.head or soup.new_tag('head')
|
||||
link_tag = soup.new_tag('link', rel='stylesheet', href=str(dark_css_path))
|
||||
if soup.head:
|
||||
soup.head.append(link_tag)
|
||||
else:
|
||||
# Create a new head section
|
||||
new_head = soup.new_tag('head')
|
||||
new_head.append(link_tag)
|
||||
soup.insert(0, new_head)
|
||||
|
||||
# Write the modified HTML
|
||||
output_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
output_file.write_text(str(soup), encoding='utf-8')
|
||||
except ImportError:
|
||||
print("BeautifulSoup not available. Skipping CSS injection.")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
root = repo_root()
|
||||
ap = argparse.ArgumentParser(description="Build MkDocs + single-page guide PDF.")
|
||||
ap = argparse.ArgumentParser(description="Build MkDocs + single-page guide PDF (light and/or dark mode).")
|
||||
ap.add_argument(
|
||||
"--site-dir",
|
||||
type=Path,
|
||||
default=root / "site",
|
||||
help="MkDocs output directory (default: ./site)",
|
||||
default=root / "build" / "html",
|
||||
help="MkDocs output directory (default: ./build/html)",
|
||||
)
|
||||
ap.add_argument(
|
||||
"--pdf",
|
||||
"--pdf-light",
|
||||
type=Path,
|
||||
default=root / "export" / "guide.pdf",
|
||||
help="Output PDF path (default: ./export/guide.pdf)",
|
||||
default=root / "export" / "thgtoa.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/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")
|
||||
ap.add_argument("--both", action="store_true", help="Generate both light and dark mode PDFs")
|
||||
args = ap.parse_args()
|
||||
|
||||
# Determine which modes to generate
|
||||
if args.dark_mode:
|
||||
modes = ["dark"]
|
||||
elif args.both:
|
||||
modes = ["light", "dark"]
|
||||
else:
|
||||
modes = ["light"]
|
||||
|
||||
guide_html = args.site_dir / "guide" / "index.html"
|
||||
if not args.skip_mkdocs:
|
||||
|
||||
if not args.skip_mkdocs or any(mode == "light" for mode in modes):
|
||||
run_mkdocs(args.site_dir)
|
||||
|
||||
if not guide_html.is_file():
|
||||
print(f"Missing {guide_html}; run without --skip-mkdocs first.", file=sys.stderr)
|
||||
return 1
|
||||
@@ -146,14 +216,30 @@ def main() -> int:
|
||||
)
|
||||
return 1
|
||||
|
||||
out = print_to_pdf(browser, guide_html, args.pdf)
|
||||
size_kb = out.stat().st_size // 1024
|
||||
print(f"Wrote {out.resolve()} ({size_kb} KiB)")
|
||||
if out.resolve() != args.pdf.resolve():
|
||||
dark_css_path = root / "docs" / "stylesheets" / "dark-extra.css"
|
||||
|
||||
# Generate light mode PDF (default)
|
||||
if "light" in modes:
|
||||
out_light = print_to_pdf(browser, guide_html, args.pdf_light, dark_mode=False)
|
||||
size_kb = out_light.stat().st_size // 1024
|
||||
print(f"Wrote {out_light.resolve()} ({size_kb} KiB) [Light Mode]")
|
||||
if out_light.resolve() != args.pdf_light.resolve():
|
||||
print(
|
||||
f"Note: {args.pdf.name} was in use; close it and rename or replace with the file above.",
|
||||
f"Note: {args.pdf_light.name} was in use; close it and rename or replace with the file above.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
# Generate dark mode PDF
|
||||
if "dark" in modes:
|
||||
out_dark = print_to_pdf(browser, guide_html, args.pdf_dark, dark_mode=True)
|
||||
size_kb = out_dark.stat().st_size // 1024
|
||||
print(f"Wrote {out_dark.resolve()} ({size_kb} KiB) [Dark Mode]")
|
||||
if out_dark.resolve() != args.pdf_dark.resolve():
|
||||
print(
|
||||
f"Note: {args.pdf_dark.name} was in use; close it and rename or replace with the file above.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,288 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Setup helper for PDF workflow configuration.
|
||||
|
||||
This script helps you configure the necessary GitHub Secrets for the automated
|
||||
PDF build, signing, and VirusTotal scanning workflows.
|
||||
|
||||
Usage:
|
||||
python scripts/setup_workflow.py
|
||||
|
||||
Requirements:
|
||||
- Python 3.8+
|
||||
- GPG installed (for key export)
|
||||
- Access to GitHub repository settings
|
||||
|
||||
What it does:
|
||||
1. Validates your GPG key setup
|
||||
2. Exports the public key for verification
|
||||
3. Provides instructions for adding secrets to GitHub
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def repo_root() -> Path:
|
||||
return Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
def check_gpg_installed() -> bool:
|
||||
"""Check if GPG is installed and accessible."""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["gpg", "--version"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10,
|
||||
)
|
||||
return result.returncode == 0
|
||||
except (FileNotFoundError, subprocess.TimeoutExpired):
|
||||
return False
|
||||
|
||||
|
||||
def list_gpg_keys() -> list[dict]:
|
||||
"""List all GPG keys in the keyring."""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["gpg", "--list-keys", "--with-colons"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
|
||||
keys = []
|
||||
current_key = {}
|
||||
|
||||
for line in result.stdout.split('\n'):
|
||||
if line.startswith('pub:'):
|
||||
if current_key:
|
||||
keys.append(current_key)
|
||||
parts = line.split(':')
|
||||
current_key = {
|
||||
'type': parts[1],
|
||||
'key_id': parts[4],
|
||||
'fingerprint': parts[9] if len(parts) > 9 else None,
|
||||
'created': parts[5],
|
||||
'expires': parts[6],
|
||||
'uid': None,
|
||||
}
|
||||
elif line.startswith('uid:'):
|
||||
parts = line.split(':')
|
||||
current_key['uid'] = parts[9] if len(parts) > 9 else None
|
||||
|
||||
if current_key:
|
||||
keys.append(current_key)
|
||||
|
||||
return keys
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error listing GPG keys: {e}")
|
||||
return []
|
||||
|
||||
|
||||
def export_public_key(key_id: str, output_file: Path | None = None) -> str | None:
|
||||
"""Export a public key in ASCII armor format."""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["gpg", "--armor", "--export", key_id],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
|
||||
if output_file:
|
||||
output_file.write_text(result.stdout)
|
||||
print(f"✓ Public key exported to {output_file}")
|
||||
|
||||
return result.stdout
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error exporting public key: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def export_private_key(key_id: str, output_file: Path | None = None) -> str | None:
|
||||
"""Export a private key in ASCII armor format (requires passphrase)."""
|
||||
try:
|
||||
# This will prompt for passphrase interactively
|
||||
result = subprocess.run(
|
||||
["gpg", "--armor", "--export-secret-keys", key_id],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
|
||||
if output_file:
|
||||
output_file.write_text(result.stdout)
|
||||
print(f"✓ Private key exported to {output_file}")
|
||||
|
||||
return result.stdout
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error exporting private key: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def validate_gpg_key(key_id: str) -> bool:
|
||||
"""Validate that a GPG key has signing capability."""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["gpg", "--list-keys", "--with-colons", key_id],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
|
||||
# Check for 's' (signing) in the pub line
|
||||
for line in result.stdout.split('\n'):
|
||||
if line.startswith('pub:'):
|
||||
flags = line.split(':')[1]
|
||||
return 's' in flags
|
||||
|
||||
return False
|
||||
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
|
||||
|
||||
def print_setup_instructions():
|
||||
"""Print instructions for configuring GitHub Secrets."""
|
||||
print("\n" + "="*70)
|
||||
print("GITHUB SECRETS SETUP INSTRUCTIONS")
|
||||
print("="*70)
|
||||
|
||||
print("""
|
||||
To enable the automated PDF workflow, you need to add three secrets to your
|
||||
GitHub repository:
|
||||
|
||||
1. GPG_PRIVATE_KEY
|
||||
- Your GPG private key in ASCII armor format
|
||||
- Used to sign PDFs and hash files
|
||||
- IMPORTANT: Keep this secret! Never commit it publicly
|
||||
|
||||
2. GPG_PASSPHRASE
|
||||
- The passphrase for your GPG private key
|
||||
- Required to unlock the private key for signing
|
||||
|
||||
3. VT_API_KEY (optional but recommended)
|
||||
- VirusTotal API key for malware scanning
|
||||
|
||||
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
|
||||
""")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
print("\n" + "="*70)
|
||||
print("PDF WORKFLOW SETUP HELPER")
|
||||
print("="*70)
|
||||
|
||||
# Check GPG installation
|
||||
if not check_gpg_installed():
|
||||
print("⚠ WARNING: GPG is not installed or not in PATH")
|
||||
print("Please install GPG before continuing:")
|
||||
print(" - Linux: sudo apt install gnupg")
|
||||
print("\nContinuing anyway...")
|
||||
|
||||
# List available keys
|
||||
print("\n🔑 Available GPG Keys:")
|
||||
print("-" * 70)
|
||||
|
||||
keys = list_gpg_keys()
|
||||
|
||||
if not keys:
|
||||
print("No GPG keys found in your keyring.")
|
||||
print("Generate a key with: gpg --full-generate-key")
|
||||
return 1
|
||||
|
||||
for i, key in enumerate(keys, 1):
|
||||
status = "✓" if validate_gpg_key(key['key_id']) else "✗"
|
||||
print(f"\n{i}. {status} Key ID: {key['key_id']}")
|
||||
print(f" Fingerprint: {key.get('fingerprint', 'N/A')}")
|
||||
print(f" UID: {key.get('uid', 'Unknown')}")
|
||||
print(f" Created: {key.get('created', 'Unknown')}")
|
||||
|
||||
if key.get('expires'):
|
||||
print(f" Expires: {key['expires']}")
|
||||
|
||||
# Ask user to select key
|
||||
print("\n" + "-" * 70)
|
||||
try:
|
||||
choice = input("\nEnter the number of the key you want to use (1-{}): ".format(len(keys)))
|
||||
selected_index = int(choice) - 1
|
||||
|
||||
if not (0 <= selected_index < len(keys)):
|
||||
print("Invalid selection!")
|
||||
return 1
|
||||
|
||||
except ValueError:
|
||||
print("Invalid input! Please enter a number.")
|
||||
return 1
|
||||
|
||||
selected_key = keys[selected_index]
|
||||
|
||||
# Validate key has signing capability
|
||||
if not validate_gpg_key(selected_key['key_id']):
|
||||
print(f"\n⚠ WARNING: Selected key does not have signing capability!")
|
||||
print("You need a key with 's' (signing) flag for PDF signatures.")
|
||||
confirm = input("Continue anyway? (y/N): ")
|
||||
if confirm.lower() != 'y':
|
||||
return 1
|
||||
|
||||
# Export public key
|
||||
print(f"\n📤 Exporting public key for {selected_key['uid']}...")
|
||||
public_key_file = repo_root() / "pgp" / "workflow-public.asc"
|
||||
|
||||
public_key = export_public_key(selected_key['key_id'], public_key_file)
|
||||
|
||||
if not public_key:
|
||||
print("Failed to export public key!")
|
||||
return 1
|
||||
|
||||
# Show public key info
|
||||
print("\n✓ Public Key Information:")
|
||||
print("-" * 70)
|
||||
for line in public_key.split('\n')[:5]:
|
||||
print(line)
|
||||
print("...")
|
||||
|
||||
# Instructions for private key export
|
||||
print("\n🔐 Private Key Export:")
|
||||
print("-" * 70)
|
||||
print("""
|
||||
To get your private key for the GPG_PRIVATE_KEY secret:
|
||||
|
||||
1. Run this command (you'll be prompted for passphrase):
|
||||
gpg --armor --export-secret-keys {} > workflow-private.asc
|
||||
|
||||
2. Copy the ENTIRE output including BEGIN and END lines
|
||||
|
||||
3. Add it to GitHub Secrets as 'GPG_PRIVATE_KEY'
|
||||
|
||||
⚠ IMPORTANT: Keep your private key secure! Never commit it publicly.
|
||||
""".format(selected_key['key_id']))
|
||||
|
||||
# Print setup instructions
|
||||
print_setup_instructions()
|
||||
|
||||
print("\n" + "="*70)
|
||||
print("SETUP COMPLETE!")
|
||||
print("="*70)
|
||||
print(f"\nPublic key saved to: {public_key_file}")
|
||||
print("Next steps:")
|
||||
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/dev-workflow.md\n")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,214 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Verification script for PDF files.
|
||||
|
||||
This script verifies:
|
||||
1. SHA256 hash integrity of PDF files
|
||||
2. GPG signature authenticity
|
||||
3. VirusTotal scan status (optional)
|
||||
|
||||
Usage:
|
||||
python scripts/verify_pdf.py --all # Verify everything
|
||||
python scripts/verify_pdf.py --hashes # Only verify hashes
|
||||
python scripts/verify_pdf.py --signatures # Only verify signatures
|
||||
python scripts/verify_pdf.py --vt # Check VT status (requires API key)
|
||||
|
||||
Examples:
|
||||
python scripts/verify_pdf.py --all
|
||||
python scripts/verify_pdf.py --hashes --file export/thgtoa.pdf
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import os
|
||||
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()
|
||||
with open(file_path, "rb") as f:
|
||||
for byte_block in iter(lambda: f.read(4096), b""):
|
||||
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)
|
||||
is_valid = actual_hash == expected_hash
|
||||
status = "✓ PASS" if is_valid else "✗ FAIL"
|
||||
print(f"{status}: {file_path.name}")
|
||||
print(f" Expected: {expected_hash}")
|
||||
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():
|
||||
print(f"✗ FAIL: Signature file not found: {sig_file}")
|
||||
return False
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["gpg", "--verify", str(sig_file), str(file_path)],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
print(f"✓ PASS: {file_path.name} signature verified")
|
||||
# Extract key info from GPG output
|
||||
for line in result.stdout.split('\n'):
|
||||
if 'Good signature' in line or 'key ID' in line.lower():
|
||||
print(f" {line.strip()}")
|
||||
return True
|
||||
else:
|
||||
print(f"✗ FAIL: {file_path.name} signature verification failed")
|
||||
print(f" Error: {result.stderr}")
|
||||
return False
|
||||
|
||||
except FileNotFoundError:
|
||||
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():
|
||||
print(f"✗ FAIL: Hash file not found: {hash_file}")
|
||||
return False
|
||||
|
||||
expected_hash = None
|
||||
with open(hash_file, 'r') as f:
|
||||
for line in f:
|
||||
parts = line.strip().split()
|
||||
if len(parts) >= 2 and parts[1] == str(file_path):
|
||||
expected_hash = parts[0]
|
||||
break
|
||||
|
||||
if not expected_hash:
|
||||
print(f"✗ FAIL: Hash not found in {hash_file.name} for {file_path.name}")
|
||||
return False
|
||||
|
||||
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:
|
||||
print("⚠ WARNING: VT_API_KEY not set. Skipping VirusTotal check.")
|
||||
return None
|
||||
|
||||
try:
|
||||
import urllib.request
|
||||
import json
|
||||
|
||||
url = f"https://www.virustotal.com/api/v3/files/{file_hash}"
|
||||
request = urllib.request.Request(url, headers={"x-apikey": api_key})
|
||||
|
||||
with urllib.request.urlopen(request, timeout=30) as response:
|
||||
data = json.loads(response.read().decode())
|
||||
|
||||
stats = data.get('data', {}).get('attributes', {}).get('last_analysis_stats', {})
|
||||
total = sum(stats.values()) if stats else 0
|
||||
|
||||
print(f"\n🦠 VirusTotal Results for {file_hash[:16]}...")
|
||||
print(f" Total scans: {total}")
|
||||
|
||||
if stats:
|
||||
print(f" Malicious: {stats.get('malicious', 0)}")
|
||||
print(f" Suspicious: {stats.get('suspicious', 0)}")
|
||||
print(f" Undetected: {stats.get('undetected', 0)}")
|
||||
print(f" Clean: {stats.get('harmless', 0)}")
|
||||
|
||||
return data
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠ ERROR checking VirusTotal: {e}")
|
||||
return None
|
||||
|
||||
def main() -> int:
|
||||
root = repo_root()
|
||||
ap = argparse.ArgumentParser(description="Verify PDF files (hashes, signatures, VT).")
|
||||
|
||||
# File paths
|
||||
ap.add_argument(
|
||||
"--light-pdf",
|
||||
type=Path,
|
||||
default=root / "export" / "thgtoa.pdf",
|
||||
help="Light mode PDF file",
|
||||
)
|
||||
ap.add_argument(
|
||||
"--dark-pdf",
|
||||
type=Path,
|
||||
default=root / "export" / "thgtoa-dark.pdf",
|
||||
help="Dark mode PDF file",
|
||||
)
|
||||
ap.add_argument(
|
||||
"--hash-file",
|
||||
type=Path,
|
||||
default=root / "export" / "thgtoa.pdf.sha256",
|
||||
help="Hash file to verify against",
|
||||
)
|
||||
|
||||
# Verification modes
|
||||
group = ap.add_mutually_exclusive_group()
|
||||
group.add_argument("--all", action="store_true", help="Verify everything")
|
||||
group.add_argument("--hashes", action="store_true", help="Only verify hashes")
|
||||
group.add_argument("--signatures", action="store_true", help="Only verify signatures")
|
||||
ap.add_argument("--vt", action="store_true", help="Check VirusTotal status")
|
||||
|
||||
args = ap.parse_args()
|
||||
|
||||
# Determine what to verify
|
||||
if not any([args.all, args.hashes, args.signatures, args.vt]):
|
||||
args.all = True
|
||||
|
||||
all_passed = True
|
||||
|
||||
pdf_files = [
|
||||
("Light", args.light_pdf),
|
||||
("Dark", args.dark_pdf),
|
||||
]
|
||||
|
||||
for mode_name, pdf_file in pdf_files:
|
||||
if not pdf_file.exists():
|
||||
print(f"⚠ WARNING: {pdf_file.name} not found. Skipping.")
|
||||
continue
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(f"Verifying {mode_name} PDF: {pdf_file.name}")
|
||||
print('='*60)
|
||||
|
||||
# Verify hash if requested
|
||||
if args.all or args.hashes:
|
||||
if not verify_from_hash_file(pdf_file, args.hash_file):
|
||||
all_passed = False
|
||||
|
||||
# Verify signature if requested
|
||||
if args.all or args.signatures:
|
||||
sig_file = pdf_file.with_suffix(pdf_file.suffix + ".sig")
|
||||
result = verify_signature(pdf_file, sig_file)
|
||||
if result is False: # None means skipped (GPG not installed)
|
||||
all_passed = False
|
||||
|
||||
# Check VirusTotal if requested
|
||||
if args.all or args.vt:
|
||||
file_hash = calculate_sha256(pdf_file)
|
||||
api_key = os.environ.get("VT_API_KEY")
|
||||
check_virustotal(file_hash, api_key)
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
if all_passed:
|
||||
print("✓ All verifications PASSED")
|
||||
return 0
|
||||
else:
|
||||
print("✗ Some verifications FAILED")
|
||||
return 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user