Files
tor-guard-relay/docs/ARCHITECTURE.md
rE-Bo0t.bx1 be4f2bc125 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.
2026-03-02 16:23:10 +08:00

31 KiB
Raw Blame History

Architecture Documentation

Tor Guard Relay Container - Technical Architecture & Design

Table of Contents

  1. Overview
  2. Container Lifecycle
  3. Initialization Flow
  4. Configuration System
  5. ENV Compatibility Layer
  6. Diagnostic Tools
  7. Directory Structure
  8. Security Model
  9. 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 (~16.8 MB total image)
  • Security-first (non-root, minimal capabilities, strict validation)
  • Multi-architecture (AMD64, ARM64)
  • Production-ready (graceful shutdown, health checks, observability)

Container Lifecycle

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 or 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]
    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]
    StopTor --> Wait[⏳ Wait for Tor Exit]
    Wait --> Cleanup[🧹 Cleanup and Exit]
    Cleanup --> End([🔴 Container Stop])

    style Start fill:#b2fab4
    style End fill:#ffb3c6
    style Running fill:#90caf9
    style TorStart fill:#fff59d
    style Trap fill:#ffcc80

Initialization Flow

The entrypoint script (docker-entrypoint.sh) executes 6 distinct phases in sequence:

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]
        P2_2 --> P2_3[👨‍👩‍👧 Detect family keys]
    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 and arch]
        P5_2 --> P5_3[📡 Show relay mode and 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:#ffcdd2
    style P4_4 fill:#ffcdd2
    style Launch fill:#fff59d

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, 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
6 Diagnostics List available tools Informational only

Configuration System

Configuration Priority

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 Start fill:#c8e6c9
    style UseMounted fill:#b2fab4
    style UseEnv fill:#b2fab4
    style Success fill:#b2fab4
    style Error1 fill:#ffcdd2
    style Error2 fill:#ffcdd2
    style Failure fill:#ffcdd2
    style Validate fill:#fff9c4
    style Generate fill:#fff9c4
    style ModeCheck fill:#e1f5fe
    style GenGuard fill:#e3f2fd
    style GenExit fill:#fce4ec
    style GenBridge fill:#e8f5e9
    style OBFS4Check fill:#f3e5f5
    style ProcessOBFS4V fill:#ede7f6

Code Reference: docker-entrypoint.sh lines 201-220 (phase_3_configuration)

ENV Variable Validation

All ENV variables are validated before config generation:

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:#b2fab4
    style Failure fill:#ffcdd2
    style V4_Warn fill:#fff59d
    style Start fill:#c8e6c9
    style V1 fill:#e3f2fd
    style V2 fill:#e3f2fd
    style V3 fill:#e3f2fd
    style V4 fill:#e3f2fd
    style V5 fill:#e3f2fd

Code Reference: docker-entrypoint.sh lines 115-198 (validate_relay_config)


ENV Compatibility Layer

The container supports two naming conventions for maximum compatibility:

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:#e3f2fd
    style Compat fill:#fff4e6
    style Internal fill:#e8f5e9
    style Config fill:#fff59d

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

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| 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)"]
        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| 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)"]
        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:#b2fab4
    style GuardConfig fill:#e3f2fd
    style ExitConfig fill:#fce4ec
    style BridgeConfig fill:#e8f5e9
    style Mode fill:#fff9c4
    style Base fill:#fff9c4

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)

OBFS4V_* Variable Processing (Bridge Mode)

Security-critical whitelisting to prevent injection attacks:

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:#b2fab4
    style Done fill:#b2fab4
    style Warn1 fill:#fff59d
    style Warn2 fill:#fff59d
    style Warn3 fill:#fff59d
    style Warn4 fill:#fff59d
    style Start fill:#c8e6c9
    style Enable fill:#e3f2fd

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
  • No code execution: Values written with printf, not eval

Code Reference: docker-entrypoint.sh lines 292-343 (OBFS4V processing)


Diagnostic Tools

Six busybox-only diagnostic tools provide observability:

flowchart TD
    User([👤 User: docker exec]) --> Choice{🛠️ Which tool?}

    Choice -->|status| StatusFlow
    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 %]
        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 and 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])

    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

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
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)
  • 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

