feat(v1.1.7): Happy Family support (Tor 0.4.9+ FamilyId)

🔧 New tool: gen-family - generate/view Happy Family keys
  - Supports --force flag to overwrite existing keys without backup prompt

🐳 Dockerfiles: gen-family in both Dockerfile and Dockerfile.edge

🔧 Entrypoint:
- Phase 2: detect *.secret_family_key, log found keys (informational only)
- Guard/exit config gen: append FamilyId + MyFamily from ENV vars
- Bridge intentionally excluded

📊 Status tool: show family key count + Happy Family config state

📚 Docs:
- README: Happy Family section (generate / import), persistence table, flowchart
- ARCHITECTURE: all mermaid diagrams updated (Phase 2, config gen, tools, dirs)
- TOOLS: full gen-family reference with examples and exit codes
- DEPLOYMENT, MIGRATION, MIGRATION-V1.1.X, TROUBLESHOOTING: 5 -> 6 tools
- FAQ, example configs: version bump + FamilyId/MyFamily placeholders
- Directory authority voting: how 9 dirauths vote on relay flags (5/9 consensus)
- CIISS v2 ContactInfo: field reference, generator link, proof:uri-rsa verification
- All TOR_CONTACT_INFO examples updated to CIISS v2 format across templates and docs

📋 Templates:
- Guard/exit/multi-relay compose: TOR_FAMILY_ID + TOR_MY_FAMILY env vars
- All cosmos-compose + docker-compose versions -> 1.1.7

👷 CI: validate.yml gen-family in 8 spots (threshold 6), security tests, quick-test

🛡️ SECURITY.md: 1.1.7 active, 1.1.6 maintenance, gen-family in tools list

🔖 Version bump 1.1.6 -> 1.1.7 across 30+ files, tool count 5 -> 6, CHANGELOG entry

No breaking changes. TOR_FAMILY_ID and TOR_MY_FAMILY are optional.
This commit is contained in:
rE-Bo0t.bx1
2026-03-02 16:23:10 +08:00
parent e861ecb623
commit be4f2bc125
39 changed files with 804 additions and 117 deletions

View File

