From 4212aa233a0dba58ddaaec8e75c55a4c7a56c19b Mon Sep 17 00:00:00 2001
From: "rE-Bo0t.bx1" <54429050+r3bo0tbx1@users.noreply.github.com>
Date: Fri, 14 Nov 2025 16:42:52 +0800
Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9D=20docs(v1.1.1):=20Documentation,?=
=?UTF-8?q?=20templates,=20and=20CI/CD=20enhancements?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- ๐ Added comprehensive docs including FAQ, architecture, and migration guides
- ๐งฉ Introduced new and updated example configs and templates
- ๐งพ Added a pull request template for contributor workflow
- ๐งช Enhanced CI/CD with SBOM generation and improved release notes
- ๐ก๏ธ Expanded Trivy security scanning coverage in pipelines
- ๐ Updated README to reference new docs and the quick start script
---
.github/FUNDING.yml | 3 +
.github/pull_request_template.md | 296 ++++++++
.github/workflows/release.yml | 142 +++-
.github/workflows/validate.yml | 104 ++-
README.md | 46 +-
docs/ARCHITECTURE.md | 818 +++++++++++++++++++++
docs/FAQ.md | 542 ++++++++++++++
scripts/migration/README.md | 413 +++++++++++
scripts/migration/migrate-from-official.sh | 635 ++++++++++++++++
scripts/release/README.md | 558 ++++++++++++++
scripts/release/generate-release-notes.sh | 441 +++++++++++
scripts/release/update-version.sh | 325 ++++++++
scripts/utilities/quick-start.sh | 625 ++++++++++++++++
13 files changed, 4923 insertions(+), 25 deletions(-)
create mode 100644 .github/FUNDING.yml
create mode 100644 .github/pull_request_template.md
create mode 100644 docs/ARCHITECTURE.md
create mode 100644 docs/FAQ.md
create mode 100644 scripts/migration/README.md
create mode 100644 scripts/migration/migrate-from-official.sh
create mode 100644 scripts/release/README.md
create mode 100644 scripts/release/generate-release-notes.sh
create mode 100644 scripts/release/update-version.sh
create mode 100644 scripts/utilities/quick-start.sh
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..a93b521
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,3 @@
+custom:
+ - https://tny.lv/donate-btc
+ - https://tny.lv/donate-xmr
\ No newline at end of file
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000..405066c
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,296 @@
+
+
+## ๐ PR Type
+
+- [x] ๐ **Documentation** (changes to documentation only)
+- [x] ๐ง **Configuration** (changes to templates, examples, or deployment configs)
+
+---
+
+## ๐ Related Issue
+
+- [x] This is a standalone improvement (no related issue)
+
+**Context:** Completes v1.1.1 release by documenting OBFS4V fix, PT_PORT support, and bandwidth configuration options across all templates and examples.
+
+---
+
+## ๐ Description
+
+### What does this PR do?
+
+- **Documents OBFS4V_* parsing fix** in CHANGELOG.md (busybox compatibility for values with spaces)
+- **Adds PT_PORT support documentation** to bridge templates and examples
+- **Clarifies bandwidth configuration options** across all templates and examples
+- **Updates 10 template files** with inline bandwidth option comments
+- **Enhances CLAUDE.md** with comprehensive bandwidth configuration guidance
+- **Creates comprehensive pull request template** for future contributions
+
+### Why is this change needed?
+
+- **OBFS4V fix** (docker-entrypoint.sh:309-321) was implemented but not documented in examples/templates
+- **PT_PORT support** was added but examples only showed TOR_* naming (missing official bridge naming)
+- **Bandwidth options** were unclear - users didn't know difference between RelayBandwidthRate vs BandwidthRate
+- **Templates lacked inline guidance** on when to use ENV vs mounted config bandwidth options
+- **No PR template existed** - needed to standardize contribution quality
+
+---
+
+## ๐งช Testing Performed
+
+### Testing Method
+
+- [x] **Documentation review** (verified all docs are accurate)
+- [x] **JSON templates validated** (all cosmos-compose-*.json files)
+- [x] **YAML templates validated** (all docker-compose-*.yml files)
+- [x] **Example configs validated** (relay-bridge.conf, relay-exit.conf, relay-guard.conf)
+- [x] **Cross-reference verification** (all references to bandwidth options are consistent)
+
+### Test Environment
+
+**Deployment Method:**
+- [x] Documentation only - no functional changes
+
+**Verification Performed:**
+```
+โ
All JSON templates parse correctly (python3 -m json.tool)
+โ
All YAML templates parse correctly (docker-compose config -q)
+โ
Example configs have valid syntax (sh -n would pass on torrc validation)
+โ
CHANGELOG.md follows Keep a Changelog format
+โ
All cross-references are accurate
+โ
PR template follows GitHub markdown standards
+```
+
+---
+
+## ๐ฅ Breaking Changes
+
+- [x] **No breaking changes**
+
+**Rationale:** Documentation and template metadata only - no functional code changes.
+
+---
+
+## ๐ Documentation Updates
+
+- [x] **CHANGELOG.md** (added comprehensive "Configuration & Documentation Enhancements" section under v1.1.1)
+- [x] **CLAUDE.md** (enhanced "Key Differences" section with bandwidth options explanation)
+- [x] **templates/README.md** (cross-references to bandwidth configuration - already present, verified)
+- [x] **examples/** (updated 3 configuration examples with PT_PORT and bandwidth options)
+- [x] **.github/pull_request_template.md** (created comprehensive PR template)
+
+**Template Updates (10 files):**
+- `cosmos-compose-bridge.json` - Note about OR_PORT/PT_PORT alternative
+- `cosmos-compose-guard.json` - Bandwidth options documentation
+- `cosmos-compose-exit.json` - Bandwidth options with recommendations
+- `docker-compose-bridge.yml` - Official naming alternative info
+- `docker-compose-guard-env.yml` - Bandwidth comment explaining options
+- `docker-compose-exit.yml` - Bandwidth comment explaining options
+
+**Example Updates (3 files):**
+- `examples/relay-bridge.conf` - Added Method 2 with PT_PORT
+- `examples/relay-exit.conf` - Added BandwidthRate/Burst Option 2
+- `examples/relay-guard.conf` - Added BandwidthRate/Burst Option 2
+
+---
+
+## โ
Code Quality Checklist
+
+### Templates
+
+- [x] JSON templates validated (valid JSON syntax)
+- [x] YAML templates validated (valid YAML syntax)
+- [x] Cosmos templates include metadata section
+- [x] Docker Compose templates include comments and usage instructions
+- [x] Volume syntax standardized (`{}` notation used consistently)
+- [x] Security options included (no-new-privileges, cap-drop/add present in templates)
+
+### General Code Quality
+
+- [x] No hardcoded secrets or sensitive data
+- [x] Documentation is clear and actionable
+- [x] Consistent formatting across all files
+- [x] Variable names are descriptive (in examples)
+- [x] Follows existing project style
+- [x] No unnecessary dependencies added
+
+---
+
+## ๐ Security Considerations
+
+- [x] **No security implications**
+
+**Rationale:** Documentation and template metadata changes only. No code execution paths modified.
+
+---
+
+## ๐ Deployment Impact
+
+### Impact on Existing Users
+
+- [x] **No impact** - Fully backward compatible
+
+**Rationale:**
+- Templates are metadata/documentation only
+- Example configs are reference materials (not deployed)
+- CHANGELOG documents existing functionality
+- No functional code changes
+
+### Benefits for Users
+
+1. **Bridge operators** - Now understand PT_PORT usage (official naming compatibility)
+2. **All relay operators** - Clear guidance on bandwidth options (RelayBandwidth vs Bandwidth)
+3. **Template users** - Inline comments explain configuration choices
+4. **Contributors** - PR template ensures quality and consistency
+
+---
+
+## ๐ธ Screenshots / Logs
+
+
+Click to expand: CHANGELOG.md additions
+
+```markdown
+### ๐ Configuration & Documentation Enhancements (Latest)
+
+* ๐ง **OBFS4V_* Variable Parsing (CRITICAL FIX)**
+ - Fixed busybox regex incompatibility causing rejection of values with spaces
+ - Issue: `OBFS4V_MaxMemInQueues="1024 MB"` was rejected with "dangerous characters" error
+ - Solution: Rewrote validation (docker-entrypoint.sh:309-321)
+ - Impact: Bridge operators can now use advanced memory/CPU settings
+
+* ๐ **PT_PORT Support & Official Bridge Naming**
+ - Added `PT_PORT` environment variable for drop-in compatibility
+ - Full compatibility with official bridge ENV naming
+
+* ๐ **Bandwidth Configuration Clarification**
+ - Documented TOR_BANDWIDTH_RATE/BURST โ RelayBandwidthRate/Burst translation
+ - Added Option 1 vs Option 2 explanations in all example configs
+
+* ๐ **Template & Example Updates**
+ - 10 template files updated with bandwidth guidance
+ - 3 example configs updated with PT_PORT and bandwidth options
+```
+
+
+
+
+Click to expand: Example config additions
+
+**relay-bridge.conf:**
+```conf
+# Method 2: Using official Tor Project naming (drop-in compatibility)
+docker run -d \
+ --name tor-bridge \
+ --network host \
+ -e NICKNAME=MyBridge \
+ -e EMAIL="your-email@example.com" \
+ -e OR_PORT=9001 \
+ -e PT_PORT=9002 \
+ ...
+```
+
+**relay-exit.conf & relay-guard.conf:**
+```conf
+# Option 1: Relay-specific bandwidth (recommended for exit relays)
+RelayBandwidthRate 50 MBytes
+RelayBandwidthBurst 100 MBytes
+
+# Option 2: Global bandwidth limits (applies to all Tor traffic)
+# BandwidthRate 50 MBytes
+# BandwidthBurst 100 MBytes
+
+# Note: Use RelayBandwidthRate/Burst for exit relays to avoid limiting
+# directory and other non-relay traffic.
+```
+
+
+
+---
+
+## ๐ฅ Reviewers
+
+**Suggested reviewers:**
+- @r3bo0tbx1 (maintainer)
+
+**For specific areas:**
+- **Documentation:** @r3bo0tbx1
+- **Template accuracy:** @r3bo0tbx1
+
+---
+
+## ๐ Pre-Submission Checklist
+
+### Required
+
+- [x] I have read the [Contributing Guidelines](../CONTRIBUTING.md)
+- [x] I have read the [Code of Conduct](../CODE_OF_CONDUCT.md)
+- [x] My code follows the project's coding standards (documentation only)
+- [x] I have performed a self-review of my documentation
+- [x] My changes generate no new warnings or errors
+- [x] I have updated documentation as needed (comprehensive updates)
+- [x] I have added an entry to CHANGELOG.md under v1.1.1
+- [x] All CI/CD checks pass (documentation changes only)
+
+### Testing
+
+- [x] JSON templates validated with `python3 -m json.tool`
+- [x] YAML templates validated with `docker-compose config -q`
+- [x] Cross-references verified for accuracy
+- [x] Markdown formatting verified (no broken links)
+
+### Optional (but recommended)
+
+- [x] Verified consistency across all 10 updated template files
+- [x] Verified CHANGELOG.md entry is comprehensive and accurate
+- [x] Created PR template for future contributor use
+
+---
+
+## ๐ฌ Additional Notes
+
+### Scope of Changes
+
+**4 commits in this PR:**
+1. `44f371d` - Update example configs with PT_PORT and bandwidth options
+2. `274d087` - Document bandwidth options and PT_PORT in templates and docs
+3. `7a66dd7` - Update CHANGELOG.md with v1.1.1 configuration enhancements
+4. `714c720` - Add comprehensive pull request template
+
+### Why These Changes Matter
+
+1. **OBFS4V Fix Documentation** - Critical fix was implemented in docker-entrypoint.sh but users needed to see it documented in CHANGELOG and examples
+
+2. **PT_PORT Visibility** - Official bridge naming (OR_PORT/PT_PORT) enables drop-in replacement for `thetorproject/obfs4-bridge`, but examples didn't show this - now they do
+
+3. **Bandwidth Clarity** - Users were confused about `RelayBandwidthRate` vs `BandwidthRate` - now every template/example explains the difference:
+ - **RelayBandwidthRate/Burst** - Limits relay traffic only (recommended)
+ - **BandwidthRate/Burst** - Limits ALL Tor traffic (directory, etc.)
+
+4. **PR Template** - Ensures future contributions meet project quality standards with comprehensive checklists
+
+### Ready for v1.1.1 Release
+
+This PR completes the v1.1.1 release documentation:
+- โ
OBFS4V fix documented
+- โ
PT_PORT support documented
+- โ
Bandwidth options clarified
+- โ
All templates updated
+- โ
Examples comprehensive
+- โ
CHANGELOG complete
+- โ
PR template created
+
+**After merge:** Ready to tag v1.1.1 and trigger release workflow.
+
+---
+
+**Thank you for reviewing!** ๐ง
โจ
+
+This PR ensures v1.1.1 users have complete, accurate documentation for all configuration options and improvements.
+
+**Questions?**
+- GitHub Discussions: https://github.com/r3bo0tbx1/tor-guard-relay/discussions
+- Issues: https://github.com/r3bo0tbx1/tor-guard-relay/issues
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 0e01150..9d7b70d 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -244,6 +244,69 @@ jobs:
org.opencontainers.image.created=${{ needs.determine-version.outputs.build_date }}
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
+ # Generate SBOM (Software Bill of Materials)
+ sbom: true
+ provenance: true
+
+ - name: ๐ Generate SBOM (CycloneDX & SPDX)
+ if: needs.determine-version.outputs.is_release == 'true'
+ run: |
+ echo "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
+ echo "๐ Generating Software Bill of Materials (SBOM)"
+ echo "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
+ echo ""
+
+ # Install syft for SBOM generation
+ curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
+
+ VERSION="${{ needs.determine-version.outputs.version }}"
+ IMAGE="${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}:${VERSION}"
+
+ echo "๐ฆ Generating SBOM for image: ${IMAGE}"
+ echo ""
+
+ # Generate CycloneDX JSON
+ echo "๐ Generating CycloneDX JSON format..."
+ syft "${IMAGE}" -o cyclonedx-json > "sbom-cyclonedx-v${VERSION}.json"
+ echo " โ
sbom-cyclonedx-v${VERSION}.json"
+
+ # Generate CycloneDX XML
+ echo "๐ Generating CycloneDX XML format..."
+ syft "${IMAGE}" -o cyclonedx-xml > "sbom-cyclonedx-v${VERSION}.xml"
+ echo " โ
sbom-cyclonedx-v${VERSION}.xml"
+
+ # Generate SPDX JSON
+ echo "๐ Generating SPDX JSON format..."
+ syft "${IMAGE}" -o spdx-json > "sbom-spdx-v${VERSION}.json"
+ echo " โ
sbom-spdx-v${VERSION}.json"
+
+ # Generate SPDX tag-value
+ echo "๐ Generating SPDX tag-value format..."
+ syft "${IMAGE}" -o spdx-tag-value > "sbom-spdx-v${VERSION}.spdx"
+ echo " โ
sbom-spdx-v${VERSION}.spdx"
+
+ # Generate human-readable table
+ echo "๐ Generating human-readable table..."
+ syft "${IMAGE}" -o table > "sbom-table-v${VERSION}.txt"
+ echo " โ
sbom-table-v${VERSION}.txt"
+
+ echo ""
+ echo "โ
SBOM generation complete"
+ echo ""
+ echo "๐ Package Statistics:"
+ jq '.components | length' "sbom-cyclonedx-v${VERSION}.json" | xargs echo " Total packages:"
+
+ - name: ๐ค Upload SBOM Artifacts
+ if: needs.determine-version.outputs.is_release == 'true'
+ uses: actions/upload-artifact@v4
+ with:
+ name: sbom-v${{ needs.determine-version.outputs.version }}
+ path: |
+ sbom-*.json
+ sbom-*.xml
+ sbom-*.spdx
+ sbom-*.txt
+ retention-days: 90
release-notes:
name: ๐ Generate Release Notes
@@ -263,14 +326,19 @@ jobs:
GHCR_IMAGE="${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}"
DOCKERHUB_IMAGE="${{ env.DOCKERHUB_IMAGE_NAME }}"
- echo "## ๐ง
Tor Guard Relay v${VERSION} Release Notes" > release_notes.md
- echo "" >> release_notes.md
+ echo "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
+ echo "๐ Generating Release Notes for v${VERSION}"
+ echo "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
+ echo ""
+
+ # Try to extract from CHANGELOG.md first
+ CHANGELOG_FOUND=0
if [ -f CHANGELOG.md ]; then
- echo "๐ Extracting changelog section for v${VERSION}..."
+ echo "๐ Checking CHANGELOG.md for v${VERSION}..."
awk -v version="${VERSION}" '
- $0 ~ "^##[[:space:]]*(\\[v?" version "\\]|v" version ")([[:space:]]*-.*)?$" {p=1; print; next}
+ $0 ~ "^##[[:space:]]*(\\[v?" version "\\]|v" version ")([[:space:]]*-.*)?$" {p=1; next}
p && /^##[[:space:]]*\[/ && !($0 ~ version) {p=0}
p
' CHANGELOG.md > tmp_notes.txt
@@ -278,21 +346,48 @@ jobs:
sed -i '/^$/N;/^\n$/D' tmp_notes.txt 2>/dev/null || true
if [ -s tmp_notes.txt ]; then
- echo "โ
Extracted changelog for v${VERSION}"
+ echo "โ
Found changelog section for v${VERSION} in CHANGELOG.md"
+ CHANGELOG_FOUND=1
+
+ echo "## ๐ง
Tor Guard Relay v${VERSION}" > release_notes.md
+ echo "" >> release_notes.md
cat tmp_notes.txt >> release_notes.md
else
- echo "โ ๏ธ No changelog section found for v${VERSION}" >> release_notes.md
- echo "โน๏ธ Header may use '[${VERSION}] - YYYY-MM-DD' or 'v${VERSION}'" >> release_notes.md
+ echo "โ ๏ธ No changelog section found for v${VERSION} in CHANGELOG.md"
fi
else
- echo "โ ๏ธ CHANGELOG.md not found. Using commit history instead." >> release_notes.md
- echo "See [commit history](https://github.com/${{ github.repository }}/commits/v${VERSION}) for details." >> release_notes.md
+ echo "โ ๏ธ CHANGELOG.md not found"
fi
+ # Fall back to auto-generated notes from commits
+ if [ "$CHANGELOG_FOUND" = "0" ]; then
+ echo "๐ Auto-generating release notes from commits..."
+
+ if [ -x scripts/release/generate-release-notes.sh ]; then
+ # Use auto-generation script
+ chmod +x scripts/release/generate-release-notes.sh
+ ./scripts/release/generate-release-notes.sh --format github "${VERSION}" > release_notes.md
+ echo "โ
Auto-generated release notes from conventional commits"
+ else
+ # Simple fallback
+ echo "## ๐ง
Tor Guard Relay v${VERSION}" > release_notes.md
+ echo "" >> release_notes.md
+ echo "### Changes" >> release_notes.md
+ echo "" >> release_notes.md
+ git log --pretty=format:"- %s (\`%h\`) by %an" "$(git describe --tags --abbrev=0)..HEAD" >> release_notes.md || echo "- Initial release" >> release_notes.md
+ echo "" >> release_notes.md
+ echo "โ ๏ธ **Note:** Release notes were auto-generated from commit history." >> release_notes.md
+ echo "For detailed changes, see the commit history below." >> release_notes.md
+ echo "โ
Generated basic release notes from commit history"
+ fi
+ fi
+
+ # Append Docker images and SBOM info
echo "" >> release_notes.md
echo "---" >> release_notes.md
echo "" >> release_notes.md
echo "### ๐ณ Docker Images" >> release_notes.md
+ echo "" >> release_notes.md
echo "\`\`\`bash" >> release_notes.md
echo "# From GitHub Container Registry (GHCR)" >> release_notes.md
echo "docker pull ${GHCR_IMAGE}:${VERSION}" >> release_notes.md
@@ -301,8 +396,28 @@ jobs:
echo "docker pull ${DOCKERHUB_IMAGE}:${VERSION}" >> release_notes.md
echo "\`\`\`" >> release_notes.md
echo "" >> release_notes.md
- echo "๐ [View full changelog](https://github.com/${{ github.repository }}/blob/main/CHANGELOG.md)" >> release_notes.md
- echo "๐ [View this release on GitHub](https://github.com/${{ github.repository }}/releases/tag/v${VERSION})" >> release_notes.md
+ echo "### ๐ Software Bill of Materials (SBOM)" >> release_notes.md
+ echo "" >> release_notes.md
+ echo "This release includes comprehensive SBOM files for supply chain security:" >> release_notes.md
+ echo "" >> release_notes.md
+ echo "- **CycloneDX**: JSON and XML formats" >> release_notes.md
+ echo "- **SPDX**: JSON and tag-value formats" >> release_notes.md
+ echo "- **Human-readable**: Table format" >> release_notes.md
+ echo "" >> release_notes.md
+ echo "Download SBOM files from the release assets below." >> release_notes.md
+ echo "" >> release_notes.md
+ echo "---" >> release_notes.md
+ echo "" >> release_notes.md
+ echo "**Full Changelog**: https://github.com/${{ github.repository }}/compare/$(git describe --tags --abbrev=0 2>/dev/null || echo 'v1.0.0')...v${VERSION}" >> release_notes.md
+
+ echo ""
+ echo "โ
Release notes generation complete"
+
+ - name: ๐ฆ Download SBOM Artifacts
+ uses: actions/download-artifact@v4
+ with:
+ name: sbom-v${{ needs.determine-version.outputs.version }}
+ path: ./sbom
- name: ๐ท๏ธ Create GitHub Release
uses: softprops/action-gh-release@v2
@@ -310,5 +425,10 @@ jobs:
tag_name: v${{ needs.determine-version.outputs.version }}
name: "๐ง
Tor Guard Relay v${{ needs.determine-version.outputs.version }}"
body_path: release_notes.md
+ files: |
+ sbom/*.json
+ sbom/*.xml
+ sbom/*.spdx
+ sbom/*.txt
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml
index 9bb712c..d52fac0 100644
--- a/.github/workflows/validate.yml
+++ b/.github/workflows/validate.yml
@@ -421,6 +421,7 @@ jobs:
permissions:
contents: read
security-events: write
+ actions: read # Required for workflow run information
steps:
- name: ๐ฅ Checkout Repository
@@ -435,29 +436,122 @@ jobs:
- name: ๐ฆ Load Docker Image
run: docker load -i /tmp/tor-relay-test.tar
- - name: ๐ Trivy Security Scan
+ - name: ๐ Trivy - Comprehensive Vulnerability Scan
uses: aquasecurity/trivy-action@master
with:
image-ref: 'tor-relay:test'
format: 'sarif'
output: 'trivy-results.sarif'
- severity: 'CRITICAL,HIGH'
+ severity: 'CRITICAL,HIGH,MEDIUM'
+ vuln-type: 'os,library'
+ ignore-unfixed: false
+ scanners: 'vuln,secret,config'
- - name: โฌ๏ธ Upload Trivy Results
+ - name: โฌ๏ธ Upload Trivy Results to GitHub Security
+ id: upload-sarif
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: 'trivy-results.sarif'
continue-on-error: true
+ # Note: SARIF upload requires GitHub Advanced Security for private repos
+ # If upload fails, security results are still available in:
+ # - Human-readable table output (next step)
+ # - JSON artifact (uploaded at end of job)
- - name: ๐ Trivy Vulnerability Report
+ - name: ๐ SARIF Upload Status
+ if: always()
+ run: |
+ if [ "${{ steps.upload-sarif.outcome }}" = "success" ]; then
+ echo "โ
SARIF results successfully uploaded to GitHub Security tab"
+ echo " View at: ${{ github.server_url }}/${{ github.repository }}/security/code-scanning"
+ else
+ echo "โ ๏ธ SARIF upload skipped or failed (this is non-blocking)"
+ echo ""
+ echo "Possible reasons:"
+ echo " โข Private repository without GitHub Advanced Security"
+ echo " โข Insufficient permissions"
+ echo " โข GitHub API rate limiting"
+ echo ""
+ echo "Security scan results are still available in:"
+ echo " โ
Human-readable table output (see steps below)"
+ echo " โ
JSON artifact (trivy-security-report)"
+ fi
+
+ - name: ๐ Trivy - Human Readable Report (Critical & High)
uses: aquasecurity/trivy-action@master
with:
image-ref: 'tor-relay:test'
format: 'table'
severity: 'CRITICAL,HIGH'
+ vuln-type: 'os,library'
+ ignore-unfixed: false
+
+ - name: ๐ Trivy - Full Vulnerability List (All Severities)
+ uses: aquasecurity/trivy-action@master
+ with:
+ image-ref: 'tor-relay:test'
+ format: 'json'
+ output: 'trivy-full-report.json'
+ severity: 'UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL'
+ vuln-type: 'os,library'
+ continue-on-error: true
+
+ - name: ๐ Trivy - Secret Scanning
+ uses: aquasecurity/trivy-action@master
+ with:
+ image-ref: 'tor-relay:test'
+ scanners: 'secret'
+ format: 'table'
+ continue-on-error: true
+
+ - name: โ๏ธ Trivy - Configuration Audit
+ uses: aquasecurity/trivy-action@master
+ with:
+ image-ref: 'tor-relay:test'
+ scanners: 'config'
+ format: 'table'
+ continue-on-error: true
+
+ - name: ๐๏ธ Trivy - Filesystem Scan
+ uses: aquasecurity/trivy-action@master
+ with:
+ scan-type: 'fs'
+ scan-ref: '.'
+ format: 'table'
+ severity: 'CRITICAL,HIGH'
+ scanners: 'vuln,secret,config,license'
+ skip-dirs: '.git,docs,examples,templates'
+ continue-on-error: true
+
+ - name: โฌ๏ธ Upload Full Report Artifact
+ uses: actions/upload-artifact@v5
+ with:
+ name: trivy-security-report
+ path: trivy-full-report.json
+ retention-days: 30
+ continue-on-error: true
+
+ - name: ๐ Generate Security Summary
+ run: |
+ echo "## ๐ก๏ธ Security Scan Results" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "### Scans Performed:" >> $GITHUB_STEP_SUMMARY
+ echo "- โ
Image vulnerability scan (OS packages & libraries)" >> $GITHUB_STEP_SUMMARY
+ echo "- โ
Secret scanning (API keys, tokens, credentials)" >> $GITHUB_STEP_SUMMARY
+ echo "- โ
Configuration audit (Dockerfile, security best practices)" >> $GITHUB_STEP_SUMMARY
+ echo "- โ
Filesystem scan (source code vulnerabilities)" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "### Report Locations:" >> $GITHUB_STEP_SUMMARY
+ echo "- **GitHub Security Tab:** Detailed SARIF results uploaded" >> $GITHUB_STEP_SUMMARY
+ echo "- **Artifacts:** Full JSON report available for download" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ echo "### Scan Coverage:" >> $GITHUB_STEP_SUMMARY
+ echo "- **Severity Levels:** CRITICAL, HIGH, MEDIUM, LOW" >> $GITHUB_STEP_SUMMARY
+ echo "- **Scan Types:** Vulnerabilities, Secrets, Configs, Licenses" >> $GITHUB_STEP_SUMMARY
+ echo "- **Databases:** Alpine, NVD, GitHub Advisory" >> $GITHUB_STEP_SUMMARY
- name: โ
Security Scan Complete
- run: echo "๐ Security scan completed"
+ run: echo "๐ Security scan completed - check GitHub Security tab for detailed results"
test-matrix:
name: ๐งช Test Matrix
diff --git a/README.md b/README.md
index 0afa6fd..7748c00 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@
**A hardened, production-ready Tor relay with built-in diagnostics and monitoring**
-[Quick Start](#-quick-start) โข [Features](#-key-features) โข [Documentation](#-documentation) โข [Tools](#-diagnostic-tools) โข [Contributing](#-contributing)
+[Quick Start](#-quick-start) โข [Features](#-key-features) โข [Documentation](#-documentation) โข [FAQ](docs/FAQ.md) โข [Architecture](docs/ARCHITECTURE.md) โข [Tools](#-diagnostic-tools) โข [Contributing](#-contributing)
@@ -92,7 +92,24 @@ Minimal surface area, roughly 17.1 MB.
- **No monitoring ports** - all diagnostics via `docker exec` commands only
- Use `--network host` for best IPv6 support (Tor recommended practice)
-### Deploy in 30 Seconds
+### Interactive Quick Start (Recommended for Beginners)
+
+**๐ Try our interactive setup script:**
+
+```bash
+# Download and run the quick-start script
+curl -fsSL https://raw.githubusercontent.com/r3bo0tbx1/tor-guard-relay/main/scripts/quick-start.sh -o quick-start.sh
+chmod +x quick-start.sh
+sh ./quick-start.sh
+```
+
+The script will:
+- โ
Guide you through relay type selection (guard, exit, bridge)
+- โ
Collect required information with validation
+- โ
Generate deployment commands or docker-compose.yml
+- โ
Provide next steps and monitoring guidance
+
+### Manual Deployment
**Step 1:** Create your relay configuration (or use our [example](examples/relay.conf)):
@@ -292,10 +309,14 @@ STATUS=$(echo "$HEALTH" | jq -r '.status')
**v1.1.1 includes comprehensive documentation** organized by topic:
### Getting Started
+- **[FAQ](docs/FAQ.md)** - โญ **NEW!** Frequently asked questions with factual answers
+- **[Quick Start Script](scripts/quick-start.sh)** - โญ **NEW!** Interactive relay deployment wizard
+- **[Migration Assistant](scripts/migration/migrate-from-official.sh)** - โญ **NEW!** Automated migration from thetorproject/obfs4-bridge
- **[Deployment Guide](docs/DEPLOYMENT.md)** - Complete installation for Docker CLI, Compose, Cosmos Cloud, and Portainer
- **[Migration Guide](docs/MIGRATION-V1.1.X.md)** - Upgrade to v1.1.1 or migrate from other Tor setups
-### Operations
+### Technical Reference
+- **[Architecture](docs/ARCHITECTURE.md)** - โญ **NEW!** Technical architecture with Mermaid diagrams
- **[Tools Reference](docs/TOOLS.md)** - Complete guide to all 4 diagnostic tools
- **[Monitoring Guide](docs/MONITORING.md)** - External monitoring integration, JSON health API, alerts, and observability
- **[Backup Guide](docs/BACKUP.md)** - Data persistence, recovery, and disaster planning
@@ -311,8 +332,7 @@ STATUS=$(echo "$HEALTH" | jq -r '.status')
- **[Code of Conduct](CODE_OF_CONDUCT.md)** - Community guidelines
- **[Changelog](CHANGELOG.md)** - Version history and changes
-> ๐ก **Tip:** Start with the [Documentation Index](docs/README.md) to find what you need quickly.
-
+> ๐ก **Tip:** Start with the [FAQ](docs/FAQ.md) for quick answers or [Documentation Index](docs/README.md) for complete navigation.
@@ -439,14 +459,22 @@ docker exec tor-relay bridge-line
โ ๐ข Architecture and Design
+> ๐ **NEW:** See the complete [Architecture Documentation](docs/ARCHITECTURE.md) for detailed technical design with Mermaid diagrams covering:
+> - Container lifecycle and initialization flow (6 phases)
+> - ENV compatibility layer and configuration priority
+> - Config generation for guard/exit/bridge modes
+> - OBFS4V security validation (v1.1.1 fix)
+> - Diagnostic tools architecture
+> - Signal handling and graceful shutdown
+
### Why Host Network Mode?
This project uses `--network host` for important reasons:
-โ
**IPv6 Support** - Direct access to host's IPv6 stack
-โ
**No NAT** - Tor binds directly to ports without translation
-โ
**Better Performance** - Eliminates network overhead
-โ
**Tor Recommended** - Follows Tor Project best practices
+โ
**IPv6 Support** - Direct access to host's IPv6 stack
+โ
**No NAT** - Tor binds directly to ports without translation
+โ
**Better Performance** - Eliminates network overhead
+โ
**Tor Recommended** - Follows Tor Project best practices
**Security:** The container still runs as non-root with restricted permissions. Host networking is standard for Tor relays.
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
new file mode 100644
index 0000000..2c5dd11
--- /dev/null
+++ b/docs/ARCHITECTURE.md
@@ -0,0 +1,818 @@
+# Architecture Documentation
+
+**Tor Guard Relay Container** - Technical Architecture & Design
+
+## Table of Contents
+
+1. [Overview](#overview)
+2. [Container Lifecycle](#container-lifecycle)
+3. [Initialization Flow](#initialization-flow)
+4. [Configuration System](#configuration-system)
+5. [ENV Compatibility Layer](#env-compatibility-layer)
+6. [Diagnostic Tools](#diagnostic-tools)
+7. [Directory Structure](#directory-structure)
+8. [Security Model](#security-model)
+9. [Signal Handling](#signal-handling)
+
+---
+
+## Overview
+
+This container implements a production-ready Tor relay with three operational modes:
+- **Guard/Middle**: Directory-enabled relay for traffic routing
+- **Exit**: High-trust relay with customizable exit policies
+- **Bridge**: Censorship-resistant relay with obfs4 transport
+
+**Design Principles:**
+- POSIX sh compatibility (busybox ash, no bash)
+- Minimal dependencies (~20 MB total image)
+- Security-first (non-root, minimal capabilities, strict validation)
+- Multi-architecture (AMD64, ARM64)
+- Production-ready (graceful shutdown, health checks, observability)
+
+---
+
+## Container Lifecycle
+
+```mermaid
+flowchart TD
+ Start([Container Start]) --> Tini[/Tini Init PID 1/]
+ Tini --> Entrypoint[docker-entrypoint.sh]
+
+ Entrypoint --> Phase1[Phase 1: Directories]
+ Phase1 --> Phase2[Phase 2: Permissions]
+ Phase2 --> Phase3[Phase 3: Configuration]
+ Phase3 --> Phase4[Phase 4: Validation]
+ Phase4 --> Phase5[Phase 5: Build Info]
+ Phase5 --> Phase6[Phase 6: Diagnostics Info]
+ Phase6 --> TorStart[Launch Tor Process]
+
+ TorStart --> Running{Container Running}
+ Running -->|Signal: SIGTERM/SIGINT| Trap[Signal Handler]
+ Running -->|Tor Exits| Cleanup
+ Running -->|User Exec| DiagTools[Diagnostic Tools]
+
+ DiagTools -->|status| StatusTool[tools/status]
+ DiagTools -->|health| HealthTool[tools/health]
+ DiagTools -->|fingerprint| FingerprintTool[tools/fingerprint]
+ DiagTools -->|bridge-line| BridgeTool[tools/bridge-line]
+
+ StatusTool --> Running
+ HealthTool --> Running
+ FingerprintTool --> Running
+ BridgeTool --> Running
+
+ Trap --> StopTail[Kill tail -F PID]
+ StopTail --> StopTor[Send SIGTERM to Tor]
+ StopTor --> Wait[Wait for Tor Exit]
+ Wait --> Cleanup[Cleanup & Exit]
+ Cleanup --> End([Container Stop])
+
+ style Start fill:#90EE90
+ style End fill:#FFB6C1
+ style Running fill:#87CEEB
+ style TorStart fill:#FFD700
+ style Trap fill:#FFA500
+```
+
+---
+
+## Initialization Flow
+
+The entrypoint script (`docker-entrypoint.sh`) executes **6 distinct phases** in sequence:
+
+```mermaid
+flowchart TD
+ Banner[Startup Banner] --> P1
+
+ subgraph P1[Phase 1: Directory Structure]
+ P1_1[mkdir -p data/log/run/tmp] --> P1_2[Show disk space]
+ end
+
+ subgraph P2[Phase 2: Permission Hardening]
+ P2_1[chmod 700 data dir] --> P2_2[chmod 755 log dir]
+ end
+
+ subgraph P3[Phase 3: Configuration Setup]
+ P3_1{Mounted config exists?} -->|Yes| P3_2[Use mounted file]
+ P3_1 -->|No| P3_3{ENV vars set?}
+ P3_3 -->|Yes| P3_4[Validate ENV] --> P3_5[Generate config]
+ P3_3 -->|No| P3_6[ERROR: No config]
+ end
+
+ subgraph P4[Phase 4: Configuration Validation]
+ P4_1[Check Tor binary] --> P4_2[Get Tor version]
+ P4_2 --> P4_3[tor --verify-config]
+ P4_3 -->|Invalid| P4_4[ERROR: Bad config]
+ P4_3 -->|Valid| P4_5[Success]
+ end
+
+ subgraph P5[Phase 5: Build Information]
+ P5_1[Read /build-info.txt] --> P5_2[Show version/arch]
+ P5_2 --> P5_3[Show relay mode & config source]
+ end
+
+ subgraph P6[Phase 6: Diagnostic Tools Info]
+ P6_1[List available tools] --> P6_2[Show usage examples]
+ end
+
+ P1 --> P2
+ P2 --> P3
+ P3 --> P4
+ P4 --> P5
+ P5 --> P6
+ P6 --> Launch[Launch Tor]
+
+ style P3_6 fill:#FFB6C1
+ style P4_4 fill:#FFB6C1
+ style Launch fill:#FFD700
+```
+
+### Phase Details
+
+| Phase | Purpose | Key Operations | Error Handling |
+|-------|---------|----------------|----------------|
+| **1** | Directory Setup | `mkdir -p` data/log/run, show disk space | Fail if mkdir fails |
+| **2** | Permissions | `chmod 700` data, `chmod 755` log | Warn on failure (read-only mount) |
+| **3** | Configuration | Priority: mounted > ENV > error | Die if no config source |
+| **4** | Validation | `tor --verify-config` syntax check | Die if invalid config |
+| **5** | Build Info | Show version/arch/mode/source | Warn if missing |
+| **6** | Diagnostics | List available tools | Informational only |
+
+---
+
+## Configuration System
+
+### Configuration Priority
+
+```mermaid
+flowchart TD
+ Start([Configuration Needed]) --> Check1{File exists at /etc/tor/torrc?}
+
+ Check1 -->|Yes| Check2{File not empty?}
+ Check2 -->|Yes| UseMounted[Use Mounted Config]
+ Check2 -->|No| Check3
+
+ Check1 -->|No| Check3{ENV vars set? TOR_NICKNAME and TOR_CONTACT_INFO}
+
+ Check3 -->|Yes| Validate[Validate ENV Values]
+ Validate -->|Valid| Generate[Generate torrc from ENV]
+ Validate -->|Invalid| Error1[ERROR: Invalid ENV]
+
+ Generate --> ModeCheck{TOR_RELAY_MODE?}
+ ModeCheck -->|guard/middle| GenGuard[Generate Guard Config]
+ ModeCheck -->|exit| GenExit[Generate Exit Config]
+ ModeCheck -->|bridge| GenBridge[Generate Bridge Config]
+
+ GenBridge --> OBFS4Check{OBFS4_ENABLE_ADDITIONAL_VARIABLES?}
+ OBFS4Check -->|Yes| ProcessOBFS4V[Process OBFS4V_* vars]
+ OBFS4Check -->|No| UseEnv
+ ProcessOBFS4V --> UseEnv[Use Generated Config]
+
+ GenGuard --> UseEnv
+ GenExit --> UseEnv
+
+ Check3 -->|No| Error2[ERROR: No Config Found]
+
+ UseMounted --> Success([Config Ready])
+ UseEnv --> Success
+ Error1 --> Failure([Container Exit])
+ Error2 --> Failure
+
+ style UseMounted fill:#90EE90
+ style UseEnv fill:#90EE90
+ style Success fill:#90EE90
+ style Error1 fill:#FFB6C1
+ style Error2 fill:#FFB6C1
+ style Failure fill:#FFB6C1
+```
+
+**Code Reference:** `docker-entrypoint.sh` lines 201-220 (phase_3_configuration)
+
+### ENV Variable Validation
+
+All ENV variables are validated before config generation:
+
+```mermaid
+flowchart TD
+ Start([ENV Validation]) --> V1{TOR_RELAY_MODE}
+ V1 --> V1_Check{Value in: guard/middle/exit/bridge?}
+ V1_Check -->|Yes| V2
+ V1_Check -->|No| V1_Fail[ERROR: Invalid mode]
+
+ V2{TOR_NICKNAME} --> V2_1{Length 1-19?}
+ V2_1 -->|Yes| V2_2{Alphanumeric only?}
+ V2_2 -->|Yes| V2_3{Not reserved name?}
+ V2_3 -->|Yes| V3
+ V2_3 -->|No| V2_Fail[ERROR: Reserved name]
+ V2_2 -->|No| V2_Fail
+ V2_1 -->|No| V2_Fail
+
+ V3{TOR_CONTACT_INFO} --> V3_1{Length >= 3?}
+ V3_1 -->|Yes| V3_2{No newlines?}
+ V3_2 -->|Yes| V4
+ V3_2 -->|No| V3_Fail[ERROR: Contains newlines]
+ V3_1 -->|No| V3_Fail
+
+ V4{Ports: ORPORT/DIRPORT/OBFS4_PORT} --> V4_1{Valid integer?}
+ V4_1 -->|Yes| V4_2{Range 1-65535 or DirPort=0?}
+ V4_2 -->|Yes| V4_3{Port less than 1024?}
+ V4_3 -->|Yes| V4_Warn[WARN: Privileged port]
+ V4_3 -->|No| V5
+ V4_Warn --> V5
+ V4_2 -->|No| V4_Fail[ERROR: Out of range]
+ V4_1 -->|No| V4_Fail
+
+ V5{Bandwidth: RATE/BURST} --> V5_1{Valid format?}
+ V5_1 -->|Yes| Success([Validation Passed])
+ V5_1 -->|No| V5_Fail[ERROR: Invalid format]
+
+ V1_Fail --> Failure([Container Exit])
+ V2_Fail --> Failure
+ V3_Fail --> Failure
+ V4_Fail --> Failure
+ V5_Fail --> Failure
+
+ style Success fill:#90EE90
+ style Failure fill:#FFB6C1
+ style V4_Warn fill:#FFD700
+```
+
+**Code Reference:** `docker-entrypoint.sh` lines 115-198 (validate_relay_config)
+
+---
+
+## ENV Compatibility Layer
+
+The container supports **two naming conventions** for maximum compatibility:
+
+```mermaid
+flowchart LR
+ subgraph Official["Official Tor Project Bridge Naming"]
+ NICKNAME["NICKNAME"]
+ EMAIL["EMAIL"]
+ OR_PORT["OR_PORT"]
+ PT_PORT["PT_PORT"]
+ OBFS4V["OBFS4V_*"]
+ end
+
+ subgraph Compat["Compatibility Layer (docker-entrypoint.sh:22-31)"]
+ Map1["Map NICKNAME"]
+ Map2["Map EMAIL"]
+ Map3["Map OR_PORT"]
+ Map4["Map PT_PORT"]
+ Auto["Auto-detect bridge mode"]
+ end
+
+ subgraph Internal["Internal TOR_* Variables"]
+ TOR_NICKNAME["TOR_NICKNAME"]
+ TOR_CONTACT["TOR_CONTACT_INFO"]
+ TOR_ORPORT["TOR_ORPORT"]
+ TOR_OBFS4["TOR_OBFS4_PORT"]
+ TOR_MODE["TOR_RELAY_MODE"]
+ end
+
+ NICKNAME --> Map1 --> TOR_NICKNAME
+ EMAIL --> Map2 --> TOR_CONTACT
+ OR_PORT --> Map3 --> TOR_ORPORT
+ PT_PORT --> Map4 --> TOR_OBFS4
+ PT_PORT --> Auto --> TOR_MODE
+ OBFS4V -.->|Processed later if enabled| TOR_MODE
+
+ TOR_NICKNAME --> Config[Config Generation]
+ TOR_CONTACT --> Config
+ TOR_ORPORT --> Config
+ TOR_OBFS4 --> Config
+ TOR_MODE --> Config
+
+ style Official fill:#E6F3FF
+ style Compat fill:#FFF4E6
+ style Internal fill:#E8F5E9
+ style Config fill:#FFD700
+```
+
+**Mapping Details:**
+- **Map NICKNAME**: `[ -n "${NICKNAME:-}" ] && TOR_NICKNAME="$NICKNAME"`
+- **Map EMAIL**: `[ -n "${EMAIL:-}" ] && TOR_CONTACT_INFO="$EMAIL"`
+- **Map OR_PORT**: `[ -n "${OR_PORT:-}" ] && TOR_ORPORT="$OR_PORT"`
+- **Map PT_PORT**: `[ -n "${PT_PORT:-}" ] && TOR_OBFS4_PORT="$PT_PORT"`
+- **Auto-detect bridge mode**: If `PT_PORT` is set and mode is guard, automatically switch to bridge
+
+### Priority Rules
+
+1. **Official names OVERRIDE Dockerfile defaults** (lines 23-26)
+ - Example: `OR_PORT=443` overrides `ENV TOR_ORPORT=9001`
+2. **PT_PORT auto-detects bridge mode** (lines 29-31)
+ - Setting `PT_PORT` automatically sets `TOR_RELAY_MODE=bridge`
+3. **OBFS4V_\* variables** require `OBFS4_ENABLE_ADDITIONAL_VARIABLES=1`
+ - Whitelist-validated for security (lines 292-343)
+
+**Code Reference:** `docker-entrypoint.sh` lines 8-31 (ENV Compatibility Layer)
+
+---
+
+## Configuration Generation
+
+### Mode-Specific Config Generation
+
+```mermaid
+flowchart TD
+ Start([Generate Config]) --> Base[Write Base Config]
+
+ Base --> Mode{TOR_RELAY_MODE}
+
+ Mode -->|guard/middle| Guard[Add Guard Config]
+ Mode -->|exit| Exit[Add Exit Config]
+ Mode -->|bridge| Bridge[Add Bridge Config]
+
+ subgraph GuardConfig["Guard/Middle Config (lines 247-257)"]
+ G1[DirPort TOR_DIRPORT] --> G2[ExitRelay 0]
+ G2 --> G3[BridgeRelay 0]
+ G3 --> G4{TOR_BANDWIDTH_RATE?}
+ G4 -->|Set| G5[Add RelayBandwidthRate]
+ G4 -->|Not set| G6
+ G5 --> G6{TOR_BANDWIDTH_BURST?}
+ G6 -->|Set| G7[Add RelayBandwidthBurst]
+ G6 -->|Not set| GuardDone
+ G7 --> GuardDone([Guard Config Done])
+ end
+
+ subgraph ExitConfig["Exit Config (lines 260-273)"]
+ E1[DirPort TOR_DIRPORT] --> E2[ExitRelay 1]
+ E2 --> E3[BridgeRelay 0]
+ E3 --> E4[Add Exit Policy]
+ E4 --> E5{TOR_BANDWIDTH_RATE?}
+ E5 -->|Set| E6[Add RelayBandwidthRate]
+ E5 -->|Not set| E7
+ E6 --> E7{TOR_BANDWIDTH_BURST?}
+ E7 -->|Set| E8[Add RelayBandwidthBurst]
+ E7 -->|Not set| ExitDone
+ E8 --> ExitDone([Exit Config Done])
+ end
+
+ subgraph BridgeConfig["Bridge Config (lines 276-343)"]
+ B1[BridgeRelay 1] --> B2[PublishServerDescriptor bridge]
+ B2 --> B3[ServerTransportPlugin obfs4]
+ B3 --> B4[ServerTransportListenAddr obfs4]
+ B4 --> B5[ExtORPort auto]
+ B5 --> B6{TOR_BANDWIDTH_RATE?}
+ B6 -->|Set| B7[Add RelayBandwidthRate]
+ B6 -->|Not set| B8
+ B7 --> B8{TOR_BANDWIDTH_BURST?}
+ B8 -->|Set| B9[Add RelayBandwidthBurst]
+ B8 -->|Not set| B10
+ B9 --> B10{OBFS4_ENABLE_ADDITIONAL_VARIABLES?}
+ B10 -->|Yes| OBFS4[Process OBFS4V_* vars]
+ B10 -->|No| BridgeDone
+ OBFS4 --> BridgeDone([Bridge Config Done])
+ end
+
+ Guard --> GuardConfig
+ Exit --> ExitConfig
+ Bridge --> BridgeConfig
+
+ GuardDone --> Complete([Config Written])
+ ExitDone --> Complete
+ BridgeDone --> Complete
+
+ style Complete fill:#90EE90
+```
+
+**Base Config Includes:** Nickname, ContactInfo, ORPort, SocksPort 0, DataDirectory, Logging
+
+**Code Reference:** `docker-entrypoint.sh` lines 222-350 (generate_config_from_env)
+
+### OBFS4V_* Variable Processing (Bridge Mode)
+
+Security-critical whitelisting to prevent injection attacks:
+
+```mermaid
+flowchart TD
+ Start([OBFS4V Processing]) --> Enable{OBFS4_ENABLE_ADDITIONAL_VARIABLES?}
+ Enable -->|No| Skip([Skip OBFS4V Processing])
+ Enable -->|Yes| GetVars[env | grep '^OBFS4V_']
+
+ GetVars --> Loop{For each OBFS4V_* var}
+
+ Loop --> Strip[Strip OBFS4V_ prefix]
+ Strip --> V1{Key valid? Alphanumeric only}
+ V1 -->|No| Warn1[WARN: Invalid name] --> Next
+ V1 -->|Yes| V2{Value has newlines?}
+
+ V2 -->|Yes| Warn2[WARN: Contains newlines] --> Next
+ V2 -->|No| V3{Value has control chars?}
+
+ V3 -->|Yes| Warn3[WARN: Control characters] --> Next
+ V3 -->|No| Whitelist{Key in whitelist?}
+
+ subgraph WhitelistCheck["Whitelist (lines 325-331)"]
+ WL1[AccountingMax/Start]
+ WL2[Address/AddressDisableIPv6]
+ WL3[Bandwidth*/RelayBandwidth*]
+ WL4[ContactInfo/DirPort/ORPort]
+ WL5[MaxMemInQueues/NumCPUs]
+ WL6[OutboundBindAddress*]
+ WL7[ServerDNS*]
+ end
+
+ Whitelist -->|Yes| Write[Write to torrc]
+ Whitelist -->|No| Warn4[WARN: Not in whitelist]
+
+ Write --> Next{More vars?}
+ Warn4 --> Next
+ Next -->|Yes| Loop
+ Next -->|No| Done([OBFS4V Processing Done])
+
+ style Write fill:#90EE90
+ style Done fill:#90EE90
+ style Warn1 fill:#FFD700
+ style Warn2 fill:#FFD700
+ style Warn3 fill:#FFD700
+ style Warn4 fill:#FFD700
+```
+
+**Security Features (v1.1.1 Fix):**
+- **Newline detection:** `wc -l` instead of busybox-incompatible `grep -qE '[\x00\n\r]'`
+- **Control char detection:** `tr -d '[ -~]'` removes printable chars, leaves control chars
+- **Whitelist enforcement:** Only known-safe torrc options allowed
+- **No code execution:** Values written with `printf`, not `eval`
+
+**Code Reference:** `docker-entrypoint.sh` lines 292-343 (OBFS4V processing)
+
+---
+
+## Diagnostic Tools
+
+Four busybox-only diagnostic tools provide observability:
+
+```mermaid
+flowchart TD
+ User([User: docker exec]) --> Choice{Which tool?}
+
+ Choice -->|status| StatusFlow
+ Choice -->|health| HealthFlow
+ Choice -->|fingerprint| FingerprintFlow
+ Choice -->|bridge-line| BridgeFlow
+
+ subgraph StatusFlow["tools/status - Full Health Report"]
+ S1[Check Tor process running] --> S2[Read bootstrap %]
+ S2 --> S3[Read reachability status]
+ S3 --> S4[Show fingerprint]
+ S4 --> S5[Show recent logs]
+ S5 --> S6[Show resource usage]
+ S6 --> S7[Output with emoji formatting]
+ end
+
+ subgraph HealthFlow["tools/health - JSON API"]
+ H1[Check Tor process] --> H2[Parse log for bootstrap]
+ H2 --> H3[Parse log for errors]
+ H3 --> H4[Get fingerprint if exists]
+ H4 --> H5[Output JSON]
+ end
+
+ subgraph FingerprintFlow["tools/fingerprint - Show Identity"]
+ F1[Read /var/lib/tor/fingerprint] --> F2{File exists?}
+ F2 -->|Yes| F3[Parse fingerprint]
+ F3 --> F4[Output fingerprint]
+ F4 --> F5[Output Tor Metrics URL]
+ F2 -->|No| F6[Warn: Not ready yet]
+ end
+
+ subgraph BridgeFlow["tools/bridge-line - Bridge Sharing"]
+ B1{Bridge mode?} -->|No| B2[Error: Not a bridge]
+ B1 -->|Yes| B3[Read pt_state/obfs4_state.json]
+ B3 --> B4{File exists?}
+ B4 -->|Yes| B5[Parse cert/iat-mode]
+ B5 --> B6[Get public IP]
+ B6 --> B7[Output bridge line]
+ B4 -->|No| B8[Warn: Not ready yet]
+ end
+
+ StatusFlow --> Output1([Human-readable output])
+ HealthFlow --> Output2([JSON output])
+ FingerprintFlow --> Output3([Fingerprint + URL])
+ BridgeFlow --> Output4([Bridge line or error])
+
+ style Output1 fill:#90EE90
+ style Output2 fill:#90EE90
+ style Output3 fill:#90EE90
+ style Output4 fill:#90EE90
+```
+
+**JSON Output Fields:** status, bootstrap_pct, reachable, errors, fingerprint, nickname, uptime_seconds
+
+### Tool Characteristics
+
+| Tool | Purpose | Output Format | Dependencies |
+|------|---------|---------------|--------------|
+| **status** | Full health check | Emoji-rich text | busybox: pgrep, grep, sed, awk, ps |
+| **health** | Monitoring integration | JSON | busybox: pgrep, grep, awk |
+| **fingerprint** | Relay identity | Text + URL | busybox: cat, awk |
+| **bridge-line** | Bridge sharing | obfs4 bridge line | busybox: grep, sed, awk, wget |
+
+**All tools:**
+- Use `#!/bin/sh` (POSIX sh, not bash)
+- No external dependencies (Python, jq, curl, etc.)
+- Numeric sanitization to prevent "bad number" errors
+- Installed at `/usr/local/bin/` (no `.sh` extensions)
+
+**Code Location:** `tools/` directory, copied to `/usr/local/bin/` in Dockerfile
+
+---
+
+## Directory Structure
+
+```mermaid
+graph TD
+ Root[/ Container Root] --> Etc[/etc]
+ Root --> Var[/var]
+ Root --> Run[/run]
+ Root --> Usr[/usr]
+ Root --> Sbin[/sbin]
+
+ Etc --> TorEtc[/etc/tor]
+ TorEtc --> TorRC[torrc]
+ TorEtc -.->|Deleted at build| TorRCSample[torrc.sample]
+
+ Var --> Lib[/var/lib]
+ Lib --> TorData[/var/lib/tor - VOLUME]
+ TorData --> Keys[keys/]
+ TorData --> FingerprintFile[fingerprint]
+ TorData --> PTState[pt_state/]
+
+ Var --> Log[/var/log]
+ Log --> TorLog[/var/log/tor - VOLUME]
+ TorLog --> Notices[notices.log]
+
+ Run --> TorRun[/run/tor]
+ TorRun --> TorPID[tor.pid]
+
+ Usr --> UsrLocal[/usr/local]
+ UsrLocal --> Bin[/usr/local/bin]
+ Bin --> Entrypoint[docker-entrypoint.sh]
+ Bin --> Healthcheck[healthcheck.sh]
+ Bin --> Status[status]
+ Bin --> Health[health]
+ Bin --> Fingerprint[fingerprint]
+ Bin --> BridgeLine[bridge-line]
+
+ Usr --> UsrBin[/usr/bin]
+ UsrBin --> TorBin[tor]
+ UsrBin --> Lyrebird[lyrebird]
+
+ Sbin --> Tini[/sbin/tini]
+
+ Root --> BuildInfo[/build-info.txt]
+
+ style TorData fill:#FFE6E6
+ style TorLog fill:#FFE6E6
+ style TorRC fill:#E6F3FF
+ style Entrypoint fill:#FFD700
+ style Tini fill:#90EE90
+```
+
+### Ownership & Permissions
+
+| Path | Owner | Permissions | Set By |
+|------|-------|-------------|--------|
+| `/var/lib/tor` | tor:tor (100:101) | `700` | Dockerfile + entrypoint |
+| `/var/log/tor` | tor:tor (100:101) | `755` | Dockerfile + entrypoint |
+| `/run/tor` | tor:tor (100:101) | `755` | Dockerfile |
+| `/etc/tor` | tor:tor (100:101) | `755` | Dockerfile |
+| `/etc/tor/torrc` | tor:tor (100:101) | `644` (default) | Generated at runtime |
+
+**Migration Note:** Official `thetorproject/obfs4-bridge` uses Debian `debian-tor` user (UID 101), while this image uses Alpine `tor` user (UID 100). Volume ownership must be fixed when migrating.
+
+---
+
+## Security Model
+
+### Attack Surface Minimization
+
+```mermaid
+flowchart TD
+ subgraph Container["Container Security"]
+ NonRoot[Non-root Execution]
+ Tini[Tini Init]
+ Minimal[Minimal Image]
+ NoCaps[Minimal Capabilities]
+ NoPriv[no-new-privileges]
+ end
+
+ subgraph CodeSec["Code Security"]
+ POSIX[POSIX sh Only]
+ SetE[set -e Exit on error]
+ Validation[Input Validation]
+ NoEval[No eval/exec]
+ Whitelist[OBFS4V Whitelist]
+ end
+
+ subgraph NetworkSec["Network Security"]
+ HostNet[--network host]
+ NoPorts[No Exposed Monitoring]
+ Configurable[Configurable Ports]
+ end
+
+ subgraph FileSec["File System Security"]
+ ReadOnly[Read-only torrc mount]
+ VolPerms[Volume Permissions]
+ NoSecrets[No Hardcoded Secrets]
+ end
+
+ Container --> Secure([Defense in Depth])
+ CodeSec --> Secure
+ NetworkSec --> Secure
+ FileSec --> Secure
+
+ style Secure fill:#90EE90
+```
+
+### Validation Points
+
+1. **Relay Mode** - Must be: guard, middle, exit, or bridge
+2. **Nickname** - 1-19 alphanumeric, not reserved (unnamed/tor/relay/etc)
+3. **Contact Info** - Minimum 3 chars, no newlines (verified with `wc -l`)
+4. **Ports** - Valid integers 1-65535 (or 0 for DirPort), warn on <1024
+5. **Bandwidth** - Valid format: `N MB`, `N GB`, `N KBytes`, etc.
+6. **OBFS4V_\* Keys** - Alphanumeric with underscores only
+7. **OBFS4V_\* Values** - No newlines (`wc -l`), no control chars (`tr -d '[ -~]'`)
+8. **OBFS4V_\* Whitelist** - Only known-safe torrc options
+
+**Code Reference:** `docker-entrypoint.sh` lines 115-198 (validation), 309-321 (OBFS4V security)
+
+---
+
+## Signal Handling
+
+Graceful shutdown ensures relay reputation is maintained:
+
+```mermaid
+sequenceDiagram
+ participant User
+ participant Docker
+ participant Tini as Tini (PID 1)
+ participant Entrypoint as docker-entrypoint.sh
+ participant Tor as Tor Process
+ participant Tail as tail -F Process
+
+ User->>Docker: docker stop
+ Docker->>Tini: SIGTERM
+ Tini->>Entrypoint: SIGTERM (forwarded)
+
+ Note over Entrypoint: trap 'cleanup_and_exit' SIGTERM
+
+ Entrypoint->>Entrypoint: cleanup_and_exit()
+ Entrypoint->>Tail: kill -TERM $TAIL_PID
+ Tail-->>Entrypoint: Process exits
+
+ Entrypoint->>Tor: kill -TERM $TOR_PID
+ Note over Tor: Graceful shutdown - Close circuits, notify directory, save state
+
+ Tor-->>Entrypoint: Process exits (wait)
+ Entrypoint->>Entrypoint: Success: Relay stopped cleanly
+ Entrypoint-->>Tini: exit 0
+ Tini-->>Docker: Container stopped
+ Docker-->>User: Stopped
+
+ Note over User,Tail: Total time 5-10 seconds. Tor gets 10s before SIGKILL
+```
+
+**Signal Flow:**
+1. Docker sends `SIGTERM` to Tini (PID 1)
+2. Tini forwards signal to entrypoint script
+3. Entrypoint trap triggers `cleanup_and_exit()` function
+4. Stop log tail process first (non-blocking)
+5. Send `SIGTERM` to Tor process
+6. Wait for Tor to exit cleanly
+7. Log success message and exit
+
+**Timeout:** Docker waits 10 seconds (default) before sending `SIGKILL`.
+
+**Code Reference:** `docker-entrypoint.sh` lines 51-74 (signal handler)
+
+---
+
+## Build Process
+
+```mermaid
+flowchart LR
+ subgraph Source["Source Files"]
+ Dockerfile[Dockerfile]
+ Scripts[Scripts]
+ Tools[Diagnostic Tools]
+ end
+
+ subgraph Build["Docker Build"]
+ Alpine[Alpine 3.22.2]
+ Install[apk add packages]
+ Copy[Copy scripts & tools]
+ Perms[Set permissions]
+ User[Switch to USER tor]
+ end
+
+ subgraph CI["CI/CD (GitHub Actions)"]
+ Trigger{Trigger Type?}
+ Trigger -->|Weekly| Weekly[Rebuild latest tag]
+ Trigger -->|Git Tag| Release[New release build]
+ Trigger -->|Manual| Manual[workflow_dispatch]
+
+ Weekly --> MultiArch[Multi-arch build]
+ Release --> MultiArch
+ Manual --> MultiArch
+
+ MultiArch --> Push[Push to registries]
+ Release --> GHRelease[Create GitHub Release]
+ end
+
+ Source --> Build
+ Build --> Image[Container Image]
+ Image --> CI
+
+ style Image fill:#FFD700
+ style Push fill:#90EE90
+ style GHRelease fill:#90EE90
+```
+
+**Weekly Rebuild Strategy:**
+- Rebuilds use the **same version tag** as the last release (e.g., `1.1.1`)
+- Overwrites existing image with fresh Alpine packages (security updates)
+- No `-weekly` suffix needed - just updated packages
+- `:latest` always points to most recent release version
+
+**Code Location:** `.github/workflows/release.yml`
+
+---
+
+## Health Check
+
+Docker `HEALTHCHECK` runs every 10 minutes:
+
+```mermaid
+flowchart TD
+ Start([Health Check Timer]) -->|Every 10 min| Script[/usr/local/bin/healthcheck.sh]
+
+ Script --> Check1{Tor process running?}
+ Check1 -->|No| Unhealthy1[Exit 1: UNHEALTHY]
+ Check1 -->|Yes| Check2{Config file exists?}
+
+ Check2 -->|No| Unhealthy2[Exit 1: No config]
+ Check2 -->|Yes| Check3{Config readable?}
+
+ Check3 -->|No| Unhealthy3[Exit 1: Unreadable config]
+ Check3 -->|Yes| Check4{Bootstrap >= 75%?}
+
+ Check4 -->|Unknown| Healthy2[Exit 0: Can't determine]
+ Check4 -->|No| Unhealthy4[Exit 1: Bootstrap stuck]
+ Check4 -->|Yes| Healthy1[Exit 0: HEALTHY]
+
+ Healthy1 --> Status([Container: healthy])
+ Healthy2 --> Status
+ Unhealthy1 --> Status2([Container: unhealthy])
+ Unhealthy2 --> Status2
+ Unhealthy3 --> Status2
+ Unhealthy4 --> Status2
+
+ style Healthy1 fill:#90EE90
+ style Healthy2 fill:#90EE90
+ style Unhealthy1 fill:#FFB6C1
+ style Unhealthy2 fill:#FFB6C1
+ style Unhealthy3 fill:#FFB6C1
+ style Unhealthy4 fill:#FFB6C1
+```
+
+**Health Check Configuration:**
+- **Interval:** 10 minutes
+- **Timeout:** 15 seconds
+- **Start Period:** 30 seconds (grace period for bootstrap)
+- **Retries:** 3 consecutive failures = unhealthy
+
+**Code Location:** `healthcheck.sh`, called by Dockerfile `HEALTHCHECK` directive
+
+---
+
+## References
+
+### Key Files
+
+| File | Purpose | Lines of Code |
+|------|---------|---------------|
+| `Dockerfile` | Container build | 117 |
+| `docker-entrypoint.sh` | Initialization & startup | 478 |
+| `healthcheck.sh` | Docker health check | ~50 |
+| `tools/status` | Human-readable status | ~150 |
+| `tools/health` | JSON health API | ~100 |
+| `tools/fingerprint` | Show relay identity | ~50 |
+| `tools/bridge-line` | Generate bridge line | ~80 |
+
+### External Documentation
+
+- [Tor Project Manual](https://2019.www.torproject.org/docs/tor-manual.html.en) - Complete torrc reference
+- [Alpine Linux](https://alpinelinux.org/) - Base image documentation
+- [Lyrebird](https://gitlab.com/yawning/lyrebird) - obfs4 pluggable transport
+- [Tini](https://github.com/krallin/tini) - Init system for containers
+
+---
+
+**Document Version:** 1.0.0
+**Last Updated:** 2025-01-14
+**Container Version:** v1.1.1
diff --git a/docs/FAQ.md b/docs/FAQ.md
new file mode 100644
index 0000000..eb71c34
--- /dev/null
+++ b/docs/FAQ.md
@@ -0,0 +1,542 @@
+# โ Frequently Asked Questions (FAQ)
+
+Common questions about Tor Guard Relay deployment, configuration, and troubleshooting.
+
+---
+
+## ๐ Table of Contents
+
+- [General](#-general)
+- [Deployment & Configuration](#-deployment--configuration)
+- [Relay Operation](#-relay-operation)
+- [Troubleshooting](#-troubleshooting)
+- [Migration](#-migration)
+- [Security & Legal](#-security--legal)
+
+---
+
+## ๐ General
+
+### What is this project?
+
+**Tor Guard Relay** is a production-ready Docker container for running Tor relays. It supports three relay types:
+- **Guard/Middle relay** - First hop in Tor circuits (default)
+- **Exit relay** - Last hop (requires legal preparation)
+- **Bridge relay** - Helps users bypass censorship (obfs4 support)
+
+Built on Alpine Linux 3.22.2 with a minimal 20MB image size, busybox-only tools, and weekly automated security rebuilds.
+
+### What makes this different from the official Tor images?
+
+| Feature | This Project | Official Images |
+|---------|--------------|-----------------|
+| **Image size** | ~20 MB | ~100+ MB |
+| **Base** | Alpine 3.22.2 | Debian |
+| **Diagnostics** | 4 busybox tools + JSON API | None |
+| **Multi-mode** | Guard/Exit/Bridge in one image | Separate images |
+| **Weekly rebuilds** | โ
Automated | โ Manual |
+| **ENV configuration** | โ
Full support | Limited |
+| **Official bridge naming** | โ
Drop-in compatible | N/A |
+
+### Is this production-ready?
+
+**Yes.** Current version is v1.1.1 (Active/Stable). Used in production with:
+- โ
Security-hardened (32 vulnerabilities fixed in v1.1.1)
+- โ
Non-root execution (tor user, UID 100)
+- โ
Weekly automated rebuilds with latest Tor + Alpine patches
+- โ
Multi-architecture support (AMD64, ARM64)
+- โ
Comprehensive documentation (11 guides)
+
+---
+
+## ๐ Deployment & Configuration
+
+### How do I choose between ENV variables and mounted config file?
+
+**Use ENV variables if:**
+- โ
Simple guard/middle/bridge setup
+- โ
Standard port configuration
+- โ
Basic bandwidth limits
+- โ
Quick deployment is priority
+
+**Use mounted config file if:**
+- โ
Complex exit policies
+- โ
Advanced Tor options not in OBFS4V_* whitelist
+- โ
Multiple ORPort addresses (IPv4 + IPv6)
+- โ
Production deployment requiring full control
+
+**Example ENV-based deployment:**
+```bash
+docker run -d \
+ --name tor-relay \
+ --network host \
+ -e TOR_RELAY_MODE=guard \
+ -e TOR_NICKNAME=MyGuardRelay \
+ -e TOR_CONTACT_INFO="admin@example.com" \
+ -e TOR_ORPORT=9001 \
+ -e TOR_DIRPORT=9030 \
+ -v tor-data:/var/lib/tor \
+ ghcr.io/r3bo0tbx1/onion-relay:latest
+```
+
+**Example mounted config:**
+```bash
+docker run -d \
+ --name tor-relay \
+ --network host \
+ -v /path/to/relay.conf:/etc/tor/torrc:ro \
+ -v tor-data:/var/lib/tor \
+ ghcr.io/r3bo0tbx1/onion-relay:latest
+```
+
+### What's the difference between TOR_* and official bridge naming?
+
+Both work identically - we support two naming conventions for compatibility:
+
+**TOR_* Naming (Our Standard):**
+```bash
+TOR_RELAY_MODE=bridge
+TOR_NICKNAME=MyBridge
+TOR_CONTACT_INFO=admin@example.com
+TOR_ORPORT=9001
+TOR_OBFS4_PORT=9002
+```
+
+**Official Tor Project Naming (Drop-in Compatible):**
+```bash
+NICKNAME=MyBridge
+EMAIL=admin@example.com
+OR_PORT=9001
+PT_PORT=9002 # Auto-detects bridge mode!
+```
+
+**Key difference:** Setting `PT_PORT` automatically enables bridge mode (no need for `TOR_RELAY_MODE=bridge`).
+
+### What's the difference between RelayBandwidthRate and BandwidthRate?
+
+**RelayBandwidthRate/Burst (Recommended):**
+- Limits **relay traffic only** (connections between Tor nodes)
+- Directory requests and other Tor infrastructure traffic NOT limited
+- Best for relays to avoid degrading directory service
+
+**BandwidthRate/Burst (Global):**
+- Limits **ALL Tor traffic** (relay + directory + everything)
+- Can slow down your relay's ability to serve directory information
+- Use only if you need strict total bandwidth control
+
+**ENV variables always use RelayBandwidthRate:**
+```bash
+TOR_BANDWIDTH_RATE="50 MBytes" # โ RelayBandwidthRate in torrc
+TOR_BANDWIDTH_BURST="100 MBytes" # โ RelayBandwidthBurst in torrc
+```
+
+**In mounted config, you choose:**
+```conf
+# Option 1 (recommended):
+RelayBandwidthRate 50 MBytes
+RelayBandwidthBurst 100 MBytes
+
+# Option 2 (global limit):
+BandwidthRate 50 MBytes
+BandwidthBurst 100 MBytes
+```
+
+### Can I use OBFS4V_* variables with spaces (like "1024 MB")?
+
+**Yes**, as of v1.1.1! The busybox regex bug was fixed (docker-entrypoint.sh:309-321).
+
+**This now works:**
+```bash
+OBFS4_ENABLE_ADDITIONAL_VARIABLES=1
+OBFS4V_MaxMemInQueues=1024 MB
+OBFS4V_AddressDisableIPv6=0
+OBFS4V_NumCPUs=4
+```
+
+**Prior to v1.1.1**, spaces caused "dangerous characters" errors. Update to v1.1.1+ if experiencing this issue.
+
+### What ports need to be publicly accessible?
+
+**Guard/Middle Relay:**
+- `TOR_ORPORT` (default: 9001) - **PUBLIC**
+- `TOR_DIRPORT` (default: 9030) - **PUBLIC** (optional, set to 0 to disable)
+
+**Exit Relay:**
+- `TOR_ORPORT` (default: 9001) - **PUBLIC**
+- `TOR_DIRPORT` (default: 9030) - **PUBLIC**
+
+**Bridge Relay:**
+- `TOR_ORPORT` (default: 9001) - **PUBLIC**
+- `TOR_OBFS4_PORT` (default: 9002) - **PUBLIC**
+
+**No monitoring ports exposed** - all diagnostics via `docker exec` only (security by design).
+
+**Firewall example (UFW):**
+```bash
+# Guard relay
+sudo ufw allow 9001/tcp
+sudo ufw allow 9030/tcp
+
+# Bridge relay
+sudo ufw allow 9001/tcp
+sudo ufw allow 9002/tcp
+```
+
+---
+
+## ๐ง
Relay Operation
+
+### Why is my relay not appearing on Tor Metrics?
+
+**Expected timeline:**
+| Milestone | Time | What to Check |
+|-----------|------|---------------|
+| Bootstrap complete | 10-30 min | `docker exec tor-relay status` shows 100% |
+| Appears on metrics | 1-2 hours | Search https://metrics.torproject.org/rs.html |
+| First statistics | 24-48 hours | Bandwidth graphs appear |
+| Guard flag | 8+ days | Relay trusted for entry connections |
+
+**Troubleshooting:**
+1. **Check bootstrap:** `docker exec tor-relay status`
+ - Must show "Bootstrapped 100%"
+2. **Check reachability:** Logs should show "Self-testing indicates your ORPort is reachable"
+3. **Verify firewall:** Ports must be accessible from outside your network
+4. **Check logs:** `docker logs tor-relay | grep -i error`
+5. **Verify fingerprint exists:** `docker exec tor-relay fingerprint`
+
+**Still not showing?**
+- Wait 24-48 hours (Tor network consensus updates slowly)
+- Ensure ExitRelay is 0 for guard relays (not publishing as exit)
+- Check `PublishServerDescriptor 1` in config
+
+### How do I get my bridge line?
+
+**After 24-48 hours**, run:
+```bash
+docker exec tor-bridge bridge-line
+```
+
+**Output format:**
+```
+Bridge obfs4 : cert= iat-mode=0
+```
+
+**Alternative methods:**
+```bash
+# Read directly from file
+docker exec tor-bridge cat /var/lib/tor/pt_state/obfs4_bridgeline.txt
+
+# Search logs
+docker logs tor-bridge | grep "bridge line"
+```
+
+**Share your bridge:**
+- โ
Share with people you trust
+- โ **DO NOT** publish publicly (defeats censorship circumvention)
+- Users can also get bridges from https://bridges.torproject.org/
+
+### Why is my relay using very little bandwidth?
+
+**This is normal for new relays!** Tor network builds trust slowly.
+
+**Typical bandwidth progression:**
+- **Week 1-2:** Almost no traffic (building reputation)
+- **Week 3-4:** Gradual increase as directory consensus includes you
+- **Week 5-8:** Significant traffic increase
+- **8+ days:** May receive Guard flag (massive traffic increase)
+
+**Factors affecting bandwidth:**
+1. **Relay age** - New relays are untrusted
+2. **Uptime percentage** - Must maintain 99%+ for Guard flag
+3. **Relay flags** - Guard, Fast, Stable flags increase usage
+4. **Configured bandwidth** - Tor won't exceed your limits
+5. **Exit policy** - Exit relays typically get more traffic
+
+**Not a bug** - be patient and maintain high uptime!
+
+---
+
+## ๐ง Troubleshooting
+
+### Container won't start - "Permission denied" errors
+
+**Problem:** `Directory /var/lib/tor cannot be read: Permission denied`
+
+**Cause:** Volume ownership mismatch (usually when migrating from Debian-based images)
+
+**Fix:**
+```bash
+# Alpine uses UID 100 (tor user)
+docker run --rm -v tor-data:/data alpine:3.22.2 chown -R 100:101 /data
+
+# Verify fix
+docker run --rm -v tor-data:/data alpine:3.22.2 ls -ldn /data
+# Should show: drwx------ X 100 101 ...
+```
+
+**Prevent in future:** Always use same image consistently (don't switch between official and this image without migration).
+
+### "OBFS4V_MaxMemInQueues: dangerous characters" error
+
+**Problem:** Bridge configuration rejected with this error (values with spaces)
+
+**Cause:** Bug in v1.1.0 and earlier - busybox regex incompatibility
+
+**Fix:** **Update to v1.1.1+**
+```bash
+docker pull ghcr.io/r3bo0tbx1/onion-relay:latest
+docker stop tor-bridge
+docker rm tor-bridge
+docker run ... # Recreate with new image
+```
+
+**Verify fix:**
+```bash
+docker exec tor-bridge cat /build-info.txt
+# Should show: Version: 1.1.1 or later
+
+# Verify OBFS4V variables work
+docker exec tor-bridge cat /etc/tor/torrc | grep MaxMemInQueues
+# Should show: MaxMemInQueues 1024 MB (if variable was set)
+```
+
+### Why does TOR_RELAY_MODE say "guard" when I set PT_PORT?
+
+**Problem:** Log shows guard mode but you expected bridge mode
+
+**Cause:** Running old image (< v1.1.1) without PT_PORT auto-detection
+
+**Fix:** Update to v1.1.1+ where PT_PORT automatically enables bridge mode:
+```bash
+# v1.1.1+ auto-detects bridge mode from PT_PORT
+docker run -d \
+ --name tor-bridge \
+ --network host \
+ -e PT_PORT=9002 \ # Auto-enables bridge mode!
+ -e NICKNAME=MyBridge \
+ -e EMAIL=admin@example.com \
+ -v tor-data:/var/lib/tor \
+ ghcr.io/r3bo0tbx1/onion-relay:latest
+```
+
+**Verify:**
+```bash
+docker logs tor-bridge | grep "Relay mode"
+# Should show: ๐ฏ Relay mode: bridge
+```
+
+### How do I restart vs recreate a container?
+
+**CRITICAL:** Many issues arise from restarting old containers instead of recreating with new image.
+
+**Wrong (uses old image):**
+```bash
+docker stop tor-relay
+docker pull ghcr.io/r3bo0tbx1/onion-relay:latest # Downloads new image
+docker start tor-relay # โ Still uses OLD image!
+```
+
+**Correct (uses new image):**
+```bash
+docker stop tor-relay
+docker rm tor-relay # Remove old container
+docker pull ghcr.io/r3bo0tbx1/onion-relay:latest # Download new image
+docker run -d --name tor-relay ... # โ
New container with new image
+```
+
+**Verify which image container is using:**
+```bash
+# Get container's image ID
+docker inspect tor-relay --format='{{.Image}}'
+
+# Get current image ID
+docker images ghcr.io/r3bo0tbx1/onion-relay:latest --format='{{.ID}}'
+
+# IDs must match!
+```
+
+---
+
+## ๐ Migration
+
+### How do I migrate from thetorproject/obfs4-bridge?
+
+**Official image โ This image migration:**
+
+1. **Backup your data:**
+```bash
+docker run --rm -v obfs4-data:/data -v /tmp:/backup \
+ alpine tar czf /backup/tor-backup.tar.gz /data
+```
+
+2. **Fix UID/GID (REQUIRED):**
+```bash
+# Official image: UID 101 (debian-tor)
+# Our image: UID 100 (tor)
+docker run --rm -v obfs4-data:/data alpine:3.22.2 chown -R 100:101 /data
+```
+
+3. **Update configuration:**
+```bash
+# Change ONLY the image name - keep same ENV variables!
+# Old:
+# image: thetorproject/obfs4-bridge:latest
+
+# New:
+image: ghcr.io/r3bo0tbx1/onion-relay:latest
+```
+
+4. **Recreate container:**
+```bash
+docker stop obfs4-bridge
+docker rm obfs4-bridge
+docker run -d \
+ --name obfs4-bridge \
+ --network host \
+ -e OR_PORT=9001 \
+ -e PT_PORT=9002 \
+ -e EMAIL=admin@example.com \
+ -e NICKNAME=MyBridge \
+ -v obfs4-data:/var/lib/tor \ # Same volume!
+ ghcr.io/r3bo0tbx1/onion-relay:latest
+```
+
+5. **Verify fingerprint unchanged:**
+```bash
+docker exec obfs4-bridge fingerprint
+# Must match your old fingerprint!
+```
+
+**See:** [MIGRATION.md](MIGRATION.md) for complete guide
+
+### How do I upgrade from v1.1.0 to v1.1.1?
+
+**Guard/Exit relays (no changes required):**
+```bash
+docker pull ghcr.io/r3bo0tbx1/onion-relay:latest
+docker stop tor-relay
+docker rm tor-relay
+docker run -d --name tor-relay ... # Same config
+```
+
+**Bridge relays (OBFS4V fix applies):**
+- Same process as above
+- OBFS4V_* variables with spaces now work correctly
+- No config changes needed
+
+**Verify upgrade:**
+```bash
+docker exec tor-relay cat /build-info.txt
+# Should show: Version: 1.1.1
+
+docker exec tor-relay fingerprint
+# Verify fingerprint unchanged
+```
+
+---
+
+## ๐ Security & Legal
+
+### Is it legal to run a Tor relay?
+
+**Generally yes**, but depends on jurisdiction and relay type:
+
+**Guard/Middle Relay:**
+- โ
Legal in most countries
+- โ
Traffic is encrypted (you can't see content)
+- โ
You're NOT the exit point
+- โ ๏ธ Inform your ISP (recommended)
+
+**Exit Relay:**
+- โ ๏ธ **Legal but complex** - requires preparation
+- โ ๏ธ Your IP associated with exit traffic
+- โ ๏ธ You WILL receive abuse complaints
+- โ ๏ธ Read [docs/LEGAL.md](LEGAL.md) **BEFORE** running exit relay
+
+**Bridge Relay:**
+- โ
Legal in most countries
+- โ
Helps censored users
+- โ
Not published in main directory
+- โ ๏ธ Check local laws on censorship circumvention tools
+
+**Resources:**
+- [EFF Tor Legal FAQ](https://community.torproject.org/relay/community-resources/eff-tor-legal-faq/)
+- [Tor Project Legal Resources](https://community.torproject.org/relay/community-resources/)
+- This project's [LEGAL.md](LEGAL.md)
+
+### How secure is this container?
+
+**Security features:**
+- โ
Non-root execution (tor user, UID 100, GID 101)
+- โ
Ultra-minimal image (~20 MB, Alpine 3.22.2)
+- โ
Busybox-only (no bash, python, or unnecessary binaries)
+- โ
No exposed monitoring ports (diagnostics via `docker exec` only)
+- โ
Weekly automated security rebuilds (Sundays 18:30 UTC)
+- โ
Tini init for proper signal handling
+- โ
Security-first template configurations (no-new-privileges, minimal caps)
+- โ
Comprehensive security audit (32 vulnerabilities fixed in v1.1.1)
+
+**Security updates:**
+- **Weekly rebuilds** pull latest Alpine + Tor patches
+- **Same version tag** overwritten with updated packages (e.g., :1.1.1)
+- **No package pinning** - always latest stable Tor from Alpine edge
+
+**Verify security:**
+```bash
+# Check build info
+docker exec tor-relay cat /build-info.txt
+
+# Run security validation
+./scripts/utilities/security-validation-tests.sh
+```
+
+### What data does the relay store?
+
+**Persistent data in `/var/lib/tor`:**
+- **Identity keys** - Your relay's cryptographic identity (CRITICAL - don't lose!)
+- **State file** - Tor's runtime state
+- **Cached directory** - Tor network consensus
+- **Bridge credentials** - obfs4 state (bridge mode only)
+
+**Logs in `/var/log/tor`:**
+- **notices.log** - Tor operational logs
+- **Rotated automatically** - No unbounded growth
+
+**Container does NOT store:**
+- โ User traffic content (encrypted)
+- โ Websites visited through relay
+- โ User IP addresses
+- โ Browsing history
+
+**Backup requirements:**
+- **MUST backup:** `/var/lib/tor` (contains identity keys)
+- **Optional:** Logs (for debugging only)
+
+**See:** [BACKUP.md](BACKUP.md) for backup strategies
+
+---
+
+## ๐ก Additional Resources
+
+### Where can I find more help?
+
+- **Documentation:** [docs/](../)
+- **GitHub Issues:** https://github.com/r3bo0tbx1/tor-guard-relay/issues
+- **GitHub Discussions:** https://github.com/r3bo0tbx1/tor-guard-relay/discussions
+- **Tor Project Relay Guide:** https://community.torproject.org/relay/
+- **Tor Metrics:** https://metrics.torproject.org/
+
+### How can I contribute?
+
+- ๐ง
**Run a relay** - Strengthen the Tor network
+- ๐ **Report bugs** - Open issues on GitHub
+- ๐ **Improve docs** - Fix typos, add examples, translate
+- ๐ป **Submit code** - Bug fixes, features, optimizations
+- โญ **Star the repo** - Show support!
+
+See [CONTRIBUTING.md](../CONTRIBUTING.md) for guidelines.
+
+---
+
+**Last Updated:** November 2025 (v1.1.1)
+**Maintained by:** [@r3bo0tbx1](https://github.com/r3bo0tbx1)
diff --git a/scripts/migration/README.md b/scripts/migration/README.md
new file mode 100644
index 0000000..9169187
--- /dev/null
+++ b/scripts/migration/README.md
@@ -0,0 +1,413 @@
+# Migration Scripts
+
+This directory contains automated migration tools for upgrading from other Tor relay images to `r3bo0tbx1/onion-relay`.
+
+## migrate-from-official.sh
+
+Automated migration assistant for users moving from the official `thetorproject/obfs4-bridge` image to this project.
+
+### What It Does
+
+1. **Detects existing setup** - Finds your running official bridge container and extracts configuration
+2. **Backs up data** - Creates tar.gz backup of your Tor data volume (keys, state)
+3. **Fixes UID mismatch** - Corrects ownership from Debian (UID 101) to Alpine (UID 100)
+4. **Deploys new container** - Creates new container with same configuration
+5. **Validates migration** - Verifies fingerprint preservation and bridge functionality
+6. **Provides next steps** - Clear guidance on monitoring and verification
+
+### Why You Need This
+
+The official `thetorproject/obfs4-bridge` image uses:
+- **Base:** Debian
+- **User:** debian-tor (UID 101)
+
+This project uses:
+- **Base:** Alpine Linux
+- **User:** tor (UID 100)
+
+Without fixing the UID mismatch, you'll get permission errors:
+```
+Directory /var/lib/tor cannot be read: Permission denied
+```
+
+### Quick Start
+
+```bash
+# Interactive migration (recommended)
+./scripts/migration/migrate-from-official.sh
+
+# The script will:
+# 1. Detect your existing container
+# 2. Extract NICKNAME, EMAIL, OR_PORT, PT_PORT
+# 3. Prompt for confirmation before each step
+# 4. Create backup in ~/tor-backups/
+# 5. Fix ownership automatically
+# 6. Deploy new container
+# 7. Validate fingerprint matches
+```
+
+### Manual Mode
+
+If you don't have a running official container, the script supports manual configuration:
+
+```bash
+./scripts/migration/migrate-from-official.sh
+
+# When prompted "No thetorproject/obfs4-bridge container found":
+# Choose "Continue with manual configuration"
+
+# You'll be asked for:
+# - Volume name (e.g., obfs4-data)
+# - New container name (default: tor-bridge)
+# - OR_PORT (default: 9001)
+# - PT_PORT (default: 9002)
+# - NICKNAME
+# - EMAIL
+```
+
+### What Gets Preserved
+
+โ
**Identity keys** - Your relay's cryptographic identity
+โ
**Fingerprint** - Relay reputation and statistics
+โ
**Bridge credentials** - obfs4 state and bridge line
+โ
**Tor state** - Bootstrap state and consensus cache
+
+### Migration Checklist
+
+**Before running:**
+- [ ] Note your current fingerprint: `docker exec cat /var/lib/tor/fingerprint`
+- [ ] Save your bridge line: `docker exec cat /var/lib/tor/pt_state/obfs4_bridgeline.txt`
+- [ ] Verify volume name: `docker inspect --format='{{range .Mounts}}{{.Name}}{{end}}'`
+- [ ] Ensure sufficient disk space for backup (~100 MB typical)
+
+**After migration:**
+- [ ] Verify fingerprint matches old fingerprint
+- [ ] Check bootstrap progress: `docker exec tor-bridge status`
+- [ ] Verify bridge line: `docker exec tor-bridge bridge-line`
+- [ ] Check Tor Metrics (may take 24h): https://metrics.torproject.org/rs.html#search/YOUR_FINGERPRINT
+- [ ] Monitor logs for 24 hours: `docker logs -f tor-bridge`
+- [ ] Keep backup for at least 1 week
+
+### Script Features
+
+**Automatic Detection:**
+- Finds `thetorproject/obfs4-bridge` containers
+- Extracts ENV variables (NICKNAME, EMAIL, OR_PORT, PT_PORT)
+- Detects volume mounts automatically
+- Reads current fingerprint from volume
+
+**Safety Features:**
+- Creates backup before any changes
+- Validates each step before proceeding
+- Preserves old container (stopped, not deleted)
+- Provides rollback instructions
+- Confirms before destructive operations
+
+**Validation:**
+- Checks volume ownership (100:101)
+- Waits for container startup (60s timeout)
+- Waits for Tor bootstrap (300s timeout)
+- Compares old vs new fingerprint
+- Validates bridge line generation
+- Runs health checks
+
+**User Experience:**
+- Color-coded output (errors, warnings, success)
+- Progress indicators with step numbers
+- Clear next steps after completion
+- Helpful error messages with troubleshooting
+
+### Example Output
+
+```
+โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ Migration Assistant: Official Tor Bridge โ Onion Relay
+โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+This script automates migration from:
+ Source: thetorproject/obfs4-bridge (Debian, UID 101)
+ Target: r3bo0tbx1/onion-relay (Alpine, UID 100)
+
+
+โโโ Step 1: Pre-flight Checks
+โ
Docker is available
+
+โโโ Step 2: Detect Existing Setup
+โ
Found container: obfs4-bridge
+โน Configuration detected:
+โน Nickname: MyBridge
+โน Email: admin@example.com
+โน OR Port: 9001
+โน PT Port: 9002
+โน Volume mounts:
+โน obfs4-data โ /var/lib/tor
+โน Checking current fingerprint...
+โ
Current fingerprint: 1234567890ABCDEF1234567890ABCDEF12345678
+
+โ Proceed with migration? [y/N]: y
+
+โโโ Step 3: Backup Current Data
+โ Create backup of volume 'obfs4-data'? [y/N]: y
+โน Creating backup of volume 'obfs4-data'...
+โ
Backup created: /home/user/tor-backups/tor-backup-20250114-120000.tar.gz
+
+โโโ Step 4: Stop Old Container
+โน Stopping container: obfs4-bridge
+โ
Container stopped
+โ Remove old container 'obfs4-bridge'? (keeps volumes) [y/N]: y
+โ
Container removed
+
+โโโ Step 5: Fix Volume Ownership
+โน Current ownership: 101:101
+โน Fixing ownership: debian-tor (101) โ tor (100)...
+โน Current ownership: 101:101
+โน New ownership: 100:101
+โ
Ownership fixed successfully
+
+โโโ Step 6: Deploy New Container
+โน Deploying new container: tor-bridge
+โน Image: r3bo0tbx1/onion-relay:latest
+โน Running command:
+ docker run -d \
+ --name tor-bridge \
+ --network host \
+ --restart unless-stopped \
+ ...
+โ
Container started
+
+โโโ Step 7: Wait for Container to Start
+โน Waiting for container to start (max 60s)...
+โ
Container is running
+
+โโโ Step 8: Wait for Tor Bootstrap
+โน Waiting for Tor to bootstrap (max 300s)...
+โน Bootstrap progress: 5%
+โน Bootstrap progress: 25%
+โน Bootstrap progress: 75%
+โน Bootstrap progress: 90%
+โ
Tor fully bootstrapped (100%)
+
+โโโ Step 9: Validate Migration
+โน Checking fingerprint...
+โ
Fingerprint: 1234567890ABCDEF1234567890ABCDEF12345678
+โ
Fingerprint matches (relay identity preserved)
+โน Checking bridge line...
+โ
Bridge line generated successfully
+โน Bridge line:
+ obfs4 1.2.3.4:9002 1234567890ABCDEF1234567890ABCDEF12345678 cert=... iat-mode=0
+โน Checking health status...
+โ
Health check passed
+
+โโโ Migration Complete!
+
+โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+โ
Migration Successful
+โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+Next Steps:
+
+1๏ธโฃ Check container status:
+ docker exec tor-bridge status
+
+2๏ธโฃ View logs:
+ docker logs -f tor-bridge
+
+3๏ธโฃ Get bridge line (after bootstrap complete):
+ docker exec tor-bridge bridge-line
+
+4๏ธโฃ Check fingerprint on Tor Metrics:
+ https://metrics.torproject.org/rs.html#details/1234567890ABCDEF1234567890ABCDEF12345678
+
+5๏ธโฃ Monitor resource usage:
+ docker stats tor-bridge
+```
+
+### Troubleshooting
+
+#### Migration Failed at Ownership Fix
+
+**Symptom:** "Failed to fix ownership" error
+
+**Cause:** Volume is mounted as read-only or insufficient permissions
+
+**Solution:**
+```bash
+# Manually fix ownership
+docker run --rm -v :/data alpine:3.22.2 chown -R 100:101 /data
+
+# Verify
+docker run --rm -v :/data alpine:3.22.2 ls -ldn /data
+# Should show: drwx------ 5 100 101 ...
+```
+
+#### Fingerprint Mismatch After Migration
+
+**Symptom:** Old and new fingerprints don't match
+
+**Cause:** Identity keys were not preserved or volume mount incorrect
+
+**Solution:**
+```bash
+# Check if keys exist in volume
+docker run --rm -v :/data alpine:3.22.2 ls -la /data/keys/
+
+# Should see:
+# - secret_id_key
+# - ed25519_master_id_public_key
+# - ed25519_master_id_secret_key
+# - ed25519_signing_cert
+# - ed25519_signing_secret_key
+
+# If keys are missing, restore from backup:
+docker run --rm -v :/data -v /path/to:/backup alpine:3.22.2 \
+ tar xzf /backup/tor-backup-*.tar.gz -C /data
+
+# Fix ownership again
+docker run --rm -v :/data alpine:3.22.2 chown -R 100:101 /data
+
+# Recreate container
+docker rm -f tor-bridge
+./scripts/migration/migrate-from-official.sh
+```
+
+#### Bootstrap Timeout
+
+**Symptom:** "Timeout waiting for bootstrap completion"
+
+**Cause:** Tor network is slow or connectivity issues
+
+**Solution:**
+```bash
+# Check if Tor is actually running
+docker exec tor-bridge pgrep tor
+
+# Check logs for errors
+docker logs tor-bridge 2>&1 | tail -50
+
+# Manual bootstrap check
+docker exec tor-bridge health | jq .
+
+# Wait longer (Tor can take 5-10 minutes on first run)
+watch -n5 'docker exec tor-bridge health | jq .bootstrap_percent'
+```
+
+#### Container Exits Immediately
+
+**Symptom:** Container starts but immediately exits
+
+**Cause:** Configuration error or volume permission issues
+
+**Solution:**
+```bash
+# Check container logs
+docker logs tor-bridge
+
+# Common issues:
+# 1. "Directory /var/lib/tor cannot be read: Permission denied"
+# โ Run ownership fix again
+
+# 2. "Invalid configuration"
+# โ Check ENV variables: docker inspect tor-bridge --format='{{range .Config.Env}}{{println .}}{{end}}'
+
+# 3. "Could not bind to 0.0.0.0:9001: Address already in use"
+# โ Change OR_PORT or stop conflicting service
+```
+
+#### Bridge Line Not Generated
+
+**Symptom:** `docker exec tor-bridge bridge-line` returns empty
+
+**Cause:** Bootstrap not complete or obfs4 not configured
+
+**Solution:**
+```bash
+# Check bootstrap status
+docker exec tor-bridge status
+
+# Should show "Bootstrap: 100% (done)"
+
+# Check if lyrebird (obfs4) is running
+docker exec tor-bridge pgrep lyrebird
+
+# Check obfs4 state file
+docker exec tor-bridge cat /var/lib/tor/pt_state/obfs4_state.json
+
+# Wait 5 minutes after 100% bootstrap, then try again
+docker exec tor-bridge bridge-line
+```
+
+### Rollback Procedure
+
+If migration fails or you want to revert:
+
+```bash
+# 1. Stop new container
+docker stop tor-bridge
+docker rm tor-bridge
+
+# 2. Restore from backup (if needed)
+docker run --rm \
+ -v :/data \
+ -v /path/to/backup:/backup \
+ alpine:3.22.2 sh -c 'rm -rf /data/* && tar xzf /backup/tor-backup-*.tar.gz -C /data'
+
+# 3. Fix ownership back to Debian UID 101 (if returning to official image)
+docker run --rm -v :/data alpine:3.22.2 chown -R 101:101 /data
+
+# 4. Restart old container
+docker start obfs4-bridge
+
+# OR deploy official image again
+docker run -d \
+ --name obfs4-bridge \
+ --network host \
+ -e NICKNAME="MyBridge" \
+ -e EMAIL="admin@example.com" \
+ -e OR_PORT=9001 \
+ -e PT_PORT=9002 \
+ -v :/var/lib/tor \
+ thetorproject/obfs4-bridge:latest
+```
+
+### Security Notes
+
+**The script is safe because:**
+- โ
Stops containers before modifying data
+- โ
Creates backups before making changes
+- โ
Validates each step before proceeding
+- โ
Never deletes volumes (only fixes ownership)
+- โ
Preserves old container until you confirm success
+- โ
Uses official Alpine image for ownership fixes
+
+**What to verify after migration:**
+- Container is running: `docker ps | grep tor-bridge`
+- Tor is bootstrapped: `docker exec tor-bridge status`
+- Fingerprint unchanged: Compare with your saved fingerprint
+- Bridge line works: Test obfs4 connection from client
+- Logs are clean: `docker logs tor-bridge` should show no errors
+- Tor Metrics updated (24h): Check bridge appears on metrics.torproject.org
+
+### Additional Resources
+
+- **Main Documentation:** [../../docs/MIGRATION-V1.1.X.md](../../docs/MIGRATION-V1.1.X.md)
+- **FAQ:** [../../docs/FAQ.md](../../docs/FAQ.md) - See "How do I migrate from the official Tor Project bridge image?"
+- **Architecture:** [../../docs/ARCHITECTURE.md](../../docs/ARCHITECTURE.md) - Understanding UID/GID differences
+- **Deployment Guide:** [../../docs/DEPLOYMENT.md](../../docs/DEPLOYMENT.md) - Post-migration deployment options
+
+### Support
+
+If you encounter issues:
+
+1. **Check logs:** `docker logs tor-bridge`
+2. **Run diagnostics:** `docker exec tor-bridge status`
+3. **Verify volume:** `docker run --rm -v :/data alpine ls -la /data`
+4. **Check FAQ:** See docs/FAQ.md for common issues
+5. **Review architecture:** See docs/ARCHITECTURE.md for technical details
+
+### Contributing
+
+Found a bug or have a suggestion? Open an issue or pull request on GitHub.
+
+### License
+
+Same license as the main project (see repository root LICENSE file).
diff --git a/scripts/migration/migrate-from-official.sh b/scripts/migration/migrate-from-official.sh
new file mode 100644
index 0000000..376b425
--- /dev/null
+++ b/scripts/migration/migrate-from-official.sh
@@ -0,0 +1,635 @@
+#!/bin/sh
+# Migration Assistant: thetorproject/obfs4-bridge โ r3bo0tbx1/onion-relay
+# Automates UID fix (Debian 101 โ Alpine 100) and validates migration
+
+set -e
+
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+# Color Output (POSIX-compatible)
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+CYAN='\033[0;36m'
+BOLD='\033[1m'
+NC='\033[0m' # No Color
+
+log() { printf "${BLUE}โน${NC} %s\n" "$*"; }
+success() { printf "${GREEN}โ
${NC} %s\n" "$*"; }
+warn() { printf "${YELLOW}โ ${NC} %s\n" "$*"; }
+error() { printf "${RED}โ${NC} %s\n" "$*"; }
+step() { printf "\n${CYAN}${BOLD}โโโ %s${NC}\n" "$*"; }
+die() { error "$*"; exit 1; }
+
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+# Utility Functions
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+# Prompt for confirmation
+confirm() {
+ prompt="$1"
+ printf "${YELLOW}โ %s [y/N]: ${NC}" "$prompt"
+ read -r response
+ case "$response" in
+ [yY][eE][sS]|[yY]) return 0 ;;
+ *) return 1 ;;
+ esac
+}
+
+# Check if Docker is available
+check_docker() {
+ if ! command -v docker >/dev/null 2>&1; then
+ die "Docker is not installed or not in PATH"
+ fi
+
+ if ! docker info >/dev/null 2>&1; then
+ die "Docker daemon is not running or permission denied"
+ fi
+}
+
+# Sanitize numeric values
+sanitize_num() {
+ v=$(printf '%s' "$1" | tr -cd '0-9')
+ [ -z "$v" ] && v=0
+ printf '%s' "$v"
+}
+
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+# Detection Functions
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+# Detect official Tor Project bridge containers
+detect_official_containers() {
+ docker ps -a --filter "ancestor=thetorproject/obfs4-bridge" --format "{{.Names}}" 2>/dev/null | head -1
+}
+
+# Extract environment variables from container
+get_container_env() {
+ container_name="$1"
+ env_var="$2"
+ docker inspect "$container_name" --format "{{range .Config.Env}}{{println .}}{{end}}" 2>/dev/null | \
+ grep "^${env_var}=" | cut -d= -f2-
+}
+
+# Extract volume mounts from container
+get_container_volumes() {
+ container_name="$1"
+ docker inspect "$container_name" --format '{{range .Mounts}}{{if eq .Type "volume"}}{{.Name}}:{{.Destination}}{{println}}{{end}}{{end}}' 2>/dev/null
+}
+
+# Get fingerprint from volume
+get_fingerprint_from_volume() {
+ volume_name="$1"
+ docker run --rm -v "${volume_name}:/data:ro" alpine:3.22.2 sh -c \
+ 'if [ -f /data/fingerprint ]; then cat /data/fingerprint; elif [ -f /data/keys/ed25519_master_id_public_key ]; then echo "Keys exist but fingerprint not yet generated"; else echo "NOT_FOUND"; fi' 2>/dev/null
+}
+
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+# Validation Functions
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+# Check volume ownership
+check_volume_ownership() {
+ volume_name="$1"
+ ownership=$(docker run --rm -v "${volume_name}:/data:ro" alpine:3.22.2 stat -c '%u:%g' /data 2>/dev/null)
+ printf '%s' "$ownership"
+}
+
+# Wait for container to be healthy
+wait_for_healthy() {
+ container_name="$1"
+ timeout="${2:-120}"
+
+ log "Waiting for container to start (max ${timeout}s)..."
+
+ counter=0
+ while [ $counter -lt $timeout ]; do
+ status=$(docker inspect "$container_name" --format '{{.State.Status}}' 2>/dev/null || echo "not_found")
+
+ if [ "$status" = "running" ]; then
+ success "Container is running"
+ return 0
+ elif [ "$status" = "exited" ] || [ "$status" = "dead" ]; then
+ error "Container exited unexpectedly"
+ docker logs "$container_name" 2>&1 | tail -20
+ return 1
+ fi
+
+ sleep 2
+ counter=$((counter + 2))
+ done
+
+ error "Timeout waiting for container to start"
+ return 1
+}
+
+# Wait for Tor bootstrap
+wait_for_bootstrap() {
+ container_name="$1"
+ timeout="${2:-300}"
+
+ log "Waiting for Tor to bootstrap (max ${timeout}s)..."
+
+ counter=0
+ last_progress=""
+
+ while [ $counter -lt $timeout ]; do
+ # Try to get bootstrap progress from health tool
+ bootstrap_output=$(docker exec "$container_name" health 2>/dev/null || echo "{}")
+ bootstrap_pct=$(printf '%s' "$bootstrap_output" | grep -o '"bootstrap_percent":[0-9]*' | cut -d: -f2 | head -1)
+ bootstrap_pct=$(sanitize_num "$bootstrap_pct")
+
+ if [ "$bootstrap_pct" -ge 100 ]; then
+ success "Tor fully bootstrapped (100%)"
+ return 0
+ elif [ "$bootstrap_pct" -gt 0 ]; then
+ if [ "$bootstrap_pct" != "$last_progress" ]; then
+ log "Bootstrap progress: ${bootstrap_pct}%"
+ last_progress="$bootstrap_pct"
+ fi
+ fi
+
+ # Check if Tor is actually running
+ if ! docker exec "$container_name" pgrep -x tor >/dev/null 2>&1; then
+ error "Tor process not running"
+ docker logs "$container_name" 2>&1 | tail -20
+ return 1
+ fi
+
+ sleep 5
+ counter=$((counter + 5))
+ done
+
+ warn "Timeout waiting for bootstrap completion"
+ log "Current progress: ${bootstrap_pct}%"
+ return 1
+}
+
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+# Migration Functions
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+# Backup volume before migration
+backup_volume() {
+ volume_name="$1"
+ backup_dir="${2:-/tmp}"
+ backup_file="${backup_dir}/tor-backup-$(date +%Y%m%d-%H%M%S).tar.gz"
+
+ log "Creating backup of volume '${volume_name}'..."
+
+ if ! docker run --rm -v "${volume_name}:/data:ro" -v "${backup_dir}:/backup" alpine:3.22.2 \
+ tar czf "/backup/$(basename "$backup_file")" -C /data . 2>/dev/null; then
+ error "Backup failed"
+ return 1
+ fi
+
+ success "Backup created: ${backup_file}"
+ printf '%s' "$backup_file"
+ return 0
+}
+
+# Fix volume ownership (Debian UID 101 โ Alpine UID 100)
+fix_volume_ownership() {
+ volume_name="$1"
+
+ log "Fixing ownership: debian-tor (101) โ tor (100)..."
+
+ current_ownership=$(check_volume_ownership "$volume_name")
+ log "Current ownership: ${current_ownership}"
+
+ if ! docker run --rm -v "${volume_name}:/data" alpine:3.22.2 chown -R 100:101 /data 2>/dev/null; then
+ error "Failed to fix ownership"
+ return 1
+ fi
+
+ new_ownership=$(check_volume_ownership "$volume_name")
+ log "New ownership: ${new_ownership}"
+
+ if [ "$new_ownership" = "100:101" ]; then
+ success "Ownership fixed successfully"
+ return 0
+ else
+ error "Ownership verification failed (expected 100:101, got ${new_ownership})"
+ return 1
+ fi
+}
+
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+# Main Migration Logic
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+show_banner() {
+ printf "\n"
+ printf "${BOLD}${CYAN}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ${NC}\n"
+ printf "${BOLD}${CYAN} Migration Assistant: Official Tor Bridge โ Onion Relay${NC}\n"
+ printf "${BOLD}${CYAN}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ${NC}\n"
+ printf "\n"
+ printf "This script automates migration from:\n"
+ printf " ${BOLD}Source:${NC} thetorproject/obfs4-bridge (Debian, UID 101)\n"
+ printf " ${BOLD}Target:${NC} ghcr.io/r3bo0tbx1/onion-relay (Alpine, UID 100)\n"
+ printf "\n"
+}
+
+main() {
+ show_banner
+
+ # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ # Step 1: Pre-flight Checks
+ # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+ step "Step 1: Pre-flight Checks"
+
+ check_docker
+ success "Docker is available"
+
+ # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ # Step 2: Detect Existing Setup
+ # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+ step "Step 2: Detect Existing Setup"
+
+ OLD_CONTAINER=$(detect_official_containers)
+
+ if [ -z "$OLD_CONTAINER" ]; then
+ warn "No thetorproject/obfs4-bridge container found"
+ log "This script is designed to migrate from the official bridge image"
+ log "If you're setting up a new relay, use scripts/quick-start.sh instead"
+
+ if ! confirm "Continue with manual configuration?"; then
+ log "Migration cancelled"
+ exit 0
+ fi
+
+ # Manual mode
+ printf "\n${BOLD}Manual Configuration Mode${NC}\n"
+ printf "Enter the volume name containing your Tor data: "
+ read -r DATA_VOLUME
+
+ if [ -z "$DATA_VOLUME" ]; then
+ die "Volume name is required"
+ fi
+
+ if ! docker volume inspect "$DATA_VOLUME" >/dev/null 2>&1; then
+ die "Volume '${DATA_VOLUME}' does not exist"
+ fi
+
+ printf "Enter the new container name (default: tor-bridge): "
+ read -r NEW_CONTAINER
+ NEW_CONTAINER="${NEW_CONTAINER:-tor-bridge}"
+
+ printf "Enter OR_PORT (default: 9001): "
+ read -r OR_PORT
+ OR_PORT="${OR_PORT:-9001}"
+
+ printf "Enter PT_PORT (default: 9002): "
+ read -r PT_PORT
+ PT_PORT="${PT_PORT:-9002}"
+
+ printf "Enter NICKNAME: "
+ read -r NICKNAME
+
+ printf "Enter EMAIL: "
+ read -r EMAIL
+
+ if [ -z "$NICKNAME" ] || [ -z "$EMAIL" ]; then
+ die "NICKNAME and EMAIL are required"
+ fi
+
+ OLD_CONTAINER=""
+
+ else
+ success "Found container: ${OLD_CONTAINER}"
+
+ # Extract configuration
+ NICKNAME=$(get_container_env "$OLD_CONTAINER" "NICKNAME")
+ EMAIL=$(get_container_env "$OLD_CONTAINER" "EMAIL")
+ OR_PORT=$(get_container_env "$OLD_CONTAINER" "OR_PORT")
+ PT_PORT=$(get_container_env "$OLD_CONTAINER" "PT_PORT")
+
+ log "Configuration detected:"
+ log " Nickname: ${NICKNAME:-not set}"
+ log " Email: ${EMAIL:-not set}"
+ log " OR Port: ${OR_PORT:-9001}"
+ log " PT Port: ${PT_PORT:-9002}"
+
+ # Get volumes
+ VOLUMES=$(get_container_volumes "$OLD_CONTAINER")
+ DATA_VOLUME=""
+
+ if [ -n "$VOLUMES" ]; then
+ log "Volume mounts:"
+ printf '%s\n' "$VOLUMES" | while IFS=: read -r vol_name vol_path; do
+ log " ${vol_name} โ ${vol_path}"
+ if [ "$vol_path" = "/var/lib/tor" ]; then
+ DATA_VOLUME="$vol_name"
+ fi
+ done
+
+ # Extract first volume for data if specific mount not found
+ if [ -z "$DATA_VOLUME" ]; then
+ DATA_VOLUME=$(printf '%s\n' "$VOLUMES" | head -1 | cut -d: -f1)
+ fi
+ fi
+
+ if [ -z "$DATA_VOLUME" ]; then
+ warn "No volume detected for /var/lib/tor"
+ printf "Enter the volume name containing Tor data: "
+ read -r DATA_VOLUME
+
+ if [ -z "$DATA_VOLUME" ]; then
+ die "Volume name is required"
+ fi
+ fi
+
+ # Get current fingerprint
+ log "Checking current fingerprint..."
+ OLD_FINGERPRINT=$(get_fingerprint_from_volume "$DATA_VOLUME")
+
+ if [ "$OLD_FINGERPRINT" = "NOT_FOUND" ]; then
+ warn "No identity keys found in volume"
+ elif [ "$OLD_FINGERPRINT" = "Keys exist but fingerprint not yet generated" ]; then
+ log "Identity keys exist, fingerprint will be generated after migration"
+ else
+ success "Current fingerprint: ${OLD_FINGERPRINT}"
+ fi
+
+ # Check container status
+ CONTAINER_STATUS=$(docker inspect "$OLD_CONTAINER" --format '{{.State.Status}}' 2>/dev/null || echo "not_found")
+ log "Container status: ${CONTAINER_STATUS}"
+
+ # Confirm migration
+ printf "\n"
+ if ! confirm "Proceed with migration?"; then
+ log "Migration cancelled"
+ exit 0
+ fi
+
+ # Ask for new container name
+ printf "\n"
+ printf "Enter new container name (default: tor-bridge): "
+ read -r NEW_CONTAINER
+ NEW_CONTAINER="${NEW_CONTAINER:-tor-bridge}"
+ fi
+
+ # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ # Step 3: Backup Current Data
+ # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+ step "Step 3: Backup Current Data"
+
+ if confirm "Create backup of volume '${DATA_VOLUME}'?"; then
+ BACKUP_DIR="${HOME}/tor-backups"
+ mkdir -p "$BACKUP_DIR" 2>/dev/null || BACKUP_DIR="/tmp"
+
+ if BACKUP_FILE=$(backup_volume "$DATA_VOLUME" "$BACKUP_DIR"); then
+ log "Backup location: ${BACKUP_FILE}"
+ else
+ warn "Backup failed, but continuing..."
+ fi
+ else
+ warn "Skipping backup (not recommended)"
+ fi
+
+ # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ # Step 4: Stop Old Container
+ # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+ if [ -n "$OLD_CONTAINER" ]; then
+ step "Step 4: Stop Old Container"
+
+ log "Stopping container: ${OLD_CONTAINER}"
+
+ if docker stop "$OLD_CONTAINER" >/dev/null 2>&1; then
+ success "Container stopped"
+ else
+ warn "Container may not be running"
+ fi
+
+ if confirm "Remove old container '${OLD_CONTAINER}'? (keeps volumes)"; then
+ if docker rm "$OLD_CONTAINER" >/dev/null 2>&1; then
+ success "Container removed"
+ else
+ warn "Failed to remove container"
+ fi
+ fi
+ else
+ step "Step 4: Stop Old Container (Skipped)"
+ log "No old container to stop"
+ fi
+
+ # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ # Step 5: Fix Volume Ownership
+ # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+ step "Step 5: Fix Volume Ownership"
+
+ CURRENT_OWNERSHIP=$(check_volume_ownership "$DATA_VOLUME")
+ log "Current ownership: ${CURRENT_OWNERSHIP}"
+
+ if [ "$CURRENT_OWNERSHIP" = "100:101" ]; then
+ success "Ownership already correct (100:101)"
+ else
+ if ! fix_volume_ownership "$DATA_VOLUME"; then
+ die "Failed to fix ownership - migration aborted"
+ fi
+ fi
+
+ # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ # Step 6: Deploy New Container
+ # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+ step "Step 6: Deploy New Container"
+
+ # Check if container already exists
+ if docker inspect "$NEW_CONTAINER" >/dev/null 2>&1; then
+ warn "Container '${NEW_CONTAINER}' already exists"
+ if confirm "Remove existing container and deploy new one?"; then
+ docker rm -f "$NEW_CONTAINER" >/dev/null 2>&1 || true
+ else
+ die "Cannot proceed with existing container"
+ fi
+ fi
+
+ log "Deploying new container: ${NEW_CONTAINER}"
+ log "Image: ghcr.io/r3bo0tbx1/onion-relay:latest"
+
+ # Build docker run command
+ DOCKER_RUN_CMD="docker run -d \
+ --name ${NEW_CONTAINER} \
+ --network host \
+ --restart unless-stopped \
+ --security-opt no-new-privileges:true \
+ --cap-drop ALL \
+ --cap-add CHOWN \
+ --cap-add SETUID \
+ --cap-add SETGID \
+ --cap-add DAC_OVERRIDE \
+ -e NICKNAME=\"${NICKNAME}\" \
+ -e EMAIL=\"${EMAIL}\" \
+ -e OR_PORT=\"${OR_PORT:-9001}\" \
+ -e PT_PORT=\"${PT_PORT:-9002}\" \
+ -v ${DATA_VOLUME}:/var/lib/tor \
+ ghcr.io/r3bo0tbx1/onion-relay:latest"
+
+ log "Running command:"
+ printf '%s\n' "$DOCKER_RUN_CMD" | sed 's/^/ /'
+
+ if eval "$DOCKER_RUN_CMD"; then
+ success "Container started"
+ else
+ die "Failed to start container"
+ fi
+
+ # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ # Step 7: Wait for Container to Start
+ # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+ step "Step 7: Wait for Container to Start"
+
+ if ! wait_for_healthy "$NEW_CONTAINER" 60; then
+ error "Container failed to start properly"
+ log "Check logs with: docker logs ${NEW_CONTAINER}"
+ die "Migration failed at container startup"
+ fi
+
+ # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ # Step 8: Wait for Tor Bootstrap
+ # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+ step "Step 8: Wait for Tor Bootstrap"
+
+ if ! wait_for_bootstrap "$NEW_CONTAINER" 300; then
+ warn "Bootstrap did not complete in time"
+ log "This may be normal for first startup - Tor can take 5-10 minutes"
+
+ if ! confirm "Continue with validation anyway?"; then
+ die "Migration incomplete - container is running but not bootstrapped"
+ fi
+ fi
+
+ # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ # Step 9: Validate Migration
+ # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+ step "Step 9: Validate Migration"
+
+ # Check fingerprint
+ log "Checking fingerprint..."
+ NEW_FINGERPRINT=$(docker exec "$NEW_CONTAINER" fingerprint 2>/dev/null | grep -oE '[A-F0-9]{40}' | head -1 || echo "")
+
+ if [ -n "$NEW_FINGERPRINT" ]; then
+ success "Fingerprint: ${NEW_FINGERPRINT}"
+
+ if [ -n "$OLD_FINGERPRINT" ] && [ "$OLD_FINGERPRINT" != "NOT_FOUND" ] && [ "$OLD_FINGERPRINT" != "Keys exist but fingerprint not yet generated" ]; then
+ if [ "$NEW_FINGERPRINT" = "$OLD_FINGERPRINT" ]; then
+ success "Fingerprint matches (relay identity preserved)"
+ else
+ error "Fingerprint mismatch!"
+ error " Old: ${OLD_FINGERPRINT}"
+ error " New: ${NEW_FINGERPRINT}"
+ warn "This means your relay has a NEW identity - you lost reputation!"
+ fi
+ fi
+ else
+ warn "Fingerprint not yet available (may still be generating)"
+ fi
+
+ # Check bridge line
+ log "Checking bridge line..."
+ BRIDGE_LINE=$(docker exec "$NEW_CONTAINER" bridge-line 2>/dev/null || echo "")
+
+ if [ -n "$BRIDGE_LINE" ]; then
+ success "Bridge line generated successfully"
+ log "Bridge line:"
+ printf '%s\n' "$BRIDGE_LINE" | sed 's/^/ /'
+ else
+ warn "Bridge line not yet available (may still be generating)"
+ fi
+
+ # Check health status
+ log "Checking health status..."
+ HEALTH_OUTPUT=$(docker exec "$NEW_CONTAINER" health 2>/dev/null || echo "{}")
+
+ if printf '%s' "$HEALTH_OUTPUT" | grep -q '"status":"healthy"'; then
+ success "Health check passed"
+ else
+ warn "Health check shows issues"
+ log "Run: docker exec ${NEW_CONTAINER} status"
+ fi
+
+ # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ # Step 10: Migration Complete
+ # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+ step "Migration Complete!"
+
+ printf "\n${GREEN}${BOLD}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ${NC}\n"
+ printf "${GREEN}${BOLD}โ
Migration Successful${NC}\n"
+ printf "${GREEN}${BOLD}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ${NC}\n\n"
+
+ printf "${BOLD}Next Steps:${NC}\n\n"
+
+ printf "1๏ธโฃ Check container status:\n"
+ printf " ${CYAN}docker exec ${NEW_CONTAINER} status${NC}\n\n"
+
+ printf "2๏ธโฃ View logs:\n"
+ printf " ${CYAN}docker logs -f ${NEW_CONTAINER}${NC}\n\n"
+
+ printf "3๏ธโฃ Get bridge line (after bootstrap complete):\n"
+ printf " ${CYAN}docker exec ${NEW_CONTAINER} bridge-line${NC}\n\n"
+
+ printf "4๏ธโฃ Check fingerprint on Tor Metrics:\n"
+ if [ -n "$NEW_FINGERPRINT" ]; then
+ printf " ${CYAN}https://metrics.torproject.org/rs.html#details/${NEW_FINGERPRINT}${NC}\n\n"
+ else
+ printf " ${CYAN}docker exec ${NEW_CONTAINER} fingerprint${NC}\n\n"
+ fi
+
+ printf "5๏ธโฃ Monitor resource usage:\n"
+ printf " ${CYAN}docker stats ${NEW_CONTAINER}${NC}\n\n"
+
+ if [ -n "$BACKUP_FILE" ]; then
+ printf "${BOLD}Backup Information:${NC}\n"
+ printf " Location: ${CYAN}${BACKUP_FILE}${NC}\n"
+ printf " Keep this backup until you verify the relay is working correctly\n\n"
+ fi
+
+ printf "${BOLD}Important Notes:${NC}\n"
+ printf " โข Tor may take 5-10 minutes to fully bootstrap\n"
+ printf " โข Bridge should appear in Tor Metrics within 24 hours\n"
+ printf " โข Monitor logs for any errors: ${CYAN}docker logs ${NEW_CONTAINER}${NC}\n"
+ printf " โข If issues occur, you can restore from backup\n\n"
+
+ if [ -n "$OLD_CONTAINER" ] && docker ps -a --format '{{.Names}}' | grep -q "^${OLD_CONTAINER}\$"; then
+ printf "${YELLOW}${BOLD}Old Container:${NC}\n"
+ printf " Container '${OLD_CONTAINER}' is still present (stopped)\n"
+ printf " After confirming migration success, remove it with:\n"
+ printf " ${CYAN}docker rm ${OLD_CONTAINER}${NC}\n\n"
+ fi
+}
+
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+# Entry Point
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+if [ "${1:-}" = "--help" ] || [ "${1:-}" = "-h" ]; then
+ show_banner
+ printf "Usage: %s [OPTIONS]\n\n" "$0"
+ printf "Automated migration from thetorproject/obfs4-bridge to r3bo0tbx1/onion-relay\n\n"
+ printf "This script:\n"
+ printf " โข Detects existing official bridge container\n"
+ printf " โข Backs up current data and fingerprint\n"
+ printf " โข Fixes volume ownership (Debian UID 101 โ Alpine UID 100)\n"
+ printf " โข Deploys new container with same configuration\n"
+ printf " โข Validates fingerprint preservation\n"
+ printf " โข Provides rollback instructions if needed\n\n"
+ printf "Options:\n"
+ printf " -h, --help Show this help message\n\n"
+ printf "Examples:\n"
+ printf " %s # Interactive migration\n\n" "$0"
+ exit 0
+fi
+
+main "$@"
diff --git a/scripts/release/README.md b/scripts/release/README.md
new file mode 100644
index 0000000..6f3f5b2
--- /dev/null
+++ b/scripts/release/README.md
@@ -0,0 +1,558 @@
+# Release Automation Scripts
+
+This directory contains automation scripts for managing releases, version updates, and release notes generation.
+
+## Overview
+
+The release automation includes three main components:
+
+1. **Auto-generate release notes** from conventional commits
+2. **Auto-update version** numbers across all documentation
+3. **SBOM generation** (CycloneDX & SPDX) integrated into CI/CD
+
+## Scripts
+
+### generate-release-notes.sh
+
+Auto-generate release notes from git commit history using conventional commit format.
+
+**Features:**
+- Parses conventional commits (feat, fix, docs, chore, etc.)
+- Categorizes changes by type with emojis
+- Detects breaking changes automatically
+- Supports multiple output formats (markdown, github, plain)
+- Falls back to all commits if no conventional commits found
+
+**Usage:**
+
+```bash
+# Auto-detect previous version and generate notes
+./scripts/release/generate-release-notes.sh 1.1.2
+
+# Specify previous version explicitly
+./scripts/release/generate-release-notes.sh 1.1.2 1.1.1
+
+# Save to file
+./scripts/release/generate-release-notes.sh -o RELEASE_NOTES.md 1.2.0
+
+# GitHub format (used by CI)
+./scripts/release/generate-release-notes.sh --format github 1.2.0
+
+# Show only breaking changes
+./scripts/release/generate-release-notes.sh --breaking-only 1.2.0
+
+# Disable emojis
+./scripts/release/generate-release-notes.sh --no-emoji 1.2.0
+```
+
+**Conventional Commit Types:**
+
+| Type | Emoji | Description |
+|------|-------|-------------|
+| `feat:` | โจ | New features |
+| `fix:` | ๐ | Bug fixes |
+| `docs:` | ๐ | Documentation changes |
+| `perf:` | โก | Performance improvements |
+| `refactor:` | โป๏ธ | Code refactoring |
+| `test:` | โ
| Testing changes |
+| `build:` | ๐๏ธ | Build system changes |
+| `ci:` | ๐ท | CI/CD changes |
+| `chore:` | ๐ง | Maintenance tasks |
+| `style:` | ๐ | Code style changes |
+| `revert:` | โช | Reverts |
+
+**Breaking Changes:**
+
+Breaking changes are detected in two ways:
+1. **Type suffix**: `feat!:` or `fix!:` (exclamation mark after type)
+2. **Body keyword**: `BREAKING CHANGE:` in commit body
+
+**Example Commits:**
+
+```bash
+# Feature
+git commit -m "feat: add migration assistant script"
+
+# Bug fix
+git commit -m "fix: resolve OBFS4V parsing issue with spaces"
+
+# Breaking change (method 1)
+git commit -m "feat!: remove legacy ENV variable support"
+
+# Breaking change (method 2)
+git commit -m "feat: redesign configuration system
+
+BREAKING CHANGE: Old ENV variables are no longer supported.
+Use TOR_* prefix instead."
+
+# Documentation
+git commit -m "docs: update README with migration guide"
+
+# Multiple types in one commit
+git commit -m "feat: add SBOM generation
+
+- Generates CycloneDX and SPDX formats
+- Integrates with CI/CD workflow
+- Attaches to GitHub releases"
+```
+
+**Output Example:**
+
+```markdown
+## ๐ง
Tor Guard Relay v1.2.0
+
+### โจ Features
+
+- Add migration assistant script (`a1b2c3d4`) by John Doe
+- Add SBOM generation to CI/CD (`e5f6g7h8`) by Jane Smith
+
+### ๐ Bug Fixes
+
+- Resolve OBFS4V parsing issue with spaces (`i9j0k1l2`) by John Doe
+- Fix Mermaid diagram rendering in GitHub (`m3n4o5p6`) by Jane Smith
+
+### ๐ Documentation
+
+- Update README with migration guide (`q7r8s9t0`) by John Doe
+- Add comprehensive FAQ (`u1v2w3x4`) by Jane Smith
+
+---
+
+**Full Changelog**: v1.1.1...v1.2.0
+```
+
+### update-version.sh
+
+Auto-update version numbers across all documentation, templates, and configuration files.
+
+**Features:**
+- Updates version in README.md (badges, examples)
+- Updates CHANGELOG.md (adds new version header)
+- Updates templates/*.yml (Docker Compose image tags)
+- Updates templates/*.json (Cosmos Cloud templates)
+- Updates docs/*.md (all documentation)
+- Updates CLAUDE.md (project documentation)
+- Creates backups by default (.bak files)
+- Dry-run mode to preview changes
+
+**Usage:**
+
+```bash
+# Update version (creates .bak backups)
+./scripts/release/update-version.sh 1.1.2
+
+# Preview changes without modifying files
+./scripts/release/update-version.sh --dry-run 1.2.0
+
+# Update without creating backups
+./scripts/release/update-version.sh --no-backup 1.1.2
+
+# Works with or without 'v' prefix
+./scripts/release/update-version.sh v1.1.2
+```
+
+**What Gets Updated:**
+
+1. **README.md**
+ - Version badges
+ - Docker image tags in examples
+ - Version references in text
+
+2. **CHANGELOG.md**
+ - Adds new version header after `## [Unreleased]` section
+ - Format: `## [v1.1.2] - 2025-01-14`
+
+3. **templates/*.yml** (Docker Compose)
+ - `image:` tags from `onion-relay:1.1.1` โ `onion-relay:1.1.2`
+
+4. **templates/*.json** (Cosmos Cloud)
+ - `"image":` fields in JSON templates
+
+5. **docs/*.md** (All documentation)
+ - Version references throughout docs
+
+6. **CLAUDE.md** (Project instructions)
+ - Version references for Claude Code
+
+**Example Output:**
+
+```
+โน Current version detected: 1.1.1
+โน New version: 1.1.2
+โน Updating README.md...
+โ
Updated: README.md
+โน Updating CHANGELOG.md...
+โ
Added version 1.1.2 to CHANGELOG.md
+โน Updating Docker Compose templates...
+โ
Updated: templates/docker-compose-guard-env.yml
+โ
Updated: templates/docker-compose-exit.yml
+โ
Updated: templates/docker-compose-bridge.yml
+...
+
+โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+โ
Version Update Complete
+โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+ Current Version: 1.1.1
+ New Version: 1.1.2
+ Files Updated: 15
+
+๐ต Backup files created with .bak extension
+๐ต To restore: for f in *.bak; do mv "$f" "${f%.bak}"; done
+
+Next Steps:
+ 1. Review changes: git diff
+ 2. Update CHANGELOG.md with release notes
+ 3. Commit changes: git add -A && git commit -m "chore: bump version to 1.1.2"
+ 4. Create tag: git tag -a v1.1.2 -m "Release v1.1.2"
+ 5. Push: git push && git push --tags
+```
+
+**Rollback:**
+
+If you need to undo changes:
+
+```bash
+# Restore from .bak files
+for f in *.bak **/*.bak; do
+ [ -f "$f" ] && mv "$f" "${f%.bak}"
+done
+
+# Or use git to reset
+git checkout -- .
+```
+
+## CI/CD Integration
+
+The release workflow (`.github/workflows/release.yml`) integrates all three automation components:
+
+### Automated SBOM Generation
+
+When a release tag is pushed, the workflow automatically:
+
+1. **Builds Docker image** with multi-arch support (AMD64, ARM64)
+2. **Generates SBOM** in multiple formats:
+ - **CycloneDX JSON** (`sbom-cyclonedx-v1.1.2.json`)
+ - **CycloneDX XML** (`sbom-cyclonedx-v1.1.2.xml`)
+ - **SPDX JSON** (`sbom-spdx-v1.1.2.json`)
+ - **SPDX tag-value** (`sbom-spdx-v1.1.2.spdx`)
+ - **Human-readable table** (`sbom-table-v1.1.2.txt`)
+3. **Uploads SBOM** as workflow artifacts (90-day retention)
+4. **Attaches SBOM** to GitHub release as downloadable assets
+
+### Automated Release Notes
+
+The workflow generates release notes with this priority:
+
+1. **CHANGELOG.md** (preferred)
+ - Extracts section for specific version
+ - Format: `## [v1.1.2] - 2025-01-14` or `## v1.1.2`
+
+2. **Auto-generated from commits** (fallback)
+ - Uses `generate-release-notes.sh` script
+ - Parses conventional commits
+ - Categorizes by type with emojis
+
+3. **Simple commit list** (last resort)
+ - Basic git log output
+ - Shows commit messages with hashes
+
+**Release Note Sections:**
+
+Every release includes:
+- ๐ฆ Changes (categorized by type)
+- ๐ณ Docker Images (pull commands for GHCR and Docker Hub)
+- ๐ SBOM (links to downloadable SBOM files)
+- ๐ Full Changelog (compare link)
+
+## Release Workflow
+
+### Manual Release Process
+
+For creating a new release manually:
+
+```bash
+# 1. Update version numbers across all files
+./scripts/release/update-version.sh 1.2.0
+
+# 2. Review changes
+git diff
+
+# 3. Generate release notes (optional, to preview)
+./scripts/release/generate-release-notes.sh 1.2.0
+
+# 4. Update CHANGELOG.md with detailed notes
+vim CHANGELOG.md
+# Add release notes under ## [v1.2.0] - 2025-01-14
+
+# 5. Commit version bump
+git add -A
+git commit -m "chore: bump version to 1.2.0"
+
+# 6. Create annotated tag
+git tag -a v1.2.0 -m "Release v1.2.0"
+
+# 7. Push to trigger release workflow
+git push origin main
+git push origin v1.2.0
+
+# GitHub Actions will:
+# - Build multi-arch Docker images
+# - Generate SBOM files
+# - Create GitHub release with notes
+# - Attach SBOM to release
+# - Push images to GHCR and Docker Hub
+```
+
+### Automated Release (CI/CD)
+
+The workflow triggers on:
+
+1. **Git tag push** (`v*.*.*`)
+ - Full release with SBOM generation
+ - Release notes from CHANGELOG.md or auto-generated
+ - Updates `:latest` tag
+
+2. **Weekly schedule** (Sundays 18:30 UTC)
+ - Rebuilds last release with updated packages
+ - No release notes or SBOM (not a new release)
+ - Overwrites version tag with fresh build
+
+3. **Manual dispatch** (workflow_dispatch)
+ - Test builds with version suffix
+ - Useful for testing release process
+
+## SBOM (Software Bill of Materials)
+
+### What is SBOM?
+
+SBOM provides transparency about software components and dependencies:
+
+- **Security**: Identify vulnerable packages quickly
+- **Compliance**: Meet regulatory requirements (NTIA, EO 14028)
+- **Supply chain**: Track third-party components
+- **Auditing**: Know exactly what's in your container
+
+### SBOM Formats
+
+**CycloneDX** (OWASP standard)
+- JSON: Machine-readable, API-friendly
+- XML: Enterprise tooling compatibility
+
+**SPDX** (Linux Foundation standard)
+- JSON: Modern, developer-friendly
+- Tag-value: Traditional, widely supported
+
+**Table** (Human-readable)
+- Plain text listing of all packages
+- Quick manual inspection
+
+### Using SBOM Files
+
+**Check for vulnerabilities:**
+
+```bash
+# Download SBOM from GitHub release
+wget https://github.com/r3bo0tbx1/test-0f376e81/releases/download/v1.1.2/sbom-cyclonedx-v1.1.2.json
+
+# Scan with Grype
+grype sbom:sbom-cyclonedx-v1.1.2.json
+
+# Scan with Trivy
+trivy sbom sbom-cyclonedx-v1.1.2.json
+```
+
+**Integrate with security tools:**
+
+```bash
+# Import into Dependency-Track
+curl -X POST "https://dtrack.example.com/api/v1/bom" \
+ -H "X-Api-Key: $API_KEY" \
+ -F "bom=@sbom-cyclonedx-v1.1.2.json"
+
+# Analyze with Syft
+syft sbom-cyclonedx-v1.1.2.json
+
+# View package list
+jq '.components[] | {name, version, type}' sbom-cyclonedx-v1.1.2.json
+```
+
+**Example SBOM Content:**
+
+```json
+{
+ "bomFormat": "CycloneDX",
+ "specVersion": "1.4",
+ "version": 1,
+ "metadata": {
+ "component": {
+ "type": "container",
+ "name": "onion-relay",
+ "version": "1.1.2"
+ }
+ },
+ "components": [
+ {
+ "type": "library",
+ "name": "alpine-baselayout",
+ "version": "3.4.3-r2",
+ "purl": "pkg:apk/alpine/alpine-baselayout@3.4.3-r2"
+ },
+ {
+ "type": "library",
+ "name": "tor",
+ "version": "0.4.8.10-r0",
+ "purl": "pkg:apk/alpine/tor@0.4.8.10-r0"
+ }
+ ]
+}
+```
+
+## Best Practices
+
+### Conventional Commits
+
+Follow the [Conventional Commits](https://www.conventionalcommits.org/) specification:
+
+```
+[optional scope]:
+
+[optional body]
+
+[optional footer(s)]
+```
+
+**Benefits:**
+- Automated changelog generation
+- Semantic versioning hints
+- Better commit history readability
+- Easier rollback and debugging
+
+### Version Numbering
+
+Follow [Semantic Versioning](https://semver.org/) (SemVer):
+
+- **MAJOR** (1.0.0 โ 2.0.0): Breaking changes
+- **MINOR** (1.1.0 โ 1.2.0): New features (backward compatible)
+- **PATCH** (1.1.1 โ 1.1.2): Bug fixes (backward compatible)
+
+**Examples:**
+- `feat!: remove old ENV variables` โ MAJOR bump
+- `feat: add migration script` โ MINOR bump
+- `fix: resolve parsing error` โ PATCH bump
+
+### CHANGELOG.md Format
+
+Use [Keep a Changelog](https://keepachangelog.com/) format:
+
+```markdown
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+## [Unreleased]
+
+## [v1.2.0] - 2025-01-14
+
+### Added
+- Migration assistant script for official Tor bridge image migration
+- SBOM generation in CI/CD workflow
+- Auto-generated release notes from conventional commits
+
+### Changed
+- Updated release workflow with SBOM integration
+- Improved release notes generation with fallback mechanism
+
+### Fixed
+- OBFS4V parsing issue with values containing spaces
+- Mermaid diagram rendering on GitHub
+
+## [v1.1.1] - 2025-01-10
+
+### Fixed
+- Busybox compatibility in OBFS4V validation
+- Numeric sanitization in diagnostic tools
+```
+
+## Troubleshooting
+
+### Release Notes Not Generating
+
+**Problem**: Auto-generation finds no commits
+
+**Solution:**
+```bash
+# Check git history
+git log --oneline
+
+# Verify previous tag exists
+git describe --tags --abbrev=0
+
+# Specify previous version explicitly
+./scripts/release/generate-release-notes.sh 1.1.2 1.1.1
+```
+
+### Version Update Missing Files
+
+**Problem**: Not all files were updated
+
+**Solution:**
+```bash
+# Check what current version was detected
+./scripts/release/update-version.sh --dry-run 1.1.2
+
+# Search for old version manually
+grep -r "1.1.1" . --exclude-dir=.git
+
+# Update manually missed files
+sed -i 's/1.1.1/1.1.2/g' path/to/file
+```
+
+### SBOM Generation Fails
+
+**Problem**: Syft can't access image
+
+**Solution:**
+```bash
+# Ensure image exists locally or in registry
+docker pull ghcr.io/r3bo0tbx1/onion-relay:1.1.2
+
+# Generate SBOM locally for testing
+docker run --rm \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ anchore/syft:latest \
+ ghcr.io/r3bo0tbx1/onion-relay:1.1.2 \
+ -o cyclonedx-json
+```
+
+### Workflow Permission Errors
+
+**Problem**: `Resource not accessible by integration`
+
+**Solution:** Ensure workflow has correct permissions:
+
+```yaml
+permissions:
+ contents: write # Create releases
+ packages: write # Push to GHCR
+ security-events: write # Upload SARIF
+```
+
+## Additional Resources
+
+- **Conventional Commits**: https://www.conventionalcommits.org/
+- **Semantic Versioning**: https://semver.org/
+- **Keep a Changelog**: https://keepachangelog.com/
+- **CycloneDX**: https://cyclonedx.org/
+- **SPDX**: https://spdx.dev/
+- **NTIA SBOM**: https://www.ntia.gov/sbom
+
+## Contributing
+
+When adding new release automation features:
+
+1. Update this README with usage examples
+2. Add tests for new functionality
+3. Update `.github/workflows/release.yml` if needed
+4. Follow existing script patterns (POSIX sh, color output, error handling)
+5. Document all environment variables and options
diff --git a/scripts/release/generate-release-notes.sh b/scripts/release/generate-release-notes.sh
new file mode 100644
index 0000000..8a2fe9b
--- /dev/null
+++ b/scripts/release/generate-release-notes.sh
@@ -0,0 +1,441 @@
+#!/bin/sh
+# Auto-generate release notes from git commits using conventional commit format
+# Supports: feat, fix, docs, chore, refactor, test, perf, ci, build, style
+# Falls back to all commits if no conventional commits found
+
+set -e
+
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+# Color Output
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+CYAN='\033[0;36m'
+BOLD='\033[1m'
+NC='\033[0m'
+
+log() { printf "${BLUE}โน${NC} %s\n" "$*"; }
+success() { printf "${GREEN}โ
${NC} %s\n" "$*"; }
+warn() { printf "${YELLOW}โ ${NC} %s\n" "$*"; }
+error() { printf "${RED}โ${NC} %s\n" "$*"; }
+die() { error "$*"; exit 1; }
+
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+# Usage
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+usage() {
+ cat << EOF
+Usage: $(basename "$0") [OPTIONS] [previous_version]
+
+Auto-generate release notes from git commits using conventional commit format.
+
+Arguments:
+ version The new version (e.g., 1.1.1 or v1.1.1)
+ previous_version Previous version to compare from (optional, auto-detected)
+
+Options:
+ -o, --output FILE Output file (default: stdout)
+ -f, --format FMT Output format: markdown (default), github, plain
+ --no-emoji Disable emojis in output
+ --breaking-only Show only breaking changes
+ -h, --help Show this help
+
+Examples:
+ $(basename "$0") 1.1.1
+ $(basename "$0") 1.1.1 1.1.0
+ $(basename "$0") -o RELEASE_NOTES.md 1.2.0
+ $(basename "$0") --format github 1.2.0 > notes.md
+
+Conventional Commit Types:
+ feat: โจ New features
+ fix: ๐ Bug fixes
+ docs: ๐ Documentation changes
+ perf: โก Performance improvements
+ refactor: โป๏ธ Code refactoring
+ test: โ
Testing changes
+ build: ๐๏ธ Build system changes
+ ci: ๐ท CI/CD changes
+ chore: ๐ง Maintenance tasks
+ style: ๐ Code style changes
+ revert: โช Reverts
+
+Breaking Changes:
+ Any commit with "BREAKING CHANGE:" in body or "!" after type
+ Example: feat!: remove legacy API
+
+EOF
+ exit 0
+}
+
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+# Argument Parsing
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+OUTPUT_FILE=""
+FORMAT="markdown"
+USE_EMOJI=1
+BREAKING_ONLY=0
+VERSION=""
+PREV_VERSION=""
+
+while [ $# -gt 0 ]; do
+ case "$1" in
+ -h|--help)
+ usage
+ ;;
+ -o|--output)
+ OUTPUT_FILE="$2"
+ shift 2
+ ;;
+ -f|--format)
+ FORMAT="$2"
+ shift 2
+ ;;
+ --no-emoji)
+ USE_EMOJI=0
+ shift
+ ;;
+ --breaking-only)
+ BREAKING_ONLY=1
+ shift
+ ;;
+ -*)
+ die "Unknown option: $1 (use --help for usage)"
+ ;;
+ *)
+ if [ -z "$VERSION" ]; then
+ VERSION="$1"
+ elif [ -z "$PREV_VERSION" ]; then
+ PREV_VERSION="$1"
+ else
+ die "Too many arguments (use --help for usage)"
+ fi
+ shift
+ ;;
+ esac
+done
+
+if [ -z "$VERSION" ]; then
+ die "Version is required (use --help for usage)"
+fi
+
+# Strip 'v' prefix if present
+VERSION="${VERSION#v}"
+
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+# Git Validation
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+if ! git rev-parse --git-dir >/dev/null 2>&1; then
+ die "Not a git repository"
+fi
+
+# Auto-detect previous version if not provided
+if [ -z "$PREV_VERSION" ]; then
+ PREV_VERSION=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
+ if [ -z "$PREV_VERSION" ]; then
+ # No previous tags, use first commit
+ PREV_VERSION=$(git rev-list --max-parents=0 HEAD)
+ log "No previous tags found, using initial commit: ${PREV_VERSION:0:8}"
+ else
+ log "Auto-detected previous version: ${PREV_VERSION}"
+ fi
+else
+ # Ensure previous version has 'v' prefix for git tag comparison
+ if ! git rev-parse "v${PREV_VERSION}" >/dev/null 2>&1; then
+ if ! git rev-parse "${PREV_VERSION}" >/dev/null 2>&1; then
+ die "Previous version '${PREV_VERSION}' not found in git history"
+ fi
+ else
+ PREV_VERSION="v${PREV_VERSION}"
+ fi
+fi
+
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+# Commit Parsing
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+# Get commit range
+COMMIT_RANGE="${PREV_VERSION}..HEAD"
+log "Generating release notes from: ${COMMIT_RANGE}"
+
+# Get all commits in range
+COMMITS=$(git log "${COMMIT_RANGE}" --pretty=format:'%H|||%s|||%b|||%an|||%ae' 2>/dev/null || echo "")
+
+if [ -z "$COMMITS" ]; then
+ warn "No commits found in range ${COMMIT_RANGE}"
+fi
+
+# Create temporary files for categorized commits
+TMP_DIR=$(mktemp -d)
+trap 'rm -rf "$TMP_DIR"' EXIT
+
+BREAKING_FILE="${TMP_DIR}/breaking.txt"
+FEAT_FILE="${TMP_DIR}/feat.txt"
+FIX_FILE="${TMP_DIR}/fix.txt"
+DOCS_FILE="${TMP_DIR}/docs.txt"
+PERF_FILE="${TMP_DIR}/perf.txt"
+REFACTOR_FILE="${TMP_DIR}/refactor.txt"
+TEST_FILE="${TMP_DIR}/test.txt"
+BUILD_FILE="${TMP_DIR}/build.txt"
+CI_FILE="${TMP_DIR}/ci.txt"
+CHORE_FILE="${TMP_DIR}/chore.txt"
+STYLE_FILE="${TMP_DIR}/style.txt"
+REVERT_FILE="${TMP_DIR}/revert.txt"
+OTHER_FILE="${TMP_DIR}/other.txt"
+
+touch "$BREAKING_FILE" "$FEAT_FILE" "$FIX_FILE" "$DOCS_FILE" "$PERF_FILE" \
+ "$REFACTOR_FILE" "$TEST_FILE" "$BUILD_FILE" "$CI_FILE" "$CHORE_FILE" \
+ "$STYLE_FILE" "$REVERT_FILE" "$OTHER_FILE"
+
+# Parse commits
+while IFS='|||' read -r hash subject body author email; do
+ [ -z "$hash" ] && continue
+
+ # Check for breaking changes
+ is_breaking=0
+ if printf '%s' "$subject" | grep -qE '^[a-z]+!:'; then
+ is_breaking=1
+ fi
+ if printf '%s' "$body" | grep -qiE '^BREAKING CHANGE:'; then
+ is_breaking=1
+ fi
+
+ # Extract commit type
+ commit_type=$(printf '%s' "$subject" | sed -nE 's/^([a-z]+)(!?):.*/\1/p')
+
+ # Clean subject (remove type prefix)
+ clean_subject=$(printf '%s' "$subject" | sed -E 's/^[a-z]+!?: *//')
+
+ # Format: - Subject (hash) by Author
+ short_hash=$(printf '%s' "$hash" | cut -c1-8)
+ formatted_commit="- ${clean_subject} (\`${short_hash}\`) by ${author}"
+
+ # Categorize
+ if [ "$is_breaking" = "1" ]; then
+ printf '%s\n' "$formatted_commit" >> "$BREAKING_FILE"
+ fi
+
+ case "$commit_type" in
+ feat|feature)
+ printf '%s\n' "$formatted_commit" >> "$FEAT_FILE"
+ ;;
+ fix)
+ printf '%s\n' "$formatted_commit" >> "$FIX_FILE"
+ ;;
+ docs|doc)
+ printf '%s\n' "$formatted_commit" >> "$DOCS_FILE"
+ ;;
+ perf|performance)
+ printf '%s\n' "$formatted_commit" >> "$PERF_FILE"
+ ;;
+ refactor)
+ printf '%s\n' "$formatted_commit" >> "$REFACTOR_FILE"
+ ;;
+ test|tests)
+ printf '%s\n' "$formatted_commit" >> "$TEST_FILE"
+ ;;
+ build)
+ printf '%s\n' "$formatted_commit" >> "$BUILD_FILE"
+ ;;
+ ci)
+ printf '%s\n' "$formatted_commit" >> "$CI_FILE"
+ ;;
+ chore)
+ printf '%s\n' "$formatted_commit" >> "$CHORE_FILE"
+ ;;
+ style)
+ printf '%s\n' "$formatted_commit" >> "$STYLE_FILE"
+ ;;
+ revert)
+ printf '%s\n' "$formatted_commit" >> "$REVERT_FILE"
+ ;;
+ *)
+ # If no conventional type, add to other
+ printf '%s\n' "- ${subject} (\`${short_hash}\`) by ${author}" >> "$OTHER_FILE"
+ ;;
+ esac
+done << EOF
+$COMMITS
+EOF
+
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+# Output Generation
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+generate_output() {
+ # Header
+ if [ "$FORMAT" = "github" ]; then
+ printf "## ๐ง
Tor Guard Relay v%s\n\n" "$VERSION"
+ elif [ "$FORMAT" = "markdown" ]; then
+ printf "## [v%s] - %s\n\n" "$VERSION" "$(date +%Y-%m-%d)"
+ else
+ printf "Tor Guard Relay v%s - Release Notes\n\n" "$VERSION"
+ fi
+
+ # Breaking changes (always show if present)
+ if [ -s "$BREAKING_FILE" ]; then
+ if [ "$USE_EMOJI" = "1" ]; then
+ printf "### ๐จ BREAKING CHANGES\n\n"
+ else
+ printf "### BREAKING CHANGES\n\n"
+ fi
+ cat "$BREAKING_FILE"
+ printf "\n"
+ fi
+
+ # If breaking-only mode, stop here
+ if [ "$BREAKING_ONLY" = "1" ]; then
+ return 0
+ fi
+
+ # Features
+ if [ -s "$FEAT_FILE" ]; then
+ if [ "$USE_EMOJI" = "1" ]; then
+ printf "### โจ Features\n\n"
+ else
+ printf "### Features\n\n"
+ fi
+ cat "$FEAT_FILE"
+ printf "\n"
+ fi
+
+ # Bug fixes
+ if [ -s "$FIX_FILE" ]; then
+ if [ "$USE_EMOJI" = "1" ]; then
+ printf "### ๐ Bug Fixes\n\n"
+ else
+ printf "### Bug Fixes\n\n"
+ fi
+ cat "$FIX_FILE"
+ printf "\n"
+ fi
+
+ # Performance
+ if [ -s "$PERF_FILE" ]; then
+ if [ "$USE_EMOJI" = "1" ]; then
+ printf "### โก Performance\n\n"
+ else
+ printf "### Performance\n\n"
+ fi
+ cat "$PERF_FILE"
+ printf "\n"
+ fi
+
+ # Documentation
+ if [ -s "$DOCS_FILE" ]; then
+ if [ "$USE_EMOJI" = "1" ]; then
+ printf "### ๐ Documentation\n\n"
+ else
+ printf "### Documentation\n\n"
+ fi
+ cat "$DOCS_FILE"
+ printf "\n"
+ fi
+
+ # Refactoring
+ if [ -s "$REFACTOR_FILE" ]; then
+ if [ "$USE_EMOJI" = "1" ]; then
+ printf "### โป๏ธ Refactoring\n\n"
+ else
+ printf "### Refactoring\n\n"
+ fi
+ cat "$REFACTOR_FILE"
+ printf "\n"
+ fi
+
+ # CI/CD
+ if [ -s "$CI_FILE" ]; then
+ if [ "$USE_EMOJI" = "1" ]; then
+ printf "### ๐ท CI/CD\n\n"
+ else
+ printf "### CI/CD\n\n"
+ fi
+ cat "$CI_FILE"
+ printf "\n"
+ fi
+
+ # Build system
+ if [ -s "$BUILD_FILE" ]; then
+ if [ "$USE_EMOJI" = "1" ]; then
+ printf "### ๐๏ธ Build System\n\n"
+ else
+ printf "### Build System\n\n"
+ fi
+ cat "$BUILD_FILE"
+ printf "\n"
+ fi
+
+ # Testing
+ if [ -s "$TEST_FILE" ]; then
+ if [ "$USE_EMOJI" = "1" ]; then
+ printf "### โ
Testing\n\n"
+ else
+ printf "### Testing\n\n"
+ fi
+ cat "$TEST_FILE"
+ printf "\n"
+ fi
+
+ # Maintenance
+ if [ -s "$CHORE_FILE" ]; then
+ if [ "$USE_EMOJI" = "1" ]; then
+ printf "### ๐ง Maintenance\n\n"
+ else
+ printf "### Maintenance\n\n"
+ fi
+ cat "$CHORE_FILE"
+ printf "\n"
+ fi
+
+ # Style changes
+ if [ -s "$STYLE_FILE" ]; then
+ if [ "$USE_EMOJI" = "1" ]; then
+ printf "### ๐ Style\n\n"
+ else
+ printf "### Style\n\n"
+ fi
+ cat "$STYLE_FILE"
+ printf "\n"
+ fi
+
+ # Reverts
+ if [ -s "$REVERT_FILE" ]; then
+ if [ "$USE_EMOJI" = "1" ]; then
+ printf "### โช Reverts\n\n"
+ else
+ printf "### Reverts\n\n"
+ fi
+ cat "$REVERT_FILE"
+ printf "\n"
+ fi
+
+ # Other commits (non-conventional)
+ if [ -s "$OTHER_FILE" ]; then
+ if [ "$USE_EMOJI" = "1" ]; then
+ printf "### ๐ฆ Other Changes\n\n"
+ else
+ printf "### Other Changes\n\n"
+ fi
+ cat "$OTHER_FILE"
+ printf "\n"
+ fi
+
+ # Footer with metadata
+ if [ "$FORMAT" = "github" ] || [ "$FORMAT" = "markdown" ]; then
+ printf "---\n\n"
+ printf "**Full Changelog**: %s...v%s\n" "$PREV_VERSION" "$VERSION"
+ fi
+}
+
+# Generate and output
+if [ -n "$OUTPUT_FILE" ]; then
+ generate_output > "$OUTPUT_FILE"
+ success "Release notes written to: ${OUTPUT_FILE}"
+else
+ generate_output
+fi
diff --git a/scripts/release/update-version.sh b/scripts/release/update-version.sh
new file mode 100644
index 0000000..0c6358f
--- /dev/null
+++ b/scripts/release/update-version.sh
@@ -0,0 +1,325 @@
+#!/bin/sh
+# Auto-update version numbers across all documentation and templates
+# Updates: README.md, templates/*.yml, templates/*.json, docs/*.md, CHANGELOG.md
+
+set -e
+
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+# Color Output
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+CYAN='\033[0;36m'
+BOLD='\033[1m'
+NC='\033[0m'
+
+log() { printf "${BLUE}โน${NC} %s\n" "$*"; }
+success() { printf "${GREEN}โ
${NC} %s\n" "$*"; }
+warn() { printf "${YELLOW}โ ${NC} %s\n" "$*"; }
+error() { printf "${RED}โ${NC} %s\n" "$*"; }
+die() { error "$*"; exit 1; }
+
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+# Usage
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+usage() {
+ cat << EOF
+Usage: $(basename "$0") [OPTIONS]
+
+Auto-update version numbers across all documentation and templates.
+
+Arguments:
+ new_version New version number (e.g., 1.1.2 or v1.1.2)
+
+Options:
+ --dry-run Show what would be changed without modifying files
+ --no-backup Don't create .bak files before modifying
+ -h, --help Show this help
+
+Examples:
+ $(basename "$0") 1.1.2
+ $(basename "$0") --dry-run 1.2.0
+ $(basename "$0") --no-backup v1.1.2
+
+Files Updated:
+ - README.md (version badges, examples)
+ - CHANGELOG.md (unreleased section header)
+ - templates/*.yml (image tags)
+ - templates/*.json (image tags in Cosmos templates)
+ - docs/*.md (version references)
+ - CLAUDE.md (version references)
+
+EOF
+ exit 0
+}
+
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+# Argument Parsing
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+DRY_RUN=0
+CREATE_BACKUP=1
+NEW_VERSION=""
+
+while [ $# -gt 0 ]; do
+ case "$1" in
+ -h|--help)
+ usage
+ ;;
+ --dry-run)
+ DRY_RUN=1
+ shift
+ ;;
+ --no-backup)
+ CREATE_BACKUP=0
+ shift
+ ;;
+ -*)
+ die "Unknown option: $1 (use --help for usage)"
+ ;;
+ *)
+ if [ -z "$NEW_VERSION" ]; then
+ NEW_VERSION="$1"
+ else
+ die "Too many arguments (use --help for usage)"
+ fi
+ shift
+ ;;
+ esac
+done
+
+if [ -z "$NEW_VERSION" ]; then
+ die "Version is required (use --help for usage)"
+fi
+
+# Strip 'v' prefix if present
+NEW_VERSION="${NEW_VERSION#v}"
+
+# Validate version format (semver)
+if ! printf '%s' "$NEW_VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$'; then
+ die "Invalid version format: ${NEW_VERSION} (expected: X.Y.Z or X.Y.Z-suffix)"
+fi
+
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+# Get Current Version
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+# Try to get current version from git tags
+if git rev-parse --git-dir >/dev/null 2>&1; then
+ CURRENT_VERSION=$(git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || echo "")
+else
+ CURRENT_VERSION=""
+fi
+
+# If no git tags, try to extract from README.md
+if [ -z "$CURRENT_VERSION" ] && [ -f "README.md" ]; then
+ CURRENT_VERSION=$(grep -oE 'onion-relay:[0-9]+\.[0-9]+\.[0-9]+' README.md | head -1 | cut -d: -f2 || echo "")
+fi
+
+if [ -z "$CURRENT_VERSION" ]; then
+ warn "Could not detect current version, will replace all version-like patterns"
+ CURRENT_VERSION="X.Y.Z"
+else
+ log "Current version detected: ${CURRENT_VERSION}"
+fi
+
+log "New version: ${NEW_VERSION}"
+
+if [ "$DRY_RUN" = "1" ]; then
+ warn "DRY RUN MODE - No files will be modified"
+fi
+
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+# File Update Functions
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+update_file() {
+ file="$1"
+
+ if [ ! -f "$file" ]; then
+ warn "File not found: ${file}"
+ return 1
+ fi
+
+ if [ "$DRY_RUN" = "1" ]; then
+ log "[DRY RUN] Would update: ${file}"
+ # Show what would change
+ if [ "$CURRENT_VERSION" != "X.Y.Z" ]; then
+ matches=$(grep -c "${CURRENT_VERSION}" "$file" 2>/dev/null || echo "0")
+ if [ "$matches" -gt 0 ]; then
+ log " Found ${matches} occurrence(s) of ${CURRENT_VERSION}"
+ fi
+ fi
+ return 0
+ fi
+
+ # Create backup if enabled
+ if [ "$CREATE_BACKUP" = "1" ]; then
+ cp "$file" "${file}.bak"
+ fi
+
+ # Replace version numbers
+ # Pattern 1: onion-relay:X.Y.Z โ onion-relay:NEW_VERSION
+ sed -i "s|onion-relay:${CURRENT_VERSION}|onion-relay:${NEW_VERSION}|g" "$file" 2>/dev/null || true
+
+ # Pattern 2: /onion-relay:X.Y.Z โ /onion-relay:NEW_VERSION
+ sed -i "s|/onion-relay:${CURRENT_VERSION}|/onion-relay:${NEW_VERSION}|g" "$file" 2>/dev/null || true
+
+ # Pattern 3: vX.Y.Z (in headings, badges, etc.)
+ sed -i "s|v${CURRENT_VERSION}|v${NEW_VERSION}|g" "$file" 2>/dev/null || true
+
+ # Pattern 4: Version X.Y.Z
+ sed -i "s|Version ${CURRENT_VERSION}|Version ${NEW_VERSION}|g" "$file" 2>/dev/null || true
+
+ # Pattern 5: version: "X.Y.Z" (YAML)
+ sed -i "s|version: \"${CURRENT_VERSION}\"|version: \"${NEW_VERSION}\"|g" "$file" 2>/dev/null || true
+
+ # Pattern 6: [vX.Y.Z] (changelog format)
+ sed -i "s|\[v${CURRENT_VERSION}\]|[v${NEW_VERSION}]|g" "$file" 2>/dev/null || true
+
+ success "Updated: ${file}"
+ return 0
+}
+
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+# Main Update Logic
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+update_count=0
+skip_count=0
+
+# Update README.md
+if [ -f "README.md" ]; then
+ log "Updating README.md..."
+ if update_file "README.md"; then
+ update_count=$((update_count + 1))
+ else
+ skip_count=$((skip_count + 1))
+ fi
+fi
+
+# Update CHANGELOG.md (add new version header if doesn't exist)
+if [ -f "CHANGELOG.md" ]; then
+ log "Updating CHANGELOG.md..."
+
+ if [ "$DRY_RUN" = "1" ]; then
+ log "[DRY RUN] Would add version header to CHANGELOG.md"
+ else
+ # Check if version already exists
+ if grep -q "## \[v${NEW_VERSION}\]" CHANGELOG.md || grep -q "## v${NEW_VERSION}" CHANGELOG.md; then
+ log "Version ${NEW_VERSION} already in CHANGELOG.md"
+ else
+ # Insert new version header after ## [Unreleased] section
+ if grep -q "## \[Unreleased\]" CHANGELOG.md; then
+ # Create backup
+ if [ "$CREATE_BACKUP" = "1" ]; then
+ cp CHANGELOG.md CHANGELOG.md.bak
+ fi
+
+ # Insert new version after Unreleased section
+ awk -v version="$NEW_VERSION" -v date="$(date +%Y-%m-%d)" '
+ /^## \[Unreleased\]/ {
+ print
+ print ""
+ print "## [v" version "] - " date
+ next
+ }
+ { print }
+ ' CHANGELOG.md > CHANGELOG.md.tmp && mv CHANGELOG.md.tmp CHANGELOG.md
+
+ success "Added version ${NEW_VERSION} to CHANGELOG.md"
+ else
+ warn "No [Unreleased] section found in CHANGELOG.md"
+ fi
+ fi
+ fi
+ update_count=$((update_count + 1))
+fi
+
+# Update templates/*.yml
+if [ -d "templates" ]; then
+ log "Updating Docker Compose templates..."
+ for file in templates/*.yml; do
+ [ -f "$file" ] || continue
+ if update_file "$file"; then
+ update_count=$((update_count + 1))
+ else
+ skip_count=$((skip_count + 1))
+ fi
+ done
+fi
+
+# Update templates/*.json (Cosmos templates)
+if [ -d "templates" ]; then
+ log "Updating Cosmos Cloud templates..."
+ for file in templates/*.json; do
+ [ -f "$file" ] || continue
+ if update_file "$file"; then
+ update_count=$((update_count + 1))
+ else
+ skip_count=$((skip_count + 1))
+ fi
+ done
+fi
+
+# Update docs/*.md
+if [ -d "docs" ]; then
+ log "Updating documentation files..."
+ for file in docs/*.md; do
+ [ -f "$file" ] || continue
+ if update_file "$file"; then
+ update_count=$((update_count + 1))
+ else
+ skip_count=$((skip_count + 1))
+ fi
+ done
+fi
+
+# Update CLAUDE.md
+if [ -f "CLAUDE.md" ]; then
+ log "Updating CLAUDE.md..."
+ if update_file "CLAUDE.md"; then
+ update_count=$((update_count + 1))
+ else
+ skip_count=$((skip_count + 1))
+ fi
+fi
+
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+# Summary
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+printf "\n"
+printf "${GREEN}${BOLD}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ${NC}\n"
+if [ "$DRY_RUN" = "1" ]; then
+ printf "${CYAN}${BOLD}Version Update Summary (DRY RUN)${NC}\n"
+else
+ printf "${GREEN}${BOLD}Version Update Complete${NC}\n"
+fi
+printf "${GREEN}${BOLD}โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ${NC}\n\n"
+
+printf " ${BOLD}Current Version:${NC} %s\n" "$CURRENT_VERSION"
+printf " ${BOLD}New Version:${NC} %s\n" "$NEW_VERSION"
+printf " ${BOLD}Files Updated:${NC} %d\n" "$update_count"
+if [ "$skip_count" -gt 0 ]; then
+ printf " ${BOLD}Files Skipped:${NC} %d\n" "$skip_count"
+fi
+
+if [ "$DRY_RUN" = "1" ]; then
+ printf "\n${YELLOW}To apply changes, run without --dry-run${NC}\n"
+elif [ "$CREATE_BACKUP" = "1" ]; then
+ printf "\n${BLUE}Backup files created with .bak extension${NC}\n"
+ printf "${BLUE}To restore: for f in *.bak; do mv \"\$f\" \"\${f%.bak}\"; done${NC}\n"
+fi
+
+printf "\n${BOLD}Next Steps:${NC}\n"
+printf " 1. Review changes: ${CYAN}git diff${NC}\n"
+printf " 2. Update CHANGELOG.md with release notes\n"
+printf " 3. Commit changes: ${CYAN}git add -A && git commit -m \"chore: bump version to ${NEW_VERSION}\"${NC}\n"
+printf " 4. Create tag: ${CYAN}git tag -a v${NEW_VERSION} -m \"Release v${NEW_VERSION}\"${NC}\n"
+printf " 5. Push: ${CYAN}git push && git push --tags${NC}\n"
+printf "\n"
diff --git a/scripts/utilities/quick-start.sh b/scripts/utilities/quick-start.sh
new file mode 100644
index 0000000..3e9fea3
--- /dev/null
+++ b/scripts/utilities/quick-start.sh
@@ -0,0 +1,625 @@
+#!/bin/sh
+# quick-start.sh - Interactive Tor relay deployment script
+# Helps beginners set up guard, exit, or bridge relays
+
+set -e
+
+# Color output helpers (if terminal supports it)
+if [ -t 1 ]; then
+ BOLD='\033[1m'
+ GREEN='\033[0;32m'
+ BLUE='\033[0;34m'
+ YELLOW='\033[1;33m'
+ RED='\033[0;31m'
+ NC='\033[0m' # No Color
+else
+ BOLD=''
+ GREEN=''
+ BLUE=''
+ YELLOW=''
+ RED=''
+ NC=''
+fi
+
+# Logging helpers
+log() { printf "${BOLD}%s${NC}\n" "$1"; }
+info() { printf " ${BLUE}โน๏ธ %s${NC}\n" "$1"; }
+success() { printf "${GREEN}โ
%s${NC}\n" "$1"; }
+warn() { printf "${YELLOW}โ ๏ธ %s${NC}\n" "$1"; }
+error() { printf "${RED}๐ ERROR: %s${NC}\n" "$1"; exit 1; }
+
+# Banner
+show_banner() {
+ cat << "EOF"
+โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+๐ง
Tor Guard Relay - Quick Start Setup
+โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+This script will help you deploy a Tor relay in minutes!
+
+EOF
+}
+
+# Prompt for input with default value
+prompt() {
+ prompt_text="$1"
+ default_val="$2"
+ var_name="$3"
+
+ if [ -n "$default_val" ]; then
+ printf "${BOLD}%s${NC} [${GREEN}%s${NC}]: " "$prompt_text" "$default_val"
+ else
+ printf "${BOLD}%s${NC}: " "$prompt_text"
+ fi
+
+ read -r input
+ if [ -z "$input" ]; then
+ input="$default_val"
+ fi
+
+ eval "$var_name=\"\$input\""
+}
+
+# Validate nickname
+validate_nickname() {
+ nickname="$1"
+
+ # Length check (1-19 characters)
+ nickname_len=$(printf "%s" "$nickname" | wc -c)
+ if [ "$nickname_len" -lt 1 ] || [ "$nickname_len" -gt 19 ]; then
+ warn "Nickname must be 1-19 characters (you entered: $nickname_len)"
+ return 1
+ fi
+
+ # Alphanumeric only
+ if ! printf "%s" "$nickname" | grep -qE '^[a-zA-Z0-9]+$'; then
+ warn "Nickname must contain only letters and numbers"
+ return 1
+ fi
+
+ # Reserved names
+ case "$(printf "%s" "$nickname" | tr '[:upper:]' '[:lower:]')" in
+ unnamed|noname|default|tor|relay|bridge|exit)
+ warn "Cannot use reserved name: $nickname"
+ return 1
+ ;;
+ esac
+
+ return 0
+}
+
+# Validate email
+validate_email() {
+ email="$1"
+
+ # Basic length check
+ email_len=$(printf "%s" "$email" | wc -c)
+ if [ "$email_len" -lt 5 ]; then
+ warn "Email must be at least 5 characters"
+ return 1
+ fi
+
+ # Very basic email format check
+ if ! printf "%s" "$email" | grep -qE '^[^@]+@[^@]+\.[^@]+$'; then
+ warn "Email format appears invalid (should be: name@domain.com)"
+ return 1
+ fi
+
+ return 0
+}
+
+# Validate port
+validate_port() {
+ port="$1"
+ port_name="$2"
+
+ # Integer check
+ if ! printf "%s" "$port" | grep -qE '^[0-9]+$'; then
+ warn "$port_name must be a valid number"
+ return 1
+ fi
+
+ # Range check
+ if [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then
+ warn "$port_name must be between 1-65535"
+ return 1
+ fi
+
+ # Warn about privileged ports
+ if [ "$port" -lt 1024 ]; then
+ warn "$port_name is a privileged port (<1024) - may require CAP_NET_BIND_SERVICE capability"
+ fi
+
+ return 0
+}
+
+# Main menu
+show_menu() {
+ log ""
+ log "What type of Tor relay do you want to run?"
+ log ""
+ printf " ${BOLD}1)${NC} ${GREEN}Guard/Middle Relay${NC} - Safest option, routes traffic\n"
+ printf " ${BOLD}2)${NC} ${YELLOW}Exit Relay${NC} - Advanced, requires legal compliance\n"
+ printf " ${BOLD}3)${NC} ${BLUE}Bridge Relay${NC} - Helps users in censored regions (obfs4)\n"
+ printf " ${BOLD}4)${NC} ${RED}Quit${NC}\n"
+ log ""
+}
+
+# Collect common information
+collect_common_info() {
+ log ""
+ log "๐ Basic Relay Information"
+ log "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
+ log ""
+
+ # Nickname
+ while true; do
+ prompt "Relay Nickname (1-19 alphanumeric)" "MyTorRelay" NICKNAME
+ if validate_nickname "$NICKNAME"; then
+ success "Nickname accepted: $NICKNAME"
+ break
+ fi
+ done
+
+ log ""
+
+ # Email
+ while true; do
+ prompt "Contact Email" "admin@example.com" EMAIL
+ if validate_email "$EMAIL"; then
+ success "Email accepted: $EMAIL"
+ break
+ fi
+ done
+
+ log ""
+
+ # ORPort
+ while true; do
+ if [ "$RELAY_MODE" = "guard" ]; then
+ prompt "ORPort (recommended: 443 or 9001)" "9001" OR_PORT
+ elif [ "$RELAY_MODE" = "exit" ]; then
+ prompt "ORPort (recommended: 443)" "443" OR_PORT
+ else
+ prompt "ORPort (recommended: 443 or 9001)" "9001" OR_PORT
+ fi
+
+ if validate_port "$OR_PORT" "ORPort"; then
+ success "ORPort set to: $OR_PORT"
+ break
+ fi
+ done
+}
+
+# Guard relay specific
+collect_guard_info() {
+ log ""
+ log "โ๏ธ Guard Relay Configuration"
+ log "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
+ log ""
+
+ info "Guard relays provide directory services and route traffic."
+ info "They are the safest and easiest type of relay to operate."
+ log ""
+
+ # DirPort
+ while true; do
+ prompt "DirPort (set to 0 to disable)" "9030" DIR_PORT
+
+ if [ "$DIR_PORT" = "0" ]; then
+ success "DirPort disabled"
+ break
+ fi
+
+ if validate_port "$DIR_PORT" "DirPort"; then
+ success "DirPort set to: $DIR_PORT"
+ break
+ fi
+ done
+
+ log ""
+
+ # Bandwidth
+ prompt "Bandwidth Rate (e.g., 50 MBytes, leave empty for unlimited)" "" BANDWIDTH_RATE
+ if [ -n "$BANDWIDTH_RATE" ]; then
+ prompt "Bandwidth Burst (e.g., 100 MBytes)" "" BANDWIDTH_BURST
+ fi
+}
+
+# Exit relay specific
+collect_exit_info() {
+ log ""
+ log "โ ๏ธ Exit Relay Configuration"
+ log "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
+ log ""
+
+ warn "EXIT RELAYS ARE ADVANCED AND HAVE LEGAL IMPLICATIONS!"
+ log ""
+ info "Exit relays route traffic to the public internet."
+ info "You WILL receive abuse complaints and may need to respond to legal requests."
+ info "Make sure you understand the risks and legal requirements."
+ log ""
+
+ # Confirmation
+ prompt "Are you sure you want to run an exit relay? (yes/no)" "no" CONFIRM
+
+ if [ "$CONFIRM" != "yes" ] && [ "$CONFIRM" != "YES" ]; then
+ error "Exit relay setup cancelled. Consider running a guard or bridge relay instead."
+ fi
+
+ log ""
+
+ # DirPort
+ while true; do
+ prompt "DirPort (set to 0 to disable)" "9030" DIR_PORT
+
+ if [ "$DIR_PORT" = "0" ]; then
+ success "DirPort disabled"
+ break
+ fi
+
+ if validate_port "$DIR_PORT" "DirPort"; then
+ success "DirPort set to: $DIR_PORT"
+ break
+ fi
+ done
+
+ log ""
+
+ # Exit policy
+ log "Exit Policy Options:"
+ printf " ${BOLD}1)${NC} Reduced exit (recommended) - allows common ports\n"
+ printf " ${BOLD}2)${NC} Reject all (safer) - effectively a guard relay\n"
+ printf " ${BOLD}3)${NC} Custom policy - you provide the policy\n"
+ log ""
+
+ prompt "Exit Policy Choice" "1" POLICY_CHOICE
+
+ case "$POLICY_CHOICE" in
+ 1)
+ EXIT_POLICY="ExitPolicy accept *:80\\nExitPolicy accept *:443\\nExitPolicy reject *:*"
+ info "Using reduced exit policy (HTTP/HTTPS only)"
+ ;;
+ 2)
+ EXIT_POLICY="ExitPolicy reject *:*"
+ info "Using reject all policy"
+ ;;
+ 3)
+ prompt "Enter custom exit policy (e.g., 'ExitPolicy reject *:*')" "ExitPolicy reject *:*" EXIT_POLICY
+ ;;
+ *)
+ EXIT_POLICY="ExitPolicy reject *:*"
+ warn "Invalid choice, using reject all"
+ ;;
+ esac
+
+ log ""
+
+ # Bandwidth
+ prompt "Bandwidth Rate (e.g., 50 MBytes, leave empty for unlimited)" "" BANDWIDTH_RATE
+ if [ -n "$BANDWIDTH_RATE" ]; then
+ prompt "Bandwidth Burst (e.g., 100 MBytes)" "" BANDWIDTH_BURST
+ fi
+}
+
+# Bridge relay specific
+collect_bridge_info() {
+ log ""
+ log "๐ Bridge Relay Configuration"
+ log "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
+ log ""
+
+ info "Bridge relays help users in censored regions access Tor."
+ info "They use obfs4 pluggable transport to disguise Tor traffic."
+ log ""
+
+ # PT_PORT (obfs4 port)
+ while true; do
+ prompt "obfs4 Port (PT_PORT, recommended: 443 or 9002)" "9002" PT_PORT
+
+ if validate_port "$PT_PORT" "PT_PORT"; then
+ success "obfs4 Port set to: $PT_PORT"
+ break
+ fi
+ done
+
+ log ""
+
+ # Bandwidth
+ prompt "Bandwidth Rate (e.g., 50 MBytes, leave empty for unlimited)" "" BANDWIDTH_RATE
+ if [ -n "$BANDWIDTH_RATE" ]; then
+ prompt "Bandwidth Burst (e.g., 100 MBytes)" "" BANDWIDTH_BURST
+ fi
+}
+
+# Generate docker run command
+generate_docker_run() {
+ log ""
+ log "๐ณ Docker Run Command"
+ log "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
+ log ""
+
+ cat > /tmp/tor-relay-run.sh << EOF
+#!/bin/sh
+# Tor ${RELAY_MODE} Relay - Generated by quick-start.sh
+# Run this script to start your Tor relay
+
+docker run -d \\
+ --name tor-${RELAY_MODE} \\
+ --network host \\
+ --restart unless-stopped \\
+ --cap-drop ALL \\
+ --cap-add NET_BIND_SERVICE \\
+ --security-opt no-new-privileges \\
+ -e NICKNAME="${NICKNAME}" \\
+ -e EMAIL="${EMAIL}" \\
+ -e OR_PORT=${OR_PORT} \\
+EOF
+
+ # Mode-specific additions
+ if [ "$RELAY_MODE" = "guard" ]; then
+ cat >> /tmp/tor-relay-run.sh << EOF
+ -e TOR_RELAY_MODE=guard \\
+ -e TOR_DIRPORT=${DIR_PORT} \\
+EOF
+ elif [ "$RELAY_MODE" = "exit" ]; then
+ cat >> /tmp/tor-relay-run.sh << EOF
+ -e TOR_RELAY_MODE=exit \\
+ -e TOR_DIRPORT=${DIR_PORT} \\
+ -e TOR_EXIT_POLICY="${EXIT_POLICY}" \\
+EOF
+ elif [ "$RELAY_MODE" = "bridge" ]; then
+ cat >> /tmp/tor-relay-run.sh << EOF
+ -e PT_PORT=${PT_PORT} \\
+EOF
+ fi
+
+ # Bandwidth (if set)
+ if [ -n "$BANDWIDTH_RATE" ]; then
+ cat >> /tmp/tor-relay-run.sh << EOF
+ -e TOR_BANDWIDTH_RATE="${BANDWIDTH_RATE}" \\
+EOF
+ fi
+
+ if [ -n "$BANDWIDTH_BURST" ]; then
+ cat >> /tmp/tor-relay-run.sh << EOF
+ -e TOR_BANDWIDTH_BURST="${BANDWIDTH_BURST}" \\
+EOF
+ fi
+
+ # Volumes and image
+ cat >> /tmp/tor-relay-run.sh << EOF
+ -v tor-${RELAY_MODE}-data:/var/lib/tor \\
+ -v tor-${RELAY_MODE}-logs:/var/log/tor \\
+ r3bo0tbx1/onion-relay:latest
+
+# Wait for container to start
+sleep 5
+
+# Show container status
+docker ps | grep tor-${RELAY_MODE}
+
+echo ""
+echo "โ
Tor ${RELAY_MODE} relay started!"
+echo ""
+echo "๐ Useful commands:"
+echo " docker logs tor-${RELAY_MODE} - View logs"
+echo " docker exec tor-${RELAY_MODE} status - Full health check"
+echo " docker exec tor-${RELAY_MODE} health - JSON health API"
+EOF
+
+ if [ "$RELAY_MODE" = "bridge" ]; then
+ cat >> /tmp/tor-relay-run.sh << EOF
+echo " docker exec tor-${RELAY_MODE} bridge-line - Get bridge line to share"
+EOF
+ else
+ cat >> /tmp/tor-relay-run.sh << EOF
+echo " docker exec tor-${RELAY_MODE} fingerprint - Get relay fingerprint"
+EOF
+ fi
+
+ chmod +x /tmp/tor-relay-run.sh
+
+ success "Docker run script generated: /tmp/tor-relay-run.sh"
+ log ""
+ cat /tmp/tor-relay-run.sh
+ log ""
+}
+
+# Generate docker-compose.yml
+generate_docker_compose() {
+ log ""
+ log "๐ณ Docker Compose Configuration"
+ log "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
+ log ""
+
+ cat > /tmp/docker-compose.yml << EOF
+# Tor ${RELAY_MODE} Relay - Generated by quick-start.sh
+# Deploy with: docker-compose up -d
+
+version: '3.8'
+
+services:
+ tor-${RELAY_MODE}:
+ image: r3bo0tbx1/onion-relay:latest
+ container_name: tor-${RELAY_MODE}
+ restart: unless-stopped
+ network_mode: host
+
+ # Security settings
+ cap_drop:
+ - ALL
+ cap_add:
+ - NET_BIND_SERVICE
+ security_opt:
+ - no-new-privileges:true
+
+ # Environment variables
+ environment:
+ NICKNAME: "${NICKNAME}"
+ EMAIL: "${EMAIL}"
+ OR_PORT: ${OR_PORT}
+EOF
+
+ # Mode-specific additions
+ if [ "$RELAY_MODE" = "guard" ]; then
+ cat >> /tmp/docker-compose.yml << EOF
+ TOR_RELAY_MODE: guard
+ TOR_DIRPORT: ${DIR_PORT}
+EOF
+ elif [ "$RELAY_MODE" = "exit" ]; then
+ cat >> /tmp/docker-compose.yml << EOF
+ TOR_RELAY_MODE: exit
+ TOR_DIRPORT: ${DIR_PORT}
+ TOR_EXIT_POLICY: "${EXIT_POLICY}"
+EOF
+ elif [ "$RELAY_MODE" = "bridge" ]; then
+ cat >> /tmp/docker-compose.yml << EOF
+ PT_PORT: ${PT_PORT}
+EOF
+ fi
+
+ # Bandwidth (if set)
+ if [ -n "$BANDWIDTH_RATE" ]; then
+ cat >> /tmp/docker-compose.yml << EOF
+ TOR_BANDWIDTH_RATE: "${BANDWIDTH_RATE}"
+EOF
+ fi
+
+ if [ -n "$BANDWIDTH_BURST" ]; then
+ cat >> /tmp/docker-compose.yml << EOF
+ TOR_BANDWIDTH_BURST: "${BANDWIDTH_BURST}"
+EOF
+ fi
+
+ # Volumes
+ cat >> /tmp/docker-compose.yml << EOF
+
+ # Data persistence
+ volumes:
+ - tor-${RELAY_MODE}-data:/var/lib/tor
+ - tor-${RELAY_MODE}-logs:/var/log/tor
+
+volumes:
+ tor-${RELAY_MODE}-data:
+ driver: local
+ tor-${RELAY_MODE}-logs:
+ driver: local
+EOF
+
+ success "Docker Compose file generated: /tmp/docker-compose.yml"
+ log ""
+ cat /tmp/docker-compose.yml
+ log ""
+}
+
+# Next steps
+show_next_steps() {
+ log ""
+ log "๐ Next Steps"
+ log "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
+ log ""
+
+ log "1. Review the generated configuration:"
+ info " - Docker run script: /tmp/tor-relay-run.sh"
+ info " - Docker Compose file: /tmp/docker-compose.yml"
+ log ""
+
+ log "2. Deploy your relay:"
+ info " ${BOLD}Option A:${NC} Run the script"
+ info " sh /tmp/tor-relay-run.sh"
+ log ""
+ info " ${BOLD}Option B:${NC} Use Docker Compose"
+ info " cp /tmp/docker-compose.yml ~/tor-relay/"
+ info " cd ~/tor-relay && docker-compose up -d"
+ log ""
+
+ log "3. Monitor your relay:"
+ info " docker logs tor-${RELAY_MODE}"
+ info " docker exec tor-${RELAY_MODE} status"
+ log ""
+
+ if [ "$RELAY_MODE" = "bridge" ]; then
+ log "4. Share your bridge line:"
+ info " Wait 2-5 minutes for Tor to bootstrap (100%)"
+ info " docker exec tor-${RELAY_MODE} bridge-line"
+ info " Share the output with users in censored regions"
+ log ""
+ else
+ log "4. Find your relay on Tor Metrics:"
+ info " Wait 24-48 hours for your relay to appear"
+ info " docker exec tor-${RELAY_MODE} fingerprint"
+ info " Visit the Tor Metrics URL shown"
+ log ""
+ fi
+
+ if [ "$RELAY_MODE" = "exit" ]; then
+ warn "IMPORTANT FOR EXIT RELAYS:"
+ info " - Monitor abuse complaints: ${EMAIL}"
+ info " - Set up reverse DNS (PTR record) for your IP"
+ info " - Consider getting AS number and WHOIS info"
+ info " - Read: https://community.torproject.org/relay/community-resources/tor-exit-guidelines/"
+ log ""
+ fi
+
+ log "5. Documentation:"
+ info " - FAQ: https://github.com/r3bo0tbx1/tor-guard-relay/blob/main/docs/FAQ.md"
+ info " - Architecture: https://github.com/r3bo0tbx1/tor-guard-relay/blob/main/docs/ARCHITECTURE.md"
+ info " - Monitoring: https://github.com/r3bo0tbx1/tor-guard-relay/blob/main/docs/MONITORING.md"
+ log ""
+
+ success "Setup complete! Thank you for supporting the Tor network! ๐ง
"
+ log ""
+}
+
+# Main execution
+main() {
+ show_banner
+
+ # Main menu loop
+ while true; do
+ show_menu
+ prompt "Choose an option" "1" CHOICE
+
+ case "$CHOICE" in
+ 1)
+ RELAY_MODE="guard"
+ collect_common_info
+ collect_guard_info
+ generate_docker_run
+ generate_docker_compose
+ show_next_steps
+ exit 0
+ ;;
+ 2)
+ RELAY_MODE="exit"
+ collect_common_info
+ collect_exit_info
+ generate_docker_run
+ generate_docker_compose
+ show_next_steps
+ exit 0
+ ;;
+ 3)
+ RELAY_MODE="bridge"
+ collect_common_info
+ collect_bridge_info
+ generate_docker_run
+ generate_docker_compose
+ show_next_steps
+ exit 0
+ ;;
+ 4)
+ log ""
+ log "๐ Goodbye! Thank you for considering running a Tor relay."
+ log ""
+ exit 0
+ ;;
+ *)
+ warn "Invalid choice. Please select 1-4."
+ ;;
+ esac
+ done
+}
+
+main "$@"