graph TD
    %% Main directory structure
    Root["📦 Container Root"] --> Etc["📁 /etc"]
    Root --> Var["📁 /var"]
    Root --> Run["📁 /run"]
    Root --> Usr["📁 /usr"]
    Root --> Sbin["📁 /sbin"]
    Root --> BuildInfo["📄 /build-info.txt"]

    %% =============== /etc/tor ===============
    subgraph etc_group["⚙️ Configuration Layer"]
        direction TB
        TorEtc["📁 /etc/tor"]
        TorRC["⚙️ torrc"]
        TorRCSample["🗑️ torrc.sample"]
        
        TorEtc --> TorRC
        TorEtc -.->|Deleted at build| TorRCSample
    end
    Etc --> TorEtc

    %% =============== /var/lib ===============
    subgraph var_lib_group["💾 Persistent Data Volume"]
        direction TB
        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
    Var --> Lib

    %% =============== /var/log ===============
    subgraph var_log_group["📜 Log Volume"]
        direction TB
        Log["📁 /var/log"]
        TorLog["📦 /var/log/tor VOLUME"]
        Notices["📄 notices.log"]
        
        Log --> TorLog
        TorLog --> Notices
    end
    Var --> Log

    %% =============== /run/tor ===============
    subgraph run_group["⚡ Runtime State"]
        direction TB
        TorRun["📁 /run/tor"]
        TorPID["🧩 tor.pid"]
        
        TorRun --> TorPID
    end
    Run --> TorRun

    %% =============== /usr/local/bin ===============
    subgraph usr_local_group["🚀 Custom Scripts"]
        direction TB
        UsrLocal["📁 /usr/local"]
        Bin["📁 /usr/local/bin"]
        Entrypoint["🚀 docker-entrypoint.sh"]
        Healthcheck["❤️ healthcheck.sh"]
        Status["📡 status"]
        Health["💚 health"]
        Fingerprint["🧬 fingerprint"]
        BridgeLine["🌉 bridge-line"]
        GenAuth["🔑 gen-auth"]
        GenFamily["👨‍👩‍👧 gen-family"]
        
        UsrLocal --> Bin
        Bin --> Entrypoint
        Bin --> Healthcheck
        Bin --> Status
        Bin --> Health
        Bin --> Fingerprint
        Bin --> BridgeLine
        Bin --> GenAuth
        Bin --> GenFamily
    end
    Usr --> UsrLocal

    %% =============== /usr/bin ===============
    subgraph usr_bin_group["🎯 Binaries"]
        direction TB
        UsrBin["📁 /usr/bin"]
        TorBin["🧅 tor"]
        Lyrebird["🎶 lyrebird"]
        
        UsrBin --> TorBin
        UsrBin --> Lyrebird
    end
    Usr --> UsrBin

    %% =============== /sbin ===============
    subgraph sbin_group["🟢 Init System"]
        direction TB
        Tini["🟩 /sbin/tini"]
    end
    Sbin --> Tini

    %% =============== Styling ===============
    classDef volumeStyle fill:#ff9e9e,stroke:#d32f2f,stroke-width:2px,color:#000
    classDef configStyle fill:#90caf9,stroke:#1976d2,stroke-width:2px,color:#000
    classDef scriptStyle fill:#fff176,stroke:#f57f17,stroke-width:2px,color:#000
    classDef binaryStyle fill:#a5d6a7,stroke:#388e3c,stroke-width:2px,color:#000
    classDef runtimeStyle fill:#ffcc80,stroke:#f57c00,stroke-width:2px,color:#000
    classDef deletedStyle fill:#e0e0e0,stroke:#9e9e9e,stroke-width:1px,color:#757575,stroke-dasharray: 5 5
    classDef infoStyle fill:#e1bee7,stroke:#7b1fa2,stroke-width:1px,color:#000

    class TorData,TorLog volumeStyle
    class TorRC configStyle
    class Entrypoint,Healthcheck,Status,Health,Fingerprint,BridgeLine,GenAuth,GenFamily scriptStyle
    class TorBin,Lyrebird,Tini binaryStyle
    class TorPID runtimeStyle
    class TorRCSample deletedStyle
    class BuildInfo infoStyle

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

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 or exec]
        Whitelist[🛡️ OBFS4V Whitelist]
    end

    subgraph NetworkSec["🌐 Network Security"]
        HostNet[🏠 --network host]
        NoPorts[🔕 No Exposed Monitoring Ports]
        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:#b2fab4

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:

sequenceDiagram
    participant User as 👤 User
    participant Docker as 🐳 Docker
    participant Tini as 🔧 Tini PID1
    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 510 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

flowchart LR
    subgraph Source["📁 Source Files"]
        Dockerfile[📄 Dockerfile]
        Scripts[🧾 Scripts]
        Tools[🛠️ Diagnostic Tools]
    end

    subgraph Build["🏗️ Docker Build"]
        Alpine[🐧 Alpine 3.23.0]
        Install[📦 apk add packages]
        Copy[📥 Copy scripts and 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:#fff59d
    style Push fill:#b2fab4
    style GHRelease fill:#b2fab4

Weekly Rebuild Strategy:

  • 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

Code Location: .github/workflows/release.yml


Health Check

Docker HEALTHCHECK runs every 10 minutes:

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:#b2fab4
    style Healthy2 fill:#b2fab4

    style Unhealthy1 fill:#ffcdd2
    style Unhealthy2 fill:#ffcdd2
    style Unhealthy3 fill:#ffcdd2
    style Unhealthy4 fill:#ffcdd2

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
tools/gen-auth Generate Control Port auth ~30
tools/gen-family Happy Family key management ~180

External Documentation


Document Version: 1.1.0 • Last Updated: 2026-03-02 • Container Version: v1.1.7