#!/bin/sh set -e # --- Configuration --- # If the first argument is 'vanguards', skip Tor setup and just run vanguards if [ "$1" = "vanguards" ]; then echo "Starting Vanguards Sidecar Mode..." shift # remove 'vanguards' from the arguments # Extract the hostname from the arguments? # For now, we assume 'tor-service' as per the standard docker-compose setup TARGET_HOST="tor-service" TARGET_PORT=9051 echo "Waiting for Tor Control Port at $TARGET_HOST:$TARGET_PORT..." # Use Python to wait for the port (more reliable than Alpine's nc) python3 -c "import socket, time; while True: try: with socket.create_connection(('$TARGET_HOST', $TARGET_PORT), timeout=1): break except (OSError, ConnectionRefusedError): time.sleep(1)" echo "Tor is awake! Resolving IP..." # Fix for ValueError: Invalid IP address # stem/vanguards requires a valid IP, not a hostname TARGET_IP=$(python3 -c "import socket; print(socket.gethostbyname('$TARGET_HOST'))") echo "Resolved $TARGET_HOST to $TARGET_IP" # Replace hostname with IP in arguments # Note: This crude replacement assumes 'tor-service' is passed as an arg args="" for arg in "$@"; do if [ "$arg" = "$TARGET_HOST" ]; then args="$args $TARGET_IP" else args="$args $arg" fi done # Execute with new args (unquoted expansion nicely splits args here, # safe enough for this specific use case of rigid flags) exec vanguards $args fi # If other arguments are passed, exec them (fallback) if [ "$#" -gt 0 ]; then exec "$@" fi # --- Tor Configuration (Only runs if no command passed) --- TOR_CONFIG="/etc/tor/torrc" # Default to /var/lib/tor if not set DATA_DIR="${TOR_DATA_DIR:-/var/lib/tor}" echo "Starting Tor Configuration..." # 1. Reset the Config File echo "DataDirectory $DATA_DIR" > "$TOR_CONFIG" echo "User tor" >> "$TOR_CONFIG" # 2. Handle Control Password (The Magic Hashing) if [ -n "$TOR_CONTROL_PASSWORD" ]; then echo "Hashing provided control password..." # Generate the hash using Tor itself HASHED_PASSWORD=$(tor --quiet --hash-password "$TOR_CONTROL_PASSWORD" | tail -n 1) if [ -z "$HASHED_PASSWORD" ]; then echo "Error: Failed to hash password." exit 1 fi echo "ControlPort 0.0.0.0:9051" >> "$TOR_CONFIG" echo "HashedControlPassword $HASHED_PASSWORD" >> "$TOR_CONFIG" echo "Control Password set." else echo "Warning: No TOR_CONTROL_PASSWORD set. Control port disabled." fi # 3. Handle Hidden Services (The Magic Parsing) # Expected Format: "80:container_name:80 22:container_name:22" if [ -n "$HIDDEN_SERVICE_HOSTS" ]; then echo "HiddenServiceDir $DATA_DIR/hidden_service/" >> "$TOR_CONFIG" echo "HiddenServiceVersion 3" >> "$TOR_CONFIG" # Split the string by spaces for rule in $HIDDEN_SERVICE_HOSTS; do # Validate format: Port:Host:Port (using grep regex) if ! echo "$rule" | grep -qE '^[0-9]+:[a-zA-Z0-9.-]+:[0-9]+$'; then echo "CRITICAL ERROR: Invalid format in HIDDEN_SERVICE_HOSTS: '$rule'" echo "Expected format: ExternalPort:ContainerHost:InternalPort (e.g., 80:my-web:80)" exit 1 fi # Extract parts EXT_PORT=$(echo "$rule" | cut -d: -f1) HOST=$(echo "$rule" | cut -d: -f2) INT_PORT=$(echo "$rule" | cut -d: -f3) # Sanitize HOST HOST_CLEAN=$(echo "$HOST" | tr -d '\r') # RESOLVE HOST TO IP FOR TOR # Tor is picky about hostnames in HiddenServicePort configs sometimes. # We pre-resolve it to be safe. echo "Resolving $HOST_CLEAN..." if ! HOST_IP=$(python3 -c "import socket; print(socket.gethostbyname('$HOST_CLEAN'))" 2>/dev/null); then echo "Warning: Could not resolve '$HOST_CLEAN'. Using hostname directly." HOST_IP=$HOST_CLEAN else echo "Resolved $HOST_CLEAN to $HOST_IP" fi echo "Adding Hidden Service Rule: Onion:$EXT_PORT -> $HOST_IP:$INT_PORT" echo "HiddenServicePort $EXT_PORT $HOST_IP:$INT_PORT" >> "$TOR_CONFIG" done else echo "Warning: HIDDEN_SERVICE_HOSTS is empty. Tor will run but host nothing." fi # 4. Ownership Fix (Crucial for Docker volumes) mkdir -p "$DATA_DIR/hidden_service/" chown -R tor:root "$DATA_DIR" chmod 700 "$DATA_DIR" echo "Configuration successful. Starting Tor..." exec tor -f "$TOR_CONFIG"