feat: v1.1 repository restructuring

Reorganize into professional directory structure:
- Add 6 comprehensive documentation files
- Update Dependabot configuration
- Update README for better navigation
- Create docs/, templates/, tools/, .github/ structure

See RESTRUCTURING-SUMMARY.md for complete details.

BREAKING CHANGES: None - fully backward compatible
This commit is contained in:
rE-Bo0t.bx1
2025-11-04 23:23:43 +08:00
parent bc5644ccc4
commit 95165aae95
36 changed files with 9842 additions and 918 deletions

534
tools/dashboard Normal file
View File

@@ -0,0 +1,534 @@
#!/bin/sh
# dashboard - Web-based relay monitoring dashboard
# Usage: dashboard [--port PORT] [--help]
set -e
# Configuration
VERSION="1.1.0"
DASHBOARD_PORT="${DASHBOARD_PORT:-8080}"
DASHBOARD_BIND="${DASHBOARD_BIND:-0.0.0.0}"
ENABLE_DASHBOARD="${ENABLE_DASHBOARD:-true}"
REFRESH_INTERVAL="${REFRESH_INTERVAL:-10}"
MULTI_RELAY="${MULTI_RELAY:-false}"
# Parse arguments
for arg in "$@"; do
case "$arg" in
--help|-h)
cat << EOF
🎨 Tor-Guard-Relay Web Dashboard v${VERSION}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USAGE:
dashboard [OPTIONS]
OPTIONS:
--port PORT Dashboard port (default: 8080)
--bind ADDR Bind address (default: 0.0.0.0)
--refresh SEC Auto-refresh interval (default: 10)
--multi Enable multi-relay support
--help, -h Show this help message
ENVIRONMENT VARIABLES:
DASHBOARD_PORT Port to listen on
DASHBOARD_BIND Address to bind
ENABLE_DASHBOARD Enable dashboard (true/false)
REFRESH_INTERVAL Auto-refresh in seconds
MULTI_RELAY Multi-relay mode (true/false)
FEATURES:
• Real-time relay status monitoring
• Bootstrap progress visualization
• Network diagnostics display
• Performance metrics graphs
• Error/warning alerts
• Multi-relay management (optional)
• Mobile-responsive design
ENDPOINTS:
http://localhost:8080/ Main dashboard
http://localhost:8080/api/status JSON API
http://localhost:8080/api/metrics Metrics API
http://localhost:8080/api/logs Recent logs
DOCKER INTEGRATION:
ports:
- "8080:8080"
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
EOF
exit 0
;;
--port)
shift
DASHBOARD_PORT="$1"
shift
;;
--bind)
shift
DASHBOARD_BIND="$1"
shift
;;
--refresh)
shift
REFRESH_INTERVAL="$1"
shift
;;
--multi)
MULTI_RELAY="true"
;;
-*)
echo "❌ Unknown option: $arg"
echo "💡 Use --help for usage information"
exit 2
;;
esac
done
# Check if dashboard is enabled
if [ "$ENABLE_DASHBOARD" != "true" ]; then
echo "🎨 Dashboard is disabled"
echo "💡 Set ENABLE_DASHBOARD=true to enable"
exit 0
fi
# Check for netcat
if ! command -v nc > /dev/null 2>&1; then
echo "❌ Error: netcat (nc) is required"
echo "💡 Install with: apk add netcat-openbsd"
exit 1
fi
# Function to generate dashboard HTML
generate_dashboard() {
# Get current status
STATUS_JSON=$(/usr/local/bin/status --json 2>/dev/null || echo '{}')
HEALTH_JSON=$(/usr/local/bin/health --json 2>/dev/null || echo '{}')
cat << 'EOF'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tor Guard Relay Dashboard</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
background: rgba(255, 255, 255, 0.95);
border-radius: 15px;
padding: 25px;
margin-bottom: 25px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
}
.header h1 {
color: #764ba2;
font-size: 28px;
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 10px;
}
.status-badge {
display: inline-block;
padding: 5px 15px;
border-radius: 20px;
font-size: 14px;
font-weight: 600;
text-transform: uppercase;
}
.status-healthy { background: #10b981; color: white; }
.status-running { background: #f59e0b; color: white; }
.status-starting { background: #3b82f6; color: white; }
.status-down { background: #ef4444; color: white; }
.status-unknown { background: #6b7280; color: white; }
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.card {
background: rgba(255, 255, 255, 0.95);
border-radius: 12px;
padding: 20px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
.card h2 {
color: #4b5563;
font-size: 16px;
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 8px;
}
.metric {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0;
border-bottom: 1px solid #e5e7eb;
}
.metric:last-child {
border-bottom: none;
}
.metric-label {
color: #6b7280;
font-size: 14px;
}
.metric-value {
font-weight: 600;
color: #1f2937;
font-size: 16px;
}
.progress-bar {
width: 100%;
height: 30px;
background: #e5e7eb;
border-radius: 15px;
overflow: hidden;
margin: 10px 0;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #667eea, #764ba2);
transition: width 0.5s ease;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: 600;
font-size: 14px;
}
.fingerprint {
font-family: 'Courier New', monospace;
background: #f3f4f6;
padding: 10px;
border-radius: 8px;
word-break: break-all;
font-size: 13px;
cursor: pointer;
transition: background 0.2s;
}
.fingerprint:hover {
background: #e5e7eb;
}
.alert {
padding: 15px;
border-radius: 8px;
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 10px;
}
.alert-error {
background: #fee2e2;
color: #991b1b;
}
.alert-warning {
background: #fef3c7;
color: #92400e;
}
.alert-success {
background: #d1fae5;
color: #065f46;
}
.footer {
text-align: center;
color: white;
margin-top: 40px;
opacity: 0.9;
}
.footer a {
color: white;
text-decoration: none;
border-bottom: 1px solid rgba(255, 255, 255, 0.5);
}
@media (max-width: 640px) {
.grid {
grid-template-columns: 1fr;
}
.header h1 {
font-size: 22px;
}
}
.refresh-timer {
position: fixed;
bottom: 20px;
right: 20px;
background: rgba(255, 255, 255, 0.95);
padding: 10px 20px;
border-radius: 20px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
font-size: 14px;
color: #4b5563;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>
🧅 Tor Guard Relay Dashboard
<span class="status-badge status-healthy" id="overall-status">Healthy</span>
</h1>
<p style="color: #6b7280; margin-top: 5px;">Real-time monitoring and management</p>
</div>
<div class="grid">
<div class="card">
<h2>🚀 Bootstrap Progress</h2>
<div class="progress-bar">
<div class="progress-fill" id="bootstrap-progress" style="width: 0%;">
0%
</div>
</div>
<p id="bootstrap-message" style="color: #6b7280; font-size: 14px; margin-top: 10px;">
Initializing...
</p>
</div>
<div class="card">
<h2>🌍 Network Status</h2>
<div class="metric">
<span class="metric-label">Reachability</span>
<span class="metric-value" id="reachability">Checking...</span>
</div>
<div class="metric">
<span class="metric-label">Public IP</span>
<span class="metric-value" id="public-ip">Loading...</span>
</div>
<div class="metric">
<span class="metric-label">ORPort</span>
<span class="metric-value" id="orport">-</span>
</div>
</div>
<div class="card">
<h2>📊 Performance</h2>
<div class="metric">
<span class="metric-label">Uptime</span>
<span class="metric-value" id="uptime">0h 0m</span>
</div>
<div class="metric">
<span class="metric-label">Bandwidth</span>
<span class="metric-value" id="bandwidth">- KB/s</span>
</div>
<div class="metric">
<span class="metric-label">Connections</span>
<span class="metric-value" id="connections">0</span>
</div>
</div>
<div class="card">
<h2>🔑 Relay Identity</h2>
<div class="metric">
<span class="metric-label">Nickname</span>
<span class="metric-value" id="nickname">-</span>
</div>
<div class="metric" style="flex-direction: column; align-items: flex-start;">
<span class="metric-label" style="margin-bottom: 10px;">Fingerprint</span>
<div class="fingerprint" id="fingerprint" onclick="copyFingerprint()">
Click to copy
</div>
</div>
</div>
<div class="card">
<h2>⚠️ Health Monitor</h2>
<div id="health-alerts">
<div class="alert alert-success">
✅ All systems operational
</div>
</div>
<div class="metric">
<span class="metric-label">Errors</span>
<span class="metric-value" id="error-count">0</span>
</div>
<div class="metric">
<span class="metric-label">Warnings</span>
<span class="metric-value" id="warning-count">0</span>
</div>
</div>
<div class="card">
<h2>🔗 Quick Actions</h2>
<div style="display: flex; flex-direction: column; gap: 10px;">
<button onclick="window.open('/api/status', '_blank')" style="padding: 10px; border: none; background: #667eea; color: white; border-radius: 8px; cursor: pointer;">
📄 View JSON Status
</button>
<button onclick="window.open('/api/metrics', '_blank')" style="padding: 10px; border: none; background: #764ba2; color: white; border-radius: 8px; cursor: pointer;">
📊 View Metrics
</button>
<button onclick="refreshData()" style="padding: 10px; border: none; background: #10b981; color: white; border-radius: 8px; cursor: pointer;">
🔄 Refresh Now
</button>
</div>
</div>
</div>
<div class="footer">
<p>
Tor-Guard-Relay v${VERSION} |
<a href="https://metrics.torproject.org" target="_blank">Tor Metrics</a> |
<a href="https://github.com/torproject/tor" target="_blank">GitHub</a>
</p>
</div>
</div>
<div class="refresh-timer">
🔄 Auto-refresh: <span id="countdown">${REFRESH_INTERVAL}</span>s
</div>
<script>
let refreshInterval = ${REFRESH_INTERVAL};
let countdown = refreshInterval;
function updateStatus(data) {
// Update overall status
const statusEl = document.getElementById('overall-status');
statusEl.className = 'status-badge status-' + (data.status || 'unknown');
statusEl.textContent = (data.status || 'Unknown').toUpperCase();
// Update bootstrap progress
const bootstrap = data.bootstrap || {};
const progressEl = document.getElementById('bootstrap-progress');
const percent = bootstrap.percent || 0;
progressEl.style.width = percent + '%';
progressEl.textContent = percent + '%';
document.getElementById('bootstrap-message').textContent =
bootstrap.message || 'Waiting for bootstrap...';
// Update network status
const reachable = data.reachability?.reachable;
document.getElementById('reachability').textContent =
reachable ? '✅ Reachable' : '⏳ Testing...';
document.getElementById('public-ip').textContent =
data.network?.public_ip || 'Unknown';
document.getElementById('orport').textContent =
data.configuration?.orport || '-';
// Update performance
document.getElementById('uptime').textContent =
data.process?.uptime || '0h 0m';
document.getElementById('bandwidth').textContent =
data.configuration?.bandwidth || '- KB/s';
// Update identity
document.getElementById('nickname').textContent =
data.identity?.nickname || '-';
document.getElementById('fingerprint').textContent =
data.identity?.fingerprint || 'Not available';
// Update health
const errors = data.issues?.errors || 0;
const warnings = data.issues?.warnings || 0;
document.getElementById('error-count').textContent = errors;
document.getElementById('warning-count').textContent = warnings;
// Update health alerts
const alertsEl = document.getElementById('health-alerts');
if (errors > 0) {
alertsEl.innerHTML = '<div class="alert alert-error">❌ ' + errors + ' errors detected</div>';
} else if (warnings > 0) {
alertsEl.innerHTML = '<div class="alert alert-warning">⚠️ ' + warnings + ' warnings detected</div>';
} else {
alertsEl.innerHTML = '<div class="alert alert-success">✅ All systems operational</div>';
}
}
function refreshData() {
fetch('/api/status')
.then(response => response.json())
.then(data => updateStatus(data))
.catch(error => console.error('Error fetching status:', error));
}
function copyFingerprint() {
const fp = document.getElementById('fingerprint').textContent;
if (fp && fp !== 'Not available' && fp !== 'Click to copy') {
navigator.clipboard.writeText(fp).then(() => {
const el = document.getElementById('fingerprint');
const original = el.textContent;
el.textContent = '✅ Copied!';
setTimeout(() => el.textContent = original, 2000);
});
}
}
// Countdown timer
setInterval(() => {
countdown--;
if (countdown <= 0) {
countdown = refreshInterval;
refreshData();
}
document.getElementById('countdown').textContent = countdown;
}, 1000);
// Initial load
refreshData();
</script>
</body>
</html>
EOF
}
# Function to handle API requests
handle_api() {
REQUEST_PATH="$1"
case "$REQUEST_PATH" in
"/api/status")
CONTENT=$(/usr/local/bin/status --json 2>/dev/null || echo '{"error":"Failed to get status"}')
echo "HTTP/1.1 200 OK"
echo "Content-Type: application/json"
echo "Cache-Control: no-cache"
echo "Connection: close"
echo ""
echo "$CONTENT"
;;
"/api/metrics")
CONTENT=$(/usr/local/bin/metrics 2>/dev/null || echo "# Error generating metrics

View File

@@ -1,21 +1,188 @@
#!/bin/sh
# fingerprint - Display relay fingerprint
# Usage: docker exec guard-relay fingerprint
# fingerprint - Display and manage Tor relay fingerprint
# Usage: docker exec guard-relay fingerprint [--json|--help]
echo "🔑 Tor Relay Fingerprint"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
set -e
if [ -f /var/lib/tor/fingerprint ]; then
cat /var/lib/tor/fingerprint
echo ""
echo "🌐 Search on Tor Metrics:"
echo " https://metrics.torproject.org/rs.html"
echo ""
echo "🧅 Search on Onion Metrics (Tor Browser):"
echo " http://hctxrvjzfpvmzh2jllqhgvvkoepxb4kfzdjm6h7egcwlumggtktiftid.onion/rs.html"
else
echo "⚠️ Fingerprint not yet generated."
echo "📍 Tor is still bootstrapping or generating keys."
echo "💡 Check back in a few minutes."
# Configuration
VERSION="1.1.0"
OUTPUT_FORMAT="${OUTPUT_FORMAT:-text}"
FINGERPRINT_FILE="${FINGERPRINT_FILE:-/var/lib/tor/fingerprint}"
SHOW_LINKS="${SHOW_LINKS:-true}"
# Parse arguments
for arg in "$@"; do
case "$arg" in
--help|-h)
cat << EOF
🔑 Tor-Guard-Relay Fingerprint Tool v${VERSION}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USAGE:
fingerprint [OPTIONS]
OPTIONS:
--json Output in JSON format
--plain Plain text output
--copy Output for easy copying
--links Show monitoring links (default)
--no-links Hide monitoring links
--help, -h Show this help message
ENVIRONMENT VARIABLES:
OUTPUT_FORMAT Output format (text/json/plain)
FINGERPRINT_FILE Path to fingerprint file
SHOW_LINKS Show monitoring links (true/false)
OUTPUT FORMATS:
text Human-readable with emojis and links
json Machine-readable JSON
plain Simple text for scripts
copy Formatted for clipboard copying
MONITORING LINKS:
• Tor Metrics (clearnet)
• Onion Metrics (Tor Browser only)
EXAMPLES:
fingerprint # Display with links
fingerprint --json # JSON output
fingerprint --copy # Copy-friendly format
fingerprint --plain # Script-friendly output
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
EOF
exit 0
;;
--json) OUTPUT_FORMAT="json" ;;
--plain) OUTPUT_FORMAT="plain" ;;
--copy) OUTPUT_FORMAT="copy" ;;
--links) SHOW_LINKS="true" ;;
--no-links) SHOW_LINKS="false" ;;
-*)
echo "❌ Unknown option: $arg"
echo "💡 Use --help for usage information"
exit 2
;;
esac
done
# Check if fingerprint exists
if [ ! -f "$FINGERPRINT_FILE" ]; then
case "$OUTPUT_FORMAT" in
json)
cat << EOF
{
"status": "not_ready",
"message": "Fingerprint not yet generated",
"fingerprint": null,
"nickname": null
}
EOF
;;
plain)
echo "NOT_READY"
;;
*)
echo "⚠️ Fingerprint not yet generated."
echo "📍 Tor is still bootstrapping or generating keys."
echo "💡 Check back in a few minutes."
;;
esac
exit 1
fi
fi
# Read fingerprint
NICKNAME=$(awk '{print $1}' "$FINGERPRINT_FILE" 2>/dev/null || echo "")
FINGERPRINT=$(awk '{print $2}' "$FINGERPRINT_FILE" 2>/dev/null || echo "")
# Validate fingerprint format (40 hex characters)
if ! echo "$FINGERPRINT" | grep -qE "^[A-F0-9]{40}$"; then
case "$OUTPUT_FORMAT" in
json)
echo '{"status":"invalid","message":"Invalid fingerprint format"}'
;;
plain)
echo "INVALID"
;;
*)
echo "❌ Invalid fingerprint format detected"
;;
esac
exit 1
fi
# Generate formatted versions
FINGERPRINT_SPACED=$(echo "$FINGERPRINT" | sed 's/\(..\)/\1 /g' | sed 's/ $//')
FINGERPRINT_COLON=$(echo "$FINGERPRINT" | sed 's/\(..\)/\1:/g' | sed 's/:$//')
# Get additional info if available
CREATION_TIME=""
if [ -f "$FINGERPRINT_FILE" ]; then
CREATION_TIME=$(stat -c %y "$FINGERPRINT_FILE" 2>/dev/null | cut -d' ' -f1,2 | cut -d'.' -f1 || echo "")
fi
# Output based on format
case "$OUTPUT_FORMAT" in
json)
cat << EOF
{
"status": "ready",
"nickname": "$NICKNAME",
"fingerprint": "$FINGERPRINT",
"fingerprint_spaced": "$FINGERPRINT_SPACED",
"fingerprint_colon": "$FINGERPRINT_COLON",
"created": "$CREATION_TIME",
"links": {
"metrics": "https://metrics.torproject.org/rs.html#search/$FINGERPRINT",
"onion_metrics": "http://hctxrvjzfpvmzh2jllqhgvvkoepxb4kfzdjm6h7egcwlumggtktiftid.onion/rs.html#search/$FINGERPRINT"
}
}
EOF
;;
plain)
echo "$NICKNAME $FINGERPRINT"
;;
copy)
echo "$FINGERPRINT"
echo ""
echo "# Formatted versions:"
echo "# Spaced: $FINGERPRINT_SPACED"
echo "# Colon: $FINGERPRINT_COLON"
;;
*)
# Default text format with emojis
echo "🔑 Tor Relay Fingerprint"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "📝 Nickname: $NICKNAME"
echo "🆔 Fingerprint: $FINGERPRINT"
echo ""
echo "📋 Formatted versions:"
echo " Spaced: $FINGERPRINT_SPACED"
echo " Colon: $FINGERPRINT_COLON"
if [ -n "$CREATION_TIME" ]; then
echo ""
echo "🕒 Created: $CREATION_TIME"
fi
if [ "$SHOW_LINKS" = "true" ]; then
echo ""
echo "🌐 Monitor your relay:"
echo ""
echo " 📊 Tor Metrics:"
echo " https://metrics.torproject.org/rs.html#search/$FINGERPRINT"
echo ""
echo " 🧅 Onion Metrics (Tor Browser only):"
echo " http://hctxrvjzfpvmzh2jllqhgvvkoepxb4kfzdjm6h7egcwlumggtktiftid.onion/rs.html#search/$FINGERPRINT"
fi
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "💡 Tip: Use 'fingerprint --copy' for easy copying"
;;
esac