@@ -78,7 +78,7 @@ jobs:
else
# Check all files in tools/ (no .sh extension)
TOOL_COUNT=0
for script in tools/status tools/health tools/fingerprint tools/bridge-line tools/gen-auth; do
for script in tools/status tools/health tools/fingerprint tools/bridge-line tools/gen-auth tools/gen-family; do
if [ -f "$script" ]; then
echo "📄 Checking $(basename "$script")..."
sh -n "$script" || exit 1
@@ -115,7 +115,7 @@ jobs:
if [ -d "tools" ]; then
echo ""
echo "🔍 ShellCheck: tools/*"
for tool in tools/status tools/health tools/fingerprint tools/bridge-line tools/gen-auth; do
for tool in tools/status tools/health tools/fingerprint tools/bridge-line tools/gen-auth tools/gen-family; do
if [ -f "$tool" ]; then
echo " Checking $(basename $tool)..."
shellcheck -S warning "$tool" || true
@@ -168,8 +168,8 @@ jobs:
if [ $HAS_SH_EXT -eq 1 ]; then
echo "❌ Some tools have .sh extension (should not have it)"
exit 1
elif [ $NO_EXT_COUNT -lt 4 ]; then
echo "❌ Expected 5 tools (status, health, fingerprint, bridge-line, gen-auth)"
elif [ $NO_EXT_COUNT -lt 5 ]; then
echo "❌ Expected 6 tools (status, health, fingerprint, bridge-line, gen-auth, gen-family)"
exit 1
else
echo "✅ All tools have correct format (no .sh extension)"
@@ -335,7 +335,7 @@ jobs:
docker run --rm --entrypoint /bin/sh tor-relay:test -c "ls -la /usr/local/bin/" || exit 1
echo ""
echo "🛠️ Available diagnostic tools:"
docker run --rm --entrypoint /bin/sh tor-relay:test -c "ls -1 /usr/local/bin/status /usr/local/bin/health /usr/local/bin/fingerprint /usr/local/bin/bridge-line /usr/local/bin/gen-auth" || exit 1
docker run --rm --entrypoint /bin/sh tor-relay:test -c "ls -1 /usr/local/bin/status /usr/local/bin/health /usr/local/bin/fingerprint /usr/local/bin/bridge-line /usr/local/bin/gen-auth /usr/local/bin/gen-family" || exit 1
- name: 🧅 Verify Tor Installation
run: |
@@ -564,7 +564,7 @@ jobs:
if: matrix.test-case == 'help-flags'
run: |
echo "🧪 Testing tool existence..."
for tool in status health fingerprint bridge-line; do
for tool in status health fingerprint bridge-line gen-auth gen-family; do
TOOL_PATH="/usr/local/bin/$tool"
if docker run --rm --entrypoint /bin/sh tor-relay:test -c "test -f $TOOL_PATH"; then
echo "✅ $tool exists at $TOOL_PATH"
@@ -579,7 +579,7 @@ jobs:
run: |
echo "🔐 Verifying file permissions for tools..."
docker run --rm --entrypoint /bin/sh tor-relay:test -c "
for tool in /usr/local/bin/status /usr/local/bin/health /usr/local/bin/fingerprint /usr/local/bin/bridge-line /usr/local/bin/gen-auth; do
for tool in /usr/local/bin/status /usr/local/bin/health /usr/local/bin/fingerprint /usr/local/bin/bridge-line /usr/local/bin/gen-auth /usr/local/bin/gen-family; do
if [ -f \"\$tool\" ]; then
test -x \"\$tool\" && echo \"✅ \$(basename \$tool) is executable\" || exit 1
fi
@@ -607,7 +607,7 @@ jobs:
run: |
echo "🔧 Testing tool execution..."
docker run --rm --entrypoint /bin/sh tor-relay:test -c "
for tool in /usr/local/bin/status /usr/local/bin/health /usr/local/bin/fingerprint /usr/local/bin/bridge-line /usr/local/bin/gen-auth; do
for tool in /usr/local/bin/status /usr/local/bin/health /usr/local/bin/fingerprint /usr/local/bin/bridge-line /usr/local/bin/gen-auth /usr/local/bin/gen-family; do
if [ -f \"\$tool\" ]; then
BASENAME=\$(basename \"\$tool\")
echo \"🔍 Testing \$BASENAME...\"
@@ -653,7 +653,7 @@ jobs:
- 🏗️ Docker image build (multi-arch ready)
- 🚀 Container smoke test
- 🧅 Tor installation verification
- 🔧 Tool availability check (status, health, fingerprint, bridge-line, gen-auth)
- 🔧 Tool availability check (status, health, fingerprint, bridge-line, gen-auth, gen-family)
- 🔐 Permission verification
- 🛡️ Security scanning with Trivy
- 🧪 Test matrix execution

View File

@@ -16,11 +16,59 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
---
## [1.1.7] - 2026-03-02
### 🎉 Happy Family Support (Tor 0.4.9+)
This release introduces full support for Tor's new **Happy Family** system (`FamilyId`), which replaces the legacy `MyFamily` fingerprint-exchange workflow. Relay operators can now link all their relays into a family using a single cryptographic key instead of manually listing every fingerprint on every relay.
### ✨ Features
- **New Tool: `gen-family`**: Generate or view Tor Happy Family keys inside the container. Supports `gen-family <Name>` (generate), `gen-family --show` (view existing), `gen-family --force` (overwrite without backup), and `gen-family --help`.
- **`FamilyId` ENV Support**: New `TOR_FAMILY_ID` environment variable to set the `FamilyId` directive in generated torrc (guard/middle/exit modes).
- **`MyFamily` ENV Support**: New `TOR_MY_FAMILY` environment variable (comma-separated fingerprints) for backward compatibility with the legacy `MyFamily` directive.
- **Family Key Detection**: Phase 2 of the entrypoint now scans `/var/lib/tor/keys/*.secret_family_key` and logs detected keys at startup.
- **Import Workflow**: Operators can import existing family keys from bare-metal Tor installations via `docker cp` + ownership fix (`chown 100:101`).
### ⚙️ Changed
- **Entrypoint** (`docker-entrypoint.sh`): Phase 2 now detects family keys; config generation for guard/middle and exit modes appends `FamilyId` and `MyFamily` lines when the corresponding ENV vars are set.
- **Dockerfiles** (`Dockerfile`, `Dockerfile.edge`): Added `COPY` and `chmod +x` for the new `gen-family` tool.
- **`status` Tool** (`tools/status`): Now displays family key count and Happy Family configuration status after the fingerprint section.
- **Tool Count**: Increased from 5 to **6** diagnostic tools (status, health, fingerprint, bridge-line, gen-auth, **gen-family**).
### 📚 Documentation
- **README.md**: Added comprehensive "Happy Family (Tor 0.4.9+)" section with Option A (generate new key) and Option B (import existing key), persistence safety table, updated tools table (6 tools), updated features list, added gen-family to flowchart diagram, and added troubleshooting entries.
- **docs/ARCHITECTURE.md**: Updated all mermaid diagrams - container lifecycle, Phase 2, config generation (guard + exit), diagnostic tools subgraph, directory structure. Updated tool characteristics table, references table, and bumped doc version to 1.1.0.
- **docs/TOOLS.md**: Added full `gen-family` documentation section with usage, output examples, exit codes, and "Set Up Happy Family" workflow. Updated count from 5 → 6 tools and FAQ.
- **docs/DEPLOYMENT.md**: Updated diagnostic tool count references (5 → 6) across 3 locations.
- **docs/MIGRATION.md**: Added `gen-family --show` to post-migration diagnostic checklist.
- **docs/MIGRATION-V1.1.X.md**: Added `gen-family` to diagnostic tool verification checklist.
- **Example Configs**: Added commented `FamilyId` and `MyFamily` placeholders to `relay-guard.conf`, `relay-exit.conf`, and `relay-bridge.conf`.
- **Docker Compose Templates**: Added `TOR_FAMILY_ID` and `TOR_MY_FAMILY` env vars to guard, exit, and multi-relay templates with setup instructions (Option A/B).
- **Directory Authority Voting**: Added explanation of how Tor's 9 directory authorities vote on relay flags (Guard, Stable, Fast, HSDir) and that at least 5 of 9 must agree in consensus, across README, FAQ, DEPLOYMENT, and MULTI-MODE docs.
- **CIISS v2 ContactInfo**: Added documentation for the [ContactInfo Information Sharing Specification v2](https://nusenu.github.io/ContactInfo-Information-Sharing-Specification/) with field reference table, generator link, and `proof:uri-rsa` verification explanation. Updated all `TOR_CONTACT_INFO` examples to use CIISS v2 format.
### 🔁 CI/CD
- **validate.yml**: Added `gen-family` to shell lint, ShellCheck, tool extension verification (threshold 5 → 6), integration tool checks, help-flags test, file-permissions test, and tool-executability test. Updated build summary.
- **scripts/utilities/security-validation-tests.sh**: Added `gen-family` to tool security checks and syntax validation loops.
- **scripts/utilities/quick-test.sh**: Added Test 4.5 for `gen-family --help` executability. Updated summary line.
### 🛡️ Security
- **SECURITY.md**: Updated supported versions table (1.1.7 active, 1.1.6 maintenance). Added `gen-family` to diagnostic tools list.
> **BREAKING CHANGES:** None. The `TOR_FAMILY_ID` and `TOR_MY_FAMILY` environment variables are entirely optional. Existing deployments continue to work without changes.
---
## [1.1.6] - 2026-02-08
### 🐛 Fixed
* **Bind Mount Ownership:** Added startup detection for bind-mounted data/keys directories with incorrect ownership. The entrypoint now warns users with actionable `chown` commands when volumes are not writable by the `tor` user (UID 100, GID 101).
* **DEBUG Flag:** Made the `DEBUG` environment variable case-insensitive now accepts `true`, `TRUE`, `1`, `yes`, `YES`.
* **DEBUG Flag:** Made the `DEBUG` environment variable case-insensitive - now accepts `true`, `TRUE`, `1`, `yes`, `YES`.
* **Documentation Typo:** Fixed incorrect `chown 1000:1000``chown 100:101` in bridge migration troubleshooting guide.
### 🛡️ Security
@@ -455,7 +503,7 @@ BREAKING CHANGES: None
## 📊 Release Information
* **🎉 First Release:** v1.0.0 (November 1, 2025)
* **📦 Current Stable:** v1.1.6 (February 8, 2026)
* **📦 Current Stable:** v1.1.7 (March 2, 2026)
* **🔗 Latest Release:** [GitHub Releases](https://github.com/r3bo0tbx1/tor-guard-relay/releases/latest)
* **🐳 Docker Images:**
@@ -468,14 +516,15 @@ BREAKING CHANGES: None
| Version | Status | Support Level |
| --------- | --------------------- | ------------------------------------------- |
| **1.1.6** | 🟢 🛡️ **Active** | Full support (current stable) |
| **1.1.5** | 🟡 🔧 **Maintenance** | Security + critical fixes only |
| **1.1.7** | 🟢 🛡️ **Active** | Full support (current stable) |
| **1.1.6** | 🟡 🔧 **Maintenance** | Security + critical fixes only |
| **< 1.1.5** | 🔴 ❌ **Deprecated** | Removed - contains CVE-2025-15467 (OpenSSL CVSS 9.8). Upgrade immediately. |
---
## 🔗 Release Links
[1.1.7]: https://github.com/r3bo0tbx1/tor-guard-relay/releases/tag/v1.1.7
[1.1.6]: https://github.com/r3bo0tbx1/tor-guard-relay/releases/tag/v1.1.6
[1.1.5]: https://github.com/r3bo0tbx1/tor-guard-relay/releases/tag/v1.1.5
[1.1.4]: https://github.com/r3bo0tbx1/tor-guard-relay/releases/tag/v1.1.4

View File

@@ -60,6 +60,7 @@ COPY tools/health /usr/local/bin/health
COPY tools/fingerprint /usr/local/bin/fingerprint
COPY tools/bridge-line /usr/local/bin/bridge-line
COPY tools/gen-auth /usr/local/bin/gen-auth
COPY tools/gen-family /usr/local/bin/gen-family
RUN set -eux \
&& chmod +x /usr/local/bin/docker-entrypoint.sh \
@@ -69,6 +70,7 @@ RUN set -eux \
/usr/local/bin/fingerprint \
/usr/local/bin/bridge-line \
/usr/local/bin/gen-auth \
/usr/local/bin/gen-family \
&& echo "🧩 Registered diagnostic tools:" \
&& ls -lh /usr/local/bin/status /usr/local/bin/health /usr/local/bin/fingerprint /usr/local/bin/bridge-line

View File

@@ -60,6 +60,7 @@ COPY tools/health /usr/local/bin/health
COPY tools/fingerprint /usr/local/bin/fingerprint
COPY tools/bridge-line /usr/local/bin/bridge-line
COPY tools/gen-auth /usr/local/bin/gen-auth
COPY tools/gen-family /usr/local/bin/gen-family
RUN set -eux \
&& chmod +x /usr/local/bin/docker-entrypoint.sh \
@@ -69,6 +70,7 @@ RUN set -eux \
/usr/local/bin/fingerprint \
/usr/local/bin/bridge-line \
/usr/local/bin/gen-auth \
/usr/local/bin/gen-family \
&& echo "🧩 Registered diagnostic tools:" \
&& ls -lh /usr/local/bin/status /usr/local/bin/health /usr/local/bin/fingerprint /usr/local/bin/bridge-line

154
README.md
View File

@@ -30,7 +30,7 @@
- 🛡️ **Security-First** - Hardened Alpine Linux, non-root operation, and minimized port exposure
- 🪶 **Very light** - Ultra-minimal 16.8 MB image
- 🎯 **Simple** - One command to deploy, minimal configuration needed
- 📊 **Observable** - 5 busybox-only diagnostic tools with JSON health API
- 📊 **Observable** - 6 busybox-only diagnostic tools with JSON health API
- 🌉 **Multi-Mode** - Supports guard, exit, and bridge (obfs4) relays
- 🔄 **Automated** - Weekly security rebuilds, CI/CD ready
- 📚 **Documented** - Comprehensive guides for deployment, monitoring, backup, and more
@@ -157,11 +157,11 @@ We offer **two build variants** to match your risk tolerance and requirements:
```bash
# Pull from Docker Hub (easiest)
docker pull r3bo0tbx1/onion-relay:latest
docker pull r3bo0tbx1/onion-relay:1.1.6
docker pull r3bo0tbx1/onion-relay:1.1.7
# Pull from GHCR
docker pull ghcr.io/r3bo0tbx1/onion-relay:latest
docker pull ghcr.io/r3bo0tbx1/onion-relay:1.1.6
docker pull ghcr.io/r3bo0tbx1/onion-relay:1.1.7
```
### Edge Variant (Testing Only)
@@ -180,7 +180,7 @@ docker pull r3bo0tbx1/onion-relay:edge
# Pull from GHCR
docker pull ghcr.io/r3bo0tbx1/onion-relay:edge
docker pull ghcr.io/r3bo0tbx1/onion-relay:1.1.6-edge
docker pull ghcr.io/r3bo0tbx1/onion-relay:1.1.7-edge
```
**When to use edge:**
@@ -230,7 +230,7 @@ See [Deployment Guide](docs/DEPLOYMENT.md) for complete instructions.
## 🔧 Diagnostic Tools
Five busybox-only diagnostic tools are included (since v1.1.1).
Six busybox-only diagnostic tools are included.
| Tool | Purpose | Usage |
|------|---------|--------|
@@ -239,6 +239,7 @@ Five busybox-only diagnostic tools are included (since v1.1.1).
| fingerprint | Show fingerprint | `docker exec tor-relay fingerprint` |
| bridge-line | obfs4 line | `docker exec tor-relay bridge-line` |
| gen-auth | Credentials for Nyx | `docker exec tor-relay gen-auth` |
| gen-family | Happy Family key gen | `docker exec tor-relay gen-family MyRelays` |
```bash
# Full health report with emojis
@@ -263,7 +264,7 @@ Example JSON:
}
```
> 📖 **Complete reference:** See [Tools Documentation](docs/TOOLS.md) for all 5 tools with examples, JSON schema, and integration guides.
> 📖 **Complete reference:** See [Tools Documentation](docs/TOOLS.md) for all 6 tools with examples, JSON schema, and integration guides.
---
@@ -340,10 +341,11 @@ STATUS=$(echo "$HEALTH" | jq -r '.status')
- ✅ Graceful shutdown with cleanup
### Operations & Automation
-**5 busybox-only diagnostic tools** (status, health, fingerprint, bridge-line, gen-auth)
-**6 busybox-only diagnostic tools** (status, health, fingerprint, bridge-line, gen-auth, gen-family)
-**JSON health API** for monitoring integration
-**Multi-mode support** (guard, exit, bridge with obfs4)
-**ENV-based config** (TOR_RELAY_MODE, TOR_NICKNAME, etc.)
-**Happy Family support** (Tor 0.4.9+ key-based relay families)
-**ENV-based config** (TOR_RELAY_MODE, TOR_NICKNAME, TOR_FAMILY_ID, etc.)
-**Multi-architecture** builds (AMD64, ARM64)
-**Weekly security rebuilds** via GitHub Actions
-**Docker Compose templates** for single/multi-relay
@@ -383,7 +385,7 @@ STATUS=$(echo "$HEALTH" | jq -r '.status')
### Technical Reference
- **[Architecture](docs/ARCHITECTURE.md)** - ⭐ **NEW!** Technical architecture with Mermaid diagrams
- **[Tools Reference](docs/TOOLS.md)** - ✨ **UPDATED!** Complete guide to all 5 diagnostic tools
- **[Tools Reference](docs/TOOLS.md)** - ✨ **UPDATED!** Complete guide to all 6 diagnostic tools
- **[Monitoring Guide](docs/MONITORING.md)** - ✨ **UPDATED!** External monitoring integration, JSON health API, alerts, and observability
- **[Control Port Guide](docs/CONTROL-PORT.md)** - ⭐ **NEW!** Authentication setup and Nyx integration
- **[Backup Guide](docs/BACKUP.md)** - Data persistence, recovery, and disaster planning
@@ -409,7 +411,7 @@ STATUS=$(echo "$HEALTH" | jq -r '.status')
```ini
Nickname MyTorRelay
ContactInfo your-email@example.com
ContactInfo email:your-email[]example.com url:https://example.com proof:uri-rsa ciissversion:2
ORPort 9001
ORPort [::]:9001
DirPort 0
@@ -419,6 +421,8 @@ DataDirectory /var/lib/tor
Log notice file /var/log/tor/notices.log
```
> 📝 **ContactInfo format:** We recommend the [ContactInfo Information Sharing Specification (CIISS) v2](https://nusenu.github.io/ContactInfo-Information-Sharing-Specification/), a machine-readable format that replaces `@` with `[]` and includes structured fields like `email:`, `url:`, `proof:`, `pgp:`, `hoster:`, and more. Use the [CIISS Generator](https://torcontactinfogenerator.netlify.app/) to create yours.
### Production Configuration
```ini
@@ -438,6 +442,109 @@ Examples are found in the [`examples/`](examples/) directory for complete, annot
> 📖 **Configuration help:** See [Deployment Guide](docs/DEPLOYMENT.md#configuration) for complete reference.
### Happy Family (Tor 0.4.9+)
Tor 0.4.9 introduces **Happy Families**, a cryptographic key-based replacement for `MyFamily`. Instead of listing every relay fingerprint in every relay's config, all relays in a family share one secret key.
**Why upgrade?**
- Eliminates huge `MyFamily` lists that waste bandwidth and memory
- Simpler to maintain - one key file instead of N×N fingerprint entries
- Required for future Arti Relay compatibility
#### Option A: Generate a new family key in Docker
Use this if you don't already have a family key from another setup.
```bash
# 1. Generate a family key (run on any ONE relay container)
docker exec tor-relay gen-family MyRelays
# 2. Note the FamilyId output - you need it for your torrc
# FamilyId <value>
# 3. Copy the key to all your other relay containers
docker cp tor-relay:/var/lib/tor/keys/MyRelays.secret_family_key .
docker cp MyRelays.secret_family_key other-relay:/var/lib/tor/keys/
# 4. Fix ownership and permissions inside the target container
docker exec -u 0 other-relay chown 100:101 /var/lib/tor/keys/MyRelays.secret_family_key
docker exec -u 0 other-relay chmod 600 /var/lib/tor/keys/MyRelays.secret_family_key
# 5. Add FamilyId to each relay's torrc, then restart
docker restart tor-relay other-relay
```
#### Option B: Import an existing family key into Docker
Use this if you already generated a family key on a bare-metal or source-built Tor relay and want to use the same key for your Docker relays.
```bash
# 1. Transfer the .secret_family_key file to your Docker host
# (via scp, sftp, or any secure method)
scp user@source-server:/var/lib/tor/keys/MyRelays.secret_family_key ~/tor-keys/
# 2. Copy the key into each running container (no restart needed yet)
docker cp ~/tor-keys/MyRelays.secret_family_key tor-relay:/var/lib/tor/keys/
# 3. Fix ownership and permissions inside the container
# The tor user in the container runs as UID 100, GID 101
docker exec -u 0 tor-relay chown 100:101 /var/lib/tor/keys/MyRelays.secret_family_key
docker exec -u 0 tor-relay chmod 600 /var/lib/tor/keys/MyRelays.secret_family_key
# 4. Verify the key is in place
docker exec tor-relay ls -la /var/lib/tor/keys/MyRelays.secret_family_key
# 5. Add the FamilyId line to your torrc (same value from your source server)
# FamilyId <your-family-id-value>
# Keep your existing MyFamily lines during the transition period
# 6. Restart the container to pick up the config change
docker restart tor-relay
```
Repeat steps 2-6 for each container that should be in the family.
> **This is safe for running containers.** `docker cp` writes directly into the named Docker volume. Your relay identity keys, family key, and all data persist in the volume across container restarts, image updates, and `docker compose up --force-recreate`. Volumes are **only** deleted if you explicitly run `docker volume rm` or `docker compose down -v`.
#### Torrc configuration
During the transition period, configure **both** `FamilyId` and `MyFamily` in your torrc:
```ini
# Happy Family (Tor 0.4.9+)
FamilyId wweKJrJxUDs1EdtFFHCDtvVgTKftOC/crUl1mYJv830
# MyFamily (legacy - keep during transition)
MyFamily 9A2B5C7D8E1F3A4B6C8D0E2F4A6B8C0D2E4F6A8B
MyFamily 1F3E5D7C9B0A2F4E6D8C0B2A4F6E8D0C2B4A6F8E
```
The Tor Project will announce when `MyFamily` can be removed.
#### ENV-based config (alternative to mounted torrc)
```yaml
environment:
TOR_FAMILY_ID: "wweKJrJxUDs1EdtFFHCDtvVgTKftOC/crUl1mYJv830"
TOR_MY_FAMILY: "FINGERPRINT1,FINGERPRINT2,FINGERPRINT3"
```
#### Key persistence
The `.secret_family_key` file lives in `/var/lib/tor/keys/` inside your data volume. It persists across container restarts and image updates automatically.
| Scenario | Identity preserved? | Family key preserved? |
|----------|:---:|:---:|
| `docker restart` | ✅ | ✅ |
| `docker compose up --force-recreate` | ✅ | ✅ |
| `docker compose down` then `up` | ✅ | ✅ |
| `docker compose down -v` | ❌ | ❌ |
| `docker volume rm <volume>` | ❌ | ❌ |
> ⚠️ **Treat the `.secret_family_key` like a private key.** Anyone with this file can claim their relay belongs to your family. Back it up securely - losing it means regenerating for all relays.
> 📖 **Official docs:** [Tor Happy Family Guide](https://community.torproject.org/relay/setup/post-install/family-ids/)
---
## 🔍 Monitoring Your Relay
@@ -475,6 +582,8 @@ Search by:
| First Statistics | 24-48 hours | Bandwidth graphs appear |
| Guard Flag | 8+ days | Trusted for entry connections |
> 🗳️ **How relay flags work:** Tor has **9 Directory Authorities** that vote every hour on relay flags (Guard, Stable, Fast, HSDir, etc.). A relay only receives a flag when **at least 5 of 9** authorities agree in the consensus. This is why flags take time - your relay must prove itself to a majority of independent authorities.
> 📖 **Detailed monitoring:** See [Monitoring Guide](docs/MONITORING.md) for complete observability setup with Prometheus and Grafana.
---
@@ -498,6 +607,10 @@ docker exec tor-relay bridge-line
# Generate Control Port hash
docker exec tor-relay gen-auth
# Generate/view Happy Family key
docker exec tor-relay gen-family MyRelays
docker exec tor-relay gen-family --show
```
### Common Issues
@@ -520,7 +633,7 @@ If you use **host bind mounts** (e.g. `-v /my/path:/var/lib/tor`) instead of nam
[warn] Failed to parse/validate config: Couldn't access private data directory "/var/lib/tor//keys"
```
**Fix set correct ownership on the host:**
**Fix - set correct ownership on the host:**
```bash
chown -R 100:101 /path/to/your/tor-data
chown -R 100:101 /path/to/your/tor-keys # if mounted separately
@@ -537,7 +650,7 @@ chown -R 100:101 /path/to/your/tor-keys # if mounted separately
> 📐 **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
> - Config generation for guard/exit/bridge modes with Happy Family support
> - OBFS4V security validation
> - Diagnostic tools architecture
> - Signal handling and graceful shutdown
@@ -627,6 +740,10 @@ flowchart TB
━━━━━━━━━━
Generate Control
Port Auth Data"]
Tools --> GenFamily["👨‍👩‍👧 gen-family
━━━━━━━━━━
Happy Family Key
Generation & Import"]
end
Running -->|docker stop SIGTERM| Shutdown
@@ -659,6 +776,7 @@ flowchart TB
style Finger fill:#4DD0E1,stroke:#0097A7,stroke-width:2px,color:#000
style BLine fill:#4DD0E1,stroke:#0097A7,stroke-width:2px,color:#000
style GenAuth fill:#4DD0E1,stroke:#0097A7,stroke-width:2px,color:#000
style GenFamily fill:#4DD0E1,stroke:#0097A7,stroke-width:2px,color:#000
style Graceful fill:#FFB74D,stroke:#F57C00,stroke-width:2px,color:#000
style End fill:#E57373,stroke:#C62828,stroke-width:2px,color:#fff
@@ -766,7 +884,7 @@ See [`examples/`](examples/) directory for relay configurations.
✅ Store `relay.conf` with restricted permissions (`chmod 600`)
✅ Never commit configs with sensitive info to Git
✅ Use PGP key in ContactInfo for verification
✅ Use [CIISS v2](https://nusenu.github.io/ContactInfo-Information-Sharing-Specification/) format in ContactInfo for verification
✅ Regularly update Docker image for security patches
✅ Monitor logs for suspicious activity
✅ Configure firewall properly
@@ -782,14 +900,14 @@ Images are automatically rebuilt on separate schedules to include security patch
**Stable Variant** (`:latest`)
- **Schedule:** Every Sunday at 18:30 UTC
- **Includes:** Latest Tor + Alpine 3.23.3 updates
- **Strategy:** Overwrites last release version (e.g., `:1.1.6`) with updated packages
- **Tags Updated:** `:latest` and version tags (e.g., `:1.1.6`)
- **Strategy:** Overwrites last release version (e.g., `:1.1.7`) with updated packages
- **Tags Updated:** `:latest` and version tags (e.g., `:1.1.7`)
**Edge Variant** (`:edge`)
- **Schedule:** Every 3 days at 12:00 UTC (independent schedule)
- **Includes:** Latest Tor + Alpine edge (bleeding-edge) updates
- **Strategy:** Overwrites last release version (e.g., `:1.1.6-edge`) with updated packages
- **Tags Updated:** `:edge` and version tags (e.g., `:1.1.6-edge`)
- **Strategy:** Overwrites last release version (e.g., `:1.1.7-edge`) with updated packages
- **Tags Updated:** `:edge` and version tags (e.g., `:1.1.7-edge`)
- **Frequency:** ~2-3x more frequent updates than stable
All images auto-published to Docker Hub and GitHub Container Registry
@@ -824,7 +942,7 @@ All images auto-published to Docker Hub and GitHub Container Registry
![GitHub Repo stars](https://img.shields.io/github/stars/r3bo0tbx1/tor-guard-relay?style=for-the-badge)
![GitHub Issues](https://img.shields.io/github/issues/r3bo0tbx1/tor-guard-relay?style=for-the-badge)
**Current Version:** v1.1.6**Status:** Production Ready
**Current Version:** v1.1.7**Status:** Production Ready
**Image Size:** 16.8 MB • **Retention:** Last 7 Releases
**Registries:** Docker Hub • GHCR

View File

@@ -14,9 +14,9 @@ We actively support the following versions with security updates:
| Version | Status | Support Level |
| --------- | --------------------- | ------------------------------------------- |
| **1.1.6** | 🟢 🛡️ **Active** | Full support (current stable) |
| **1.1.5** | 🟡 🔧 **Maintenance** | Security + critical fixes only |
| **< 1.1.5** | 🔴 ❌ **Deprecated** | Removed contains CVE-2025-15467 (OpenSSL CVSS 9.8). Upgrade immediately. |
| **1.1.7** | 🟢 🛡️ **Active** | Full support (current stable) |
| **1.1.6** | 🟡 🔧 **Maintenance** | Security + critical fixes only |
| **< 1.1.5** | 🔴 ❌ **Deprecated** | Removed - contains CVE-2025-15467 (OpenSSL CVSS 9.8). Upgrade immediately. |
---
@@ -74,6 +74,7 @@ docker exec tor-relay health # JSON health output
docker exec tor-relay fingerprint # Display fingerprint
docker exec tor-relay bridge-line # Get bridge line (bridge mode)
docker exec tor-relay gen-auth # Generate Control Port hash
docker exec tor-relay gen-family # Generate/view Happy Family key (Tor 0.4.9+)
```
### Network Architecture
@@ -324,13 +325,15 @@ chown root:root /path/to/relay.conf
#### Contact Information
```conf
# Use a dedicated email for relay operations
ContactInfo tor-relay@example.com <0xPGP_FINGERPRINT>
# CIISS v2 format (recommended) - generate at https://torcontactinfogenerator.netlify.app/
ContactInfo email:tor-relay[]example.com url:https://example.com proof:uri-rsa pgp:EF6E286DDA85EA2A4BA7DE684E2C6E8793298290 ciissversion:2
# Optionally include abuse contact
ContactInfo your-email proof:uri-rsa abuse:abuse@example.com
# With abuse contact (recommended for exits)
ContactInfo email:tor-relay[]example.com abuse:abuse[]example.com url:https://example.com proof:uri-rsa ciissversion:2
```
> 📝 **CIISS v2:** The [ContactInfo Information Sharing Specification](https://nusenu.github.io/ContactInfo-Information-Sharing-Specification/) provides a machine-readable, verifiable format. The `proof:uri-rsa` method lets tools verify your relay ownership by checking `https://your-domain/.well-known/tor-relay/rsa-fingerprint.txt` for your relay's fingerprint.
#### Network Security
```bash
@@ -658,4 +661,4 @@ Security researchers who responsibly disclose vulnerabilities will be listed her
---
*Last Updated: 2026-02-08 | Version: 1.1.6*
*Last Updated: 2026-03-02 | Version: 1.1.7*

View File

@@ -50,7 +50,7 @@ cleanup_and_exit() {
startup_banner() {
log "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
log "🧅 Tor Guard Relay v1.1.6 - Initialization"
log "🧅 Tor Guard Relay v1.1.7 - Initialization"
log "https://github.com/r3bo0tbx1/tor-guard-relay"
log "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
log ""
@@ -90,10 +90,24 @@ phase_2_permissions() {
if [ -d "$TOR_DATA_DIR/keys" ] && [ ! -w "$TOR_DATA_DIR/keys" ]; then
warn "Keys directory $TOR_DATA_DIR/keys has wrong ownership!"
KEYS_OWNER=$(stat -c '%u:%g' "$TOR_DATA_DIR/keys" 2>/dev/null || echo "unknown")
warn " Current owner: $KEYS_OWNER Expected: $CURRENT_UID:$CURRENT_GID"
warn " Current owner: $KEYS_OWNER - Expected: $CURRENT_UID:$CURRENT_GID"
warn " Fix on the host: chown -R $CURRENT_UID:$CURRENT_GID <host-keys-path>"
fi
# Check for Happy Family key files
FAMILY_KEY_COUNT=0
if [ -d "$TOR_DATA_DIR/keys" ]; then
for fk in "$TOR_DATA_DIR/keys"/*.secret_family_key; do
[ -f "$fk" ] || continue
FAMILY_KEY_COUNT=$((FAMILY_KEY_COUNT + 1))
FK_NAME=$(basename "$fk" .secret_family_key)
info "Found Happy Family key: $FK_NAME"
done
fi
if [ "$FAMILY_KEY_COUNT" -gt 0 ]; then
success "$FAMILY_KEY_COUNT family key(s) detected in keys directory"
fi
success "Permissions configured securely"
log ""
}
@@ -218,6 +232,19 @@ BridgeRelay 0
EOF
[ -n "${TOR_BANDWIDTH_RATE:-}" ] && echo "RelayBandwidthRate ${TOR_BANDWIDTH_RATE}" >> "$TOR_CONFIG"
[ -n "${TOR_BANDWIDTH_BURST:-}" ] && echo "RelayBandwidthBurst ${TOR_BANDWIDTH_BURST}" >> "$TOR_CONFIG"
# Happy Family (Tor 0.4.9+)
[ -n "${TOR_FAMILY_ID:-}" ] && echo "" >> "$TOR_CONFIG" && echo "# Happy Family (Tor 0.4.9+)" >> "$TOR_CONFIG" && echo "FamilyId ${TOR_FAMILY_ID}" >> "$TOR_CONFIG"
# MyFamily (legacy, keep during transition)
if [ -n "${TOR_MY_FAMILY:-}" ]; then
echo "" >> "$TOR_CONFIG"
echo "# MyFamily (legacy - keep during transition to Happy Family)" >> "$TOR_CONFIG"
echo "$TOR_MY_FAMILY" | tr ',' '\n' | while IFS= read -r fp; do
fp=$(printf "%s" "$fp" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
[ -n "$fp" ] && echo "MyFamily $fp" >> "$TOR_CONFIG"
done
fi
;;
exit)
@@ -234,6 +261,19 @@ ${TOR_EXIT_POLICY:-ExitPolicy reject *:*}
EOF
[ -n "${TOR_BANDWIDTH_RATE:-}" ] && echo "RelayBandwidthRate ${TOR_BANDWIDTH_RATE}" >> "$TOR_CONFIG"
[ -n "${TOR_BANDWIDTH_BURST:-}" ] && echo "RelayBandwidthBurst ${TOR_BANDWIDTH_BURST}" >> "$TOR_CONFIG"
# Happy Family (Tor 0.4.9+)
[ -n "${TOR_FAMILY_ID:-}" ] && echo "" >> "$TOR_CONFIG" && echo "# Happy Family (Tor 0.4.9+)" >> "$TOR_CONFIG" && echo "FamilyId ${TOR_FAMILY_ID}" >> "$TOR_CONFIG"
# MyFamily (legacy, keep during transition)
if [ -n "${TOR_MY_FAMILY:-}" ]; then
echo "" >> "$TOR_CONFIG"
echo "# MyFamily (legacy - keep during transition to Happy Family)" >> "$TOR_CONFIG"
echo "$TOR_MY_FAMILY" | tr ',' '\n' | while IFS= read -r fp; do
fp=$(printf "%s" "$fp" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
[ -n "$fp" ] && echo "MyFamily $fp" >> "$TOR_CONFIG"
done
fi
;;
bridge)

View File

@@ -58,12 +58,14 @@ flowchart TD
DiagTools -->|fingerprint| FingerprintTool[🆔 tools/fingerprint]
DiagTools -->|bridge-line| BridgeTool[🌉 tools/bridge-line]
DiagTools -->|gen-auth| GenAuthTool[🔑 tools/gen-auth]
DiagTools -->|gen-family| GenFamilyTool[👨‍👩‍👧 tools/gen-family]
StatusTool --> Running
HealthTool --> Running
FingerprintTool --> Running
BridgeTool --> Running
GenAuthTool --> Running
GenFamilyTool --> Running
Trap --> StopTail[🧽 Kill tail -F PID]
StopTail --> StopTor[📨 Send SIGTERM to Tor]
@@ -94,6 +96,7 @@ flowchart TD
subgraph P2["🔐 Phase 2: Permission Hardening"]
P2_1[🔒 chmod 700 data dir] --> P2_2[📁 chmod 755 log dir]
P2_2 --> P2_3[👨‍👩‍👧 Detect family keys]
end
subgraph P3["⚙️ Phase 3: Configuration Setup"]
@@ -136,7 +139,7 @@ flowchart TD
| 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) |
| **2** | Permissions | `chmod 700` data, `chmod 755` log, detect family keys | 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 |
@@ -351,8 +354,14 @@ flowchart TD
G4 -->|Not set| G6
G5 --> G6{TOR_BANDWIDTH_BURST?}
G6 -->|Set| G7[Add RelayBandwidthBurst]
G6 -->|Not set| GuardDone
G7 --> GuardDone([Guard Config Done])
G6 -->|Not set| G8
G7 --> G8{TOR_FAMILY_ID?}
G8 -->|Set| G9[Add FamilyId]
G8 -->|Not set| G10
G9 --> G10{TOR_MY_FAMILY?}
G10 -->|Set| G11[Add MyFamily entries]
G10 -->|Not set| GuardDone
G11 --> GuardDone([🛡️ Guard Config Done])
end
subgraph ExitConfig["🚪 Exit Config (lines 260-273)"]
@@ -364,8 +373,14 @@ flowchart TD
E5 -->|Not set| E7
E6 --> E7{TOR_BANDWIDTH_BURST?}
E7 -->|Set| E8[Add RelayBandwidthBurst]
E7 -->|Not set| ExitDone
E8 --> ExitDone([Exit Config Done])
E7 -->|Not set| E9
E8 --> E9{TOR_FAMILY_ID?}
E9 -->|Set| E10[Add FamilyId]
E9 -->|Not set| E11
E10 --> E11{TOR_MY_FAMILY?}
E11 -->|Set| E12[Add MyFamily entries]
E11 -->|Not set| ExitDone
E12 --> ExitDone([🚪 Exit Config Done])
end
subgraph BridgeConfig["🌉 Bridge Config (lines 276-343)"]
@@ -402,6 +417,7 @@ flowchart TD
```
**Base Config Includes:** Nickname, ContactInfo, ORPort, SocksPort 0, DataDirectory, Logging
**Family Config (guard/exit):** Optional FamilyId (Tor 0.4.9+) and MyFamily (legacy, comma-separated fingerprints via TOR_MY_FAMILY)
**Code Reference:** `docker-entrypoint.sh` lines 222-350 (generate_config_from_env)
@@ -456,7 +472,7 @@ flowchart TD
style Enable fill:#e3f2fd
```
**Security Features (fixed in v1.1.1, improved through v1.1.6):**
**Security Features (fixed in v1.1.1, improved through v1.1.7):**
- **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
@@ -468,7 +484,7 @@ flowchart TD
## Diagnostic Tools
Five busybox-only diagnostic tools provide observability:
Six busybox-only diagnostic tools provide observability:
```mermaid
flowchart TD
@@ -478,6 +494,7 @@ flowchart TD
Choice -->|health| HealthFlow
Choice -->|fingerprint| FingerprintFlow
Choice -->|bridge-line| BridgeFlow
Choice -->|gen-family| FamilyFlow
subgraph StatusFlow["📊 tools/status - Full Health Report"]
S1[🔍 Check Tor process running] --> S2[📈 Read bootstrap %]
@@ -518,10 +535,25 @@ flowchart TD
FingerprintFlow --> Output3([🟢 Fingerprint + URL])
BridgeFlow --> Output4([🟢 Bridge line or error])
subgraph FamilyFlow["👨‍👩‍👧 tools/gen-family - Happy Family Management"]
FM1{Which action?}
FM1 -->|gen-family Name| FM2[🔑 Check Tor version]
FM2 --> FM3{Key already exists?}
FM3 -->|Yes| FM4[⚠️ Warn: key exists]
FM3 -->|No| FM5[🔐 tor --keygen-family Name]
FM5 --> FM6[📤 Output FamilyId + instructions]
FM1 -->|gen-family --show| FM7[🔍 Scan keys dir for .secret_family_key]
FM7 --> FM8[📝 Show FamilyId from torrc]
FM8 --> FM9[ Show MyFamily status]
end
FamilyFlow --> Output5([🟢 Key + FamilyId or status])
style Output1 fill:#b2fab4
style Output2 fill:#b2fab4
style Output3 fill:#b2fab4
style Output4 fill:#b2fab4
style Output5 fill:#b2fab4
```
**JSON Output Fields:** status, pid, uptime, bootstrap, reachable, errors, fingerprint, nickname
@@ -535,6 +567,7 @@ flowchart TD
| **fingerprint** | Relay identity | Text + URL | busybox: cat, awk |
| **bridge-line** | Bridge sharing | obfs4 bridge line | busybox: grep, sed, awk, wget |
| **gen-auth** | Credential generation | Text (Pass + Hash) | busybox: head, tr, tor |
| **gen-family** | Happy Family key mgmt | Text (Key + FamilyId) | busybox: tor --keygen-family, grep, basename |
**All tools:**
- Use `#!/bin/sh` (POSIX sh, not bash)
@@ -576,11 +609,13 @@ graph TD
Lib["📁 /var/lib"]
TorData["📦 /var/lib/tor VOLUME"]
Keys["🔑 keys/"]
FamilyKey["👨‍👩‍👧 *.secret_family_key"]
FingerprintFile["🆔 fingerprint"]
PTState["🌀 pt_state/"]
Lib --> TorData
TorData --> Keys
Keys --> FamilyKey
TorData --> FingerprintFile
TorData --> PTState
end
@@ -620,6 +655,7 @@ graph TD
Fingerprint["🧬 fingerprint"]
BridgeLine["🌉 bridge-line"]
GenAuth["🔑 gen-auth"]
GenFamily["👨‍👩‍👧 gen-family"]
UsrLocal --> Bin
Bin --> Entrypoint
@@ -628,6 +664,8 @@ graph TD
Bin --> Health
Bin --> Fingerprint
Bin --> BridgeLine
Bin --> GenAuth
Bin --> GenFamily
end
Usr --> UsrLocal
@@ -661,7 +699,7 @@ graph TD
class TorData,TorLog volumeStyle
class TorRC configStyle
class Entrypoint,Healthcheck,Status,Health,Fingerprint,BridgeLine scriptStyle
class Entrypoint,Healthcheck,Status,Health,Fingerprint,BridgeLine,GenAuth,GenFamily scriptStyle
class TorBin,Lyrebird,Tini binaryStyle
class TorPID runtimeStyle
class TorRCSample deletedStyle
@@ -831,7 +869,7 @@ flowchart LR
```
**Weekly Rebuild Strategy:**
- Rebuilds use the **same version tag** as the last release (e.g., `1.1.6`)
- Rebuilds use the **same version tag** as the last release (e.g., `1.1.7`)
- Overwrites existing image with fresh Alpine packages (security updates)
- No `-weekly` suffix needed - just updated packages
- `:latest` always points to most recent release version
@@ -902,6 +940,7 @@ flowchart TD
| `tools/fingerprint` | Show relay identity | ~50 |
| `tools/bridge-line` | Generate bridge line | ~80 |
| `tools/gen-auth` | Generate Control Port auth | ~30 |
| `tools/gen-family` | Happy Family key management | ~180 |
### External Documentation
@@ -913,6 +952,6 @@ flowchart TD
---
<div align="center">
**Document Version:** 1.0.5**Last Updated:** 2026-02-08**Container Version:** v1.1.6
**Document Version:** 1.1.0**Last Updated:** 2026-03-02**Container Version:** v1.1.7
</div>

View File

@@ -54,7 +54,7 @@ nano relay-guard.conf
**Minimum required edits:**
- `Nickname` - Your relay name
- `ContactInfo` - Your email
- `ContactInfo` - Your contact info ([CIISS v2](https://nusenu.github.io/ContactInfo-Information-Sharing-Specification/) format recommended)
- `ORPort` - Usually 9001 or 443
- `RelayBandwidthRate` - Your bandwidth limit
@@ -91,7 +91,7 @@ docker ps | grep tor-relay
# Check logs and bootstrap progress
docker logs -f tor-relay
# Run diagnostics (5 tools available)
# Run diagnostics (6 tools available)
docker exec tor-relay status # Full health report with emojis
docker exec tor-relay health # JSON health data
docker exec tor-relay fingerprint # Show fingerprint + Tor Metrics URL
@@ -128,7 +128,7 @@ nano relay.conf
Edit at minimum:
- `Nickname`
- `ContactInfo`
- `ContactInfo` ([CIISS v2](https://nusenu.github.io/ContactInfo-Information-Sharing-Specification/) format recommended)
- `RelayBandwidthRate`
### Step 4: Deploy
@@ -194,7 +194,7 @@ Paste your relay configuration (see [example config](../examples/relay-guard.con
**Important**: Edit at minimum:
- `Nickname` - Your relay name
- `ContactInfo` - Your email
- `ContactInfo` - Your contact info ([CIISS v2](https://nusenu.github.io/ContactInfo-Information-Sharing-Specification/) format recommended)
- `RelayBandwidthRate` - Your bandwidth limit
Save and set permissions:
@@ -347,7 +347,7 @@ docker run -d \
--network host \
-e TOR_RELAY_MODE=guard \
-e TOR_NICKNAME=MyGuardRelay \
-e TOR_CONTACT_INFO=tor@example.com \
-e TOR_CONTACT_INFO="email:tor[]example.com ciissversion:2" \
-e TOR_ORPORT=9001 \
-v tor-data:/var/lib/tor \
r3bo0tbx1/onion-relay:latest
@@ -370,7 +370,7 @@ docker run -d \
--network host \
-e TOR_RELAY_MODE=exit \
-e TOR_NICKNAME=MyExitRelay \
-e TOR_CONTACT_INFO=tor@example.com \
-e TOR_CONTACT_INFO="email:tor[]example.com ciissversion:2" \
-e TOR_ORPORT=9001 \
-e TOR_EXIT_POLICY="accept *:80,accept *:443,reject *:*" \
-v tor-data:/var/lib/tor \
@@ -394,7 +394,7 @@ docker run -d \
--network host \
-e TOR_RELAY_MODE=bridge \
-e TOR_NICKNAME=MyBridge \
-e TOR_CONTACT_INFO=tor@example.com \
-e TOR_CONTACT_INFO="email:tor[]example.com ciissversion:2" \
-e TOR_ORPORT=9001 \
-e TOR_OBFS4_PORT=9002 \
-v tor-data:/var/lib/tor \
@@ -420,7 +420,7 @@ Full configuration via environment variables is supported (no config file needed
#### Core Configuration
- `TOR_RELAY_MODE` - guard, exit, or bridge (default: guard)
- `TOR_NICKNAME` - Relay nickname (required for ENV config)
- `TOR_CONTACT_INFO` - Contact email (required for ENV config)
- `TOR_CONTACT_INFO` - Contact info ([CIISS v2](https://nusenu.github.io/ContactInfo-Information-Sharing-Specification/) format recommended, required for ENV config)
- `TOR_ORPORT` - ORPort (default: 9001)
- `TOR_DIRPORT` - DirPort for guard/exit (default: 0 - disabled)
- `TOR_OBFS4_PORT` - obfs4 port for bridge mode (default: 9002)
@@ -453,7 +453,7 @@ services:
environment:
TOR_RELAY_MODE: guard
TOR_NICKNAME: MyRelay
TOR_CONTACT_INFO: tor@example.com
TOR_CONTACT_INFO: "email:tor[]example.com ciissversion:2"
TOR_ORPORT: 9001
TOR_BANDWIDTH_RATE: 50 MBytes
TOR_BANDWIDTH_BURST: 100 MBytes
@@ -510,7 +510,7 @@ abc123def456 r3bo0tbx1/onion-relay:latest Up 5 minutes (healthy)
### 2. Run Diagnostics
The image provides **5 diagnostic tools**:
The image provides **6 diagnostic tools**:
```bash
# Full health report with emojis
@@ -562,6 +562,8 @@ docker exec tor-relay health
- **24-48 hours**: Full statistics available
- **8+ days**: Eligible for Guard flag (guard relays only)
> 🗳️ **How flags are assigned:** Tor's **9 Directory Authorities** vote every hour on relay flags (Guard, Stable, Fast, HSDir, etc.). Your relay only receives a flag when **at least 5 of 9** authorities reach consensus. New relays start as unmeasured middle relays and earn flags over time as authorities observe stable uptime and sufficient bandwidth.
Search for your relay:
- **Clearnet**: https://metrics.torproject.org/rs.html
- **Tor Browser**: http://hctxrvjzfpvmzh2jllqhgvvkoepxb4kfzdjm6h7egcwlumggtktiftid.onion/rs.html
@@ -964,7 +966,7 @@ After successful deployment:
## Support
- 📖 [Main README](../README.md)
- 🔧 [Tools Documentation](TOOLS.md) - Complete guide to the 5 diagnostic tools
- 🔧 [Tools Documentation](TOOLS.md) - Complete guide to the 6 diagnostic tools
- 📊 [Monitoring Guide](MONITORING.md) - External monitoring integration
- 🐛 [Report Issues](https://github.com/r3bo0tbx1/tor-guard-relay/issues)
- 💬 [Tor Project Forum](https://forum.torproject.net/)

View File

@@ -32,7 +32,7 @@ Built on Alpine Linux 3.23.0 with a minimal 20MB image size, busybox-only tools,
|---------|--------------|-----------------|
| **Image size** | ~16.8 MB | ~100+ MB |
| **Base** | Alpine 3.23.0 | Debian |
| **Diagnostics** | 5 busybox tools + JSON API | None |
| **Diagnostics** | 6 busybox tools + JSON API | None |
| **Multi-mode** | Guard/Exit/Bridge in one image | Separate images |
| **Weekly rebuilds** | ✅ Automated | ❌ Manual |
| **ENV configuration** | ✅ Full support | Limited |
@@ -72,7 +72,7 @@ docker run -d \
--network host \
-e TOR_RELAY_MODE=guard \
-e TOR_NICKNAME=MyGuardRelay \
-e TOR_CONTACT_INFO="admin@example.com" \
-e TOR_CONTACT_INFO="email:admin[]example.com ciissversion:2" \
-e TOR_ORPORT=9001 \
-e TOR_DIRPORT=9030 \
-v tor-data:/var/lib/tor \
@@ -89,6 +89,37 @@ docker run -d \
ghcr.io/r3bo0tbx1/onion-relay:latest
```
### What is the ContactInfo Information Sharing Specification (CIISS)?
The [CIISS v2](https://nusenu.github.io/ContactInfo-Information-Sharing-Specification/) is a machine-readable format for the Tor relay `ContactInfo` field. Instead of a plain email, it uses structured `key:value` pairs that tools can parse and verify automatically.
**Format:**
```
email:your-email[]example.com url:https://example.com proof:uri-rsa ciissversion:2
```
**Key fields:**
| Field | Purpose | Example |
|-------|---------|---------|
| `email:` | Contact email (`@``[]`) | `email:tor[]example.com` |
| `url:` | Operator website | `url:https://example.com` |
| `proof:` | URL ownership verification | `proof:uri-rsa` |
| `pgp:` | 40-char PGP fingerprint | `pgp:EF6E286DDA85EA2A4BA7DE684E2C6E8793298290` |
| `abuse:` | Abuse contact (exits) | `abuse:abuse[]example.com` |
| `hoster:` | Hosting provider domain | `hoster:www.example-hoster.com` |
| `uplinkbw:` | Uplink bandwidth (Mbit/s) | `uplinkbw:1000` |
| `ciissversion:` | Spec version (**mandatory**) | `ciissversion:2` |
**Why use it?**
- Tools like [Tor Metrics](https://metrics.torproject.org/) can parse your info automatically
- `proof:uri-rsa` lets anyone verify you own the URL (place relay fingerprints at `https://your-domain/.well-known/tor-relay/rsa-fingerprint.txt`)
- Helps detect impersonation - operators can't fake verified URLs
- Improves trust and visibility in the Tor network
**Generate your string:** Use the [CIISS Generator](https://torcontactinfogenerator.netlify.app/) - fill in the fields and copy the result into your `ContactInfo` line or `TOR_CONTACT_INFO` env var.
> 📖 **Full spec:** [nusenu.github.io/ContactInfo-Information-Sharing-Specification](https://nusenu.github.io/ContactInfo-Information-Sharing-Specification/)
### What's the difference between TOR_* and official bridge naming?
Both work identically - we support two naming conventions for compatibility:
@@ -97,7 +128,7 @@ Both work identically - we support two naming conventions for compatibility:
```bash
TOR_RELAY_MODE=bridge
TOR_NICKNAME=MyBridge
TOR_CONTACT_INFO=admin@example.com
TOR_CONTACT_INFO=email:admin[]example.com ciissversion:2
TOR_ORPORT=9001
TOR_OBFS4_PORT=9002
```
@@ -195,6 +226,8 @@ sudo ufw allow 9002/tcp
| First statistics | 24-48 hours | Bandwidth graphs appear |
| Guard flag | 8+ days | Relay trusted for entry connections |
> 🗳️ **Directory Authority Voting:** Tor has **9 Directory Authorities** that vote hourly on relay flags. A relay only earns a flag (Guard, Stable, Fast, HSDir, etc.) when **at least 5 of 9** authorities agree in the consensus. This is why flags aren't instant - your relay must prove itself to a majority of independent authorities.
**Troubleshooting:**
1. **Check bootstrap:** `docker exec tor-relay status`
- Must show "Bootstrapped 100%"
@@ -247,7 +280,7 @@ docker logs tor-bridge | grep "bridge line"
**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
3. **Relay flags** - Guard, Fast, Stable flags increase usage (assigned by directory authority consensus - at least 5 of 9 authorities must vote for each flag)
4. **Configured bandwidth** - Tor won't exceed your limits
5. **Exit policy** - Exit relays typically get more traffic
@@ -426,7 +459,7 @@ docker run -d --name tor-relay ... # Same config
**Verify upgrade:**
```bash
docker exec tor-relay cat /build-info.txt
# Should show: Version: 1.1.6
# Should show: Version: 1.1.7
docker exec tor-relay fingerprint
# Verify fingerprint unchanged
@@ -537,5 +570,5 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md) for guidelines.
---
**Last Updated:** Feburary 2026 (v1.1.6)
**Last Updated:** March 2026 (v1.1.7)
**Maintained by:** [@r3bo0tbx1](https://github.com/r3bo0tbx1)

View File

@@ -436,7 +436,7 @@ docker rm obfs4-bridge
- [ ] Fingerprint matches backup
- [ ] Container starts without errors
- [ ] Bootstrap reaches 100%
- [ ] Diagnostic tools work (status, health, fingerprint)
- [ ] Diagnostic tools work (status, health, fingerprint, gen-family)
- [ ] Monitoring updated (if applicable)
---

View File

@@ -178,6 +178,7 @@ After any migration:
- `docker exec <container> status`
- `docker exec <container> health`
- `docker exec <container> fingerprint`
- `docker exec <container> gen-family --show` (if using Happy Family)
- [ ] Tor Metrics shows relay (after 1-2 hours)
---

View File

@@ -49,7 +49,7 @@ The Tor Guard Relay container now supports **three relay modes**:
**Best for:**
- Operators who want to contribute without legal complexity
- Stable, high-bandwidth connections
- Long-term operation (8+ days to earn Guard flag)
- Long-term operation (8+ days to earn Guard flag - requires at least 5 of 9 directory authorities to vote in consensus)
**Requirements:**
- Public IP with ports 9001, 9030 accessible
@@ -491,7 +491,7 @@ nc -zv <your-ip> 9001
**Normal for:**
- New relays (2-8 weeks to build reputation)
- Bridges (intentionally low visibility)
- Guards without Guard flag (need 8+ days uptime)
- Guards without Guard flag (need 8+ days uptime - at least 5 of 9 directory authorities must agree in consensus)
**Check:**
1. Verify relay is reachable: `docker exec <container> status`

View File

@@ -1,6 +1,6 @@
# 🛠️ Tools Reference Guide
**Tor Guard Relay 1.1.3** includes 5 essential diagnostic tools built directly into the ultra-optimized ~20 MB container. All tools are busybox-compatible, executable without file extensions, and designed for production use.
**Tor Guard Relay 1.1.3** includes 6 essential diagnostic tools built directly into the ultra-optimized ~20 MB container. All tools are busybox-compatible, executable without file extensions, and designed for production use.
---
@@ -13,6 +13,7 @@
| **fingerprint** | Display relay fingerprint | Text | With Tor Metrics link |
| **bridge-line** | Get obfs4 bridge line | Text | Bridge mode only |
| gen-auth | Generate Control Port auth | Text | Password + Hash |
| gen-family | Generate/view Happy Family key | Text | Tor 0.4.9+ only |
---
@@ -224,6 +225,53 @@ When to use:
---
### `gen-family`
**Purpose:** Generate or view a Tor Happy Family key (Tor 0.4.9+). This replaces the old `MyFamily` fingerprint-exchange workflow with a single shared `FamilyId`.
**Usage:**
```bash
# Generate a new family key
docker exec tor-relay gen-family MyRelays
# View existing family key and FamilyId
docker exec tor-relay gen-family --show
# Show help
docker exec tor-relay gen-family --help
```
**Output Example (generate):**
```
════════════════════════════════════════════════════════════
Tor Happy Family Key Generator (Tor 0.4.9+)
════════════════════════════════════════════════════════════
✓ Generated family key: MyRelays
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Your FamilyId (add to torrc or TOR_FAMILY_ID env var):
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
FamilyId wweKJrJxUDs1EdtFFHCDtvVgTKftOC/crUl1mYJv830
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
💡 Copy the secret key file to all relays in this family.
Then set TOR_FAMILY_ID in each relay's environment.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```
**Exit Codes:**
- `0` - Key generated or displayed successfully
- `1` - Error (key already exists, Tor not found, etc.)
**When to use:**
- When linking multiple relays (guard, exit, middle) into a family
- Run once to generate, then copy the key file to all family members
- Replaces the old manual fingerprint exchange (`MyFamily`)
---
## 🚀 Common Workflows
### 1. Quick Health Check
@@ -270,7 +318,21 @@ docker exec tor-bridge bridge-line
# Share ONLY with trusted users, NOT publicly
```
### 5. Automated Monitoring
### 5. Set Up Happy Family (Tor 0.4.9+)
```bash
# Generate a family key on one relay
docker exec tor-relay gen-family MyRelays
# Copy the key file to all other family members
docker cp tor-relay:/var/lib/tor/keys/MyRelays.secret_family_key ./
docker cp ./MyRelays.secret_family_key tor-relay-2:/var/lib/tor/keys/
docker exec -u 0 tor-relay-2 chown 100:101 /var/lib/tor/keys/MyRelays.secret_family_key
# Set TOR_FAMILY_ID on each relay and restart
# (use the FamilyId from gen-family --show output)
```
### 6. Automated Monitoring
```bash
# Simple monitoring script
while true; do
@@ -288,7 +350,7 @@ while true; do
done
```
### 6. Check Logs
### 7. Check Logs
```bash
# View recent logs
docker logs --tail 100 tor-relay
@@ -322,7 +384,7 @@ docker logs tor-relay 2>&1 | grep -i warn
# Verify tools exist
docker exec tor-relay ls -la /usr/local/bin/
# Should show: status, health, fingerprint, bridge-line, gen-auth
# Should show: status, health, fingerprint, bridge-line, gen-auth, gen-family
# Check PATH
docker exec tor-relay echo $PATH
@@ -401,18 +463,22 @@ docker logs tor-relay | grep -i obfs4
## ❓ FAQ
**Q: Why only 5 tools instead of 9?**
A: The v1.1.3 build remains ultra-light (~16.8 MB). These 5 tools cover all essential operations including health checks, identity, and authentication setup.
**Q: Why only 6 tools instead of 9?**
A: The v1.1.3 build remains ultra-light (~16.8 MB). These 6 tools cover all essential operations including health checks, identity, authentication setup, and Happy Family key management.
**Q: Where are metrics/monitoring endpoints?**
A: Removed to achieve ultra-small image size. Use `health` tool with external monitoring systems or check `/var/log/tor/notices.log` directly.
**Q: Can I still use Prometheus?**
A: Yes! Use `gen-auth` to configure the Control Port, then run a separate `prometheus-tor-exporter` container alongside this one.
**Q: What happened to the dashboard?**
A: Removed (required Python/Flask). Use `status` tool for visual output or build your own dashboard using `health` JSON.
---
**Last Updated:** December 2025 | **Version:** 1.1.3
**Last Updated:** March 2026 | **Version:** 1.1.7

View File

@@ -501,7 +501,7 @@ After migration:
- ✨ Official Tor Project ENV variable compatibility
- ✨ Bootstrap progress logs in terminal
- ✨ Enhanced emoji logging (v1.1.0 style)
-5 diagnostic tools (status, health, fingerprint, bridge-line, gen-auth)
-6 diagnostic tools (status, health, fingerprint, bridge-line, gen-auth, gen-family)
- ✨ Auto-detection of bridge mode from PT_PORT
- ✨ OBFS4V_* variable processing

View File

@@ -1,5 +1,14 @@
Nickname ShinobiKage
Nickname MyTorBridge
ContactInfo email:your-email[]example.com pgp:YOUR_PGP_FINGERPRINT ciissversion:2
# Happy Family (Tor 0.4.9+) - replaces MyFamily
# Generate with: docker exec tor-bridge gen-family MyRelays
#FamilyId YOUR_FAMILY_ID_HERE
# MyFamily (legacy - keep BOTH during transition period)
#MyFamily FINGERPRINT_OF_OTHER_RELAY_1
#MyFamily FINGERPRINT_OF_OTHER_RELAY_2
Address YOUR.IPV4.IP.ADDRESS
ORPort 24819 IPv4Only
ORPort [YOUR:IPV6:IP:ADDRESS::]:24819

View File

@@ -1,5 +1,15 @@
Nickname MyTorExitRelay
ContactInfo email:your-email[]example.com pgp:YOUR_PGP_FINGERPRINT ciissversion:2
# Happy Family (Tor 0.4.9+) - replaces MyFamily
# Generate with: docker exec tor-relay gen-family MyRelays
#FamilyId YOUR_FAMILY_ID_HERE
# MyFamily (legacy - keep BOTH during transition period)
# Once Tor Project announces MyFamily removal, you can drop these lines
#MyFamily FINGERPRINT_OF_OTHER_RELAY_1
#MyFamily FINGERPRINT_OF_OTHER_RELAY_2
Address YOUR.IPV4.IP.ADDRESS
ORPort 9001 IPv4Only
ORPort [YOUR:IPV6:IP:ADDRESS::]:9001

View File

@@ -1,5 +1,15 @@
Nickname MyTorGuardRelay
ContactInfo email:your-email[]example.com pgp:YOUR_PGP_FINGERPRINT ciissversion:2
# Happy Family (Tor 0.4.9+) - replaces MyFamily
# Generate with: docker exec tor-relay gen-family MyRelays
#FamilyId YOUR_FAMILY_ID_HERE
# MyFamily (legacy - keep BOTH during transition period)
# Once Tor Project announces MyFamily removal, you can drop these lines
#MyFamily FINGERPRINT_OF_OTHER_RELAY_1
#MyFamily FINGERPRINT_OF_OTHER_RELAY_2
Address YOUR.IPV4.IP.ADDRESS
ORPort 9001 IPv4Only
ORPort [YOUR:IPV6:IP:ADDRESS::]:9001

View File

@@ -620,7 +620,7 @@ main() {
log ""
log "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
log "🧅 Tor Relay Migration Validator v1.1.6"
log "🧅 Tor Relay Migration Validator v1.1.7"
log "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
log ""

View File

@@ -163,7 +163,7 @@ Auto-update version numbers across all documentation, templates, and configurati
- Format: `## [v1.1.2] - 2025-01-14`
3. **templates/*.yml** (Docker Compose)
- `image:` tags from `onion-relay:1.1.5``onion-relay:1.1.6`
- `image:` tags from `onion-relay:1.1.6``onion-relay:1.1.7`
4. **templates/*.json** (Cosmos Cloud)
- `"image":` fields in JSON templates
@@ -515,13 +515,13 @@ sed -i 's/1.1.1/1.1.2/g' path/to/file
**Solution:**
```bash
# Ensure image exists locally or in registry
docker pull ghcr.io/r3bo0tbx1/onion-relay:1.1.6
docker pull ghcr.io/r3bo0tbx1/onion-relay:1.1.7
# 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.6 \
ghcr.io/r3bo0tbx1/onion-relay:1.1.7 \
-o cyclonedx-json
```

View File

@@ -231,6 +231,13 @@ else
warn "bridge-line tool not ready yet (needs full bootstrap - 10-30 minutes)"
fi
# Test 4.5: gen-family tool (help flag)
if docker exec test-tools gen-family --help >/dev/null 2>&1; then
success "gen-family tool works"
else
warn "gen-family tool not available (requires Tor 0.4.9+)"
fi
# Cleanup
docker stop test-tools >/dev/null 2>&1
docker rm test-tools >/dev/null 2>&1
@@ -306,7 +313,7 @@ echo "✅ OBFS4V_* variables are processed correctly"
echo "✅ Bridge mode auto-detected from PT_PORT"
echo "✅ TOR_* ENV naming works (TOR_ORPORT, TOR_CONTACT_INFO, etc.)"
echo "✅ Guard/Exit/Bridge modes configured correctly"
echo "✅ Diagnostic tools work (status, health, fingerprint, bridge-line)"
echo "✅ Diagnostic tools work (status, health, fingerprint, bridge-line, gen-family)"
echo "✅ Mixed ENV naming works (can combine official + TOR_* prefix)"
echo ""
echo "🎯 Your image is fully compatible with thetorproject/obfs4-bridge!"

View File

@@ -1,7 +1,7 @@
#!/bin/bash
#
# relay-status.sh Tor relay/bridge status checker with security validation
# Version: 1.1.6
# relay-status.sh - Tor relay/bridge status checker with security validation
# Version: 1.1.7
# Automatically detects Tor containers or uses specified container name
#
@@ -11,7 +11,7 @@ set -euo pipefail
CONTAINER="${1:-}" # Accept container name as first argument
readonly FINGERPRINT_PATH="/var/lib/tor/fingerprint"
readonly TORRC_PATH="/etc/tor/torrc"
readonly VERSION="1.1.6"
readonly VERSION="1.1.7"
# Colors for output
readonly RED='\033[0;31m'
@@ -359,7 +359,7 @@ EOF
# Show version
show_version() {
echo "relay-status.sh version ${VERSION}"
echo "Part of Tor Guard Relay v1.1.6"
echo "Part of Tor Guard Relay v1.1.7"
}
# Main execution

View File

@@ -4,7 +4,7 @@
set -e
echo "🔐 Security Validation Tests - Tor Guard Relay v1.1.6"
echo "🔐 Security Validation Tests - Tor Guard Relay v1.1.7"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
@@ -126,7 +126,7 @@ echo "Test 3: Tool Scripts Security"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
# Check for 4 essential tools
for tool in status health fingerprint bridge-line; do
for tool in status health fingerprint bridge-line gen-family; do
if [ -f "tools/$tool" ]; then
if head -1 "tools/$tool" | grep -q "^#!/bin/sh"; then
test_pass "tools/$tool uses busybox sh"
@@ -303,7 +303,7 @@ echo ""
echo "Test 8: Shell Script Syntax"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
for script in docker-entrypoint.sh tools/status tools/health tools/fingerprint tools/bridge-line; do
for script in docker-entrypoint.sh tools/status tools/health tools/fingerprint tools/bridge-line tools/gen-family; do
if [ -f "$script" ]; then
if sh -n "$script" 2>/dev/null; then
test_pass "$script has valid POSIX sh syntax"

View File

@@ -49,7 +49,7 @@ docker run -d \
--security-opt no-new-privileges:true \
-e TOR_RELAY_MODE=bridge \
-e TOR_NICKNAME=MyBridge \
-e TOR_CONTACT_INFO=admin@example.com \
-e TOR_CONTACT_INFO="email:admin[]example.com ciissversion:2" \
-e TOR_ORPORT=9001 \
-e TOR_OBFS4_PORT=9002 \
-v tor-data:/var/lib/tor \
@@ -61,7 +61,7 @@ docker run -d \
# Core (required for ENV-based config)
TOR_RELAY_MODE=guard|exit|bridge # Relay mode
TOR_NICKNAME=MyRelay # Relay nickname (1-19 chars, alphanumeric)
TOR_CONTACT_INFO=admin@example.com # Contact email
TOR_CONTACT_INFO="email:admin[]example.com ciissversion:2" # Contact info (CIISS v2)
# Ports (configurable)
TOR_ORPORT=9001 # ORPort for relay traffic (default: 9001)
@@ -127,7 +127,7 @@ We support **BOTH** naming conventions for maximum compatibility:
```bash
TOR_RELAY_MODE=bridge
TOR_NICKNAME=MyBridge
TOR_CONTACT_INFO=admin@example.com
TOR_CONTACT_INFO="email:admin[]example.com ciissversion:2"
TOR_ORPORT=9001
TOR_OBFS4_PORT=9002
```
@@ -296,6 +296,6 @@ If you still see this error after updating to the latest version:
---
**Version:** 1.1.6
**Last Updated:** 2026-02-08
**Version:** 1.1.7
**Last Updated:** 2026-03-02
**Maintainer:** rE-Bo0t.bx1 <r3bo0tbx1@brokenbotnet.com>

View File

@@ -46,7 +46,7 @@
"cosmos-icon": "https://raw.githubusercontent.com/r3bo0tbx1/tor-guard-relay/refs/heads/main/src/onion.png",
"cosmos-stack": "TorGuardRelay",
"cosmos-stack-main": "TorGuardRelay",
"cosmos-version": "1.1.6",
"cosmos-version": "1.1.7",
"maintainer": "rE-Bo0t.bx1 <r3bo0tbx1@brokenbotnet.com>"
}
}

View File

@@ -46,7 +46,7 @@
"cosmos-icon": "https://raw.githubusercontent.com/r3bo0tbx1/tor-guard-relay/refs/heads/main/src/obfs4.png",
"cosmos-stack": "OBFS4-Bridge",
"cosmos-stack-main": "OBFS4-Bridge",
"cosmos-version": "1.1.6",
"cosmos-version": "1.1.7",
"maintainer": "rE-Bo0t.bx1 <r3bo0tbx1@brokenbotnet.com>"
}
}

View File

@@ -52,7 +52,7 @@
"cosmos-stack-main": "OBFS4-Bridge",
"cosmos-description": "🌉 Tor obfs4 Bridge - Drop-in replacement for thetorproject/obfs4-bridge",
"cosmos-icon": "https://raw.githubusercontent.com/r3bo0tbx1/tor-guard-relay/refs/heads/main/src/obfs4.png",
"cosmos-version": "1.1.6",
"cosmos-version": "1.1.7",
"maintainer": "rE-Bo0t.bx1 <r3bo0tbx1@brokenbotnet.com>"
}
}

View File

@@ -9,7 +9,7 @@
"environment": [
"TOR_RELAY_MODE=bridge",
"TOR_NICKNAME=MyTorBridge",
"TOR_CONTACT_INFO=admin@example.com",
"TOR_CONTACT_INFO=email:admin[]example.com ciissversion:2",
"TOR_ORPORT=9001",
"TOR_OBFS4_PORT=9002"
],
@@ -54,7 +54,7 @@
"cosmos-description": "🧅 Tor obfs4 Bridge - ENV-based config",
"cosmos-icon": "https://raw.githubusercontent.com/r3bo0tbx1/tor-guard-relay/refs/heads/main/src/obfs4.png",
"cosmos-force-network-secured": "false",
"cosmos-version": "1.1.6"
"cosmos-version": "1.1.7"
}
}
},

View File

@@ -9,7 +9,7 @@
"environment": [
"TOR_RELAY_MODE=exit",
"TOR_NICKNAME=MyExitRelay",
"TOR_CONTACT_INFO=admin@example.com",
"TOR_CONTACT_INFO=email:admin[]example.com ciissversion:2",
"TOR_ORPORT=9001",
"TOR_DIRPORT=0",
"TOR_BANDWIDTH_RATE=50 MBytes",
@@ -58,7 +58,7 @@
"cosmos-icon": "https://raw.githubusercontent.com/r3bo0tbx1/tor-guard-relay/refs/heads/main/src/exit.png",
"cosmos-auto-update": "true",
"cosmos-force-network-secured": "false",
"cosmos-version": "1.1.6"
"cosmos-version": "1.1.7"
}
}
},

View File

@@ -9,7 +9,7 @@
"environment": [
"TOR_RELAY_MODE=guard",
"TOR_NICKNAME=MyGuardRelay",
"TOR_CONTACT_INFO=admin@example.com",
"TOR_CONTACT_INFO=email:admin[]example.com ciissversion:2",
"TOR_ORPORT=9001",
"TOR_DIRPORT=0",
"TOR_BANDWIDTH_RATE=50 MBytes",
@@ -56,7 +56,7 @@
"cosmos-description": "🛡️ Tor Guard Relay | ENV-based config",
"cosmos-icon": "https://raw.githubusercontent.com/r3bo0tbx1/tor-guard-relay/refs/heads/main/src/onion.png",
"cosmos-force-network-secured": "false",
"cosmos-version": "1.1.6"
"cosmos-version": "1.1.7"
}
}
},

View File

@@ -56,7 +56,7 @@
"cosmos-description": "🛡️ Multi Tor Guard Relay - 1",
"cosmos-icon": "https://raw.githubusercontent.com/r3bo0tbx1/tor-guard-relay/main/src/onion.png",
"cosmos-force-network-secured": "false",
"cosmos-version": "1.1.6",
"cosmos-version": "1.1.7",
"maintainer": "rE-Bo0t.bx1 <r3bo0tbx1@brokenbotnet.com>"
}
},
@@ -114,7 +114,7 @@
"cosmos-description": "🛡️ Multi Tor Guard Relay - 2",
"cosmos-icon": "https://raw.githubusercontent.com/r3bo0tbx1/tor-guard-relay/main/src/onion.png",
"cosmos-force-network-secured": "false",
"cosmos-version": "1.1.6",
"cosmos-version": "1.1.7",
"maintainer": "rE-Bo0t.bx1 <r3bo0tbx1@brokenbotnet.com>"
}
},
@@ -172,7 +172,7 @@
"cosmos-description": "🛡️ Multi Tor Guard Relay - 3",
"cosmos-icon": "https://raw.githubusercontent.com/r3bo0tbx1/tor-guard-relay/main/src/onion.png",
"cosmos-force-network-secured": "false",
"cosmos-version": "1.1.6",
"cosmos-version": "1.1.7",
"maintainer": "rE-Bo0t.bx1 <r3bo0tbx1@brokenbotnet.com>"
}
}

View File

@@ -41,7 +41,7 @@ services:
labels:
com.centurylinklabs.watchtower.enable: "true"
description: "Tor obfs4 Bridge - Drop-in replacement for thetorproject/obfs4-bridge"
version: "1.1.6"
version: "1.1.7"
maintainer: "rE-Bo0t.bx1 <r3bo0tbx1@brokenbotnet.com>"
volumes:

View File

@@ -9,7 +9,7 @@ services:
environment:
TOR_RELAY_MODE: bridge
TOR_NICKNAME: MyTorBridge
TOR_CONTACT_INFO: "your-email@example.com"
TOR_CONTACT_INFO: "email:your-email[]example.com ciissversion:2"
TOR_ORPORT: 9001
TOR_OBFS4_PORT: 9002
TOR_BANDWIDTH_RATE: "10 MBytes"
@@ -36,7 +36,7 @@ services:
labels:
com.centurylinklabs.watchtower.enable: "true"
description: "Tor obfs4 Bridge"
version: "1.1.6"
version: "1.1.7"
maintainer: "rE-Bo0t.bx1 <r3bo0tbx1@brokenbotnet.com>"
volumes:

View File

@@ -9,11 +9,15 @@ services:
environment:
TOR_RELAY_MODE: exit
TOR_NICKNAME: MyExitRelay
TOR_CONTACT_INFO: "your-email@example.com <0xYOUR_PGP_KEY>"
TOR_CONTACT_INFO: "email:your-email[]example.com url:https://example.com proof:uri-rsa ciissversion:2"
TOR_ORPORT: 9001
TOR_DIRPORT: 0
TOR_BANDWIDTH_RATE: "50 MBytes"
TOR_BANDWIDTH_BURST: "100 MBytes"
# Happy Family (Tor 0.4.9+) - generate with: docker exec tor-exit-relay gen-family MyRelays
# TOR_FAMILY_ID: "YOUR_FAMILY_ID_HERE"
# MyFamily (legacy, comma-separated fingerprints - keep during transition)
# TOR_MY_FAMILY: "FINGERPRINT1,FINGERPRINT2"
TOR_EXIT_POLICY: "accept *:20-23,accept *:43,accept *:53,accept *:79-81,accept *:88,accept *:110,accept *:143,accept *:194,accept *:220,accept *:389,accept *:443,accept *:464,accept *:465,accept *:531,accept *:543-544,accept *:554,accept *:563,accept *:636,accept *:706,accept *:749,accept *:873,accept *:902-904,accept *:981,accept *:989-995,accept *:1194,accept *:1220,accept *:1293,accept *:1500,accept *:1533,accept *:1677,accept *:1723,accept *:1755,accept *:1863,accept *:2082,accept *:2083,accept *:2086-2087,accept *:2095-2096,accept *:2102-2104,accept *:3128,accept *:3389,accept *:3690,accept *:4321,accept *:4643,accept *:5050,accept *:5190,accept *:5222-5223,accept *:5228,accept *:5900,accept *:6660-6669,accept *:6679,accept *:6697,accept *:8000,accept *:8008,accept *:8074,accept *:8080,accept *:8082,accept *:8087-8088,accept *:8232-8233,accept *:8332-8333,accept *:8443,accept *:8888,accept *:9418,accept *:9999,accept *:10000,accept *:11371,accept *:19294,accept *:19638,accept *:50002,accept *:64738,reject *:*"
volumes:
- tor-exit-data:/var/lib/tor
@@ -37,7 +41,7 @@ services:
labels:
com.centurylinklabs.watchtower.enable: "true"
description: "Tor Exit Relay"
version: "1.1.6"
version: "1.1.7"
maintainer: "rE-Bo0t.bx1 <r3bo0tbx1@brokenbotnet.com>"
volumes:

View File

@@ -9,11 +9,15 @@ services:
environment:
TOR_RELAY_MODE: guard
TOR_NICKNAME: MyGuardRelay
TOR_CONTACT_INFO: "your-email@example.com <0xYOUR_PGP_KEY>"
TOR_CONTACT_INFO: "email:your-email[]example.com url:https://example.com proof:uri-rsa ciissversion:2"
TOR_ORPORT: 9001
TOR_DIRPORT: 0
TOR_BANDWIDTH_RATE: "50 MBytes"
TOR_BANDWIDTH_BURST: "100 MBytes"
# Happy Family (Tor 0.4.9+) - generate with: docker exec tor-guard-relay gen-family MyRelays
# TOR_FAMILY_ID: "YOUR_FAMILY_ID_HERE"
# MyFamily (legacy, comma-separated fingerprints - keep during transition)
# TOR_MY_FAMILY: "FINGERPRINT1,FINGERPRINT2"
volumes:
- tor-guard-data:/var/lib/tor
- tor-guard-logs:/var/log/tor

View File

@@ -1,5 +1,26 @@
version: '3.8'
# Happy Family Setup (Tor 0.4.9+):
#
# Option A - Generate a new key:
# 1. Start relay-1, then run: docker exec guard-relay-1 gen-family MyRelays
# 2. Copy the .secret_family_key to all relay volumes:
# docker cp guard-relay-1:/var/lib/tor/keys/MyRelays.secret_family_key .
# docker cp MyRelays.secret_family_key guard-relay-2:/var/lib/tor/keys/
# docker cp MyRelays.secret_family_key guard-relay-3:/var/lib/tor/keys/
# 3. Fix ownership: docker exec -u 0 <container> chown 100:101 /var/lib/tor/keys/MyRelays.secret_family_key
# 4. Add 'FamilyId <value>' to each relay's torrc
# 5. Keep MyFamily lines during the transition period
# 6. Restart all relays
#
# Option B - Import an existing key from a non-Docker setup:
# 1. Copy your .secret_family_key to the Docker host
# 2. docker cp MyRelays.secret_family_key guard-relay-1:/var/lib/tor/keys/
# (repeat for each relay container)
# 3. Fix ownership: docker exec -u 0 <container> chown 100:101 /var/lib/tor/keys/MyRelays.secret_family_key
# 4. Add 'FamilyId <value>' to each relay's torrc
# 5. Restart all relays
services:
tor-relay-1:
image: r3bo0tbx1/onion-relay:latest

View File

@@ -703,7 +703,7 @@
<p>
<strong>Important:</strong> The traffic you observed <strong>did not originate from this machine</strong>. It originated from an anonymous user somewhere in the world who routed their traffic through the Tor network. This server merely forwarded that traffic to your destination.
</p>
<p>Think of this server like a postal service that delivers lettersthe postal worker doesn't write the letters, they just deliver them. Similarly, this exit node doesn't create traffic, it only forwards it.</p>
<p>Think of this server like a postal service that delivers letters - the postal worker doesn't write the letters, they just deliver them. Similarly, this exit node doesn't create traffic, it only forwards it.</p>
</section>
<section class="card">
<h2>

249
tools/gen-family Normal file
View File

@@ -0,0 +1,249 @@
#!/bin/sh
# Generate or display Tor Happy Family key and FamilyId
# Requires Tor >= 0.4.9.x with --keygen-family support
set -e
GREEN=$(printf '\033[0;32m')
BLUE=$(printf '\033[0;34m')
YELLOW=$(printf '\033[1;33m')
CYAN=$(printf '\033[0;36m')
RED=$(printf '\033[0;31m')
BOLD=$(printf '\033[1m')
NC=$(printf '\033[0m')
TOR_DATA_DIR="${TOR_DATA_DIR:-/var/lib/tor}"
KEYS_DIR="${TOR_DATA_DIR}/keys"
FORCE=0
for arg in "$@"; do
case "$arg" in
--force|-f) FORCE=1 ;;
esac
done
FAMILY_NAME=""
for arg in "$@"; do
case "$arg" in
--force|-f|--help|-h|--show|-s) ;;
*) FAMILY_NAME="$arg"; break ;;
esac
done
FAMILY_NAME="${FAMILY_NAME:-TorFamily}"
show_usage() {
echo ""
echo "${CYAN}╔════════════════════════════════════════════════════════════╗${NC}"
echo "${CYAN}║${NC} ${BOLD}Tor Happy Family Key Generator${NC} ${CYAN}║${NC}"
echo "${CYAN}╚════════════════════════════════════════════════════════════╝${NC}"
echo ""
echo "${BOLD}Usage:${NC}"
echo " gen-family [name] Generate a new family key"
echo " gen-family [name] --force Overwrite existing key (no backup prompt)"
echo " gen-family --show Show existing FamilyId"
echo " gen-family --help Show this help"
echo ""
echo "${BOLD}Examples:${NC}"
echo " docker exec tor-relay gen-family MyRelays"
echo " docker exec tor-relay gen-family --show"
echo ""
echo "${BOLD}What is Happy Family?${NC}"
echo " Tor 0.4.9+ replaces MyFamily with a cryptographic key-based"
echo " system. All relays in a family share one secret key file."
echo " See: https://community.torproject.org/relay/setup/post-install/family-ids/"
echo ""
}
show_existing() {
echo ""
echo "${CYAN}╔════════════════════════════════════════════════════════════╗${NC}"
echo "${CYAN}║${NC} ${BOLD}Tor Happy Family - Current Status${NC} ${CYAN}║${NC}"
echo "${CYAN}╚════════════════════════════════════════════════════════════╝${NC}"
echo ""
FOUND=0
for keyfile in "$KEYS_DIR"/*.secret_family_key; do
[ -f "$keyfile" ] || continue
FOUND=1
BASENAME=$(basename "$keyfile" .secret_family_key)
echo "${GREEN}✓${NC} Found family key: ${BOLD}${BASENAME}${NC}"
echo " File: ${BLUE}${keyfile}${NC}"
done
if [ "$FOUND" -eq 0 ]; then
echo "${YELLOW}⚠${NC} No family keys found in ${KEYS_DIR}"
echo ""
echo " Generate one with: ${BOLD}gen-family MyRelays${NC}"
echo ""
exit 1
fi
TORRC="${TOR_CONFIG:-/etc/tor/torrc}"
if [ -f "$TORRC" ]; then
echo ""
FAMILY_IDS=$(grep -i "^FamilyId " "$TORRC" 2>/dev/null || true)
if [ -n "$FAMILY_IDS" ]; then
echo "${GREEN}✓${NC} FamilyId in torrc:"
echo "$FAMILY_IDS" | while read -r line; do
echo " ${BLUE}${line}${NC}"
done
else
echo "${YELLOW}⚠${NC} No FamilyId line found in torrc"
echo " Add the FamilyId line to your torrc to activate Happy Family"
fi
fi
if [ -f "$TORRC" ]; then
MY_FAMILY=$(grep -i "^MyFamily " "$TORRC" 2>/dev/null || true)
if [ -n "$MY_FAMILY" ]; then
echo ""
echo "${YELLOW}${NC} MyFamily lines also present (transition period - this is expected)"
MY_FAMILY_COUNT=$(echo "$MY_FAMILY" | wc -l)
echo " ${MY_FAMILY_COUNT} MyFamily entries configured"
fi
fi
echo ""
}
generate_key() {
echo ""
echo "${CYAN}╔════════════════════════════════════════════════════════════╗${NC}"
echo "${CYAN}║${NC} ${BOLD}Tor Happy Family Key Generator${NC} ${CYAN}║${NC}"
echo "${CYAN}╚════════════════════════════════════════════════════════════╝${NC}"
echo ""
TOR_VERSION=$(tor --version 2>/dev/null | head -n1 || echo "unknown")
echo "${BOLD}Tor version:${NC} ${TOR_VERSION}"
echo ""
if [ -f "${KEYS_DIR}/${FAMILY_NAME}.secret_family_key" ]; then
if [ "$FORCE" -eq 1 ]; then
echo "${YELLOW}⚠${NC} Removing existing family key: ${BOLD}${FAMILY_NAME}${NC}"
rm -f "${KEYS_DIR}/${FAMILY_NAME}.secret_family_key"
echo "${GREEN}✓${NC} Old key removed"
echo ""
else
echo "${YELLOW}⚠${NC} Family key '${FAMILY_NAME}' already exists!"
echo " File: ${BLUE}${KEYS_DIR}/${FAMILY_NAME}.secret_family_key${NC}"
echo ""
echo " ${BOLD}To replace without backup:${NC}"
echo " ${BOLD}gen-family ${FAMILY_NAME} --force${NC}"
echo ""
echo " ${BOLD}To replace safely:${NC}"
echo " 1. Back it up first:"
echo " ${BOLD}docker cp <container>:${KEYS_DIR}/${FAMILY_NAME}.secret_family_key ./${FAMILY_NAME}.secret_family_key.bak${NC}"
echo " 2. Remove the old key:"
echo " ${BOLD}docker exec -u 0 <container> rm ${KEYS_DIR}/${FAMILY_NAME}.secret_family_key${NC}"
echo " 3. Run gen-family again:"
echo " ${BOLD}gen-family ${FAMILY_NAME}${NC}"
echo ""
echo " ${RED}⚠️ WARNING:${NC} Replacing a family key means ALL relays using it"
echo " must be updated with the new key and FamilyId. Back up first!"
echo ""
echo " ${BOLD}Or use a different name:${NC} gen-family NewFamilyName"
echo " ${BOLD}View existing keys:${NC} gen-family --show"
echo ""
exit 1
fi
fi
mkdir -p "$KEYS_DIR" 2>/dev/null || true
echo "${BOLD}Generating family key: ${CYAN}${FAMILY_NAME}${NC}"
echo ""
KEYGEN_OUTPUT=$(cd "$KEYS_DIR" && tor --keygen-family "$FAMILY_NAME" 2>&1) || {
echo "${RED}✗${NC} Failed to generate family key!"
echo ""
echo " Output: ${KEYGEN_OUTPUT}"
echo ""
echo " This requires Tor >= 0.4.9.2-alpha"
echo " Your version: ${TOR_VERSION}"
echo ""
exit 1
}
FAMILY_ID=$(echo "$KEYGEN_OUTPUT" | grep "^FamilyId " | head -1)
if [ -z "$FAMILY_ID" ]; then
FAMILY_ID=$(echo "$KEYGEN_OUTPUT" | grep "FamilyId" | head -1 | sed 's/.*\(FamilyId .*\)/\1/')
fi
echo "${GREEN}✓${NC} Family key generated successfully!"
echo ""
if [ -n "$FAMILY_ID" ]; then
echo "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo "${BOLD}1. Add this line to your torrc:${NC}"
echo "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
echo " ${GREEN}${FAMILY_ID}${NC}"
echo ""
else
echo "${YELLOW}⚠${NC} Could not extract FamilyId from output."
echo " Full output:"
echo " ${KEYGEN_OUTPUT}"
echo ""
fi
echo "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo "${BOLD}2. Key file location:${NC}"
echo "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
echo " ${BLUE}${KEYS_DIR}/${FAMILY_NAME}.secret_family_key${NC}"
echo ""
echo "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo "${BOLD}3. For multi-relay setups:${NC}"
echo "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
echo " Copy this key to ALL your relays. Each relay needs:"
echo " • The ${BOLD}.secret_family_key${NC} file in its keys directory"
echo " • The ${BOLD}FamilyId${NC} line in its torrc"
echo ""
echo " Extract from this container:"
echo " ${BOLD}docker cp <container>:${KEYS_DIR}/${FAMILY_NAME}.secret_family_key .${NC}"
echo ""
echo " Copy to another container's volume:"
echo " ${BOLD}docker cp ${FAMILY_NAME}.secret_family_key <other>:${KEYS_DIR}/${NC}"
echo ""
echo "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
echo "${CYAN}📝 Next steps:${NC}"
echo " 1. Add the FamilyId line to your torrc (all relays)"
echo " 2. Copy the .secret_family_key to all relay keys directories"
echo " 3. Restart your relays: ${BOLD}docker restart tor-relay${NC}"
echo " 4. Keep MyFamily lines during the transition period"
echo ""
echo "${CYAN}⚠️ Important:${NC}"
echo " • Keep your .secret_family_key ${RED}SECRET${NC} - treat it like a private key"
echo " • Back up the key file - losing it means regenerating for all relays"
echo " • The key persists in your Docker volume (${BOLD}/var/lib/tor${NC})"
echo " • During transition, keep BOTH MyFamily AND FamilyId configured"
echo ""
echo "${CYAN}📖 Documentation:${NC}"
echo " https://community.torproject.org/relay/setup/post-install/family-ids/"
echo ""
}
ACTION=""
for arg in "$@"; do
case "$arg" in
--help|-h) ACTION="help"; break ;;
--show|-s) ACTION="show"; break ;;
--force|-f) ;;
*) ACTION="generate"; break ;;
esac
done
case "${ACTION:-help}" in
help)
show_usage
;;
show)
show_existing
;;
generate)
generate_key
;;
esac

View File

@@ -57,6 +57,24 @@ if [ -f "$TOR_DATA/fingerprint" ] && [ -r "$TOR_DATA/fingerprint" ]; then
echo "🔑 Fingerprint: ${FP_START}...${FP_END}"
fi
# Happy Family status
FAMILY_KEY_COUNT=0
if [ -d "$TOR_DATA/keys" ]; then
for fk in "$TOR_DATA/keys"/*.secret_family_key; do
[ -f "$fk" ] || continue
FAMILY_KEY_COUNT=$((FAMILY_KEY_COUNT + 1))
done
fi
TORRC="${TOR_CONFIG:-/etc/tor/torrc}"
if [ "$FAMILY_KEY_COUNT" -gt 0 ]; then
echo "👨‍👩‍👧 Family Key: $FAMILY_KEY_COUNT key(s) present"
fi
if [ -f "$TORRC" ] && grep -qi "^FamilyId " "$TORRC" 2>/dev/null; then
echo "✅ Happy Family: CONFIGURED"
elif [ "$FAMILY_KEY_COUNT" -gt 0 ]; then
echo "⚠️ Happy Family: key present but FamilyId not in torrc"
fi
ERRORS=$(grep -c "\[err\]" "$TOR_LOG" 2>/dev/null || echo "0")
ERRORS=$(sanitize_num "$ERRORS")
WARNINGS=$(grep -c "\[warn\]" "$TOR_LOG" 2>/dev/null || echo "0")