mirror of
https://github.com/r3bo0tbx1/tor-guard-relay.git
synced 2026-04-06 00:32:04 +02:00
📝 docs(v1.1.1): Documentation, templates, and CI/CD enhancements
- 📘 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
This commit is contained in:
818
docs/ARCHITECTURE.md
Normal file
818
docs/ARCHITECTURE.md
Normal file
@@ -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 <container>
|
||||
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
|
||||
542
docs/FAQ.md
Normal file
542
docs/FAQ.md
Normal file
@@ -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 <IP>:<PORT> <FINGERPRINT> cert=<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)
|
||||
Reference in New Issue
Block a user