231
tools/health Normal file
View File

@@ -0,0 +1,231 @@
#!/bin/sh
# health - Comprehensive Tor relay health check (hybrid stable version)
# Combines full structured output with simplified inline logic (Alpine-safe)
set -e
VERSION="1.1.0"
OUTPUT_FORMAT="${OUTPUT_FORMAT:-text}"
ENABLE_HEALTH_CHECK="${ENABLE_HEALTH_CHECK:-true}"
HEALTH_WEBHOOK_URL="${HEALTH_WEBHOOK_URL:-}"
CHECK_TIMEOUT="${CHECK_TIMEOUT:-5}"
safe() { "$@" 2>/dev/null || true; }
for arg in "$@"; do
case "$arg" in
--help|-h)
cat << EOF
🧅 Tor-Guard-Relay Health Check v${VERSION}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USAGE:
health [--json|--plain|--text|--webhook]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
EOF
exit 0 ;;
--json) OUTPUT_FORMAT="json" ;;
--plain) OUTPUT_FORMAT="plain" ;;
--text) OUTPUT_FORMAT="text" ;;
--webhook) SEND_WEBHOOK="true" ;;
esac
done
# Skip if disabled
if [ "$ENABLE_HEALTH_CHECK" != "true" ]; then
case "$OUTPUT_FORMAT" in
json) echo '{"status":"disabled"}' ;;
plain) echo "DISABLED" ;;
*) echo "⏸️ Health checking disabled" ;;
esac
exit 0
fi
# Initialize variables
STATUS="unknown"
BOOTSTRAP_PERCENT=0
IS_RUNNING=false
IS_REACHABLE=false
FINGERPRINT=""
NICKNAME=""
UPTIME="0"
ERRORS=0
WARNINGS=0
VERSION_INFO=""
ORPORT=""
DIRPORT=""
EXIT_RELAY="false"
# --- Inline Checks (Simplified Logic that Works) ---
# Tor process
if safe pgrep -x tor >/dev/null; then
IS_RUNNING=true
PID=$(safe pgrep -x tor | head -1)
UPTIME=$(safe ps -p "$PID" -o time= | tr -d ' ')
[ -z "$UPTIME" ] && UPTIME="0"
fi
# Bootstrap progress
if [ -f /var/log/tor/notices.log ]; then
BOOTSTRAP_LINE=$(safe grep "Bootstrapped" /var/log/tor/notices.log | tail -1)
BOOTSTRAP_PERCENT=$(echo "$BOOTSTRAP_LINE" | grep -oE '[0-9]+%' | tr -d '%' | tail -1)
[ -z "$BOOTSTRAP_PERCENT" ] && BOOTSTRAP_PERCENT=0
fi
# Reachability
if [ -f /var/log/tor/notices.log ]; then
if safe grep -q "reachable from the outside" /var/log/tor/notices.log; then
IS_REACHABLE=true
fi
fi
# Relay info
if [ -f /var/lib/tor/fingerprint ]; then
NICKNAME=$(safe awk '{print $1}' /var/lib/tor/fingerprint)
FINGERPRINT=$(safe awk '{print $2}' /var/lib/tor/fingerprint)
fi
if [ -f /etc/tor/torrc ]; then
ORPORT=$(safe grep -E "^ORPort" /etc/tor/torrc | awk '{print $2}' | head -1)
DIRPORT=$(safe grep -E "^DirPort" /etc/tor/torrc | awk '{print $2}' | head -1)
if safe grep -qE "^ExitRelay\s+1" /etc/tor/torrc; then
EXIT_RELAY="true"
fi
fi
if [ -f /build-info.txt ]; then
VERSION_INFO=$(safe head -1 /build-info.txt | cut -d: -f2- | tr -d ' ')
fi
# Count issues
if [ -f /var/log/tor/notices.log ]; then
ERRORS=$(safe grep -ciE "\[err\]" /var/log/tor/notices.log)
WARNINGS=$(safe grep -ciE "\[warn\]" /var/log/tor/notices.log)
fi
# Determine overall status
if [ "$IS_RUNNING" = false ]; then
STATUS="down"
elif [ "$BOOTSTRAP_PERCENT" -eq 100 ] && [ "$IS_REACHABLE" = true ]; then
STATUS="healthy"
elif [ "$BOOTSTRAP_PERCENT" -eq 100 ]; then
STATUS="running"
elif [ "$BOOTSTRAP_PERCENT" -gt 0 ]; then
STATUS="starting"
else
STATUS="unknown"
fi
TIMESTAMP=$(date -u '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || date)
# --- Output ---
case "$OUTPUT_FORMAT" in
json)
cat << EOF
{
"status": "$STATUS",
"timestamp": "$TIMESTAMP",
"process": {
"running": $IS_RUNNING,
"uptime": "$UPTIME"
},
"bootstrap": {
"percent": $BOOTSTRAP_PERCENT,
"complete": $([ "$BOOTSTRAP_PERCENT" -eq 100 ] && echo "true" || echo "false")
},
"network": {
"reachable": $IS_REACHABLE,
"orport": "$ORPORT",
"dirport": "$DIRPORT"
},
"relay": {
"nickname": "$NICKNAME",
"fingerprint": "$FINGERPRINT",
"exit_relay": $EXIT_RELAY,
"version": "$VERSION_INFO"
},
"issues": {
"errors": $ERRORS,
"warnings": $WARNINGS
}
}
EOF
;;
plain)
echo "STATUS=$STATUS"
echo "RUNNING=$IS_RUNNING"
echo "BOOTSTRAP=$BOOTSTRAP_PERCENT"
echo "REACHABLE=$IS_REACHABLE"
echo "NICKNAME=$NICKNAME"
echo "FINGERPRINT=$FINGERPRINT"
echo "ERRORS=$ERRORS"
echo "WARNINGS=$WARNINGS"
echo "UPTIME=$UPTIME"
;;
*)
echo "🧅 Tor Relay Health Check"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
case "$STATUS" in
healthy) echo "📊 Status: ✅ HEALTHY" ;;
running) echo "📊 Status: 🟡 RUNNING (awaiting reachability)" ;;
starting) echo "📊 Status: 🔄 STARTING ($BOOTSTRAP_PERCENT% bootstrapped)" ;;
down) echo "📊 Status: ❌ DOWN" ;;
*) echo "📊 Status: ❓ UNKNOWN" ;;
esac
echo ""
echo "⚙️ Process:"
if [ "$IS_RUNNING" = true ]; then
echo " ✅ Tor is running (uptime: $UPTIME)"
else
echo " ❌ Tor process not found"
fi
echo ""
echo "🚀 Bootstrap:"
if [ "$BOOTSTRAP_PERCENT" -eq 100 ]; then
echo " ✅ Fully bootstrapped (100%)"
elif [ "$BOOTSTRAP_PERCENT" -gt 0 ]; then
echo " 🔄 Bootstrapping... ($BOOTSTRAP_PERCENT%)"
else
echo " ⏳ Not started"
fi
echo ""
echo "🌍 Network:"
if [ "$IS_REACHABLE" = true ]; then
echo " ✅ Reachable from the outside"
else
echo " ⏳ Testing reachability..."
fi
[ -n "$ORPORT" ] && echo " 📍 ORPort: $ORPORT"
[ -n "$DIRPORT" ] && echo " 📍 DirPort: $DIRPORT"
echo ""
if [ -n "$NICKNAME" ] || [ -n "$FINGERPRINT" ]; then
echo "🔑 Relay Identity:"
[ -n "$NICKNAME" ] && echo " 📝 Nickname: $NICKNAME"
[ -n "$FINGERPRINT" ] && echo " 🆔 Fingerprint: $FINGERPRINT"
[ "$EXIT_RELAY" = "true" ] && echo " 🚪 Type: Exit Relay" || echo " 🔒 Type: Guard/Middle Relay"
echo ""
fi
if [ "$ERRORS" -gt 0 ] || [ "$WARNINGS" -gt 0 ]; then
echo "⚠️ Issues:"
[ "$ERRORS" -gt 0 ] && echo " ❌ Errors: $ERRORS"
[ "$WARNINGS" -gt 0 ] && echo " ⚠️ Warnings: $WARNINGS"
echo ""
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "🕒 Checked: $TIMESTAMP"
;;
esac
# Optional webhook support
if [ "$SEND_WEBHOOK" = "true" ] && [ -n "$HEALTH_WEBHOOK_URL" ]; then
if command -v curl >/dev/null 2>&1; then
/usr/local/bin/health --json | curl -s -X POST "$HEALTH_WEBHOOK_URL" \
-H "Content-Type: application/json" -d @- >/dev/null 2>&1
fi
fi
case "$STATUS" in
healthy|running|starting) exit 0 ;;
*) exit 1 ;;
esac

