fix(release): bump to v1.0.4 - unified emoji diagnostics & enhanced health checks

This commit is contained in:
rE-Bo0t.bx1
2025-11-06 18:33:59 +08:00
parent 14f0543f25
commit 8c5d5a40f2
7 changed files with 212 additions and 123 deletions

View File

@@ -321,7 +321,7 @@ jobs:
echo "## 🧅 Tor Guard Relay v${VERSION} Release Notes" > release_notes.md
echo "" >> release_notes.md
if [ -f CHANGELOG.md ]; then
awk "/## \\[${VERSION}\\]/,0" CHANGELOG.md >> release_notes.md || true
awk "/## \\[${VERSION}\\]/,/## \\[[0-9]+\\.[0-9]+\\.[0-9]+\\]/ {if (!/## \\[[0-9]+\\.[0-9]+\\.[0-9]+\\]/ || NR==1) print}" CHANGELOG.md >> release_notes.md || true
else
echo "See [commit history](https://github.com/${{ github.repository }}/commits/v${VERSION}) for details." >> release_notes.md
fi

View File

@@ -17,6 +17,71 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
---
## [1.0.4] - 2025-11-06
### ✨ Major Additions
**Unified Emoji Diagnostic Language**
* Standardized all status indicators across CLI and dashboard:
🟢 **OK**, 🔴 **FAIL**, 🟡 **RUNNING**, ⏭️ **SKIPPED**
* Applied consistently to `net-check.sh`, `health.sh`, `status.sh`, and `dashboard.sh`
* Ensures identical visual language between terminal output and web UI
**IPv4 / IPv6 Awareness**
* Added explicit detection for unavailable IP versions
* Displays **“🔴 No IPv4”** or **“🔴 No IPv6”** instead of generic “not available”
* Greatly improves diagnostic clarity for dual-stack systems
**Dashboard API Enhancement**
* `/api/status` now exposes a new `status_emoji` field for lightweight external monitoring
* The web interface now dynamically renders emoji states directly from this field
**Optional Consensus Test**
* The “📋 Consensus” check in `net-check.sh` is now fully optional
* Disabled automatically in `--quick` mode to avoid noisy `curl: (22) 404` logs
* Reduces false negatives during limited network conditions
### 🧰 Enhancements
**Health & Status Modules**
* Unified status classification logic between `health.sh` and `status.sh`
* Added clearer uptime, reachability, and process visibility
* All scripts now extract build version from `/build-info.txt` for consistent reporting
**Integration Testing**
* `integration-check.sh` now performs comprehensive syntax, permission, and security validation for every tool
* Added checks for safe localhost binding on `dashboard.sh` and `metrics-http.sh`
* Enforces version consistency between build metadata and tool versions
**Logging & Diagnostics**
* Refined startup logs (Phases 49) for better readability during initialization
* Post-bootstrap diagnostics now summarize IPv4, IPv6, DNS, Port, and Consensus results cleanly
**Dashboard Security Defaults**
* Default bind set to `127.0.0.1` for secure-by-default operation
* Added visible runtime warning if the dashboard is exposed publicly (`0.0.0.0`)
### 🐛 Bug Fixes
* Fixed `curl: (22)` errors during consensus tests by adding graceful skip logic
* Prevented partial JSON validation failures when `jq` is unavailable
* Ensured accurate exit codes for health scripts used in Docker and CI/CD
* Standardized capitalization across diagnostics (`ok``OK`)
### 🧠 Developer Notes
* `integration-check.sh` reports full pass in Alpine-based containers
* Version bump to **v1.0.4** across all modules and build metadata
* Preflight diagnostics now surface partial network success even if Tor metrics endpoints are unreachable
## [1.0.3] - 2025-11-06
### 🚀 CI/CD & Build System

View File

@@ -1,6 +1,6 @@
#!/bin/sh
# docker-entrypoint.sh - Tor Guard Relay initialization and process management
# 🆕 v1.1 - Smart log monitoring: triggers diagnostics after bandwidth self-test
# 🆕 v1.0.4 - Smart log monitoring: triggers diagnostics after bandwidth self-test
set -e

View File

