🎬 ARR Suite Template Bootstrap - Complete Media Automation Stack Features: - 16 production services (Prowlarr, Sonarr, Radarr, Plex, etc.) - One-command Ansible deployment - VPN-protected downloads via Gluetun - Tailscale secure access - Production-ready security (UFW, Fail2Ban) - Automated backups and monitoring - Comprehensive documentation Ready for customization and deployment to any VPS. Co-authored-by: openhands <openhands@all-hands.dev>
237 lines
8.9 KiB
Django/Jinja
237 lines
8.9 KiB
Django/Jinja
#!/bin/bash
|
|
# Network monitoring script for Arrs Media Stack
|
|
# Generated by Ansible
|
|
|
|
LOG_DIR="{{ docker_root }}/logs/system"
|
|
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
|
|
NET_LOG="$LOG_DIR/network-monitor-$(date '+%Y%m%d').log"
|
|
|
|
# Ensure log directory exists
|
|
mkdir -p "$LOG_DIR"
|
|
|
|
# Function to log with timestamp
|
|
log_net() {
|
|
echo "[$TIMESTAMP] $1" >> "$NET_LOG"
|
|
}
|
|
|
|
log_net "=== NETWORK MONITORING ==="
|
|
|
|
# Network interface information
|
|
log_net "=== NETWORK INTERFACES ==="
|
|
ip addr show | grep -E "^[0-9]+:|inet " | while IFS= read -r line; do
|
|
log_net "INTERFACE $line"
|
|
done
|
|
|
|
# Default route
|
|
DEFAULT_ROUTE=$(ip route | grep default)
|
|
log_net "DEFAULT_ROUTE $DEFAULT_ROUTE"
|
|
|
|
# Network statistics
|
|
log_net "=== NETWORK STATISTICS ==="
|
|
MAIN_INTERFACE=$(ip route | grep default | awk '{print $5}' | head -1)
|
|
|
|
if [[ -n "$MAIN_INTERFACE" ]]; then
|
|
# Interface statistics
|
|
RX_BYTES=$(cat /sys/class/net/$MAIN_INTERFACE/statistics/rx_bytes 2>/dev/null || echo "0")
|
|
TX_BYTES=$(cat /sys/class/net/$MAIN_INTERFACE/statistics/tx_bytes 2>/dev/null || echo "0")
|
|
RX_PACKETS=$(cat /sys/class/net/$MAIN_INTERFACE/statistics/rx_packets 2>/dev/null || echo "0")
|
|
TX_PACKETS=$(cat /sys/class/net/$MAIN_INTERFACE/statistics/tx_packets 2>/dev/null || echo "0")
|
|
RX_ERRORS=$(cat /sys/class/net/$MAIN_INTERFACE/statistics/rx_errors 2>/dev/null || echo "0")
|
|
TX_ERRORS=$(cat /sys/class/net/$MAIN_INTERFACE/statistics/tx_errors 2>/dev/null || echo "0")
|
|
RX_DROPPED=$(cat /sys/class/net/$MAIN_INTERFACE/statistics/rx_dropped 2>/dev/null || echo "0")
|
|
TX_DROPPED=$(cat /sys/class/net/$MAIN_INTERFACE/statistics/tx_dropped 2>/dev/null || echo "0")
|
|
|
|
# Convert bytes to human readable
|
|
RX_MB=$((RX_BYTES / 1024 / 1024))
|
|
TX_MB=$((TX_BYTES / 1024 / 1024))
|
|
|
|
log_net "INTERFACE_STATS $MAIN_INTERFACE - RX: ${RX_MB}MB (${RX_PACKETS} packets, ${RX_ERRORS} errors, ${RX_DROPPED} dropped)"
|
|
log_net "INTERFACE_STATS $MAIN_INTERFACE - TX: ${TX_MB}MB (${TX_PACKETS} packets, ${TX_ERRORS} errors, ${TX_DROPPED} dropped)"
|
|
|
|
# Check for high error rates
|
|
if [[ $RX_ERRORS -gt 100 ]]; then
|
|
log_net "ALERT_NETWORK High RX errors on $MAIN_INTERFACE: $RX_ERRORS"
|
|
fi
|
|
|
|
if [[ $TX_ERRORS -gt 100 ]]; then
|
|
log_net "ALERT_NETWORK High TX errors on $MAIN_INTERFACE: $TX_ERRORS"
|
|
fi
|
|
fi
|
|
|
|
# Network connectivity tests
|
|
log_net "=== CONNECTIVITY TESTS ==="
|
|
|
|
# Test DNS resolution
|
|
if nslookup google.com >/dev/null 2>&1; then
|
|
log_net "DNS_TEST OK - DNS resolution working"
|
|
else
|
|
log_net "DNS_TEST FAILED - DNS resolution not working"
|
|
fi
|
|
|
|
# Test internet connectivity
|
|
if ping -c 1 8.8.8.8 >/dev/null 2>&1; then
|
|
log_net "INTERNET_TEST OK - Internet connectivity working"
|
|
else
|
|
log_net "INTERNET_TEST FAILED - No internet connectivity"
|
|
fi
|
|
|
|
# Test Tailscale connectivity (if configured)
|
|
if command -v tailscale >/dev/null 2>&1; then
|
|
TAILSCALE_STATUS=$(tailscale status --json 2>/dev/null | jq -r '.BackendState' 2>/dev/null || echo "unknown")
|
|
log_net "TAILSCALE_STATUS $TAILSCALE_STATUS"
|
|
|
|
if [[ "$TAILSCALE_STATUS" == "Running" ]]; then
|
|
TAILSCALE_IP=$(tailscale ip -4 2>/dev/null || echo "unknown")
|
|
log_net "TAILSCALE_IP $TAILSCALE_IP"
|
|
fi
|
|
fi
|
|
|
|
# Port connectivity tests for Arrs services
|
|
log_net "=== SERVICE PORT TESTS ==="
|
|
SERVICES_PORTS=(
|
|
"sonarr:{{ ports.sonarr }}"
|
|
"radarr:{{ ports.radarr }}"
|
|
"lidarr:{{ ports.lidarr }}"
|
|
"bazarr:{{ ports.bazarr }}"
|
|
"prowlarr:{{ ports.prowlarr }}"
|
|
)
|
|
|
|
for service_port in "${SERVICES_PORTS[@]}"; do
|
|
SERVICE=$(echo "$service_port" | cut -d: -f1)
|
|
PORT=$(echo "$service_port" | cut -d: -f2)
|
|
|
|
if nc -z localhost "$PORT" 2>/dev/null; then
|
|
log_net "PORT_TEST $SERVICE (port $PORT) - OK"
|
|
else
|
|
log_net "PORT_TEST $SERVICE (port $PORT) - FAILED"
|
|
fi
|
|
done
|
|
|
|
# Active network connections
|
|
log_net "=== ACTIVE CONNECTIONS ==="
|
|
ACTIVE_CONNECTIONS=$(netstat -tuln 2>/dev/null | grep LISTEN | wc -l)
|
|
log_net "LISTENING_PORTS Total listening ports: $ACTIVE_CONNECTIONS"
|
|
|
|
# Show listening ports for our services
|
|
netstat -tuln 2>/dev/null | grep -E ":{{ ports.sonarr }}|:{{ ports.radarr }}|:{{ ports.lidarr }}|:{{ ports.bazarr }}|:{{ ports.prowlarr }}" | while IFS= read -r line; do
|
|
log_net "SERVICE_PORT $line"
|
|
done
|
|
|
|
# Network load monitoring
|
|
log_net "=== NETWORK LOAD ==="
|
|
if command -v ss >/dev/null 2>&1; then
|
|
ESTABLISHED_CONNECTIONS=$(ss -t state established | wc -l)
|
|
TIME_WAIT_CONNECTIONS=$(ss -t state time-wait | wc -l)
|
|
|
|
log_net "CONNECTION_STATS Established: $ESTABLISHED_CONNECTIONS, Time-wait: $TIME_WAIT_CONNECTIONS"
|
|
|
|
# Check for high connection counts
|
|
if [[ $ESTABLISHED_CONNECTIONS -gt 1000 ]]; then
|
|
log_net "ALERT_NETWORK High number of established connections: $ESTABLISHED_CONNECTIONS"
|
|
fi
|
|
|
|
if [[ $TIME_WAIT_CONNECTIONS -gt 5000 ]]; then
|
|
log_net "ALERT_NETWORK High number of time-wait connections: $TIME_WAIT_CONNECTIONS"
|
|
fi
|
|
fi
|
|
|
|
# Docker network information
|
|
if command -v docker >/dev/null 2>&1; then
|
|
log_net "=== DOCKER NETWORK ==="
|
|
|
|
# Docker networks
|
|
DOCKER_NETWORKS=$(docker network ls --format "{{ '{{.Name}}' }}\t{{ '{{.Driver}}' }}\t{{ '{{.Scope}}' }}" 2>/dev/null)
|
|
if [[ -n "$DOCKER_NETWORKS" ]]; then
|
|
echo "$DOCKER_NETWORKS" | while IFS=$'\t' read -r name driver scope; do
|
|
log_net "DOCKER_NETWORK $name - Driver: $driver, Scope: $scope"
|
|
done
|
|
fi
|
|
|
|
# Container network stats
|
|
cd {{ docker_compose_dir }}
|
|
SERVICES=("sonarr" "radarr" "lidarr" "bazarr" "prowlarr" "watchtower")
|
|
|
|
for service in "${SERVICES[@]}"; do
|
|
CONTAINER_ID=$(docker-compose ps -q "$service" 2>/dev/null)
|
|
if [[ -n "$CONTAINER_ID" ]]; then
|
|
# Get container network stats
|
|
NET_STATS=$(docker stats --no-stream --format "{{ '{{.NetIO}}' }}" "$CONTAINER_ID" 2>/dev/null)
|
|
if [[ -n "$NET_STATS" ]]; then
|
|
log_net "CONTAINER_NETWORK $service - Network I/O: $NET_STATS"
|
|
fi
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# Firewall status
|
|
log_net "=== FIREWALL STATUS ==="
|
|
if command -v ufw >/dev/null 2>&1; then
|
|
UFW_STATUS=$(ufw status 2>/dev/null | head -1)
|
|
log_net "UFW_STATUS $UFW_STATUS"
|
|
|
|
# Show rules for our ports
|
|
ufw status numbered 2>/dev/null | grep -E "{{ ports.sonarr }}|{{ ports.radarr }}|{{ ports.lidarr }}|{{ ports.bazarr }}|{{ ports.prowlarr }}" | while IFS= read -r line; do
|
|
log_net "UFW_RULE $line"
|
|
done
|
|
fi
|
|
|
|
# Network security checks
|
|
log_net "=== SECURITY CHECKS ==="
|
|
|
|
# Check for open ports that shouldn't be
|
|
UNEXPECTED_PORTS=$(netstat -tuln 2>/dev/null | grep LISTEN | grep -v -E ":22|:{{ ports.sonarr }}|:{{ ports.radarr }}|:{{ ports.lidarr }}|:{{ ports.bazarr }}|:{{ ports.prowlarr }}|:127.0.0.1" | wc -l)
|
|
if [[ $UNEXPECTED_PORTS -gt 0 ]]; then
|
|
log_net "SECURITY_ALERT $UNEXPECTED_PORTS unexpected open ports detected"
|
|
netstat -tuln 2>/dev/null | grep LISTEN | grep -v -E ":22|:{{ ports.sonarr }}|:{{ ports.radarr }}|:{{ ports.lidarr }}|:{{ ports.bazarr }}|:{{ ports.prowlarr }}|:127.0.0.1" | while IFS= read -r line; do
|
|
log_net "UNEXPECTED_PORT $line"
|
|
done
|
|
fi
|
|
|
|
# Check for failed connection attempts (from auth.log)
|
|
FAILED_CONNECTIONS=$(grep "Failed" /var/log/auth.log 2>/dev/null | grep "$(date '+%b %d')" | wc -l)
|
|
if [[ $FAILED_CONNECTIONS -gt 10 ]]; then
|
|
log_net "SECURITY_ALERT $FAILED_CONNECTIONS failed connection attempts today"
|
|
fi
|
|
|
|
# Bandwidth usage estimation
|
|
log_net "=== BANDWIDTH ESTIMATION ==="
|
|
if [[ -n "$MAIN_INTERFACE" ]]; then
|
|
# Read current stats
|
|
CURRENT_RX=$(cat /sys/class/net/$MAIN_INTERFACE/statistics/rx_bytes 2>/dev/null || echo "0")
|
|
CURRENT_TX=$(cat /sys/class/net/$MAIN_INTERFACE/statistics/tx_bytes 2>/dev/null || echo "0")
|
|
|
|
# Read previous stats if available
|
|
STATS_FILE="/tmp/network_stats_$MAIN_INTERFACE"
|
|
if [[ -f "$STATS_FILE" ]]; then
|
|
PREV_TIMESTAMP=$(head -1 "$STATS_FILE")
|
|
PREV_RX=$(sed -n '2p' "$STATS_FILE")
|
|
PREV_TX=$(sed -n '3p' "$STATS_FILE")
|
|
|
|
# Calculate time difference (in seconds)
|
|
TIME_DIFF=$(($(date +%s) - PREV_TIMESTAMP))
|
|
|
|
if [[ $TIME_DIFF -gt 0 ]]; then
|
|
# Calculate bandwidth (bytes per second)
|
|
RX_RATE=$(((CURRENT_RX - PREV_RX) / TIME_DIFF))
|
|
TX_RATE=$(((CURRENT_TX - PREV_TX) / TIME_DIFF))
|
|
|
|
# Convert to human readable (Mbps)
|
|
RX_MBPS=$(echo "scale=2; $RX_RATE * 8 / 1024 / 1024" | bc -l 2>/dev/null || echo "0")
|
|
TX_MBPS=$(echo "scale=2; $TX_RATE * 8 / 1024 / 1024" | bc -l 2>/dev/null || echo "0")
|
|
|
|
log_net "BANDWIDTH_USAGE RX: ${RX_MBPS} Mbps, TX: ${TX_MBPS} Mbps (over ${TIME_DIFF}s)"
|
|
fi
|
|
fi
|
|
|
|
# Save current stats for next run
|
|
echo "$(date +%s)" > "$STATS_FILE"
|
|
echo "$CURRENT_RX" >> "$STATS_FILE"
|
|
echo "$CURRENT_TX" >> "$STATS_FILE"
|
|
fi
|
|
|
|
log_net "=== END NETWORK MONITORING ==="
|
|
|
|
# Cleanup old network logs (keep 7 days)
|
|
find "$LOG_DIR" -name "network-monitor-*.log" -mtime +7 -delete 2>/dev/null
|
|
|
|
exit 0 |