213
tools/metrics Normal file
View File

@@ -0,0 +1,213 @@
#!/bin/sh
# metrics - Prometheus-compatible metrics exporter for Tor relay
# Usage: docker exec guard-relay metrics [--help]
set -e
# Configuration
VERSION="1.1.0"
METRICS_PREFIX="${METRICS_PREFIX:-tor_relay}"
INCLUDE_LABELS="${INCLUDE_LABELS:-true}"
METRICS_FORMAT="${METRICS_FORMAT:-prometheus}"
# Parse arguments
for arg in "$@"; do
case "$arg" in
--help|-h)
cat << EOF
📊 Tor-Guard-Relay Metrics Exporter v${VERSION}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USAGE:
metrics [OPTIONS]
OPTIONS:
--prometheus Output in Prometheus format (default)
--json Output metrics as JSON
--help, -h Show this help message
ENVIRONMENT VARIABLES:
METRICS_PREFIX Prefix for metric names (default: tor_relay)
INCLUDE_LABELS Include labels in output (true/false)
METRICS_FORMAT Output format (prometheus/json)
METRICS EXPORTED:
• ${METRICS_PREFIX}_up Relay status (0/1)
• ${METRICS_PREFIX}_bootstrap_percent Bootstrap progress
• ${METRICS_PREFIX}_reachable Reachability status
• ${METRICS_PREFIX}_uptime_seconds Process uptime
• ${METRICS_PREFIX}_errors_total Total error count
• ${METRICS_PREFIX}_warnings_total Total warning count
• ${METRICS_PREFIX}_bandwidth_read_bytes Bytes read
• ${METRICS_PREFIX}_bandwidth_write_bytes Bytes written
• ${METRICS_PREFIX}_circuits_total Active circuits
PROMETHEUS INTEGRATION:
# prometheus.yml
scrape_configs:
- job_name: 'tor-relay'
static_configs:
- targets: ['relay:9052']
EXAMPLES:
metrics # Prometheus format output
metrics --json # JSON metrics
curl localhost:9052/metrics # HTTP endpoint
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
EOF
exit 0
;;
--prometheus) METRICS_FORMAT="prometheus" ;;
--json) METRICS_FORMAT="json" ;;
-*)
echo "# ERROR: Unknown option: $arg"
echo "# Use --help for usage information"
exit 2
;;
esac
done
# Initialize metrics
RELAY_UP=0
BOOTSTRAP_PERCENT=0
IS_REACHABLE=0
UPTIME_SECONDS=0
ERROR_COUNT=0
WARNING_COUNT=0
BANDWIDTH_READ=0
BANDWIDTH_WRITE=0
CIRCUITS_ACTIVE=0
NICKNAME=""
FINGERPRINT=""
VERSION_INFO=""
# Get relay identity
if [ -f /var/lib/tor/fingerprint ]; then
NICKNAME=$(awk '{print $1}' /var/lib/tor/fingerprint 2>/dev/null || echo "unknown")
FINGERPRINT=$(awk '{print $2}' /var/lib/tor/fingerprint 2>/dev/null || echo "")
fi
# Check if Tor is running
if pgrep -x tor > /dev/null 2>&1; then
RELAY_UP=1
# Calculate uptime in seconds
PID=$(pgrep -x tor | head -1)
if [ -n "$PID" ]; then
# Get process start time
if [ -f "/proc/$PID/stat" ]; then
STARTTIME=$(awk '{print $22}' "/proc/$PID/stat" 2>/dev/null || echo 0)
UPTIME_TICKS=$(($(cat /proc/uptime | cut -d. -f1) * 100))
if [ "$STARTTIME" -gt 0 ]; then
UPTIME_SECONDS=$(((UPTIME_TICKS - STARTTIME) / 100))
fi
fi
fi
fi
# Parse bootstrap percentage
if [ -f /var/log/tor/notices.log ]; then
BOOTSTRAP_LINE=$(grep "Bootstrapped" /var/log/tor/notices.log 2>/dev/null | tail -1)
if [ -n "$BOOTSTRAP_LINE" ]; then
BOOTSTRAP_PERCENT=$(echo "$BOOTSTRAP_LINE" | grep -oE '[0-9]+%' | tr -d '%' | tail -1)
[ -z "$BOOTSTRAP_PERCENT" ] && BOOTSTRAP_PERCENT=0
fi
# Check reachability
if grep -q "reachable from the outside" /var/log/tor/notices.log 2>/dev/null; then
IS_REACHABLE=1
fi
# Count errors and warnings
ERROR_COUNT=$(grep -cE "\[err\]|\[error\]" /var/log/tor/notices.log 2>/dev/null || echo 0)
WARNING_COUNT=$(grep -cE "\[warn\]|\[warning\]" /var/log/tor/notices.log 2>/dev/null || echo 0)
fi
# Parse bandwidth from state file
if [ -f /var/lib/tor/state ]; then
BANDWIDTH_READ=$(grep "^AccountingBytesReadInterval" /var/lib/tor/state 2>/dev/null | awk '{print $2}' || echo 0)
BANDWIDTH_WRITE=$(grep "^AccountingBytesWrittenInterval" /var/lib/tor/state 2>/dev/null | awk '{print $2}' || echo 0)
fi
# Get version info
if [ -f /build-info.txt ]; then
VERSION_INFO=$(head -1 /build-info.txt 2>/dev/null | cut -d: -f2- | tr -d ' ' || echo "unknown")
fi
# Generate timestamp
TIMESTAMP=$(date +%s)000
# Output based on format
case "$METRICS_FORMAT" in
json)
cat << EOF
{
"timestamp": $TIMESTAMP,
"metrics": {
"up": $RELAY_UP,
"bootstrap_percent": $BOOTSTRAP_PERCENT,
"reachable": $IS_REACHABLE,
"uptime_seconds": $UPTIME_SECONDS,
"errors_total": $ERROR_COUNT,
"warnings_total": $WARNING_COUNT,
"bandwidth_read_bytes": $BANDWIDTH_READ,
"bandwidth_write_bytes": $BANDWIDTH_WRITE,
"circuits_total": $CIRCUITS_ACTIVE
},
"labels": {
"nickname": "$NICKNAME",
"fingerprint": "$FINGERPRINT",
"version": "$VERSION_INFO"
}
}
EOF
;;
*)
# Prometheus format (default)
echo "# HELP ${METRICS_PREFIX}_up Tor relay status (1 = up, 0 = down)"
echo "# TYPE ${METRICS_PREFIX}_up gauge"
if [ "$INCLUDE_LABELS" = "true" ] && [ -n "$NICKNAME" ]; then
echo "${METRICS_PREFIX}_up{nickname=\"$NICKNAME\",fingerprint=\"$FINGERPRINT\"} $RELAY_UP"
else
echo "${METRICS_PREFIX}_up $RELAY_UP"
fi
echo "# HELP ${METRICS_PREFIX}_bootstrap_percent Bootstrap completion percentage"
echo "# TYPE ${METRICS_PREFIX}_bootstrap_percent gauge"
echo "${METRICS_PREFIX}_bootstrap_percent $BOOTSTRAP_PERCENT"
echo "# HELP ${METRICS_PREFIX}_reachable Relay reachability status"
echo "# TYPE ${METRICS_PREFIX}_reachable gauge"
echo "${METRICS_PREFIX}_reachable $IS_REACHABLE"
echo "# HELP ${METRICS_PREFIX}_uptime_seconds Relay process uptime in seconds"
echo "# TYPE ${METRICS_PREFIX}_uptime_seconds counter"
echo "${METRICS_PREFIX}_uptime_seconds $UPTIME_SECONDS"
echo "# HELP ${METRICS_PREFIX}_errors_total Total number of errors in log"
echo "# TYPE ${METRICS_PREFIX}_errors_total counter"
echo "${METRICS_PREFIX}_errors_total $ERROR_COUNT"
echo "# HELP ${METRICS_PREFIX}_warnings_total Total number of warnings in log"
echo "# TYPE ${METRICS_PREFIX}_warnings_total counter"
echo "${METRICS_PREFIX}_warnings_total $WARNING_COUNT"
echo "# HELP ${METRICS_PREFIX}_bandwidth_read_bytes Total bytes read"
echo "# TYPE ${METRICS_PREFIX}_bandwidth_read_bytes counter"
echo "${METRICS_PREFIX}_bandwidth_read_bytes $BANDWIDTH_READ"
echo "# HELP ${METRICS_PREFIX}_bandwidth_write_bytes Total bytes written"
echo "# TYPE ${METRICS_PREFIX}_bandwidth_write_bytes counter"
echo "${METRICS_PREFIX}_bandwidth_write_bytes $BANDWIDTH_WRITE"
echo "# HELP ${METRICS_PREFIX}_circuits_total Active circuit count"
echo "# TYPE ${METRICS_PREFIX}_circuits_total gauge"
echo "${METRICS_PREFIX}_circuits_total $CIRCUITS_ACTIVE"
echo "# HELP ${METRICS_PREFIX}_info Relay information"
echo "# TYPE ${METRICS_PREFIX}_info gauge"
echo "${METRICS_PREFIX}_info{nickname=\"$NICKNAME\",version=\"$VERSION_INFO\"} 1"
;;
esac

