mirror of
https://github.com/r3bo0tbx1/tor-guard-relay.git
synced 2026-04-06 00:32:04 +02:00
🔧 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.
407 lines
12 KiB
Bash
407 lines
12 KiB
Bash
#!/bin/bash
|
||
#
|
||
# relay-status.sh - Tor relay/bridge status checker with security validation
|
||
# Version: 1.1.7
|
||
# Automatically detects Tor containers or uses specified container name
|
||
#
|
||
|
||
set -euo pipefail
|
||
|
||
# Configuration
|
||
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.7"
|
||
|
||
# Colors for output
|
||
readonly RED='\033[0;31m'
|
||
readonly GREEN='\033[0;32m'
|
||
readonly YELLOW='\033[1;33m'
|
||
readonly BLUE='\033[0;34m'
|
||
readonly CYAN='\033[0;36m'
|
||
readonly NC='\033[0m' # No Color
|
||
|
||
# Helper functions
|
||
print_header() {
|
||
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||
echo -e "${BLUE}$1${NC}"
|
||
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||
}
|
||
|
||
print_section() {
|
||
echo -e "\n${CYAN}▶ $1${NC}"
|
||
}
|
||
|
||
print_success() {
|
||
echo -e "${GREEN}✓${NC} $1"
|
||
}
|
||
|
||
print_warning() {
|
||
echo -e "${YELLOW}⚠${NC} $1"
|
||
}
|
||
|
||
print_error() {
|
||
echo -e "${RED}✗${NC} $1"
|
||
}
|
||
|
||
print_info() {
|
||
echo -e "${BLUE}ℹ${NC} $1"
|
||
}
|
||
|
||
# Detect Tor container automatically
|
||
detect_tor_container() {
|
||
local containers
|
||
containers=$(sudo docker ps --format '{{.Names}}' | grep -iE 'tor|relay|bridge|onion' || true)
|
||
|
||
if [[ -z "${containers}" ]]; then
|
||
print_error "No Tor-related containers found running"
|
||
echo
|
||
print_info "Running containers:"
|
||
sudo docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}' | sed 's/^/ /'
|
||
echo
|
||
print_info "Usage: $0 [container-name]"
|
||
exit 1
|
||
fi
|
||
|
||
local count
|
||
count=$(echo "${containers}" | wc -l)
|
||
|
||
if [[ ${count} -eq 1 ]]; then
|
||
CONTAINER="${containers}"
|
||
print_success "Auto-detected container: ${CONTAINER}"
|
||
else
|
||
print_warning "Multiple Tor containers found:"
|
||
echo "${containers}" | sed 's/^/ - /'
|
||
echo
|
||
print_info "Please specify which container to check:"
|
||
print_info "Usage: $0 <container-name>"
|
||
echo
|
||
print_info "Example: $0 ${containers%$'\n'*}"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
# Check if container is running
|
||
check_container() {
|
||
print_section "Container Status"
|
||
|
||
if ! sudo docker ps --format '{{.Names}}' | grep -q "^${CONTAINER}$"; then
|
||
print_error "Container '${CONTAINER}' is not running"
|
||
exit 1
|
||
fi
|
||
|
||
print_success "Container '${CONTAINER}' is running"
|
||
|
||
# Show uptime
|
||
local uptime
|
||
uptime=$(sudo docker inspect -f '{{.State.StartedAt}}' "${CONTAINER}" 2>/dev/null)
|
||
if [[ -n "${uptime}" ]]; then
|
||
echo -e " ${BLUE}Started:${NC} ${uptime}"
|
||
fi
|
||
|
||
# Show image version
|
||
local image
|
||
image=$(sudo docker inspect -f '{{.Config.Image}}' "${CONTAINER}" 2>/dev/null)
|
||
if [[ -n "${image}" ]]; then
|
||
echo -e " ${BLUE}Image:${NC} ${image}"
|
||
fi
|
||
|
||
# Check build info if available
|
||
local build_info
|
||
build_info=$(sudo docker exec "${CONTAINER}" cat /build-info.txt 2>/dev/null || echo "")
|
||
if [[ -n "${build_info}" ]]; then
|
||
echo -e " ${BLUE}Build:${NC}"
|
||
echo "${build_info}" | sed 's/^/ /'
|
||
fi
|
||
}
|
||
|
||
# Validate port security configuration
|
||
check_port_security() {
|
||
print_section "Port Security Validation"
|
||
|
||
# Check exposed ports
|
||
local exposed_ports
|
||
exposed_ports=$(sudo docker port "${CONTAINER}" 2>/dev/null || echo "")
|
||
|
||
if [[ -n "${exposed_ports}" ]]; then
|
||
echo -e " ${BLUE}Exposed ports:${NC}"
|
||
echo "${exposed_ports}" | sed 's/^/ /'
|
||
|
||
# Validate only 9001 and 9030 are exposed
|
||
if echo "${exposed_ports}" | grep -qE "^9001/tcp"; then
|
||
print_success "ORPort 9001 properly exposed"
|
||
else
|
||
print_warning "ORPort 9001 not exposed (may be using host network)"
|
||
fi
|
||
|
||
if echo "${exposed_ports}" | grep -qE "^9030/tcp"; then
|
||
print_success "DirPort 9030 properly exposed"
|
||
fi
|
||
|
||
# Check for improperly exposed internal ports
|
||
if echo "${exposed_ports}" | grep -qE "^903[5-9]/tcp"; then
|
||
print_error "SECURITY ISSUE: Internal metrics port exposed externally!"
|
||
echo -e " ${RED}Fix: Ensure metrics bind to 127.0.0.1 only${NC}"
|
||
fi
|
||
else
|
||
print_info "Using host network mode - checking port bindings..."
|
||
fi
|
||
|
||
# Check internal service bindings
|
||
local internal_bindings
|
||
internal_bindings=$(sudo docker exec "${CONTAINER}" netstat -tlnp 2>/dev/null | grep -E "9035|9036|9037" || echo "")
|
||
|
||
if [[ -n "${internal_bindings}" ]]; then
|
||
echo -e "\n ${BLUE}Internal services:${NC}"
|
||
|
||
# Validate localhost-only bindings
|
||
while IFS= read -r line; do
|
||
if echo "${line}" | grep -q "127.0.0.1"; then
|
||
local port
|
||
port=$(echo "${line}" | awk '{print $4}' | cut -d':' -f2)
|
||
print_success "Port ${port} properly bound to localhost"
|
||
else
|
||
local port
|
||
port=$(echo "${line}" | awk '{print $4}' | cut -d':' -f2)
|
||
print_error "SECURITY ISSUE: Port ${port} not bound to localhost!"
|
||
fi
|
||
done <<< "${internal_bindings}"
|
||
else
|
||
print_info "No internal service ports detected"
|
||
fi
|
||
}
|
||
|
||
# Display recent logs
|
||
show_logs() {
|
||
print_section "Recent Logs (last 15 lines)"
|
||
sudo docker logs --tail 15 "${CONTAINER}" 2>&1 | sed 's/^/ /'
|
||
}
|
||
|
||
# Show bootstrap progress
|
||
show_bootstrap() {
|
||
print_section "Bootstrap Progress"
|
||
|
||
local bootstrap_logs
|
||
bootstrap_logs=$(sudo docker logs "${CONTAINER}" 2>&1 | grep "Bootstrapped" | tail -5)
|
||
|
||
if [[ -n "${bootstrap_logs}" ]]; then
|
||
echo "${bootstrap_logs}" | sed 's/^/ /'
|
||
|
||
# Check if fully bootstrapped
|
||
if echo "${bootstrap_logs}" | grep -q "Bootstrapped 100%"; then
|
||
echo
|
||
print_success "Relay is fully bootstrapped!"
|
||
fi
|
||
else
|
||
print_warning "No bootstrap messages found in logs"
|
||
print_info "Relay might still be starting up"
|
||
fi
|
||
}
|
||
|
||
# Check if relay is reachable
|
||
check_reachability() {
|
||
print_section "Reachability Status"
|
||
|
||
local reachable_logs
|
||
reachable_logs=$(sudo docker logs "${CONTAINER}" 2>&1 | grep -iE "reachable|self-testing" | tail -3)
|
||
|
||
if [[ -n "${reachable_logs}" ]]; then
|
||
echo "${reachable_logs}" | sed 's/^/ /'
|
||
|
||
if echo "${reachable_logs}" | grep -q "reachable from the outside"; then
|
||
echo
|
||
print_success "ORPort is reachable!"
|
||
fi
|
||
else
|
||
print_warning "No reachability test results yet"
|
||
fi
|
||
}
|
||
|
||
# Display fingerprint
|
||
show_fingerprint() {
|
||
print_section "Relay Fingerprint"
|
||
|
||
if sudo docker exec "${CONTAINER}" test -f "${FINGERPRINT_PATH}" 2>/dev/null; then
|
||
local fingerprint
|
||
fingerprint=$(sudo docker exec "${CONTAINER}" cat "${FINGERPRINT_PATH}")
|
||
echo -e " ${GREEN}${fingerprint}${NC}"
|
||
echo
|
||
print_info "Search on Tor Metrics: https://metrics.torproject.org/rs.html"
|
||
print_info "Search on Onion Metrics: http://hctxrvjzfpvmzh2jllqhgvvkoepxb4kfzdjm6h7egcwlumggtktiftid.onion/rs.html"
|
||
else
|
||
print_warning "Fingerprint not found yet"
|
||
print_info "Tor might still be bootstrapping or generating keys"
|
||
fi
|
||
}
|
||
|
||
# Show ORPort configuration
|
||
show_orport() {
|
||
print_section "ORPort Configuration"
|
||
|
||
local orport_config
|
||
orport_config=$(sudo docker exec "${CONTAINER}" grep -iE "^(ORPort|DirPort|ObfsPort)" "${TORRC_PATH}" 2>/dev/null || true)
|
||
|
||
if [[ -n "${orport_config}" ]]; then
|
||
echo "${orport_config}" | sed 's/^/ /'
|
||
|
||
# Validate port numbers
|
||
if echo "${orport_config}" | grep -q "ORPort 9001"; then
|
||
print_success "ORPort configured correctly (9001)"
|
||
fi
|
||
|
||
if echo "${orport_config}" | grep -q "DirPort 9030"; then
|
||
print_success "DirPort configured correctly (9030)"
|
||
fi
|
||
else
|
||
print_warning "No ORPort configuration found"
|
||
fi
|
||
}
|
||
|
||
# Show relay type and settings
|
||
show_relay_info() {
|
||
print_section "Relay Information"
|
||
|
||
local relay_info
|
||
relay_info=$(sudo docker exec "${CONTAINER}" grep -iE "^(Nickname|ContactInfo|ExitRelay|BridgeRelay)" "${TORRC_PATH}" 2>/dev/null || true)
|
||
|
||
if [[ -n "${relay_info}" ]]; then
|
||
echo "${relay_info}" | sed 's/^/ /'
|
||
|
||
# Validate relay type
|
||
if echo "${relay_info}" | grep -q "ExitRelay 0"; then
|
||
print_success "Configured as guard/middle relay (not exit)"
|
||
elif echo "${relay_info}" | grep -q "ExitRelay 1"; then
|
||
print_warning "Configured as EXIT relay (higher legal risk)"
|
||
fi
|
||
else
|
||
print_warning "No relay information found in config"
|
||
fi
|
||
}
|
||
|
||
# Check for errors
|
||
check_errors() {
|
||
print_section "Recent Errors/Warnings"
|
||
|
||
local errors
|
||
errors=$(sudo docker logs "${CONTAINER}" 2>&1 | grep -iE "(error|warn|critical)" | tail -5)
|
||
|
||
if [[ -n "${errors}" ]]; then
|
||
echo "${errors}" | sed 's/^/ /'
|
||
else
|
||
print_success "No recent errors or warnings"
|
||
fi
|
||
}
|
||
|
||
# Show resource usage
|
||
show_resources() {
|
||
print_section "Resource Usage"
|
||
|
||
if command -v docker &> /dev/null; then
|
||
local stats
|
||
stats=$(sudo docker stats "${CONTAINER}" --no-stream --format "table {{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}" 2>/dev/null || true)
|
||
|
||
if [[ -n "${stats}" ]]; then
|
||
echo "${stats}" | sed 's/^/ /'
|
||
fi
|
||
fi
|
||
}
|
||
|
||
# Show network diagnostics
|
||
show_network_diagnostics() {
|
||
print_section "Network Diagnostics"
|
||
|
||
print_info "Basic network connectivity check..."
|
||
|
||
# IPv4 check
|
||
if sudo docker exec "${CONTAINER}" curl -4 -s --max-time 5 https://icanhazip.com &>/dev/null; then
|
||
print_success "IPv4 connectivity OK"
|
||
else
|
||
print_warning "IPv4 connectivity issues"
|
||
fi
|
||
|
||
# IPv6 check
|
||
if sudo docker exec "${CONTAINER}" curl -6 -s --max-time 5 https://icanhazip.com &>/dev/null; then
|
||
print_success "IPv6 connectivity OK"
|
||
else
|
||
print_info "IPv6 not available or configured"
|
||
fi
|
||
}
|
||
|
||
# Show quick help
|
||
show_help() {
|
||
cat << EOF
|
||
${BLUE}Tor Relay Status Checker v${VERSION}${NC}
|
||
|
||
Usage: $0 [container-name]
|
||
|
||
If no container name is provided, the script will attempt to auto-detect
|
||
Tor containers by searching for containers with names containing:
|
||
tor, relay, bridge, or onion
|
||
|
||
Examples:
|
||
$0 # Auto-detect Tor container
|
||
$0 guard-relay # Check specific container
|
||
$0 my-tor-bridge # Check bridge container
|
||
|
||
Options:
|
||
-h, --help Show this help message
|
||
-v, --version Show version information
|
||
|
||
Security Checks:
|
||
- Port exposure validation (9001/9030 only)
|
||
- Internal service binding verification (127.0.0.1)
|
||
- Bootstrap and reachability status
|
||
- Error detection and reporting
|
||
|
||
EOF
|
||
}
|
||
|
||
# Show version
|
||
show_version() {
|
||
echo "relay-status.sh version ${VERSION}"
|
||
echo "Part of Tor Guard Relay v1.1.7"
|
||
}
|
||
|
||
# Main execution
|
||
main() {
|
||
# Check for help flag
|
||
if [[ "${1:-}" =~ ^(-h|--help)$ ]]; then
|
||
show_help
|
||
exit 0
|
||
fi
|
||
|
||
# Check for version flag
|
||
if [[ "${1:-}" =~ ^(-v|--version)$ ]]; then
|
||
show_version
|
||
exit 0
|
||
fi
|
||
|
||
# Detect container if not specified
|
||
if [[ -z "${CONTAINER}" ]]; then
|
||
detect_tor_container
|
||
fi
|
||
|
||
print_header "🧅 Tor Relay Status Check: ${CONTAINER} (v${VERSION})"
|
||
|
||
check_container
|
||
check_port_security
|
||
show_relay_info
|
||
show_logs
|
||
show_bootstrap
|
||
check_reachability
|
||
show_fingerprint
|
||
show_orport
|
||
check_errors
|
||
show_resources
|
||
show_network_diagnostics
|
||
|
||
echo
|
||
print_header "✅ Status Check Complete"
|
||
echo
|
||
print_info "For live monitoring, use: sudo docker logs -f ${CONTAINER}"
|
||
print_info "For detailed diagnostics, use: sudo docker exec ${CONTAINER} status"
|
||
echo
|
||
}
|
||
|
||
# Run main function
|
||
main "$@" |