33 Commits

Author SHA1 Message Date
nopeitsnothing 04f5036f3e docs(meta): update license
See LICENSE for new legal licensing information.
2026-06-18 06:25:30 -04:00
nopeitsnothing f079b5aba8 docs(web!): get new web design up
Serve the new look, update licensing.

  Serve the new CSS layouts for the web interface including:
  - Main documentation overhaul in docs/*.md files
  - New LICENSE file (CC BY-SA 4.0)
  - Hacker-themed stylesheets (hacker.css, hacker-extra.css)
  - Footer fixes and navigation improvements
  - Updated PGP key cross-signing

  **Additional CSS work:**
  - Social links inline fix for footer
  - CRT overlay cleanup from license display
  - New accessibility stylesheets
2026-06-18 06:06:55 -04:00
nopeitsnothing fc3763f85c docs(web!): get new web design up
Serve the new look, update licensing.
2026-06-18 05:37:28 -04:00
nopeitsnothing 4e26ae486b fix(css): display social links inline on same line
- Added CSS rules to force all .social and .social__link elements to use flexbox
  display instead of block, preventing them from wrapping onto new lines
- Force social icons to remain on same line with nowrap white-space
- Adjust gap between icons for better spacing (0.75em)
- Responsive adjustments for mobile screens
- Fixed icon sizes to match inline display requirements
2026-06-14 14:14:31 -04:00
nopeitsnothing 9dee4c7691 fix(css): cleanup footer license display by removing CRT overlay effects 2026-06-14 13:39:42 -04:00
nopeitsnothing 4579c5f14e chore(ci): fix domain
New domain, who dis?
2026-06-08 18:10:21 -04:00
nopeitsnothing bf00272811 ci(web): use new domain (anonymousplanet.net)
This is the new domain.
2026-06-03 18:04:07 -04:00
github-actions[bot] ae1c293c6b docs(pgp): cross-signing pgp keys
Release cross-signed copies of PGP keys per Qubes-spec.
2026-06-03 17:56:31 -04:00
nopeitsnothing 45a8539a9e ci(GitHub-CI): draft only, also use version output
Set the draft to true and manually verify tags before release
Set our version tag so we use [vX.X.X] for cleaner release
2026-05-31 06:24:20 -04:00
nopeitsnothing cc5ad371a8 ci(darktea): change the repo URL for our tor mirror
The repo is defunct - please use the new one.
2026-05-31 03:42:30 -04:00
nopeitsnothing 84a7ccbdd9 fix: fix inline reference
The ref should be [link](#target)
2026-05-30 11:49:26 -04:00
nopeitsnothing 4eaca49a1c style(docs): fix recommended reading admonition
- docs/about/index.md: convert "Recommended Reading" to collapsible
  admonition
2026-05-30 11:01:51 -04:00
nopeitsnothing c5e5ae48e1 ci: refactoring some things and removing others
Lots of source additions here from long-standing notes over the past few
months. Squashed to make it neater than 219 commits.

- bump version to v1.2.4, Jun 2026
- expand Tor section with new "Traffic analysis and the limits of Tor" subsection
  guard node persistence, website fingerprinting, and a practical breakdown of
  when Tor is and is not sufficient
- expand hardware/firmware threat section with new subsections on firmware
  implants, USB attack hardware (O.MG Cable, Rubber Ducky), Evil Maid attacks,
  supply chain compromise, and a physical inspection checklist
- rename "Removing Metadata from Files/Documents/Pictures" section to "Metadata
  auditing"; add reference table of tools by file type; expand EXIF/XMP coverage,
  PDF metadata (font fingerprinting), and DOCX revision history with real-world
  source identification cases; restructure subsections
- add introductory paragraph to "Your Metadata" section
- add new appendix B8: operational security failure case studies with common
  threads
- add new appendix B9: post-quantum cryptography covering HNDL threat, NIST PQC
  standards, Signal's PQXDH, browser hybrid KEM, PGP limitations, VPN guidance,
  and Monero note
- add new appendix C1: stylometric analysis and writing style covering features
  measured, deployed tools, real cases (J.K. Rowling), effective and ineffective
  countermeasures including AI rewriting
- fix Dangerzone GitHub URL (firstlook -> freedomofpress)
- Remove duplicate footnote [^500]; minor wording fixes ("users" -> "people",
  passive voice tweaks, cross-reference updates)

- docs/index.md: both MSK and RSK GPG fingerprints in a collapsible tip admonition
  instead of bare text
- docs/about/index.md: convert Note admonitions to tip; reformat social media
  links into collapsible tip block
- docs/mirrors/index.md: simplify PDF download instructions to point to Releases;
- README.md: add star history chart
- mkdocs.yml: rename site to "The Hitchhiker's Guide"; update site description
  with hashtags

- sign.yml: remove commented-out workflow_run trigger and if: condition; add
  verify job that runs after sign, downloads artifacts, runs verify_pdf.py, and
  writes a full job summary with hashes; update artifact upload description; minor
  comment and whitespace cleanup
- release.yml, changelog.yml: replace decorative banner comments with single-line
  comments; fix trailing-space style in permissions block
- publish.yml: remove stale comment about nomaterial theme
- verify_pdf.py: full rewrite: replace single-hash-file lookup with flexible
  resolver that checks both bare hash files (.sha256, .b2sum) and two-column
  sumfiles (sha256sums.txt, b2sums.txt); add BLAKE2b verification alongside
  SHA-256; fix signature extension (.asc not .sig); improve CLI (--file,
  --export-dir flags; remove --all; default runs all checks); improve VirusTotal
  output with direct link; cleaner output formatting with ruled separators
2026-05-30 09:32:16 -04:00
nopeitsnothing d1817e9049 ci(pipeline): more meta changes to the pipeline
pre-commit install --install-hooks
2026-05-27 23:49:19 -04:00
nopeitsnothing ede2a53437 ci(pipeline): replace semver tagging with timestamp tags, drop tag_release.py
- release.yml now generates release-YYYYMMDD-<sha> tags automatically
- changelog.yml requires explicit version input, no auto-increment from tags
- sign.yml normalises extensions to .asc and .b2sum
- build-sign-release.yml neutered to a no-op with descriptive error
- tag_release.py archived to scripts/archived/
- update_changelog.py: version_from_changelog() is now primary version source
- .gitignore: fix export/ tracking to match actual file extensions
- docs/code/develop.md: fully rewritten to reflect new manual four-step flow
2026-05-27 23:49:19 -04:00
github-actions[bot] 91a77ed552 chore(export): update PDFs, hashes and signatures [skip ci] 2026-05-26 00:14:20 +00:00
nopeitsnothing 1c3cf75cf0 ci(github): Manual only
automatic triggering is disabled to prevent version mismatches
2026-05-25 19:34:04 -04:00
github-actions[bot] 121be79cd8 docs: update changelog [skip ci] 2026-05-24 12:03:20 +00:00
nopeitsnothing 3b550119a8 chore(lint): commitizen passes
Passed a couple times through the automatic linter to fix some markdown

Signed-off-by: nopeitsnothing <no@anonymousplanet.org>
2026-05-24 07:58:11 -04:00
nopeitsnothing c19389ce49 change(changelog): v1.2.3
Signed-off-by: nopeitsnothing <no@anonymousplanet.org>
2026-05-24 07:57:59 -04:00
nopeitsnothing aabcbac3d9 fix(develop): we use the Anonymous Planet RSK for releases
Signed-off-by: nopeitsnothing <no@anonymousplanet.org>
2026-05-24 01:16:29 -04:00
nopeitsnothing e11a1eb1ce fix(release): sign using RSK instead
Signed-off-by: nopeitsnothing <no@anonymousplanet.org>
2026-05-24 01:05:31 -04:00
nopeitsnothing df6cfbc94b ci(release): auto-increment using [vX.X.X]
Keep it clean, simple, only include the semver tag:

LATEST=$(git tag --list 'v*' --sort=-version:refname \
  | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+

Signed-off-by: nopeitsnothing <no@anonymousplanet.org>
2026-05-24 00:35:59 -04:00
github-actions[bot] 095bb0d8be docs: update changelog [skip ci] 2026-05-24 04:12:03 +00:00
nopeitsnothing 8b81081089 change(changelog): only use "vX.X.X" in version tags
Signed-off-by: nopeitsnothing <no@anonymousplanet.org>
2026-05-24 00:10:06 -04:00
nopeitsnothing ccc97461c9 add(changelog): explain missing v1.2.2 tag
v1.2.2 contained broken Python and other additions that were not meant for release

Signed-off-by: nopeitsnothing <no@anonymousplanet.org>
2026-05-24 00:07:25 -04:00
github-actions[bot] 8d74635d49 docs: update changelog [skip ci] 2026-05-24 03:59:47 +00:00
nopeitsnothing f71e5e2a28 fix(changelog): prevent history dump and filter noise commits
commits_since(): when no prior tag exists, scope to commits not yet on
origin/main via merge-base instead of walking the entire history. This
is what caused the v2.0.1 entry to contain every commit back to project
inception.

categorise(): replace the minimal skip pattern with a compiled NOISE
regex that also drops:
  - numbered series commits (3/8, 7/8, etc.)
  - vague WIP messages (Tweaking, Moving some, Still broken, pt2...)
  - one-word infrastructure fixes (Fix workflow, Fix path, Fix README)
  - oops commits (Forgot to, Revert "...")
  - joke messages (One job to rule them all)

Signed-off-by: nopeitsnothing <no@anonymousplanet.org>
2026-05-23 23:55:34 -04:00
nopeitsnothing 3e28ec19ad fix(convert): actually save per-page PDFs for qpdf, not PNGs
We ignore this for the guide

Signed-off-by: nopeitsnothing <no@anonymousplanet.org>
2026-05-23 23:21:14 -04:00
github-actions[bot] 192da89138 docs: update changelog [skip ci] 2026-05-24 03:05:09 +00:00
nopeitsnothing c658c354ee fix(convert): actually save per-page PDFs for qpdf, not PNGs
Previous filesystem edits to _save_images_as_pdf did not persist to
disk. Rewrote the function: quantize each dark-themed RGB image to
palette mode (256 colours, FASTOCTREE) so Pillow uses zlib/deflate
instead of JPEG (no libjpeg needed), save each as a single-page PDF,
then merge with qpdf. qpdf only accepts PDF inputs to --pages.

Also restores the orphaned footnote citations [^536] and [^537] in
docs/guide/index.md at the key disclosure law paragraph (line 8586).
Previous edit also did not persist to disk.

Signed-off-by: nopeitsnothing <no@anonymousplanet.org>
2026-05-23 22:57:26 -04:00
nopeitsnothing 343ad7f037 fix(convert): fail fast with helpful message if pdftoppm or qpdf missing
Previously the script crashed with a FileNotFoundError traceback when
system tools were absent. Now _check_dependencies() runs before any
work begins and prints install instructions for Linux/WSL, macOS, and
a pointer to develop.md for Windows.

Signed-off-by: nopeitsnothing <no@anonymousplanet.org>
2026-05-23 22:53:28 -04:00
nopeitsnothing 85ea1fee66 docs(develop): rewrite developer guide for current pipeline
Replaces the thin stub describing the old monolithic workflow with a
full developer reference covering:

- Prerequisites (Linux/macOS/Windows tabs)
- Repository layout
- Local build instructions for both PDFs and the MkDocs site
- Pipeline flow diagram (build → sign → release → changelog)
- What to check before pushing
- Every GitHub Secret: what it is, how to generate it, what breaks
  without it, and a summary table
- Step-by-step release process using tag_release.py
- Release verification instructions (GPG + hash checks)
- Troubleshooting section for every known CI failure mode

Signed-off-by: nopeitsnothing <no@anonymousplanet.org>
2026-05-23 22:48:13 -04:00
61 changed files with 4538 additions and 2042 deletions
+9
View File
@@ -0,0 +1,9 @@
[tool.commitizen]
name = "cz_conventional_commits"
# enforce sign-off below as well
extra_arguments = ["-S", "--signoff"]
# harmless redundancy
[tool.commitizen.customize]
schema_pattern = '^(feat|add|fix|bugfix|revert|security|perf|refactor|change|chore|ci|docs|style|test|build)(\(.+\))?(!)?: .{1,72}(\n.*)*\nSigned-off-by: .+ <.+@.+>'
@@ -1,3 +1,8 @@
# 1. Push to main → 01-build.yml runs automatically → note the run ID
# 2. Manually trigger 02-sign.yml with that build run ID → note the sign run ID
# 3. Manually trigger 03-release.yml with: version=v1.2.5, sign_run_id=<id>
# 4. Manually trigger 04-changelog.yml with: version=v1.2.5
name: 📖 Build PDFs
on:
@@ -19,7 +24,7 @@ on:
- "docs/**"
- "mkdocs.yml"
- "scripts/**"
- ".github/workflows/build.yml"
- ".github/workflows/01-build.yml"
permissions:
contents: read
+256
View File
@@ -0,0 +1,256 @@
name: 🔏 Sign PDFs
# Can be triggered:
# 1. Automatically after build.yml completes on main
# 2. Manually, pointing at a specific build run to pull PDFs from
on:
workflow_dispatch:
inputs:
build_run_id:
description: 'build.yml run ID to download PDFs from'
required: true
type: string
# Download artifacts from other runs + commit export/ files back to the repo
permissions:
actions: read
contents: write
jobs:
sign:
name: Hash & Sign PDFs
runs-on: ubuntu-latest
outputs:
light_sha256: ${{ steps.hashes.outputs.light_sha256 }}
dark_sha256: ${{ steps.hashes.outputs.dark_sha256 }}
light_b2: ${{ steps.hashes.outputs.light_b2 }}
dark_b2: ${{ steps.hashes.outputs.dark_b2 }}
steps:
- name: 🛠️ Checkout (for pgp/ key reference only)
uses: actions/checkout@v4
with:
sparse-checkout: pgp
- name: 📥 Download PDF artifacts
uses: actions/download-artifact@v4
with:
name: pdfs
path: export/
run-id: ${{ inputs.build_run_id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: 📋 List downloaded files
run: ls -lh export/
# Hash - extensions match export/ conventions: .sha256, .b2sum
- name: "#️⃣ Hash PDFs"
id: hashes
run: |
cd export
for f in thgtoa.pdf thgtoa-dark.pdf; do
[ -f "$f" ] || continue
sha256sum "$f" | awk '{print $1}' > "${f}.sha256"
b2sum "$f" | awk '{print $1}' > "${f}.b2sum"
done
# Combined summary files
sha256sum thgtoa.pdf thgtoa-dark.pdf 2>/dev/null > sha256sums.txt || \
sha256sum thgtoa.pdf 2>/dev/null > sha256sums.txt
b2sum thgtoa.pdf thgtoa-dark.pdf 2>/dev/null > b2sums.txt || \
b2sum thgtoa.pdf 2>/dev/null > b2sums.txt
light_sha256=$(cat thgtoa.pdf.sha256 2>/dev/null || echo "")
dark_sha256=$(cat thgtoa-dark.pdf.sha256 2>/dev/null || echo "")
light_b2=$(cat thgtoa.pdf.b2sum 2>/dev/null || echo "")
dark_b2=$(cat thgtoa-dark.pdf.b2sum 2>/dev/null || echo "")
echo "light_sha256=$light_sha256" >> $GITHUB_OUTPUT
echo "dark_sha256=$dark_sha256" >> $GITHUB_OUTPUT
echo "light_b2=$light_b2" >> $GITHUB_OUTPUT
echo "dark_b2=$dark_b2" >> $GITHUB_OUTPUT
echo "--- SHA-256 ---"
cat sha256sums.txt
echo "--- BLAKE2b ---"
cat b2sums.txt
# GPG sign — detached ASCII-armor signatures use .asc extension
- name: 🔑 Install GPG
run: |
sudo apt-get update -qq
sudo apt-get install -y gnupg
- name: 🔏 Import GPG signing key
env:
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
run: |
echo "$GPG_PRIVATE_KEY" | gpg --batch --import
echo "$GPG_PASSPHRASE" | gpg --batch --yes --passphrase-fd 0 \
--pinentry-mode loopback --list-secret-keys
- name: 🔏 Sign PDFs and hash files
env:
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
run: |
sign() {
local file="$1"
[ -f "$file" ] || return 0
echo "$GPG_PASSPHRASE" | gpg --batch --yes --passphrase-fd 0 \
--pinentry-mode loopback \
--detach-sign --armor --output "${file}.asc" "$file"
echo "Signed: $file → ${file}.asc"
}
sign export/thgtoa.pdf
sign export/thgtoa-dark.pdf
sign export/sha256sums.txt
sign export/b2sums.txt
# Commit export/ back to main
- name: 📦 Checkout full repo for commit
uses: actions/checkout@v4
with:
ref: main
fetch-depth: 0
path: repo
- name: 📂 Copy export files into repo
run: cp -v export/* repo/export/
- name: 🔏 Configure SSH commit signing
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ACTIONS_SSH_SIGNING_KEY }}" > ~/.ssh/signing_key
chmod 600 ~/.ssh/signing_key
git config --global gpg.format ssh
git config --global user.signingKey ~/.ssh/signing_key
git config --global commit.gpgSign true
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
# If no change in git diff, do nothing
- name: 📤 Commit and push export/ to main
working-directory: repo
run: |
git add export/
if git diff --cached --quiet; then
echo "Nothing to commit — export/ is already up to date."
else
git commit -S -m "chore(export): update PDFs, hashes and signatures [skip ci]"
git push origin main
fi
# Upload artifacts for 03-release.yml and verify job to consume
- name: 📤 Upload signatures artifact
uses: actions/upload-artifact@v4
with:
name: signatures
path: |
export/sha256sums.txt
export/b2sums.txt
export/thgtoa.pdf.sha256
export/thgtoa-dark.pdf.sha256
export/thgtoa.pdf.b2sum
export/thgtoa-dark.pdf.b2sum
export/thgtoa.pdf.asc
export/thgtoa-dark.pdf.asc
export/sha256sums.txt.asc
export/b2sums.txt.asc
if-no-files-found: error
retention-days: 90
compression-level: 0
- name: 📤 Upload signed PDFs artifact
uses: actions/upload-artifact@v4
with:
name: pdfs-signed
path: |
export/thgtoa.pdf
export/thgtoa-dark.pdf
if-no-files-found: warn
retention-days: 90
compression-level: 0
# Verify — runs after sign, surfaces results as a job summary
verify:
name: Verify hashes & signatures
runs-on: ubuntu-latest
needs: sign
# Always run so the summary is visible even if sign partially failed
if: always()
steps:
- name: 🛠️ Checkout scripts and public key
uses: actions/checkout@v4
with:
sparse-checkout: |
scripts/verify_pdf.py
pgp
- name: 📥 Download signatures artifact
uses: actions/download-artifact@v4
with:
name: signatures
path: export/
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: 📥 Download signed PDFs artifact
uses: actions/download-artifact@v4
with:
name: pdfs-signed
path: export/
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: 🔑 Install GPG and import public key
run: |
sudo apt-get update -qq
sudo apt-get install -y gnupg
gpg --import pgp/anonymousplanet-release.asc
- name: 🐍 Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: 🔍 Run verify_pdf.py
id: verify
run: |
# Capture output and exit code separately so we can write the
# summary regardless of whether verification passed or failed.
set +e
output=$(python scripts/verify_pdf.py --hashes --signatures --export-dir export 2>&1)
exit_code=$?
set -e
echo "exit_code=$exit_code" >> $GITHUB_OUTPUT
# ── Job summary ──────────────────────────────────────────────
{
if [ "$exit_code" -eq 0 ]; then
echo "## ✅ Verification passed"
else
echo "## ❌ Verification failed"
fi
echo ""
echo "**Run:** [\`${{ github.run_id }}\`](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) &nbsp;·&nbsp; **Commit:** [\`${GITHUB_SHA::7}\`](${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}) &nbsp;·&nbsp; **By:** \`${{ github.actor }}\`"
echo ""
echo "### Script output"
echo '```'
echo "$output"
echo '```'
echo ""
echo "### Hashes"
echo '```'
echo "── SHA-256 ──────────────────────────────────────────────────────"
cat export/sha256sums.txt 2>/dev/null || echo "(not found)"
echo ""
echo "── BLAKE2b ──────────────────────────────────────────────────────"
cat export/b2sums.txt 2>/dev/null || echo "(not found)"
echo '```'
} >> $GITHUB_STEP_SUMMARY
# Propagate failure so the job is marked red if verification fails
exit $exit_code
+191
View File
@@ -0,0 +1,191 @@
name: 🚀 Release
# Manual only — run this deliberately after build and sign are confirmed good.
# Provide the 02-sign.yml run ID to pull artifacts from. The release tag is
# automatically passed to the tag input. Exports "inputs.version" to $TAG.
on:
workflow_dispatch:
inputs:
sign_run_id:
description: '02-sign.yml run ID to pull signatures and PDFs from'
required: true
type: string
prerelease:
description: 'Mark as pre-release?'
required: false
default: false
type: boolean
version:
description: 'Version string to record (e.g. v1.2.4) — required'
required: true
type: string
permissions:
contents: write # create releases and tags
actions: read # download artifacts from other runs
jobs:
release:
name: Publish GitHub Release
runs-on: ubuntu-latest
steps:
- name: 🛠️ Checkout (for pgp/)
uses: actions/checkout@v4
with:
fetch-depth: 0
sparse-checkout: pgp
# Download artifacts from the specified sign run
- name: 📥 Download signatures artifact
uses: actions/download-artifact@v4
with:
name: signatures
path: release/
run-id: ${{ inputs.sign_run_id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: 📥 Download signed PDFs artifact
uses: actions/download-artifact@v4
with:
name: pdfs-signed
path: release/
run-id: ${{ inputs.sign_run_id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: 📋 List release assets
run: ls -lh release/
# Read hashes for the release body
- name: "#️⃣ Read hashes"
id: hashes
run: |
read_hash() { cat "release/$1" 2>/dev/null || echo "(not built)"; }
echo "light_sha256=$(read_hash thgtoa.pdf.sha256)" >> $GITHUB_OUTPUT
echo "dark_sha256=$(read_hash thgtoa-dark.pdf.sha256)" >> $GITHUB_OUTPUT
echo "light_b2=$(read_hash thgtoa.pdf.b2sum)" >> $GITHUB_OUTPUT
echo "dark_b2=$(read_hash thgtoa-dark.pdf.b2sum)" >> $GITHUB_OUTPUT
# VirusTotal
- name: 🦠 Upload PDFs to VirusTotal
id: vt
uses: crazy-max/ghaction-virustotal@v5
with:
vt_api_key: ${{ secrets.VT_API_KEY }}
files: |
release/thgtoa.pdf
release/thgtoa-dark.pdf
- name: 🔗 Build VT report URLs
id: vt_urls
run: |
light_hash=$(cat release/thgtoa.pdf.sha256 2>/dev/null || echo "")
dark_hash=$(cat release/thgtoa-dark.pdf.sha256 2>/dev/null || echo "")
if [ -n "$light_hash" ]; then
echo "light_vt=https://www.virustotal.com/gui/file/${light_hash}" >> $GITHUB_OUTPUT
else
echo "light_vt=(not built)" >> $GITHUB_OUTPUT
fi
if [ -n "$dark_hash" ]; then
echo "dark_vt=https://www.virustotal.com/gui/file/${dark_hash}" >> $GITHUB_OUTPUT
else
echo "dark_vt=(not built)" >> $GITHUB_OUTPUT
fi
# Generate release tag — timestamp + short SHA, always unique
- name: 🏷️ Generate release tag
id: tag
run: |
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
DATE=$(date -u +'%Y%m%d')
TAG="${{ inputs.version }}"
NAME="Release ${DATE} (${SHORT_SHA})"
echo "tag=$TAG" >> $GITHUB_OUTPUT
echo "name=$NAME" >> $GITHUB_OUTPUT
echo "Tag: $TAG"
# Create GitHub Release
- name: 🚀 Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.tag.outputs.tag }}
name: ${{ steps.tag.outputs.name }}
prerelease: ${{ inputs.prerelease || false }}
draft: true
fail_on_unmatched_files: false
body: |
## 📖 The Hitchhiker's Guide to Online Anonymity
Built from [`${{ inputs.version }}`](${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ inputs.version }}).
---
### 📄 Release assets
| File | Description |
|------|-------------|
| `thgtoa.pdf` | Light mode PDF |
| `thgtoa-dark.pdf` | Dark mode PDF (hacker theme) |
| `sha256sums.txt` | SHA-256 checksums (both files) |
| `b2sums.txt` | BLAKE2b checksums (both files) |
| `thgtoa.pdf.sha256` | SHA-256 — light PDF |
| `thgtoa-dark.pdf.sha256` | SHA-256 — dark PDF |
| `thgtoa.pdf.b2sum` | BLAKE2b — light PDF |
| `thgtoa-dark.pdf.b2sum` | BLAKE2b — dark PDF |
| `*.asc` | GPG detached signatures (ASCII armor) |
---
### #️⃣ Hashes
**thgtoa.pdf** (light)
```text
SHA-256 ${{ steps.hashes.outputs.light_sha256 }}
BLAKE2b ${{ steps.hashes.outputs.light_b2 }}
```
**thgtoa-dark.pdf** (dark)
```text
SHA-256 ${{ steps.hashes.outputs.dark_sha256 }}
BLAKE2b ${{ steps.hashes.outputs.dark_b2 }}
```
---
### 🔏 Verifying GPG signatures
```sh
# Import the release signing key
gpg --import pgp/anonymousplanet-release.asc
# Verify PDFs
gpg --verify thgtoa.pdf.asc thgtoa.pdf
gpg --verify thgtoa-dark.pdf.asc thgtoa-dark.pdf
# Verify hash files
gpg --verify sha256sums.txt.asc sha256sums.txt
gpg --verify b2sums.txt.asc b2sums.txt
```
---
### 🦠 VirusTotal scans
| File | Report |
|------|--------|
| `thgtoa.pdf` | ${{ steps.vt_urls.outputs.light_vt }} |
| `thgtoa-dark.pdf` | ${{ steps.vt_urls.outputs.dark_vt }} |
files: |
release/thgtoa.pdf
release/thgtoa-dark.pdf
release/sha256sums.txt
release/b2sums.txt
release/thgtoa.pdf.sha256
release/thgtoa-dark.pdf.sha256
release/thgtoa.pdf.b2sum
release/thgtoa-dark.pdf.b2sum
release/thgtoa.pdf.asc
release/thgtoa-dark.pdf.asc
release/sha256sums.txt.asc
release/b2sums.txt.asc
@@ -1,17 +1,14 @@
name: 📝 Update Changelog
# Runs after build.yml completes on main — at that point we know what changed.
# Can also be triggered manually to backfill a missing entry.
# Manual only — run after a release is published. Provide the exact version
# string (e.g. v1.2.4) to prepend to the changelog. Version is required to
# prevent silent auto-increment drift from release tags.
on:
workflow_run:
workflows: ["📖 Build PDFs"]
types: [completed]
branches: [main]
workflow_dispatch:
inputs:
version:
description: 'Version string (e.g. v1.2.4) — leave blank to auto-increment'
required: false
description: 'Version string to record (e.g. v1.2.4) — required'
required: true
type: string
dry_run:
description: 'Dry run — print entry without committing'
@@ -20,14 +17,11 @@ on:
type: boolean
permissions:
contents: write # commit changelog back to main
contents: write # commit changelog back to main
jobs:
changelog:
name: Prepend changelog entry
if: >
github.event_name == 'workflow_dispatch' ||
github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
steps:
@@ -46,7 +40,7 @@ jobs:
- name: 📝 Generate and prepend changelog entry
env:
DRY_RUN: ${{ inputs.dry_run || 'false' }}
MANUAL_VERSION: ${{ inputs.version || '' }}
MANUAL_VERSION: ${{ inputs.version }}
GH_SHA: ${{ github.sha }}
GH_REF: ${{ github.ref_name }}
TRIGGERING_SHA: ${{ github.event.workflow_run.head_sha || github.sha }}
-218
View File
@@ -1,218 +0,0 @@
# DEPRECATED — replaced by build.yml, sign.yml, and release.yml
# This file is kept temporarily so in-flight runs are not broken.
#
# name: 📖 Build & Sign PDFs
on:
workflow_dispatch: # manual only — no automatic triggers (deprecated)
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 Python dependencies
run: pip install mkdocs-material pillow numpy
- name: 🖼️ Install poppler (pdftoppm) and qpdf
run: |
sudo apt-get update
sudo apt-get install -y poppler-utils qpdf
- name: Setup Chrome
uses: browser-actions/setup-chrome@v2
with:
chrome-version: 120
install-dependencies: true
install-chromedriver: true
- name: 🔑 Install GPG tools
run: sudo apt-get install -y gnupg
# ------------------------------------------------------------------ #
# Build PDFs
# ------------------------------------------------------------------ #
- name: 🖨️ Build PDFs
env:
CI: true
run: python scripts/build_guide_pdf.py --${{ inputs.build_mode || 'both' }}
# ------------------------------------------------------------------ #
# Hash (SHA-256 + BLAKE2b)
# ------------------------------------------------------------------ #
- name: #️⃣ Hash PDFs
id: hashes
run: |
mkdir -p export
sha256sum export/thgtoa.pdf | awk '{print $1}' > export/thgtoa.pdf.sha256
sha256sum export/thgtoa-dark.pdf | awk '{print $1}' > export/thgtoa-dark.pdf.sha256
b2sum export/thgtoa.pdf | awk '{print $1}' > export/thgtoa.pdf.b2
b2sum export/thgtoa-dark.pdf | awk '{print $1}' > export/thgtoa-dark.pdf.b2
# Also write combined human-readable files
sha256sum export/thgtoa.pdf export/thgtoa-dark.pdf > export/sha256sums.txt
b2sum export/thgtoa.pdf export/thgtoa-dark.pdf > export/b2sums.txt
# Expose hashes as step outputs for the release body
echo "light_sha256=$(cat export/thgtoa.pdf.sha256)" >> $GITHUB_OUTPUT
echo "dark_sha256=$(cat export/thgtoa-dark.pdf.sha256)" >> $GITHUB_OUTPUT
echo "light_b2=$(cat export/thgtoa.pdf.b2)" >> $GITHUB_OUTPUT
echo "dark_b2=$(cat export/thgtoa-dark.pdf.b2)" >> $GITHUB_OUTPUT
# ------------------------------------------------------------------ #
# GPG sign (detached .sig for each PDF + each hash file)
# ------------------------------------------------------------------ #
- name: 🔏 Import GPG key
env:
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
run: |
echo "$GPG_PRIVATE_KEY" | gpg --batch --import
# Pre-cache the passphrase so signing doesn't prompt
echo "$GPG_PASSPHRASE" | gpg --batch --yes --passphrase-fd 0 \
--pinentry-mode loopback --list-secret-keys
- name: 🔏 GPG sign PDFs and hash files
env:
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
run: |
sign() {
echo "$GPG_PASSPHRASE" | gpg --batch --yes --passphrase-fd 0 \
--pinentry-mode loopback \
--detach-sign --armor --output "${1}.sig" "$1"
}
sign export/thgtoa.pdf
sign export/thgtoa-dark.pdf
sign export/sha256sums.txt
sign export/b2sums.txt
# ------------------------------------------------------------------ #
# VirusTotal
# ------------------------------------------------------------------ #
- name: 🦠 Upload PDFs to VirusTotal
id: vt
uses: crazy-max/ghaction-virustotal@v5
with:
vt_api_key: ${{ secrets.VT_API_KEY }}
files: |
export/thgtoa.pdf
export/thgtoa-dark.pdf
- name: 🔗 Build VT report URLs
id: vt_urls
run: |
light_hash=$(cat export/thgtoa.pdf.sha256)
dark_hash=$(cat export/thgtoa-dark.pdf.sha256)
echo "light_vt=https://www.virustotal.com/gui/file/${light_hash}" >> $GITHUB_OUTPUT
echo "dark_vt=https://www.virustotal.com/gui/file/${dark_hash}" >> $GITHUB_OUTPUT
# ------------------------------------------------------------------ #
# Create GitHub Release
# ------------------------------------------------------------------ #
- name: 🏷️ Generate release tag
id: tag
run: |
TAG="release-$(date -u +'%Y%m%d-%H%M%S')"
echo "tag=$TAG" >> $GITHUB_OUTPUT
echo "name=Release $(date -u +'%Y-%m-%d %H:%M UTC')" >> $GITHUB_OUTPUT
- name: 🚀 Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.tag.outputs.tag }}
name: ${{ steps.tag.outputs.name }}
body: |
## 📖 The Hitchhiker's Guide to Online Anonymity
Built from commit ${{ github.sha }} on `${{ github.ref_name }}`.
---
### 📄 Files
| File | Description |
|------|-------------|
| `thgtoa.pdf` | Light mode PDF |
| `thgtoa-dark.pdf` | Dark mode PDF (hacker theme) |
| `sha256sums.txt` | SHA-256 checksums |
| `b2sums.txt` | BLAKE2b checksums |
| `*.sig` | GPG detached signatures (ASCII armor) |
---
### #️⃣ Hashes
#### thgtoa.pdf (Light)
```
SHA-256: ${{ steps.hashes.outputs.light_sha256 }}
BLAKE2b: ${{ steps.hashes.outputs.light_b2 }}
```
#### thgtoa-dark.pdf (Dark)
```
SHA-256: ${{ steps.hashes.outputs.dark_sha256 }}
BLAKE2b: ${{ steps.hashes.outputs.dark_b2 }}
```
---
### 🔏 GPG Signatures
Detached signatures (`.sig`) are included in the release assets.
Verify with:
```bash
gpg --verify thgtoa.pdf.sig thgtoa.pdf
gpg --verify thgtoa-dark.pdf.sig thgtoa-dark.pdf
```
The signing key is published at `pgp/anonymousplanet-release.asc`.
---
### 🦠 VirusTotal Scans
| File | Report |
|------|--------|
| `thgtoa.pdf` | ${{ steps.vt_urls.outputs.light_vt }} |
| `thgtoa-dark.pdf` | ${{ steps.vt_urls.outputs.dark_vt }} |
files: |
export/thgtoa.pdf
export/thgtoa-dark.pdf
export/sha256sums.txt
export/b2sums.txt
export/thgtoa.pdf.sha256
export/thgtoa-dark.pdf.sha256
export/thgtoa.pdf.b2
export/thgtoa-dark.pdf.b2
export/thgtoa.pdf.sig
export/thgtoa-dark.pdf.sig
export/sha256sums.txt.sig
export/b2sums.txt.sig
draft: false
prerelease: false
fail_on_unmatched_files: true
# ------------------------------------------------------------------ #
# Upload everything as a workflow artifact (90-day archive)
# ------------------------------------------------------------------ #
- name: 📤 Upload export as workflow artifact
uses: actions/upload-artifact@v4
with:
name: pdf-release-${{ steps.tag.outputs.tag }}
path: export/*
if-no-files-found: error
retention-days: 90
compression-level: 0
+1 -2
View File
@@ -14,7 +14,6 @@ jobs:
- name: Deploy docs
uses: mhausenblas/mkdocs-deploy-gh-pages@master
# Or use mhausenblas/mkdocs-deploy-gh-pages@nomaterial to build without the mkdocs-material theme
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CUSTOM_DOMAIN: anonymousplanet.org
CUSTOM_DOMAIN: anonymousplanet.net
-215
View File
@@ -1,215 +0,0 @@
name: 🚀 Release
# Can be triggered:
# 1. Automatically after sign.yml completes on main
# 2. Manually, pointing at specific build/sign runs to pull artifacts from
on:
workflow_run:
workflows: ["🔏 Sign PDFs"]
types: [completed]
branches: [main]
workflow_dispatch:
inputs:
sign_run_id:
description: 'sign.yml run ID to pull signatures from'
required: true
type: string
build_run_id:
description: 'build.yml run ID to pull PDFs from (leave blank to use pdfs-signed from sign run)'
required: false
type: string
prerelease:
description: 'Mark as pre-release?'
required: false
default: false
type: boolean
permissions:
contents: write # create releases and tags
actions: read # download artifacts from other runs
jobs:
release:
name: Publish GitHub Release
if: >
github.event_name == 'workflow_dispatch' ||
github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
steps:
- name: 🛠️ Checkout (for commit metadata only)
uses: actions/checkout@v4
with:
sparse-checkout: pgp
# ------------------------------------------------------------------ #
# Resolve which run IDs to pull artifacts from
# ------------------------------------------------------------------ #
- name: 🔍 Resolve run IDs
id: runs
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
SIGN_RUN="${{ inputs.sign_run_id }}"
BUILD_RUN="${{ inputs.build_run_id }}"
else
SIGN_RUN="${{ github.event.workflow_run.id }}"
BUILD_RUN=""
fi
echo "sign_run=$SIGN_RUN" >> $GITHUB_OUTPUT
echo "build_run=$BUILD_RUN" >> $GITHUB_OUTPUT
echo "Sign run: $SIGN_RUN"
echo "Build run: ${BUILD_RUN:-'(using pdfs-signed from sign run)'}"
# ------------------------------------------------------------------ #
# Download artifacts
# ------------------------------------------------------------------ #
- name: 📥 Download signatures artifact
uses: actions/download-artifact@v4
with:
name: signatures
path: release/
run-id: ${{ steps.runs.outputs.sign_run }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: 📥 Download PDFs (from sign run)
uses: actions/download-artifact@v4
with:
name: pdfs-signed
path: release/
run-id: ${{ steps.runs.outputs.sign_run }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: 📋 List release assets
run: ls -lh release/
# ------------------------------------------------------------------ #
# Read hashes for the release body
# ------------------------------------------------------------------ #
- name: #️⃣ Read hashes
id: hashes
run: |
read_hash() { cat "release/$1" 2>/dev/null || echo "(not built)"; }
echo "light_sha256=$(read_hash thgtoa.pdf.sha256)" >> $GITHUB_OUTPUT
echo "dark_sha256=$(read_hash thgtoa-dark.pdf.sha256)" >> $GITHUB_OUTPUT
echo "light_b2=$(read_hash thgtoa.pdf.b2)" >> $GITHUB_OUTPUT
echo "dark_b2=$(read_hash thgtoa-dark.pdf.b2)" >> $GITHUB_OUTPUT
# ------------------------------------------------------------------ #
# VirusTotal — upload whichever PDFs are present
# ------------------------------------------------------------------ #
- name: 🦠 Upload PDFs to VirusTotal
id: vt
uses: crazy-max/ghaction-virustotal@v5
with:
vt_api_key: ${{ secrets.VT_API_KEY }}
files: |
release/thgtoa.pdf
release/thgtoa-dark.pdf
- name: 🔗 Build VT report URLs
id: vt_urls
run: |
light_hash=$(cat release/thgtoa.pdf.sha256 2>/dev/null || echo "")
dark_hash=$(cat release/thgtoa-dark.pdf.sha256 2>/dev/null || echo "")
[ -n "$light_hash" ] && \
echo "light_vt=https://www.virustotal.com/gui/file/${light_hash}" >> $GITHUB_OUTPUT || \
echo "light_vt=(not built)" >> $GITHUB_OUTPUT
[ -n "$dark_hash" ] && \
echo "dark_vt=https://www.virustotal.com/gui/file/${dark_hash}" >> $GITHUB_OUTPUT || \
echo "dark_vt=(not built)" >> $GITHUB_OUTPUT
# ------------------------------------------------------------------ #
# Tag + Release
# ------------------------------------------------------------------ #
- name: 🏷️ Generate release tag
id: tag
run: |
TAG="v$(date -u +'%Y.%m.%d')-$(echo ${{ github.sha }} | cut -c1-7)"
echo "tag=$TAG" >> $GITHUB_OUTPUT
echo "name=Release $(date -u +'%Y-%m-%d') (${TAG})" >> $GITHUB_OUTPUT
- name: 🚀 Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.tag.outputs.tag }}
name: ${{ steps.tag.outputs.name }}
prerelease: ${{ inputs.prerelease || false }}
draft: false
fail_on_unmatched_files: false
body: |
## 📖 The Hitchhiker's Guide to Online Anonymity
Built from [`${{ github.sha }}`](${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}) on `${{ github.ref_name }}`.
---
### 📄 Release assets
| File | Description |
|------|-------------|
| `thgtoa.pdf` | Light mode PDF |
| `thgtoa-dark.pdf` | Dark mode PDF (hacker theme) |
| `sha256sums.txt` | SHA-256 checksums (both files) |
| `b2sums.txt` | BLAKE2b checksums (both files) |
| `thgtoa.pdf.sha256` | SHA-256 — light PDF |
| `thgtoa-dark.pdf.sha256` | SHA-256 — dark PDF |
| `thgtoa.pdf.b2` | BLAKE2b — light PDF |
| `thgtoa-dark.pdf.b2` | BLAKE2b — dark PDF |
| `*.sig` | GPG detached signatures (ASCII armor) |
---
### #️⃣ Hashes
**thgtoa.pdf** (light)
```
SHA-256 ${{ steps.hashes.outputs.light_sha256 }}
BLAKE2b ${{ steps.hashes.outputs.light_b2 }}
```
**thgtoa-dark.pdf** (dark)
```
SHA-256 ${{ steps.hashes.outputs.dark_sha256 }}
BLAKE2b ${{ steps.hashes.outputs.dark_b2 }}
```
---
### 🔏 Verifying GPG signatures
```bash
# Import the release signing key
gpg --import pgp/anonymousplanet-release.asc
# Verify PDFs
gpg --verify thgtoa.pdf.sig thgtoa.pdf
gpg --verify thgtoa-dark.pdf.sig thgtoa-dark.pdf
# Verify hash files
gpg --verify sha256sums.txt.sig sha256sums.txt
gpg --verify b2sums.txt.sig b2sums.txt
```
---
### 🦠 VirusTotal scans
| File | Report |
|------|--------|
| `thgtoa.pdf` | ${{ steps.vt_urls.outputs.light_vt }} |
| `thgtoa-dark.pdf` | ${{ steps.vt_urls.outputs.dark_vt }} |
files: |
release/thgtoa.pdf
release/thgtoa-dark.pdf
release/sha256sums.txt
release/b2sums.txt
release/thgtoa.pdf.sha256
release/thgtoa-dark.pdf.sha256
release/thgtoa.pdf.b2
release/thgtoa-dark.pdf.b2
release/thgtoa.pdf.sig
release/thgtoa-dark.pdf.sig
release/sha256sums.txt.sig
release/b2sums.txt.sig
-165
View File
@@ -1,165 +0,0 @@
name: 🔏 Sign PDFs
# Can be triggered:
# 1. Automatically after build.yml completes on main
# 2. Manually, pointing at a specific build run to pull PDFs from
on:
workflow_run:
workflows: ["📖 Build PDFs"]
types: [completed]
branches: [main]
workflow_dispatch:
inputs:
build_run_id:
description: 'build.yml run ID to download PDFs from (leave blank for latest)'
required: false
type: string
permissions:
actions: read # download artifacts from other runs
contents: read
jobs:
sign:
name: Hash & Sign PDFs
# On workflow_run, only proceed if the build actually succeeded
if: >
github.event_name == 'workflow_dispatch' ||
github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
outputs:
light_sha256: ${{ steps.hashes.outputs.light_sha256 }}
dark_sha256: ${{ steps.hashes.outputs.dark_sha256 }}
light_b2: ${{ steps.hashes.outputs.light_b2 }}
dark_b2: ${{ steps.hashes.outputs.dark_b2 }}
steps:
- name: 🛠️ Checkout (for pgp/ key reference only)
uses: actions/checkout@v4
with:
sparse-checkout: pgp
- name: 🔑 Install GPG
run: |
sudo apt-get update -qq
sudo apt-get install -y gnupg
# Download PDFs from the triggering build run, or a manually specified one
- name: 📥 Resolve source run ID
id: src
run: |
if [ -n "${{ inputs.build_run_id }}" ]; then
echo "run_id=${{ inputs.build_run_id }}" >> $GITHUB_OUTPUT
else
echo "run_id=${{ github.event.workflow_run.id }}" >> $GITHUB_OUTPUT
fi
- name: 📥 Download PDF artifacts
uses: actions/download-artifact@v4
with:
name: pdfs
path: export/
run-id: ${{ steps.src.outputs.run_id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: 📋 List downloaded files
run: ls -lh export/
# ------------------------------------------------------------------ #
# Hash
# ------------------------------------------------------------------ #
- name: #️⃣ Hash PDFs
id: hashes
run: |
cd export
for f in thgtoa.pdf thgtoa-dark.pdf; do
[ -f "$f" ] || continue
sha256sum "$f" | awk '{print $1}' > "${f}.sha256"
b2sum "$f" | awk '{print $1}' > "${f}.b2"
done
# Combined files (only include files that exist)
sha256sum thgtoa.pdf thgtoa-dark.pdf 2>/dev/null > sha256sums.txt || \
sha256sum thgtoa.pdf 2>/dev/null > sha256sums.txt
b2sum thgtoa.pdf thgtoa-dark.pdf 2>/dev/null > b2sums.txt || \
b2sum thgtoa.pdf 2>/dev/null > b2sums.txt
# Expose individual hashes as outputs (empty string if file absent)
light_sha256=$(cat thgtoa.pdf.sha256 2>/dev/null || echo "")
dark_sha256=$(cat thgtoa-dark.pdf.sha256 2>/dev/null || echo "")
light_b2=$(cat thgtoa.pdf.b2 2>/dev/null || echo "")
dark_b2=$(cat thgtoa-dark.pdf.b2 2>/dev/null || echo "")
echo "light_sha256=$light_sha256" >> $GITHUB_OUTPUT
echo "dark_sha256=$dark_sha256" >> $GITHUB_OUTPUT
echo "light_b2=$light_b2" >> $GITHUB_OUTPUT
echo "dark_b2=$dark_b2" >> $GITHUB_OUTPUT
echo "--- SHA-256 ---"
cat sha256sums.txt
echo "--- BLAKE2b ---"
cat b2sums.txt
# ------------------------------------------------------------------ #
# GPG sign
# ------------------------------------------------------------------ #
- name: 🔏 Import GPG signing key
env:
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
run: |
echo "$GPG_PRIVATE_KEY" | gpg --batch --import
# Pre-cache passphrase to prevent interactive prompt
echo "$GPG_PASSPHRASE" | gpg --batch --yes --passphrase-fd 0 \
--pinentry-mode loopback --list-secret-keys
- name: 🔏 Sign PDFs and hash files
env:
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
run: |
sign() {
local file="$1"
[ -f "$file" ] || return 0
echo "$GPG_PASSPHRASE" | gpg --batch --yes --passphrase-fd 0 \
--pinentry-mode loopback \
--detach-sign --armor --output "${file}.sig" "$file"
echo "Signed: $file"
}
sign export/thgtoa.pdf
sign export/thgtoa-dark.pdf
sign export/sha256sums.txt
sign export/b2sums.txt
# ------------------------------------------------------------------ #
# Upload — PDFs + all signatures and hashes together
# ------------------------------------------------------------------ #
- name: 📤 Upload signatures artifact
uses: actions/upload-artifact@v4
with:
name: signatures
path: |
export/sha256sums.txt
export/b2sums.txt
export/thgtoa.pdf.sha256
export/thgtoa-dark.pdf.sha256
export/thgtoa.pdf.b2
export/thgtoa-dark.pdf.b2
export/thgtoa.pdf.sig
export/thgtoa-dark.pdf.sig
export/sha256sums.txt.sig
export/b2sums.txt.sig
if-no-files-found: error
retention-days: 90
compression-level: 0
- name: 📤 Upload signed PDFs artifact
uses: actions/upload-artifact@v4
with:
name: pdfs-signed
path: |
export/thgtoa.pdf
export/thgtoa-dark.pdf
if-no-files-found: warn
retention-days: 90
compression-level: 0
+4 -4
View File
@@ -21,9 +21,9 @@ _site/
_site_test/
build/
# Export directory - but track hash files and signatures
# Export directory track only hashes and signatures, not the PDFs themselves
export/thgtoa.pdf.sha256
export/thgtoa-dark.pdf.sha256
export/thgtoa.pdf.b2
export/thgtoa-dark.pdf.b2
*.sig
export/thgtoa.pdf.b2sum
export/thgtoa-dark.pdf.b2sum
export/*.asc
+1 -18
View File
@@ -62,24 +62,7 @@ MD012:
# Consecutive blank lines
maximum: 1
# MD013/line-length - Line length
#
MD013:
# Number of characters
line_length: 80
# Number of characters for headings
heading_line_length: 80
# Number of characters for code blocks
code_block_line_length: 160
# Include code blocks
code_blocks: false
# Include tables
tables: false
# Include headings
headings: true
# Strict length checking (e.g. allow for longer URLs)
strict: false
# Stern length checking
stern: false
MD013: false
# MD014/commands-show-output - Dollar signs used before commands without showing output
# TODO: set false for now but we should consider enabling it
+12 -5
View File
@@ -10,14 +10,21 @@ repos:
- id: check-added-large-files
- id: check-merge-conflict
- id: check-symlinks
- id: detect-private-key
- id: end-of-file-fixer
- id: trailing-whitespace
- id: mixed-line-ending
args: [--fix=lf]
- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.41.0
- repo: https://github.com/commitizen-tools/commitizen
rev: v4.8.3
hooks:
- id: markdownlint
- id: markdownlint-fix
- id: commitizen
stages: [commit-msg]
- repo: local
hooks:
- id: require-commit-body
name: Avoid bad commits
language: script
entry: scripts/hooks/require-commit-body.sh
stages: [commit-msg]
+1
View File
@@ -0,0 +1 @@
anonymousplanet.net
+427
View File
@@ -0,0 +1,427 @@
Attribution-ShareAlike 4.0 International
=======================================================================
Creative Commons Corporation ("Creative Commons") is not a law firm and
does not provide legal services or legal advice. Distribution of
Creative Commons public licenses does not create a lawyer-client or
other relationship. Creative Commons makes its licenses and related
information available on an "as-is" basis. Creative Commons gives no
warranties regarding its licenses, any material licensed under their
terms and conditions, or any related information. Creative Commons
disclaims all liability for damages resulting from their use to the
fullest extent possible.
Using Creative Commons Public Licenses
Creative Commons public licenses provide a standard set of terms and
conditions that creators and other rights holders may use to share
original works of authorship and other material subject to copyright
and certain other rights specified in the public license below. The
following considerations are for informational purposes only, are not
exhaustive, and do not form part of our licenses.
Considerations for licensors: Our public licenses are
intended for use by those authorized to give the public
permission to use material in ways otherwise restricted by
copyright and certain other rights. Our licenses are
irrevocable. Licensors should read and understand the terms
and conditions of the license they choose before applying it.
Licensors should also secure all rights necessary before
applying our licenses so that the public can reuse the
material as expected. Licensors should clearly mark any
material not subject to the license. This includes other CC-
licensed material, or material used under an exception or
limitation to copyright. More considerations for licensors:
wiki.creativecommons.org/Considerations_for_licensors
Considerations for the public: By using one of our public
licenses, a licensor grants the public permission to use the
licensed material under specified terms and conditions. If
the licensor's permission is not necessary for any reason--for
example, because of any applicable exception or limitation to
copyright--then that use is not regulated by the license. Our
licenses grant only permissions under copyright and certain
other rights that a licensor has authority to grant. Use of
the licensed material may still be restricted for other
reasons, including because others have copyright or other
rights in the material. A licensor may make special requests,
such as asking that all changes be marked or described.
Although not required by our licenses, you are encouraged to
respect those requests where reasonable. More considerations
for the public:
wiki.creativecommons.org/Considerations_for_licensees
=======================================================================
Creative Commons Attribution-ShareAlike 4.0 International Public
License
By exercising the Licensed Rights (defined below), You accept and agree
to be bound by the terms and conditions of this Creative Commons
Attribution-ShareAlike 4.0 International Public License ("Public
License"). To the extent this Public License may be interpreted as a
contract, You are granted the Licensed Rights in consideration of Your
acceptance of these terms and conditions, and the Licensor grants You
such rights in consideration of benefits the Licensor receives from
making the Licensed Material available under these terms and
conditions.
Section 1 -- Definitions.
a. Adapted Material means material subject to Copyright and Similar
Rights that is derived from or based upon the Licensed Material
and in which the Licensed Material is translated, altered,
arranged, transformed, or otherwise modified in a manner requiring
permission under the Copyright and Similar Rights held by the
Licensor. For purposes of this Public License, where the Licensed
Material is a musical work, performance, or sound recording,
Adapted Material is always produced where the Licensed Material is
synched in timed relation with a moving image.
b. Adapter's License means the license You apply to Your Copyright
and Similar Rights in Your contributions to Adapted Material in
accordance with the terms and conditions of this Public License.
c. BY-SA Compatible License means a license listed at
creativecommons.org/compatiblelicenses, approved by Creative
Commons as essentially the equivalent of this Public License.
d. Copyright and Similar Rights means copyright and/or similar rights
closely related to copyright including, without limitation,
performance, broadcast, sound recording, and Sui Generis Database
Rights, without regard to how the rights are labeled or
categorized. For purposes of this Public License, the rights
specified in Section 2(b)(1)-(2) are not Copyright and Similar
Rights.
e. Effective Technological Measures means those measures that, in the
absence of proper authority, may not be circumvented under laws
fulfilling obligations under Article 11 of the WIPO Copyright
Treaty adopted on December 20, 1996, and/or similar international
agreements.
f. Exceptions and Limitations means fair use, fair dealing, and/or
any other exception or limitation to Copyright and Similar Rights
that applies to Your use of the Licensed Material.
g. License Elements means the license attributes listed in the name
of a Creative Commons Public License. The License Elements of this
Public License are Attribution and ShareAlike.
h. Licensed Material means the artistic or literary work, database,
or other material to which the Licensor applied this Public
License.
i. Licensed Rights means the rights granted to You subject to the
terms and conditions of this Public License, which are limited to
all Copyright and Similar Rights that apply to Your use of the
Licensed Material and that the Licensor has authority to license.
j. Licensor means the individual(s) or entity(ies) granting rights
under this Public License.
k. Share means to provide material to the public by any means or
process that requires permission under the Licensed Rights, such
as reproduction, public display, public performance, distribution,
dissemination, communication, or importation, and to make material
available to the public including in ways that members of the
public may access the material from a place and at a time
individually chosen by them.
l. Sui Generis Database Rights means rights other than copyright
resulting from Directive 96/9/EC of the European Parliament and of
the Council of 11 March 1996 on the legal protection of databases,
as amended and/or succeeded, as well as other essentially
equivalent rights anywhere in the world.
m. You means the individual or entity exercising the Licensed Rights
under this Public License. Your has a corresponding meaning.
Section 2 -- Scope.
a. License grant.
1. Subject to the terms and conditions of this Public License,
the Licensor hereby grants You a worldwide, royalty-free,
non-sublicensable, non-exclusive, irrevocable license to
exercise the Licensed Rights in the Licensed Material to:
a. reproduce and Share the Licensed Material, in whole or
in part; and
b. produce, reproduce, and Share Adapted Material.
2. Exceptions and Limitations. For the avoidance of doubt, where
Exceptions and Limitations apply to Your use, this Public
License does not apply, and You do not need to comply with
its terms and conditions.
3. Term. The term of this Public License is specified in Section
6(a).
4. Media and formats; technical modifications allowed. The
Licensor authorizes You to exercise the Licensed Rights in
all media and formats whether now known or hereafter created,
and to make technical modifications necessary to do so. The
Licensor waives and/or agrees not to assert any right or
authority to forbid You from making technical modifications
necessary to exercise the Licensed Rights, including
technical modifications necessary to circumvent Effective
Technological Measures. For purposes of this Public License,
simply making modifications authorized by this Section 2(a)
(4) never produces Adapted Material.
5. Downstream recipients.
a. Offer from the Licensor -- Licensed Material. Every
recipient of the Licensed Material automatically
receives an offer from the Licensor to exercise the
Licensed Rights under the terms and conditions of this
Public License.
b. Additional offer from the Licensor -- Adapted Material.
Every recipient of Adapted Material from You
automatically receives an offer from the Licensor to
exercise the Licensed Rights in the Adapted Material
under the conditions of the Adapter's License You apply.
c. No downstream restrictions. You may not offer or impose
any additional or different terms or conditions on, or
apply any Effective Technological Measures to, the
Licensed Material if doing so restricts exercise of the
Licensed Rights by any recipient of the Licensed
Material.
6. No endorsement. Nothing in this Public License constitutes or
may be construed as permission to assert or imply that You
are, or that Your use of the Licensed Material is, connected
with, or sponsored, endorsed, or granted official status by,
the Licensor or others designated to receive attribution as
provided in Section 3(a)(1)(A)(i).
b. Other rights.
1. Moral rights, such as the right of integrity, are not
licensed under this Public License, nor are publicity,
privacy, and/or other similar personality rights; however, to
the extent possible, the Licensor waives and/or agrees not to
assert any such rights held by the Licensor to the limited
extent necessary to allow You to exercise the Licensed
Rights, but not otherwise.
2. Patent and trademark rights are not licensed under this
Public License.
3. To the extent possible, the Licensor waives any right to
collect royalties from You for the exercise of the Licensed
Rights, whether directly or through a collecting society
under any voluntary or waivable statutory or compulsory
licensing scheme. In all other cases the Licensor expressly
reserves any right to collect such royalties.
Section 3 -- License Conditions.
Your exercise of the Licensed Rights is expressly made subject to the
following conditions.
a. Attribution.
1. If You Share the Licensed Material (including in modified
form), You must:
a. retain the following if it is supplied by the Licensor
with the Licensed Material:
i. identification of the creator(s) of the Licensed
Material and any others designated to receive
attribution, in any reasonable manner requested by
the Licensor (including by pseudonym if
designated);
ii. a copyright notice;
iii. a notice that refers to this Public License;
iv. a notice that refers to the disclaimer of
warranties;
v. a URI or hyperlink to the Licensed Material to the
extent reasonably practicable;
b. indicate if You modified the Licensed Material and
retain an indication of any previous modifications; and
c. indicate the Licensed Material is licensed under this
Public License, and include the text of, or the URI or
hyperlink to, this Public License.
2. You may satisfy the conditions in Section 3(a)(1) in any
reasonable manner based on the medium, means, and context in
which You Share the Licensed Material. For example, it may be
reasonable to satisfy the conditions by providing a URI or
hyperlink to a resource that includes the required
information.
3. If requested by the Licensor, You must remove any of the
information required by Section 3(a)(1)(A) to the extent
reasonably practicable.
b. ShareAlike.
In addition to the conditions in Section 3(a), if You Share
Adapted Material You produce, the following conditions also apply.
1. The Adapter's License You apply must be a Creative Commons
license with the same License Elements, this version or
later, or a BY-SA Compatible License.
2. You must include the text of, or the URI or hyperlink to, the
Adapter's License You apply. You may satisfy this condition
in any reasonable manner based on the medium, means, and
context in which You Share Adapted Material.
3. You may not offer or impose any additional or different terms
or conditions on, or apply any Effective Technological
Measures to, Adapted Material that restrict exercise of the
rights granted under the Adapter's License You apply.
Section 4 -- Sui Generis Database Rights.
Where the Licensed Rights include Sui Generis Database Rights that
apply to Your use of the Licensed Material:
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
to extract, reuse, reproduce, and Share all or a substantial
portion of the contents of the database;
b. if You include all or a substantial portion of the database
contents in a database in which You have Sui Generis Database
Rights, then the database in which You have Sui Generis Database
Rights (but not its individual contents) is Adapted Material,
including for purposes of Section 3(b); and
c. You must comply with the conditions in Section 3(a) if You Share
all or a substantial portion of the contents of the database.
For the avoidance of doubt, this Section 4 supplements and does not
replace Your obligations under this Public License where the Licensed
Rights include other Copyright and Similar Rights.
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
c. The disclaimer of warranties and limitation of liability provided
above shall be interpreted in a manner that, to the extent
possible, most closely approximates an absolute disclaimer and
waiver of all liability.
Section 6 -- Term and Termination.
a. This Public License applies for the term of the Copyright and
Similar Rights licensed here. However, if You fail to comply with
this Public License, then Your rights under this Public License
terminate automatically.
b. Where Your right to use the Licensed Material has terminated under
Section 6(a), it reinstates:
1. automatically as of the date the violation is cured, provided
it is cured within 30 days of Your discovery of the
violation; or
2. upon express reinstatement by the Licensor.
For the avoidance of doubt, this Section 6(b) does not affect any
right the Licensor may have to seek remedies for Your violations
of this Public License.
c. For the avoidance of doubt, the Licensor may also offer the
Licensed Material under separate terms or conditions or stop
distributing the Licensed Material at any time; however, doing so
will not terminate this Public License.
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
License.
Section 7 -- Other Terms and Conditions.
a. The Licensor shall not be bound by any additional or different
terms or conditions communicated by You unless expressly agreed.
b. Any arrangements, understandings, or agreements regarding the
Licensed Material not stated herein are separate from and
independent of the terms and conditions of this Public License.
Section 8 -- Interpretation.
a. For the avoidance of doubt, this Public License does not, and
shall not be interpreted to, reduce, limit, restrict, or impose
conditions on any use of the Licensed Material that could lawfully
be made without permission under this Public License.
b. To the extent possible, if any provision of this Public License is
deemed unenforceable, it shall be automatically reformed to the
minimum extent necessary to make it enforceable. If the provision
cannot be reformed, it shall be severed from this Public License
without affecting the enforceability of the remaining terms and
conditions.
c. No term or condition of this Public License will be waived and no
failure to comply consented to unless expressly agreed to by the
Licensor.
d. Nothing in this Public License constitutes or may be interpreted
as a limitation upon, or waiver of, any privileges and immunities
that apply to the Licensor or You, including from the legal
processes of any jurisdiction or authority.
=======================================================================
Creative Commons is not a party to its public
licenses. Notwithstanding, Creative Commons may elect to apply one of
its public licenses to material it publishes and in those instances
will be considered the “Licensor.” The text of the Creative Commons
public licenses is dedicated to the public domain under the CC0 Public
Domain Dedication. Except for the limited purpose of indicating that
material is shared under a Creative Commons public license or as
otherwise permitted by the Creative Commons policies published at
creativecommons.org/policies, Creative Commons does not authorize the
use of the trademark "Creative Commons" or any other trademark or logo
of Creative Commons without its prior written consent including,
without limitation, in connection with any unauthorized modifications
to any of its public licenses or any other arrangements,
understandings, or agreements concerning use of licensed material. For
the avoidance of doubt, this paragraph does not form part of the
public licenses.
Creative Commons may be contacted at creativecommons.org.
+16 -2
View File
@@ -1,12 +1,26 @@
<div align="center">
<a href="https://anonymousplanet.net">
<img src="https://anonymousplanet.net/media/profile.png"></a>
<p><em>The comprehensive guide for online anonymity and OpSec.</em></p>
<a href="https://github.com/Anon-Planet/thgtoa/stargazers">
<img src="https://img.shields.io/github/stars/Anon-Planet?style=social"></a>
<a href="https://github.com/Anon-Planet/thgtoa/releases/latest">
<img src="https://img.shields.io/badge/MkDocs_Material-526CFE?logo=MaterialForMkDocs&logoColor=green"></a>
<a href="https://github.com/Anon-Planet/thgtoa/actions/workflows/release.yml">
<img src="https://img.shields.io/github/actions/workflow/status/Anon-Planet/thgtoa/release.yml?label=release"></a></p>
</div>
Welcome.
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.
This guide is an open-source non-profit initiative, [licensed](LICENSE.html) under **Creative Commons Attribution-ShareAlike 4.0 International** ([cc-by-sa-4.0](https://creativecommons.org/licenses/by-sa/4.0/) <sup>[[Archive.org]](https://web.archive.org/web/https://creativecommons.org/licenses/by-sa/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.
**If you would like to make a donation to help this project, you can do so from [here](donations.html) where you will also find the project goals. All the donations will be strictly used within the context of this project. All donations and spendings are logged on the donations page.**
**Ways to read or export the guide**
- **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.
- **In your browser:** [Hitchhiker's Guide](https://anonymousplanet.net/) (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`).
+1 -1
View File
@@ -1 +1 @@
anonymousplanet.org
anonymousplanet.net
+102 -114
View File
@@ -1,114 +1,102 @@
---
title: "Anonymous Planet"
description: We are the maintainers of the Hitchhiker's Guide and the PSA Matrix space.
schema:
"@context": https://schema.org
"@type": Organization
"@id": https://www.anonymousplanet.org/
name: Anonymous Planet
url: https://www.anonymousplanet.org/about/
logo: ../media/profile.png
sameAs:
- https://github.com/Anon-Planet
- https://opencollective.com/anonymousplanetorg
- https://mastodon.social/@anonymousplanet
---
![Anonymous Planet logo](../media/profile.png){ align=right }
**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.
??? Note "Where do I start?"
Start either by going to [the beginning](../guide/index.md) or using the search at top right of the page. It is also available at whatever point you are in your reading.
??? Note "Notes on the journey"
This guide is a work in progress. It will probably never be "finished". You may (will) find broken links when you click on some search results and during some navigation steps. Please report these. Otherwise, most of the search functionality is a great experience and can help you find linked topics. Try to search for something in one section of the reading. It will show up in many other places.
??? Note "Disclaimer"
There might be some wrong or outdated information in this guide because no one is perfect. Your experience may vary. Remember, check regularly for an updated version of this guide. Please do your own independent, well-thought research. There is no one resource online that can provide 100% security, anonymity, and/or privacy.
This guide is a non-profit open-source initiative, licensed 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>).
- For mirrors see [Mirrors](../mirrors/index.md) and the links at the bottom right of the page. You should see these on every page.
- For help in comparing versions see [Comparing versions](../guide/index.md#appendix-a6-comparing-versions)
Feel free to submit issues **(please do report anything wrong)** using GitHub Issues at: <https://github.com/Anon-Planet/thgtoa/issues>. We also accept Merge Requests (MR) from our Gitlab and many other places. Do not hesitate to report issues and suggestions!
??? Note "Discuss ideas on Matrix for real-time chat"
We offer a Matrix.org hosted space of our own. Check it out!
- Read [the rules](https://psa.anonymousplanet.org/), please
- Matrix Room: https://matrix.to/#/#nth:anonymousplanet.net
- Matrix Space: https://matrix.to/#/#psa:anonymousplanet.net
- Admins: @daskolburn:thomcat.rocks and @thehidden:tchncs.de
Follow us on:
- Twitter at <https://twitter.com/AnonyPla>
- Mastodon at <https://mastodon.social/@anonymousplanet>
To contact me, see the updated information on the website or send an e-mail to <contact@anonymousplanet.org>
**Please consider [donating](../guide/index.md#donations) if you enjoy the project and want to support the hosting fees or support the funding of initiatives like the hosting of Tor Exit Nodes.**
### Recommended Reading
Some of those resources may, in order to sustain their project, contain or propose:
- Sponsored commercial content
- Monetized content through third party platforms (such as YouTube)
- Affiliate links to commercial services
- Paid Services such as consultancy
- Premium content such as ad-free content or updated content
- Merchandising
_Note that these websites could contain affiliate/sponsored content and/or merchandising. This guide does not endorse and is not sponsored by any commercial entity in any way._
If you skipped those, you should really still consider viewing this YouTube playlist from the Techlore Go Incognito project (<https://github.com/techlore-official/go-incognito> <sup>[[Archive.org]](https://web.archive.org/web/https://github.com/techlore-official/go-incognito)</sup>) as an introduction before going further: <https://www.youtube.com/playlist?list=PL3KeV6Ui_4CayDGHw64OFXEPHgXLkrtJO> <sup>[[Invidious]](https://yewtu.be/playlist?list=PL3KeV6Ui_4CayDGHw64OFXEPHgXLkrtJO)</sup>. This guide will cover many of the topics in the videos of this playlist with more details and references as well as some added topics not covered within that series. This will just take you 2 or 3 hours to watch it all.
_Anonymous Planet_ **does not** participate in any sponsoring, endorsement, advertising, or other affiliate programs for any entity. We only rely on anonymous donations in a closed, transparent loop system.
??? Note "Privacy related"
- AnarSec: <https://www.anarsec.guide/>
- EFF Surveillance Self-Defense: <https://ssd.eff.org/>
- Prism-Break: <https://prism-break.org/>
- Privacy Guides: <https://privacyguides.org>
- Techlore: <https://techlore.tech>
- The New Oil: <https://thenewoil.org>
- PrivacyTools.io: <https://privacytools.io>
??? Note "Blogs and personal websites"
- CIA Officer's Blog: <https://officercia.mirror.xyz/>
- Continuing Ed: <https://edwardsnowden.substack.com/>
- Madaidan's Insecurities: <https://madaidans-insecurities.github.io/>
- Seirdy's Home: <https://seirdy.one/>
??? Note "Useful resources"
- KYC? Not me: <https://kycnot.me/>
- Library Genesis: <https://en.wikipedia.org/wiki/Library_Genesis> <sup>[[Wikiless]](https://wikiless.com/wiki/Library_Genesis)</sup> (see their latest known URL in the Wikipedia article)
- Real World Onion Sites: <https://github.com/alecmuffett/real-world-onion-sites>
- Sci-Hub <https://en.wikipedia.org/wiki/Sci-Hub> <sup>[[Wikiless]](https://wikiless.com/wiki/Sci-Hub)</sup> (see their latest known URL in the main Wikipedia article)
- Terms of Service, Didn't Read: <https://tosdr.org>
- Whonix Documentation: <https://www.whonix.org/wiki/Documentation>
??? Note "We are not affiliated with Anonymous or Riseup"
One or two of our community members uses or has used the resources of Riseup. We are not affiliated with Riseup in any manner.
We also hold **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> hacker collective.
## License
!!! Danger ""
:fontawesome-brands-creative-commons: :fontawesome-brands-creative-commons-by: :fontawesome-brands-creative-commons-nd: This guide is an open-source non-profit initiative, licensed under [Creative Commons Attribution-NonCommercial 4.0 International](https://github.com/Anon-Planet/thgtoa/blob/master/LICENSE.md) 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.
---
title: "Anonymous Planet"
description: We are the maintainers of the Hitchhiker's Guide and the PSA Matrix space.
schema:
"@context": https://schema.org
"@type": Organization
"@id": https://anonymousplanet.net/
name: Anonymous Planet
url: https://anonymousplanet.net/about/
logo: ../media/profile.png
sameAs:
- https://github.com/Anon-Planet
- https://opencollective.com/anonymousplanetorg
- https://mastodon.social/@anonymousplanet
---
**Anonymous Planet** are the maintainers of [_The Hitchhiker's Guide_](../guide/index.md) and the [_PSA Community_](https://psa.anonymousplanet.net). This project is part of our ongoing efforts to provide open-source tools and resources for the community, made by people with extensive knowledge in signals and forensics, and expertise in various distributions of Linux. We are 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). We are high at risk individuals. We are not simply hackers with a bunch of recommendations and affiliate links like privacytools. To be blunt like Linus Torvalds with a bullshit Merge Request, we don't do that. Here, you will find a trove of information compiled into a single, cohesive set of instructions and sub-guides.
??? Note "Where do I start?"
Start either by going to [the beginning](../guide/index.md) or using the search at top right of the page. It is also available at whatever point you are in your reading.
??? Note "Notes on the journey"
This guide is a work in progress. It will probably never be "finished". You may find broken links when you click on some search results and during some navigation steps. Please report these. Otherwise, most of the search functionality is a great experience and can help you find linked topics. Try to search for something in one section of the reading. It will show up in many other places.
??? Note "Disclaimer"
There might be some wrong or outdated information in this guide because no one is perfect. Your experience may vary. Remember, check regularly for an updated version of this guide. Please do your own independent, well-thought research. There is no one resource online that can provide 100% security, anonymity, and/or privacy.
- For mirrors see [Mirrors](../mirrors/index.md) and the links at the bottom right of the page. You should see these on every page.
- For help in comparing versions see [Comparing versions](../guide/index.md#appendix-a6-comparing-versions)
Feel free to submit issues **(please do report anything wrong)** using GitHub Issues at: <https://github.com/Anon-Planet/thgtoa/issues>. We also accept Merge Requests (MR) from our Gitlab and many other places. Do not hesitate to report issues and suggestions!
??? tip "Discuss ideas on Matrix for real-time chat"
We offer a Matrix.org hosted space of our own. Check it out!
- Read [the rules](https://psa.anonymousplanet.net/), please
- Matrix Room: https://matrix.to/#/#nth:anonymousplanet.net
- Matrix Space: https://matrix.to/#/#psa:anonymousplanet.net
- Admins: @daskolburn:thomcat.rocks and @thehidden:tchncs.de
???+ tip "Follow us on"
- Twitter at <https://twitter.com/AnonyPla>
- Mastodon at <https://mastodon.social/@anonymousplanet>
To contact me, see the updated information on the website or send an e-mail to <contact@anonymousplanet.net>
**Please consider [donating](../guide/index.md#donations) if you enjoy the project and want to support the hosting fees or support the funding of initiatives like the hosting of Tor Exit Nodes.**
???+ example "Recommended Reading"
Some of those resources may, in order to sustain their project, contain or propose:
- Sponsored commercial content
- Monetized content through third party platforms (such as YouTube)
- Affiliate links to commercial services
- Paid Services such as consultancy
- Premium content such as ad-free content or updated content
- Merchandising
_Note that these websites could contain affiliate/sponsored content and/or merchandising. This guide does not endorse and is not sponsored by any commercial entity in any way._
If you skipped those, you should really still consider viewing this YouTube playlist from the Techlore Go Incognito project (<https://github.com/techlore-official/go-incognito> <sup>[[Archive.org]](https://web.archive.org/web/https://github.com/techlore-official/go-incognito)</sup>) as an introduction before going further: <https://www.youtube.com/playlist?list=PL3KeV6Ui_4CayDGHw64OFXEPHgXLkrtJO> <sup>[[Invidious]](https://yewtu.be/playlist?list=PL3KeV6Ui_4CayDGHw64OFXEPHgXLkrtJO)</sup>. This guide will cover many of the topics in the videos of this playlist with more details and references as well as some added topics not covered within that series. This will just take you 2 or 3 hours to watch it all.
_Anonymous Planet_ **does not** participate in any sponsoring, endorsement, advertising, or other affiliate programs for any entity. We only rely on anonymous donations in a closed, transparent loop system.
??? tip "Privacy related"
- AnarSec: <https://www.anarsec.guide/>
- EFF Surveillance Self-Defense: <https://ssd.eff.org/>
- Prism-Break: <https://prism-break.org/>
- Privacy Guides: <https://privacyguides.org>
- Techlore: <https://techlore.tech>
- The New Oil: <https://thenewoil.org>
- PrivacyTools.io: <https://privacytools.io>
??? tip "Blogs and personal websites"
- CIA Officer's Blog: <https://officercia.mirror.xyz/>
- Continuing Ed: <https://edwardsnowden.substack.com/>
- Madaidan's Insecurities: <https://madaidans-insecurities.github.io/>
- Seirdy's Home: <https://seirdy.one/>
??? tip "Useful resources"
- KYC? Not me: <https://kycnot.me/>
- Library Genesis: <https://en.wikipedia.org/wiki/Library_Genesis> <sup>[[Wikiless]](https://wikiless.tiekoetter.com/wiki/Library_Genesis)</sup> (see their latest known URL in the Wikipedia article)
- Real World Onion Sites: <https://github.com/alecmuffett/real-world-onion-sites>
- Sci-Hub <https://en.wikipedia.org/wiki/Sci-Hub> <sup>[[Wikiless]](https://wikiless.tiekoetter.com/wiki/Sci-Hub)</sup> (see their latest known URL in the main Wikipedia article)
- Terms of Service, Didn't Read: <https://tosdr.org>
- Whonix Documentation: <https://www.whonix.org/wiki/Documentation> <sup>[[Archive.org]](https://web.archive.org/web/https://www.whonix.org/wiki/Documentation)</sup>
??? note "We are not affiliated with Anonymous or Riseup"
One or two of our community members uses or has used the resources of Riseup. We are not affiliated with Riseup in any manner.
We also hold **no affiliation** with the [Anonymous](https://en.wikipedia.org/wiki/Anonymous_(hacker_group)) <sup>[[Wikiless]](https://wikiless.tiekoetter.com/wiki/Anonymous_(hacker_group))</sup> <sup>[[Archive.org]](https://web.archive.org/web/https://en.wikipedia.org/wiki/Anonymous_(hacker_group))</sup> hacker collective.
+85 -20
View File
@@ -4,9 +4,9 @@ description: "Release Notes"
schema:
"@context": https://schema.org
"@type": Organization
"@id": https://www.anonymousplanet.org/
"@id": https://anonymousplanet.net/
name: Anonymous Planet
url: https://www.anonymousplanet.org/authors/
url: https://anonymousplanet.net/
logo: ../media/profile.png
sameAs:
- https://github.com/Anon-Planet
@@ -14,45 +14,108 @@ schema:
- https://mastodon.social/@anonymousplanet
---
# Release Notes
Notable changes to the guide and its tooling. Follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
Notable changes to the guide. Follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
---
## [v1.2.3] — 2026-05-22
## [v1.2.5]
CI/CD pipeline split into independent stages, dark PDF quality improved, and the changelog is now updated automatically on every release. v1.2.2 was just a placeholder, this is a minor but CI breaking change.
!!! Note "Meta"
!!! success "Added"
- Website theme customization
- License change to accomodate our fiscal host Open Source Collective
- **Dark mode PDF** (`scripts/convert.py`): pixel-level converter replaces the broken `--prefers-color-scheme=dark` Chromium flag. Produces a 200 DPI hacker-themed PDF (`#1f1f31` background, `#e0e0e0` text, `#5e8bde` links) with batched page processing to avoid OOM.
!!! Note "Changed"
- Comprehensive updates throughout the guide reflecting the transition to Whonix 18.x as the newest version
- Replaced outdated Whonix.org wiki docs links with GitHub releases mirror and Archive.org backups
- Updated Virtualbox hardening instructions (e.g., network time desync offsets, Spectre/Meltdown mitigations)
- Added AppArmor enabling guidance for Debian-based Whonix 18.x Workstation VMs
- Upgraded download instructions to use GitHub Releases as primary source with Archive.org fallback
- Added new "Whonix Improvements" section documenting key changes from 17.x to 18.x (automated release upgrade, improved Qubes integration)
- Updated Qubes OS compatibility notes: Whonix 18.x officially supported on R4.3+, Whonix 17.x compatible with R4.2+
- Enhanced system verification instructions after upgrades (checkvm, tor --verify)
- Improved documentation links to use clean markdown format with Archive.org mirrors for resilience
- Added comprehensive upgrade path guidance with backup procedures
- Updated 196! Wikipedia reference links throughout the entire guide
!!! Note "Added"
- All Whonix.org/wiki links now have Archive.org mirror backups for availability
- Upgrade path documented: Whonix 17 to 18 with automated release-upgrade support
- Detailed Qubes OS compatibility notes for both Whonix versions
- You can now get the Anonymous Planet PGP keyring from the site (copy/paste or download)
!!! Note "Improved"
- Virtualbox hardening section reorganized
- AppArmor configuration guidance added where applicable (Whonix 18.x)
- KVM alternative documented in Appendix N for Linux users seeking better security than VirtualBox
- Whonix Improvements subsection added before "Pick your guest workstation" VMs section
## [v1.2.4]
!!! Note "Meta"
- Rename workflows (GH - now we can know the order)
!!! Note "Changed"
- Change the repo URL for our tor mirror
- Fix recommended reading admonition
- Refactoring some things and removing others
- More meta changes to the pipeline
- Rewrite developer guide for current pipeline
!!! Note "Fixed"
- Fix an inline reference
- Use the Anonymous Planet RSK for releases (we used the MSK for testing)
- Prevent history dump and filter noise commits
- Actually save per-page PDFs for qpdf, not PNGs
- Fail fast with helpful message if pdftoppm or qpdf missing
## [v1.2.3]
CI/CD pipeline split into independent stages, dark PDF quality improved, release signing automated, and the changelog now updates itself on every build. Skipping v1.2.2 which was a placeholder and contained broken Python unsuitable for a tag/release.
???+ tip "Added"
- **Dark mode PDF** (`scripts/convert.py`): pixel-level converter replaces the broken `--prefers-color-scheme=dark` Chromium flag. Produces a 200 DPI hacker-themed PDF (`#1f1f31` background, `#e0e0e0` text, `#5e8bde` links) with batched page processing to avoid OOM on large documents.
- **Three independent CI workflows** replacing the old monolithic `build-sign-release.yml`:
- `build.yml` builds PDFs and uploads them as an artifact; no secrets required.
- `sign.yml` downloads the PDF artifact, computes SHA-256 and BLAKE2b hashes, GPG-signs all outputs, and uploads a `signatures` artifact. Can be re-run against any historical build.
- `release.yml` downloads both artifacts, uploads to VirusTotal, and publishes a tagged GitHub Release with all 12 assets attached. **Can be triggered manually against any previous sign run**.
- **`scripts/update_changelog.py`**: reads `git log` since the last version tag, categorises commits by conventional-commit prefix, and prepends a new entry here automatically after each successful build.
- **`changelog.yml`** workflow: commits the auto-generated changelog entry back to `main` after every build, with `dry_run` and `manual_version` dispatch inputs for testing.
- `01-build.yml`: builds PDFs and uploads them as an artifact; no secrets required, can be re-run freely.
- `02-sign.yml`: downloads the PDF artifact, computes SHA-256 and BLAKE2b hashes, GPG-signs all outputs, and uploads a `signatures` artifact. Can be re-run against any historical build.
- `03-release.yml`: downloads both artifacts, uploads to VirusTotal, and publishes a tagged GitHub Release with all 12 assets attached. Can be triggered manually against any previous sign run.
- **`scripts/update_changelog.py`**: reads `git log` since the last version tag, categorises commits by conventional-commit prefix, and prepends a new entry to this file automatically after each successful build.
- **`04-changelog.yml`** workflow: commits the auto-generated changelog entry back to `main` after every build, with `dry_run` and `manual_version` dispatch inputs for safe local testing.
- **`scripts/tag_release.py`**: interactive guided helper for maintainers to create GPG-signed annotated tags. Checks clean tree and branch, auto-increments the version, pulls the message from the changelog, resolves the release signing key, creates and verifies the tag, then prints the push command.
- **`docs/code/develop.md`**: full developer reference covering prerequisites, local build instructions, the pipeline flow, all required GitHub Secrets, the release process, verification steps, and a troubleshooting section for every known CI failure mode.
!!! warning "Changed"
- `build-sign-release.yml` is now deprecated push triggers removed, manual dispatch only. Will be deleted once in-flight runs complete.
- The full pipeline (build → sign → release) now chains automatically via `workflow_run` on every push to `main`.
- `build-sign-release.yml` deprecated (now removed) - push triggers removed, manual dispatch only. Will be deleted once in-flight runs complete.
- The full pipeline (build → sign → release → changelog) now chains automatically via `workflow_run` on every push to `main`.
- GPG signing uses `--pinentry-mode loopback` and `--passphrase-fd 0` to avoid interactive prompts on headless runners.
- VirusTotal scans moved to the release stage so they run once per release, not once per build.
- `.gitignore` updated to track `.b2` per-file hash files alongside existing `.sha256` and `.sig` entries.
- Stale information removed from the guide; deprecated ODT section in Appendix A6 commented out.
- Footer copyright information corrected.
!!! bug "Fixed"
- Broken internal links and a mismatched cross-reference in `docs/about/index.md`.
- Deprecated ODT section commented out in Appendix A6 of the guide.
- `_save_images_as_pdf` in `convert.py` was passing raw PNG files to `qpdf --pages`, which only accepts PDF inputs. Fixed by quantizing each page to palette mode (256 colours, FASTOCTREE) and saving as a single-page PDF before merging.
- `convert.py` now fails immediately with install instructions if `pdftoppm` or `qpdf` are missing, instead of crashing with an unhelpful `FileNotFoundError`.
- Pillow `KeyError: 'JPEG'` on CI resolved by installing `mkdocs-material[imaging]` and using palette-mode PDF encoding instead of RGB+JPEG.
- Orphaned footnote citations `[^536]` and `[^537]` (Australian privacy law and the Identify and Disrupt Act) restored at the key disclosure law paragraph in the guide.
- Broken internal links and mismatched cross-references throughout the guide corrected.
---
## [v1.2.1] — 2025
## [v1.2.1]
First automated PDF build and the start of the CI pipeline.
!!! success "Added"
???+ tip "Added"
- `scripts/build_guide_pdf.py`: builds the MkDocs site and renders the full guide to a single PDF via headless Chromium (Chrome or Edge). Supports `--dark`, `--light`, and `--both` modes.
- GitHub Actions workflow that installs Chromium, runs the build script, and uploads `export/thgtoa.pdf` as an artifact on every push to `main` or manual dispatch.
@@ -71,5 +134,7 @@ First automated PDF build and the start of the CI pipeline.
---
[v1.2.5]: https://github.com/Anon-Planet/thgtoa/releases/tag/v1.2.5
[v1.2.4]: https://github.com/Anon-Planet/thgtoa/releases/tag/v1.2.4
[v1.2.3]: https://github.com/Anon-Planet/thgtoa/releases/tag/v1.2.3
[v1.2.1]: https://github.com/Anon-Planet/thgtoa/releases/tag/v1.2.1
-50
View File
@@ -1,50 +0,0 @@
# 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.*
+349 -10
View File
@@ -1,6 +1,58 @@
---
title: Content Contributions
title: "Content Contributions"
description: We are the maintainers of the Hitchhiker's Guide and the PSA Matrix space.
schema:
"@context": https://schema.org
"@type": Organization
"@id": https://anonymousplanet.net/
name: Anonymous Planet
url: https://anonymousplanet.net/code/
logo: ../media/profile.png
sameAs:
- https://github.com/Anon-Planet
- https://opencollective.com/anonymousplanetorg
---
Install these before anything else.
=== "Linux / macOS"
```sh
# Python 3.11+
python3 --version
# poppler (pdftoppm) and qpdf
sudo apt install poppler-utils qpdf # Debian/Ubuntu
brew install poppler qpdf # macOS
# GPG
sudo apt install gnupg # Debian/Ubuntu
brew install gnupg # macOS
# Python dependencies
pip install "mkdocs-material[imaging]" pillow numpy
```
=== "Windows"
```powershell
# Python 3.11+ from https://python.org
# poppler: download from https://github.com/oschwartz10612/poppler-windows/releases
# Extract and add the bin\ folder to PATH
# qpdf: download from https://github.com/qpdf/qpdf/releases
# Extract and add the bin\ folder to PATH
# GPG: download Gpg4win from https://gpg4win.org
# Python dependencies
pip install "mkdocs-material[imaging]" pillow numpy
```
You also need **Google Chrome** or **Microsoft Edge** installed for the light-mode PDF build (headless Chromium).
You can [submit bugs and feature requests](https://github.com/Anon-Planet/thgtoa/issues/new) with detailed information about your issue or idea:
- If you'd like to propose an addition, please follow the standards outlined here.
@@ -10,8 +62,6 @@ You can [submit bugs and feature requests](https://github.com/Anon-Planet/thgtoa
For those of you who are looking to add content to the guide, include the following:
##### <u>Pull Requests</u>
- **Do** create a [topic branch] to work on instead of working directly on `main`. This helps to:
+ Protect the process.
+ Ensures users are aware of commits on the branch being considered for merge.
@@ -26,7 +76,7 @@ For those of you who are looking to add content to the guide, include the follow
- **Don't** abandon your pull request. Being responsive helps us land your changes faster.
- **Don't** post questions in older closed PRs.
- **Do** stick to the guide to find common style issues.
- **Don't** make mass changes (such as replacing "I" with "we") using automated serach/replace functionality.
- **Don't** make mass changes (such as replacing "I" with "we") using automated search/replace functionality.
+ Search/replace doesn't understand context, and as such, will inevitably cause inconsistencies and make the guide harder to read.
+ If it's part of a larger PR, it'll also make the reviewer's life harder, as they'll have to go through manually and undo everything by hand.
+ _If you're going to make mass changes, take the time to do it properly_. Otherwise we'll just have to undo it anyway.
@@ -39,23 +89,312 @@ When reporting guide issues:
- **Don't** file duplicate reports; search for your bug before filing a new report.
- **Don't** attempt to report issues on a closed PR.
### Large PRs
Please split large sets of changes into multiple PRs. For example, a PR that adds Windows 11 support, removes Windows AME references, and fixes typos can be split into 3 PRs. This makes PRs easier to review prior to merging.
For an example of what _not_ to do, see: <https://github.com/Anon-Planet/thgtoa/pull/51>. This PR contains enough changes to split into multiple smaller and individually reviewable PRs.
### Updating PRs
While a PR is being reviewed, modifications may be made to it by the reviewer prior to merging. If this is the case, a new branch will be created for the PR's review. If you would like to submit a change to a PR that is in the process of being reviewed, _do not update the PR directly_. This will only cause merge conflicts and delay the PR from being merged. Instead, submit your changes to the PR's review branch.
For an example of what _not_ to do, see: <https://github.com/Anon-Planet/thgtoa/pull/51>. Instead of submitting changes to the PR directly, they should have been submitted as changes to the PR's associated review branch.
---
**Thank you** for taking the few moments to read this far! You're already way ahead of the
curve, so keep it up!
## Repository layout
```txt
.github/
workflows/
01-build.yml # builds PDFs, uploads artifact
02-sign.yml # hashes + GPG signs, uploads signatures artifact
03-release.yml # publishes GitHub Release with all assets
04-changelog.yml # prepends a new entry to docs/changelog/index.md
publish.yml # deploys MkDocs site to GitHub Pages
docs/
guide/index.md # the guide (single Markdown file)
changelog/ # release notes
code/ # this page
export/ # PDF output (PDFs gitignored; .sha256, .b2sum, .asc tracked)
pgp/ # public signing keys
scripts/
build_guide_pdf.py # MkDocs + Chromium PDF builder
convert.py # pixel-based dark mode PDF converter
update_changelog.py # auto-generates changelog entries from git log
setup_workflow.py # GitHub Secrets setup assistant
verify_pdf.py # signature verification helper
archived/
tag_release.py # ARCHIVED - GPG tag helper (not used in current flow)
```
## Building locally
```sh
python scripts/build_guide_pdf.py --both
```
This builds the MkDocs site, renders it to `export/thgtoa.pdf` via headless Chromium, then calls `scripts/convert.py` to produce `export/thgtoa-dark.pdf`.
| Flag | Effect |
|------|--------|
| `--both` | Light PDF then dark PDF |
| (no flag) | Light PDF only |
| `--dark` | Dark PDF only (light PDF must already exist) |
Build only the dark PDF from an existing light PDF:
```sh
python scripts/convert.py export/thgtoa.pdf export/thgtoa-dark.pdf
```
Options:
| Flag | Default | Description |
|------|---------|-------------|
| `--dpi` | `200` | Rasterization DPI. 150 = smaller file, 300 = sharper but slow |
| `--batch-size` | `50` | Pages per batch. Reduce if you hit OOM |
| `--bg` | `1f1f31` | Background colour (hex) |
| `--text` | `e0e0e0` | Body text colour (hex) |
| `--link` | `5e8bde` | Link / blue element colour (hex) |
# Preview the MkDocs site
```sh
mkdocs serve
```
Opens at `http://127.0.0.1:8000`.
# CI/CD pipeline overview
The pipeline is fully manual after the initial build - no step automatically triggers the next. This prevents version mismatches between what was built, what was signed, and what gets released. The workflows are numbered to help guide you.
```txt
push to main (or manual trigger)
01-build.yml
Builds thgtoa.pdf + thgtoa-dark.pdf.
Uploads artifact: pdfs
Note the run ID.
│ # manually trigger 02-sign.yml with the build run ID
02-sign.yml
Downloads pdfs artifact. Hashes (SHA-256 + BLAKE2b) and GPG-signs
all files. Commits export/ back to main. Uploads artifacts:
signatures, pdfs-signed
Note the run ID.
│ # manually trigger 03-release.yml with the sign run ID
03-release.yml
Downloads signatures + pdfs-signed artifacts. Runs VirusTotal.
Creates GitHub Release tagged release-YYYYMMDD-<short-sha>.
│ # manually trigger 04-changelog.yml with the version string
04-changelog.yml
Runs update_changelog.py, prepends a new ## [vX.Y.Z] entry,
commits back to main.
```
Each stage is independent. If signing fails (e.g. an expired/revoked key, other problems in CI), re-run only `02-sign.yml` pointing at the existing build artifact - no need to rebuild the PDFs.
!!! warning "Before you push"
- Make sure the working tree is clean (`git status`)
- Run `mkdocs build` locally if you changed `docs/` to catch broken links before CI does
- If you added new footnotes, verify they have both a definition `[^N]:` and at least one inline citation `[^N]`
---
# Release process
## Trigger a build
Push to `main` - `01-build.yml` runs automatically when `docs/`, `mkdocs.yml`, or `scripts/` change. You can also trigger it manually from **Actions → Build PDFs → Run workflow**.
Once it completes successfully, **note the run ID** from the URL or the Actions list.
---
## Sign the PDFs
Go to **Actions → Sign PDFs → Run workflow**.
| Input | Value |
|-------|-------|
| `build_run_id` | The run ID from step 1 |
`02-sign.yml` will:
- Download the PDFs artifact from the build run
- Compute SHA-256 and BLAKE2b hashes, writing `thgtoa.pdf.sha256`, `thgtoa.pdf.b2sum`, `sha256sums.txt`, `b2sums.txt`, and the dark equivalents
- GPG-sign all PDFs and hash files, writing `.asc` detached signature files
- Commit the updated `export/` directory back to `main`
- Upload two artifacts: `signatures` and `pdfs-signed`
Once it completes successfully, **note the run ID**.
---
## Publish the release
Go to **Actions → Release → Run workflow**.
| Input | Value |
|-------|-------|
| `sign_run_id` | The run ID from step 2 |
| `prerelease` | `false` for a normal release |
`03-release.yml` will:
- Download `signatures` and `pdfs-signed` artifacts from the sign run
- Upload both PDFs to VirusTotal
- Auto-generate a release tag in the format `release-YYYYMMDD-<short-sha>` (e.g. `release-20260527-abc1234`)
- Create a GitHub Release with all PDFs, hash files, and signatures attached, and the VirusTotal report URLs in the body
No version number needs to be chosen at this step - the tag is derived from the date and commit SHA, so it is always unique and always traceable.
---
## Update the changelog
Go to **Actions → Update Changelog → Run workflow**.
| Input | Value |
|-------|-------|
| `version` | The human-readable version string, e.g. `v1.2.4` |
| `dry_run` | `true` to preview without committing |
`04-changelog.yml` runs `scripts/update_changelog.py`, which:
- Reads git log since the last `## [vX.Y.Z]` heading in the changelog
- Categorises commits into Added / Changed / Fixed using conventional-commit prefixes
- Prepends a new `## [version]` admonition block to `docs/changelog/index.md`
- Commits the result back to `main`
The version string is the only human decision in the release process. It goes into the changelog only - it does not affect the release tag.
!!! tip "Previewing the changelog entry"
Run with `dry_run: true` first to review the generated entry before it is committed.
---
## Release tag format
Release tags use the format `release-YYYYMMDD-<short-sha>`, for example:
```txt
release-20260527-abc1234
```
This format is always unique, requires no version decision at release time, and is directly traceable to the commit that was built. The version string (e.g. `v1.2.4`) is a separate, human-assigned label that lives only in the changelog.
---
## Commit message format
All commits must follow the [Conventional Commits](https://www.conventionalcommits.org) format. This is enforced by the `commitizen` pre-commit hook. Not because we want to limit cooperation with others, but becasue it promotes a cleaner Changelog; we can avoid all the noise by doing this programatically.
```txt
<type>(<scope>): <description>
```
Accepted types and their changelog bucket:
| Type | Bucket |
|------|--------|
| `feat`, `feature`, `add` | Added |
| `fix`, `bugfix`, `revert`, `security` | Fixed |
| `perf`, `refactor`, `change`, `chore`, `ci`, `docs`, `style`, `test`, `build` | Changed |
Examples:
```sh
feat: add dark-mode PDF export
fix(scripts): handle locked PDF on Windows
docs: update developer workflow guide
chore(ci): pin Chrome version to 120
```
# Verifying a release
Anyone can verify the authenticity of a release download.
```sh
# Import the release signing key
gpg --import pgp/anonymousplanet-release.asc
# Verify the PDFs
gpg --verify thgtoa.pdf.asc thgtoa.pdf
gpg --verify thgtoa-dark.pdf.asc thgtoa-dark.pdf
# Verify the hash files
gpg --verify sha256sums.txt.asc sha256sums.txt
gpg --verify b2sums.txt.asc b2sums.txt
# Check the PDF hashes match
sha256sum -c sha256sums.txt
b2sum -c b2sums.txt
```
A successful verify looks like:
```txt
gpg: Signature made Sun 31 May 2026 03:23:26 AM EDT
gpg: using EDDSA key C3023DBEA3FB38C438BA1EEDCEC60AEDE8B992A2
gpg: Good signature from "Anonymous Planet Release Signing Key" [ultimate]
Primary key fingerprint: C302 3DBE A3FB 38C4 38BA 1EED CEC6 0AED E8B9 92A2
```
You can safely ignore Github, Codeberg, etc. warnings like "The email in this signature doesnt match the committer email."
```txt
λ > git tag -v v1.2.3
object cdc54d8b3bc2b286827b23921d8d4062f85295cf
type commit
tag v1.2.3
tagger nopeitsnothing <no@anonymousplanet.net> 1780212206 -0400
v1.2.3
gpg: Signature made Sun 31 May 2026 03:23:26 AM EDT
gpg: using EDDSA key C3023DBEA3FB38C438BA1EEDCEC60AEDE8B992A2
gpg: Good signature from "Anonymous Planet Release Signing Key" [ultimate]
Primary key fingerprint: C302 3DBE A3FB 38C4 38BA 1EED CEC6 0AED E8B9 92A2
```
---
## Troubleshooting
**`cairosvg` missing during MkDocs build**
Install the imaging extras: `pip install "mkdocs-material[imaging]"`. Required by the `social` plugin.
**`KeyError: 'JPEG'` in convert.py**
Pillow needs libjpeg. Reinstall after installing the system lib: `sudo apt install libjpeg-dev && pip install --force-reinstall pillow`.
**`qpdf: can't find PDF header`**
Ensure you are on the current version of `convert.py` - qpdf only accepts PDF inputs, not PNG.
**GPG signing fails on CI with `No secret key`**
The `GPG_PRIVATE_KEY` secret is missing or malformed. Re-export with `gpg --armor --export-secret-keys <fingerprint>` and paste the full block including header and footer lines.
**GPG signing fails with `Bad passphrase`**
The `GPG_PASSPHRASE` secret has a trailing space or newline. Paste it again with no surrounding whitespace.
**`03-release.yml` fails on VirusTotal**
The `VT_API_KEY` is missing, invalid, or over the rate limit (500 requests/day on the free tier). Check the secret and re-run after a few minutes.
**`02-sign.yml` fails downloading PDF artifact**
The `build_run_id` is wrong, or the artifact has expired (90-day retention). Trigger a new build and use the fresh run ID.
**Changelog already contains version X**
`update_changelog.py` will error if `MANUAL_VERSION` is set to a version already in the changelog. Choose the next version string.
**Footnote warnings from MkDocs (`link '#fnref:N' has no anchor`)**
A footnote definition `[^N]:` exists without a matching inline citation. Add the citation or remove the orphaned definition.
[discussions]: https://github.com/Anon-Planet/thgtoa/discussions
[issues]: https://github.com/Anon-Planet/thgtoa/issues
[help fellow users with open issues]: https://github.com/Anon-Planet/thgtoa/issues
+94 -87
View File
@@ -1,87 +1,94 @@
---
title: Impressum
---
# A Constitution for an Anonymous Planet.
To amend the rules and regulations of the network and of the PSA community, this constitution is hereby set forth. It is applicable to all the projects of the initiative, especially the Hitchhiker's Guide to Online Anonymity. All members/collaborators must abide by these lines when contributing within the context of the initiative.
## Requirements
> Content is licensed under **[Creative Commons Attribution NonCommercial](https://creativecommons.org/licenses/by-nc/3.0/)** to prevent commercial usage.
### Anonymity above everything.
Anonymity is necessary to maintain the balance of power, specifically to help journalists, whistleblowers, lawyers, scientists, and victims of oppression. Anonymity first, even if that means using non-free and/or proprietary means. Security and privacy are second, again, even if using non-free or non-open-source and/or proprietary means. In this sense, the ends may at times justify proprietary means.
### Independence.
The Anonymous Planet initiative has no affiliation with the "Anonymous" collective and does not endorse their activities.
Any overlap of their activities and our guide are purely coincidental.
### Accessibility.
We will strive to always keep available the following methods of reading the Hitchhiker's Guide:
- online;
- offline (e.g., PDF, ODT);
- via the Tor network
### Freedom.
Maintain free, open-source, and non-commercial nature of all our projects. This does not mean proprietary and/or closed-source tools won't be recommendeded. All scientific knowledge should be free for anyone and we support and encourage Sci-Hub and LibGen. Any attempt to erode the freedom of information and flow of knowledge of our projects, in any manner, is hostile.
### Verifiability, falsifiability and reproducibility.
We will make every effort to be transparent about any and all bias we have.
Anyone claiming to be unbiased is lying, therefore we will not falsely claim to be.
All our content shall be verifiable, reproducible and fact-checked:
- academic references (e.g., studies, papers, and peer reviewed publications);
- reputable media references (e.g., articles, videos, and documentaries);
- official documentation (e.g., manuals, field guides, and technical documents);
- renowned and reputable expert review;
- direct testing by our own collaborators for falsifiablity
### Innocence.
Suspected offenders are innocent until proven guilty, with zero tolerance for abuse of power or position.
Any accusing/moderating member is:
- Subject to the burden of proving the wrong-doing of the offender.
- Required to motivate any sanction.
Any offender has the right to:
- Face their accuser (know who is accusing them).
- Appeal sanctions to an uninvolved third party.
- Participate in their own incrimination (the burden of proof lies with the accuser).
- Due process of the above.
### Freedom of thought.
Open-minded and pragmatic - with no tolerance for gatekeeping.
Critical thinking and fact-checking are strongly encouraged; we welcome criticism including of a harsh nature (excluding ad-hominem and slurs).
### We do not tolerate intolerance.
See the [Paradox of Tolerance](https://en.wikipedia.org/wiki/Paradox_of_tolerance), which includes hate speech.
### No analytics.
Note that, while we will never use analytics, the (now free) platforms hosting our content might be gathering such analytics outside of our control, such as Github pages. As the initiative progresses, we will strive to avoid these as soon as possible.
### No profit.
Any excess donations will only be used to support our main projects first and possibly support other intitiatives (like hosting Tor exit nodes). In all cases, we abide by the following principles:
- Funding transparency (i.e., all donations, spendings, source code, and future goals will be public).
- Acceptance of donations from any entity anonymously or acknowledged (opt-in) will not have any influence on our content.
- No sponsored content.
- No affiliate links.
- No product placements.
- No advertising.
**Disclaimer: it is possible that, coincidentally, a donation could correlate with a recommendation. It will then be clearly stated that while the donation was welcome, the donating entity will not be gaining visibility/coverage/endorsement/recommendations due to such a donation.**
## Core Goals.
Help people in need of anonymity to maintain both their physical and digital safety.
## Non-Goals.
Help any people who are using this knowledge for bad purposes. Helping people takes precedence and we know our content can be used nefariously. Our initiative believes in having one good person given an anonymous voice, safely, is worth the risk of having several using our content for evil. As we do adhere to a fair "rule of law" system which, having 9 criminals and 1 innocent person free, is much better than having one innocent person in prison among 9 criminals.
**Yours faithfully, Anonymous Planet**
---
title: Impressum
---
To amend the rules and regulations of the network and of the PSA community, this constitution is hereby set forth. It is applicable to all the projects of the initiative, especially the Hitchhiker's Guide to Online Anonymity. All members/collaborators must abide by these lines when contributing within the context of the initiative.
## Requirements
### Anonymity above everything
Anonymity is necessary to maintain the balance of power, specifically to help journalists, whistleblowers, lawyers, scientists, and victims of oppression. Anonymity first, even if that means using non-free and/or proprietary means. Security and privacy are second, again, even if using non-free or non-open-source and/or proprietary means. In this sense, the ends may at times justify proprietary means.
### Independence
The Anonymous Planet initiative has no affiliation with the "Anonymous" collective and does not endorse their activities
Any overlap of their activities and our guide are purely coincidental.
### Accessibility
We will strive to always keep available the following methods of reading the Hitchhiker's Guide:
- online;
- offline (e.g., PDF, ODT);
- via the Tor network
### Freedom
Maintain free, open-source, and non-commercial nature of all our projects. This does not mean proprietary and/or closed-source tools won't be recommendeded. All scientific knowledge should be free for anyone and we support and encourage Sci-Hub and LibGen. Any attempt to erode the freedom of information and flow of knowledge of our projects, in any manner, is hostile.
### Verifiability, falsifiability and reproducibility
We will make every effort to be transparent about any and all bias we have.
Anyone claiming to be unbiased is lying, therefore we will not falsely claim to be.
All our content shall be verifiable, reproducible and fact-checked:
- academic references (e.g., studies, papers, and peer reviewed publications);
- reputable media references (e.g., articles, videos, and documentaries);
- official documentation (e.g., manuals, field guides, and technical documents);
- renowned and reputable expert review;
- direct testing by our own collaborators for falsifiablity
### Innocence
Suspected offenders are innocent until proven guilty, with zero tolerance for abuse of power or position.
Any accusing/moderating member is:
- Subject to the burden of proving the wrong-doing of the offender
- Required to motivate any sanction
Any offender has the right to:
- Face their accuser (know who is accusing them)
- Appeal sanctions to an uninvolved third party
- Participate in their own incrimination (the burden of proof lies with the accuser)
- Due process of the above
### Freedom of thought
Open-minded and pragmatic - with no tolerance for gatekeeping.
Critical thinking and fact-checking are strongly encouraged; we welcome criticism including of a harsh nature (excluding ad-hominem and slurs).
### We do not tolerate intolerance
See the [Paradox of Tolerance](https://en.wikipedia.org/wiki/Paradox_of_tolerance), which includes hate speech.
### No analytics
Note that, while we will never use analytics, the (now free) platforms hosting our content might be gathering such analytics outside of our control, such as Github pages. As the initiative progresses, we will strive to avoid these as soon as possible.
### No profit
Any excess donations will only be used to support our main projects first and possibly support other intitiatives (like hosting Tor exit nodes). In all cases, we abide by the following principles:
- Funding transparency (i.e., all donations, spendings, source code, and future goals will be public)
- Acceptance of donations from any entity anonymously or acknowledged (opt-in) will not have any influence on our content
- No sponsored content
- No affiliate links
- No product placements
- No advertising
**Disclaimer: it is possible that, coincidentally, a donation could correlate with a recommendation. It will then be clearly stated that while the donation was welcome, the donating entity will not be gaining visibility/coverage/endorsement/recommendations due to such a donation.**
## Core Goals
Help people in need of anonymity to maintain both their physical and digital safety.
## Non-Goals
Help any people who are using this knowledge for bad purposes. Helping people takes precedence and we know our content can be used nefariously. Our initiative believes in having one good person given an anonymous voice, safely, is worth the risk of having several using our content for evil. As we do adhere to a fair "rule of law" system which, having 9 criminals and 1 innocent person free, is much better than having one innocent person in prison among 9 criminals.
**Yours faithfully, Anonymous Planet**
+112 -111
View File
@@ -1,111 +1,112 @@
---
title: How to Get Involved
---
There are multiple ways you can add to the guide. Donations to support this project are welcome but are entirely optional. Those donations are mainly used to pay for Tor onion hosting (VPS), mail hosting, domain name registration, and to maintain/run Tor exit nodes. **No profit is ever being made**. All donations and spendings are being logged here below for transparency. Some costs for load balancer servers have been omitted for privacy reasons, but are not paid for with existing Anonymous Planet finances.
<span style="color: red">**Current project donation goals:**</span>
- <del>Funding for a VPS for hosting our .onion website</del>: **done**
- <del>Funding for extending our domain name</del>: **Recovery of original domain secured until 2029**
- Funding for a decent mail hosting
- Funding for a VPS for hosting various services
## Donate using Monero (XMR)
Total Monero donations received: **7.101317184263 XMR**
Total Monero remaining: **2.059336719397 XMR**
Here is the address for the main project:
```46crzj54eL493BA68pPT4A1MZyKQxrpZu9tVNsfsoa5nT85QqCt8cDTfy1fcTH1oyjdtUbhmpZ4QcVtfEXB337Ng6PS21ML```
![][1]
## Donate using Bitcoin (BTC)
Total Bitcoin donations received: **1.89353 mBTC**
Total Bitcoin remaining: **0 mBTC**
Here are the addresses for the main project:
SegWit address: ```bc1qp9g2c6dquh5lnvft50esxsl97kupdpyqyd4kkv```
Legacy address: ```1BBgBSVe6w4DWq2BewUQhDEjsNovhfPswD```
![][2]_____________________![][3]
## Content Contributions
You can easily contribute code or information suggestions at our code repositories listed at the bottom of the website and on the [Mirrors](../mirrors/index.md) tab above. We have many options that are easily accessible. Please follow our [contributing guidelines](../code/index.md) and use good PR syntax.
**Thank you for any contribution. All donations will be mentioned on this page.**
### Donations log
- 2021-02-06 16:48: 0.1 XMR
- 2021-03-15 00:09: 1.24869 mBTC
- 2021-03-15 08:41: 0.07896 mBTC
- 2021-03-31 16:28: 1 XMR (Special thanks for this very generous donation)
- 2021-04-03 22:31: 0.5 XMR (Special thanks for this very generous donation)
- 2021-05-07 06:22: 0.010433355105 XMR
- 2021-06-16 03:05: 0.03 XMR
- 2021-06-27 18:39: 0.05 XMR
- 2021-07-12 07:24: 0.02 XMR
- 2021-07-16 14:31: 0.1 mBTC
- 2021-07-20 21:01: 0.058981 XMR
- 2021-07-24 15:16: 0.000000000001 XMR
- 2021-07-25 02:37: 0.000000000001 XMR
- 2021-08-03 00:17: 0.04119191113 XMR
- 2021-08-07 15:05: 0.206328241262 XMR
- 2021-08-10 11:42: 0.21 mBTC
- 2021-08-13 00:25: 0.25 XMR
- 2021-08-14 04:58: 0.25588 mBTC
- 2021-08-30 17:32: 0.000000000001 XMR
- 2021-09-17 14:34: 0.018 XMR
- 2021-10-01 06:23: 0.000000002137 XMR
- 2021-10-02 19:16: 1 XMR (Special thanks for this very generous donation)
- 2021-10-17 15:40: 0.02 XMR
- 2021-10-18 16:06: 0.1958 XMR
- 2021-11-12 20:42: 0.02 XMR
- 2021-11-14 18:28: 0.018 XMR
- 2021-12-03 21:38: 0.10134722595 XMR
- 2021-12-16 01:16: 1 XMR (Special thanks for this very generous donation)
- 2021-12-16 18:06: 0.017 XMR
- 2022-01-09 17:54: 0.045918219893 XMR
- 2022-01-15 17:35: 0.014 XMR
- 2022-01-24 21:08: 0.010786 XMR
- 2022-01-26 12:07: 0.010391 XMR
- 2022-02-03 19:59: 0.013013984 XMR
- 2022-02-18 17:27: 0.019 XMR
- 2022-03-14 10:25: 0.0139887 XMR
- 2022-07-30 03:51: 0.0222 XMR
- 2022-09-28 05:13: 2 XMR
- 2022-08-19: SimpleLogin.io Lifetime Premium
- 2022-09-19: 0.345024603905 XMR (Special thanks to a previous maintainer)
#### Spendings log
- 2021-03-12: 0.08181086 XMR (+fees) for domain anonymousplanet.org (1 year)
- 2021-03-16: 1.20179 mBTC (+fees) for domain anonymousplanet.org renewal (extension 3 years totalling 4 years)
- 2021-04-01: 0.8317 XMR (+fees) for basic VPS for Tor Mirror hosting
- <del>2021-04-05: 0.99367 mBTC (+fees +exchange from XMR to BTC) for Mail Hosting (1 year): <span style="color: red">**Lost**</span>
- <del>2021-04-13: 0.71895 mBTC (+fees +exchange from XMR to BTC) for Mail Hosting (extension to 2 years)</del>: <span style="color: red">**Lost**</span>
- 2021-04-25: 0.02892 mBTC (Wallet to Wallet transfer fee)
- 2021-07-13: 0.78463 mBTC (+fees +exchange from BTC to XMR) for consolidation
- <del>2021-07-13: 0.067261698061 XMR (+fees) for a Tor Exit Node (01) Hosting (3 months)</del>: <span style="color: red">**Lost**</span>
- <del>2021-07-15: 0.151959953047 XMR (+fees) for a Tor Exit Node (02) Hosting (6 months)</del>: <span style="color: red">**Lost**</span>
- <del>2021-08-16: 0.253331471239 XMR (+fees) for a Tor Exit Node (03) Hosting (12 months)</del>: <span style="color: red">**Lost**</span>
- 2021-08-18: AtomicSwap conversion from remaining mBTC (-0.56588) to XMR (+0.081904862179)
- <del>2021-08-19: 0.0644 XMR (+fees) for Mail Hosting extension</del>: <span style="color: red">**Lost**</span>
- <del>2021-09-18: 0.246971511836 XMR (+fees) for renewal 1 year of Tor Exit Node 01</del>: <span style="color: red">**Lost**</span>
- 2021-10-04: 0.26954 XMR (+fees) for domain anonymousplanet.org extension until 2029
- <del>2021-10-06: 0.236073464623 XMR (+fees) for a Tor Exit Node (04) Hosting (12 months)</del>: <span style="color: red">**Lost**</span>
- <del>2021-10-18: 0.01952 XMR (+fees) for testing a new VPS hosting provider (Privex.io) for one month</del>: <span style="color: red">**Ended**</span>
- <del>2021-10-30: 0.240787814495 XMR (+fees) for a Synapse Hosting VPS (12 months) with bots to help grow the community. This is a test program that will be converted into a Tor Exit Node in case of failure</del>: <span style="color: red">**Lost**</span>
- <del>2022-01-01: 0.28055816111 XMR (+fees) for renewal 1 year of Tor Exit Node 02</del>: <span style="color: red">**Lost**</span>
- <del>2022-02-02: 0.966793601024 XMR (+fees) to sponsor a special project (w/ Universal Declaration of Human Rights)</del>: <span style="color: red">**Lost**</span>
- <del>2022-07-11: 0.503232784687 XMR (+fees) for 1984.is VPS (12 months)</del>: <span style="color: red">**Ended**</span>
- <del>2022-09-19: 0.345024603905 XMR (+fees) for upgrading VPS RAM/Disk</del>: <span style="color: red">**Ended**</span>
[1]: ../media/monero.png
[2]: ../media/bitcoin-segwit.png
[3]: ../media/bitcoin-legacy.png
---
title: How to Get Involved
---
There are multiple ways you can add to the guide. Donations to support this project are welcome but are entirely optional. Those donations are mainly used to pay for Tor onion hosting (VPS), mail hosting, domain name registration, and to maintain/run Tor exit nodes. **No profit is ever being made**. All donations and spendings are being logged here below for transparency. Some costs for load balancer servers have been omitted for privacy reasons, but are not paid for with existing Anonymous Planet finances.
<span style="color: red">**Current project donation goals:**</span>
- <del>Funding for a VPS for hosting our .onion website</del>: **done**
- <del>Funding for extending our domain name</del>: **Recovery of original domain secured until 2029**
- Funding for a decent mail hosting
- Funding for a VPS for hosting various services
## Donate using Monero (XMR)
Total Monero donations received: **7.101317184263 XMR**
Total Monero remaining: **2.059336719397 XMR**
Here is the address for the main project:
```46crzj54eL493BA68pPT4A1MZyKQxrpZu9tVNsfsoa5nT85QqCt8cDTfy1fcTH1oyjdtUbhmpZ4QcVtfEXB337Ng6PS21ML```
![][1]
## Donate using Bitcoin (BTC)
Total Bitcoin donations received: **1.89353 mBTC**
Total Bitcoin remaining: **0 mBTC**
Here are the addresses for the main project:
SegWit address: ```bc1qp9g2c6dquh5lnvft50esxsl97kupdpyqyd4kkv```
Legacy address: ```1BBgBSVe6w4DWq2BewUQhDEjsNovhfPswD```
![][2]_____________________![][3]
## Content Contributions
You can easily contribute code or information suggestions at our code repositories listed at the bottom of the website and on the [Mirrors](../mirrors/index.md) tab above. We have many options that are easily accessible. Please follow our [contributing guidelines](../code/index.md) and use good PR syntax. Be sure to go to the [developer guide](../code/index.md) first.
**Thank you for any contribution. All donations will be mentioned on this page.**
### Donations log
- 2021-02-06 16:48: 0.1 XMR
- 2021-03-15 00:09: 1.24869 mBTC
- 2021-03-15 08:41: 0.07896 mBTC
- 2021-03-31 16:28: 1 XMR (Special thanks for this very generous donation)
- 2021-04-03 22:31: 0.5 XMR (Special thanks for this very generous donation)
- 2021-05-07 06:22: 0.010433355105 XMR
- 2021-06-16 03:05: 0.03 XMR
- 2021-06-27 18:39: 0.05 XMR
- 2021-07-12 07:24: 0.02 XMR
- 2021-07-16 14:31: 0.1 mBTC
- 2021-07-20 21:01: 0.058981 XMR
- 2021-07-24 15:16: 0.000000000001 XMR
- 2021-07-25 02:37: 0.000000000001 XMR
- 2021-08-03 00:17: 0.04119191113 XMR
- 2021-08-07 15:05: 0.206328241262 XMR
- 2021-08-10 11:42: 0.21 mBTC
- 2021-08-13 00:25: 0.25 XMR
- 2021-08-14 04:58: 0.25588 mBTC
- 2021-08-30 17:32: 0.000000000001 XMR
- 2021-09-17 14:34: 0.018 XMR
- 2021-10-01 06:23: 0.000000002137 XMR
- 2021-10-02 19:16: 1 XMR (Special thanks for this very generous donation)
- 2021-10-17 15:40: 0.02 XMR
- 2021-10-18 16:06: 0.1958 XMR
- 2021-11-12 20:42: 0.02 XMR
- 2021-11-14 18:28: 0.018 XMR
- 2021-12-03 21:38: 0.10134722595 XMR
- 2021-12-16 01:16: 1 XMR (Special thanks for this very generous donation)
- 2021-12-16 18:06: 0.017 XMR
- 2022-01-09 17:54: 0.045918219893 XMR
- 2022-01-15 17:35: 0.014 XMR
- 2022-01-24 21:08: 0.010786 XMR
- 2022-01-26 12:07: 0.010391 XMR
- 2022-02-03 19:59: 0.013013984 XMR
- 2022-02-18 17:27: 0.019 XMR
- 2022-03-14 10:25: 0.0139887 XMR
- 2022-07-30 03:51: 0.0222 XMR
- 2022-09-28 05:13: 2 XMR
- 2022-08-19: SimpleLogin.io Lifetime Premium
- 2022-09-19: 0.345024603905 XMR (Special thanks to a previous maintainer)
#### Spendings log
- 2021-03-12: 0.08181086 XMR (+fees) for domain anonymousplanet.net (1 year)
- 2021-03-16: 1.20179 mBTC (+fees) for domain anonymousplanet.net renewal (extension 3 years totalling 4 years)
- 2021-04-01: 0.8317 XMR (+fees) for basic VPS for Tor Mirror hosting
- <del>2021-04-05: 0.99367 mBTC (+fees +exchange from XMR to BTC) for Mail Hosting (1 year): <span style="color: red">**Lost**</span>
- <del>2021-04-13: 0.71895 mBTC (+fees +exchange from XMR to BTC) for Mail Hosting (extension to 2 years)</del>: <span style="color: red">**Lost**</span>
- 2021-04-25: 0.02892 mBTC (Wallet to Wallet transfer fee)
- 2021-07-13: 0.78463 mBTC (+fees +exchange from BTC to XMR) for consolidation
- <del>2021-07-13: 0.067261698061 XMR (+fees) for a Tor Exit Node (01) Hosting (3 months)</del>: <span style="color: red">**Lost**</span>
- <del>2021-07-15: 0.151959953047 XMR (+fees) for a Tor Exit Node (02) Hosting (6 months)</del>: <span style="color: red">**Lost**</span>
- <del>2021-08-16: 0.253331471239 XMR (+fees) for a Tor Exit Node (03) Hosting (12 months)</del>: <span style="color: red">**Lost**</span>
- 2021-08-18: AtomicSwap conversion from remaining mBTC (-0.56588) to XMR (+0.081904862179)
- <del>2021-08-19: 0.0644 XMR (+fees) for Mail Hosting extension</del>: <span style="color: red">**Lost**</span>
- <del>2021-09-18: 0.246971511836 XMR (+fees) for renewal 1 year of Tor Exit Node 01</del>: <span style="color: red">**Lost**</span>
- 2021-10-04: 0.26954 XMR (+fees) for domain anonymousplanet.net extension until 2029
- <del>2021-10-06: 0.236073464623 XMR (+fees) for a Tor Exit Node (04) Hosting (12 months)</del>: <span style="color: red">**Lost**</span>
- <del>2021-10-18: 0.01952 XMR (+fees) for testing a new VPS hosting provider (Privex.io) for one month</del>: <span style="color: red">**Ended**</span>
- <del>2021-10-30: 0.240787814495 XMR (+fees) for a Synapse Hosting VPS (12 months) with bots to help grow the community. This is a test program that will be converted into a Tor Exit Node in case of failure</del>: <span style="color: red">**Lost**</span>
- <del>2022-01-01: 0.28055816111 XMR (+fees) for renewal 1 year of Tor Exit Node 02</del>: <span style="color: red">**Lost**</span>
- <del>2022-02-02: 0.966793601024 XMR (+fees) to sponsor a special project (w/ Universal Declaration of Human Rights)</del>: <span style="color: red">**Lost**</span>
- <del>2022-07-11: 0.503232784687 XMR (+fees) for 1984.is VPS (12 months)</del>: <span style="color: red">**Ended**</span>
- <del>2022-09-19: 0.345024603905 XMR (+fees) for upgrading VPS RAM/Disk</del>: <span style="color: red">**Ended**</span>
[1]: ../media/monero.png
[2]: ../media/bitcoin-segwit.png
[3]: ../media/bitcoin-legacy.png
+671 -352
View File
File diff suppressed because it is too large Load Diff
+55 -22
View File
@@ -1,40 +1,73 @@
---
title: ""
description: We are the maintainers of the Hitchhiker's Guide and the PSA Matrix space.
title: "Home"
description: "The Hitchhiker's Guide to Online Anonymity"
schema:
"@context": https://schema.org
"@type": Organization
"@id": https://www.anonymousplanet.org/
"@type": WebPage
"@id": https://anonymousplanet.net/
name: Anonymous Planet
url: https://www.anonymousplanet.org/authors/
logo: ../media/profile.png
url: https://anonymousplanet.net/
logo: media/profile.png
sameAs:
- https://github.com/Anon-Planet
- https://opencollective.com/anonymousplanetorg
- https://mastodon.social/@anonymousplanet
---
# **Hello, and welcome to the Hitchhiker's Guide.**
<div style="text-align: center; font-size: 3.9rem; font-weight: 700; line-height: 1; margin: 2em 0;">
<span>Privacy.</span><br>
<span style="color: var(--crt-green);">Security.</span><br>
<span>Anonymity.</span>
</div>
**9FA5 436D 0EE3 6098 5157 3825 17EC A05F 768D EDF6**
<div style="text-align: center; font-size: 3.0rem; font-weight: 400; line-height: 0.9; margin: 2em 0;">
<span style="color: var(--crt-green);">No ads.</span><br>
<span style="color: var(--crt-amber);">No affiliate links.</span><br>
<span style="color: var(--crt-red);">No bullshit.</span>
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.
</div>
![Anonymous Planet logo](media/profile.png){ align=right }
<div style="text-align: center; background: rgba(255, 0, 0, 0.1); border: 1px solid rgba(255, 0, 0, 0.3);
border-left: 4px solid #ff4444; padding: 1em; max-width: 70ch; margin: 3em auto; color: var(--crt-red)">
<span style="color: #ff6b6b;">&#9888;</span> <strong style="color: var(--status-error);">NOTICE:</strong> Always
verify you are on <a href="https://anonymousplanet.net/" style="color:
var(--status-error);">anonymousplanet.net</a><strong style="color: #ff6b6b;"> &#9888;</strong>
</div>
Anonymous Planet is a collective of volunteers.
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5em; max-width:
1000px; margin: 0 auto;">
??? person "Das Kolburn"
<div class="quick-access-card">
<h3 style="font-family: var(--code-font); font-size: 1.1rem; margin: 0;">Online</h3>
<p style="margin: 0 0 1em;">Comprehensive coverage of tracking techniques, ID verification, and how to create
truly anonymous identities.</p>
<a href="guide/" style="font-family: var(--code-font); font-size: 0.85rem; color: var(--crt-amber);">Read online
</a>
</div>
- [:simple-github: GitHub](https://github.com/NobodySpecial256 "@NobodySpecial256")
- [:fontawesome-solid-envelope: E-mail](mailto:contact@anonymousplanet.org)
- [:simple-matrix: Personal Matrix](https://matrix.to/#/@daskolburn:thomcat.rocks "@daskolburn:thomcat.rocks"), [:simple-matrix: Org Matrix](https://matrix.to/#/@daskolburn:anonymousplanet.net "@daskolburn:anonymousplanet.net")
<div class="quick-access-card">
<h3 style="font-family: var(--code-font); font-size: 1.1rem; margin: 0;">PDF</h3>
<p style="margin: 0 0 1em;">Download the guide as a PDF (best for readability) or ODT. Verify integrity with the
provided public key before trusting any downloaded file.</p>
<a href="export/thgtoa.pdf" class="btn-download" style="min-width: auto !important; color: var(--crt-amber);">Get
the files </a>
</div>
??? person "Nope"
<div class="quick-access-card">
<h3 style="font-family: var(--code-font); font-size: 1.1rem; margin: 0;">Tor</h3>
<p style="margin: 0 0 1em;">Access the guide over the Tor network for maximum privacy. The .onion address ensures
you reach us without exposing your IP or destination.</p>
<a href="mirrors/" style="font-family: var(--code-font); font-size: 0.85rem; color: var(--crt-amber);">Open .onion
</a>
</div>
- [:simple-github: GitHub](https://github.com/nopeitsnothing "@nopeitsnothing")
- [:simple-mastodon: Mastodon](https://ioc.exchange/@unknown "@unknown@ioc.exchange"){rel=me}
- [:fontawesome-solid-house: Homepage](https://www.itsnothing.net)
- [:fontawesome-solid-envelope: E-mail](mailto:contact@anonymousplanet.org)
- [:simple-matrix: Personal Matrix](https://matrix.to/#/@thehidden:tchncs.de "@thehidden:tchncs.de"), [:simple-matrix: Org Matrix](https://matrix.to/#/@nope:anonymousplanet.net "@nope:anonymousplanet.net")
</div>
<div style="text-align: center; font-family: var(--code-font); font-size: 0.8rem; padding: 1.5em 0; border-top:
1px solid rgba(255,255,255,0.1); margin-top: 4em; color: var(--crt-green); opacity: 0.9;">
<strong>The Hitchhiker's Guide</strong> is
licensed under <a href="https://creativecommons.org/licenses/by-sa/4.0/" style="color: var(--crt-amber);">CC BY-SA
4.0</a>.
</div>
</div>
+8 -13
View File
@@ -4,32 +4,27 @@ description: Maintainers of the Hitchhiker's Guide and the PSA Community.
schema:
"@context": https://schema.org
"@type": Organization
"@id": https://www.anonymousplanet.org/
"@id": https://anonymousplanet.net/
name: Anonymous Planet
url: https://www.anonymousplanet.org/mirrors/
url: https://anonymousplanet.net/mirrors/
logo: ../media/profile.png
sameAs:
- https://github.com/Anon-Planet
- https://opencollective.com/anonymousplanetorg
- https://mastodon.social/@anonymousplanet
---
![metadata](../media/profile.png){ align=right }
---
???+ tip "Where to find the Hitchhiker's Guide"
!!! Note "Where to find the Hitchhiker's Guide"
- [Original](https://anonymousplanet.org)
- [Original](https://anonymousplanet.net)
- [Tor v3](http://thgtoa3jzy3doku7hkna32htpghjijefscwvh4dyjgfydbbjkeiohgid.onion) **Down**
- [Archive.org](https://web.archive.org/web/https://anonymousplanet.org)
- [Archive.today](https://archive.fo/anonymousplanet.org)
- [Archive.today over Tor](http://archiveiya74codqgiixo33q62qlrqtkgmcitqx5u2oeqnmn5bpcbiyd.onion/anonymousplanet.org)
- [Archive.org](https://web.archive.org/web/https://anonymousplanet.net)
- [Archive.today](https://archive.fo/anonymousplanet.net)
- [Archive.today over Tor](http://archiveiya74codqgiixo33q62qlrqtkgmcitqx5u2oeqnmn5bpcbiyd.onion/anonymousplanet.net)
!!! 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-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 `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).
The guide is also available as a **PDF** (images and layout preserved). It is built automatically. See the [Releases](https://github.com/Anon-Planet/thgtoa/releases). 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"
+89
View File
@@ -0,0 +1,89 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mDMEZc0QYxYJKwYBBAHaRw8BAQdAm8mOR8/0qWrm9Tqzfl9Ks5rjtIbQZLAR/qxH
HVGJsxi0LUFub255bW91cyBQbGFuZXQgRW1haWwgRW5jcnlwdGlvbi9TaWduaW5n
IEtleYiTBBMWCgA7FiEE/L0sq979H7ounnWRoags0t0s+JAFAmXNEGMCGwMFCwkI
BwICIgIGFQoJCAsCBBYCAwECHgcCF4AACgkQoags0t0s+JDbRAEAuZlBmMGgZ3bh
12Js9jjDcu+jhKqL4fJrJG5z9+KFkQwA/An1StA6EhcM7qlzZ5bzm2SZAbP9hQRZ
GmfaeU2P5KgHiHgEMBYKACAWIQSfpUNtDuNgmFFXOCUX7KBfdo3t9gUCaiCYTgId
AAAKCRAX7KBfdo3t9gNUAP9/SyGBYJ7s9YeqLHOJ+veQZjZYHvFGQ7yPn0Fetx0Z
LAD/UOQ8rP2QaldCMyVSG8SqfPd7n++SEAXWAl2gAo9mhg6IdQQQFgoAHRYhBJ+l
Q20O42CYUVc4JRfsoF92je32BQJmpEUQAAoJEBfsoF92je32tD8A/ir9hE8UjrJE
psG+PNfxYAwAagKUGbAMDUxQp3z+t81+AP45hYT4aR89zSQaankHLs3Lh7Cp5ael
NBe/BtfR9hCLAYkCMwQQAQgAHRYhBF7WeRgs0hkTAMDm6kyyELegkVLWBQJqIJiM
AAoJEEyyELegkVLWjZYP/j1k5vl+r0NDQXmE8hS9IKhaQPggP72iXc5RWeMQHuIv
b1laQZm64xerJNdAh0uk1bwfmJnVGfyxBUrlCgAIeVGRSlni2Rig4azaQ1IS0pqF
4sC1KzKEhEaNdkh3pJyGtP1cikcSjWeU2oYQou3/7VN3vNyW+n8OAVF+2fsC5d78
EvdpZgal+komb+J8Bt552uDbCCVI4TFIPBZmHWoXjaP6L+730YphbV7Aw0L5J6OO
ob0nzHn4X0dIvGE7Phdp2e1yNRUOSRLh8B/D5OiE9k7CaeYmJNPv5qOw/R+NgrrA
ZFnoOuwHo0D+aL9WT9q4aM/cDCEIbvhQ4l5ZhVGqZuQ9wxNCgPi3ZiZRTfk1PW4v
uMw1xGwXBKy7jDO12xWIWWv9MiwIQLw0OxSxKbr76rgucq7e7JrWr64rItu5Wm7F
8qxg2cwmDat6tFSRVWlEDy8oNkRMJNjdQJDu3ez9YOfJNnApAz94Of1XU7CUuYjY
PV88BaHdUBVtANEzy0iSDCcSj6auzLfv9dBN8cOdUxlVcrPf2jjK6JR/6qe6VWNp
wRg9VQW2fe8HJTMUt0o9qQBJUsF68KOHtIdoE4az9AyyBNKl67dKqLB9HoIItLzD
MJRcbS2p6plCTNagwPVvgtPRChll9JP3jLPVhRL2BixYVkbHUoJxsEfscTUl6Azt
tEtBbm9ueW1vdXMgUGxhbmV0IEVtYWlsIEVuY3J5cHRpb24vU2lnbmluZyBLZXkg
PGFub255bW91c3BsYW5ldEBkaXNyb290Lm9yZz6IkwQTFgoAOxYhBPy9LKve/R+6
Lp51kaGoLNLdLPiQBQJmhqXqAhsDBQsJCAcCAiICBhUKCQgLAgQWAgMBAh4HAheA
AAoJEKGoLNLdLPiQJ34A/RJT9Hyj7hT/0D1BbDU6s6YzD+/x7Pyq2+9kNSI0L77W
AQAAG+CfDrKDXJtBNKZVNFZpld3wUeoIOcAqLl7KpsVGCYh1BBAWCgAdFiEEn6VD
bQ7jYJhRVzglF+ygX3aN7fYFAmakRRAACgkQF+ygX3aN7fbypgEAnEg0IbWnpaLj
/4wU179vUZZu/Y0DE63GbJuZjj72hKUA/0xyzIgSvXByjoOkEwCn5w1+RPYXKw7Z
syERsDCUAAMIiQIzBBABCAAdFiEEXtZ5GCzSGRMAwObqTLIQt6CRUtYFAmogmIwA
CgkQTLIQt6CRUtafPQ/+LTWFU84tDZAM0Hp7bWB0dw8nP0JvNQ2WtZf0flh+r1tF
cmVnc9szZBh+zzSpY25iK5+Waa6+l1POYSQpkS67VR0Jrv9nL94YrRhqalSRWsjW
MQJO+Obu4LIRIqiMZLJlAd9Bg9FshYagbQDVDOI8v9mxqCzIVm3tBx1Jp57ATHgm
sMDWn7l1BI0SkLlG49LYxVDQ6QAx4XLCQw+JzdiJs+yExa5ymYmV61evVVbDV5UF
pEwW6nsuEDc68UN6npjr8OuGH5y+1ot1vaBderoXFZ8hRG/czzODX5L0zGDX9R2C
cGyIrv4AoXTtnbiVZGG6Vn1p3C/RMFZsVOMKvyQKh0rjcD9dqVQ4thI41o92jZ0V
K5ALjPiWe1kM4DVYgk/b46q9/8rjzYb4WJCwPQJkRBp36y26oRWM0JaY2Tobzt/H
3c8d36hQSXtjKLY27ZY5jL0N4vJaiclAuy03wKonmKlUc1ROUBEgNoZcvx6rLx6e
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
Eztpvc4cyyGAmI42SLM/jKbqO2yWqwVoAg==
=ww/S
-----END PGP PUBLIC KEY BLOCK-----
+133
View File
@@ -0,0 +1,133 @@
---
title: "PGP"
description: "Import our GPG keys to verify our releases and signed content."
schema:
"@context": https://schema.org
"@type": Organization
"@id": https://anonymousplanet.net/
name: Anonymous Planet PGP
url: https://anonymousplanet.net/pgp/
logo: ../media/profile.png
sameAs:
- https://github.com/Anon-Planet
- https://opencollective.com/anonymousplanetorg
- https://mastodon.social/@anonymousplanet
---
# PGP
<div style="font-family: var(--text-primary); color: var(--crt-green); font-size: 1.1rem; margin-bottom: 2em;">
<a href="anonymousplanet.asc" class="btn-download">
>>Download Our Public Keyring<<
</a>
</div>
Anonymous Planet uses GnuPG keys as the standard for encryption/signing. Import this keyring to verify the authenticity of our releases, signed content, and secure your emails to us.
## Anonymous Planet Keys
Our entire keyring is available at <https://anonymousplanet.net/pgp/anonymousplanet.asc> (click the above button to get it). It is also pasted below.
| Name | Key ID | Purpose |
|------|--------|---------|
| Master Signing Key (MSK) | `9FA5436D0EE360985157382517ECA05F768DEFDA` | Announcements, signing subkeys, etc. |
| Release Signing Key (RSK) | `C3023DBEA3FB38C438BA1EECEC60AEDE8B992A2` | Release signing, occasional commit signing |
| Email Encryption/Signing Key (ESK) | `FCBD2CABDEFD1FBA2E9E7591A1A82CD2DD2CF890` | Secure email |
**Fingerprint verification:** Always verify key fingerprints against our [GitHub announcements](https://github.com/Anon-Planet/thgtoa/releases) before importing.
## Key Rotation
We may rotate keys periodically. Check our [GitHub Releases](https://github.com/Anon-Planet/thgtoa/releases) and [changelog](../changelog/index.md) for announcements.
```txt
-----BEGIN PGP PUBLIC KEY BLOCK-----
mDMEZc0QYxYJKwYBBAHaRw8BAQdAm8mOR8/0qWrm9Tqzfl9Ks5rjtIbQZLAR/qxH
HVGJsxi0LUFub255bW91cyBQbGFuZXQgRW1haWwgRW5jcnlwdGlvbi9TaWduaW5n
IEtleYiTBBMWCgA7FiEE/L0sq979H7ounnWRoags0t0s+JAFAmXNEGMCGwMFCwkI
BwICIgIGFQoJCAsCBBYCAwECHgcCF4AACgkQoags0t0s+JDbRAEAuZlBmMGgZ3bh
12Js9jjDcu+jhKqL4fJrJG5z9+KFkQwA/An1StA6EhcM7qlzZ5bzm2SZAbP9hQRZ
GmfaeU2P5KgHiHgEMBYKACAWIQSfpUNtDuNgmFFXOCUX7KBfdo3t9gUCaiCYTgId
AAAKCRAX7KBfdo3t9gNUAP9/SyGBYJ7s9YeqLHOJ+veQZjZYHvFGQ7yPn0Fetx0Z
LAD/UOQ8rP2QaldCMyVSG8SqfPd7n++SEAXWAl2gAo9mhg6IdQQQFgoAHRYhBJ+l
Q20O42CYUVc4JRfsoF92je32BQJmpEUQAAoJEBfsoF92je32tD8A/ir9hE8UjrJE
psG+PNfxYAwAagKUGbAMDUxQp3z+t81+AP45hYT4aR89zSQaankHLs3Lh7Cp5ael
NBe/BtfR9hCLAYkCMwQQAQgAHRYhBF7WeRgs0hkTAMDm6kyyELegkVLWBQJqIJiM
AAoJEEyyELegkVLWjZYP/j1k5vl+r0NDQXmE8hS9IKhaQPggP72iXc5RWeMQHuIv
b1laQZm64xerJNdAh0uk1bwfmJnVGfyxBUrlCgAIeVGRSlni2Rig4azaQ1IS0pqF
4sC1KzKEhEaNdkh3pJyGtP1cikcSjWeU2oYQou3/7VN3vNyW+n8OAVF+2fsC5d78
EvdpZgal+komb+J8Bt552uDbCCVI4TFIPBZmHWoXjaP6L+730YphbV7Aw0L5J6OO
ob0nzHn4X0dIvGE7Phdp2e1yNRUOSRLh8B/D5OiE9k7CaeYmJNPv5qOw/R+NgrrA
ZFnoOuwHo0D+aL9WT9q4aM/cDCEIbvhQ4l5ZhVGqZuQ9wxNCgPi3ZiZRTfk1PW4v
uMw1xGwXBKy7jDO12xWIWWv9MiwIQLw0OxSxKbr76rgucq7e7JrWr64rItu5Wm7F
8qxg2cwmDat6tFSRVWlEDy8oNkRMJNjdQJDu3ez9YOfJNnApAz94Of1XU7CUuYjY
PV88BaHdUBVtANEzy0iSDCcSj6auzLfv9dBN8cOdUxlVcrPf2jjK6JR/6qe6VWNp
wRg9VQW2fe8HJTMUt0o9qQBJUsF68KOHtIdoE4az9AyyBNKl67dKqLB9HoIItLzD
MJRcbS2p6plCTNagwPVvgtPRChll9JP3jLPVhRL2BixYVkbHUoJxsEfscTUl6Azt
tEtBbm9ueW1vdXMgUGxhbmV0IEVtYWlsIEVuY3J5cHRpb24vU2lnbmluZyBLZXkg
PGFub255bW91c3BsYW5ldEBkaXNyb290Lm9yZz6IkwQTFgoAOxYhBPy9LKve/R+6
Lp51kaGoLNLdLPiQBQJmhqXqAhsDBQsJCAcCAiICBhUKCQgLAgQWAgMBAh4HAheA
AAoJEKGoLNLdLPiQJ34A/RJT9Hyj7hT/0D1BbDU6s6YzD+/x7Pyq2+9kNSI0L77W
AQAAG+CfDrKDXJtBNKZVNFZpld3wUeoIOcAqLl7KpsVGCYh1BBAWCgAdFiEEn6VD
bQ7jYJhRVzglF+ygX3aN7fYFAmakRRAACgkQF+ygX3aN7fbypgEAnEg0IbWnpaLj
/4wU179vUZZu/Y0DE63GbJuZjj72hKUA/0xyzIgSvXByjoOkEwCn5w1+RPYXKw7Z
syERsDCUAAMIiQIzBBABCAAdFiEEXtZ5GCzSGRMAwObqTLIQt6CRUtYFAmogmIwA
CgkQTLIQt6CRUtafPQ/+LTWFU84tDZAM0Hp7bWB0dw8nP0JvNQ2WtZf0flh+r1tF
cmVnc9szZBh+zzSpY25iK5+Waa6+l1POYSQpkS67VR0Jrv9nL94YrRhqalSRWsjW
MQJO+Obu4LIRIqiMZLJlAd9Bg9FshYagbQDVDOI8v9mxqCzIVm3tBx1Jp57ATHgm
sMDWn7l1BI0SkLlG49LYxVDQ6QAx4XLCQw+JzdiJs+yExa5ymYmV61evVVbDV5UF
pEwW6nsuEDc68UN6npjr8OuGH5y+1ot1vaBderoXFZ8hRG/czzODX5L0zGDX9R2C
cGyIrv4AoXTtnbiVZGG6Vn1p3C/RMFZsVOMKvyQKh0rjcD9dqVQ4thI41o92jZ0V
K5ALjPiWe1kM4DVYgk/b46q9/8rjzYb4WJCwPQJkRBp36y26oRWM0JaY2Tobzt/H
3c8d36hQSXtjKLY27ZY5jL0N4vJaiclAuy03wKonmKlUc1ROUBEgNoZcvx6rLx6e
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
Eztpvc4cyyGAmI42SLM/jKbqO2yWqwVoAg==
=ww/S
-----END PGP PUBLIC KEY BLOCK-----
```
+105
View File
@@ -0,0 +1,105 @@
.md-typeset a::after,
a:not(.btn)::after {
content: "";
position: absolute;
bottom: -3px;
left: 0;
width: 0;
height: 1px;
background: var(--bg-card);
transition: width 0.2s ease;
}
.md-typeset a:hover::after,
a:not(.btn):hover::after {
width: 100%;
}
.md-typeset code {
background-color: rgba(0, 26, 3, 0.226) !important;
padding: 0.2em 0.4em;
border-radius: 3px;
color: var(--text-primary);
}
.md-typeset .admonition code {
background-color: rgba(0, 26, 3, 0.226) !important;
}
.admonition {
border-left: 4px solid var(--crt-amber);
background: linear-gradient(180deg, transparent 0%, rgba(0, 255, 0, 0.03) 100%);
}
.admonition-note,
.admonition-todo,
.admonition-important {
background: linear-gradient(180deg, var(--bg-secondary) 0%, rgba(0, 255, 0, 0.05) 100%) !important;
}
.admonition-tip,
.admonition-hint,
.admonition-important,
.admonition-new {
background: linear-gradient(180deg, var(--bg-secondary) 0%, rgba(0, 255, 0, 0.05) 100%) !important;
}
.admonition-caution,
.admonition-warning,
.admonition-failure {
background: linear-gradient(180deg, var(--bg-secondary) 0%, rgba(255, 165, 0, 0.08) 100%) !important;
}
.admonition-error,
.admonition-failure {
background: linear-gradient(180deg, var(--bg-secondary) 0%, rgba(239, 68, 68, 0.08) 100%) !important;
}
.md-typeset__table th {
background-color: var(--bg-primary) !important;
border-bottom: 2px solid var(--accent-green);
}
.md-typeset h1,
.md-typeset h2,
.md-typeset h3 {
animation: fadeIn 0.4s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
[data-md-color-scheme="slate"] .md-search__overlay .md-search__scrollwrap .md-search__form {
background-color: var(--bg-card) !important;
}
[data-md-color-scheme="slate"] .md-search__overlay .md-search__scrollwrap .md-search__form input {
background-color: var(--bg-card) !important;
color: var(--text-primary) !important;
}
[data-md-color-scheme="slate"] .md-search__overlay .md-search__results {
background-color: var(--bg-card) !important;
}
.md-footer {
background: linear-gradient(180deg, var(--bg-secondary) 0%, var(--bg-primary) 100%);
border-top: 2px solid var(--accent-green);
}
/* High contrast mode overrides */
@media (prefers-contrast: high) {
.admonition-note,
.admonition-tip,
.admonition-important,
.md-typeset details.admonition {
background: var(--bg-color) !important;
}
nav[data-md-level="1"] .md-nav__link,
nav[data-md-level="2"] .md-nav__link {
color: var(--bg-color) !important;
}
}
-130
View File
@@ -1,130 +0,0 @@
/* 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;
}
}
-55
View File
@@ -1,55 +0,0 @@
/* Title sheet: visible only when printing / generating PDF (not on screen). */
.pdf-title-page {
display: none;
}
@media print {
.pdf-title-page {
display: block;
box-sizing: border-box;
text-align: center;
padding: 5rem 2rem 4rem;
page-break-after: always;
}
.pdf-title-page__title {
font-size: 1.65rem;
font-weight: 700;
line-height: 1.25;
margin: 0 0 1.25rem;
}
.pdf-title-page__subtitle {
font-size: 1.05rem;
line-height: 1.4;
margin: 0 0 2rem;
}
.pdf-title-page__meta {
font-size: 0.95rem;
font-style: normal;
margin: 0;
}
}
/* Guide landing: small floating logo so opening copy flows beside it (HTML + PDF). */
.guide-intro-lead {
overflow: auto;
}
.guide-intro-lead > p:first-child {
margin-top: 0;
}
.guide-intro-lead > p:first-child img {
float: right;
max-width: 6.5rem;
height: auto;
margin: 0 0 0.5rem 1rem;
}
@media print {
.guide-intro-lead > p:first-child img {
max-width: 5rem;
}
}
+153
View File
@@ -0,0 +1,153 @@
#md-content > :first-child:is(h1, h2, h3),
.md-typeset__content > :first-child:is(h1, h2, h3) {
display: none !important;
}
.social,
.social__link {
display: inline-flex !important;
align-items: center;
justify-content: flex-start;
white-space: nowrap;
vertical-align: middle;
margin-right: 0.75em !important;
position: relative;
}
.social svg,
.social__link svg {
width: 24px !important;
height: 24px !important;
}
#social,
.social-container,
.md-social-list {
display: flex !important;
flex-wrap: nowrap !important;
gap: 0.75em !important;
align-items: center;
justify-content: flex-start;
white-space: nowrap;
}
.md-social,
[data-md-color-scheme="slate"] .md-typeset a[href*="social"],
.md-social__link {
display: inline-flex !important;
flex-direction: row;
white-space: nowrap;
vertical-align: middle;
background-color: transparent !important;
}
.social span,
.social__link span {
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
#social > :is(a[href], .md-icon),
.social__link > :is(a[href], .md-icon) {
display: inline-flex !important;
flex-direction: row;
}
.md-social-list,
.social-container {
flex-wrap: nowrap !important;
}
body .social,
body .md-footer__social {
display: inline-flex !important;
margin: 0;
padding: 0;
border: 0;
}
.social::before,
.social::after {
display: none !important;
}
@media (max-width: 60em) {
.social {
margin-right: 0.5em !important;
}
.social svg,
.social__link svg {
width: 20px !important;
height: 20px !important;
}
}
@media (prefers-contrast: high) {
#social {
gap: 0.5em;
}
}
@media (prefers-reduced-motion: reduce) {
.md-footer h4,
.md-footer p {
animation: none !important;
}
}
.md-social {
display: inline-flex !important;
flex-wrap: nowrap !important;
gap: 0.75em !important;
align-items: center !important;
}
.md-social__link {
display: inline-flex !important;
flex-direction: row !important;
align-items: center !important;
white-space: nowrap !important;
margin: 0 !important;
}
.md-social__link span {
font-size: 0.9rem !important;
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
vertical-align: middle !important;
}
.md-social__link svg {
display: inline-flex !important;
width: 24px !important;
height: 24px !important;
flex-shrink: 0 !important;
}
.md-footer-meta__inner {
display: flex !important;
flex-wrap: nowrap !important;
align-items: center !important;
justify-content: space-between !important;
background-color: var(--bg-color) !important;
}
@media (max-width: 60em) {
.md-social {
gap: 0.5em !important;
}
.md-social__link svg {
width: 20px !important;
height: 20px !important;
}
.md-social__link span {
font-size: 0.8rem !important;
max-width: 100px;
}
}
+122
View File
@@ -0,0 +1,122 @@
:root {
--md-primary-fg-color:#4052b500;
--bg-color: #020307;
--text-primary: #c5c5c5;
--text-secondary: #a0a0a0;
--accent-color: #039634;
--border-color: #0b85005d;
--font-main: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
* {
box-sizing: border-box;
}
.no-js .md-sidebar {
align-self: auto;
}
body {
background-color: var(--bg-color);
color: var(--text-primary);
font-family: var(--font-main);
line-height: 1.6;
margin: 0;
padding: 0;
}
h1 {
color: var(--crt-green);
font-weight: 700;
margin-top: 1.5em;
margin-bottom: 0.5em;
}
h2, h3, h4, h5, h6 {
color: var(--crt-green);
font-weight: 600;
margin-top: 1.25em;
margin-bottom: 0.5em;
}
p {
margin-bottom: 1rem;
color: var(--text-primary);
}
a {
color: var(--crt-green);
}
.container {
max-width: 800px;
margin: 40px auto;
padding: 20px;
}
.card {
background-color: var(--bg-color);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
}
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
color: var(--text-secondary);
}
th, td {
padding: 12px;
text-align: left;
border-bottom: 1px solid var(--border-color);
}
th {
background-color: var(--bg-color);
font-weight: bold;
}
tr:last-child td {
border-bottom: none;
}
img {
max-width: 100%;
height: auto;
display: block;
margin: 20px 0;
filter: brightness(1.1);
animation: none !important;
background: none !important;
}
@media print {
@page {
size: A4;
margin: 20mm;
}
body {
background-color: var(--bg-color) !important;
color: var(--text-primary) !important;
}
.card, table th {
-webkit-print-color-adjust: exact !important;
print-color-adjust: exact !important;
background-color: var(--bg-card) !important;
color: var(--text-primary) !important;
}
.card {
break-inside: avoid;
}
nav, footer, button {
display: none !important;
}
}
+344
View File
@@ -0,0 +1,344 @@
.command-palette {
font-family: var(--code-font);
font-size: 0.75rem;
letter-spacing: 0.1em;
text-align: center;
color: var(--text-muted);
padding: 0.5em;
border-top: 1px dashed var(--terminal-line-color);
border-bottom: 1px dashed var(--terminal-line-color);
background: linear-gradient(
90deg,
transparent 0%,
rgba(0, 255, 0, 0.03) 50%,
transparent 100%
);
position: relative;
}
.command-palette::before,
.command-palette::after {
content: "|";
color: var(--accent-green);
animation: cursorBlink 1s step-end infinite;
}
@keyframes cursorBlink {
0%, 49% { opacity: 1; }
50%, 100% { opacity: 0; }
}
.headerlink {
color: var(--crt-green);
font-size: 0.8rem;
margin-left: 0.5em;
opacity: 0.7;
}
.terminal-session {
font-family: var(--code-font);
text-align: center;
padding: 0.25em 0;
color: var(--text-secondary);
font-size: 0.7rem;
}
.system-status {
position: fixed;
bottom: 60px;
left: 50%;
transform: translateX(-50%);
background: var(--bg-card);
border: 1px solid var(--border-color);
padding: 0.4em 1em;
border-radius: 4px;
font-family: var(--code-font);
font-size: 0.7rem;
color: var(--text-secondary);
z-index: 100;
box-shadow: 0 4px 12px rgba(0, 255, 0, 0.1);
}
.system-status .status-dot {
display: inline-block;
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--status-success);
margin-right: 0.5em;
box-shadow: 0 0 4px var(--status-success);
}
.terminal-corner {
position: fixed;
width: 10px;
height: 10px;
pointer-events: none;
z-index: 9999;
}
.top-left {
top: 20px;
left: 20px;
border-top: 2px solid var(--accent-green);
border-left: 2px solid var(--accent-green);
}
.top-right {
top: 20px;
right: 20px;
border-top: 2px solid var(--accent-green);
border-right: 2px solid var(--accent-green);
}
.bottom-left {
bottom: 60px;
left: 20px;
border-bottom: 2px solid var(--accent-green);
border-left: 2px solid var(--accent-green);
}
.hud-line {
position: fixed;
left: 5%;
top: 30%;
right: 5%;
height: 1px;
background: linear-gradient(
90deg,
transparent 0%,
rgba(0, 255, 0, 0.1) 50%,
transparent 100%
);
pointer-events: none;
}
.hud-coordinate {
position: fixed;
bottom: 60px;
left: 20px;
font-family: var(--code-font);
font-size: 0.65rem;
color: var(--bg-color);
z-index: 9999;
}
.hud-coordinate::before {
content: "";
position: absolute;
left: -20px;
top: 50%;
width: 20px;
height: 1px;
background: var(--terminal-line-color);
}
.data-stream {
position: fixed;
bottom: 60px;
right: 20px;
font-family: var(--code-font);
font-size: 0.6rem;
color: var(--status-success);
opacity: 0.5;
z-index: 9999;
white-space: pre;
}
.md-nav__link.nav-primary,
.md-nav__link.nav-secondary {
font-family: var(--code-font);
text-transform: uppercase;
letter-spacing: 0.05em;
font-size: 0.8rem;
}
.admonition-terminal {
border-left: 4px solid var(--accent-green);
background: linear-gradient(180deg,
transparent 0%,
rgba(0, 255, 0, 0.05) 100%
);
}
.admonition-terminal .admonition-title {
font-family: var(--code-font);
text-transform: uppercase;
letter-spacing: 0.1em;
background: var(--terminal-green-glow);
}
.file-tree {
font-family: var(--code-font);
font-size: 0.8rem;
padding-left: 1em;
margin: 1em 0;
}
.system-metadata {
font-family: var(--code-font);
font-size: 0.65rem;
text-align: right;
color: var(--text-muted);
padding-right: 2em;
}
pre.highlight {
box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.5);
}
pre.highlight .hll {
background-color: rgba(0, 255, 0, 0.05);
}
@keyframes matrixFall {
0% { transform: translateY(-100vh); opacity: 0; }
10% { opacity: 0.5; }
90% { opacity: 0.5; }
100% { transform: translateY(100vh); opacity: 0; }
}
.matrix-char {
position: fixed;
color: var(--accent-green);
font-family: var(--code-font);
font-size: 1rem;
user-select: none;
pointer-events: none;
z-index: 9998;
animation: matrixFall 15s linear infinite;
}
.palette-section {
margin-top: 4em;
}
.palette-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1em;
}
.palette-item {
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 1.5em;
text-align: center;
transition: all 0.2s ease;
cursor: pointer;
}
.palette-item:hover {
border-color: var(--accent-green);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 255, 0, 0.2);
}
.palette-item .icon-label {
font-size: 2rem;
margin-bottom: 0.5em;
}
.palette-item a {
color: var(--text-primary);
text-decoration: none;
display: block;
}
.network-viz {
position: fixed;
top: 20px;
right: 20px;
width: 150px;
height: 150px;
pointer-events: none;
opacity: 0.3;
}
.network-viz::before,
.network-viz::after {
content: "";
position: absolute;
border-radius: 50%;
border: 1px solid var(--accent-green);
}
.network-viz::before {
width: 100%;
height: 100%;
animation: networkSpin 20s linear infinite;
}
.network-viz::after {
width: 70%;
height: 70%;
animation: networkSpin 15s linear infinite reverse;
}
@keyframes networkSpin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.terminal-decorator {
position: fixed;
top: 10px;
right: 10px;
font-family: var(--code-font);
font-size: 0.6rem;
color: var(--text-muted);
z-index: 9999;
opacity: 0.5;
}
.system-notification {
position: fixed;
top: 20px;
right: 20px;
background: var(--bg-color);
border-left: 4px solid var(--accent-green);
padding: 1em 1.5em;
border-radius: 4px;
font-family: var(--code-font);
font-size: 0.8rem;
box-shadow: 0 8px 24px rgba(0, 255, 0, 0.15);
z-index: 10000;
min-width: 300px;
animation: slideInRight 0.3s ease-out;
}
@keyframes slideInRight {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@media (prefers-reduced-motion: reduce) {
body::before,
body::after,
.command-palette::before,
.command-palette::after,
.matrix-char,
.network-viz::before,
.network-viz::after {
animation: none;
}
}
@media screen and (min-width: 76.25em) {
.md-main__inner {
flex-direction: row-reverse;
}
}
@media screen {
[data-md-color-scheme="slate"] {
--md-default-bg-color: var(--bg-primary);
}
}
+433
View File
@@ -0,0 +1,433 @@
:root {
--crt-amber: #e7d9a3;
--crt-blue: #00d9ff;
--crt-green: #00f080;
--crt-red: #ff4c4c;
--phosphor-dim: #1a3a1a;
--status-success: #4ade80;
--status-warning: #fbbf24;
--status-error: #ef4444;
--status-info: #60a5fa;
--terminal-green-glow: rgba(0, 255, 0, 0.15);
--terminal-line-color: rgba(0, 255, 0, 0.3);
--terminal-border-faint: rgba(0, 255, 0, 0.08);
--md-default-bg-color: rgba(0, 255, 0, 0.08);
}
body::before {
content: "";
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
pointer-events: none;
z-index: 9999;
background: linear-gradient(
rgba(18, 32, 12, 0) 50%,
rgba(0, 20, 0, 0.1) 50%
),
linear-gradient(
90deg,
rgba(12, 36, 18, 0) 50%,
rgba(0, 16, 0, 0.08) 50%
);
background-size: 100% 4px, 6px 100%;
animation: scanline 4ms linear infinite;
}
@keyframes scanline {
0% { background-position: 0 0, 0 0; }
100% { background-position: 0 4px, 6px 100%; }
}
body::after {
content: "";
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
pointer-events: none;
z-index: 9998;
background: radial-gradient(
ellipse at center,
transparent 50%,
rgba(0, 0, 0, 0.3) 100%
);
}
@keyframes cursorBlink {
0%, 49% { opacity: 1; }
50%, 100% { opacity: 0; }
}
.md-header__topic::before,
body > *:first-child::before {
content: "λ ";
animation: cursorBlink 1s step-end infinite;
color: var(--crt-amber);
font-family: var(--code-font);
margin-right: 0.25em;
}
.terminal-window {
background: linear-gradient(180deg,
var(--bg-primary) 0%,
var(--bg-card) 100%
);
border: 2px solid var(--border-color);
border-radius: 8px;
padding: 1.5em;
position: relative;
box-shadow: 0 8px 32px rgba(0, 255, 0, 0.1);
}
.terminal-window::before {
content: "";
position: absolute;
top: -2px;
left: -2px;
right: -2px;
height: 3px;
background: linear-gradient(90deg,
transparent,
var(--accent-green),
transparent
);
opacity: 0.5;
}
strong {
text-shadow: 0 0 1px var(--border-color);
}
h1, h2 {
color: var(--crt-amber);
font-family: var(--code-font);
font-weight: bold;
margin-right: 0.02em;}
h3, h4, h5, h6 {
color: var(--crt-amber);
font-family: var(--code-font);
font-weight: bold;
}
p strong,
strong {
text-shadow: 0 0 1px var(--border-color);
}
pre {
background: linear-gradient(180deg,
var(--bg-color) 0%,
#0d150d 100%
);
border-left: 3px solid var(--accent-green);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
}
pre[role="presentation"] [data-line] {
color: var(--text-muted);
font-family: var(--code-font);
user-select: none;
min-width: 3em;
text-align: right;
padding-right: 1em;
}
pre code .token-keyword {
color: var(--crt-blue);
font-weight: bold;
}
pre code .token-string {
color: var(--status-success);
}
pre code .token-comment {
color: var(--text-muted);
font-style: italic;
}
.kbd,
kbd,
[data-type="kbd"] {
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-bottom: 2px solid var(--border-color);
border-radius: 4px;
padding: 0.2em 0.5em;
font-family: var(--code-font);
font-size: 0.85em;
color: var(--accent-green);
text-shadow: 0 1px 2px rgba(0, 255, 0, 0.1);
transition: all 0.1s ease;
}
kbd:hover {
border-color: var(--accent-green);
box-shadow: 0 2px 8px rgba(0, 255, 0, 0.2);
transform: translateY(-1px);
}
.status-indicator {
display: inline-flex;
align-items: center;
gap: 0.5em;
font-family: var(--code-font);
font-size: 0.85em;
text-transform: uppercase;
letter-spacing: 0.1em;
padding: 0.25em 0.5em;
border-radius: 4px;
}
.status-success {
color: var(--status-success);
background: rgba(74, 222, 128, 0.1);
border: 1px solid rgba(74, 222, 128, 0.3);
}
.status-warning {
color: var(--status-warning);
background: rgba(251, 191, 36, 0.1);
border: 1px solid rgba(251, 191, 36, 0.3);
}
.status-error {
color: var(--status-error);
background: rgba(239, 68, 68, 0.1);
border: 1px solid rgba(239, 68, 68, 0.3);
}
.status-info {
color: var(--status-info);
background: rgba(96, 165, 250, 0.1);
border: 1px solid rgba(96, 165, 250, 0.3);
}
.admonition {
border-left: 4px solid var(--accent-green);
background: linear-gradient(180deg,
transparent 0%,
rgba(0, 255, 0, 0.03) 100%
);
}
.admonition-title {
font-family: var(--code-font);
letter-spacing: 0.02em;
text-transform: uppercase;
font-size: 0.85em;
color: var(--accent-green);
background: var(--terminal-green-glow);
padding: 0.4em 0.8em;
border-radius: 4px 0 0 4px;
}
.md-typeset details.admonition {
border: 1px solid var(--border-color);
background: var(--bg-primary);
border-radius: 6px;
overflow: hidden;
}
.md-typeset table:not([class]) {
font-family: var(--code-font);
font-size: 0.95em;
}
.md-typeset__table th {
background: linear-gradient(180deg,
var(--bg-secondary) 0%,
var(--bg-card) 100%
);
border-bottom: 2px solid var(--accent-green);
font-weight: 700;
letter-spacing: 0.05em;
}
.md-typeset__table tr:hover {
background-color: var(--bg-primary);
}
blockquote, .admonition-blockquote {
border-left: 3px solid var(--accent-green);
margin: 1.5em 0;
padding-left: 1.5em;
background: linear-gradient(90deg,
rgba(0, 255, 0, 0.05) 0%,
transparent 100%
);
}
blockquote p:first-of-type::before {
content: " ";
color: var(--accent-green);
font-family: var(--code-font);
}
.md-typeset a::after,
a:not(.btn)::after {
content: "";
position: absolute;
bottom: -3px;
left: 0;
width: 0;
height: 1px;
background: var(--bg-card);
transition: width 0.2s ease;
}
.md-typeset a:hover::after,
a:not(.btn):hover::after {
width: 100%;
}
.btn {
font-family: var(--code-font);
text-transform: uppercase;
letter-spacing: 0.05em;
font-size: 0.85em;
transition: all 0.2s ease;
}
.md-typeset .admonition-title,
div.admonition > div:first-of-type::before {
font-family: var(--code-font);
text-transform: uppercase;
letter-spacing: 0.1em;
}
.md-typeset mark,
.highlight-mark {
background-color: rgba(255, 235, 0, 0.3);
color: inherit;
padding: 0.1em;
border-radius: 2px;
}
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: var(--bg-primary);
}
::-webkit-scrollbar-thumb {
background: var(--terminal-green-glow);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--accent-green-dim);
}
@media (max-width: 768px) {
body::before {
background-size: 100% 2px, 3px 100%;
animation-duration: 6s;
}
}
@media (prefers-contrast: high) {
:root {
--accent-green: #fff;
--bg-primary: #000;
}
body::before {
background-size: 100% 3px, 4px 100%;
}
}
@keyframes terminalLoad {
0% { opacity: 0; transform: translateY(25px); }
100% { opacity: 1; transform: translateY(0); }
}
.md-typeset h1,
.md-content {
animation: terminalLoad 200ms ease-out forwards;
}
body::after {
content: "";
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
pointer-events: none;
z-index: 9997;
background: radial-gradient(
circle at center,
rgba(0, 255, 0, 0.03) 0%,
transparent 60%
);
animation: phosphorDecay 8s ease-out infinite;
}
@keyframes phosphorDecay {
0%, 100% { opacity: 0.4; }
50% { opacity: 0.2; }
}
body::after::before {
content: "";
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
pointer-events: none;
z-index: 9998;
background: radial-gradient(
circle at center,
transparent 40%,
rgba(0, 0, 0, 0.6) 100%
);
}
.terminal-banner {
font-family: var(--code-font);
font-size: 0.75em;
letter-spacing: 0.2em;
text-align: center;
color: var(--text-muted);
padding: 1em 0;
border-top: 1px dashed var(--terminal-line-color);
border-bottom: 1px dashed var(--terminal-line-color);
margin: 3em 0;
}
.holographic-grid {
background-image:
linear-gradient(var(--terminal-border-faint) 1px, transparent 1px),
linear-gradient(90deg, var(--terminal-border-faint) 1px, transparent 1px);
background-size: 20px 20px;
position: relative;
}
.holographic-grid::before {
content: "";
position: absolute;
inset: 0;
background:
linear-gradient(
rgba(0, 255, 0, 0.03) 10px,
transparent 10px
),
linear-gradient(
90deg,
rgba(0, 255, 0, 0.03) 10px,
transparent 10px
);
background-size: 10px 10px;
}
+165
View File
@@ -0,0 +1,165 @@
.md-sidebar {
background-color: var(--bg-color) !important;
}
.md-sidebar__scrollwrap {
overflow-x: hidden;
}
.md-header__topic {
font-family: 'JetBrains Mono', monospace;
font-size: 0.75rem;
letter-spacing: -0.02em;
opacity: 0.9;
}
.md-typeset h1::before,
div[data-md-component="content"] > h1::before {
content: 'λ ';
color: var(--text-primary);
font-family: 'JetBrains Mono';
white-space: pre;
}
.md-typeset h2::before,
div[data-md-component="content"] > h2::before {
color: var(--text-primary);
font-family: 'JetBrains Mono';
white-space: pre;
}
.md-typeset h3::before,
div[data-md-component="content"] > h3::before {
color: var(--text-primary);
font-family: 'JetBrains Mono';
white-space: pre;
}
.md-header__button.md-icon {
padding: 0.5em;
animation: none !important;
}
nav[data-md-level="1"] .md-nav__link,
nav[data-md-level="2"] .md-nav__link {
font-family: var(--code-font);
font-size: 0.9rem;
text-transform: none;
letter-spacing: 0.01em;
color: var(--text-primary);
}
.md-sidebar.nav-primary > nav > .md-nav__list > li > .md-nav__link {
font-weight: 600;
padding-left: 0;
}
.md-sidebar.nav-primary > nav > .md-nav__list > li.is-parent > .md-nav__nested > li > a {
color: var(--text-secondary);
}
.md-sidebar.nav-primary > nav > .md-nav__list > li.is-parent > .md-nav__nested > li > a:hover,
.md-sidebar.nav-primary > nav > .md-nav__list > li.is-parent > .md-nav__nested > li > a.md-nav--opened {
color: var(--crt-red);
}
.md-sidebar__section.md-nav__inner > .md-nav__title {
font-family: var(--text-primary);
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--text-muted);
padding: 0.75em 1.5em 0.25em;
}
.md-sidebar__inner > .md-nav__title {
font-family: 'JetBrains Mono', monospace;
font-size: 0.8rem;
color: var(--crt-red);
padding: 1em 1.5em;
}
.md-sidebar__inner > .md-nav__title::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 4px;
}
.md-typeset {
max-width: none;
}
.quick-access,
div[data-md-include]:has(h3:contains("Quick Access")) {
border-top: 1px solid var(--border-color);
margin-top: 3em;
padding-top: 2em;
}
.md-typeset .md-typeset__grid,
div[data-md-type="grid"] {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1.5em;
}
.quick-access-card,
.md-typeset__section:not(:last-child) {
background: var(--bg-color);
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 1.25em;
}
.quick-access-card h3 {
font-family: var(--code-font);
margin-top: 0.5em;
margin-bottom: 0.5em;
color: var(--crt-green);
}
.quick-access-card > p {
color: var(--text-secondary);
font-size: 0.9rem;
margin-bottom: 1em;
}
.quick-access-card .md-icon,
.quick-access-card svg {
width: 2.5em;
height: 2.5em;
color: var(--crt-green);
margin-bottom: 0.75em;
}
.quick-access-card a:hover {
text-decoration-color: var(--crt-green);
}
.md-footer-meta {
background-color: var(--bg-primary) !important;
border-top: 1px solid var(--border-color);
}
pre[role="presentation"] [data-line] {
padding-left: calc(var(--per-page) * 0.75em + 1.25em);
}
.md-typeset a {
color:var(--crt-green);
word-break:break-word
}
.md-typeset h1,
.md-typeset h2,
.md-typeset h3 {
animation: fadeIn 0.4s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
-10
View File
@@ -1,10 +0,0 @@
---
title: Notice
---
# Anonymous Planet has left Twitter
Anonymous Planet has moved to Mastodon. This was largely due to Twitter being owned by Elon Musk, the $8 fees, and recent security issues that have come to light. We do not regret this decision, as it has only gotten worse for Twitter users since.
- [Twitter $8 Fee Exploited by Cybercriminals](https://heimdalsecurity.com/blog/twitter-8-fee-exploited-by-cybercriminals/)
Our mastodon can be found at [@anonymousplanet@mastodon.social](https://mastodon.social/@anonymousplanet)
+46 -31
View File
@@ -1,23 +1,35 @@
---
title: "Verify"
description: How to verify the authenticity of our files and check virus scans
title: "Verifying authenticity"
---
# PDF Verification Guide
<div style="font-family: var(--code-font); color: var(--crt-red); font-size: 0.9rem; margin-bottom: 2em;">
Never blindly trust the information you see online.
</div>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5em; max-width: 1000px; margin: 0 auto;">
<div class="quick-access-card">
<h4 style="font-family: var(--code-font); font-size: 1.1rem; margin: 0;">Get our keyring</h4>
<p style="margin: 0 0 1em;">The Anonymous Planet MSK and other keys in our keyring can be found here.</p>
<a href="../../pgp/" style="font-family: var(--code-font); font-size: 0.85rem; color: var(--crt-amber);">Access our public keyring <span style="font-size: 0.7em;"></span></a>
</div>
</div>
## Files Provided
For each PDF release, you'll receive:
For each 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
| File Type | Purpose | Verification Command |
|-----------|---------|---------------------|
| **PDF** (`thgtoa.pdf`) | The actual guide document | Check hash + signature |
| **.sig file** | GPG detached signature for authenticity | `gpg --verify file.sig file.pdf` |
| **.sha256** | SHA256 checksum for integrity | `sha256sum -c file.sha256` |
## Quick Verification
### Using Python Script (Recommended)
```bash
```sh
# Verify everything (hashes, signatures, and optionally VirusTotal)
python scripts/verify_pdf.py --all
@@ -36,12 +48,14 @@ python scripts/verify_pdf.py --vt
#### 1. Verify SHA256 Hash
**Linux/macOS:**
```bash
```sh
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
@@ -50,26 +64,31 @@ Get-FileHash -Algorithm SHA256 export\thgtoa.pdf | Select-Object Hash
#### 2. Verify GPG Signature
First, import the public key:
```bash
gpg --import pgp/anonymousplanet-master.asc
```sh
gpg --import pgp/anonymousplanet.asc
```
Then verify the signature:
```bash
```sh
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:
```
**Example output for successful verification:**
```text
gpg: Signature made Mon 20 Apr 2026 01:46:40 AM EDT
gpg: using EDDSA key 9FA5436D0EE360985157382517ECA05F768DEDF6
gpg: using EDDSA key 9FA5436D0EE360985157382517ECA05F768DEFDA
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.
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
```
**Note:** The "WARNING" is expected - it means the key hasn't been signed by another trusted key. This is normal for independent signing keys.
#### 3. Check VirusTotal Status
Visit the VirusTotal report links (automatically generated in release notes):
@@ -77,7 +96,8 @@ Visit the VirusTotal report links (automatically generated in release notes):
- Dark mode: `https://www.virustotal.com/gui/file/[hash]`
Or use the Python script with API key:
```bash
```sh
export VT_API_KEY=your_vt_api_key
python scripts/verify_pdf.py --vt
```
@@ -103,24 +123,19 @@ The GitHub Actions workflows automatically:
## Troubleshooting
### "Good signature" but wrong owner?
- Ensure you imported the correct public key
- Check the key fingerprint matches the official one from the repository
- Ensure you imported the correct public key from [`pgp/`](../pgp/index.md)
- Check the key fingerprint matches the official one from the repository announcements
### Hash mismatch?
- Re-download the file (corruption during transfer)
- Verify you're checking against the correct hash file
- Verify you're checking against the correct hash file for the mode (light/dark)
- 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.*
- **Linux/Debian:** `sudo apt install gnupg`
- **Linux/RHEL/CentOS:** `sudo yum install gnupg2` or `sudo dnf install gnupg2`
- **macOS:** `brew install gnupg` or use Homebrew Casks: `brew install --cask gnupg`
- **Windows:** Use [Gpg4win](https://www.gpg4win.org/)
+2
View File
@@ -0,0 +1,2 @@
39e7f8098d6c9511b98f83f4548ef8bac0d604fe820c4dbe1f731dbdff47676c0800872ba329492427cdfdf66734f55d03e3b4dd95b48e9e2ca2b3b4cd716213 thgtoa.pdf
ba29fcd4ee9bd43a7ed96752bc372f7d374d69f3d37e33e04d07fd14fe4e62afccbc05471e8ad89632d31045a56eee9bde7c15a0c405f64c977e5e4ac30654fa thgtoa-dark.pdf
+2
View File
@@ -0,0 +1,2 @@
ad7b3e327559dd835755615103bb1c59ef6f41ba652f6ee40c8fcdd082914f49 thgtoa.pdf
1174ec6f1e074b6b0115cea54ee135e82e56771d7129dcf367037a7020d5b39c thgtoa-dark.pdf
Binary file not shown.
+4 -5
View File
@@ -1,8 +1,7 @@
-----BEGIN PGP SIGNATURE-----
iJEEABYKADkWIQSfpUNtDuNgmFFXOCUX7KBfdo3t9gUCaeXaqxsUgAAAAAAEAA5t
YW51MiwyLjUrMS4xMiwyLDIACgkQF+ygX3aN7fY6QAD/YCGJqs9HiRllFrF9EluE
Ga4XUEQ/R6Q2zc+X6lX856sBAJIpxeMxUmMUXyr3xBAHxUf5eV+nQYkQQMKI81L1
x8gL
=VX6l
iHUEABYKAB0WIQTDAj2+o/s4xDi6Hu3Oxgrt6LmSogUCahwNewAKCRDOxgrt6LmS
opw7AQDdsg3JaS2vy2ZYCI4L1F+guKHF/zItJUSTj76DdOVzSAD+PKDCa4Io6OO9
7v2odiJHOrbYNmte5FhhffUZL8Nz1A4=
=oBfF
-----END PGP SIGNATURE-----
+1 -1
View File
@@ -1 +1 @@
f212d0425b38d5cd10da6dc804b60f143da23d4b07051aae31d0966082519b300af0e1c423683e0223738b33b138c687232b1c8bd68cf643777bbc5b588152bd ./export/thgtoa-dark.pdf
ba29fcd4ee9bd43a7ed96752bc372f7d374d69f3d37e33e04d07fd14fe4e62afccbc05471e8ad89632d31045a56eee9bde7c15a0c405f64c977e5e4ac30654fa
+5 -5
View File
@@ -1,8 +1,8 @@
-----BEGIN PGP SIGNATURE-----
iJEEABYKADkWIQSfpUNtDuNgmFFXOCUX7KBfdo3t9gUCaeXaqxsUgAAAAAAEAA5t
YW51MiwyLjUrMS4xMiwyLDIACgkQF+ygX3aN7fbdDgEAoSslLR47ydW/3r1wJOPY
X/waLkVbkGZpHqwd4RjywwcA/3B7Ci+jUg+yP5TRsuChagEhwyO5vw2DxSlUGoB4
+ksH
=2ja9
iJEEABYKADkWIQTDAj2+o/s4xDi6Hu3Oxgrt6LmSogUCahwDIRsUgAAAAAAEAA5t
YW51MiwyLjUrMS4xMiwyLDIACgkQzsYK7ei5kqJkrQD/etBsZk8BI71Dn0mgTDIQ
HaYuAqtld5MmKaV9AxlniWABANt6V/0ivcXSsxajFdvpdu4TI9D4GR07ZeKFjYXV
EZsM
=/p57
-----END PGP SIGNATURE-----
+5 -5
View File
@@ -1,8 +1,8 @@
-----BEGIN PGP SIGNATURE-----
iJEEABYKADkWIQSfpUNtDuNgmFFXOCUX7KBfdo3t9gUCaeXaqxsUgAAAAAAEAA5t
YW51MiwyLjUrMS4xMiwyLDIACgkQF+ygX3aN7faErgD/Svj1G+B7gmrZQ6AsLZ5J
HfeldxjmrXE99dig1iHtl5IBAMndZZb+95TO03IZ9eLGfYuyTz4GCUanmftsY9yv
LAIN
=MEd0
iJEEABYKADkWIQTDAj2+o/s4xDi6Hu3Oxgrt6LmSogUCahwDHxsUgAAAAAAEAA5t
YW51MiwyLjUrMS4xMiwyLDIACgkQzsYK7ei5kqIsEgD+PNgOOJy7GPQUYuaDlxeh
ldQWf58ivLfQ6zpgeSSTiqIA/19EDw+Un9AYuxikZGp39vcNFxEhnwD7dRWZo/Ie
ZyAE
=OrTx
-----END PGP SIGNATURE-----
BIN
View File
Binary file not shown.
+4 -5
View File
@@ -1,8 +1,7 @@
-----BEGIN PGP SIGNATURE-----
iJEEABYKADkWIQSfpUNtDuNgmFFXOCUX7KBfdo3t9gUCaeXaqxsUgAAAAAAEAA5t
YW51MiwyLjUrMS4xMiwyLDIACgkQF+ygX3aN7favvgEAvFFSB5NrsrKMYvGG5ZYB
iLIyt8Sn1rZmlVkibssMPq0BAImpZe8S7hWNkbukyEC4sLbKiOYvjbVipQHnrIUV
xPMH
=0hnj
iHUEABYKAB0WIQTDAj2+o/s4xDi6Hu3Oxgrt6LmSogUCahwNegAKCRDOxgrt6LmS
ov0tAQCNiaIONY2A6zRVXUcOolOOCJY1pi9SvuJ/yalbTQewawEAsi7bhFYAo6c0
yAy/jBcGD5E5HzLlmjkGvYcwsvWPfQo=
=lnwq
-----END PGP SIGNATURE-----
+1 -1
View File
@@ -1 +1 @@
436ed0df78c299f95b8d5ff94f43f26ec2e7825d92d843fc15419630d55ed5e0c98485e738c12715a2b6242633faae38e8a98935b361d44ddde97a1692cb01a1 ./export/thgtoa.pdf
39e7f8098d6c9511b98f83f4548ef8bac0d604fe820c4dbe1f731dbdff47676c0800872ba329492427cdfdf66734f55d03e3b4dd95b48e9e2ca2b3b4cd716213
+5 -5
View File
@@ -1,8 +1,8 @@
-----BEGIN PGP SIGNATURE-----
iJEEABYKADkWIQSfpUNtDuNgmFFXOCUX7KBfdo3t9gUCaeXaqxsUgAAAAAAEAA5t
YW51MiwyLjUrMS4xMiwyLDIACgkQF+ygX3aN7fatsgEAixDzH+zTnKYMEx3sikWp
dsNTiHTU6wJY/brVJIU879UBAJntBIq72vqwKtMb/ZlVvomdDvKVllZw8ZsYBz1n
aTkM
=vkgy
iJEEABYKADkWIQTDAj2+o/s4xDi6Hu3Oxgrt6LmSogUCahwDIBsUgAAAAAAEAA5t
YW51MiwyLjUrMS4xMiwyLDIACgkQzsYK7ei5kqJ6/QEAk2Ta0gygpWKSKstLjKwX
wmqIyrEza93Xk22owhYi3FAA/jQslZb0MahgPZyf3PQ8syUlBJS8gKQ8nBEpf5BO
Q/EK
=Fvmv
-----END PGP SIGNATURE-----
+5 -5
View File
@@ -1,8 +1,8 @@
-----BEGIN PGP SIGNATURE-----
iJEEABYKADkWIQSfpUNtDuNgmFFXOCUX7KBfdo3t9gUCaeXaqxsUgAAAAAAEAA5t
YW51MiwyLjUrMS4xMiwyLDIACgkQF+ygX3aN7faAGQEAyEhVKrRoXIsV3E5f1FZg
8fcsmbxCnKBqxichCkf0dWYBAIvbI146mQLHaNqLDaTIqCUQbkq1aE/YMFDGykUG
ngsJ
=/0RY
iJEEABYKADkWIQTDAj2+o/s4xDi6Hu3Oxgrt6LmSogUCahwDHxsUgAAAAAAEAA5t
YW51MiwyLjUrMS4xMiwyLDIACgkQzsYK7ei5kqIN4gEA2T011PhyNNqhGcj0uVTD
47AZKLxWhZXnLzD0sRUHY/oBAMWFfSXrKN5q8yml5dWLbvFqbcIpefgHD8smBd6v
fzUH
=3Cxi
-----END PGP SIGNATURE-----
+34 -41
View File
@@ -1,9 +1,9 @@
site_name: Hitchhiker's Guide
site_name: The Hitchhiker's Guide
site_author: Anonymous Planet
site_description: "The comprehensive guide for online anonymity and OpSec."
site_dir: '/site/'
docs_dir: 'docs/'
site_url: "https://www.anonymousplanet.org/"
site_description: "The comprehensive guide for online #anonymity and #opsec."
site_dir: "site"
docs_dir: "docs"
site_url: "https://anonymousplanet.net/"
repo_url: "https://github.com/Anon-Planet/thgtoa"
repo_name: ""
#edit_uri: ""
@@ -22,6 +22,7 @@ theme:
- navigation.instant.prefetch
- navigation.tabs
- navigation.sections
- navigation.path
- toc.integrate
- navigation.top
- search.suggest
@@ -31,32 +32,30 @@ theme:
- content.code.copy
language: en
palette:
- scheme: default
toggle:
icon: material/eye-outline
name: Switch to dark mode
primary: green
accent: blue
- scheme: slate
toggle:
icon: material/eye
name: Switch to light mode
primary: grey
accent: teal
primary: hacker
accent: green
plugins:
- social: {}
# - with-pdf:
- search:
separator: '[\s\u200b\-_,:!=\[\]()"`/]+|\.(?!\d)|&[lg]t;|(?!\b)(?=[A-Z][a-z])'
# - macros: {}
# - meta: {}
# - git-latest-tag: {}
# - git-authors: {}
# - git-latest-release: {}
# - meta: {}
# Soon this will all be minified (It's a personal WIP)
extra_css:
- stylesheets/extra.css
- stylesheets/hacker.css
- stylesheets/hacker-extra.css
- stylesheets/going-dark.css
- stylesheets/navigation.css
- stylesheets/footer.css
- stylesheets/accessibility.css
# extra_css:
# - stylesheets/hacker-core.css
# - stylesheets/components.css
# - stylesheets/accessibility.css
# - stylesheets/print-and-utilities.css
extra:
social:
@@ -67,7 +66,7 @@ extra:
link: http://wmj5kiic7b6kjplpbvwadnht2nh2qnkbnqtcv3dyvpqtz7ssbssftxid.onion/
name: "0xacab"
- icon: simple/gitea
link: http://it7otdanqu7ktntxzm427cba6i53w6wlanlh23v5i3siqmos47pzhvyd.onion/anonymousplanetorg
link: http://it7otdanqu7ktntxzm427cba6i53w6wlanlh23v5i3siqmos47pzhvyd.onion/anonypla
name: Darktea
- icon: simple/github
link: https://github.com/anon-planet
@@ -121,23 +120,17 @@ markdown_extensions:
- tables: {}
- footnotes: {}
- toc:
permalink: true
permalink: "λ"
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: |
<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>
- "Home": index.md
- "Verify": verify/index.md
- "Guide": guide/index.md
- "Code": code/index.md
- "Donations": contribute/index.md
- "A Constitution": constitution/index.md
- "Mirrors": mirrors/index.md
- "Changelog": changelog/index.md
- "About": about/index.md
- "PGP": pgp/index.md
-69
View File
@@ -1,69 +0,0 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBGNnDKYBEADEwpJcPVDJLJHlaRtBtVVJ2p2SaNwbZKgeI2zfpiLu4rTmpxUp
cbyW5S3mI++kGt4ljcKTzQM0+upr2hcdZi/rpwliHLOxsC32cvTy4YtPmoKdOalo
blJ9+llDbl0lBBvnqQcqFhnDMPXQPsaewWmCpGjwCwnQpxXLWmKhTYMxoQtzzZ8U
oagorLwASkb6+NZoha96ayDlE41KNErI51U8qiVxMR+8iN8pcJ1l3XA9bfMKBz45
TnlaoJ391CvJUgJ9535FjifmOyWTB0OYgJptMPz+n0K5jTOE7mvoqT6a/hqbAGDp
5i5LgSYVPfJqZsdrkQBMwO5pW9XymH7hNHPhaX6nPkDB8RLKexqso9pzLapG8WNC
sk+jxTC77TOFh9CniGks7UZoa0pRdhA5sGD0Wjh8eWgDRqdgYEmqviuulWnJDti0
dIQNixzh+TylEO8YNJyz49KUIr/ckapHfPI1BZWUyZZLpcvNvT/2IzcEeT3Tgmfr
IZsk2U91kA9z+BKEx8mJ7V5KZo7ku0uVgAtQn5oyluSIptUGwYu5DqhnZAqKXZok
S7i2NMghrPMM/Wf048VXuxO1Dx7CwP7Q1LCNhwL0jsLWtXIJVm7NtTt+1Vj/M4EH
Fl4g0B7iK6JiZEPYEp5YGSWpyhpSTKQaOOCHHKSCIjVx6VLm+/Xbaf6/TwARAQAB
tEtBbm9ueW1vdXMgUGxhbmV0IEVtYWlsIEVuY3J5cHRpb24vU2lnbmluZyBLZXkg
PGNvbnRhY3RAYW5vbnltb3VzcGxhbmV0Lm9yZz6JAk4EEwEKADgWIQS20XV2MqKA
+Z8ty/25q52Tr/BbnAUCY2cMpgIbAwULCQgHAwUVCgkICwUWAgMBAAIeAQIXgAAK
CRC5q52Tr/BbnOA7EACeevkcNYbacvNJx+E8cAHyVRS7kuSWDfV0EvCeiCsZ3+sq
q9CFADbBn4wXuELdFcPSME5UGOMpZ3MVwWocHyRrU+axseO/OCgbl15nxgk3lsSK
Tew/1YHnjTIfpDkSOw5kT86yxea9/bpIWVzb1aCkKxVogr1cXzvBdYRWV5qC3BP4
EITVs+5fX4kfW80ZoX6juopI7ymqRzEL9iml1ydWIr+cAwYYzhGvyBjrzm7psV2/
C+X9dXsLexQlb9Ef1WJA6R+z92f/HFUhjrEPTKpypWZIZhwkXMUDeykn5A9Szaqw
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
J0nsuzwoKTRfhIDlYN7e
=Mkw3
-----END PGP PUBLIC KEY BLOCK-----
-14
View File
@@ -1,14 +0,0 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mDMEY2cLxRYJKwYBBAHaRw8BAQdA1wWVN04/7B2thXG3Ppm9nj9BXOosgFUCq+6m
7q7jDUG0QUFub255bW91cyBQbGFuZXQgTWFzdGVyIFNpZ25pbmcgS2V5IChodHRw
czovL2Fub255bW91c3BsYW5ldC5vcmcpiJAEExYKADgWIQSeqYJ4Y58c2FPglsv/
lFB1h6apuQUCY2cLxQIbAwULCQgHAwUVCgkICwUWAgMBAAIeAQIXgAAKCRD/lFB1
h6apuVvhAP0UTSY/QchH8LfHaw1inGaViik9rALbjdBeVRWofwyRSQD8DH2LRX3v
f/DgBOK7Li6OL05s9wsEYwoF+8B1qWJinQu4OARjZwvFEgorBgEEAZdVAQUBAQdA
xO3KbSonM28D2uTNHpXFRneFL3LqUO+8JW14eULOdxoDAQgHiHgEGBYKACAWIQSe
qYJ4Y58c2FPglsv/lFB1h6apuQUCY2cLxQIbDAAKCRD/lFB1h6apuZ32AQCiiR0d
bD29xEmQYf4b9F77jAdFFr2DoEGjeZBPoTrJywEA8m1dD5ZOS0qn1Yz3WkTgBflL
/0VkU6m06r/KxLL4fg0=
=4NMF
-----END PGP PUBLIC KEY BLOCK-----
-16
View File
@@ -1,16 +0,0 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mDMEY2cNGBYJKwYBBAHaRw8BAQdAbKn/ExAQ+aq6/o2yc04B9jx5PMloaxux1eoT
iKwQgX60JEFub255bW91cyBQbGFuZXQgUmVsZWFzZSBTaWduaW5nIEtleYiQBBMW
CgA4FiEEg6bPnvV6wltcf10pKF5gSKEjIbIFAmNnDRgCGwMFCwkIBwMFFQoJCAsF
FgIDAQACHgECF4AACgkQKF5gSKEjIbI5+QD/YSQ5E+LW4YJEAQQ+D3LFsGtGGRf3
qQRD5plsUvTtBfsA/15EJaIjzSwrsf/3wsW48zSYKCer/nrhGY9y5yd0m2gBiHUE
EBYKAB0WIQSeqYJ4Y58c2FPglsv/lFB1h6apuQUCY2cNxAAKCRD/lFB1h6apuXun
AQCSNwZBNybUZzN/K4Zl1j6uhCqqnvbUlO80wvbHDMXpywD/dpabqjmpfxfJC20n
t3OFxKSeIbfJ0VHvoHKpwcaGuwC4OARjZw0YEgorBgEEAZdVAQUBAQdAE7WMDHTx
zWp542lXGLxSsiE4gtMvVxkEneKmZWwzbDcDAQgHiHgEGBYKACAWIQSDps+e9XrC
W1x/XSkoXmBIoSMhsgUCY2cNGAIbDAAKCRAoXmBIoSMhsowLAP42HbiJIsIodWwn
C3yBzwGrd1xRtf/91MpQUgFpCx7xuAD9G0F3l04hKkjxiHK+wJ27LnYcigaTVdje
6d7bt7TerwE=
=Hgos
-----END PGP PUBLIC KEY BLOCK-----
+89
View File
@@ -0,0 +1,89 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mDMEZc0QYxYJKwYBBAHaRw8BAQdAm8mOR8/0qWrm9Tqzfl9Ks5rjtIbQZLAR/qxH
HVGJsxi0LUFub255bW91cyBQbGFuZXQgRW1haWwgRW5jcnlwdGlvbi9TaWduaW5n
IEtleYiTBBMWCgA7FiEE/L0sq979H7ounnWRoags0t0s+JAFAmXNEGMCGwMFCwkI
BwICIgIGFQoJCAsCBBYCAwECHgcCF4AACgkQoags0t0s+JDbRAEAuZlBmMGgZ3bh
12Js9jjDcu+jhKqL4fJrJG5z9+KFkQwA/An1StA6EhcM7qlzZ5bzm2SZAbP9hQRZ
GmfaeU2P5KgHiHgEMBYKACAWIQSfpUNtDuNgmFFXOCUX7KBfdo3t9gUCaiCYTgId
AAAKCRAX7KBfdo3t9gNUAP9/SyGBYJ7s9YeqLHOJ+veQZjZYHvFGQ7yPn0Fetx0Z
LAD/UOQ8rP2QaldCMyVSG8SqfPd7n++SEAXWAl2gAo9mhg6IdQQQFgoAHRYhBJ+l
Q20O42CYUVc4JRfsoF92je32BQJmpEUQAAoJEBfsoF92je32tD8A/ir9hE8UjrJE
psG+PNfxYAwAagKUGbAMDUxQp3z+t81+AP45hYT4aR89zSQaankHLs3Lh7Cp5ael
NBe/BtfR9hCLAYkCMwQQAQgAHRYhBF7WeRgs0hkTAMDm6kyyELegkVLWBQJqIJiM
AAoJEEyyELegkVLWjZYP/j1k5vl+r0NDQXmE8hS9IKhaQPggP72iXc5RWeMQHuIv
b1laQZm64xerJNdAh0uk1bwfmJnVGfyxBUrlCgAIeVGRSlni2Rig4azaQ1IS0pqF
4sC1KzKEhEaNdkh3pJyGtP1cikcSjWeU2oYQou3/7VN3vNyW+n8OAVF+2fsC5d78
EvdpZgal+komb+J8Bt552uDbCCVI4TFIPBZmHWoXjaP6L+730YphbV7Aw0L5J6OO
ob0nzHn4X0dIvGE7Phdp2e1yNRUOSRLh8B/D5OiE9k7CaeYmJNPv5qOw/R+NgrrA
ZFnoOuwHo0D+aL9WT9q4aM/cDCEIbvhQ4l5ZhVGqZuQ9wxNCgPi3ZiZRTfk1PW4v
uMw1xGwXBKy7jDO12xWIWWv9MiwIQLw0OxSxKbr76rgucq7e7JrWr64rItu5Wm7F
8qxg2cwmDat6tFSRVWlEDy8oNkRMJNjdQJDu3ez9YOfJNnApAz94Of1XU7CUuYjY
PV88BaHdUBVtANEzy0iSDCcSj6auzLfv9dBN8cOdUxlVcrPf2jjK6JR/6qe6VWNp
wRg9VQW2fe8HJTMUt0o9qQBJUsF68KOHtIdoE4az9AyyBNKl67dKqLB9HoIItLzD
MJRcbS2p6plCTNagwPVvgtPRChll9JP3jLPVhRL2BixYVkbHUoJxsEfscTUl6Azt
tEtBbm9ueW1vdXMgUGxhbmV0IEVtYWlsIEVuY3J5cHRpb24vU2lnbmluZyBLZXkg
PGFub255bW91c3BsYW5ldEBkaXNyb290Lm9yZz6IkwQTFgoAOxYhBPy9LKve/R+6
Lp51kaGoLNLdLPiQBQJmhqXqAhsDBQsJCAcCAiICBhUKCQgLAgQWAgMBAh4HAheA
AAoJEKGoLNLdLPiQJ34A/RJT9Hyj7hT/0D1BbDU6s6YzD+/x7Pyq2+9kNSI0L77W
AQAAG+CfDrKDXJtBNKZVNFZpld3wUeoIOcAqLl7KpsVGCYh1BBAWCgAdFiEEn6VD
bQ7jYJhRVzglF+ygX3aN7fYFAmakRRAACgkQF+ygX3aN7fbypgEAnEg0IbWnpaLj
/4wU179vUZZu/Y0DE63GbJuZjj72hKUA/0xyzIgSvXByjoOkEwCn5w1+RPYXKw7Z
syERsDCUAAMIiQIzBBABCAAdFiEEXtZ5GCzSGRMAwObqTLIQt6CRUtYFAmogmIwA
CgkQTLIQt6CRUtafPQ/+LTWFU84tDZAM0Hp7bWB0dw8nP0JvNQ2WtZf0flh+r1tF
cmVnc9szZBh+zzSpY25iK5+Waa6+l1POYSQpkS67VR0Jrv9nL94YrRhqalSRWsjW
MQJO+Obu4LIRIqiMZLJlAd9Bg9FshYagbQDVDOI8v9mxqCzIVm3tBx1Jp57ATHgm
sMDWn7l1BI0SkLlG49LYxVDQ6QAx4XLCQw+JzdiJs+yExa5ymYmV61evVVbDV5UF
pEwW6nsuEDc68UN6npjr8OuGH5y+1ot1vaBderoXFZ8hRG/czzODX5L0zGDX9R2C
cGyIrv4AoXTtnbiVZGG6Vn1p3C/RMFZsVOMKvyQKh0rjcD9dqVQ4thI41o92jZ0V
K5ALjPiWe1kM4DVYgk/b46q9/8rjzYb4WJCwPQJkRBp36y26oRWM0JaY2Tobzt/H
3c8d36hQSXtjKLY27ZY5jL0N4vJaiclAuy03wKonmKlUc1ROUBEgNoZcvx6rLx6e
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
Eztpvc4cyyGAmI42SLM/jKbqO2yWqwVoAg==
=ww/S
-----END PGP PUBLIC KEY BLOCK-----
+23 -23
View File
@@ -1,23 +1,23 @@
# Import
```bash
$ gpg --import pgp/core-devs/*
```
# Verify
```bash
$ gpg --verify pgp/core-devs/than/than-crypto.txt
gpg: Signature made Sat 19 Jul 2025 02:04:10 AM EDT
gpg: using EDDSA key 8B3A74890536BAD50D9376EBF1CB32F67E3302A1
gpg: Good signature from "nopenothinghere@proton.me <nopenothinghere@proton.me>" [ultimate]
gpg: aka "Nope Nothing (Anonymous Planet Contact) <no@anonymousplanet.org>" [ultimate]
gpg: aka "Nope Nothing (Systems Administrator) <admin@itsnothing.net>" [ultimate]
Primary key fingerprint: 8B3A 7489 0536 BAD5 0D93 76EB F1CB 32F6 7E33 02A1
```
## All signing keys are signed by the Master Signing Key
TODO
# Import
```sh
$ gpg --import pgp/core-devs/*
```
# Verify
```sh
$ gpg --verify pgp/core-devs/than/than-crypto.txt
gpg: Signature made Sat 19 Jul 2025 02:04:10 AM EDT
gpg: using EDDSA key 8B3A74890536BAD50D9376EBF1CB32F67E3302A1
gpg: Good signature from "nopenothinghere@proton.me <nopenothinghere@proton.me>" [ultimate]
gpg: aka "Nope Nothing (Anonymous Planet Contact) <no@anonymousplanet.net>" [ultimate]
gpg: aka "Nope Nothing (Systems Administrator) <admin@itsnothing.net>" [ultimate]
Primary key fingerprint: 8B3A 7489 0536 BAD5 0D93 76EB F1CB 32F6 7E33 02A1
```
## All signing keys are signed by the Master Signing Key
TODO
+7
View File
@@ -0,0 +1,7 @@
# scripts/archived
Scripts kept for reference but no longer part of the active pipeline.
| Script | Why archived |
|--------|-------------|
| `tag_release.py` | Created GPG-signed `vX.Y.Z` annotated tags. Superseded by the `release-YYYYMMDD-<sha>` timestamp tagging built into `03-release.yml`. Re-enable if semver release tagging is reintroduced. |
@@ -38,7 +38,7 @@ from pathlib import Path
# Default release signing key fingerprint.
# Maintainers with a different key can pass --key on the CLI.
DEFAULT_SIGNING_KEY = "9FA5436D0EE360985157382517ECA05F768DEDF6"
DEFAULT_SIGNING_KEY = "C3023DBEA3FB38C438BA1EEDCEC60AEDE8B992A2"
CHANGELOG = Path(__file__).resolve().parent.parent / "docs" / "changelog" / "index.md"
@@ -396,7 +396,7 @@ def main() -> int:
print("\n" + "=" * 70)
print(" ✓ All done. Push the tag with:")
print(f"\n git push origin {version}\n")
print(" The release.yml workflow can then be triggered manually from")
print(" The 03-release.yml workflow can then be triggered manually from")
print(" GitHub Actions to publish the GitHub Release for this tag.")
print("=" * 70 + "\n")
+37 -10
View File
@@ -93,22 +93,28 @@ def apply_dark_theme(
def _save_images_as_pdf(images: list, output_path: str) -> None:
"""Save a list of RGB PIL images as a PDF using PNG compression via qpdf.
"""Save a list of RGB PIL images as a PDF without requiring libjpeg.
Pillow's built-in PDF writer defaults to JPEG encoding for RGB images,
which fails when libjpeg is not available in the environment. Instead we
write each page as a lossless PNG to a temp directory and assemble them
with qpdf, which embeds the PNGs directly without re-encoding.
Pillow's PDF writer defaults to JPEG encoding for RGB images, which
fails when libjpeg is absent in the environment. Fix: quantize each
image to palette mode (256 colours, FASTOCTREE) so Pillow uses
zlib/deflate instead of JPEG, save each as an individual single-page
PDF, then merge the page PDFs with qpdf.
Colour fidelity is preserved — the hacker theme uses only a handful
of distinct colours so 256-colour quantization is visually lossless.
"""
import tempfile as _tempfile
with _tempfile.TemporaryDirectory() as staging:
png_paths = []
page_pdfs = []
for i, img in enumerate(images):
p = os.path.join(staging, f'p{i:05d}.png')
img.save(p, format='PNG')
png_paths.append(p)
page_path = os.path.join(staging, f'p{i:05d}.pdf')
img.quantize(colors=256, method=Image.Quantize.FASTOCTREE).save(
page_path, format='PDF'
)
page_pdfs.append(page_path)
subprocess.run(
['qpdf', '--empty', '--pages'] + png_paths + ['--', output_path],
['qpdf', '--empty', '--pages'] + page_pdfs + ['--', output_path],
check=True,
)
@@ -119,6 +125,25 @@ def _check_qpdf() -> bool:
).returncode == 0
def _check_dependencies() -> None:
"""Verify required system tools are available before doing any work."""
missing = []
for tool in ('pdftoppm', 'qpdf'):
if subprocess.run(['which', tool], capture_output=True).returncode != 0:
missing.append(tool)
if missing:
tools = ', '.join(missing)
instructions = (
f"Install with:\n"
f" Linux/WSL: sudo apt install poppler-utils qpdf\n"
f" macOS: brew install poppler qpdf\n"
f" Windows: see docs/code/develop.md"
)
raise RuntimeError(
f"Missing required system tool(s): {tools}\n{instructions}"
)
def convert_pdf_to_dark(
input_path: str | Path,
output_path: str | Path,
@@ -138,6 +163,8 @@ def convert_pdf_to_dark(
input_path = str(input_path)
output_path = str(output_path)
_check_dependencies()
with tempfile.TemporaryDirectory() as tmp:
# 1. Rasterize all pages
prefix = os.path.join(tmp, 'page')
+58
View File
@@ -0,0 +1,58 @@
#!/usr/bin/env bash
COMMIT_MSG_FILE="$1"
if [ -z "$COMMIT_MSG_FILE" ]; then
echo "require-commit-body: no commit message file supplied" >&2
exit 1
fi
stripped=$(grep -v '^\s*#' "$COMMIT_MSG_FILE" | sed 's/[[:space:]]*$//')
subject=$(echo "$stripped" | sed -n '1p')
separator=$(echo "$stripped" | sed -n '2p')
body=$(echo "$stripped" | tail -n +3 | grep -v '^\s*$')
if [ -z "$subject" ]; then
echo ""
echo " COMMIT REJECTED: subject line is empty." >&2
echo ""
exit 1
fi
if [ -z "$separator" ] && [ -z "$body" ]; then
echo ""
echo " COMMIT REJECTED: commit has no body." >&2
echo ""
echo " Every commit must explain *why*, not just *what*." >&2
echo " Format:" >&2
echo ""
echo " type(scope): short subject" >&2
echo ""
echo " Explain the motivation for this change. What problem does" >&2
echo " it solve? Why is this the right approach? Reference any" >&2
echo " relevant issues, prior art, or context." >&2
echo ""
exit 1
fi
if [ -n "$separator" ]; then
echo ""
echo " COMMIT REJECTED: no blank line between subject and body." >&2
echo ""
echo " The second line of a commit message must be blank." >&2
echo ""
exit 1
fi
if [ -z "$body" ]; then
echo ""
echo " COMMIT REJECTED: commit body is empty." >&2
echo ""
echo " Add at least one line after the blank separator explaining" >&2
echo " why this change was made." >&2
echo ""
exit 1
fi
exit 0
+57 -33
View File
@@ -1,15 +1,19 @@
#!/usr/bin/env python3
"""Auto-generate and prepend a changelog entry to docs/changelog/index.md.
Called by .github/workflows/changelog.yml. Reads git log since the last
changelog version tag, categorises commits by conventional-commit prefix,
Called by .github/workflows/04-changelog.yml. Reads git log since the last
changelog version, categorises commits by conventional-commit prefix,
and prepends a new ## [vX.Y.Z] section in the MkDocs admonition format used
by the rest of the file.
Environment variables (all optional — reasonable defaults apply):
MANUAL_VERSION Override the auto-incremented version string.
Environment variables:
MANUAL_VERSION Version string to record (required when run from CI).
Falls back to auto-increment from the changelog for local runs.
TRIGGERING_SHA The commit SHA that triggered this run (used as range end).
DRY_RUN If "true", print the entry and exit without writing.
Note: version is sourced from the changelog file, not from git tags. Git tags
are no longer used as the version authority. The changelog is the source of truth.
"""
from __future__ import annotations
@@ -17,7 +21,6 @@ from __future__ import annotations
import os
import re
import subprocess
import sys
from datetime import datetime, timezone
from pathlib import Path
@@ -52,15 +55,6 @@ def run(cmd: list[str]) -> str:
return result.stdout.strip()
def latest_version_tag() -> str | None:
"""Return the most recent vX.Y.Z tag reachable from HEAD, or None."""
out = run(["git", "tag", "--sort=-version:refname", "--list", "v*"])
for line in out.splitlines():
if re.match(r"^v\d+\.\d+\.\d+", line):
return line
return None
def auto_increment_version(current: str | None) -> str:
"""Bump the patch number of the current version, or default to v1.0.0."""
if not current:
@@ -73,7 +67,11 @@ def auto_increment_version(current: str | None) -> str:
def version_from_changelog() -> str | None:
"""Parse the most recent ## [vX.Y.Z] heading from the changelog file."""
"""Parse the most recent ## [vX.Y.Z] heading from the changelog file.
This is the primary version source — the changelog is the authority,
not git tags.
"""
if not CHANGELOG.exists():
return None
for line in CHANGELOG.read_text(encoding="utf-8").splitlines():
@@ -84,11 +82,20 @@ def version_from_changelog() -> str | None:
def commits_since(ref: str | None, until: str) -> list[str]:
"""Return one-line commit messages between ref and until (exclusive/inclusive)."""
"""Return one-line commit messages between ref and until (exclusive/inclusive).
When no ref is given we fall back to the merge-base between HEAD and
origin/main to avoid dumping the entire history into the changelog.
"""
if ref:
log_range = f"{ref}..{until}"
else:
log_range = until
merge_base = run(["git", "merge-base", "HEAD", "origin/main"])
if merge_base:
log_range = f"{merge_base}..{until}"
else:
# Truly brand new repo with no remote — limit to last 50 commits
log_range = f"-50 {until}"
out = run(["git", "log", "--pretty=format:%s", log_range])
return [line.strip() for line in out.splitlines() if line.strip()]
@@ -97,12 +104,30 @@ def categorise(messages: list[str]) -> dict[str, list[str]]:
"""Sort commit messages into Added / Changed / Fixed buckets."""
buckets: dict[str, list[str]] = {b: [] for b in BUCKET_ORDER}
NOISE = re.compile(
r"""
\[skip\ ci\] # CI skip marker
| ^Merge\ (pull\ request|branch) # merge commits
| ^chore:\ bump # version bump chores
| update\ changelog # self-referential
| ^\d+/\d+ # numbered commit series (e.g. 3/8)
| ^Tweaking # vague WIP messages
| ^Moving\ some # vague WIP messages
| \ pt\d+$ # "...pt2", "...pt3" suffixes
| ^Fix\ (workflow|path|README)$ # one-word infrastructure fixes
| ^Still\ broken # embarrassing mid-fix notes
| ^WIP\b # work in progress
| ^Forgot\ to # oops commits
| ^Revert\ " # reverts (surface the original instead)
| ^One\ job\ to\ rule # joke commit messages
""",
re.VERBOSE | re.IGNORECASE,
)
for msg in messages:
# Skip automated / noise commits
if re.search(r"\[skip ci\]|^Merge |^chore: bump|update changelog", msg, re.I):
if NOISE.search(msg):
continue
# Strip conventional-commit prefix to get the plain description
m = re.match(r"^(\w+)(?:\([^)]+\))?!?:\s*(.+)$", msg)
if m:
prefix = m.group(1).lower()
@@ -112,7 +137,6 @@ def categorise(messages: list[str]) -> dict[str, list[str]]:
description = msg
bucket = "Changed"
# Capitalise first letter
description = description[0].upper() + description[1:] if description else description
buckets[bucket].append(description)
@@ -130,7 +154,7 @@ def format_admonition(bucket: str, items: list[str]) -> str:
def build_entry(version: str, buckets: dict[str, list[str]], sha: str) -> str:
date = datetime.now(timezone.utc).strftime("%Y-%m-%d")
lines = [f"## [{version}]", ""]
lines.append(f'!!! Note "Meta"')
lines.append('!!! Note "Meta"')
lines.append("")
lines.append(f" - Released {date} from [`{sha[:7]}`](https://github.com/Anon-Planet/thgtoa/commit/{sha})")
lines.append("")
@@ -139,7 +163,6 @@ def build_entry(version: str, buckets: dict[str, list[str]], sha: str) -> str:
if buckets.get(bucket):
lines.append(format_admonition(bucket, buckets[bucket]))
# If no commits were categorised, add a placeholder
if not any(buckets[b] for b in BUCKET_ORDER):
lines.append('!!! Note "Changed"')
lines.append("")
@@ -153,10 +176,8 @@ def prepend_entry(entry: str) -> None:
"""Insert the new entry after the # Release Notes heading."""
content = CHANGELOG.read_text(encoding="utf-8")
# Find the first ## heading and insert before it
insert_at = content.find("\n## ")
if insert_at == -1:
# No existing version section — append after the header block
content = content.rstrip() + "\n\n" + entry + "\n"
else:
content = content[: insert_at + 1] + entry + "\n" + content[insert_at + 1 :]
@@ -175,13 +196,11 @@ def main() -> int:
manual_version = os.environ.get("MANUAL_VERSION", "").strip()
triggering_sha = os.environ.get("TRIGGERING_SHA", "HEAD").strip() or "HEAD"
# Determine version
last_tag = latest_version_tag()
last_cl_ver = version_from_changelog()
base_version = last_tag or last_cl_ver
new_version = manual_version or auto_increment_version(base_version)
# Version authority: MANUAL_VERSION (required in CI) → changelog → auto-increment.
# Git tags are intentionally not consulted.
last_cl_ver = version_from_changelog()
new_version = manual_version or auto_increment_version(last_cl_ver)
print(f"Last tag: {last_tag or '(none)'}")
print(f"Last CL version: {last_cl_ver or '(none)'}")
print(f"New version: {new_version}")
print(f"Triggering SHA: {triggering_sha}")
@@ -190,8 +209,13 @@ def main() -> int:
print(f"Changelog already contains {new_version} — nothing to do.")
return 0
# Collect commits since the last tag (or all commits if no tag)
messages = commits_since(last_tag, triggering_sha)
if already_has_version(new_version) and manual_version:
print(f"::error::Changelog already contains {new_version}. Choose a different version.")
return 1
# Collect commits since the last changelog version (using it as a git ref
# is a best-effort — if the tag doesn't exist, commits_since handles it gracefully).
messages = commits_since(last_cl_ver, triggering_sha)
print(f"Commits found: {len(messages)}")
for m in messages:
print(f" {m}")
+199 -153
View File
@@ -1,214 +1,260 @@
#!/usr/bin/env python3
"""Verification script for PDF files.
"""Verification script for thgtoa PDF releases.
This script verifies:
1. SHA256 hash integrity of PDF files
2. GPG signature authenticity
3. VirusTotal scan status (optional)
Verifies SHA-256 hashes, BLAKE2b hashes, and GPG signatures (.asc) for
the light and dark PDFs. Optionally checks VirusTotal scan status.
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
python scripts/verify_pdf.py
python scripts/verify_pdf.py --hashes
python scripts/verify_pdf.py --signatures
python scripts/verify_pdf.py --vt
python scripts/verify_pdf.py --file export/thgtoa.pdf --hashes
"""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import subprocess
import sys
import urllib.request
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 _read_bare_hash(hash_file: Path) -> str | None:
"""Read a bare hex digest from a single-value hash file."""
try:
return hash_file.read_text(encoding="utf-8").strip().split()[0]
except (OSError, IndexError):
return None
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
def _read_hash_from_sumfile(sum_file: Path, pdf_path: Path) -> str | None:
"""Read a hash from a two-column sumfile (sha256sum / b2sum format).
Matches on the filename only (not the full path) so the file can be used
regardless of where the PDFs sit on disk.
"""
if not sum_file.exists():
return None
target = pdf_path.name
try:
for line in sum_file.read_text(encoding="utf-8").splitlines():
parts = line.strip().split(None, 1)
if len(parts) == 2 and Path(parts[1].lstrip("*")).name == target:
return parts[0]
except OSError:
return None
return None
# Hash verification
def _sha256(path: Path) -> str:
h = hashlib.sha256()
with path.open("rb") as fh:
for chunk in iter(lambda: fh.read(65536), b""):
h.update(chunk)
return h.hexdigest()
def _blake2b(path: Path) -> str:
h = hashlib.blake2b()
with path.open("rb") as fh:
for chunk in iter(lambda: fh.read(65536), b""):
h.update(chunk)
return h.hexdigest()
def verify_hashes(pdf: Path, export_dir: Path) -> bool:
"""Verify all available hash files for a PDF. Returns True if all pass."""
stem = pdf.name # e.g. "thgtoa.pdf" or "thgtoa-dark.pdf"
results: list[bool] = []
checks = [
("SHA-256", _sha256, export_dir / f"{stem}.sha256", export_dir / "sha256sums.txt"),
("BLAKE2b", _blake2b, export_dir / f"{stem}.b2sum", export_dir / "b2sums.txt"),
]
for algo, fn, bare_file, sum_file in checks:
# Resolve expected hash — prefer bare file, fall back to sumfile
expected = _read_bare_hash(bare_file) if bare_file.exists() else None
if expected is None:
expected = _read_hash_from_sumfile(sum_file, pdf)
if expected is None:
print(f"{algo}: no hash file found (checked {bare_file.name}, {sum_file.name})")
continue
actual = fn(pdf)
ok = actual == expected
results.append(ok)
mark = "" if ok else ""
print(f" {mark} {algo}")
if not ok:
print(f" expected: {expected}")
print(f" actual: {actual}")
return all(results) if results else False
# Signature verification
def verify_signature(pdf: Path) -> bool | None:
"""Verify the .asc detached signature for a PDF.
Returns True on success, False on failure, None if GPG is not installed
or the signature file is missing.
"""
sig = pdf.with_suffix(pdf.suffix + ".asc")
if not sig.exists():
print(f" ⚠ Signature file not found: {sig.name}")
return None
try:
result = subprocess.run(
["gpg", "--verify", str(sig_file), str(file_path)],
["gpg", "--verify", str(sig), str(pdf)],
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.")
print(" ⚠ 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}")
if result.returncode == 0:
print(f" ✓ GPG signature valid")
# Surface the key info line from stderr (that's where gpg writes it)
for line in result.stderr.splitlines():
if any(kw in line for kw in ("Good signature", "key ID", "fingerprint", "using")):
print(f" {line.strip()}")
return True
else:
print(f" ✗ GPG signature INVALID")
for line in result.stderr.splitlines():
if line.strip():
print(f" {line.strip()}")
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
# VirusTotal
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
def check_virustotal(pdf: Path, api_key: str) -> bool:
"""Query VirusTotal for the SHA-256 of a PDF. Returns True if clean."""
file_hash = _sha256(pdf)
url = f"https://www.virustotal.com/api/v3/files/{file_hash}"
req = urllib.request.Request(url, headers={"x-apikey": api_key})
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
with urllib.request.urlopen(req, timeout=30) as resp:
data = json.loads(resp.read().decode())
except urllib.error.HTTPError as e:
if e.code == 404:
print(f" ⚠ Not yet scanned on VirusTotal (hash: {file_hash[:16]}…)")
else:
print(f" ⚠ VirusTotal HTTP error: {e.code}")
return False
except Exception as e:
print(f"ERROR checking VirusTotal: {e}")
return None
print(f" ⚠ VirusTotal error: {e}")
return False
stats = data.get("data", {}).get("attributes", {}).get("last_analysis_stats", {})
malicious = stats.get("malicious", 0)
suspicious = stats.get("suspicious", 0)
undetected = stats.get("undetected", 0)
harmless = stats.get("harmless", 0)
total = malicious + suspicious + undetected + harmless
clean = malicious == 0 and suspicious == 0
mark = "" if clean else ""
print(f" {mark} VirusTotal ({malicious} malicious, {suspicious} suspicious, "
f"{harmless} clean / {total} engines)")
print(f" https://www.virustotal.com/gui/file/{file_hash}")
return clean
def main() -> int:
root = repo_root()
ap = argparse.ArgumentParser(description="Verify PDF files (hashes, signatures, VT).")
export = root / "export"
# File paths
ap.add_argument(
"--light-pdf",
type=Path,
default=root / "export" / "thgtoa.pdf",
help="Light mode PDF file",
ap = argparse.ArgumentParser(
description="Verify thgtoa PDF hashes, signatures, and VirusTotal status.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__,
)
ap.add_argument(
"--dark-pdf",
"--file",
type=Path,
default=root / "export" / "thgtoa-dark.pdf",
help="Dark mode PDF file",
default=None,
metavar="PDF",
help="Verify a single PDF instead of both light and dark",
)
ap.add_argument(
"--hash-file",
"--export-dir",
type=Path,
default=root / "export" / "thgtoa.pdf.sha256",
help="Hash file to verify against",
default=export,
metavar="DIR",
help=f"Directory containing hash and signature files (default: {export})",
)
# 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")
ap.add_argument("--hashes", action="store_true", help="Verify hashes only")
ap.add_argument("--signatures", action="store_true", help="Verify signatures only")
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
# Default: verify everything
do_hashes = args.hashes or not any([args.hashes, args.signatures, args.vt])
do_sigs = args.signatures or not any([args.hashes, args.signatures, args.vt])
do_vt = args.vt or not any([args.hashes, args.signatures, args.vt])
all_passed = True
# Resolve PDFs to check
if args.file:
pdfs = [args.file]
else:
pdfs = [export / "thgtoa.pdf", export / "thgtoa-dark.pdf"]
pdf_files = [
("Light", args.light_pdf),
("Dark", args.dark_pdf),
]
vt_api_key = os.environ.get("VT_API_KEY", "")
for mode_name, pdf_file in pdf_files:
if not pdf_file.exists():
print(f"⚠ WARNING: {pdf_file.name} not found. Skipping.")
overall_pass = True
for pdf in pdfs:
bar = "" * 60
print(f"\n{bar}")
print(f" {pdf.name}")
print(bar)
if not pdf.exists():
print(f" ⚠ File not found: {pdf} — skipping")
overall_pass = False
continue
print(f"\n{'='*60}")
print(f"Verifying {mode_name} PDF: {pdf_file.name}")
print('='*60)
if do_hashes:
ok = verify_hashes(pdf, args.export_dir)
if not ok:
overall_pass = False
# Verify hash if requested
if args.all or args.hashes:
if not verify_from_hash_file(pdf_file, args.hash_file):
all_passed = False
if do_sigs:
result = verify_signature(pdf)
if result is False:
overall_pass = 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
if do_vt:
if not vt_api_key:
print(" ⚠ VT_API_KEY not set — skipping VirusTotal check")
else:
ok = check_virustotal(pdf, vt_api_key)
if not ok:
overall_pass = 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
print(f"\n{'' * 60}")
if overall_pass:
print(" ✓ All checks passed")
else:
print("✗ Some verifications FAILED")
return 1
print(" ✗ One or more checks failed")
print()
return 0 if overall_pass else 1
if __name__ == "__main__":
raise SystemExit(main())