251
tools/metrics-http Normal file
View File

@@ -0,0 +1,251 @@
#!/bin/sh
# metrics-http - HTTP server for Prometheus metrics endpoint
# Usage: metrics-http [--port PORT] [--help]
set -e
# Configuration
VERSION="1.1.0"
METRICS_PORT="${METRICS_PORT:-9052}"
METRICS_BIND="${METRICS_BIND:-0.0.0.0}"
METRICS_PATH="${METRICS_PATH:-/metrics}"
ENABLE_METRICS="${ENABLE_METRICS:-true}"
RESPONSE_TIMEOUT="${RESPONSE_TIMEOUT:-10}"
# Parse arguments
for arg in "$@"; do
case "$arg" in
--help|-h)
cat << EOF
🌐 Tor-Guard-Relay Metrics HTTP Server v${VERSION}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USAGE:
metrics-http [OPTIONS]
OPTIONS:
--port PORT Listen port (default: 9052)
--bind ADDR Bind address (default: 0.0.0.0)
--path PATH Metrics path (default: /metrics)
--daemon Run as daemon
--help, -h Show this help message
ENVIRONMENT VARIABLES:
METRICS_PORT Port to listen on (default: 9052)
METRICS_BIND Address to bind (default: 0.0.0.0)
METRICS_PATH URL path for metrics (default: /metrics)
ENABLE_METRICS Enable metrics server (true/false)
RESPONSE_TIMEOUT Response timeout in seconds
ENDPOINTS:
http://localhost:9052/metrics Prometheus metrics
http://localhost:9052/health Health check endpoint
http://localhost:9052/ Status page
PROMETHEUS CONFIG:
scrape_configs:
- job_name: 'tor-relay'
static_configs:
- targets: ['relay:9052']
metrics_path: '/metrics'
scrape_interval: 30s
DOCKER INTEGRATION:
# Expose in docker-compose.yml
ports:
- "9052:9052"
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
EOF
exit 0
;;
--port)
shift
METRICS_PORT="$1"
shift
;;
--bind)
shift
METRICS_BIND="$1"
shift
;;
--path)
shift
METRICS_PATH="$1"
shift
;;
--daemon)
DAEMON_MODE="true"
;;
-*)
echo "❌ Unknown option: $arg"
echo "💡 Use --help for usage information"
exit 2
;;
esac
done
# Check if metrics are enabled
if [ "$ENABLE_METRICS" != "true" ]; then
echo "📊 Metrics HTTP server is disabled"
echo "💡 Set ENABLE_METRICS=true to enable"
exit 0
fi
# Check for netcat
if ! command -v nc > /dev/null 2>&1; then
echo "❌ Error: netcat (nc) is required but not installed"
echo "💡 Install with: apk add netcat-openbsd"
exit 1
fi
# Function to generate HTTP response
generate_response() {
REQUEST_PATH="$1"
case "$REQUEST_PATH" in
"$METRICS_PATH")
# Generate metrics
METRICS_OUTPUT=$(/usr/local/bin/metrics 2>/dev/null || echo "# Error generating metrics")
CONTENT_LENGTH=$(echo -n "$METRICS_OUTPUT" | wc -c)
cat << EOF
HTTP/1.1 200 OK
Content-Type: text/plain; version=0.0.4
Content-Length: $CONTENT_LENGTH
Cache-Control: no-cache
Connection: close
$METRICS_OUTPUT
EOF
;;
"/health")
# Health check endpoint
HEALTH_JSON=$(/usr/local/bin/health --json 2>/dev/null || echo '{"status":"error"}')
CONTENT_LENGTH=$(echo -n "$HEALTH_JSON" | wc -c)
cat << EOF
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: $CONTENT_LENGTH
Cache-Control: no-cache
Connection: close
$HEALTH_JSON
EOF
;;
"/")
# Status page
HTML_CONTENT="<!DOCTYPE html>
<html>
<head>
<title>Tor Relay Metrics</title>
<style>
body { font-family: sans-serif; margin: 40px; background: #f5f5f5; }
h1 { color: #7d4698; }
.status { padding: 20px; background: white; border-radius: 8px; margin: 20px 0; }
.endpoint { background: #f0f0f0; padding: 10px; margin: 10px 0; border-radius: 4px; }
a { color: #7d4698; text-decoration: none; }
a:hover { text-decoration: underline; }
.ok { color: green; }
.loading { color: orange; }
</style>
</head>
<body>
<h1>🧅 Tor Relay Metrics Server</h1>
<div class=\"status\">
<h2>Available Endpoints:</h2>
<div class=\"endpoint\">
📊 <a href=\"$METRICS_PATH\">$METRICS_PATH</a> - Prometheus metrics
</div>
<div class=\"endpoint\">
💚 <a href=\"/health\">/health</a> - Health check (JSON)
</div>
<div class=\"endpoint\">
🏠 <a href=\"/\">/</a> - This status page
</div>
</div>
<div class=\"status\">
<h2>Configuration:</h2>
<p>Port: $METRICS_PORT</p>
<p>Bind: $METRICS_BIND</p>
<p>Version: $VERSION</p>
</div>
</body>
</html>"
CONTENT_LENGTH=$(echo -n "$HTML_CONTENT" | wc -c)
cat << EOF
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: $CONTENT_LENGTH
Cache-Control: no-cache
Connection: close
$HTML_CONTENT
EOF
;;
*)
# 404 Not Found
ERROR_MSG="404 - Not Found"
CONTENT_LENGTH=$(echo -n "$ERROR_MSG" | wc -c)
cat << EOF
HTTP/1.1 404 Not Found
Content-Type: text/plain
Content-Length: $CONTENT_LENGTH
Connection: close
$ERROR_MSG
EOF
;;
esac
}
# Signal handler for clean shutdown
cleanup() {
echo ""
echo "🛑 Shutting down metrics HTTP server..."
exit 0
}
trap cleanup INT TERM
# Start server
echo "📊 Starting Tor Relay Metrics HTTP Server v${VERSION}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "🌐 Listening on: http://$METRICS_BIND:$METRICS_PORT"
echo "📍 Metrics path: $METRICS_PATH"
echo "💡 Press Ctrl+C to stop"
echo ""
# Main server loop
if [ "$DAEMON_MODE" = "true" ]; then
# Run in background
while true; do
echo -e "$(generate_response "$METRICS_PATH")" | nc -l -p "$METRICS_PORT" -w "$RESPONSE_TIMEOUT" > /dev/null 2>&1 &
wait
done &
echo "✅ Server running in daemon mode (PID: $!)"
else
# Run in foreground
while true; do
# Wait for connection and parse request
REQUEST=$(echo -e "HTTP/1.1 200 OK\r\n\r\n" | nc -l -p "$METRICS_PORT" -w "$RESPONSE_TIMEOUT" 2>/dev/null | head -1)
if [ -n "$REQUEST" ]; then
# Extract path from request
REQUEST_PATH=$(echo "$REQUEST" | awk '{print $2}')
# Log request
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $REQUEST"
# Generate and send response
generate_response "$REQUEST_PATH" | nc -l -p "$METRICS_PORT" -w 1 > /dev/null 2>&1 &
fi
done
fi

216
tools/net-check Normal file
View File

@@ -0,0 +1,216 @@
#!/bin/sh
# net-check - Comprehensive network diagnostics for Tor relay (curl-only edition)
# Usage: docker exec guard-relay net-check [--json|--plain|--quick|--full|--help]
set -e
VERSION="1.1.0"
OUTPUT_FORMAT="text"
CHECK_IPV4="true"
CHECK_IPV6="true"
CHECK_DNS="true"
CHECK_CONSENSUS="true"
CHECK_PORTS="true"
DNS_SERVERS="1.1.1.1 8.8.8.8 9.9.9.9"
TEST_TIMEOUT="5"
# Parse arguments
for arg in "$@"; do
case "$arg" in
--help|-h)
cat << EOF
🌐 Tor-Guard-Relay Network Diagnostics v${VERSION}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USAGE:
net-check [--json|--plain|--quick|--full|--help]
OPTIONS:
--json Output JSON format
--plain Minimal output for scripts
--text Formatted output (default)
--quick Skip extended tests
--full Run all tests (default)
--help Show this help message
EOF
exit 0
;;
--json) OUTPUT_FORMAT="json" ;;
--plain) OUTPUT_FORMAT="plain" ;;
--text) OUTPUT_FORMAT="text" ;;
--quick)
CHECK_CONSENSUS="false"
CHECK_PORTS="false"
;;
--full)
CHECK_IPV4="true"
CHECK_IPV6="true"
CHECK_DNS="true"
CHECK_CONSENSUS="true"
CHECK_PORTS="true"
;;
esac
done
# Defaults
IPV4_STATUS="unknown"
IPV6_STATUS="unknown"
DNS_STATUS="unknown"
CONSENSUS_STATUS="unknown"
PORT_STATUS="unknown"
PUBLIC_IP=""
PUBLIC_IP6=""
FAILED_TESTS=0
TOTAL_TESTS=0
command_exists() { command -v "$1" >/dev/null 2>&1; }
# IPv4 check
check_ipv4() {
TOTAL_TESTS=$((TOTAL_TESTS + 1))
if [ "$CHECK_IPV4" = "true" ] && command_exists curl; then
PUBLIC_IP=$(curl -4 -fsS --max-time "$TEST_TIMEOUT" https://ipv4.icanhazip.com 2>/dev/null | tr -d '\r')
if [ -n "$PUBLIC_IP" ]; then
IPV4_STATUS="ok"
else
IPV4_STATUS="failed"; FAILED_TESTS=$((FAILED_TESTS + 1))
fi
else
IPV4_STATUS="skipped"
fi
}
# IPv6 check
check_ipv6() {
TOTAL_TESTS=$((TOTAL_TESTS + 1))
if [ "$CHECK_IPV6" = "true" ] && command_exists curl; then
PUBLIC_IP6=$(curl -6 -fsS --max-time "$TEST_TIMEOUT" https://ipv6.icanhazip.com 2>/dev/null | tr -d '\r')
if [ -n "$PUBLIC_IP6" ]; then
IPV6_STATUS="ok"
else
IPV6_STATUS="not_available"
fi
else
IPV6_STATUS="skipped"
fi
}
# DNS resolution check
check_dns() {
TOTAL_TESTS=$((TOTAL_TESTS + 1))
DNS_WORKING=false
if [ "$CHECK_DNS" = "true" ]; then
for dns_server in $DNS_SERVERS; do
if command_exists nslookup; then
if nslookup torproject.org "$dns_server" >/dev/null 2>&1; then DNS_WORKING=true; break; fi
elif command_exists dig; then
if dig @"$dns_server" torproject.org +short +time="$TEST_TIMEOUT" >/dev/null 2>&1; then DNS_WORKING=true; break; fi
elif command_exists host; then
if host -t A torproject.org "$dns_server" >/dev/null 2>&1; then DNS_WORKING=true; break; fi
fi
done
[ "$DNS_WORKING" = "true" ] && DNS_STATUS="ok" || { DNS_STATUS="failed"; FAILED_TESTS=$((FAILED_TESTS + 1)); }
else
DNS_STATUS="skipped"
fi
}
# Tor network reachability
check_consensus() {
TOTAL_TESTS=$((TOTAL_TESTS + 1))
if [ "$CHECK_CONSENSUS" = "true" ] && command_exists curl; then
if curl -fsS --max-time "$TEST_TIMEOUT" https://collector.torproject.org/index.json | grep -q "metrics"; then
CONSENSUS_STATUS="ok"
else
CONSENSUS_STATUS="failed"; FAILED_TESTS=$((FAILED_TESTS + 1))
fi
else
CONSENSUS_STATUS="skipped"
fi
}
# Port accessibility (optional)
check_ports() {
TOTAL_TESTS=$((TOTAL_TESTS + 1))
if [ "$CHECK_PORTS" = "true" ]; then
if ! command_exists nc; then PORT_STATUS="skipped"; return; fi
if [ -f /etc/tor/torrc ]; then
ORPORT=$(grep -E "^ORPort" /etc/tor/torrc 2>/dev/null | awk '{print $2}' | head -1)
if [ -n "$ORPORT" ] && [ -n "$PUBLIC_IP" ]; then
if nc -z -w "$TEST_TIMEOUT" "$PUBLIC_IP" "$ORPORT" >/dev/null 2>&1; then
PORT_STATUS="ok"
else
PORT_STATUS="closed"; FAILED_TESTS=$((FAILED_TESTS + 1))
fi
else
PORT_STATUS="not_configured"
fi
else
PORT_STATUS="not_configured"
fi
else
PORT_STATUS="skipped"
fi
}
# Run all
check_ipv4
check_ipv6
check_dns
check_consensus
check_ports
TOTAL_PASSED=$((TOTAL_TESTS - FAILED_TESTS))
SUCCESS_RATE=$((TOTAL_PASSED * 100 / TOTAL_TESTS))
TIMESTAMP=$(date -u '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || date '+%Y-%m-%d %H:%M:%S')
# Output
case "$OUTPUT_FORMAT" in
json)
cat <<EOF
{
"timestamp": "$TIMESTAMP",
"success_rate": $SUCCESS_RATE,
"tests": { "total": $TOTAL_TESTS, "failed": $FAILED_TESTS },
"ipv4": { "status": "$IPV4_STATUS", "address": "$PUBLIC_IP" },
"ipv6": { "status": "$IPV6_STATUS", "address": "$PUBLIC_IP6" },
"dns": { "status": "$DNS_STATUS", "servers": "$DNS_SERVERS" },
"consensus": { "status": "$CONSENSUS_STATUS" },
"ports": { "status": "$PORT_STATUS" }
}
EOF
;;
plain)
echo "TIMESTAMP=$TIMESTAMP"
echo "SUCCESS_RATE=$SUCCESS_RATE"
echo "IPV4_STATUS=$IPV4_STATUS"
echo "IPV4_ADDRESS=$PUBLIC_IP"
echo "IPV6_STATUS=$IPV6_STATUS"
echo "IPV6_ADDRESS=$PUBLIC_IP6"
echo "DNS_STATUS=$DNS_STATUS"
echo "CONSENSUS_STATUS=$CONSENSUS_STATUS"
echo "PORT_STATUS=$PORT_STATUS"
;;
*)
echo "🌐 Network Diagnostics v$VERSION"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
if [ "$FAILED_TESTS" -eq 0 ]; then
echo "📊 Overall: ✅ All checks passed ($SUCCESS_RATE%)"
elif [ "$FAILED_TESTS" -lt "$TOTAL_TESTS" ]; then
echo "📊 Overall: ⚠️ Some issues detected ($SUCCESS_RATE% passed)"
else
echo "📊 Overall: ❌ Multiple failures ($SUCCESS_RATE% passed)"
fi
echo ""
echo "🔌 IPv4: $IPV4_STATUS ${PUBLIC_IP:+($PUBLIC_IP)}"
echo "🔌 IPv6: $IPV6_STATUS ${PUBLIC_IP6:+($PUBLIC_IP6)}"
echo "🔍 DNS: $DNS_STATUS"
echo "📋 Consensus: $CONSENSUS_STATUS"
echo "🚪 Ports: $PORT_STATUS"
echo ""
echo "🕒 Tested at: $TIMESTAMP"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
;;
esac
[ "$FAILED_TESTS" -eq 0 ] && exit 0 || exit 1

