mirror of
https://github.com/Anon-Planet/thgtoa.git
synced 2026-05-06 11:34:18 +02:00
Moving some things around
Signed-off-by: nopeitsnothing <no@anonymousplanet.org>
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
# PDF Build, Scan & Release Scripts
|
||||
|
||||
This directory contains scripts for building PDFs from MkDocs documentation, scanning them with VirusTotal, generating hashes, and uploading artifacts to GitHub releases.
|
||||
|
||||
## Scripts
|
||||
|
||||
### `pdf_release.sh` (v2 - Recommended)
|
||||
The main script that handles:
|
||||
- SHA256 hash generation for PDF files
|
||||
- VirusTotal scanning of PDFs
|
||||
- Release creation/update on GitHub
|
||||
- GPG signature verification support
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
./scripts/pdf_release.sh --build <light|dark|both> --release <tag|latest> [--vt-api-key VT_KEY] [--github-token TOKEN]
|
||||
```
|
||||
|
||||
**Options:**
|
||||
- `--build`: PDF build mode (`light`, `dark`, or `both`) - Required
|
||||
- `--release`: Release update mode (`tag` for tagged releases, `latest` to always update) - Default: `tag`
|
||||
- `--vt-api-key`: VirusTotal API key (optional)
|
||||
- `--github-token`: GitHub token for release operations (optional)
|
||||
|
||||
### `build_guide_pdf.py`
|
||||
Python script that builds MkDocs documentation and converts it to PDF using Chromium/Chrome.
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
python scripts/build_guide_pdf.py --both # Build both light and dark mode
|
||||
python scripts/build_guide_pdf.py --dark-mode # Dark mode only
|
||||
python scripts/build_guide_pdf.py --skip-mkdocs # Skip MkDocs build, use existing site
|
||||
```
|
||||
|
||||
## GitHub Actions Workflow
|
||||
|
||||
The workflow `.github/workflows/build-pdf-combined.yml` combines all operations:
|
||||
|
||||
1. **Build PDFs** - Generates light/dark mode PDFs with GPG signatures
|
||||
2. **Scan & Release** - Scans with VirusTotal and updates/releases artifacts
|
||||
|
||||
### Required Secrets
|
||||
|
||||
Add these to your repository settings under **Settings > Secrets and variables > Actions**:
|
||||
|
||||
- `GPG_PRIVATE_KEY`: Your GPG private key for signing
|
||||
- `GPG_PASSPHRASE`: Passphrase for the GPG key (if any)
|
||||
- `VT_API_KEY`: VirusTotal API key for malware scanning
|
||||
- `GITHUB_TOKEN`: Automatically available, but can be manually added
|
||||
|
||||
### Workflow Triggers
|
||||
|
||||
The workflow runs on:
|
||||
- Manual dispatch (`workflow_dispatch`) with customizable options
|
||||
- Push to main branch when docs, mkdocs.yml, or scripts change
|
||||
|
||||
## Output Files
|
||||
|
||||
After running the build and release process, you'll get:
|
||||
|
||||
```
|
||||
export/
|
||||
├── thgtoa.pdf # Light mode PDF
|
||||
├── thgtoa-dark.pdf # Dark mode PDF
|
||||
├── thgtoa.pdf.sig # GPG signature for light PDF
|
||||
├── thgtoa-dark.pdf.sig # GPG signature for dark PDF
|
||||
├── thgtoa.pdf.sha256 # SHA256 hash for light PDF
|
||||
├── thgtoa-dark.pdf.sha256 # SHA256 hash for dark PDF
|
||||
├── sha256sum-combined.txt # Combined hash file
|
||||
├── sha256sum-combined.txt.sig # GPG signature for combined hashes
|
||||
└── virus-total-results.md # VirusTotal scan results
|
||||
```
|
||||
|
||||
## Hash Verification
|
||||
|
||||
To verify the integrity of downloaded PDFs:
|
||||
|
||||
```bash
|
||||
# Verify against individual hash file
|
||||
sha256sum -c thgtoa.pdf.sha256
|
||||
|
||||
# Or verify against combined hash file
|
||||
sha256sum -c sha256sum-combined.txt
|
||||
```
|
||||
|
||||
## VirusTotal Integration
|
||||
|
||||
When a `VT_API_KEY` is provided, the script will:
|
||||
1. Upload each PDF to VirusTotal's API
|
||||
2. Generate individual scan reports
|
||||
3. Include VT report links in release notes and artifacts
|
||||
|
||||
The VT results file (`virus-total-results.md`) contains:
|
||||
- Scan timestamp
|
||||
- SHA256 hashes for each PDF
|
||||
- Direct links to VirusTotal GUI reports
|
||||
|
||||
## Release Management
|
||||
|
||||
The script supports two release modes:
|
||||
|
||||
1. **Tag mode** (`--release tag`): Updates the release matching the current git tag
|
||||
2. **Latest mode** (`--release latest`): Always updates the most recent release (useful for continuous deployment)
|
||||
|
||||
When running in a GitHub Actions workflow with a tag push, it will automatically create or update the corresponding release.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### PDF Build Fails
|
||||
- Ensure Chrome/Chromium is installed: `sudo apt install chromium-browser`
|
||||
- Check MkDocs configuration is valid: `mkdocs build --strict`
|
||||
- Verify all documentation files are present and properly formatted
|
||||
|
||||
### VirusTotal Scan Fails
|
||||
- Check VT_API_KEY secret is correctly set in repository settings
|
||||
- Verify the API key has sufficient quota (free tier allows 4 requests/minute)
|
||||
- Check network connectivity to VirusTotal API
|
||||
|
||||
### Release Upload Fails
|
||||
- Ensure GITHUB_TOKEN has appropriate permissions (repo scope)
|
||||
- For existing releases, use `--release latest` instead of `tag`
|
||||
- Check that the release tag format matches GitHub's requirements (e.g., `v1.0.0`)
|
||||
|
||||
## Security Notes
|
||||
|
||||
- **GPG Keys**: Never commit private keys to version control. Use GitHub Secrets.
|
||||
- **VT API Key**: Keep your VirusTotal API key secret and rotate periodically.
|
||||
- **Release Artifacts**: All uploaded artifacts are publicly visible on your releases page.
|
||||
|
||||
## License
|
||||
|
||||
These scripts are part of the "The How-To Guide To Anonymity" project and follow the same licensing as the main repository.
|
||||
@@ -177,13 +177,13 @@ def main() -> int:
|
||||
"--pdf-light",
|
||||
type=Path,
|
||||
default=root / "export" / "thgtoa.pdf",
|
||||
help="Output PDF path for light mode (default: ./export/guide.pdf)",
|
||||
help="Output PDF path for light mode (default: ./export/thgtoa.pdf)",
|
||||
)
|
||||
ap.add_argument(
|
||||
"--pdf-dark",
|
||||
type=Path,
|
||||
default=root / "export" / "thgtoa-dark.pdf",
|
||||
help="Output PDF path for dark mode (default: ./export/guide-dark.pdf)",
|
||||
help="Output PDF path for dark mode (default: ./export/thgtoa-dark.pdf)",
|
||||
)
|
||||
ap.add_argument("--skip-mkdocs", action="store_true", help="Reuse existing site dir; only run print-to-pdf.")
|
||||
ap.add_argument("--dark-mode", action="store_true", help="Generate dark mode PDF only")
|
||||
|
||||
@@ -1,365 +0,0 @@
|
||||
!/bin/bash
|
||||
set -e
|
||||
|
||||
# PDF Hashing, Scanning, Release and Management script (hSCRAM)
|
||||
# Usage: ./pdf_release.sh --build <light|dark|both> --release <tag|latest> [--vt-api-key]
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
EXPORT_DIR="$ROOT_DIR/export"
|
||||
|
||||
# Default values
|
||||
MODE="both" # light, dark, or both
|
||||
RELEASE_MODE="tag" # tag (e.g. "v2.1.2") or "latest"
|
||||
VT_API_KEY=""
|
||||
GITHUB_TOKEN=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--build)
|
||||
MODE="$2"
|
||||
shift 2
|
||||
;;
|
||||
--release)
|
||||
RELEASE_MODE="$2"
|
||||
shift 2
|
||||
;;
|
||||
--vt-api-key)
|
||||
VT_API_KEY="$2"
|
||||
shift 2
|
||||
;;
|
||||
--github-token)
|
||||
GITHUB_TOKEN="$2"
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ ! "$MODE" =~ ^(light|dark|both)$ ]]; then
|
||||
echo "Error: Invalid build mode '$MODE'. Must be 'light', 'dark', or 'both'."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Hashing, Scanning, Release and Management script (hSCRAM)"
|
||||
echo "Mode: $MODE"
|
||||
echo "Release Mode: $RELEASE_MODE"
|
||||
|
||||
generate_hash() {
|
||||
local file="$1"
|
||||
sha256sum "$file" | cut -d' ' -f1
|
||||
}
|
||||
|
||||
create_hash_file() {
|
||||
local pdf_path="$1"
|
||||
local base_name=$(basename "$pdf_path")
|
||||
local hash_file="${EXPORT_DIR}/${base_name}.sha256"
|
||||
|
||||
(cd "$EXPORT_DIR" && sha256sum "$base_name") > "$hash_file"
|
||||
echo "Created: $hash_file"
|
||||
}
|
||||
|
||||
scan_with_virustotal() {
|
||||
local pdf_path="$1"
|
||||
local base_name=$(basename "$pdf_path")
|
||||
|
||||
if [[ -z "$VT_API_KEY" ]]; then
|
||||
echo "Warning: VT_API_KEY not provided, skipping VirusTotal scan for $base_name"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "Scanning $base_name with VirusTotal..."
|
||||
|
||||
local upload_response=$(curl -s -X POST \
|
||||
-H "x-apikey: $VT_API_KEY" \
|
||||
-F "file=@$pdf_path" \
|
||||
https://www.virustotal.com/api/v3/files)
|
||||
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo "Error uploading $base_name to VirusTotal"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local file_id=$(echo "$upload_response" | python3 -c "import sys, json; print(json.load(sys.stdin)['data']['id'])" 2>/dev/null || echo "")
|
||||
|
||||
if [[ -z "$file_id" ]]; then
|
||||
echo "Error: Could not extract file ID from VirusTotal response for $base_name"
|
||||
echo "Response: $upload_response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local vt_url="https://www.virustotal.com/gui/file/$file_id"
|
||||
echo "$vt_url"
|
||||
}
|
||||
|
||||
scan_all_pdfs() {
|
||||
local results_file="$EXPORT_DIR/virus-total-results.md"
|
||||
|
||||
cat > "$results_file" << 'HEADER'
|
||||
## VirusTotal Scan Results
|
||||
|
||||
**Scan Date:** TIMESTAMP
|
||||
|
||||
---
|
||||
HEADER
|
||||
|
||||
sed -i "s/TIMESTAMP/$(date -u +"%Y-%m-%d %H:%M UTC")/" "$results_file"
|
||||
|
||||
local pdf_files=()
|
||||
if [[ "$MODE" == "light" || "$MODE" == "both" ]]; then
|
||||
pdf_files+=("$EXPORT_DIR/thgtoa.pdf")
|
||||
fi
|
||||
if [[ "$MODE" == "dark" || "$MODE" == "both" ]]; then
|
||||
pdf_files+=("$EXPORT_DIR/thgtoa-dark.pdf")
|
||||
fi
|
||||
|
||||
for pdf in "${pdf_files[@]}"; do
|
||||
if [[ -f "$pdf" ]]; then
|
||||
local base_name=$(basename "$pdf")
|
||||
local hash=$(generate_hash "$pdf")
|
||||
|
||||
echo "" >> "$results_file"
|
||||
echo "### $base_name" >> "$results_file"
|
||||
echo "- **SHA256 Hash:** \`$hash\`" >> "$results_file"
|
||||
|
||||
if [[ -n "$VT_API_KEY" ]]; then
|
||||
local vt_url=$(scan_with_virustotal "$pdf")
|
||||
if [[ $? -eq 0 && -n "$vt_url" ]]; then
|
||||
echo "- **VirusTotal Report:** [$vt_url]($vt_url)" >> "$results_file"
|
||||
else
|
||||
echo "- **VirusTotal Report:** Scan failed or API key not provided" >> "$results_file"
|
||||
fi
|
||||
else
|
||||
echo "- **VirusTotal Report:** VT_API_KEY not configured, scan skipped" >> "$results_file"
|
||||
fi
|
||||
|
||||
create_hash_file "$pdf"
|
||||
else
|
||||
echo "Warning: $pdf does not exist, skipping..."
|
||||
fi
|
||||
done
|
||||
|
||||
cat >> "$results_file" << 'FOOTER'
|
||||
|
||||
---
|
||||
*Scan performed automatically by GitHub Actions*
|
||||
FOOTER
|
||||
|
||||
echo "VirusTotal results saved to: $results_file"
|
||||
}
|
||||
|
||||
update_release() {
|
||||
local tag="${1:-}"
|
||||
local release_notes="$EXPORT_DIR/release-notes.md"
|
||||
|
||||
if [[ "$RELEASE_MODE" == "tag" && -z "$tag" ]]; then
|
||||
tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
|
||||
fi
|
||||
|
||||
if [[ -z "$tag" ]]; then
|
||||
echo "Warning: No release tag found, skipping release update."
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "Updating release for tag: $tag"
|
||||
|
||||
cat > "$release_notes" << EOF
|
||||
# Release Notes - $tag
|
||||
|
||||
**Release Date:** $(date -u +"%Y-%m-%d %H:%M UTC")
|
||||
|
||||
## PDF Files
|
||||
|
||||
EOF
|
||||
|
||||
if [[ "$MODE" == "light" || "$MODE" == "both" ]]; then
|
||||
local hash=$(generate_hash "$EXPORT_DIR/thgtoa.pdf")
|
||||
echo "- **thgtoa.pdf (Light Mode)**" >> "$release_notes"
|
||||
echo " - SHA256: \`$hash\`" >> "$release_notes"
|
||||
if [[ -f "$EXPORT_DIR/thgtoa.pdf.sig" ]]; then
|
||||
echo " - Signature: \`thgtoa.pdf.sig\` (GPG signed)" >> "$release_notes"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$MODE" == "dark" || "$MODE" == "both" ]]; then
|
||||
local hash=$(generate_hash "$EXPORT_DIR/thgtoa-dark.pdf")
|
||||
echo "- **thgtoa-dark.pdf (Dark Mode)**" >> "$release_notes"
|
||||
echo " - SHA256: \`$hash\`" >> "$release_notes"
|
||||
if [[ -f "$EXPORT_DIR/thgtoa-dark.pdf.sig" ]]; then
|
||||
echo " - Signature: \`thgtoa-dark.pdf.sig\` (GPG signed)" >> "$release_notes"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "" >> "$release_notes"
|
||||
echo "---" >> "$release_notes"
|
||||
|
||||
if [[ -f "$EXPORT_DIR/virus-total-results.md" ]]; then
|
||||
echo "## VirusTotal Scan Results" >> "$release_notes"
|
||||
echo "" >> "$release_notes"
|
||||
cat "$EXPORT_DIR/virus-total-results.md" >> "$release_notes"
|
||||
echo "" >> "$release_notes"
|
||||
fi
|
||||
|
||||
local files_to_upload=""
|
||||
|
||||
if [[ -f "$EXPORT_DIR/thgtoa.pdf" ]]; then
|
||||
files_to_upload+="$EXPORT_DIR/thgtoa.pdf "
|
||||
fi
|
||||
if [[ -f "$EXPORT_DIR/thgtoa-dark.pdf" ]]; then
|
||||
files_to_upload+="$EXPORT_DIR/thgtoa-dark.pdf "
|
||||
fi
|
||||
if [[ -f "$EXPORT_DIR/thgtoa.pdf.sig" ]]; then
|
||||
files_to_upload+="$EXPORT_DIR/thgtoa.pdf.sig "
|
||||
fi
|
||||
if [[ -f "$EXPORT_DIR/thgtoa-dark.pdf.sig" ]]; then
|
||||
files_to_upload+="$EXPORT_DIR/thgtoa-dark.pdf.sig "
|
||||
fi
|
||||
|
||||
local combined_hash_file="$EXPORT_DIR/sha256sum-combined.txt"
|
||||
|
||||
if [[ -f "$EXPORT_DIR/thgtoa.pdf.sha256" ]]; then
|
||||
cat "$EXPORT_DIR/thgtoa.pdf.sha256" >> "$combined_hash_file" 2>/dev/null || true
|
||||
fi
|
||||
if [[ -f "$EXPORT_DIR/thgtoa-dark.pdf.sha256" ]]; then
|
||||
echo "" >> "$combined_hash_file"
|
||||
cat "$EXPORT_DIR/thgtoa-dark.pdf.sha256" >> "$combined_hash_file"
|
||||
fi
|
||||
|
||||
files_to_upload+="$combined_hash_file "
|
||||
|
||||
if [[ -n "${GPG_PRIVATE_KEY:-}" && -n "${GPG_PASSPHRASE:-}" ]]; then
|
||||
echo "$GPG_PRIVATE_KEY" | gpg --batch --import 2>/dev/null || true
|
||||
gpg --batch --yes --armor --detach-sign --output "$combined_hash_file.sig" "$combined_hash_file" 2>/dev/null || true
|
||||
if [[ -f "$combined_hash_file.sig" ]]; then
|
||||
files_to_upload+="$combined_hash_file.sig "
|
||||
fi
|
||||
fi
|
||||
|
||||
if command -v gh &> /dev/null && [[ -n "$GITHUB_TOKEN" ]]; then
|
||||
echo "Uploading release with GitHub CLI..."
|
||||
|
||||
local release_exists=$(gh release view "$tag" 2>/dev/null && echo "yes" || echo "no")
|
||||
|
||||
if [[ "$release_exists" == "yes" ]]; then
|
||||
gh release edit "$tag" --notes-file "$release_notes" 2>/dev/null || {
|
||||
echo "Warning: Failed to update release notes"
|
||||
}
|
||||
|
||||
for file in $files_to_upload; do
|
||||
if [[ -f "$file" ]]; then
|
||||
local file_name=$(basename "$file")
|
||||
gh release upload "$tag" "$file" 2>/dev/null || {
|
||||
echo "Warning: Failed to upload $file_name"
|
||||
}
|
||||
fi
|
||||
done
|
||||
else
|
||||
gh release create "$tag" \
|
||||
--title "Release $tag" \
|
||||
--notes-file "$release_notes" \
|
||||
$files_to_upload 2>/dev/null || {
|
||||
echo "Error: Failed to create release"
|
||||
return 1
|
||||
}
|
||||
fi
|
||||
|
||||
else
|
||||
if [[ -n "$GITHUB_TOKEN" && -n "$tag" ]]; then
|
||||
echo "Using GitHub API to upload release..."
|
||||
|
||||
local repo="${GITHUB_REPOSITORY:-}"
|
||||
local api_url="https://api.github.com/repos/$repo/releases"
|
||||
|
||||
local existing_release=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
|
||||
"$api_url/tags/$tag")
|
||||
|
||||
if [[ $(echo "$existing_release" | grep -c '"id":') -gt 0 ]]; then
|
||||
echo "Release already exists, updating..."
|
||||
local release_id=$(echo "$existing_release" | python3 -c "import sys, json; print(json.load(sys.stdin)['id'])" 2>/dev/null || echo "")
|
||||
|
||||
curl -X PATCH \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
"$api_url/$release_id" \
|
||||
-d "{\"body\":\"$(cat "$release_notes")\"}" 2>/dev/null || true
|
||||
|
||||
for file in $files_to_upload; do
|
||||
if [[ -f "$file" ]]; then
|
||||
local file_name=$(basename "$file")
|
||||
local mime_type=$(file --mime-type -b "$file")
|
||||
|
||||
curl -X POST \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
-H "Content-Type: $mime_type" \
|
||||
--data-binary @"$file" \
|
||||
"https://uploads.github.com/repos/$repo/releases/$release_id/assets?name=$file_name" 2>/dev/null || {
|
||||
echo "Warning: Failed to upload $file_name"
|
||||
}
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo "Creating new release..."
|
||||
|
||||
local create_response=$(curl -s -X POST \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
"$api_url" \
|
||||
-d "{\"tag_name\":\"$tag\",\"name\":\"Release $tag\",\"body\":\"$(cat "$release_notes")\"}")
|
||||
|
||||
local release_id=$(echo "$create_response" | python3 -c "import sys, json; print(json.load(sys.stdin)['id'])" 2>/dev/null || echo "")
|
||||
|
||||
for file in $files_to_upload; do
|
||||
if [[ -f "$file" ]]; then
|
||||
local file_name=$(basename "$file")
|
||||
local mime_type=$(file --mime-type -b "$file")
|
||||
|
||||
curl -X POST \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
-H "Content-Type: $mime_type" \
|
||||
--data-binary @"$file" \
|
||||
"https://uploads.github.com/repos/$repo/releases/$release_id/assets?name=$file_name" 2>/dev/null || {
|
||||
echo "Warning: Failed to upload $file_name"
|
||||
}
|
||||
fi
|
||||
done
|
||||
fi
|
||||
else
|
||||
echo "Error: GITHUB_TOKEN required for release upload."
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Release update complete!"
|
||||
}
|
||||
|
||||
echo ""
|
||||
echo "Step 1: Generating hashes..."
|
||||
if [[ "$MODE" == "light" || "$MODE" == "both" ]]; then
|
||||
if [[ -f "$EXPORT_DIR/thgtoa.pdf" ]]; then
|
||||
create_hash_file "$EXPORT_DIR/thgtoa.pdf"
|
||||
else
|
||||
echo "Warning: $EXPORT_DIR/thgtoa.pdf not found. Ensure PDF is built first."
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$MODE" == "dark" || "$MODE" == "both" ]]; then
|
||||
if [[ -f "$EXPORT_DIR/thgtoa-dark.pdf" ]]; then
|
||||
create_hash_file "$EXPORT_DIR/thgtoa-dark.pdf"
|
||||
else
|
||||
echo "Warning: $EXPORT_DIR/thgtoa-dark.pdf not found. Ensure PDF is built first."
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Step 2: Scanning with VirusTotal..."
|
||||
scan_all_pdfs
|
||||
|
||||
echo ""
|
||||
echo "Step 3: Updating release..."
|
||||
update_release "$GITHUB_REF_NAME"
|
||||
|
||||
echo ""
|
||||
echo "PDF Release Script Complete!"
|
||||
@@ -169,44 +169,12 @@ GitHub repository:
|
||||
|
||||
3. VT_API_KEY (optional but recommended)
|
||||
- VirusTotal API key for malware scanning
|
||||
- Get a free key at: https://www.virustotal.com/gui/join-us
|
||||
|
||||
|
||||
HOW TO ADD SECRETS:
|
||||
|
||||
1. Go to your repository on GitHub
|
||||
2. Click 'Settings' → 'Secrets and variables' → 'Actions'
|
||||
3. Click 'New repository secret' for each secret below:
|
||||
|
||||
Secret Name | Value Format
|
||||
---------------------|--------------------------------------------------
|
||||
GPG_PRIVATE_KEY | Paste the entire ASCII armored key (BEGIN PGP...)
|
||||
GPG_PASSPHRASE | Your key's passphrase (no special characters issues)
|
||||
VT_API_KEY | Your VirusTotal API key
|
||||
|
||||
|
||||
VERIFYING YOUR SETUP:
|
||||
|
||||
After adding secrets, you can test by:
|
||||
1. Going to 'Actions' tab
|
||||
2. Selecting 'Build guide PDF' workflow
|
||||
3. Clicking 'Run workflow'
|
||||
4. Checking if the workflow completes successfully
|
||||
|
||||
|
||||
TROUBLESHOOTING:
|
||||
|
||||
- If GPG signing fails: Check that your key has signing capability ('s' flag)
|
||||
- If passphrase is wrong: Verify you're using the correct passphrase
|
||||
- If VT scan fails: Ensure API key is valid and within rate limits
|
||||
|
||||
|
||||
SECURITY NOTES:
|
||||
|
||||
⚠ NEVER share your private key or passphrase publicly
|
||||
⚠ Always use repository secrets, never hardcode in scripts
|
||||
⚠ Rotate keys periodically if compromised
|
||||
⚠ Use strong passphrases (12+ characters recommended)
|
||||
""")
|
||||
|
||||
|
||||
@@ -220,8 +188,6 @@ def main() -> int:
|
||||
print("⚠ WARNING: GPG is not installed or not in PATH")
|
||||
print("Please install GPG before continuing:")
|
||||
print(" - Linux: sudo apt install gnupg")
|
||||
print(" - macOS: brew install gnupg")
|
||||
print(" - Windows: https://www.gpg4win.org/")
|
||||
print("\nContinuing anyway...")
|
||||
|
||||
# List available keys
|
||||
@@ -313,7 +279,7 @@ To get your private key for the GPG_PRIVATE_KEY secret:
|
||||
print("1. Export your private key (see instructions above)")
|
||||
print("2. Add all three secrets to GitHub repository settings")
|
||||
print("3. Test the workflow by triggering a manual build")
|
||||
print("\nFor more information, see: docs/guide/pdf-workflow.md\n")
|
||||
print("\nFor more information, see: docs/guide/dev-workflow.md\n")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script to generate checksums (SHA256, B2SUM) and GPG sign PDF files
|
||||
# Usage: ./sign-pdfs.sh [input_directory] [output_directory]
|
||||
# If directories are not provided, defaults will be used
|
||||
|
||||
set -e # Exit on error
|
||||
|
||||
# Configuration
|
||||
INPUT_DIR="${1:-./export}" # Default: build-output directory
|
||||
OUTPUT_DIR="${2:-./export}" # Default: signed-pdfs directory
|
||||
CHECKSUMS_DIR="${3:-./export}" # Default: checksums directory
|
||||
GPG_KEY_ID="9FA5436D0EE360985157382517ECA05F768DEDF6"
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Function to print colored messages
|
||||
print_info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
print_warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# Check if required tools are available
|
||||
check_dependencies() {
|
||||
print_info "Checking dependencies..."
|
||||
|
||||
for cmd in sha256sum b2sum gpg; do
|
||||
if ! command -v "$cmd" &> /dev/null; then
|
||||
print_error "$cmd is not installed. Please install it and try again."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Check GPG key availability
|
||||
if [ -z "$GPG_KEY_ID" ]; then
|
||||
GPG_KEY_ID="${SIGN_PDF_GPG_KEY:-}"
|
||||
fi
|
||||
|
||||
if [ -n "$GPG_KEY_ID" ]; then
|
||||
if ! gpg --list-keys "$GPG_KEY_ID" &> /dev/null; then
|
||||
print_error "GPG key '$GPG_KEY_ID' not found in your keyring."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
# List available keys and prompt user
|
||||
print_warn "No GPG key ID specified. Listing available secret keys:"
|
||||
gpg --list-secret-keys --keyid-format LONG
|
||||
|
||||
read -p "Enter the GPG key ID to use for signing (or press Enter to skip): " GPG_KEY_ID
|
||||
if [ -n "$GPG_KEY_ID" ]; then
|
||||
if ! gpg --list-keys "$GPG_KEY_ID" &> /dev/null; then
|
||||
print_error "GPG key '$GPG_KEY_ID' not found in your keyring."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
print_warn "No GPG signing will be performed. Set SIGN_PDF_GPG_KEY environment variable or pass key ID as argument."
|
||||
fi
|
||||
fi
|
||||
|
||||
print_info "All dependencies checked successfully!"
|
||||
}
|
||||
|
||||
# Create output directories
|
||||
setup_directories() {
|
||||
print_info "Setting up directories..."
|
||||
|
||||
if [ ! -d "$INPUT_DIR" ]; then
|
||||
print_error "Input directory '$INPUT_DIR' does not exist."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
mkdir -p "$CHECKSUMS_DIR"
|
||||
|
||||
print_info "Input directory: $INPUT_DIR"
|
||||
print_info "Output directory: $OUTPUT_DIR"
|
||||
print_info "Checksums directory: $CHECKSUMS_DIR"
|
||||
}
|
||||
|
||||
# Generate SHA256 checksum for a file
|
||||
generate_sha256() {
|
||||
local file="$1"
|
||||
local filename=$(basename "$file")
|
||||
local output_file="${CHECKSUMS_DIR}/${filename}.sha256"
|
||||
|
||||
sha256sum "$file" > "$output_file"
|
||||
print_info "SHA256 checksum generated: $output_file"
|
||||
}
|
||||
|
||||
# Generate B2SUM checksum for a file
|
||||
generate_b2sum() {
|
||||
local file="$1"
|
||||
local filename=$(basename "$file")
|
||||
local output_file="${CHECKSUMS_DIR}/${filename}.b2sum"
|
||||
|
||||
b2sum "$file" > "$output_file"
|
||||
print_info "B2SUM checksum generated: $output_file"
|
||||
}
|
||||
|
||||
# GPG sign a file
|
||||
gpg_sign() {
|
||||
local file="$1"
|
||||
local filename=$(basename "$file")
|
||||
|
||||
if [ -z "$GPG_KEY_ID" ]; then
|
||||
print_warn "Skipping GPG signing for '$filename' (no key ID provided)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Sign the file in detached mode with ASCII armor
|
||||
gpg --batch --yes --detach-sign --armor --local-user "$GPG_KEY_ID" \
|
||||
--output "${file}.sig" "$file"
|
||||
|
||||
print_info "GPG signature generated: ${file}.sig"
|
||||
}
|
||||
|
||||
# Process a single PDF file
|
||||
process_pdf() {
|
||||
local pdf_file="$1"
|
||||
local filename=$(basename "$pdf_file")
|
||||
|
||||
print_info "Processing: $filename"
|
||||
|
||||
# Generate checksums
|
||||
generate_sha256 "$pdf_file"
|
||||
generate_b2sum "$pdf_file"
|
||||
|
||||
# GPG sign if key is available
|
||||
gpg_sign "$pdf_file"
|
||||
}
|
||||
|
||||
# Main function
|
||||
main() {
|
||||
echo ""
|
||||
check_dependencies
|
||||
setup_directories
|
||||
|
||||
# Find all PDF files in input directory (recursively)
|
||||
pdf_files=($(find "$INPUT_DIR" -type f -name "*.pdf"))
|
||||
|
||||
if [ ${#pdf_files[@]} -eq 0 ]; then
|
||||
print_error "No PDF files found in '$INPUT_DIR'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_info "Found ${#pdf_files[@]} PDF file(s) to process"
|
||||
|
||||
# Process each PDF file
|
||||
for pdf_file in "${pdf_files[@]}"; do
|
||||
process_pdf "$pdf_file"
|
||||
done
|
||||
|
||||
print_info "=========================================="
|
||||
print_info "Processing Complete!"
|
||||
print_info "=========================================="
|
||||
print_info "Checksums saved to: $CHECKSUMS_DIR"
|
||||
print_info "Signed files and signatures in: $(dirname "$INPUT_DIR")"
|
||||
|
||||
# Display summary of checksums
|
||||
print_info "SHA256 Checksums:"
|
||||
cat "${CHECKSUMS_DIR}"/*.sha256 2>/dev/null || true
|
||||
print_info "B2SUM Checksums:"
|
||||
cat "${CHECKSUMS_DIR}"/*.b2sum 2>/dev/null || true
|
||||
}
|
||||
|
||||
# Run main function
|
||||
main "$@"
|
||||
+4
-12
@@ -26,11 +26,9 @@ import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def repo_root() -> Path:
|
||||
return Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
def calculate_sha256(file_path: Path) -> str:
|
||||
"""Calculate SHA256 hash of a file."""
|
||||
sha256_hash = hashlib.sha256()
|
||||
@@ -39,7 +37,6 @@ def calculate_sha256(file_path: Path) -> str:
|
||||
sha256_hash.update(byte_block)
|
||||
return sha256_hash.hexdigest()
|
||||
|
||||
|
||||
def verify_hash(file_path: Path, expected_hash: str) -> bool:
|
||||
"""Verify file hash against expected value."""
|
||||
actual_hash = calculate_sha256(file_path)
|
||||
@@ -50,7 +47,6 @@ def verify_hash(file_path: Path, expected_hash: str) -> bool:
|
||||
print(f" Actual: {actual_hash}")
|
||||
return is_valid
|
||||
|
||||
|
||||
def verify_signature(file_path: Path, sig_file: Path) -> bool:
|
||||
"""Verify GPG signature of a file."""
|
||||
if not sig_file.exists():
|
||||
@@ -81,7 +77,6 @@ def verify_signature(file_path: Path, sig_file: Path) -> bool:
|
||||
print("⚠ WARNING: GPG not installed. Skipping signature verification.")
|
||||
return None
|
||||
|
||||
|
||||
def verify_from_hash_file(file_path: Path, hash_file: Path) -> bool:
|
||||
"""Verify file hash from a hash file."""
|
||||
if not hash_file.exists():
|
||||
@@ -102,7 +97,6 @@ def verify_from_hash_file(file_path: Path, hash_file: Path) -> bool:
|
||||
|
||||
return verify_hash(file_path, expected_hash)
|
||||
|
||||
|
||||
def check_virustotal(file_hash: str, api_key: str | None = None) -> dict | None:
|
||||
"""Check VirusTotal scan status for a file hash."""
|
||||
if not api_key:
|
||||
@@ -127,9 +121,9 @@ def check_virustotal(file_hash: str, api_key: str | None = None) -> dict | None:
|
||||
|
||||
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)}")
|
||||
print(f" Suspicious: {stats.get('suspicious', 0)}")
|
||||
print(f" Undetected: {stats.get('undetected', 0)}")
|
||||
print(f" Clean: {stats.get('harmless', 0)}")
|
||||
|
||||
return data
|
||||
|
||||
@@ -137,7 +131,6 @@ def check_virustotal(file_hash: str, api_key: str | None = None) -> dict | None:
|
||||
print(f"⚠ ERROR checking VirusTotal: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def main() -> int:
|
||||
root = repo_root()
|
||||
ap = argparse.ArgumentParser(description="Verify PDF files (hashes, signatures, VT).")
|
||||
@@ -158,7 +151,7 @@ def main() -> int:
|
||||
ap.add_argument(
|
||||
"--hash-file",
|
||||
type=Path,
|
||||
default=root / "sha256sum-light.txt",
|
||||
default=root / "export" / "thgtoa.pdf.sha256",
|
||||
help="Hash file to verify against",
|
||||
)
|
||||
|
||||
@@ -217,6 +210,5 @@ def main() -> int:
|
||||
print("✗ Some verifications FAILED")
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
||||
Reference in New Issue
Block a user