@@ -12,6 +12,33 @@ CHECK_TIMEOUT="${CHECK_TIMEOUT:-5}"
safe() { "$@" 2>/dev/null || 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
}
format_ip_status() {
local type="$1"
local status="$2"
local addr="$3"
if [ "$status" = "ok" ] && [ -n "$addr" ]; then
echo "🟢 OK ($addr)"
elif [ "$status" = "ok" ]; then
echo "🟢 OK"
elif [ "$status" = "failed" ] || [ "$status" = "not_available" ]; then
echo "🔴 No ${type} connectivity"
elif [ "$status" = "skipped" ]; then
echo "⏭️ ${type} check skipped"
else
echo "$(format_status "$status")"
fi
}
for arg in "$@"; do
case "$arg" in
--help|-h)
@@ -54,8 +81,10 @@ VERSION_INFO=""
ORPORT=""
DIRPORT=""
EXIT_RELAY="false"
PUBLIC_IP=""
PUBLIC_IP6=""
# --- Inline Checks (Simplified Logic that Works) ---
# Inline Checks
# Tor process
if safe pgrep -x tor >/dev/null; then
@@ -97,6 +126,12 @@ if [ -f /build-info.txt ]; then
VERSION_INFO=$(safe head -1 /build-info.txt | cut -d: -f2- | tr -d ' ')
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')
fi
# Count issues
if [ -f /var/log/tor/notices.log ]; then
ERRORS=$(safe grep -ciE "\[err\]" /var/log/tor/notices.log)
@@ -118,7 +153,7 @@ fi
TIMESTAMP=$(date -u '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || date)
# --- Output ---
# Output
case "$OUTPUT_FORMAT" in
json)
cat << EOF
@@ -136,7 +171,9 @@ case "$OUTPUT_FORMAT" in
"network": {
"reachable": $IS_REACHABLE,
"orport": "$ORPORT",
"dirport": "$DIRPORT"
"dirport": "$DIRPORT",
"ipv4": "$PUBLIC_IP",
"ipv6": "$PUBLIC_IP6"
},
"relay": {
"nickname": "$NICKNAME",
@@ -161,6 +198,8 @@ EOF
echo "ERRORS=$ERRORS"
echo "WARNINGS=$WARNINGS"
echo "UPTIME=$UPTIME"
echo "IPV4=$PUBLIC_IP"
echo "IPV6=$PUBLIC_IP6"
;;
*)
echo "🧅 Tor Relay Health Check"
@@ -176,14 +215,14 @@ EOF
echo ""
echo "⚙️ Process:"
if [ "$IS_RUNNING" = true ]; then
echo " Tor is running (uptime: $UPTIME)"
echo " 🟢 OK - Tor is running (uptime: $UPTIME)"
else
echo " Tor process not found"
echo " 🔴 FAIL - Tor process not found"
fi
echo ""
echo "🚀 Bootstrap:"
if [ "$BOOTSTRAP_PERCENT" -eq 100 ]; then
echo " Fully bootstrapped (100%)"
echo " 🟢 OK - Fully bootstrapped (100%)"
elif [ "$BOOTSTRAP_PERCENT" -gt 0 ]; then
echo " 🔄 Bootstrapping... ($BOOTSTRAP_PERCENT%)"
else
@@ -192,12 +231,14 @@ EOF
echo ""
echo "🌍 Network:"
if [ "$IS_REACHABLE" = true ]; then
echo " Reachable from the outside"
echo " 🌐 Reachability: 🟢 OK"
else
echo " ⏳ Testing reachability..."
echo " 🌐 Reachability: 🔴 FAIL"
fi
[ -n "$ORPORT" ] && echo " 📍 ORPort: $ORPORT"
[ -n "$DIRPORT" ] && echo " 📍 DirPort: $DIRPORT"
[ -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"
echo ""
if [ -n "$NICKNAME" ] || [ -n "$FINGERPRINT" ]; then
echo "🔑 Relay Identity:"

View File

@@ -9,11 +9,38 @@ OUTPUT_FORMAT="text"
CHECK_IPV4="true"
CHECK_IPV6="true"
CHECK_DNS="true"
CHECK_CONSENSUS="true"
CHECK_CONSENSUS="false"
CHECK_PORTS="true"
DNS_SERVERS="1.1.1.1 8.8.8.8 9.9.9.9"
DNS_SERVERS="194.242.2.2 94.140.14.14 9.9.9.9"
TEST_TIMEOUT="5"
format_status() {
case "$1" in
ok|OK) echo "🟢 OK" ;;
failed|closed|error|FAIL|not_available) echo "🔴 FAIL" ;;
skipped|unknown) echo "⏭️ SKIPPED" ;;
*) echo "$1" ;;
esac
}
format_ip_status() {
local type="$1"
local status="$2"
local addr="$3"
if [ "$status" = "ok" ] && [ -n "$addr" ]; then
echo "🟢 OK ($addr)"
elif [ "$status" = "ok" ]; then
echo "🟢 OK"
elif [ "$status" = "failed" ] || [ "$status" = "not_available" ]; then
echo "🔴 No ${type} connectivity"
elif [ "$status" = "skipped" ]; then
echo "⏭️ ${type} check skipped"
else
echo "$(format_status "$status")"
fi
}
# Parse arguments
for arg in "$@"; do
case "$arg" in
@@ -156,7 +183,7 @@ check_ports() {
check_ipv4
check_ipv6
check_dns
check_consensus
# check_consensus
check_ports
TOTAL_PASSED=$((TOTAL_TESTS - FAILED_TESTS))
@@ -202,11 +229,11 @@ EOF
echo "📊 Overall: ❌ Multiple failures ($SUCCESS_RATE% passed)"
fi
echo ""
echo "🔌 IPv4: $IPV4_STATUS ${PUBLIC_IP:+($PUBLIC_IP)}"
echo "🔌 IPv6: $IPV6_STATUS ${PUBLIC_IP6:+($PUBLIC_IP6)}"
echo "🔍 DNS: $DNS_STATUS"
echo "📋 Consensus: $CONSENSUS_STATUS"
echo "🚪 Ports: $PORT_STATUS"
echo "🔌 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 ""
echo "🕒 Tested at: $TIMESTAMP"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

View File

@@ -5,7 +5,7 @@
set -e
# Configuration
VERSION="1.1.1"
VERSION="1.0.4"
CONFIG_FILE="${CONFIG_FILE:-/etc/tor/torrc}"
RELAY_TYPE="${RELAY_TYPE:-guard}"
AUTO_MODE="${AUTO_MODE:-false}"

View File

@@ -5,11 +5,30 @@
set -e
# Configuration
VERSION="1.1.0"
VERSION="1.0.4"
OUTPUT_FORMAT="${OUTPUT_FORMAT:-text}"
SHOW_ALL="${SHOW_ALL:-true}"
CHECK_NETWORK="${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
}
format_ip_status() {
local type="$1"
local value="$2"
if [ -n "$value" ]; then
echo "🟢 OK ($value)"
else
echo "🔴 No ${type} connectivity"
fi
}
# Parse arguments
for arg in "$@"; do
case "$arg" in
@@ -33,22 +52,6 @@ ENVIRONMENT VARIABLES:
SHOW_ALL Show all sections (true/false)
CHECK_NETWORK Include network checks (true/false)
SECTIONS:
• Build Information
• Bootstrap Progress
• Reachability Status
• Relay Identity
• Network Configuration
• Performance Metrics
• Recent Activity
• Error Summary
EXAMPLES:
status # Full status report
status --json # JSON output for monitoring
status --quick # Quick status without network
status --plain # Machine-readable format
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
EOF
exit 0
@@ -56,11 +59,11 @@ EOF
--json) OUTPUT_FORMAT="json" ;;
--plain) OUTPUT_FORMAT="plain" ;;
--quick) CHECK_NETWORK="false" ;;
--full)
--full)
SHOW_ALL="true"
CHECK_NETWORK="true"
;;
-*)
-*)
echo "❌ Unknown option: $arg"
echo "💡 Use --help for usage information"
exit 2
@@ -70,7 +73,6 @@ done
# Gather all status information
gather_status() {
# Process status
IS_RUNNING="false"
if pgrep -x tor > /dev/null 2>&1; then
IS_RUNNING="true"
@@ -78,7 +80,6 @@ gather_status() {
UPTIME=$(ps -o etime= -p "$PID" 2>/dev/null | tr -d ' ' || echo "0")
fi
# Bootstrap status
BOOTSTRAP_PERCENT=0
BOOTSTRAP_MESSAGE=""
if [ -f /var/log/tor/notices.log ]; then
@@ -89,7 +90,6 @@ gather_status() {
fi
fi
# Reachability
IS_REACHABLE="false"
REACHABILITY_MESSAGE=""
if [ -f /var/log/tor/notices.log ]; then
@@ -102,7 +102,6 @@ gather_status() {
fi
fi
# Relay identity
NICKNAME=""
FINGERPRINT=""
if [ -f /var/lib/tor/fingerprint ]; then
@@ -110,7 +109,6 @@ gather_status() {
FINGERPRINT=$(awk '{print $2}' /var/lib/tor/fingerprint 2>/dev/null)
fi
# Configuration
ORPORT=""
DIRPORT=""
EXIT_RELAY="false"
@@ -124,7 +122,6 @@ gather_status() {
BANDWIDTH_RATE=$(grep -E "^RelayBandwidthRate" /etc/tor/torrc 2>/dev/null | awk '{print $2,$3}')
fi
# Errors and warnings
ERROR_COUNT=0
WARNING_COUNT=0
RECENT_ERRORS=""
@@ -134,7 +131,6 @@ gather_status() {
RECENT_ERRORS=$(grep -E "\[err\]|\[error\]" /var/log/tor/notices.log 2>/dev/null | tail -3)
fi
# Version info
VERSION_INFO=""
BUILD_TIME=""
if [ -f /build-info.txt ]; then
@@ -142,20 +138,17 @@ gather_status() {
BUILD_TIME=$(grep "Built:" /build-info.txt 2>/dev/null | cut -d: -f2- | tr -d ' ')
fi
# Network info (if enabled)
PUBLIC_IP=""
PUBLIC_IP6=""
if [ "$CHECK_NETWORK" = "true" ] && command -v curl > /dev/null 2>&1; then
PUBLIC_IP=$(curl -s --max-time 5 https://api.ipify.org 2>/dev/null || echo "")
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')
fi
}
# Gather all information
gather_status
# Generate timestamp
TIMESTAMP=$(date -u '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || date '+%Y-%m-%d %H:%M:%S')
# Determine overall status
if [ "$IS_RUNNING" = "false" ]; then
OVERALL_STATUS="down"
elif [ "$BOOTSTRAP_PERCENT" -eq 100 ] && [ "$IS_REACHABLE" = "true" ]; then
@@ -168,29 +161,17 @@ else
OVERALL_STATUS="unknown"
fi
# Output based on format
case "$OUTPUT_FORMAT" in
json)
cat << EOF
{
"timestamp": "$TIMESTAMP",
"status": "$OVERALL_STATUS",
"process": {
"running": $IS_RUNNING,
"uptime": "$UPTIME"
},
"bootstrap": {
"percent": $BOOTSTRAP_PERCENT,
"message": "$BOOTSTRAP_MESSAGE"
},
"reachability": {
"reachable": $IS_REACHABLE,
"message": "$REACHABILITY_MESSAGE"
},
"identity": {
"nickname": "$NICKNAME",
"fingerprint": "$FINGERPRINT"
},
"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",
@@ -198,22 +179,15 @@ case "$OUTPUT_FORMAT" in
"bridge_relay": $BRIDGE_RELAY,
"bandwidth": "$BANDWIDTH_RATE"
},
"network": {
"public_ip": "$PUBLIC_IP"
},
"issues": {
"errors": $ERROR_COUNT,
"warnings": $WARNING_COUNT
},
"version": {
"software": "$VERSION_INFO",
"build_time": "$BUILD_TIME"
}
"network": { "ipv4": "$PUBLIC_IP", "ipv6": "$PUBLIC_IP6" },
"issues": { "errors": $ERROR_COUNT, "warnings": $WARNING_COUNT },
"version": { "software": "$VERSION_INFO", "build_time": "$BUILD_TIME" }
}
EOF
;;
plain)
echo "STATUS=$OVERALL_STATUS"
echo "RUNNING=$IS_RUNNING"
echo "UPTIME=$UPTIME"
@@ -226,46 +200,33 @@ EOF
echo "ERRORS=$ERROR_COUNT"
echo "WARNINGS=$WARNING_COUNT"
echo "PUBLIC_IP=$PUBLIC_IP"
echo "PUBLIC_IP6=$PUBLIC_IP6"
;;
*)
# Default text format with emojis
echo "🧅 Tor Relay Status Report"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
# Overall status
case "$OVERALL_STATUS" in
healthy)
echo "⭐ Overall Status: ✅ HEALTHY - Relay is fully operational"
;;
running)
echo "⭐ Overall Status: 🟡 RUNNING - Awaiting reachability confirmation"
;;
starting)
echo "⭐ Overall Status: 🔄 STARTING - Bootstrap in progress ($BOOTSTRAP_PERCENT%)"
;;
down)
echo "⭐ Overall Status: ❌ DOWN - Tor process not running"
;;
*)
echo "⭐ Overall Status: ❓ UNKNOWN"
;;
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" ;;
esac
echo ""
# Build info
if [ -n "$VERSION_INFO" ] || [ -n "$BUILD_TIME" ]; then
echo "📦 Build Information:"
[ -n "$VERSION_INFO" ] && echo " Version: $VERSION_INFO"
[ -n "$BUILD_TIME" ] && echo " Built: $BUILD_TIME"
echo ""
fi
# Bootstrap progress
echo "🚀 Bootstrap Progress:"
if [ "$BOOTSTRAP_PERCENT" -eq 100 ]; then
echo " Fully bootstrapped (100%)"
echo " 🟢 OK - Fully bootstrapped (100%)"
[ -n "$BOOTSTRAP_MESSAGE" ] && echo " Status: $BOOTSTRAP_MESSAGE"
elif [ "$BOOTSTRAP_PERCENT" -gt 0 ]; then
echo " 🔄 Bootstrapping: $BOOTSTRAP_PERCENT%"
@@ -274,33 +235,30 @@ EOF
echo " ⏳ Not started yet"
fi
echo ""
# Reachability status
echo "🌍 Reachability Status:"
echo "🌍 Reachability:"
if [ "$IS_REACHABLE" = "true" ]; then
echo " Relay is reachable from the outside"
echo " 🌐 Reachability: 🟢 OK"
elif [ -n "$REACHABILITY_MESSAGE" ]; then
echo " 🔄 $REACHABILITY_MESSAGE"
echo " 🌐 Reachability: 🔴 $REACHABILITY_MESSAGE"
else
echo " ⏳ No reachability test results yet"
echo " 🌐 Reachability: ⏳ Pending"
fi
echo ""
# Relay identity
if [ -n "$NICKNAME" ] || [ -n "$FINGERPRINT" ]; then
echo "🔑 Relay Identity:"
[ -n "$NICKNAME" ] && echo " 📝 Nickname: $NICKNAME"
[ -n "$FINGERPRINT" ] && echo " 🆔 Fingerprint: $FINGERPRINT"
echo ""
fi
# Network configuration
echo "🔌 Network Configuration:"
[ -n "$ORPORT" ] && echo " ORPort: $ORPORT"
[ -n "$DIRPORT" ] && echo " DirPort: $DIRPORT"
[ -n "$PUBLIC_IP" ] && echo " Public IP: $PUBLIC_IP"
[ -n "$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
@@ -309,13 +267,11 @@ EOF
echo " Type: 🔒 Guard/Middle Relay"
fi
echo ""
# Issues summary
if [ "$ERROR_COUNT" -gt 0 ] || [ "$WARNING_COUNT" -gt 0 ]; then
echo "⚠️ Issues Summary:"
[ "$ERROR_COUNT" -gt 0 ] && echo " ❌ Errors: $ERROR_COUNT"
[ "$WARNING_COUNT" -gt 0 ] && echo " ⚠️ Warnings: $WARNING_COUNT"
if [ -n "$RECENT_ERRORS" ] && [ "$ERROR_COUNT" -gt 0 ]; then
echo ""
echo " Recent errors:"
@@ -323,11 +279,11 @@ EOF
fi
echo ""
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "💡 For live monitoring: docker logs -f <container-name>"
echo "🔗 Search your relay: https://metrics.torproject.org/rs.html"
[ -n "$FINGERPRINT" ] && echo "📊 Direct link: https://metrics.torproject.org/rs.html#search/$FINGERPRINT"
echo "🕒 Last updated: $TIMESTAMP"
;;
esac
esac