View File

@@ -1,105 +0,0 @@
#!/bin/sh
# relay-status - Comprehensive relay health check
# Usage: docker exec guard-relay relay-status
echo "🧅 Tor Relay Status Report"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
# Build Info
echo "📦 Build Information:"
if [ -f /build-info.txt ]; then
cat /build-info.txt | sed 's/^/ /'
else
echo " Version: Unknown"
fi
echo ""
# Bootstrap Progress
echo "🚀 Bootstrap Progress:"
if [ -f /var/log/tor/notices.log ]; then
BOOTSTRAP=$(grep "Bootstrapped" /var/log/tor/notices.log | tail -5)
if [ -n "$BOOTSTRAP" ]; then
echo "$BOOTSTRAP" | sed 's/^/ /'
if echo "$BOOTSTRAP" | grep -q "Bootstrapped 100%"; then
echo " ✅ Relay is fully bootstrapped!"
fi
else
echo " ⏳ No bootstrap messages yet. Tor is starting..."
fi
else
echo " ⚠️ Log file not found."
fi
echo ""
# Reachability Status
echo "🌍 Reachability Status:"
if [ -f /var/log/tor/notices.log ]; then
REACHABLE=$(grep -iE "reachable|self-testing" /var/log/tor/notices.log | tail -3)
if [ -n "$REACHABLE" ]; then
echo "$REACHABLE" | sed 's/^/ /'
if echo "$REACHABLE" | grep -q "reachable from the outside"; then
echo " ✅ ORPort is reachable!"
fi
else
echo " ⏳ No reachability test results yet."
fi
else
echo " ⚠️ Log file not found."
fi
echo ""
# Fingerprint
echo "🔑 Relay Fingerprint:"
if [ -f /var/lib/tor/fingerprint ]; then
cat /var/lib/tor/fingerprint | sed 's/^/ /'
else
echo " ⏳ Fingerprint not generated yet."
fi
echo ""
# ORPort Configuration
echo "🔌 ORPort Configuration:"
if [ -f /etc/tor/torrc ]; then
ORPORT=$(grep -iE "^(ORPort|DirPort|ObfsPort)" /etc/tor/torrc)
if [ -n "$ORPORT" ]; then
echo "$ORPORT" | sed 's/^/ /'
else
echo " ⚠️ No ORPort configuration found."
fi
else
echo " ⚠️ Configuration file not found."
fi
echo ""
# Relay Information
echo "📝 Relay Information:"
if [ -f /etc/tor/torrc ]; then
RELAY_INFO=$(grep -iE "^(Nickname|ContactInfo|ExitRelay|BridgeRelay)" /etc/tor/torrc)
if [ -n "$RELAY_INFO" ]; then
echo "$RELAY_INFO" | sed 's/^/ /'
else
echo " ⚠️ No relay information found."
fi
else
echo " ⚠️ Configuration file not found."
fi
echo ""
# Recent Errors
echo "⚠️ Recent Errors/Warnings:"
if [ -f /var/log/tor/notices.log ]; then
ERRORS=$(grep -iE "(error|warn|critical)" /var/log/tor/notices.log | tail -5)
if [ -n "$ERRORS" ]; then
echo "$ERRORS" | sed 's/^/ /'
else
echo " ✅ No recent errors or warnings."
fi
else
echo " ⚠️ Log file not found."
fi
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "💡 For live monitoring: docker logs -f <container-name>"
echo "🔗 Search your relay: https://metrics.torproject.org/rs.html"

