mirror of
https://github.com/r3bo0tbx1/tor-guard-relay.git
synced 2026-04-06 00:32:04 +02:00
🚀 feat: Release v1.1.0
Bumped version to v1.1.0 across all project files. 🏗️ Infrastructure & Templates: - 🐳 Docker Compose: Enhanced security, added persistent volume support. - 📋 Cosmos templates: Updated with new environment variable defaults. 🛠️ Scripts: - dashboard.sh: Implemented API token authentication, improved error handling, and added a notification UI. - fingerprint.sh: Refactored for clearer output formatting, robust validation, and a comprehensive help message. ✨ General: - Improved configuration management, health checks, and monitoring integration.
This commit is contained in:
@@ -5,13 +5,15 @@
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
VERSION="1.1.1"
|
||||
VERSION="1.1.0"
|
||||
DASHBOARD_PORT="${DASHBOARD_PORT:-8080}"
|
||||
DASHBOARD_BIND="${DASHBOARD_BIND:-127.0.0.1}" # ⚠️ CHANGED: Secure default
|
||||
ENABLE_DASHBOARD="${ENABLE_DASHBOARD:-true}"
|
||||
REFRESH_INTERVAL="${REFRESH_INTERVAL:-10}"
|
||||
MULTI_RELAY="${MULTI_RELAY:-false}"
|
||||
MAX_CONNECTIONS="${MAX_CONNECTIONS:-5}" # 🔒 NEW: Rate limiting
|
||||
API_TOKEN="${API_TOKEN:-}" # 🔒 NEW: API authentication token
|
||||
LOG_RETENTION="${LOG_RETENTION:-100}" # 📝 NEW: Number of log lines to keep
|
||||
|
||||
# Trap for clean exit
|
||||
trap 'cleanup' INT TERM
|
||||
@@ -19,9 +21,19 @@ trap 'cleanup' INT TERM
|
||||
cleanup() {
|
||||
echo ""
|
||||
echo "🛑 Dashboard shutting down..."
|
||||
# Clean up any temporary files
|
||||
[ -f "/tmp/dashboard.html" ] && rm -f "/tmp/dashboard.html"
|
||||
[ -f "/tmp/dashboard.pid" ] && rm -f "/tmp/dashboard.pid"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Enhanced error handling
|
||||
handle_error() {
|
||||
echo "❌ Error: $1" >&2
|
||||
logger -t "dashboard" "Error: $1"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Parse arguments
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
@@ -38,6 +50,7 @@ OPTIONS:
|
||||
--bind ADDR Bind address (default: 127.0.0.1)
|
||||
--refresh SEC Auto-refresh interval (default: 10)
|
||||
--multi Enable multi-relay support
|
||||
--token TOKEN API authentication token
|
||||
--help, -h Show this help message
|
||||
|
||||
ENVIRONMENT VARIABLES:
|
||||
@@ -47,6 +60,8 @@ ENVIRONMENT VARIABLES:
|
||||
REFRESH_INTERVAL Auto-refresh in seconds
|
||||
MULTI_RELAY Multi-relay mode (true/false)
|
||||
MAX_CONNECTIONS Max concurrent connections (default: 5)
|
||||
API_TOKEN API authentication token
|
||||
LOG_RETENTION Number of log lines to keep (default: 100)
|
||||
|
||||
⚠️ SECURITY NOTICE:
|
||||
Default binding is 127.0.0.1 (localhost only).
|
||||
@@ -55,6 +70,9 @@ ENVIRONMENT VARIABLES:
|
||||
|
||||
⚠️ WARNING: External exposure without authentication is NOT recommended!
|
||||
Use a reverse proxy (nginx/caddy) with authentication for production.
|
||||
|
||||
🔒 SECURITY: Set API_TOKEN to protect API endpoints:
|
||||
API_TOKEN=your-secure-token
|
||||
|
||||
FEATURES:
|
||||
• Real-time relay status monitoring
|
||||
@@ -64,6 +82,7 @@ FEATURES:
|
||||
• Error/warning alerts
|
||||
• Multi-relay management (optional)
|
||||
• Mobile-responsive design
|
||||
• API authentication (with token)
|
||||
|
||||
ENDPOINTS:
|
||||
http://localhost:8080/ Main dashboard
|
||||
@@ -79,6 +98,7 @@ DOCKER INTEGRATION:
|
||||
# For external access (use with caution):
|
||||
environment:
|
||||
- DASHBOARD_BIND=0.0.0.0
|
||||
- API_TOKEN=your-secure-token
|
||||
ports:
|
||||
- "8080:8080"
|
||||
|
||||
@@ -101,6 +121,11 @@ EOF
|
||||
REFRESH_INTERVAL="$1"
|
||||
shift
|
||||
;;
|
||||
--token)
|
||||
shift
|
||||
API_TOKEN="$1"
|
||||
shift
|
||||
;;
|
||||
--multi)
|
||||
MULTI_RELAY="true"
|
||||
;;
|
||||
@@ -122,20 +147,42 @@ fi
|
||||
# Security warning for external binding
|
||||
if [ "$DASHBOARD_BIND" = "0.0.0.0" ]; then
|
||||
echo "⚠️ WARNING: Dashboard is bound to 0.0.0.0 (all interfaces)"
|
||||
echo "⚠️ This exposes the dashboard without authentication!"
|
||||
echo "⚠️ This exposes dashboard without authentication!"
|
||||
if [ -z "$API_TOKEN" ]; then
|
||||
echo "⚠️ Consider setting API_TOKEN for API protection."
|
||||
else
|
||||
echo "✅ API endpoints are protected with token authentication."
|
||||
fi
|
||||
echo "⚠️ Consider using a reverse proxy with authentication."
|
||||
echo ""
|
||||
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
|
||||
handle_error "netcat (nc) is required. Install with: apk add netcat-openbsd"
|
||||
fi
|
||||
|
||||
# Connection counter (simple rate limiting)
|
||||
CONNECTION_COUNT=0
|
||||
echo $$ > /tmp/dashboard.pid
|
||||
|
||||
# Enhanced authentication check
|
||||
check_api_auth() {
|
||||
AUTH_HEADER="$1"
|
||||
if [ -n "$API_TOKEN" ]; then
|
||||
# Extract token from Authorization header
|
||||
TOKEN=$(echo "$AUTH_HEADER" | sed -n 's/.*Bearer *\([^ ]*\).*/\1p')
|
||||
if [ "$TOKEN" != "$API_TOKEN" ]; then
|
||||
echo "HTTP/1.1 401 Unauthorized"
|
||||
echo "Content-Type: application/json"
|
||||
echo "Connection: close"
|
||||
echo ""
|
||||
echo '{"error":"Unauthorized"}'
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# Function to generate dashboard HTML
|
||||
generate_dashboard() {
|
||||
@@ -143,7 +190,14 @@ generate_dashboard() {
|
||||
STATUS_JSON=$(/usr/local/bin/status --json 2>/dev/null || echo '{}')
|
||||
HEALTH_JSON=$(/usr/local/bin/health --json 2>/dev/null || echo '{}')
|
||||
|
||||
cat << 'EOF'
|
||||
# Cache the HTML to avoid regenerating on every request
|
||||
if [ -f "/tmp/dashboard.html" ] && [ $(find /tmp/dashboard.html -mmin -1 2>/dev/null) ]; then
|
||||
cat /tmp/dashboard.html
|
||||
return
|
||||
fi
|
||||
|
||||
# Generate new HTML and cache it
|
||||
cat << 'EOF' > /tmp/dashboard.html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
@@ -341,6 +395,31 @@ generate_dashboard() {
|
||||
font-size: 14px;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.notification {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
padding: 15px 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
||||
max-width: 300px;
|
||||
transform: translateX(400px);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.notification.show {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.notification.error {
|
||||
border-left: 4px solid #ef4444;
|
||||
}
|
||||
|
||||
.notification.success {
|
||||
border-left: 4px solid #10b981;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -458,11 +537,32 @@ generate_dashboard() {
|
||||
🔄 Auto-refresh: <span id="countdown">${REFRESH_INTERVAL}</span>s
|
||||
</div>
|
||||
|
||||
<div class="notification" id="notification"></div>
|
||||
|
||||
<script>
|
||||
let refreshInterval = ${REFRESH_INTERVAL};
|
||||
let countdown = refreshInterval;
|
||||
let lastStatus = null;
|
||||
|
||||
function showNotification(message, type = 'success') {
|
||||
const notification = document.getElementById('notification');
|
||||
notification.textContent = message;
|
||||
notification.className = 'notification ' + type;
|
||||
notification.classList.add('show');
|
||||
|
||||
setTimeout(() => {
|
||||
notification.classList.remove('show');
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
function updateStatus(data) {
|
||||
// Check for status changes
|
||||
if (lastStatus && lastStatus.status !== data.status) {
|
||||
showNotification('Status changed to: ' + data.status,
|
||||
data.status === 'healthy' ? 'success' : 'error');
|
||||
}
|
||||
lastStatus = data;
|
||||
|
||||
// Update overall status
|
||||
const statusEl = document.getElementById('overall-status');
|
||||
statusEl.className = 'status-badge status-' + (data.status || 'unknown');
|
||||
@@ -517,9 +617,15 @@ generate_dashboard() {
|
||||
|
||||
function refreshData() {
|
||||
fetch('/api/status')
|
||||
.then(response => response.json())
|
||||
.then(response => {
|
||||
if (!response.ok) throw new Error('Network response was not ok');
|
||||
return response.json();
|
||||
})
|
||||
.then(data => updateStatus(data))
|
||||
.catch(error => console.error('Error fetching status:', error));
|
||||
.catch(error => {
|
||||
console.error('Error fetching status:', error);
|
||||
showNotification('Failed to fetch status data', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
function copyFingerprint() {
|
||||
@@ -530,6 +636,10 @@ generate_dashboard() {
|
||||
const original = el.textContent;
|
||||
el.textContent = '✅ Copied!';
|
||||
setTimeout(() => el.textContent = original, 2000);
|
||||
showNotification('Fingerprint copied to clipboard', 'success');
|
||||
}).catch(err => {
|
||||
console.error('Failed to copy: ', err);
|
||||
showNotification('Failed to copy fingerprint', 'error');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -550,11 +660,20 @@ generate_dashboard() {
|
||||
</body>
|
||||
</html>
|
||||
EOF
|
||||
|
||||
# Return the cached HTML
|
||||
cat /tmp/dashboard.html
|
||||
}
|
||||
|
||||
# Function to handle API requests
|
||||
handle_api() {
|
||||
REQUEST_PATH="$1"
|
||||
AUTH_HEADER="$2"
|
||||
|
||||
# Check authentication for API endpoints
|
||||
if [ "$REQUEST_PATH" != "/" ] && ! check_api_auth "$AUTH_HEADER"; then
|
||||
return
|
||||
fi
|
||||
|
||||
case "$REQUEST_PATH" in
|
||||
"/api/status")
|
||||
@@ -619,30 +738,39 @@ echo "🎨 Starting Tor Relay Dashboard v${VERSION}"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "🌐 Listening on: http://$DASHBOARD_BIND:$DASHBOARD_PORT"
|
||||
echo "🔒 Max connections: $MAX_CONNECTIONS"
|
||||
if [ -n "$API_TOKEN" ]; then
|
||||
echo "🔐 API authentication: Enabled"
|
||||
else
|
||||
echo "⚠️ API authentication: Disabled"
|
||||
fi
|
||||
echo "💡 Press Ctrl+C to stop"
|
||||
echo ""
|
||||
|
||||
# Main server loop with basic connection limiting
|
||||
# Main server loop
|
||||
while true; do
|
||||
# Simple connection limiting
|
||||
if [ "$CONNECTION_COUNT" -ge "$MAX_CONNECTIONS" ]; then
|
||||
sleep 1
|
||||
CONNECTION_COUNT=0
|
||||
fi
|
||||
|
||||
# Wait for connection and parse request
|
||||
REQUEST=$(echo "" | nc -l -p "$DASHBOARD_PORT" -s "$DASHBOARD_BIND" -w 5 2>/dev/null | head -1)
|
||||
|
||||
if [ -n "$REQUEST" ]; then
|
||||
CONNECTION_COUNT=$((CONNECTION_COUNT + 1))
|
||||
CONNECTION_COUNT=$(( (CONNECTION_COUNT + 1) % MAX_CONNECTIONS ))
|
||||
|
||||
# Reset counter if needed
|
||||
[ "$CONNECTION_COUNT" -eq 0 ] && sleep 1
|
||||
|
||||
# Accept connection using a single listener
|
||||
nc -lk -p "$DASHBOARD_PORT" -s "$DASHBOARD_BIND" -w 5 | while read -r REQUEST; do
|
||||
# Only process first line
|
||||
PATH_REQ=$(echo "$REQUEST" | awk '{print $2}')
|
||||
|
||||
# Extract path from request
|
||||
REQUEST_PATH=$(echo "$REQUEST" | awk '{print $2}')
|
||||
# Extract Authorization header if present
|
||||
AUTH_HEADER=""
|
||||
while read -r header; do
|
||||
[ -z "$header" ] && break
|
||||
case "$header" in
|
||||
Authorization:*) AUTH_HEADER="$header" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Log request
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $REQUEST"
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Request: $PATH_REQ"
|
||||
|
||||
# Generate and send response in background to avoid blocking
|
||||
(handle_api "$REQUEST_PATH" | nc -l -p "$DASHBOARD_PORT" -s "$DASHBOARD_BIND" -w 1 > /dev/null 2>&1) &
|
||||
fi
|
||||
done
|
||||
# Generate and send response directly
|
||||
handle_api "$PATH_REQ" "$AUTH_HEADER"
|
||||
break
|
||||
done
|
||||
done
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
VERSION="1.1.0"
|
||||
OUTPUT_FORMAT="${OUTPUT_FORMAT:-text}"
|
||||
FINGERPRINT_FILE="${FINGERPRINT_FILE:-/var/lib/tor/fingerprint}"
|
||||
@@ -17,11 +16,10 @@ for arg in "$@"; do
|
||||
cat << EOF
|
||||
🔑 Tor-Guard-Relay Fingerprint Tool v${VERSION}
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
USAGE:
|
||||
Usage:
|
||||
fingerprint [OPTIONS]
|
||||
|
||||
OPTIONS:
|
||||
Options:
|
||||
--json Output in JSON format
|
||||
--plain Plain text output
|
||||
--copy Output for easy copying
|
||||
@@ -29,106 +27,76 @@ OPTIONS:
|
||||
--no-links Hide monitoring links
|
||||
--help, -h Show this help message
|
||||
|
||||
ENVIRONMENT VARIABLES:
|
||||
Environment:
|
||||
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
|
||||
Examples:
|
||||
fingerprint # Display formatted fingerprint
|
||||
fingerprint --json # JSON output
|
||||
fingerprint --copy # Copy-friendly format
|
||||
fingerprint --plain # Script-friendly output
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
EOF
|
||||
exit 0
|
||||
;;
|
||||
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
|
||||
;;
|
||||
-*) echo "❌ Unknown option: $arg" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Check if fingerprint exists
|
||||
# Check file existence
|
||||
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."
|
||||
;;
|
||||
printf '{ "status":"not_ready", "message":"Fingerprint not yet generated" }\n' ;;
|
||||
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
|
||||
|
||||
# Read fingerprint
|
||||
NICKNAME=$(awk '{print $1}' "$FINGERPRINT_FILE" 2>/dev/null || echo "")
|
||||
FINGERPRINT=$(awk '{print $2}' "$FINGERPRINT_FILE" 2>/dev/null || echo "")
|
||||
# Extract data safely
|
||||
NICKNAME=$(awk 'NF {print $1; exit}' "$FINGERPRINT_FILE" 2>/dev/null || echo "")
|
||||
FINGERPRINT=$(awk 'NF {print $2; exit}' "$FINGERPRINT_FILE" 2>/dev/null || echo "")
|
||||
|
||||
# Validate fingerprint format (40 hex characters)
|
||||
if ! echo "$FINGERPRINT" | grep -qE "^[A-F0-9]{40}$"; then
|
||||
# Validate fingerprint format (accept uppercase and lowercase)
|
||||
if ! echo "$FINGERPRINT" | grep -qE '^[A-Fa-f0-9]{40}$'; then
|
||||
case "$OUTPUT_FORMAT" in
|
||||
json)
|
||||
echo '{"status":"invalid","message":"Invalid fingerprint format"}'
|
||||
;;
|
||||
plain)
|
||||
echo "INVALID"
|
||||
;;
|
||||
*)
|
||||
echo "❌ Invalid fingerprint format detected"
|
||||
;;
|
||||
json) printf '{ "status":"invalid", "message":"Invalid fingerprint format" }\n' ;;
|
||||
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/:$//')
|
||||
# Generate formatted variants
|
||||
FINGERPRINT_SPACED=$(echo "$FINGERPRINT" | sed 's/\(..\)/\1 /g; s/ $//')
|
||||
FINGERPRINT_COLON=$(echo "$FINGERPRINT" | sed 's/\(..\)/\1:/g; s/:$//')
|
||||
|
||||
# Get additional info if available
|
||||
# Get creation time (portable fallback)
|
||||
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 "")
|
||||
if command -v stat >/dev/null 2>&1; then
|
||||
CREATION_TIME=$(stat -c %y "$FINGERPRINT_FILE" 2>/dev/null | cut -d'.' -f1 || true)
|
||||
elif command -v date >/dev/null 2>&1; then
|
||||
CREATION_TIME=$(date -r "$FINGERPRINT_FILE" 2>/dev/null || true)
|
||||
fi
|
||||
|
||||
# Output based on format
|
||||
# Escape JSON strings
|
||||
escape_json() { printf '%s' "$1" | sed 's/"/\\"/g'; }
|
||||
|
||||
# Output formats
|
||||
case "$OUTPUT_FORMAT" in
|
||||
json)
|
||||
cat << EOF
|
||||
{
|
||||
"status": "ready",
|
||||
"nickname": "$NICKNAME",
|
||||
"nickname": "$(escape_json "$NICKNAME")",
|
||||
"fingerprint": "$FINGERPRINT",
|
||||
"fingerprint_spaced": "$FINGERPRINT_SPACED",
|
||||
"fingerprint_colon": "$FINGERPRINT_COLON",
|
||||
@@ -140,21 +108,14 @@ case "$OUTPUT_FORMAT" in
|
||||
}
|
||||
EOF
|
||||
;;
|
||||
|
||||
plain)
|
||||
echo "$NICKNAME $FINGERPRINT"
|
||||
;;
|
||||
|
||||
echo "$NICKNAME $FINGERPRINT" ;;
|
||||
copy)
|
||||
echo "$FINGERPRINT"
|
||||
echo ""
|
||||
echo "# Formatted versions:"
|
||||
echo "# Spaced: $FINGERPRINT_SPACED"
|
||||
echo "# Colon: $FINGERPRINT_COLON"
|
||||
;;
|
||||
|
||||
echo "# Colon: $FINGERPRINT_COLON" ;;
|
||||
*)
|
||||
# Default text format with emojis
|
||||
echo "🔑 Tor Relay Fingerprint"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
@@ -164,23 +125,19 @@ EOF
|
||||
echo "📋 Formatted versions:"
|
||||
echo " Spaced: $FINGERPRINT_SPACED"
|
||||
echo " Colon: $FINGERPRINT_COLON"
|
||||
|
||||
if [ -n "$CREATION_TIME" ]; then
|
||||
[ -n "$CREATION_TIME" ] && {
|
||||
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"
|
||||
|
||||
392
tools/health.sh
392
tools/health.sh
@@ -4,14 +4,39 @@
|
||||
|
||||
set -e
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# Configuration
|
||||
# ──────────────────────────────────────────────
|
||||
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}"
|
||||
VERBOSE="${VERBOSE:-false}"
|
||||
LOG_LEVEL="${LOG_LEVEL:-warning}" # error, warning, info
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# Utility functions
|
||||
# ──────────────────────────────────────────────
|
||||
|
||||
# Safe execution with error handling
|
||||
safe() { "$@" 2>/dev/null || true; }
|
||||
|
||||
# Log messages based on verbosity level
|
||||
log() {
|
||||
level="$1"
|
||||
message="$2"
|
||||
|
||||
if [ "$VERBOSE" = "true" ] || [ "$level" = "error" ]; then
|
||||
case "$level" in
|
||||
error) echo "❌ ERROR: $message" >&2 ;;
|
||||
warn) echo "⚠️ WARNING: $message" >&2 ;;
|
||||
info) echo "ℹ️ INFO: $message" >&2 ;;
|
||||
esac
|
||||
fi
|
||||
}
|
||||
|
||||
# Format status with appropriate emoji
|
||||
format_status() {
|
||||
case "$1" in
|
||||
ok|OK) echo "🟢 OK" ;;
|
||||
@@ -21,10 +46,11 @@ format_status() {
|
||||
esac
|
||||
}
|
||||
|
||||
# Format IP status with address
|
||||
format_ip_status() {
|
||||
local type="$1"
|
||||
local status="$2"
|
||||
local addr="$3"
|
||||
type="$1"
|
||||
status="$2"
|
||||
addr="$3"
|
||||
|
||||
if [ "$status" = "ok" ] && [ -n "$addr" ]; then
|
||||
echo "🟢 OK ($addr)"
|
||||
@@ -39,6 +65,96 @@ format_ip_status() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Get public IP with multiple fallback methods (from status script)
|
||||
get_public_ip() {
|
||||
ip_type=$1 # "ipv4" or "ipv6"
|
||||
ip=""
|
||||
|
||||
# Try multiple services in order of preference
|
||||
if [ "$ip_type" = "ipv4" ]; then
|
||||
# Try curl with multiple services
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
ip=$(curl -4 -s --max-time 5 --connect-timeout 3 https://api.ipify.org 2>/dev/null || true)
|
||||
[ -z "$ip" ] && ip=$(curl -4 -s --max-time 5 --connect-timeout 3 https://ipinfo.io/ip 2>/dev/null || true)
|
||||
[ -z "$ip" ] && ip=$(curl -4 -s --max-time 5 --connect-timeout 3 https://ipv4.icanhazip.com 2>/dev/null || true)
|
||||
# Fallback to wget
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
ip=$(wget -4 -q -O - --timeout=5 https://api.ipify.org 2>/dev/null || true)
|
||||
[ -z "$ip" ] && ip=$(wget -4 -q -O - --timeout=5 https://ipinfo.io/ip 2>/dev/null || true)
|
||||
[ -z "$ip" ] && ip=$(wget -4 -q -O - --timeout=5 https://ipv4.icanhazip.com 2>/dev/null || true)
|
||||
fi
|
||||
|
||||
# Validate IPv4 format
|
||||
if printf '%s' "$ip" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'; then
|
||||
printf '%s' "$ip"
|
||||
fi
|
||||
else # IPv6
|
||||
# Try curl with multiple services
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
ip=$(curl -6 -s --max-time 5 --connect-timeout 3 https://api6.ipify.org 2>/dev/null || true)
|
||||
[ -z "$ip" ] && ip=$(curl -6 -s --max-time 5 --connect-timeout 3 https://ipv6.icanhazip.com 2>/dev/null || true)
|
||||
# Fallback to wget
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
ip=$(wget -6 -q -O - --timeout=5 https://api6.ipify.org 2>/dev/null || true)
|
||||
[ -z "$ip" ] && ip=$(wget -6 -q -O - --timeout=5 https://ipv6.icanhazip.com 2>/dev/null || true)
|
||||
fi
|
||||
|
||||
# Basic IPv6 validation (simplified)
|
||||
if printf '%s' "$ip" | grep -Eq '^[0-9a-fA-F:]+$'; then
|
||||
printf '%s' "$ip"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Check if a value is numeric
|
||||
is_numeric() { echo "$1" | grep -qE '^[0-9]+$'; }
|
||||
|
||||
# Validate IP address format
|
||||
validate_ip() {
|
||||
type="$1"
|
||||
ip="$2"
|
||||
|
||||
if [ -z "$ip" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
case "$type" in
|
||||
ipv4)
|
||||
echo "$ip" | grep -qE '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$'
|
||||
;;
|
||||
ipv6)
|
||||
echo "$ip" | grep -qE '^[0-9a-fA-F:]+$'
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Get port status
|
||||
check_port_status() {
|
||||
port="$1"
|
||||
host="$2"
|
||||
|
||||
if [ -z "$port" ] || [ "$port" = "0" ]; then
|
||||
echo "not_configured"
|
||||
return
|
||||
fi
|
||||
|
||||
if command -v nc >/dev/null 2>&1; then
|
||||
if nc -z -w "$CHECK_TIMEOUT" "$host" "$port" 2>/dev/null; then
|
||||
echo "open"
|
||||
else
|
||||
echo "closed"
|
||||
fi
|
||||
else
|
||||
echo "skipped"
|
||||
fi
|
||||
}
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# Argument parsing
|
||||
# ──────────────────────────────────────────────
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--help|-h)
|
||||
@@ -46,19 +162,63 @@ for arg in "$@"; do
|
||||
🧅 Tor-Guard-Relay Health Check v${VERSION}
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
USAGE:
|
||||
health [--json|--plain|--text|--webhook]
|
||||
health [--json|--plain|--text|--webhook|--verbose]
|
||||
|
||||
OPTIONS:
|
||||
--json Output in JSON format
|
||||
--plain Plain text output (key=value)
|
||||
--text Formatted text output (default)
|
||||
--webhook Send health status to webhook
|
||||
--verbose Show detailed execution information
|
||||
--log-level Set log level (error|warning|info)
|
||||
--help, -h Show this help message
|
||||
|
||||
ENVIRONMENT VARIABLES:
|
||||
OUTPUT_FORMAT Output format (text/json/plain)
|
||||
ENABLE_HEALTH_CHECK Enable health checks (true/false)
|
||||
HEALTH_WEBHOOK_URL Webhook URL for health notifications
|
||||
CHECK_TIMEOUT Network check timeout in seconds (default: 5)
|
||||
VERBOSE Show verbose output (true/false)
|
||||
LOG_LEVEL Minimum log level (error/warning/info)
|
||||
|
||||
EXIT CODES:
|
||||
0 Relay is healthy or starting
|
||||
1 Relay is down or has critical issues
|
||||
2 Configuration or execution error
|
||||
|
||||
EXAMPLES:
|
||||
health # Basic health check
|
||||
health --json # JSON output
|
||||
health --verbose # Verbose output
|
||||
health --webhook # Send to webhook
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
EOF
|
||||
exit 0 ;;
|
||||
exit 0
|
||||
;;
|
||||
--json) OUTPUT_FORMAT="json" ;;
|
||||
--plain) OUTPUT_FORMAT="plain" ;;
|
||||
--text) OUTPUT_FORMAT="text" ;;
|
||||
--webhook) SEND_WEBHOOK="true" ;;
|
||||
--verbose) VERBOSE="true" ;;
|
||||
--log-level)
|
||||
shift
|
||||
LOG_LEVEL="$1"
|
||||
shift
|
||||
;;
|
||||
-*)
|
||||
echo "❌ Unknown option: $arg" >&2
|
||||
echo "💡 Use --help for usage information" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Skip if disabled
|
||||
# ──────────────────────────────────────────────
|
||||
# Early exit if disabled
|
||||
# ──────────────────────────────────────────────
|
||||
if [ "$ENABLE_HEALTH_CHECK" != "true" ]; then
|
||||
log "info" "Health checking is disabled"
|
||||
case "$OUTPUT_FORMAT" in
|
||||
json) echo '{"status":"disabled"}' ;;
|
||||
plain) echo "DISABLED" ;;
|
||||
@@ -67,7 +227,11 @@ if [ "$ENABLE_HEALTH_CHECK" != "true" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
log "info" "Starting health check with format: $OUTPUT_FORMAT"
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# Initialize variables
|
||||
# ──────────────────────────────────────────────
|
||||
STATUS="unknown"
|
||||
BOOTSTRAP_PERCENT=0
|
||||
IS_RUNNING=false
|
||||
@@ -80,65 +244,150 @@ WARNINGS=0
|
||||
VERSION_INFO=""
|
||||
ORPORT=""
|
||||
DIRPORT=""
|
||||
EXIT_RELAY="false"
|
||||
RELAY_TYPE="guard"
|
||||
PUBLIC_IP=""
|
||||
PUBLIC_IP6=""
|
||||
ORPORT_STATUS="unknown"
|
||||
DIRPORT_STATUS="unknown"
|
||||
|
||||
# Inline Checks
|
||||
|
||||
# Tor process
|
||||
# ──────────────────────────────────────────────
|
||||
# Process check
|
||||
# ──────────────────────────────────────────────
|
||||
log "info" "Checking Tor process status"
|
||||
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"
|
||||
log "info" "Tor process found (PID: $PID, uptime: $UPTIME)"
|
||||
else
|
||||
log "error" "Tor process not found"
|
||||
fi
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# Bootstrap progress
|
||||
# ──────────────────────────────────────────────
|
||||
log "info" "Checking 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
|
||||
BOOTSTRAP_PERCENT=${BOOTSTRAP_PERCENT:-0}
|
||||
log "info" "Bootstrap progress: $BOOTSTRAP_PERCENT%"
|
||||
else
|
||||
log "warn" "Tor notices log not found"
|
||||
fi
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# Reachability
|
||||
# ──────────────────────────────────────────────
|
||||
log "info" "Checking 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
|
||||
log "info" "Relay is reachable from outside"
|
||||
else
|
||||
log "warn" "Relay may not be reachable from outside"
|
||||
fi
|
||||
else
|
||||
log "warn" "Cannot check reachability without log file"
|
||||
fi
|
||||
|
||||
# Relay info
|
||||
# ──────────────────────────────────────────────
|
||||
# Relay identity
|
||||
# ──────────────────────────────────────────────
|
||||
log "info" "Getting relay identity"
|
||||
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)
|
||||
log "info" "Relay identity: $NICKNAME ($FINGERPRINT)"
|
||||
else
|
||||
log "warn" "Fingerprint file not found"
|
||||
fi
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# Network configuration and relay type
|
||||
# ──────────────────────────────────────────────
|
||||
log "info" "Reading network configuration"
|
||||
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)
|
||||
|
||||
# Fixed relay type detection - check for exact matches
|
||||
if safe grep -qE "^ExitRelay\s+1" /etc/tor/torrc; then
|
||||
EXIT_RELAY="true"
|
||||
RELAY_TYPE="exit"
|
||||
elif safe grep -qE "^BridgeRelay\s+1" /etc/tor/torrc; then
|
||||
RELAY_TYPE="bridge"
|
||||
else
|
||||
RELAY_TYPE="guard"
|
||||
fi
|
||||
|
||||
log "info" "Relay type: $RELAY_TYPE, ORPort: $ORPORT, DirPort: $DIRPORT"
|
||||
else
|
||||
log "warn" "Tor configuration file not found"
|
||||
fi
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# Version info
|
||||
# ──────────────────────────────────────────────
|
||||
log "info" "Getting version information"
|
||||
if [ -f /build-info.txt ]; then
|
||||
VERSION_INFO=$(safe head -1 /build-info.txt | cut -d: -f2- | tr -d ' ')
|
||||
log "info" "Version: $VERSION_INFO"
|
||||
else
|
||||
log "warn" "Build info file not found"
|
||||
fi
|
||||
|
||||
# IPv4/IPv6 check (added for visual consistency)
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
PUBLIC_IP=$(curl -4 -fsS --max-time "$CHECK_TIMEOUT" https://ipv4.icanhazip.com 2>/dev/null | tr -d '\r')
|
||||
PUBLIC_IP6=$(curl -6 -fsS --max-time "$CHECK_TIMEOUT" https://ipv6.icanhazip.com 2>/dev/null | tr -d '\r')
|
||||
# ──────────────────────────────────────────────
|
||||
# Network IP checks (using failsafe method from status script)
|
||||
# ──────────────────────────────────────────────
|
||||
log "info" "Checking network connectivity"
|
||||
if command -v curl >/dev/null 2>&1 || command -v wget >/dev/null 2>&1; then
|
||||
PUBLIC_IP=$(get_public_ip "ipv4")
|
||||
PUBLIC_IP6=$(get_public_ip "ipv6")
|
||||
|
||||
if [ -n "$PUBLIC_IP" ]; then
|
||||
log "info" "IPv4: $PUBLIC_IP"
|
||||
else
|
||||
log "warn" "Failed to get IPv4 address"
|
||||
fi
|
||||
|
||||
if [ -n "$PUBLIC_IP6" ]; then
|
||||
log "info" "IPv6: $PUBLIC_IP6"
|
||||
else
|
||||
log "warn" "Failed to get IPv6 address"
|
||||
fi
|
||||
else
|
||||
log "warn" "Neither curl nor wget available for network checks"
|
||||
fi
|
||||
|
||||
# Count issues
|
||||
# ──────────────────────────────────────────────
|
||||
# Port status checks
|
||||
# ──────────────────────────────────────────────
|
||||
if [ -n "$PUBLIC_IP" ]; then
|
||||
log "info" "Checking port status"
|
||||
ORPORT_STATUS=$(check_port_status "$ORPORT" "$PUBLIC_IP")
|
||||
DIRPORT_STATUS=$(check_port_status "$DIRPORT" "$PUBLIC_IP")
|
||||
|
||||
log "info" "ORPort status: $ORPORT_STATUS"
|
||||
log "info" "DirPort status: $DIRPORT_STATUS"
|
||||
fi
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# Error and warning counts
|
||||
# ──────────────────────────────────────────────
|
||||
log "info" "Counting errors and warnings"
|
||||
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)
|
||||
log "info" "Errors: $ERRORS, Warnings: $WARNINGS"
|
||||
else
|
||||
log "warn" "Cannot count errors without log file"
|
||||
fi
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# Determine overall status
|
||||
# ──────────────────────────────────────────────
|
||||
log "info" "Determining overall status"
|
||||
if [ "$IS_RUNNING" = false ]; then
|
||||
STATUS="down"
|
||||
elif [ "$BOOTSTRAP_PERCENT" -eq 100 ] && [ "$IS_REACHABLE" = true ]; then
|
||||
@@ -151,53 +400,56 @@ else
|
||||
STATUS="unknown"
|
||||
fi
|
||||
|
||||
log "info" "Overall status: $STATUS"
|
||||
|
||||
TIMESTAMP=$(date -u '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || date)
|
||||
|
||||
# Output
|
||||
# ──────────────────────────────────────────────
|
||||
# Output formatting
|
||||
# ──────────────────────────────────────────────
|
||||
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",
|
||||
"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",
|
||||
"ipv4": "$PUBLIC_IP",
|
||||
"ipv6": "$PUBLIC_IP6"
|
||||
"orport_status": "$ORPORT_STATUS",
|
||||
"dirport_status": "$DIRPORT_STATUS",
|
||||
"ipv4": "$PUBLIC_IP",
|
||||
"ipv6": "$PUBLIC_IP6"
|
||||
},
|
||||
"relay": {
|
||||
"nickname": "$NICKNAME",
|
||||
"fingerprint": "$FINGERPRINT",
|
||||
"exit_relay": $EXIT_RELAY,
|
||||
"version": "$VERSION_INFO"
|
||||
"relay": {
|
||||
"nickname": "$NICKNAME",
|
||||
"fingerprint": "$FINGERPRINT",
|
||||
"type": "$RELAY_TYPE",
|
||||
"version": "$VERSION_INFO"
|
||||
},
|
||||
"issues": {
|
||||
"errors": $ERRORS,
|
||||
"warnings": $WARNINGS
|
||||
}
|
||||
"issues": { "errors": $ERRORS, "warnings": $WARNINGS }
|
||||
}
|
||||
EOF
|
||||
;;
|
||||
plain)
|
||||
echo "STATUS=$STATUS"
|
||||
echo "TIMESTAMP=$TIMESTAMP"
|
||||
echo "RUNNING=$IS_RUNNING"
|
||||
echo "UPTIME=$UPTIME"
|
||||
echo "BOOTSTRAP=$BOOTSTRAP_PERCENT"
|
||||
echo "REACHABLE=$IS_REACHABLE"
|
||||
echo "NICKNAME=$NICKNAME"
|
||||
echo "FINGERPRINT=$FINGERPRINT"
|
||||
echo "RELAY_TYPE=$RELAY_TYPE"
|
||||
echo "ERRORS=$ERRORS"
|
||||
echo "WARNINGS=$WARNINGS"
|
||||
echo "UPTIME=$UPTIME"
|
||||
echo "ORPORT=$ORPORT"
|
||||
echo "DIRPORT=$DIRPORT"
|
||||
echo "ORPORT_STATUS=$ORPORT_STATUS"
|
||||
echo "DIRPORT_STATUS=$DIRPORT_STATUS"
|
||||
echo "IPV4=$PUBLIC_IP"
|
||||
echo "IPV6=$PUBLIC_IP6"
|
||||
;;
|
||||
@@ -237,14 +489,40 @@ EOF
|
||||
fi
|
||||
[ -n "$PUBLIC_IP" ] && echo " 🌐 IPv4: 🟢 OK ($PUBLIC_IP)" || echo " 🌐 IPv4: 🔴 No IPv4 connectivity"
|
||||
[ -n "$PUBLIC_IP6" ] && echo " 🌐 IPv6: 🟢 OK ($PUBLIC_IP6)" || echo " 🌐 IPv6: 🔴 No IPv6 connectivity"
|
||||
[ -n "$ORPORT" ] && echo " 📍 ORPort: $ORPORT" || echo " 📍 ORPort: 🔴 Not configured"
|
||||
[ -n "$DIRPORT" ] && echo " 📍 DirPort: $DIRPORT" || echo " 📍 DirPort: 🔴 Not configured"
|
||||
|
||||
# Port status with better formatting
|
||||
if [ -n "$ORPORT" ]; then
|
||||
case "$ORPORT_STATUS" in
|
||||
open) echo " 📍 ORPort: 🟢 Open ($ORPORT)" ;;
|
||||
closed) echo " 📍 ORPort: 🔴 Closed ($ORPORT)" ;;
|
||||
not_configured) echo " 📍 ORPort: ⏭️ Not configured" ;;
|
||||
*) echo " 📍 ORPort: ❓ Unknown ($ORPORT)" ;;
|
||||
esac
|
||||
else
|
||||
echo " 📍 ORPort: 🔴 Not configured"
|
||||
fi
|
||||
|
||||
if [ -n "$DIRPORT" ]; then
|
||||
case "$DIRPORT_STATUS" in
|
||||
open) echo " 📍 DirPort: 🟢 Open ($DIRPORT)" ;;
|
||||
closed) echo " 📍 DirPort: 🔴 Closed ($DIRPORT)" ;;
|
||||
not_configured) echo " 📍 DirPort: ⏭️ Not configured" ;;
|
||||
*) echo " 📍 DirPort: ❓ Unknown ($DIRPORT)" ;;
|
||||
esac
|
||||
else
|
||||
echo " 📍 DirPort: 🔴 Not configured"
|
||||
fi
|
||||
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"
|
||||
case "$RELAY_TYPE" in
|
||||
bridge) echo " 🌉 Type: Bridge Relay" ;;
|
||||
exit) echo " 🚪 Type: Exit Relay" ;;
|
||||
guard) echo " 🔒 Type: Guard/Middle Relay" ;;
|
||||
*) echo " ❓ Type: Unknown" ;;
|
||||
esac
|
||||
echo ""
|
||||
fi
|
||||
if [ "$ERRORS" -gt 0 ] || [ "$WARNINGS" -gt 0 ]; then
|
||||
@@ -258,15 +536,35 @@ EOF
|
||||
;;
|
||||
esac
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# Optional webhook support
|
||||
# ──────────────────────────────────────────────
|
||||
if [ "$SEND_WEBHOOK" = "true" ] && [ -n "$HEALTH_WEBHOOK_URL" ]; then
|
||||
log "info" "Sending health status to webhook"
|
||||
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
|
||||
if [ $? -eq 0 ]; then
|
||||
log "info" "Webhook sent successfully"
|
||||
else
|
||||
log "error" "Failed to send webhook"
|
||||
fi
|
||||
else
|
||||
log "error" "curl not available for webhook"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# Exit code mapping
|
||||
# ──────────────────────────────────────────────
|
||||
log "info" "Health check completed with status: $STATUS"
|
||||
case "$STATUS" in
|
||||
healthy|running|starting) exit 0 ;;
|
||||
*) exit 1 ;;
|
||||
healthy|running|starting)
|
||||
log "info" "Exiting with code 0 (healthy)"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
log "error" "Exiting with code 1 (unhealthy)"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -1,316 +1,86 @@
|
||||
#!/bin/sh
|
||||
# metrics-http - HTTP server for Prometheus metrics endpoint
|
||||
# Usage: metrics-http [--port PORT] [--help]
|
||||
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
VERSION="1.1.1"
|
||||
VERSION="1.1.0"
|
||||
METRICS_PORT="${METRICS_PORT:-9052}"
|
||||
METRICS_BIND="${METRICS_BIND:-127.0.0.1}" # ⚠️ CHANGED: Secure default
|
||||
METRICS_BIND="${METRICS_BIND:-127.0.0.1}"
|
||||
METRICS_PATH="${METRICS_PATH:-/metrics}"
|
||||
ENABLE_METRICS="${ENABLE_METRICS:-true}"
|
||||
RESPONSE_TIMEOUT="${RESPONSE_TIMEOUT:-10}"
|
||||
MAX_CONNECTIONS="${MAX_CONNECTIONS:-10}" # 🔒 NEW: Rate limiting
|
||||
MAX_CONNECTIONS="${MAX_CONNECTIONS:-10}"
|
||||
|
||||
# Trap for clean exit
|
||||
trap 'cleanup' INT TERM
|
||||
trap 'echo; echo "🛑 Metrics HTTP server shutting down..."; exit 0' INT TERM
|
||||
|
||||
cleanup() {
|
||||
echo ""
|
||||
echo "🛑 Metrics HTTP server shutting down..."
|
||||
exit 0
|
||||
}
|
||||
|
||||
# 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: 127.0.0.1)
|
||||
--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: 127.0.0.1)
|
||||
METRICS_PATH URL path for metrics (default: /metrics)
|
||||
ENABLE_METRICS Enable metrics server (true/false)
|
||||
RESPONSE_TIMEOUT Response timeout in seconds
|
||||
MAX_CONNECTIONS Max concurrent connections (default: 10)
|
||||
|
||||
⚠️ SECURITY NOTICE:
|
||||
Default binding is 127.0.0.1 (localhost only).
|
||||
To expose externally for Prometheus scraping, explicitly set:
|
||||
METRICS_BIND=0.0.0.0
|
||||
|
||||
⚠️ WARNING: Metrics may contain sensitive relay information!
|
||||
Recommendations for production:
|
||||
1. Use network-level access controls (firewall rules)
|
||||
2. Deploy Prometheus in same network/VPN
|
||||
3. Use TLS termination proxy (nginx with client certs)
|
||||
4. Never expose directly to public internet
|
||||
|
||||
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:
|
||||
# For localhost access only (secure):
|
||||
ports:
|
||||
- "127.0.0.1:9052:9052"
|
||||
|
||||
# For Prometheus in same Docker network:
|
||||
networks:
|
||||
- monitoring
|
||||
# No port exposure needed!
|
||||
|
||||
# For external Prometheus (use with caution):
|
||||
environment:
|
||||
- METRICS_BIND=0.0.0.0
|
||||
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 "📊 Metrics HTTP server disabled"
|
||||
echo "💡 Set ENABLE_METRICS=true to enable"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Security warning for external binding
|
||||
if [ "$METRICS_BIND" = "0.0.0.0" ]; then
|
||||
echo "⚠️ WARNING: Metrics server is bound to 0.0.0.0 (all interfaces)"
|
||||
echo "⚠️ Relay metrics may contain sensitive information!"
|
||||
echo "⚠️ Ensure proper firewall rules or use a secure network."
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Check for netcat
|
||||
if ! command -v nc > /dev/null 2>&1; then
|
||||
echo "❌ Error: netcat (nc) is required but not installed"
|
||||
if ! command -v nc >/dev/null 2>&1; then
|
||||
echo "❌ Error: netcat (nc) not found"
|
||||
echo "💡 Install with: apk add netcat-openbsd"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Connection counter for basic rate limiting
|
||||
CONNECTION_COUNT=0
|
||||
LAST_RESET=$(date +%s)
|
||||
if [ "$METRICS_BIND" = "0.0.0.0" ]; then
|
||||
echo "⚠️ WARNING: Bound to all interfaces"
|
||||
echo "⚠️ Ensure firewall rules restrict access"
|
||||
echo ""
|
||||
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
|
||||
X-Content-Type-Options: nosniff
|
||||
|
||||
$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>
|
||||
<meta charset=\"utf-8\">
|
||||
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">
|
||||
<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; }
|
||||
.warning { background: #fff3cd; border-left: 4px solid #ffc107; padding: 15px; margin: 20px 0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🧅 Tor Relay Metrics Server</h1>
|
||||
|
||||
<div class=\"warning\">
|
||||
<strong>⚠️ Security Notice:</strong> This server exposes relay metrics.
|
||||
Ensure it's only accessible from trusted networks (Prometheus, monitoring systems).
|
||||
</div>
|
||||
|
||||
<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><strong>Bind Address:</strong> $METRICS_BIND</p>
|
||||
<p><strong>Port:</strong> $METRICS_PORT</p>
|
||||
<p><strong>Version:</strong> $VERSION</p>
|
||||
<p><strong>Rate Limit:</strong> $MAX_CONNECTIONS connections/window</p>
|
||||
</div>
|
||||
|
||||
<div class=\"status\">
|
||||
<h2>Integration:</h2>
|
||||
<p>Add to your <code>prometheus.yml</code>:</p>
|
||||
<pre style=\"background: #f0f0f0; padding: 15px; border-radius: 4px; overflow-x: auto;\">
|
||||
scrape_configs:
|
||||
- job_name: 'tor-relay'
|
||||
static_configs:
|
||||
- targets: ['$METRICS_BIND:$METRICS_PORT']
|
||||
metrics_path: '$METRICS_PATH'
|
||||
scrape_interval: 30s</pre>
|
||||
</div>
|
||||
</body>
|
||||
</html>"
|
||||
|
||||
CONTENT_LENGTH=$(echo -n "$HTML_CONTENT" | wc -c)
|
||||
|
||||
cat << EOF
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: text/html; charset=utf-8
|
||||
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
|
||||
}
|
||||
|
||||
# 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 "🔒 Max connections: $MAX_CONNECTIONS/window"
|
||||
echo "🔒 Max connections: $MAX_CONNECTIONS/min"
|
||||
echo "💡 Press Ctrl+C to stop"
|
||||
echo ""
|
||||
|
||||
# Main server loop with connection limiting
|
||||
CONNECTION_COUNT=0
|
||||
LAST_RESET=$(date +%s)
|
||||
|
||||
handle_request() {
|
||||
REQ_PATH="$1"
|
||||
case "$REQ_PATH" in
|
||||
"$METRICS_PATH")
|
||||
METRICS=$(/usr/local/bin/metrics 2>/dev/null || echo "# Error generating metrics")
|
||||
printf "HTTP/1.1 200 OK\r\nContent-Type: text/plain; version=0.0.4\r\nConnection: close\r\n\r\n%s" "$METRICS"
|
||||
;;
|
||||
"/health")
|
||||
HEALTH=$(/usr/local/bin/health --json 2>/dev/null || echo '{"status":"error"}')
|
||||
printf "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nConnection: close\r\n\r\n%s" "$HEALTH"
|
||||
;;
|
||||
"/")
|
||||
HTML="<!DOCTYPE html><html><head><title>Tor Relay Metrics</title>
|
||||
<style>body{font-family:sans-serif;margin:40px;background:#f5f5f5}h1{color:#7d4698}
|
||||
.b{background:#fff;border-radius:8px;padding:20px;margin:20px 0}</style></head><body>
|
||||
<h1>🧅 Tor Relay Metrics Server</h1>
|
||||
<div class='b'><b>Metrics:</b> <a href='$METRICS_PATH'>$METRICS_PATH</a></div>
|
||||
<div class='b'><b>Health:</b> <a href='/health'>/health</a></div>
|
||||
<div class='b'><b>Version:</b> $VERSION</div>
|
||||
</body></html>"
|
||||
printf "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nConnection: close\r\n\r\n%s" "$HTML"
|
||||
;;
|
||||
*)
|
||||
printf "HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\n404 - Not Found"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
while true; do
|
||||
# Reset counter every 60 seconds
|
||||
CURRENT_TIME=$(date +%s)
|
||||
if [ $((CURRENT_TIME - LAST_RESET)) -ge 60 ]; then
|
||||
CONNECTION_COUNT=0
|
||||
LAST_RESET=$CURRENT_TIME
|
||||
fi
|
||||
|
||||
# Basic rate limiting
|
||||
if [ "$CONNECTION_COUNT" -ge "$MAX_CONNECTIONS" ]; then
|
||||
sleep 1
|
||||
continue
|
||||
fi
|
||||
|
||||
# Wait for connection and parse request
|
||||
REQUEST=$(echo "" | nc -l -p "$METRICS_PORT" -s "$METRICS_BIND" -w "$RESPONSE_TIMEOUT" 2>/dev/null | head -1)
|
||||
|
||||
if [ -n "$REQUEST" ]; then
|
||||
CURRENT=$(date +%s)
|
||||
[ $((CURRENT - LAST_RESET)) -ge 60 ] && CONNECTION_COUNT=0 && LAST_RESET=$CURRENT
|
||||
[ "$CONNECTION_COUNT" -ge "$MAX_CONNECTIONS" ] && sleep 1 && continue
|
||||
|
||||
# Read request
|
||||
REQ_LINE=$(nc -lk -p "$METRICS_PORT" -s "$METRICS_BIND" -w "$RESPONSE_TIMEOUT" | head -n 1)
|
||||
if [ -n "$REQ_LINE" ]; then
|
||||
PATH_REQ=$(echo "$REQ_LINE" | awk '{print $2}')
|
||||
CONNECTION_COUNT=$((CONNECTION_COUNT + 1))
|
||||
|
||||
# Extract path from request
|
||||
REQUEST_PATH=$(echo "$REQUEST" | awk '{print $2}')
|
||||
|
||||
# Log request (without sensitive info)
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $REQUEST_PATH ($CONNECTION_COUNT/$MAX_CONNECTIONS)"
|
||||
|
||||
# Generate and send response in background
|
||||
(generate_response "$REQUEST_PATH" | nc -l -p "$METRICS_PORT" -s "$METRICS_BIND" -w 1 > /dev/null 2>&1) &
|
||||
echo "[$(date '+%H:%M:%S')] $PATH_REQ ($CONNECTION_COUNT/$MAX_CONNECTIONS)"
|
||||
handle_request "$PATH_REQ"
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
162
tools/metrics.sh
162
tools/metrics.sh
@@ -1,10 +1,9 @@
|
||||
#!/bin/sh
|
||||
# metrics - Prometheus-compatible metrics exporter for Tor relay
|
||||
# Usage: docker exec guard-relay metrics [--help]
|
||||
# Usage: docker exec guard-relay metrics [--json|--help]
|
||||
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
VERSION="1.1.0"
|
||||
METRICS_PREFIX="${METRICS_PREFIX:-tor_relay}"
|
||||
INCLUDE_LABELS="${INCLUDE_LABELS:-true}"
|
||||
@@ -17,58 +16,28 @@ for arg in "$@"; do
|
||||
cat << EOF
|
||||
📊 Tor-Guard-Relay Metrics Exporter v${VERSION}
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
Usage:
|
||||
metrics [--prometheus|--json|--help]
|
||||
|
||||
USAGE:
|
||||
metrics [OPTIONS]
|
||||
|
||||
OPTIONS:
|
||||
Options:
|
||||
--prometheus Output in Prometheus format (default)
|
||||
--json Output metrics as JSON
|
||||
--help, -h Show this help message
|
||||
--json Output metrics as JSON
|
||||
--help, -h Show this message
|
||||
|
||||
ENVIRONMENT VARIABLES:
|
||||
METRICS_PREFIX Prefix for metric names (default: tor_relay)
|
||||
Environment:
|
||||
METRICS_PREFIX Prefix for metric names
|
||||
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
|
||||
|
||||
METRICS_FORMAT prometheus or json
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
EOF
|
||||
exit 0
|
||||
;;
|
||||
exit 0 ;;
|
||||
--prometheus) METRICS_FORMAT="prometheus" ;;
|
||||
--json) METRICS_FORMAT="json" ;;
|
||||
-*)
|
||||
echo "# ERROR: Unknown option: $arg"
|
||||
echo "# Use --help for usage information"
|
||||
exit 2
|
||||
;;
|
||||
-*) echo "# ERROR: Unknown option: $arg" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Initialize metrics
|
||||
# Initialize
|
||||
RELAY_UP=0
|
||||
BOOTSTRAP_PERCENT=0
|
||||
IS_REACHABLE=0
|
||||
@@ -78,67 +47,60 @@ WARNING_COUNT=0
|
||||
BANDWIDTH_READ=0
|
||||
BANDWIDTH_WRITE=0
|
||||
CIRCUITS_ACTIVE=0
|
||||
NICKNAME=""
|
||||
NICKNAME="unknown"
|
||||
FINGERPRINT=""
|
||||
VERSION_INFO=""
|
||||
VERSION_INFO="unknown"
|
||||
|
||||
# Get relay identity
|
||||
# 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
|
||||
# Process state
|
||||
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
|
||||
if [ -n "$PID" ] && [ -r /proc/$PID/stat ]; then
|
||||
START_TICKS=$(awk '{print $22}' /proc/$PID/stat)
|
||||
HZ=$(getconf CLK_TCK 2>/dev/null || echo 100)
|
||||
SYSTEM_UPTIME=$(awk '{print int($1)}' /proc/uptime)
|
||||
PROC_UPTIME=$((SYSTEM_UPTIME - START_TICKS / HZ))
|
||||
[ "$PROC_UPTIME" -ge 0 ] 2>/dev/null && UPTIME_SECONDS=$PROC_UPTIME || UPTIME_SECONDS=0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Parse bootstrap percentage
|
||||
# Logs
|
||||
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)
|
||||
BOOTSTRAP_PERCENT=$(echo "$BOOTSTRAP_LINE" | grep -oE '[0-9]+' | tail -1)
|
||||
BOOTSTRAP_PERCENT=$(printf '%s' "$BOOTSTRAP_PERCENT" | tr -cd '0-9')
|
||||
[ -z "$BOOTSTRAP_PERCENT" ] && BOOTSTRAP_PERCENT=0
|
||||
|
||||
grep -q "reachable from the outside" /var/log/tor/notices.log 2>/dev/null && IS_REACHABLE=1
|
||||
|
||||
ERROR_COUNT=$(grep -ciE "\[err\]|\[error\]" /var/log/tor/notices.log 2>/dev/null || echo 0)
|
||||
WARNING_COUNT=$(grep -ciE "\[warn\]|\[warning\]" /var/log/tor/notices.log 2>/dev/null || echo 0)
|
||||
fi
|
||||
|
||||
# Parse bandwidth from state file
|
||||
# Bandwidth
|
||||
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)
|
||||
BANDWIDTH_READ=$(awk '/AccountingBytesReadInterval/ {print $2}' /var/lib/tor/state | tail -1)
|
||||
BANDWIDTH_WRITE=$(awk '/AccountingBytesWrittenInterval/ {print $2}' /var/lib/tor/state | tail -1)
|
||||
BANDWIDTH_READ=${BANDWIDTH_READ:-0}
|
||||
BANDWIDTH_WRITE=${BANDWIDTH_WRITE:-0}
|
||||
fi
|
||||
|
||||
# Get version info
|
||||
# Version
|
||||
if [ -f /build-info.txt ]; then
|
||||
VERSION_INFO=$(head -1 /build-info.txt 2>/dev/null | cut -d: -f2- | tr -d ' ' || echo "unknown")
|
||||
VERSION_INFO=$(awk -F: '/Version/ {print $2}' /build-info.txt | tr -d ' ')
|
||||
VERSION_INFO=${VERSION_INFO:-unknown}
|
||||
fi
|
||||
|
||||
# Generate timestamp
|
||||
TIMESTAMP=$(date +%s)000
|
||||
# Timestamp (ms)
|
||||
TIMESTAMP=$(($(date +%s) * 1000))
|
||||
|
||||
# Output based on format
|
||||
# Output
|
||||
case "$METRICS_FORMAT" in
|
||||
json)
|
||||
cat << EOF
|
||||
@@ -163,51 +125,47 @@ case "$METRICS_FORMAT" in
|
||||
}
|
||||
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
|
||||
[ "$INCLUDE_LABELS" = "true" ] && \
|
||||
echo "${METRICS_PREFIX}_up{nickname=\"$NICKNAME\",fingerprint=\"$FINGERPRINT\"} $RELAY_UP" || \
|
||||
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 "# HELP ${METRICS_PREFIX}_errors_total Total number of errors"
|
||||
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 "# HELP ${METRICS_PREFIX}_warnings_total Total number of warnings"
|
||||
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 "# HELP ${METRICS_PREFIX}_bandwidth_read_bytes Bytes read during current interval"
|
||||
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 "# HELP ${METRICS_PREFIX}_bandwidth_write_bytes Bytes written during current interval"
|
||||
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 "# HELP ${METRICS_PREFIX}_circuits_total Active circuit count (placeholder)"
|
||||
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
|
||||
esac
|
||||
|
||||
@@ -9,11 +9,16 @@ OUTPUT_FORMAT="text"
|
||||
CHECK_IPV4="true"
|
||||
CHECK_IPV6="true"
|
||||
CHECK_DNS="true"
|
||||
CHECK_CONSENSUS="false"
|
||||
CHECK_CONSENSUS="false" # Disabled by default
|
||||
CHECK_PORTS="true"
|
||||
DNS_SERVERS="194.242.2.2 94.140.14.14 9.9.9.9"
|
||||
TEST_TIMEOUT="5"
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# Helpers
|
||||
# ──────────────────────────────────────────────
|
||||
safe() { "$@" 2>/dev/null || true; }
|
||||
|
||||
format_status() {
|
||||
case "$1" in
|
||||
ok|OK) echo "🟢 OK" ;;
|
||||
@@ -41,7 +46,11 @@ format_ip_status() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Parse arguments
|
||||
command_exists() { command -v "$1" >/dev/null 2>&1; }
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# Argument parsing
|
||||
# ──────────────────────────────────────────────
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--help|-h)
|
||||
@@ -53,14 +62,14 @@ USAGE:
|
||||
|
||||
OPTIONS:
|
||||
--json Output JSON format
|
||||
--plain Minimal output for scripts
|
||||
--plain Minimal key=value output
|
||||
--text Formatted output (default)
|
||||
--quick Skip extended tests
|
||||
--full Run all tests (default)
|
||||
--quick Skip port and consensus tests
|
||||
--full Run all checks including consensus
|
||||
--help Show this help message
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
EOF
|
||||
exit 0
|
||||
;;
|
||||
exit 0 ;;
|
||||
--json) OUTPUT_FORMAT="json" ;;
|
||||
--plain) OUTPUT_FORMAT="plain" ;;
|
||||
--text) OUTPUT_FORMAT="text" ;;
|
||||
@@ -78,7 +87,9 @@ EOF
|
||||
esac
|
||||
done
|
||||
|
||||
# Defaults
|
||||
# ──────────────────────────────────────────────
|
||||
# Initialize
|
||||
# ──────────────────────────────────────────────
|
||||
IPV4_STATUS="unknown"
|
||||
IPV6_STATUS="unknown"
|
||||
DNS_STATUS="unknown"
|
||||
@@ -89,51 +100,37 @@ PUBLIC_IP6=""
|
||||
FAILED_TESTS=0
|
||||
TOTAL_TESTS=0
|
||||
|
||||
command_exists() { command -v "$1" >/dev/null 2>&1; }
|
||||
|
||||
# IPv4 check
|
||||
# ──────────────────────────────────────────────
|
||||
# Check functions
|
||||
# ──────────────────────────────────────────────
|
||||
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
|
||||
PUBLIC_IP=$(safe curl -4 -fsS --max-time "$TEST_TIMEOUT" https://ipv4.icanhazip.com | tr -d '\r')
|
||||
[ -n "$PUBLIC_IP" ] && IPV4_STATUS="ok" || { IPV4_STATUS="failed"; FAILED_TESTS=$((FAILED_TESTS + 1)); }
|
||||
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
|
||||
PUBLIC_IP6=$(safe curl -6 -fsS --max-time "$TEST_TIMEOUT" https://ipv6.icanhazip.com | tr -d '\r')
|
||||
[ -n "$PUBLIC_IP6" ] && IPV6_STATUS="ok" || IPV6_STATUS="not_available"
|
||||
else
|
||||
IPV6_STATUS="skipped"
|
||||
fi
|
||||
}
|
||||
|
||||
# DNS resolution check
|
||||
check_dns() {
|
||||
TOTAL_TESTS=$((TOTAL_TESTS + 1))
|
||||
DNS_WORKING=false
|
||||
local 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
|
||||
if command_exists nslookup && nslookup torproject.org "$dns_server" >/dev/null 2>&1; then DNS_WORKING="true"; break; fi
|
||||
if command_exists dig && dig @"$dns_server" torproject.org +short +time="$TEST_TIMEOUT" >/dev/null 2>&1; then DNS_WORKING="true"; break; fi
|
||||
if command_exists host && host -t A torproject.org "$dns_server" >/dev/null 2>&1; then DNS_WORKING="true"; break; fi
|
||||
done
|
||||
[ "$DNS_WORKING" = "true" ] && DNS_STATUS="ok" || { DNS_STATUS="failed"; FAILED_TESTS=$((FAILED_TESTS + 1)); }
|
||||
else
|
||||
@@ -141,11 +138,10 @@ check_dns() {
|
||||
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
|
||||
if safe 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))
|
||||
@@ -155,15 +151,14 @@ check_consensus() {
|
||||
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)
|
||||
ORPORT=$(safe grep -E "^ORPort" /etc/tor/torrc | 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
|
||||
if safe nc -z -w "$TEST_TIMEOUT" "$PUBLIC_IP" "$ORPORT"; then
|
||||
PORT_STATUS="ok"
|
||||
else
|
||||
PORT_STATUS="closed"; FAILED_TESTS=$((FAILED_TESTS + 1))
|
||||
@@ -179,18 +174,27 @@ check_ports() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Run all
|
||||
# ──────────────────────────────────────────────
|
||||
# Run checks
|
||||
# ──────────────────────────────────────────────
|
||||
check_ipv4
|
||||
check_ipv6
|
||||
check_dns
|
||||
# Consensus disabled by default
|
||||
# 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')
|
||||
if [ "$TOTAL_TESTS" -eq 0 ]; then
|
||||
SUCCESS_RATE=100
|
||||
else
|
||||
SUCCESS_RATE=$((TOTAL_PASSED * 100 / TOTAL_TESTS))
|
||||
fi
|
||||
TIMESTAMP=$(date -u '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || date)
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# Output
|
||||
# ──────────────────────────────────────────────
|
||||
case "$OUTPUT_FORMAT" in
|
||||
json)
|
||||
cat <<EOF
|
||||
@@ -207,35 +211,40 @@ case "$OUTPUT_FORMAT" in
|
||||
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 "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 "ports_status=$PORT_STATUS"
|
||||
;;
|
||||
*)
|
||||
echo "🌐 Network Diagnostics v$VERSION"
|
||||
echo "🌐 Tor Relay Network Check"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
if [ "$FAILED_TESTS" -eq 0 ]; then
|
||||
echo "📊 Overall: ✅ All checks passed ($SUCCESS_RATE%)"
|
||||
echo "📊 Overall: 🟢 OK - All checks passed ($SUCCESS_RATE%)"
|
||||
elif [ "$FAILED_TESTS" -lt "$TOTAL_TESTS" ]; then
|
||||
echo "📊 Overall: ⚠️ Some issues detected ($SUCCESS_RATE% passed)"
|
||||
echo "📊 Overall: 🟡 PARTIAL - Some checks failed ($SUCCESS_RATE% passed)"
|
||||
else
|
||||
echo "📊 Overall: ❌ Multiple failures ($SUCCESS_RATE% passed)"
|
||||
echo "📊 Overall: 🔴 FAIL - All checks failed ($SUCCESS_RATE% passed)"
|
||||
fi
|
||||
echo ""
|
||||
echo "🔌 IPv4: $(format_ip_status IPv4 "$IPV4_STATUS" "$PUBLIC_IP")"
|
||||
echo "🔌 IPv6: $(format_ip_status IPv6 "$IPV6_STATUS" "$PUBLIC_IP6")"
|
||||
echo "🔍 DNS: $(format_status "$DNS_STATUS")"
|
||||
echo "📋 Consensus: $(format_status "$CONSENSUS_STATUS")"
|
||||
echo "🚪 Ports: $(format_status "$PORT_STATUS")"
|
||||
echo "🔌 Connectivity:"
|
||||
echo " IPv4: $(format_ip_status IPv4 "$IPV4_STATUS" "$PUBLIC_IP")"
|
||||
echo " IPv6: $(format_ip_status IPv6 "$IPV6_STATUS" "$PUBLIC_IP6")"
|
||||
echo ""
|
||||
echo "🕒 Tested at: $TIMESTAMP"
|
||||
echo "🧭 DNS & Consensus:"
|
||||
echo " DNS: $(format_status "$DNS_STATUS")"
|
||||
echo " Consensus: $(format_status "$CONSENSUS_STATUS")"
|
||||
echo ""
|
||||
echo "🚪 Ports:"
|
||||
echo " $(format_status "$PORT_STATUS")"
|
||||
echo ""
|
||||
echo "🕒 Checked: $TIMESTAMP"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
;;
|
||||
esac
|
||||
|
||||
458
tools/setup.sh
458
tools/setup.sh
@@ -1,121 +1,114 @@
|
||||
#!/bin/sh
|
||||
# setup - Interactive configuration wizard for Tor relay
|
||||
# Usage: setup [--auto|--help]
|
||||
# Version: 1.1.0
|
||||
# Usage: setup [--auto|--help|--json|--apply]
|
||||
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
VERSION="1.0.4"
|
||||
VERSION="1.1.0"
|
||||
CONFIG_FILE="${CONFIG_FILE:-/etc/tor/torrc}"
|
||||
RELAY_TYPE="${RELAY_TYPE:-guard}"
|
||||
AUTO_MODE="${AUTO_MODE:-false}"
|
||||
APPLY_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}"
|
||||
CREATE_BACKUP="${CREATE_BACKUP:-true}" # 🔒 NEW: Backup control
|
||||
CREATE_BACKUP="${CREATE_BACKUP:-true}"
|
||||
OUTPUT_FORMAT="${OUTPUT_FORMAT:-text}"
|
||||
|
||||
# Colors for terminal output
|
||||
# Colors
|
||||
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
|
||||
NC='\033[0m'
|
||||
|
||||
safe() { "$@" 2>/dev/null || true; }
|
||||
|
||||
# Trap for clean exit
|
||||
trap 'cleanup' INT TERM
|
||||
|
||||
cleanup() {
|
||||
echo ""
|
||||
echo -e "${YELLOW}Setup cancelled by user${NC}"
|
||||
if [ -z "$CONFIG_WRITTEN" ]; then
|
||||
echo -e "${YELLOW}Setup cancelled by user${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}Setup interrupted after configuration${NC}"
|
||||
fi
|
||||
exit 130
|
||||
}
|
||||
|
||||
# Parse arguments
|
||||
# Argument parsing
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--help|-h)
|
||||
cat << EOF
|
||||
🧙 Tor-Guard-Relay Setup Wizard v${VERSION}
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
USAGE:
|
||||
setup [OPTIONS]
|
||||
setup [--auto|--json|--apply|--type bridge|--no-backup]
|
||||
|
||||
OPTIONS:
|
||||
--auto Use defaults for all prompts
|
||||
--config FILE Config file path (default: /etc/tor/torrc)
|
||||
--apply Automatically apply /tmp config to /etc/tor/torrc
|
||||
--type TYPE Relay type: guard|exit|bridge
|
||||
--no-backup Skip backup creation (not recommended)
|
||||
--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
|
||||
CREATE_BACKUP Create backup before overwriting (default: true)
|
||||
|
||||
RELAY TYPES:
|
||||
guard Middle/Guard relay (recommended for beginners)
|
||||
exit Exit relay (requires careful consideration)
|
||||
bridge Bridge relay (helps censored users)
|
||||
|
||||
SAFETY FEATURES:
|
||||
🔒 Automatic backup creation before overwriting existing config
|
||||
🔒 Backup stored as: [config].backup.[timestamp]
|
||||
🔒 Validation before writing
|
||||
🔒 Safe cancellation with Ctrl+C
|
||||
|
||||
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
|
||||
setup --no-backup # Skip backup (not recommended)
|
||||
|
||||
OUTPUT:
|
||||
Creates a complete torrc configuration file ready
|
||||
for production use with all required settings.
|
||||
|
||||
--config FILE Custom torrc path
|
||||
--no-backup Skip backup creation
|
||||
--json Output summary as JSON
|
||||
--help, -h Show this message
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
EOF
|
||||
exit 0
|
||||
;;
|
||||
exit 0 ;;
|
||||
--auto) AUTO_MODE="true" ;;
|
||||
--config)
|
||||
shift
|
||||
CONFIG_FILE="$1"
|
||||
shift
|
||||
;;
|
||||
--type)
|
||||
shift
|
||||
RELAY_TYPE="$1"
|
||||
shift
|
||||
;;
|
||||
--apply) APPLY_MODE="true" ;;
|
||||
--type) shift; RELAY_TYPE="$1"; shift ;;
|
||||
--config) shift; CONFIG_FILE="$1"; shift ;;
|
||||
--no-backup) CREATE_BACKUP="false" ;;
|
||||
-*)
|
||||
echo "❌ Unknown option: $arg"
|
||||
echo "💡 Use --help for usage information"
|
||||
exit 2
|
||||
;;
|
||||
--json) OUTPUT_FORMAT="json" ;;
|
||||
-*) echo "❌ Unknown option: $arg"; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Helper functions
|
||||
# Validation helpers
|
||||
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; }
|
||||
|
||||
# Backup creation
|
||||
create_config_backup() {
|
||||
if [ -f "$CONFIG_FILE" ] && [ "$CREATE_BACKUP" = "true" ]; then
|
||||
BACKUP_TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
BACKUP_FILE="${CONFIG_FILE}.backup.${BACKUP_TIMESTAMP}"
|
||||
echo -e "${BLUE}📦 Creating backup...${NC}"
|
||||
if safe cp "$CONFIG_FILE" "$BACKUP_FILE"; then
|
||||
echo -e "${GREEN}✅ Backup created: $BACKUP_FILE${NC}"
|
||||
BACKUP_COUNT=$(safe ls -1 "${CONFIG_FILE}.backup."* | wc -l)
|
||||
if [ "$BACKUP_COUNT" -gt 5 ]; then
|
||||
echo -e "${YELLOW}🧹 Cleaning old backups (keeping last 5)...${NC}"
|
||||
safe ls -1t "${CONFIG_FILE}.backup."* | tail -n +6 | safe xargs rm -f
|
||||
fi
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ Could not create backup${NC}"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
restore_from_backup() {
|
||||
LATEST_BACKUP=$(safe ls -1t "${CONFIG_FILE}.backup."* | head -1)
|
||||
if [ -n "$LATEST_BACKUP" ]; then
|
||||
echo -e "${YELLOW}❌ Setup failed. Restoring backup...${NC}"
|
||||
if safe cp "$LATEST_BACKUP" "$CONFIG_FILE"; then
|
||||
echo -e "${GREEN}✅ Restored from: $LATEST_BACKUP${NC}"
|
||||
else
|
||||
echo -e "${RED}❌ Restore failed${NC}"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# UI helpers
|
||||
print_header() {
|
||||
echo ""
|
||||
echo -e "${PURPLE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
@@ -123,291 +116,222 @@ print_header() {
|
||||
echo -e "${PURPLE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
echo ""
|
||||
}
|
||||
print_step() { echo -e "${GREEN}[$1/6]${NC} $2"; }
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
# 🔒 NEW: Backup function
|
||||
create_config_backup() {
|
||||
if [ -f "$CONFIG_FILE" ] && [ "$CREATE_BACKUP" = "true" ]; then
|
||||
BACKUP_TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
BACKUP_FILE="${CONFIG_FILE}.backup.${BACKUP_TIMESTAMP}"
|
||||
|
||||
echo -e "${BLUE}📦 Creating backup...${NC}"
|
||||
|
||||
if cp "$CONFIG_FILE" "$BACKUP_FILE" 2>/dev/null; then
|
||||
echo -e "${GREEN}✅ Backup created: $BACKUP_FILE${NC}"
|
||||
|
||||
# Keep only last 5 backups
|
||||
BACKUP_COUNT=$(ls -1 "${CONFIG_FILE}.backup."* 2>/dev/null | wc -l)
|
||||
if [ "$BACKUP_COUNT" -gt 5 ]; then
|
||||
echo -e "${YELLOW}🧹 Cleaning old backups (keeping last 5)...${NC}"
|
||||
ls -1t "${CONFIG_FILE}.backup."* | tail -n +6 | xargs rm -f 2>/dev/null || true
|
||||
fi
|
||||
|
||||
return 0
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ Warning: Could not create backup${NC}"
|
||||
echo -e "${YELLOW} Proceeding anyway...${NC}"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# 🔒 NEW: Restore function (if setup fails)
|
||||
restore_from_backup() {
|
||||
LATEST_BACKUP=$(ls -1t "${CONFIG_FILE}.backup."* 2>/dev/null | head -1)
|
||||
if [ -n "$LATEST_BACKUP" ]; then
|
||||
echo ""
|
||||
echo -e "${YELLOW}❌ Setup failed. Restoring from backup...${NC}"
|
||||
if cp "$LATEST_BACKUP" "$CONFIG_FILE" 2>/dev/null; then
|
||||
echo -e "${GREEN}✅ Configuration restored from: $LATEST_BACKUP${NC}"
|
||||
else
|
||||
echo -e "${RED}❌ Failed to restore backup${NC}"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Main setup wizard
|
||||
# Header
|
||||
clear
|
||||
cat << EOF
|
||||
${PURPLE}╔══════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ 🧅 Tor-Guard-Relay Setup Wizard v${VERSION} ║
|
||||
║ ║
|
||||
║ Configure your Tor relay in 6 steps ║
|
||||
║ ║
|
||||
╚══════════════════════════════════════════════════════════╝${NC}
|
||||
echo ""
|
||||
echo -e "${PURPLE}╔══════════════════════════════════════════════════════════╗${NC}"
|
||||
echo "🧅 Tor-Guard-Relay Setup Wizard v${VERSION}"
|
||||
echo " Configure your Tor relay in 6 steps"
|
||||
echo -e "${PURPLE}╚══════════════════════════════════════════════════════════╝${NC}"
|
||||
echo "Press Ctrl+C to cancel safely at any time."
|
||||
echo ""
|
||||
|
||||
This wizard will help you configure a Tor relay with optimal
|
||||
settings for your network and preferences.
|
||||
safe mkdir -p "$(dirname "$CONFIG_FILE")"
|
||||
|
||||
Press Ctrl+C at any time to cancel safely.
|
||||
EOF
|
||||
|
||||
# Check if config exists
|
||||
# Check existing config
|
||||
if [ -f "$CONFIG_FILE" ]; then
|
||||
echo ""
|
||||
echo -e "${YELLOW}⚠️ Existing configuration found at:${NC}"
|
||||
echo -e "${YELLOW} $CONFIG_FILE${NC}"
|
||||
echo ""
|
||||
|
||||
echo -e "${YELLOW}⚠️ Existing configuration found at:${NC} $CONFIG_FILE"
|
||||
if [ "$CREATE_BACKUP" = "true" ]; then
|
||||
echo -e "${GREEN}🔒 A backup will be created before making changes.${NC}"
|
||||
echo -e "${GREEN}🔒 Backup will be created.${NC}"
|
||||
else
|
||||
echo -e "${RED}⚠️ Backup creation is disabled!${NC}"
|
||||
echo -e "${RED}⚠️ Backup disabled.${NC}"
|
||||
fi
|
||||
|
||||
if [ "$AUTO_MODE" != "true" ]; then
|
||||
echo ""
|
||||
printf "Continue and overwrite existing config? [y/N]: "
|
||||
read CONFIRM
|
||||
if [ "$CONFIRM" != "y" ] && [ "$CONFIRM" != "Y" ]; then
|
||||
echo -e "${YELLOW}Setup cancelled.${NC}"
|
||||
exit 0
|
||||
fi
|
||||
case "$CONFIRM" in
|
||||
[yY]) echo -e "${GREEN}✔ Proceeding with overwrite.${NC}" ;;
|
||||
*) echo -e "${YELLOW}Cancelled.${NC}"; exit 0 ;;
|
||||
esac
|
||||
fi
|
||||
fi
|
||||
|
||||
# Step 1: Nickname
|
||||
# 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 ""
|
||||
|
||||
print_step 1 "Choose a nickname"
|
||||
if [ "$AUTO_MODE" = "true" ]; then
|
||||
NICKNAME="$DEFAULT_NICKNAME"
|
||||
echo -e "${GREEN}✓${NC} Using default: $NICKNAME"
|
||||
echo -e "${GREEN}✓ Using default: $NICKNAME${NC}"
|
||||
else
|
||||
while true; do
|
||||
printf "Enter nickname [${DEFAULT_NICKNAME}]: "
|
||||
read NICKNAME
|
||||
[ -z "$NICKNAME" ] && NICKNAME="$DEFAULT_NICKNAME"
|
||||
|
||||
if [ -z "$NICKNAME" ]; then
|
||||
echo -e "${YELLOW}⚠ Empty input, using default: ${DEFAULT_NICKNAME}${NC}"
|
||||
NICKNAME="$DEFAULT_NICKNAME"
|
||||
fi
|
||||
if validate_nickname "$NICKNAME"; then
|
||||
echo -e "${GREEN}✓${NC} Nickname accepted: $NICKNAME"
|
||||
echo -e "${GREEN}✓ Accepted: ${NICKNAME}${NC}"
|
||||
break
|
||||
else
|
||||
echo -e "${RED}✗${NC} Invalid nickname. Please try again."
|
||||
echo -e "${RED}✗ Invalid nickname (must be alphanumeric, ≤19 chars)${NC}"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Step 2: Contact Info
|
||||
# Step 2 – Contact
|
||||
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 ""
|
||||
|
||||
print_step 2 "Provide email (public)"
|
||||
if [ "$AUTO_MODE" = "true" ]; then
|
||||
CONTACT="$DEFAULT_CONTACT"
|
||||
echo -e "${GREEN}✓${NC} Using default: $CONTACT"
|
||||
echo -e "${GREEN}✓ Using default: $CONTACT${NC}"
|
||||
else
|
||||
while true; do
|
||||
printf "Enter email [${DEFAULT_CONTACT}]: "
|
||||
read CONTACT
|
||||
[ -z "$CONTACT" ] && CONTACT="$DEFAULT_CONTACT"
|
||||
|
||||
if [ -z "$CONTACT" ]; then
|
||||
echo -e "${YELLOW}⚠ Empty input, using default: ${DEFAULT_CONTACT}${NC}"
|
||||
CONTACT="$DEFAULT_CONTACT"
|
||||
fi
|
||||
if validate_email "$CONTACT"; then
|
||||
echo -e "${GREEN}✓${NC} Contact accepted: $CONTACT"
|
||||
echo -e "${GREEN}✓ Accepted: ${CONTACT}${NC}"
|
||||
break
|
||||
else
|
||||
echo -e "${YELLOW}⚠${NC} Invalid email format, but continuing..."
|
||||
echo -e "${YELLOW}⚠ Nonstandard email format, continuing anyway.${NC}"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Step 3: Port Configuration
|
||||
# Step 3 – Ports
|
||||
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 ""
|
||||
|
||||
print_step 3 "Configure ORPort and DirPort"
|
||||
if [ "$AUTO_MODE" = "true" ]; then
|
||||
ORPORT="$DEFAULT_ORPORT"
|
||||
DIRPORT="$DEFAULT_DIRPORT"
|
||||
echo -e "${GREEN}✓${NC} Using defaults: ORPort=$ORPORT, DirPort=$DIRPORT"
|
||||
ORPORT="$DEFAULT_ORPORT"; DIRPORT="$DEFAULT_DIRPORT"
|
||||
echo -e "${GREEN}✓ Defaults: ORPort=$ORPORT DirPort=$DIRPORT${NC}"
|
||||
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
|
||||
[ -z "$ORPORT" ] && { echo -e "${YELLOW}⚠ Using default ORPort: ${DEFAULT_ORPORT}${NC}"; ORPORT="$DEFAULT_ORPORT"; }
|
||||
validate_port "$ORPORT" && break || echo -e "${RED}✗ Invalid port${NC}"
|
||||
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
|
||||
[ -z "$DIRPORT" ] && { echo -e "${YELLOW}⚠ Using default DirPort: ${DEFAULT_DIRPORT}${NC}"; DIRPORT="$DEFAULT_DIRPORT"; }
|
||||
validate_port "$DIRPORT" && break || echo -e "${RED}✗ Invalid port${NC}"
|
||||
done
|
||||
fi
|
||||
|
||||
# Step 4: Bandwidth Limits
|
||||
# Step 4 – Bandwidth
|
||||
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 ""
|
||||
|
||||
print_step 4 "Set bandwidth limit (KB/s)"
|
||||
if [ "$AUTO_MODE" = "true" ]; then
|
||||
BANDWIDTH="$DEFAULT_BANDWIDTH"
|
||||
echo -e "${GREEN}✓${NC} Using default: $BANDWIDTH KB/s"
|
||||
echo -e "${GREEN}✓ Using default: $BANDWIDTH KB/s${NC}"
|
||||
else
|
||||
while true; do
|
||||
printf "Enter bandwidth limit in KB/s [${DEFAULT_BANDWIDTH}]: "
|
||||
printf "Enter bandwidth [${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."
|
||||
if [ -z "$BANDWIDTH" ]; then
|
||||
echo -e "${YELLOW}⚠ Using default bandwidth: ${DEFAULT_BANDWIDTH}${NC}"
|
||||
BANDWIDTH="$DEFAULT_BANDWIDTH"
|
||||
fi
|
||||
validate_bandwidth "$BANDWIDTH" && break || echo -e "${RED}✗ Too low, must be ≥256 KB/s${NC}"
|
||||
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 ""
|
||||
|
||||
# Step 5 – Relay Type
|
||||
print_header "Step 5: Relay Type"
|
||||
print_step 5 "Choose relay type"
|
||||
if [ "$AUTO_MODE" = "true" ]; then
|
||||
echo -e "${GREEN}✓${NC} Using default type: $RELAY_TYPE"
|
||||
echo -e "${GREEN}✓ Using default: $RELAY_TYPE${NC}"
|
||||
else
|
||||
printf "Enter relay type [guard/exit/bridge] [${RELAY_TYPE}]: "
|
||||
printf "Enter 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"
|
||||
;;
|
||||
guard|exit|bridge) echo -e "${GREEN}✓ Type: $RELAY_TYPE${NC}" ;;
|
||||
*) echo -e "${YELLOW}⚠ Unknown type, defaulting to guard${NC}"; RELAY_TYPE="guard" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Step 6: Generate Configuration
|
||||
# Step 6 – Config Generation
|
||||
print_header "Step 6: Generating Configuration"
|
||||
print_step "6" "Creating torrc file"
|
||||
echo ""
|
||||
|
||||
# 🔒 Create backup before overwriting
|
||||
print_step 6 "Writing torrc file"
|
||||
create_config_backup
|
||||
CONFIG_WRITTEN=true
|
||||
|
||||
# Create configuration
|
||||
# Non-root fallback
|
||||
if [ "$(id -u)" != "0" ]; then
|
||||
TMP_PATH="/tmp/torrc.$(date +%s)"
|
||||
echo -e "${YELLOW}⚠️ Non-root user detected, writing config to: $TMP_PATH${NC}"
|
||||
echo -e "${YELLOW}💡 To apply it: sudo mv $TMP_PATH /etc/tor/torrc${NC}"
|
||||
CONFIG_FILE="$TMP_PATH"
|
||||
fi
|
||||
|
||||
# Write configuration
|
||||
if ! 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
|
||||
|
||||
EOF
|
||||
then
|
||||
echo -e "${RED}❌ Failed to write configuration${NC}"
|
||||
echo -e "${RED}❌ Write failed${NC}"
|
||||
restore_from_backup
|
||||
exit 1
|
||||
else
|
||||
echo -e "${GREEN}✅ Configuration saved: $CONFIG_FILE${NC}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Relay type directives
|
||||
case "$RELAY_TYPE" in
|
||||
guard) echo "ExitRelay 0" >> "$CONFIG_FILE"; echo "BridgeRelay 0" >> "$CONFIG_FILE" ;;
|
||||
exit) echo "ExitRelay 1" >> "$CONFIG_FILE"; echo "BridgeRelay 0" >> "$CONFIG_FILE" ;;
|
||||
bridge) echo "BridgeRelay 1" >> "$CONFIG_FILE"; echo "ExitRelay 0" >> "$CONFIG_FILE" ;;
|
||||
esac
|
||||
|
||||
cat <<EOF >> "$CONFIG_FILE"
|
||||
RunAsDaemon 1
|
||||
SocksPort 0
|
||||
ControlPort 9051
|
||||
DataDirectory /var/lib/tor
|
||||
EOF
|
||||
|
||||
# Auto-apply if root and --apply used
|
||||
if [ "$APPLY_MODE" = "true" ] && [ "$(id -u)" = "0" ] && [ "$CONFIG_FILE" != "/etc/tor/torrc" ]; then
|
||||
echo -e "${BLUE}🔧 Applying generated config to /etc/tor/torrc...${NC}"
|
||||
mv "$CONFIG_FILE" /etc/tor/torrc && echo -e "${GREEN}✅ Applied successfully.${NC}" || echo -e "${RED}❌ Apply failed.${NC}"
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✅ Configuration saved: $CONFIG_FILE${NC}"
|
||||
|
||||
# Summary
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "📄 Configuration Summary"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "📁 File: $CONFIG_FILE"
|
||||
echo "🧅 Nickname: $NICKNAME"
|
||||
echo "📧 Contact: $CONTACT"
|
||||
echo "🔌 ORPort: $ORPORT"
|
||||
echo "📡 DirPort: $DIRPORT"
|
||||
echo "📈 Bandwidth: $BANDWIDTH KB/s"
|
||||
echo "🏷️ Type: $RELAY_TYPE"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "✅ Setup complete. You can now restart your relay."
|
||||
echo ""
|
||||
|
||||
if [ "$OUTPUT_FORMAT" = "json" ]; then
|
||||
cat <<EOF
|
||||
{
|
||||
"nickname": "$NICKNAME",
|
||||
"contact": "$CONTACT",
|
||||
"orport": "$ORPORT",
|
||||
"dirport": "$DIRPORT",
|
||||
"bandwidth": "$BANDWIDTH",
|
||||
"relay_type": "$RELAY_TYPE",
|
||||
"config_file": "$CONFIG_FILE"
|
||||
}
|
||||
EOF
|
||||
fi
|
||||
|
||||
564
tools/status.sh
564
tools/status.sh
@@ -1,300 +1,462 @@
|
||||
#!/bin/sh
|
||||
# status - Comprehensive relay status dashboard
|
||||
# Usage: docker exec guard-relay status [--json|--help]
|
||||
# status - Tor Guard Relay status dashboard
|
||||
# Usage: docker exec TorGuardRelay status [--short|--json|--plain|--quick|--full|--help]
|
||||
|
||||
set -e
|
||||
set -eu
|
||||
|
||||
# Configuration
|
||||
VERSION="1.0.9"
|
||||
OUTPUT_FORMAT="${OUTPUT_FORMAT:-text}"
|
||||
SHOW_ALL="${SHOW_ALL:-true}"
|
||||
CHECK_NETWORK="${CHECK_NETWORK:-true}"
|
||||
VERSION="1.1.0"
|
||||
OUTPUT_FORMAT=""
|
||||
CHECK_NETWORK="true"
|
||||
|
||||
format_status() {
|
||||
case "$1" in
|
||||
ok|OK) echo "🟢 OK" ;;
|
||||
failed|closed|error|FAIL|not_available) echo "🔴 FAIL" ;;
|
||||
skipped|unknown) echo "⏭️ SKIPPED" ;;
|
||||
*) echo "$1" ;;
|
||||
esac
|
||||
}
|
||||
# ───────────────────────────
|
||||
# Helper Functions
|
||||
# ───────────────────────────
|
||||
is_integer() { case "$1" in ''|*[!0-9]*) return 1 ;; *) return 0 ;; esac; }
|
||||
sanitize_num() { v=$(printf '%s' "$1" | tr -cd '0-9'); [ -z "$v" ] && v=0; printf '%s' "$v"; }
|
||||
format_ip_status() { [ -n "$2" ] && printf '🟢 %s' "$2" || printf '🔴 No %s connectivity' "$1"; }
|
||||
separator() { printf '%s\n' "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"; }
|
||||
|
||||
format_ip_status() {
|
||||
local type="$1"
|
||||
local value="$2"
|
||||
if [ -n "$value" ]; then
|
||||
echo "🟢 OK ($value)"
|
||||
# Convert seconds → human-readable Dd Hh Mm
|
||||
secs_to_human() {
|
||||
s=${1:-0}
|
||||
days=$((s / 86400))
|
||||
hours=$(( (s % 86400) / 3600 ))
|
||||
mins=$(( (s % 3600) / 60 ))
|
||||
|
||||
if [ "$days" -gt 0 ]; then
|
||||
printf '%dd %dh %dm' "$days" "$hours" "$mins"
|
||||
elif [ "$hours" -gt 0 ]; then
|
||||
printf '%dh %dm' "$hours" "$mins"
|
||||
else
|
||||
echo "🔴 No ${type} connectivity"
|
||||
printf '%dm' "$mins"
|
||||
fi
|
||||
}
|
||||
|
||||
# Safe integer check
|
||||
is_integer() {
|
||||
case "$1" in
|
||||
''|*[!0-9]*) return 1 ;;
|
||||
*) return 0 ;;
|
||||
# Convert ps etime ("2-14:30:00") → seconds
|
||||
etime_to_seconds() {
|
||||
raw=${1:-}
|
||||
[ -z "$raw" ] && { printf '%d' 0; return; }
|
||||
|
||||
days=0; hh=0; mm=0; ss=0
|
||||
|
||||
# Handle format with days (DD-HH:MM:SS or DD-HH:MM)
|
||||
case "$raw" in
|
||||
*-*)
|
||||
days=${raw%%-*}
|
||||
raw=${raw#*-}
|
||||
;;
|
||||
esac
|
||||
|
||||
# Parse the time part
|
||||
IFS=:; set -- $raw
|
||||
case $# in
|
||||
3) hh=$1; mm=$2; ss=$3 ;;
|
||||
2) hh=$1; mm=$2 ;;
|
||||
1) ss=$1 ;;
|
||||
esac
|
||||
|
||||
# Default to 0 if any value is empty
|
||||
hh=${hh:-0}; mm=${mm:-0}; ss=${ss:-0}; days=${days:-0}
|
||||
|
||||
# Calculate total seconds
|
||||
printf '%d' $((days*86400 + hh*3600 + mm*60 + ss))
|
||||
}
|
||||
|
||||
# Get public IP with multiple fallback methods
|
||||
get_public_ip() {
|
||||
ip_type=$1 # "ipv4" or "ipv6"
|
||||
ip=""
|
||||
|
||||
# Try multiple services in order of preference
|
||||
if [ "$ip_type" = "ipv4" ]; then
|
||||
# Try curl with multiple services
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
ip=$(curl -4 -s --max-time 5 --connect-timeout 3 https://api.ipify.org 2>/dev/null || true)
|
||||
[ -z "$ip" ] && ip=$(curl -4 -s --max-time 5 --connect-timeout 3 https://ipinfo.io/ip 2>/dev/null || true)
|
||||
[ -z "$ip" ] && ip=$(curl -4 -s --max-time 5 --connect-timeout 3 https://ipv4.icanhazip.com 2>/dev/null || true)
|
||||
# Fallback to wget
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
ip=$(wget -4 -q -O - --timeout=5 https://api.ipify.org 2>/dev/null || true)
|
||||
[ -z "$ip" ] && ip=$(wget -4 -q -O - --timeout=5 https://ipinfo.io/ip 2>/dev/null || true)
|
||||
[ -z "$ip" ] && ip=$(wget -4 -q -O - --timeout=5 https://ipv4.icanhazip.com 2>/dev/null || true)
|
||||
fi
|
||||
|
||||
# Validate IPv4 format
|
||||
if printf '%s' "$ip" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'; then
|
||||
printf '%s' "$ip"
|
||||
fi
|
||||
else # IPv6
|
||||
# Try curl with multiple services
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
ip=$(curl -6 -s --max-time 5 --connect-timeout 3 https://api6.ipify.org 2>/dev/null || true)
|
||||
[ -z "$ip" ] && ip=$(curl -6 -s --max-time 5 --connect-timeout 3 https://ipv6.icanhazip.com 2>/dev/null || true)
|
||||
# Fallback to wget
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
ip=$(wget -6 -q -O - --timeout=5 https://api6.ipify.org 2>/dev/null || true)
|
||||
[ -z "$ip" ] && ip=$(wget -6 -q -O - --timeout=5 https://ipv6.icanhazip.com 2>/dev/null || true)
|
||||
fi
|
||||
|
||||
# Basic IPv6 validation (simplified)
|
||||
if printf '%s' "$ip" | grep -Eq '^[0-9a-fA-F:]+$'; then
|
||||
printf '%s' "$ip"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Check if port is open
|
||||
check_port() {
|
||||
host=$1
|
||||
port=$2
|
||||
timeout=$3
|
||||
|
||||
if command -v nc >/dev/null 2>&1; then
|
||||
# Use netcat if available
|
||||
if nc -z -w "$timeout" "$host" "$port" 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
elif command -v timeout >/dev/null 2>&1 && command -v bash >/dev/null 2>&1; then
|
||||
# Use timeout with bash's built-in TCP feature
|
||||
if timeout "$timeout" bash -c "echo >/dev/tcp/$host/$port" 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# ───────────────────────────
|
||||
# Parse arguments
|
||||
# ───────────────────────────
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--help|-h)
|
||||
cat << EOF
|
||||
cat <<EOF
|
||||
🧅 Tor-Guard-Relay Status Dashboard v${VERSION}
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
USAGE:
|
||||
status [OPTIONS]
|
||||
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)
|
||||
|
||||
--short Compact summary output
|
||||
--json JSON format
|
||||
--plain Key=value format
|
||||
--quick Skip network checks
|
||||
--full Detailed dashboard (default)
|
||||
--help, -h Show this message
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
EOF
|
||||
exit 0
|
||||
;;
|
||||
exit 0 ;;
|
||||
--short) OUTPUT_FORMAT="short" ;;
|
||||
--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
|
||||
--full) OUTPUT_FORMAT="text" ;;
|
||||
*)
|
||||
if [ "${arg#-}" != "$arg" ]; then
|
||||
printf '❌ Unknown option: %s\n' "$arg" >&2
|
||||
printf '💡 Use --help for usage information\n' >&2
|
||||
exit 2
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
[ -z "${OUTPUT_FORMAT}" ] && OUTPUT_FORMAT="text"
|
||||
|
||||
# Gather all status information
|
||||
# ───────────────────────────
|
||||
# Gather status info
|
||||
# ───────────────────────────
|
||||
gather_status() {
|
||||
IS_RUNNING="false"
|
||||
PID=""
|
||||
TOR_UPTIME_SECONDS=0
|
||||
CONTAINER_UPTIME_SECONDS=0
|
||||
TOR_UPTIME="0m"
|
||||
CONTAINER_UPTIME="0m"
|
||||
|
||||
# Tor process uptime
|
||||
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")
|
||||
PID=$(pgrep -x tor | head -n1)
|
||||
TOR_RAW=$(ps -o etime= -p "$PID" 2>/dev/null | awk '{$1=$1};1' || true)
|
||||
if [ -n "$TOR_RAW" ]; then
|
||||
TOR_UPTIME_SECONDS=$(etime_to_seconds "$TOR_RAW")
|
||||
TOR_UPTIME=$(secs_to_human "$TOR_UPTIME_SECONDS")
|
||||
fi
|
||||
fi
|
||||
|
||||
# Container uptime
|
||||
if [ -r /proc/1/uptime ]; then
|
||||
sec=$(awk '{print int($1)}' /proc/1/uptime 2>/dev/null || echo 0)
|
||||
CONTAINER_UPTIME_SECONDS=$sec
|
||||
CONTAINER_UPTIME=$(secs_to_human "$sec")
|
||||
fi
|
||||
|
||||
# Always display the most relevant uptime in a clean one-line format
|
||||
if [ "$IS_RUNNING" = "true" ] && [ "$TOR_UPTIME_SECONDS" -gt 0 ]; then
|
||||
UPTIME_DISPLAY="${TOR_UPTIME}"
|
||||
UPTIME_SOURCE="Tor process"
|
||||
else
|
||||
UPTIME_DISPLAY="${CONTAINER_UPTIME}"
|
||||
UPTIME_SOURCE="Container"
|
||||
fi
|
||||
|
||||
# Bootstrap progress
|
||||
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 || true)
|
||||
if [ -n "$BOOTSTRAP_LINE" ]; then
|
||||
# Extract clean integer only
|
||||
BOOTSTRAP_PERCENT=$(echo "$BOOTSTRAP_LINE" | grep -oE '[0-9]+' | tail -1 | tr -d '\r' || echo 0)
|
||||
BOOTSTRAP_PERCENT=${BOOTSTRAP_PERCENT:-0}
|
||||
BOOTSTRAP_MESSAGE=$(echo "$BOOTSTRAP_LINE" | sed 's/.*Bootstrapped [0-9]*%[: ]*//')
|
||||
fi
|
||||
BOOTSTRAP_LINE=$(grep "Bootstrapped" /var/log/tor/notices.log 2>/dev/null | tail -n1 || true)
|
||||
[ -n "$BOOTSTRAP_LINE" ] && BOOTSTRAP_PERCENT=$(sanitize_num "$(printf '%s' "$BOOTSTRAP_LINE" | grep -oE '[0-9]+' | tail -n1)")
|
||||
fi
|
||||
|
||||
# Reachability
|
||||
IS_REACHABLE="false"
|
||||
REACHABILITY_MESSAGE=""
|
||||
REACHABILITY_STATUS="Unknown"
|
||||
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 || true)
|
||||
if echo "$REACHABLE_LINE" | grep -q "reachable from the outside" 2>/dev/null; then
|
||||
if grep -q "reachable from the outside" /var/log/tor/notices.log 2>/dev/null; then
|
||||
IS_REACHABLE="true"
|
||||
REACHABILITY_MESSAGE="ORPort is reachable from the outside"
|
||||
elif [ -n "$REACHABLE_LINE" ]; then
|
||||
REACHABILITY_MESSAGE=$(echo "$REACHABLE_LINE" | sed 's/.*] //')
|
||||
REACHABILITY_STATUS="Reachable"
|
||||
else
|
||||
REACHABILITY_STATUS="Not reachable"
|
||||
fi
|
||||
fi
|
||||
|
||||
NICKNAME=""
|
||||
FINGERPRINT=""
|
||||
# Relay identity
|
||||
NICKNAME="unknown"
|
||||
FINGERPRINT="N/A"
|
||||
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)
|
||||
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 "N/A")
|
||||
fi
|
||||
|
||||
ORPORT=""
|
||||
DIRPORT=""
|
||||
EXIT_RELAY="false"
|
||||
BRIDGE_RELAY="false"
|
||||
BANDWIDTH_RATE=""
|
||||
# Config
|
||||
ORPORT="N/A"
|
||||
DIRPORT="N/A"
|
||||
RELAY_TYPE="🔒 Guard/Middle Relay"
|
||||
CONTACT_INFO="N/A"
|
||||
|
||||
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}')
|
||||
ORPORT=$(grep -E "^ORPort" /etc/tor/torrc 2>/dev/null | awk '{print $2}' | head -n1 || echo "N/A")
|
||||
DIRPORT=$(grep -E "^DirPort" /etc/tor/torrc 2>/dev/null | awk '{print $2}' | head -n1 || echo "N/A")
|
||||
CONTACT_INFO=$(grep -E "^ContactInfo" /etc/tor/torrc 2>/dev/null | cut -d' ' -f2- | head -n1 || echo "N/A")
|
||||
|
||||
if grep -qE "^ExitRelay\s+1" /etc/tor/torrc 2>/dev/null; then
|
||||
RELAY_TYPE="🚪 Exit Relay"
|
||||
elif grep -qE "^BridgeRelay\s+1" /etc/tor/torrc 2>/dev/null; then
|
||||
RELAY_TYPE="🌉 Bridge Relay"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Logs
|
||||
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)
|
||||
ERROR_COUNT=$(sanitize_num "$(grep -cE '\[err\]|\[error\]' /var/log/tor/notices.log 2>/dev/null || echo 0)")
|
||||
WARNING_COUNT=$(sanitize_num "$(grep -cE '\[warn\]|\[warning\]' /var/log/tor/notices.log 2>/dev/null || echo 0)")
|
||||
fi
|
||||
|
||||
# Version and build info from build-info.txt
|
||||
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
|
||||
|
||||
# Fallback if build-info.txt doesn't exist or doesn't contain expected info
|
||||
if [ -z "$VERSION_INFO" ]; then
|
||||
if command -v tor >/dev/null 2>&1; then
|
||||
VERSION_INFO=$(tor --version 2>/dev/null | awk 'NR==1{for(i=1;i<=NF;i++) if ($i ~ /[0-9]+\.[0-9]+/) {print $i; exit}}' || echo "unknown")
|
||||
else
|
||||
VERSION_INFO="unknown"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$BUILD_TIME" ]; then
|
||||
BUILD_TIME=$(date '+%Y-%m-%d' 2>/dev/null || echo "unknown")
|
||||
fi
|
||||
|
||||
ARCH=$(uname -m 2>/dev/null || echo "unknown")
|
||||
|
||||
# Format build info as requested
|
||||
BUILD_INFO="v${VERSION_INFO} (${BUILD_TIME}, ${ARCH})"
|
||||
|
||||
# IPv4/IPv6 detection with improved reliability
|
||||
PUBLIC_IP=""
|
||||
PUBLIC_IP6=""
|
||||
if [ "$CHECK_NETWORK" = "true" ] && command -v curl >/dev/null 2>&1; then
|
||||
PUBLIC_IP=$(curl -4 -s --max-time 5 https://ipv4.icanhazip.com 2>/dev/null | tr -d '\r')
|
||||
PUBLIC_IP6=$(curl -6 -s --max-time 5 https://ipv6.icanhazip.com 2>/dev/null | tr -d '\r')
|
||||
IPV4_OK="false"
|
||||
IPV6_OK="false"
|
||||
ORPORT_OPEN="false"
|
||||
DIRPORT_OPEN="false"
|
||||
|
||||
if [ "$CHECK_NETWORK" = "true" ]; then
|
||||
# Get IPv4
|
||||
ip4=$(get_public_ip "ipv4")
|
||||
if [ -n "$ip4" ]; then
|
||||
PUBLIC_IP="$ip4"
|
||||
IPV4_OK="true"
|
||||
|
||||
# Check if ORPort is open (only if it's not the default 0)
|
||||
if [ "$ORPORT" != "N/A" ] && [ "$ORPORT" != "0" ]; then
|
||||
if check_port "$PUBLIC_IP" "$ORPORT" 3; then
|
||||
ORPORT_OPEN="true"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check if DirPort is open (only if it's not the default 0)
|
||||
if [ "$DIRPORT" != "N/A" ] && [ "$DIRPORT" != "0" ]; then
|
||||
if check_port "$PUBLIC_IP" "$DIRPORT" 3; then
|
||||
DIRPORT_OPEN="true"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Get IPv6
|
||||
ip6=$(get_public_ip "ipv6")
|
||||
if [ -n "$ip6" ]; then
|
||||
PUBLIC_IP6="$ip6"
|
||||
IPV6_OK="true"
|
||||
fi
|
||||
fi
|
||||
|
||||
TIMESTAMP=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
|
||||
}
|
||||
|
||||
# ───────────────────────────
|
||||
# Gather data
|
||||
# ───────────────────────────
|
||||
gather_status
|
||||
|
||||
# Sanitize percent and timestamp
|
||||
BOOTSTRAP_PERCENT=$(echo "$BOOTSTRAP_PERCENT" | tr -cd '0-9')
|
||||
BOOTSTRAP_PERCENT=${BOOTSTRAP_PERCENT:-0}
|
||||
TIMESTAMP=$(date -u '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || date '+%Y-%m-%d %H:%M:%S')
|
||||
|
||||
# Determine overall status safely
|
||||
if [ "$IS_RUNNING" = "false" ]; then
|
||||
# Determine status
|
||||
if [ "${IS_RUNNING}" = "false" ]; then
|
||||
OVERALL_STATUS="down"
|
||||
elif is_integer "$BOOTSTRAP_PERCENT" && [ "$BOOTSTRAP_PERCENT" -eq 100 ] && [ "$IS_REACHABLE" = "true" ]; then
|
||||
elif [ "$BOOTSTRAP_PERCENT" -eq 100 ] && [ "${IS_REACHABLE}" = "true" ]; then
|
||||
OVERALL_STATUS="healthy"
|
||||
elif is_integer "$BOOTSTRAP_PERCENT" && [ "$BOOTSTRAP_PERCENT" -eq 100 ]; then
|
||||
elif [ "$BOOTSTRAP_PERCENT" -eq 100 ]; then
|
||||
OVERALL_STATUS="running"
|
||||
elif is_integer "$BOOTSTRAP_PERCENT" && [ "$BOOTSTRAP_PERCENT" -gt 0 ]; then
|
||||
elif [ "$BOOTSTRAP_PERCENT" -gt 0 ]; then
|
||||
OVERALL_STATUS="starting"
|
||||
else
|
||||
OVERALL_STATUS="unknown"
|
||||
fi
|
||||
|
||||
# ───────────────────────────
|
||||
# Output
|
||||
# ───────────────────────────
|
||||
case "$OUTPUT_FORMAT" in
|
||||
short)
|
||||
printf '🧅 Tor Relay Status Summary\n'
|
||||
separator
|
||||
printf '📦 Build: %s\n' "${BUILD_INFO}"
|
||||
[ "$BOOTSTRAP_PERCENT" -eq 100 ] && printf '🚀 Bootstrap: ✅ 100%% Complete\n' || printf '🚀 Bootstrap: %s%%\n' "$BOOTSTRAP_PERCENT"
|
||||
[ "${IS_REACHABLE}" = "true" ] && printf '🌐 Reachable: ✅ %s\n' "${REACHABILITY_STATUS}" || printf '🌐 Reachable: ❌ %s\n' "${REACHABILITY_STATUS}"
|
||||
printf '📊 Uptime: %s (%s)\n' "${UPTIME_DISPLAY}" "${UPTIME_SOURCE}"
|
||||
printf '🔑 %s (%s)\n' "${NICKNAME}" "${FINGERPRINT}"
|
||||
printf '🔌 ORPort: %s | DirPort: %s\n' "${ORPORT}" "${DIRPORT}"
|
||||
printf '⚙️ Type: %s\n' "${RELAY_TYPE}"
|
||||
printf '⚠️ Errors: %02d | Warnings: %d\n' "${ERROR_COUNT}" "${WARNING_COUNT}"
|
||||
printf '🕒 %s\n\n' "${TIMESTAMP}"
|
||||
;;
|
||||
|
||||
json)
|
||||
cat << EOF
|
||||
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": { "ipv4": "$PUBLIC_IP", "ipv6": "$PUBLIC_IP6" },
|
||||
"issues": { "errors": $ERROR_COUNT, "warnings": $WARNING_COUNT },
|
||||
"version": { "software": "$VERSION_INFO", "build_time": "$BUILD_TIME" }
|
||||
"timestamp": "${TIMESTAMP}",
|
||||
"status": "${OVERALL_STATUS}",
|
||||
"uptime": "${UPTIME_DISPLAY}",
|
||||
"uptime_source": "${UPTIME_SOURCE}",
|
||||
"bootstrap": ${BOOTSTRAP_PERCENT},
|
||||
"reachable": "${IS_REACHABLE}",
|
||||
"reachability_status": "${REACHABILITY_STATUS}",
|
||||
"ipv4": "${PUBLIC_IP}",
|
||||
"ipv6": "${PUBLIC_IP6}",
|
||||
"orport": "${ORPORT}",
|
||||
"dirport": "${DIRPORT}",
|
||||
"orport_open": "${ORPORT_OPEN}",
|
||||
"dirport_open": "${DIRPORT_OPEN}",
|
||||
"nickname": "${NICKNAME}",
|
||||
"fingerprint": "${FINGERPRINT}",
|
||||
"relay_type": "${RELAY_TYPE}",
|
||||
"contact_info": "${CONTACT_INFO}",
|
||||
"errors": ${ERROR_COUNT},
|
||||
"warnings": ${WARNING_COUNT},
|
||||
"version": "${VERSION_INFO}",
|
||||
"build_time": "${BUILD_TIME}",
|
||||
"arch": "${ARCH}",
|
||||
"build_info": "${BUILD_INFO}"
|
||||
}
|
||||
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"
|
||||
echo "PUBLIC_IP6=$PUBLIC_IP6"
|
||||
printf 'timestamp=%s\n' "${TIMESTAMP}"
|
||||
printf 'status=%s\n' "${OVERALL_STATUS}"
|
||||
printf 'uptime=%s\n' "${UPTIME_DISPLAY}"
|
||||
printf 'uptime_source=%s\n' "${UPTIME_SOURCE}"
|
||||
printf 'bootstrap=%d\n' "${BOOTSTRAP_PERCENT}"
|
||||
printf 'reachable=%s\n' "${IS_REACHABLE}"
|
||||
printf 'reachability_status=%s\n' "${REACHABILITY_STATUS}"
|
||||
printf 'ipv4=%s\n' "${PUBLIC_IP}"
|
||||
printf 'ipv6=%s\n' "${PUBLIC_IP6}"
|
||||
printf 'orport=%s\n' "${ORPORT}"
|
||||
printf 'dirport=%s\n' "${DIRPORT}"
|
||||
printf 'orport_open=%s\n' "${ORPORT_OPEN}"
|
||||
printf 'dirport_open=%s\n' "${DIRPORT_OPEN}"
|
||||
printf 'nickname=%s\n' "${NICKNAME}"
|
||||
printf 'fingerprint=%s\n' "${FINGERPRINT}"
|
||||
printf 'relay_type=%s\n' "${RELAY_TYPE}"
|
||||
printf 'contact_info=%s\n' "${CONTACT_INFO}"
|
||||
printf 'errors=%d\n' "${ERROR_COUNT}"
|
||||
printf 'warnings=%d\n' "${WARNING_COUNT}"
|
||||
printf 'version=%s\n' "${VERSION_INFO}"
|
||||
printf 'build_time=%s\n' "${BUILD_TIME}"
|
||||
printf 'arch=%s\n' "${ARCH}"
|
||||
printf 'build_info=%s\n' "${BUILD_INFO}"
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "🧅 Tor Relay Status Report"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
printf '🧅 Tor Relay Status Report\n'
|
||||
separator
|
||||
printf '\n⭐ Overall Status: '
|
||||
case "$OVERALL_STATUS" in
|
||||
healthy) echo "⭐ Overall Status: 🟢 OK - 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: 🔴 FAIL - Tor process not running" ;;
|
||||
*) echo "⭐ Overall Status: ❓ UNKNOWN" ;;
|
||||
healthy) printf '🟢 OK - Relay is fully operational\n' ;;
|
||||
running) printf '🟡 RUNNING - Awaiting reachability confirmation\n' ;;
|
||||
starting) printf '🔄 STARTING - Bootstrap in progress (%s%%)\n' "$BOOTSTRAP_PERCENT" ;;
|
||||
down) printf '🔴 FAIL - Tor process not running\n' ;;
|
||||
*) printf '❓ UNKNOWN\n' ;;
|
||||
esac
|
||||
echo ""
|
||||
|
||||
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 ""
|
||||
printf '\n📦 Build: %s\n' "${BUILD_INFO}"
|
||||
|
||||
printf '\n🚀 Bootstrap Progress:\n'
|
||||
[ "$BOOTSTRAP_PERCENT" -eq 100 ] && printf ' ✅ 100%% Complete\n' || printf ' 🔄 %s%%\n' "$BOOTSTRAP_PERCENT"
|
||||
|
||||
printf '\n🌐 Network Status:\n'
|
||||
[ "${IS_REACHABLE}" = "true" ] && printf ' 🟢 Reachable: Yes - Relay can accept connections\n' || printf ' 🔴 Reachable: No - Relay may be behind firewall/NAT\n'
|
||||
printf ' IPv4: %s\n' "$(format_ip_status IPv4 "$PUBLIC_IP")"
|
||||
printf ' IPv6: %s\n' "$(format_ip_status IPv6 "$PUBLIC_IP6")"
|
||||
|
||||
if [ "$ORPORT" != "N/A" ] && [ "$ORPORT" != "0" ]; then
|
||||
[ "$ORPORT_OPEN" = "true" ] && printf ' 🟢 ORPort %s: Open\n' "$ORPORT" || printf ' 🔴 ORPort %s: Closed or filtered\n' "$ORPORT"
|
||||
fi
|
||||
|
||||
if [ "$DIRPORT" != "N/A" ] && [ "$DIRPORT" != "0" ]; then
|
||||
[ "$DIRPORT_OPEN" = "true" ] && printf ' 🟢 DirPort %s: Open\n' "$DIRPORT" || printf ' 🔴 DirPort %s: Closed or filtered\n' "$DIRPORT"
|
||||
fi
|
||||
|
||||
echo "🚀 Bootstrap Progress:"
|
||||
if is_integer "$BOOTSTRAP_PERCENT" && [ "$BOOTSTRAP_PERCENT" -eq 100 ]; then
|
||||
echo " 🟢 OK - Fully bootstrapped (100%)"
|
||||
[ -n "$BOOTSTRAP_MESSAGE" ] && echo " Status: $BOOTSTRAP_MESSAGE"
|
||||
elif is_integer "$BOOTSTRAP_PERCENT" && [ "$BOOTSTRAP_PERCENT" -gt 0 ]; then
|
||||
echo " 🔄 Bootstrapping: $BOOTSTRAP_PERCENT%"
|
||||
[ -n "$BOOTSTRAP_MESSAGE" ] && echo " Status: $BOOTSTRAP_MESSAGE"
|
||||
else
|
||||
echo " ⏳ Not started yet"
|
||||
fi
|
||||
echo ""
|
||||
printf '\n🔑 Relay Identity:\n'
|
||||
printf ' 📝 Nickname: %s\n' "${NICKNAME}"
|
||||
printf ' 🆔 Fingerprint: %s\n' "${FINGERPRINT}"
|
||||
printf ' 📧 Contact: %s\n' "${CONTACT_INFO}"
|
||||
|
||||
echo "🌍 Reachability:"
|
||||
if [ "$IS_REACHABLE" = "true" ]; then
|
||||
echo " 🌐 Reachability: 🟢 OK"
|
||||
elif [ -n "$REACHABILITY_MESSAGE" ]; then
|
||||
echo " 🌐 Reachability: 🔴 $REACHABILITY_MESSAGE"
|
||||
else
|
||||
echo " 🌐 Reachability: ⏳ Pending"
|
||||
fi
|
||||
echo ""
|
||||
printf '\n📊 Uptime:\n'
|
||||
printf ' %s (%s)\n' "${UPTIME_DISPLAY}" "${UPTIME_SOURCE}"
|
||||
|
||||
if [ -n "$NICKNAME" ] || [ -n "$FINGERPRINT" ]; then
|
||||
echo "🔑 Relay Identity:"
|
||||
[ -n "$NICKNAME" ] && echo " 📝 Nickname: $NICKNAME"
|
||||
[ -n "$FINGERPRINT" ] && echo " 🆔 Fingerprint: $FINGERPRINT"
|
||||
echo ""
|
||||
fi
|
||||
printf '\n🔌 Configuration:\n'
|
||||
printf ' ORPort: %s | DirPort: %s\n' "${ORPORT}" "${DIRPORT}"
|
||||
printf ' Type: %s\n' "${RELAY_TYPE}"
|
||||
|
||||
echo "🔌 Network Configuration:"
|
||||
[ -n "$PUBLIC_IP" ] && echo " IPv4: $(format_ip_status IPv4 "$PUBLIC_IP")" || echo " IPv4: 🔴 No IPv4 connectivity"
|
||||
[ -n "$PUBLIC_IP6" ] && echo " IPv6: $(format_ip_status IPv6 "$PUBLIC_IP6")" || echo " IPv6: 🔴 No IPv6 connectivity"
|
||||
[ -n "$ORPORT" ] && echo " ORPort: $ORPORT" || echo " ORPort: 🔴 Not configured"
|
||||
[ -n "$DIRPORT" ] && echo " DirPort: $DIRPORT" || echo " DirPort: 🔴 Not configured"
|
||||
[ -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 ""
|
||||
printf '\n⚠️ Errors: %d | Warnings: %d\n' "${ERROR_COUNT}" "${WARNING_COUNT}"
|
||||
|
||||
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"
|
||||
printf '\n'
|
||||
separator
|
||||
printf '🕒 Last updated: %s\n' "${TIMESTAMP}"
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -1,202 +1,183 @@
|
||||
#!/bin/sh
|
||||
# view-logs - Advanced log viewer with filtering and analysis
|
||||
# view-logs - Advanced Tor relay log viewer with filtering and analysis
|
||||
# Usage: docker exec guard-relay view-logs [--follow|--errors|--help]
|
||||
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
VERSION="1.0.9"
|
||||
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}"
|
||||
FOLLOW_MODE="false"
|
||||
FILTER_MODE="all"
|
||||
OUTPUT_FORMAT="text"
|
||||
COLOR_OUTPUT="true"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
# Force color by default
|
||||
FORCE_COLOR="${FORCE_COLOR:-true}"
|
||||
[ "$FORCE_COLOR" = "true" ] && COLOR_OUTPUT="true"
|
||||
|
||||
# Parse arguments
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
# Colours
|
||||
ESC="$(printf '\033')"
|
||||
RED="${ESC}[0;31m"
|
||||
YELLOW="${ESC}[1;33m"
|
||||
GREEN="${ESC}[0;32m"
|
||||
BLUE="${ESC}[0;34m"
|
||||
CYAN="${ESC}[0;36m"
|
||||
MAGENTA="${ESC}[0;35m"
|
||||
BOLD="${ESC}[1m"
|
||||
NC="${ESC}[0m"
|
||||
|
||||
is_integer() {
|
||||
case "$1" in
|
||||
''|*[!0-9]*) return 1 ;;
|
||||
*) return 0 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Argument parsing
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--help|-h)
|
||||
cat << EOF
|
||||
📜 Tor-Guard-Relay Log Viewer v${VERSION}
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
USAGE:
|
||||
view-logs [OPTIONS]
|
||||
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
|
||||
|
||||
--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
|
||||
--network Show network/connectivity logs
|
||||
--last N Show last N lines (default: 50)
|
||||
--no-color Disable color output
|
||||
--json Output as JSON
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
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" ;;
|
||||
exit 0 ;;
|
||||
--follow|-f) FOLLOW_MODE="true"; shift ;;
|
||||
--all) FILTER_MODE="all"; shift ;;
|
||||
--errors) FILTER_MODE="errors"; shift ;;
|
||||
--warnings) FILTER_MODE="warnings"; shift ;;
|
||||
--info) FILTER_MODE="info"; shift ;;
|
||||
--bootstrap) FILTER_MODE="bootstrap"; shift ;;
|
||||
--network) FILTER_MODE="network"; shift ;;
|
||||
--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
|
||||
;;
|
||||
if is_integer "$1"; then LOG_LINES="$1"; shift
|
||||
else echo "❌ Invalid number for --last: $1"; exit 2; fi ;;
|
||||
--no-color) COLOR_OUTPUT="false"; shift ;;
|
||||
--json) OUTPUT_FORMAT="json"; shift ;;
|
||||
-*) echo "❌ Unknown option: $1"; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Check if log file exists
|
||||
# Verify file
|
||||
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
|
||||
case "$OUTPUT_FORMAT" in
|
||||
json) printf '{"error":"Log file not found","path":"%s"}\n' "$LOG_FILE" ;;
|
||||
*) echo "⚠️ Log file not found: $LOG_FILE"
|
||||
echo "📍 Tor might still be starting or logging elsewhere."
|
||||
echo "💡 Check again shortly." ;;
|
||||
esac
|
||||
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
|
||||
# Read identity
|
||||
FP_NICKNAME=""
|
||||
FP_FINGERPRINT=""
|
||||
if [ -f /var/lib/tor/fingerprint ]; then
|
||||
FP_NICKNAME=$(awk '{print $1}' /var/lib/tor/fingerprint 2>/dev/null || true)
|
||||
FP_FINGERPRINT=$(awk '{print $2}' /var/lib/tor/fingerprint 2>/dev/null || true)
|
||||
FP_NICKNAME=$(printf '%s' "$FP_NICKNAME" | tr -d '\r\n' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
||||
FP_FINGERPRINT=$(printf '%s' "$FP_FINGERPRINT" | tr -d '\r\n' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
||||
fi
|
||||
|
||||
# Regular output mode
|
||||
# Filter expressions
|
||||
case "$FILTER_MODE" in
|
||||
errors) FILTER_EXPR='\[err\]|\[error\]|failed|failure|critical' ;;
|
||||
warnings) FILTER_EXPR='\[warn\]|\[warning\]' ;;
|
||||
info) FILTER_EXPR='\[notice\]|\[info\]' ;;
|
||||
bootstrap) FILTER_EXPR='bootstrapped|starting|loading|establishing' ;;
|
||||
network) FILTER_EXPR='reachable|connection|network|port|address' ;;
|
||||
*) FILTER_EXPR='' ;;
|
||||
esac
|
||||
|
||||
# Stats
|
||||
if [ "$FILTER_MODE" = "all" ]; then
|
||||
TOTAL_MATCHES=$(wc -l < "$LOG_FILE" 2>/dev/null || echo 0)
|
||||
ERROR_COUNT=$(grep -ciE '\[err\]|\[error\]' "$LOG_FILE" 2>/dev/null || echo 0)
|
||||
WARNING_COUNT=$(grep -ciE '\[warn\]|\[warning\]' "$LOG_FILE" 2>/dev/null || echo 0)
|
||||
else
|
||||
TOTAL_MATCHES=$(grep -ciE "$FILTER_EXPR" "$LOG_FILE" 2>/dev/null || echo 0)
|
||||
ERROR_COUNT=$(grep -iE "$FILTER_EXPR" "$LOG_FILE" 2>/dev/null | grep -ciE '\[err\]|\[error\]' || echo 0)
|
||||
WARNING_COUNT=$(grep -iE "$FILTER_EXPR" "$LOG_FILE" 2>/dev/null | grep -ciE '\[warn\]|\[warning\]' || echo 0)
|
||||
fi
|
||||
|
||||
sanitize_num() { printf '%s' "$1" | tr -d '\r\n' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//'; }
|
||||
TOTAL_MATCHES=$(sanitize_num "$TOTAL_MATCHES")
|
||||
ERROR_COUNT=$(sanitize_num "$ERROR_COUNT")
|
||||
WARNING_COUNT=$(sanitize_num "$WARNING_COUNT")
|
||||
|
||||
sanitize_stream() { sed "s|${ESC}\[[0-9;]*[mK]||g; s/\r$//"; }
|
||||
|
||||
# Colorize output
|
||||
colorize_line() {
|
||||
if [ "$COLOR_OUTPUT" != "true" ]; then cat; return; fi
|
||||
sed -E \
|
||||
-e "s/\[err\]/${RED}[err]${NC}/Ig" \
|
||||
-e "s/\[error\]/${RED}[error]${NC}/Ig" \
|
||||
-e "s/\[warn\]/${YELLOW}[warn]${NC}/Ig" \
|
||||
-e "s/\[warning\]/${YELLOW}[warning]${NC}/Ig" \
|
||||
-e "s/\[notice\]/${GREEN}[notice]${NC}/Ig" \
|
||||
-e "s/\[info\]/${BLUE}[info]${NC}/Ig" \
|
||||
-e "s/Bootstrapped[[:space:]]*[0-9]{1,3}%/${GREEN}&${NC}/Ig" \
|
||||
-e "s/(Your Tor server's identity key fingerprint is ')[[:space:]]*([^[:space:]]+)[[:space:]]+([A-F0-9]{16,})(')/\1${CYAN}\2${NC} ${MAGENTA}\3${NC}\4/Ig" \
|
||||
-e "s/(Your Tor server's identity key ed25519 fingerprint is ')[[:space:]]*([^[:space:]]+)[[:space:]]+([A-Za-z0-9+\/=]{32,})(')/\1${CYAN}\2${NC} ${MAGENTA}\3${NC}\4/Ig" \
|
||||
-e "s/([A-F0-9]{10,})([[:space:]]*[A-F0-9]{2,})*/${MAGENTA}&${NC}/Ig" \
|
||||
-e "s/([A-Za-z0-9+\/]{40,}={0,2})/${MAGENTA}&${NC}/Ig"
|
||||
}
|
||||
|
||||
# Header
|
||||
echo "📜 Tor Relay Logs"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "📁 File: $LOG_FILE"
|
||||
echo "🔍 Filter: $FILTER_MODE"
|
||||
printf "📊 Stats: %s total | %s errors | %s warnings\n" "$TOTAL_MATCHES" "$ERROR_COUNT" "$WARNING_COUNT"
|
||||
|
||||
# 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"
|
||||
if [ -n "$FP_FINGERPRINT" ] || [ -n "$FP_NICKNAME" ]; then
|
||||
if [ "$COLOR_OUTPUT" = "true" ]; then
|
||||
echo "🔑 Identity:"
|
||||
printf " Nickname ✨: %s\n" "${CYAN}${FP_NICKNAME:-unknown}${NC}"
|
||||
printf " Fingerprint : %s\n" "${MAGENTA}${FP_FINGERPRINT:-unknown}${NC}"
|
||||
else
|
||||
echo "🔑 Identity:"
|
||||
echo " Nickname ✨: ${FP_NICKNAME:-unknown}"
|
||||
echo " Fingerprint : ${FP_FINGERPRINT:-unknown}"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# Display logs
|
||||
# Stream logs
|
||||
if [ "$FOLLOW_MODE" = "true" ]; then
|
||||
echo "🔄 Following log output (Ctrl+C to stop)..."
|
||||
echo "🔄 Following live output (Ctrl+C to stop)..."
|
||||
echo ""
|
||||
tail -n "$LOG_LINES" -f "$LOG_FILE" | apply_filter | colorize_line
|
||||
if [ "$FILTER_MODE" = "all" ]; then
|
||||
tail -n "$LOG_LINES" -f "$LOG_FILE" | sanitize_stream | colorize_line
|
||||
else
|
||||
tail -n "$LOG_LINES" -f "$LOG_FILE" | sanitize_stream | grep -iE "$FILTER_EXPR" 2>/dev/null | colorize_line
|
||||
fi
|
||||
else
|
||||
tail -n "$LOG_LINES" "$LOG_FILE" | apply_filter | colorize_line
|
||||
if [ "$FILTER_MODE" = "all" ]; then
|
||||
tail -n "$LOG_LINES" "$LOG_FILE" | sanitize_stream | colorize_line
|
||||
else
|
||||
tail -n "$LOG_LINES" "$LOG_FILE" | sanitize_stream | grep -iE "$FILTER_EXPR" 2>/dev/null | colorize_line
|
||||
fi
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "💡 Use 'view-logs --follow' for live updates"
|
||||
fi
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user