388
tools/setup Normal file
View File

@@ -0,0 +1,388 @@
#!/bin/sh
# setup - Interactive configuration wizard for Tor relay
# Usage: setup [--auto|--help]
set -e
# Configuration
VERSION="1.1.0"
CONFIG_FILE="${CONFIG_FILE:-/etc/tor/torrc}"
RELAY_TYPE="${RELAY_TYPE:-guard}"
AUTO_MODE="${AUTO_MODE:-false}"
DEFAULT_NICKNAME="${DEFAULT_NICKNAME:-MyTorRelay}"
DEFAULT_CONTACT="${DEFAULT_CONTACT:-admin@example.com}"
DEFAULT_ORPORT="${DEFAULT_ORPORT:-9001}"
DEFAULT_DIRPORT="${DEFAULT_DIRPORT:-9030}"
DEFAULT_BANDWIDTH="${DEFAULT_BANDWIDTH:-1024}"
# Colors for terminal output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
NC='\033[0m' # No Color
# Parse arguments
for arg in "$@"; do
case "$arg" in
--help|-h)
cat << EOF
🧙 Tor-Guard-Relay Setup Wizard v${VERSION}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USAGE:
setup [OPTIONS]
OPTIONS:
--auto Use defaults for all prompts
--config FILE Config file path (default: /etc/tor/torrc)
--type TYPE Relay type: guard|exit|bridge
--help, -h Show this help message
ENVIRONMENT VARIABLES:
CONFIG_FILE Path to torrc file
RELAY_TYPE Type of relay (guard/exit/bridge)
DEFAULT_NICKNAME Default nickname
DEFAULT_CONTACT Default contact info
DEFAULT_ORPORT Default ORPort
DEFAULT_DIRPORT Default DirPort
DEFAULT_BANDWIDTH Default bandwidth in KB/s
AUTO_MODE Skip prompts and use defaults
RELAY TYPES:
guard Middle/Guard relay (recommended for beginners)
exit Exit relay (requires careful consideration)
bridge Bridge relay (helps censored users)
WIZARD STEPS:
1. Relay nickname selection
2. Contact information
3. Port configuration (ORPort/DirPort)
4. Bandwidth limits
5. Relay type selection
6. Configuration validation
EXAMPLES:
setup # Interactive wizard
setup --auto # Auto-configure with defaults
setup --type bridge # Configure as bridge relay
OUTPUT:
Creates a complete torrc configuration file ready
for production use with all required settings.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
EOF
exit 0
;;
--auto) AUTO_MODE="true" ;;
--config)
shift
CONFIG_FILE="$1"
shift
;;
--type)
shift
RELAY_TYPE="$1"
shift
;;
-*)
echo "❌ Unknown option: $arg"
echo "💡 Use --help for usage information"
exit 2
;;
esac
done
# Helper functions
print_header() {
echo ""
echo -e "${PURPLE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${BLUE}$1${NC}"
echo -e "${PURPLE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
}
print_step() {
echo -e "${GREEN}[$1/6]${NC} $2"
}
validate_nickname() {
echo "$1" | grep -qE "^[a-zA-Z0-9]{1,19}$"
}
validate_email() {
echo "$1" | grep -qE "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
}
validate_port() {
[ "$1" -ge 1 ] && [ "$1" -le 65535 ] 2>/dev/null
}
validate_bandwidth() {
[ "$1" -ge 256 ] 2>/dev/null
}
# Main setup wizard
clear
cat << EOF
${PURPLE}╔══════════════════════════════════════════════════════════╗
║ ║
║ 🧅 Tor-Guard-Relay Setup Wizard v${VERSION} ║
║ ║
║ Configure your Tor relay in 6 steps ║
║ ║
╚══════════════════════════════════════════════════════════╝${NC}
This wizard will help you configure a Tor relay with optimal
settings for your network and preferences.
Press Ctrl+C at any time to cancel.
EOF
# Step 1: Nickname
print_header "Step 1: Relay Nickname"
print_step "1" "Choose a nickname for your relay"
echo ""
echo "Requirements:"
echo " • 1-19 characters"
echo " • Only letters and numbers"
echo " • No spaces or special characters"
echo ""
if [ "$AUTO_MODE" = "true" ]; then
NICKNAME="$DEFAULT_NICKNAME"
echo -e "${GREEN}✓${NC} Using default: $NICKNAME"
else
while true; do
printf "Enter nickname [${DEFAULT_NICKNAME}]: "
read NICKNAME
[ -z "$NICKNAME" ] && NICKNAME="$DEFAULT_NICKNAME"
if validate_nickname "$NICKNAME"; then
echo -e "${GREEN}✓${NC} Nickname accepted: $NICKNAME"
break
else
echo -e "${RED}✗${NC} Invalid nickname. Please try again."
fi
done
fi
# Step 2: Contact Info
print_header "Step 2: Contact Information"
print_step "2" "Provide contact information (public)"
echo ""
echo "This helps the Tor Project contact you if needed."
echo "Use an email address you're comfortable making public."
echo ""
if [ "$AUTO_MODE" = "true" ]; then
CONTACT="$DEFAULT_CONTACT"
echo -e "${GREEN}✓${NC} Using default: $CONTACT"
else
while true; do
printf "Enter email [${DEFAULT_CONTACT}]: "
read CONTACT
[ -z "$CONTACT" ] && CONTACT="$DEFAULT_CONTACT"
if validate_email "$CONTACT"; then
echo -e "${GREEN}✓${NC} Contact accepted: $CONTACT"
break
else
echo -e "${YELLOW}⚠${NC} Invalid email format, but continuing..."
break
fi
done
fi
# Step 3: Port Configuration
print_header "Step 3: Port Configuration"
print_step "3" "Configure network ports"
echo ""
echo "ORPort: Main port for Tor traffic (must be accessible)"
echo "DirPort: Directory service port (optional)"
echo ""
if [ "$AUTO_MODE" = "true" ]; then
ORPORT="$DEFAULT_ORPORT"
DIRPORT="$DEFAULT_DIRPORT"
echo -e "${GREEN}✓${NC} Using defaults: ORPort=$ORPORT, DirPort=$DIRPORT"
else
while true; do
printf "Enter ORPort [${DEFAULT_ORPORT}]: "
read ORPORT
[ -z "$ORPORT" ] && ORPORT="$DEFAULT_ORPORT"
if validate_port "$ORPORT"; then
echo -e "${GREEN}✓${NC} ORPort accepted: $ORPORT"
break
else
echo -e "${RED}✗${NC} Invalid port (1-65535). Please try again."
fi
done
while true; do
printf "Enter DirPort [${DEFAULT_DIRPORT}]: "
read DIRPORT
[ -z "$DIRPORT" ] && DIRPORT="$DEFAULT_DIRPORT"
if validate_port "$DIRPORT"; then
echo -e "${GREEN}✓${NC} DirPort accepted: $DIRPORT"
break
else
echo -e "${RED}✗${NC} Invalid port (1-65535). Please try again."
fi
done
fi
# Step 4: Bandwidth Limits
print_header "Step 4: Bandwidth Allocation"
print_step "4" "Set bandwidth limits (KB/s)"
echo ""
echo "Recommended minimums:"
echo " • Guard relay: 1024 KB/s (1 MB/s)"
echo " • Exit relay: 2048 KB/s (2 MB/s)"
echo " • Bridge: 512 KB/s"
echo ""
if [ "$AUTO_MODE" = "true" ]; then
BANDWIDTH="$DEFAULT_BANDWIDTH"
echo -e "${GREEN}✓${NC} Using default: $BANDWIDTH KB/s"
else
while true; do
printf "Enter bandwidth limit in KB/s [${DEFAULT_BANDWIDTH}]: "
read BANDWIDTH
[ -z "$BANDWIDTH" ] && BANDWIDTH="$DEFAULT_BANDWIDTH"
if validate_bandwidth "$BANDWIDTH"; then
echo -e "${GREEN}✓${NC} Bandwidth accepted: $BANDWIDTH KB/s"
break
else
echo -e "${RED}✗${NC} Minimum bandwidth is 256 KB/s. Please try again."
fi
done
fi
# Step 5: Relay Type
print_header "Step 5: Relay Type Selection"
print_step "5" "Choose relay type"
echo ""
echo "Available types:"
echo " ${GREEN}guard${NC} - Middle/Guard relay (recommended)"
echo " ${YELLOW}exit${NC} - Exit relay (requires careful consideration)"
echo " ${BLUE}bridge${NC} - Bridge relay (helps censored users)"
echo ""
if [ "$AUTO_MODE" = "true" ]; then
echo -e "${GREEN}✓${NC} Using default type: $RELAY_TYPE"
else
printf "Enter relay type [guard/exit/bridge] [${RELAY_TYPE}]: "
read TYPE_INPUT
[ -n "$TYPE_INPUT" ] && RELAY_TYPE="$TYPE_INPUT"
case "$RELAY_TYPE" in
guard|exit|bridge)
echo -e "${GREEN}✓${NC} Relay type: $RELAY_TYPE"
;;
*)
echo -e "${YELLOW}⚠${NC} Unknown type, defaulting to guard"
RELAY_TYPE="guard"
;;
esac
fi
# Step 6: Generate Configuration
print_header "Step 6: Generating Configuration"
print_step "6" "Creating torrc file"
echo ""
# Create configuration
cat > "$CONFIG_FILE" << EOF
# Tor Relay Configuration
# Generated by Tor-Guard-Relay Setup Wizard v${VERSION}
# Date: $(date '+%Y-%m-%d %H:%M:%S')
# Basic Information
Nickname $NICKNAME
ContactInfo $CONTACT
# Network Settings
ORPort $ORPORT
DirPort $DIRPORT
# Bandwidth Limits
RelayBandwidthRate $BANDWIDTH KB
RelayBandwidthBurst $((BANDWIDTH * 2)) KB
# Relay Type Configuration
EOF
case "$RELAY_TYPE" in
exit)
cat >> "$CONFIG_FILE" << EOF
ExitRelay 1
ExitPolicy accept *:80 # HTTP
ExitPolicy accept *:443 # HTTPS
ExitPolicy accept *:6667 # IRC
ExitPolicy accept *:22 # SSH
ExitPolicy reject *:* # Reject everything else
EOF
;;
bridge)
cat >> "$CONFIG_FILE" << EOF
BridgeRelay 1
PublishServerDescriptor bridge
ExitRelay 0
EOF
;;
*)
cat >> "$CONFIG_FILE" << EOF
ExitRelay 0
ExitPolicy reject *:*
EOF
;;
esac
# Add common settings
cat >> "$CONFIG_FILE" << EOF
# Logging
Log notice file /var/log/tor/notices.log
Log notice syslog
# Data Directory
DataDirectory /var/lib/tor
# Performance
NumCPUs 0 # Use all available CPUs
HardwareAccel 1
# Security
NoExec 1
EOF
# Validate configuration
echo -e "${GREEN}✓${NC} Configuration generated successfully!"
echo ""
echo "📋 Configuration Summary:"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " Nickname: $NICKNAME"
echo " Contact: $CONTACT"
echo " ORPort: $ORPORT"
echo " DirPort: $DIRPORT"
echo " Bandwidth: $BANDWIDTH KB/s"
echo " Type: $RELAY_TYPE"
echo " Config: $CONFIG_FILE"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo -e "${GREEN}✅ Setup complete!${NC}"
echo ""
echo "Next steps:"
echo " 1. Review the configuration: cat $CONFIG_FILE"
echo " 2. Start your relay: tor -f $CONFIG_FILE"
echo " 3. Monitor status: /usr/local/bin/status"
echo " 4. Check metrics: /usr/local/bin/metrics"
echo ""
echo "🌐 Your relay will appear on Tor Metrics after ~3 hours:"
echo " https://metrics.torproject.org/rs.html"

333
tools/status Normal file
View File

@@ -0,0 +1,333 @@
#!/bin/sh
# status - Comprehensive relay status dashboard
# Usage: docker exec guard-relay status [--json|--help]
set -e
# Configuration
VERSION="1.1.0"
OUTPUT_FORMAT="${OUTPUT_FORMAT:-text}"
SHOW_ALL="${SHOW_ALL:-true}"
CHECK_NETWORK="${CHECK_NETWORK:-true}"
# Parse arguments
for arg in "$@"; do
case "$arg" in
--help|-h)
cat << EOF
🧅 Tor-Guard-Relay Status Dashboard v${VERSION}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USAGE:
status [OPTIONS]
OPTIONS:
--json Output in JSON format
--plain Plain text output
--quick Quick status check (skip network tests)
--full Full status report (default)
--help, -h Show this help message
ENVIRONMENT VARIABLES:
OUTPUT_FORMAT Output format (text/json/plain)
SHOW_ALL Show all sections (true/false)
CHECK_NETWORK Include network checks (true/false)
SECTIONS:
• Build Information
• Bootstrap Progress
• Reachability Status
• Relay Identity
• Network Configuration
• Performance Metrics
• Recent Activity
• Error Summary
EXAMPLES:
status # Full status report
status --json # JSON output for monitoring
status --quick # Quick status without network
status --plain # Machine-readable format
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
EOF
exit 0
;;
--json) OUTPUT_FORMAT="json" ;;
--plain) OUTPUT_FORMAT="plain" ;;
--quick) CHECK_NETWORK="false" ;;
--full)
SHOW_ALL="true"
CHECK_NETWORK="true"
;;
-*)
echo "❌ Unknown option: $arg"
echo "💡 Use --help for usage information"
exit 2
;;
esac
done
# Gather all status information
gather_status() {
# Process status
IS_RUNNING="false"
if pgrep -x tor > /dev/null 2>&1; then
IS_RUNNING="true"
PID=$(pgrep -x tor | head -1)
UPTIME=$(ps -o etime= -p "$PID" 2>/dev/null | tr -d ' ' || echo "0")
fi
# Bootstrap status
BOOTSTRAP_PERCENT=0
BOOTSTRAP_MESSAGE=""
if [ -f /var/log/tor/notices.log ]; then
BOOTSTRAP_LINE=$(grep "Bootstrapped" /var/log/tor/notices.log 2>/dev/null | tail -1)
if [ -n "$BOOTSTRAP_LINE" ]; then
BOOTSTRAP_PERCENT=$(echo "$BOOTSTRAP_LINE" | grep -oE '[0-9]+%' | tr -d '%' | tail -1)
BOOTSTRAP_MESSAGE=$(echo "$BOOTSTRAP_LINE" | sed 's/.*Bootstrapped [0-9]*%[: ]*//')
fi
fi
# Reachability
IS_REACHABLE="false"
REACHABILITY_MESSAGE=""
if [ -f /var/log/tor/notices.log ]; then
REACHABLE_LINE=$(grep -E "reachable|self-testing" /var/log/tor/notices.log 2>/dev/null | tail -1)
if echo "$REACHABLE_LINE" | grep -q "reachable from the outside"; then
IS_REACHABLE="true"
REACHABILITY_MESSAGE="ORPort is reachable from the outside"
elif [ -n "$REACHABLE_LINE" ]; then
REACHABILITY_MESSAGE=$(echo "$REACHABLE_LINE" | sed 's/.*] //')
fi
fi
# Relay identity
NICKNAME=""
FINGERPRINT=""
if [ -f /var/lib/tor/fingerprint ]; then
NICKNAME=$(awk '{print $1}' /var/lib/tor/fingerprint 2>/dev/null)
FINGERPRINT=$(awk '{print $2}' /var/lib/tor/fingerprint 2>/dev/null)
fi
# Configuration
ORPORT=""
DIRPORT=""
EXIT_RELAY="false"
BRIDGE_RELAY="false"
BANDWIDTH_RATE=""
if [ -f /etc/tor/torrc ]; then
ORPORT=$(grep -E "^ORPort" /etc/tor/torrc 2>/dev/null | awk '{print $2}' | head -1)
DIRPORT=$(grep -E "^DirPort" /etc/tor/torrc 2>/dev/null | awk '{print $2}' | head -1)
grep -qE "^ExitRelay\s+1" /etc/tor/torrc 2>/dev/null && EXIT_RELAY="true"
grep -qE "^BridgeRelay\s+1" /etc/tor/torrc 2>/dev/null && BRIDGE_RELAY="true"
BANDWIDTH_RATE=$(grep -E "^RelayBandwidthRate" /etc/tor/torrc 2>/dev/null | awk '{print $2,$3}')
fi
# Errors and warnings
ERROR_COUNT=0
WARNING_COUNT=0
RECENT_ERRORS=""
if [ -f /var/log/tor/notices.log ]; then
ERROR_COUNT=$(grep -cE "\[err\]|\[error\]" /var/log/tor/notices.log 2>/dev/null || echo 0)
WARNING_COUNT=$(grep -cE "\[warn\]|\[warning\]" /var/log/tor/notices.log 2>/dev/null || echo 0)
RECENT_ERRORS=$(grep -E "\[err\]|\[error\]" /var/log/tor/notices.log 2>/dev/null | tail -3)
fi
# Version info
VERSION_INFO=""
BUILD_TIME=""
if [ -f /build-info.txt ]; then
VERSION_INFO=$(grep "Version:" /build-info.txt 2>/dev/null | cut -d: -f2- | tr -d ' ')
BUILD_TIME=$(grep "Built:" /build-info.txt 2>/dev/null | cut -d: -f2- | tr -d ' ')
fi
# Network info (if enabled)
PUBLIC_IP=""
if [ "$CHECK_NETWORK" = "true" ] && command -v curl > /dev/null 2>&1; then
PUBLIC_IP=$(curl -s --max-time 5 https://api.ipify.org 2>/dev/null || echo "")
fi
}
# Gather all information
gather_status
# Generate timestamp
TIMESTAMP=$(date -u '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || date '+%Y-%m-%d %H:%M:%S')
# Determine overall status
if [ "$IS_RUNNING" = "false" ]; then
OVERALL_STATUS="down"
elif [ "$BOOTSTRAP_PERCENT" -eq 100 ] && [ "$IS_REACHABLE" = "true" ]; then
OVERALL_STATUS="healthy"
elif [ "$BOOTSTRAP_PERCENT" -eq 100 ]; then
OVERALL_STATUS="running"
elif [ "$BOOTSTRAP_PERCENT" -gt 0 ]; then
OVERALL_STATUS="starting"
else
OVERALL_STATUS="unknown"
fi
# Output based on format
case "$OUTPUT_FORMAT" in
json)
cat << EOF
{
"timestamp": "$TIMESTAMP",
"status": "$OVERALL_STATUS",
"process": {
"running": $IS_RUNNING,
"uptime": "$UPTIME"
},
"bootstrap": {
"percent": $BOOTSTRAP_PERCENT,
"message": "$BOOTSTRAP_MESSAGE"
},
"reachability": {
"reachable": $IS_REACHABLE,
"message": "$REACHABILITY_MESSAGE"
},
"identity": {
"nickname": "$NICKNAME",
"fingerprint": "$FINGERPRINT"
},
"configuration": {
"orport": "$ORPORT",
"dirport": "$DIRPORT",
"exit_relay": $EXIT_RELAY,
"bridge_relay": $BRIDGE_RELAY,
"bandwidth": "$BANDWIDTH_RATE"
},
"network": {
"public_ip": "$PUBLIC_IP"
},
"issues": {
"errors": $ERROR_COUNT,
"warnings": $WARNING_COUNT
},
"version": {
"software": "$VERSION_INFO",
"build_time": "$BUILD_TIME"
}
}
EOF
;;
plain)
echo "STATUS=$OVERALL_STATUS"
echo "RUNNING=$IS_RUNNING"
echo "UPTIME=$UPTIME"
echo "BOOTSTRAP=$BOOTSTRAP_PERCENT"
echo "REACHABLE=$IS_REACHABLE"
echo "NICKNAME=$NICKNAME"
echo "FINGERPRINT=$FINGERPRINT"
echo "ORPORT=$ORPORT"
echo "DIRPORT=$DIRPORT"
echo "ERRORS=$ERROR_COUNT"
echo "WARNINGS=$WARNING_COUNT"
echo "PUBLIC_IP=$PUBLIC_IP"
;;
*)
# Default text format with emojis
echo "🧅 Tor Relay Status Report"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
# Overall status
case "$OVERALL_STATUS" in
healthy)
echo "⭐ Overall Status: ✅ HEALTHY - Relay is fully operational"
;;
running)
echo "⭐ Overall Status: 🟡 RUNNING - Awaiting reachability confirmation"
;;
starting)
echo "⭐ Overall Status: 🔄 STARTING - Bootstrap in progress ($BOOTSTRAP_PERCENT%)"
;;
down)
echo "⭐ Overall Status: ❌ DOWN - Tor process not running"
;;
*)
echo "⭐ Overall Status: ❓ UNKNOWN"
;;
esac
echo ""
# Build info
if [ -n "$VERSION_INFO" ] || [ -n "$BUILD_TIME" ]; then
echo "📦 Build Information:"
[ -n "$VERSION_INFO" ] && echo " Version: $VERSION_INFO"
[ -n "$BUILD_TIME" ] && echo " Built: $BUILD_TIME"
echo ""
fi
# Bootstrap progress
echo "🚀 Bootstrap Progress:"
if [ "$BOOTSTRAP_PERCENT" -eq 100 ]; then
echo " ✅ Fully bootstrapped (100%)"
[ -n "$BOOTSTRAP_MESSAGE" ] && echo " Status: $BOOTSTRAP_MESSAGE"
elif [ "$BOOTSTRAP_PERCENT" -gt 0 ]; then
echo " 🔄 Bootstrapping: $BOOTSTRAP_PERCENT%"
[ -n "$BOOTSTRAP_MESSAGE" ] && echo " Status: $BOOTSTRAP_MESSAGE"
else
echo " ⏳ Not started yet"
fi
echo ""
# Reachability status
echo "🌍 Reachability Status:"
if [ "$IS_REACHABLE" = "true" ]; then
echo " ✅ Relay is reachable from the outside"
elif [ -n "$REACHABILITY_MESSAGE" ]; then
echo " 🔄 $REACHABILITY_MESSAGE"
else
echo " ⏳ No reachability test results yet"
fi
echo ""
# Relay identity
if [ -n "$NICKNAME" ] || [ -n "$FINGERPRINT" ]; then
echo "🔑 Relay Identity:"
[ -n "$NICKNAME" ] && echo " 📝 Nickname: $NICKNAME"
[ -n "$FINGERPRINT" ] && echo " 🆔 Fingerprint: $FINGERPRINT"
echo ""
fi
# Network configuration
echo "🔌 Network Configuration:"
[ -n "$ORPORT" ] && echo " ORPort: $ORPORT"
[ -n "$DIRPORT" ] && echo " DirPort: $DIRPORT"
[ -n "$PUBLIC_IP" ] && echo " Public IP: $PUBLIC_IP"
[ -n "$BANDWIDTH_RATE" ] && echo " Bandwidth: $BANDWIDTH_RATE"
if [ "$EXIT_RELAY" = "true" ]; then
echo " Type: 🚪 Exit Relay"
elif [ "$BRIDGE_RELAY" = "true" ]; then
echo " Type: 🌉 Bridge Relay"
else
echo " Type: 🔒 Guard/Middle Relay"
fi
echo ""
# Issues summary
if [ "$ERROR_COUNT" -gt 0 ] || [ "$WARNING_COUNT" -gt 0 ]; then
echo "⚠️ Issues Summary:"
[ "$ERROR_COUNT" -gt 0 ] && echo " ❌ Errors: $ERROR_COUNT"
[ "$WARNING_COUNT" -gt 0 ] && echo " ⚠️ Warnings: $WARNING_COUNT"
if [ -n "$RECENT_ERRORS" ] && [ "$ERROR_COUNT" -gt 0 ]; then
echo ""
echo " Recent errors:"
echo "$RECENT_ERRORS" | sed 's/^/ /'
fi
echo ""
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "💡 For live monitoring: docker logs -f <container-name>"
echo "🔗 Search your relay: https://metrics.torproject.org/rs.html"
[ -n "$FINGERPRINT" ] && echo "📊 Direct link: https://metrics.torproject.org/rs.html#search/$FINGERPRINT"
echo "🕒 Last updated: $TIMESTAMP"
;;
esac

View File

@@ -1,11 +1,201 @@
#!/bin/sh
# view-logs - Display recent Tor relay logs
# Usage: docker exec guard-relay view-logs
# view-logs - Advanced log viewer with filtering and analysis
# Usage: docker exec guard-relay view-logs [--follow|--errors|--help]
if [ -f /var/log/tor/notices.log ]; then
tail -n 50 -f /var/log/tor/notices.log
else
echo "⚠️ Log file not found. Tor might still be starting."
echo "📍 Expected location: /var/log/tor/notices.log"
set -e
# Configuration
VERSION="1.1.0"
LOG_FILE="${LOG_FILE:-/var/log/tor/notices.log}"
LOG_LINES="${LOG_LINES:-50}"
FOLLOW_MODE="${FOLLOW_MODE:-false}"
FILTER_MODE="${FILTER_MODE:-all}"
COLOR_OUTPUT="${COLOR_OUTPUT:-true}"
# Colors
RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m'
# Parse arguments
for arg in "$@"; do
case "$arg" in
--help|-h)
cat << EOF
📜 Tor-Guard-Relay Log Viewer v${VERSION}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
USAGE:
view-logs [OPTIONS]
OPTIONS:
--follow, -f Follow log output (tail -f)
--all Show all log entries (default)
--errors Show only errors
--warnings Show only warnings
--info Show only info messages
--bootstrap Show bootstrap progress
--last N Show last N lines (default: 50)
--no-color Disable colored output
--json Output as JSON
--help, -h Show this help message
ENVIRONMENT VARIABLES:
LOG_FILE Path to log file
LOG_LINES Default number of lines to show
COLOR_OUTPUT Enable colored output (true/false)
FILTER MODES:
all All log entries
errors Error messages only
warnings Warning messages only
info Info/notice messages
bootstrap Bootstrap related messages
network Network/connectivity messages
EXAMPLES:
view-logs # Last 50 lines
view-logs --follow # Follow new entries
view-logs --errors # Show only errors
view-logs --last 100 # Show last 100 lines
view-logs --bootstrap # Bootstrap progress
LOG LEVELS:
[err] Error - Critical issues
[warn] Warning - Potential problems
[notice] Notice - Normal operations
[info] Info - Detailed information
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
EOF
exit 0
;;
--follow|-f) FOLLOW_MODE="true" ;;
--all) FILTER_MODE="all" ;;
--errors) FILTER_MODE="errors" ;;
--warnings) FILTER_MODE="warnings" ;;
--info) FILTER_MODE="info" ;;
--bootstrap) FILTER_MODE="bootstrap" ;;
--network) FILTER_MODE="network" ;;
--last)
shift
LOG_LINES="$1"
shift
;;
--no-color) COLOR_OUTPUT="false" ;;
--json) OUTPUT_FORMAT="json" ;;
-*)
echo "❌ Unknown option: $arg"
echo "💡 Use --help for usage information"
exit 2
;;
esac
done
# Check if log file exists
if [ ! -f "$LOG_FILE" ]; then
if [ "$OUTPUT_FORMAT" = "json" ]; then
echo '{"error":"Log file not found","path":"'$LOG_FILE'"}'
else
echo "⚠️ Log file not found: $LOG_FILE"
echo "📍 Tor might still be starting."
echo "💡 Check back in a moment or verify Tor is running."
fi
exit 1
fi
# Function to colorize log lines
colorize_line() {
if [ "$COLOR_OUTPUT" != "true" ]; then
cat
return
fi
sed -e "s/\[err\]/$(printf "${RED}[err]${NC}")/g" \
-e "s/\[error\]/$(printf "${RED}[error]${NC}")/g" \
-e "s/\[warn\]/$(printf "${YELLOW}[warn]${NC}")/g" \
-e "s/\[warning\]/$(printf "${YELLOW}[warning]${NC}")/g" \
-e "s/\[notice\]/$(printf "${GREEN}[notice]${NC}")/g" \
-e "s/\[info\]/$(printf "${BLUE}[info]${NC}")/g" \
-e "s/Bootstrapped [0-9]*%/$(printf "${GREEN}&${NC}")/g"
}
# Function to apply filters
apply_filter() {
case "$FILTER_MODE" in
errors)
grep -iE "\[err\]|\[error\]|failed|failure|critical"
;;
warnings)
grep -iE "\[warn\]|\[warning\]"
;;
info)
grep -iE "\[notice\]|\[info\]"
;;
bootstrap)
grep -iE "bootstrap|starting|loading|opening|establishing"
;;
network)
grep -iE "reachable|connection|network|port|address"
;;
*)
cat
;;
esac
}
# JSON output mode
if [ "$OUTPUT_FORMAT" = "json" ]; then
TOTAL_LINES=$(wc -l < "$LOG_FILE")
ERROR_COUNT=$(grep -cE "\[err\]|\[error\]" "$LOG_FILE" 2>/dev/null || echo 0)
WARNING_COUNT=$(grep -cE "\[warn\]|\[warning\]" "$LOG_FILE" 2>/dev/null || echo 0)
echo '{'
echo ' "file": "'$LOG_FILE'",'
echo ' "total_lines": '$TOTAL_LINES','
echo ' "error_count": '$ERROR_COUNT','
echo ' "warning_count": '$WARNING_COUNT','
echo ' "entries": ['
tail -n "$LOG_LINES" "$LOG_FILE" | apply_filter | while IFS= read -r line; do
# Escape quotes and backslashes for JSON
line=$(echo "$line" | sed 's/\\/\\\\/g; s/"/\\"/g')
echo ' "'$line'",'
done | sed '$ s/,$//'
echo ' ]'
echo '}'
exit 0
fi
# Regular output mode
echo "📜 Tor Relay Logs"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📁 File: $LOG_FILE"
echo "🔍 Filter: $FILTER_MODE"
# Count log entries
if [ "$FILTER_MODE" = "all" ]; then
TOTAL_LINES=$(wc -l < "$LOG_FILE")
ERROR_COUNT=$(grep -cE "\[err\]|\[error\]" "$LOG_FILE" 2>/dev/null || echo 0)
WARNING_COUNT=$(grep -cE "\[warn\]|\[warning\]" "$LOG_FILE" 2>/dev/null || echo 0)
echo "📊 Stats: $TOTAL_LINES total | $ERROR_COUNT errors | $WARNING_COUNT warnings"
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
# Display logs
if [ "$FOLLOW_MODE" = "true" ]; then
echo "🔄 Following log output (Ctrl+C to stop)..."
echo ""
tail -n "$LOG_LINES" -f "$LOG_FILE" | apply_filter | colorize_line
else
tail -n "$LOG_LINES" "$LOG_FILE" | apply_filter | colorize_line
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "💡 Use 'view-logs --follow' for live updates"
fi