Files
tinyboard/health-check.sh

205 lines
6.1 KiB
Bash
Raw Normal View History

#!/usr/bin/env bash
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'
ok() { echo -e " ${GREEN}[OK]${NC} $*"; }
fail() { echo -e " ${RED}[FAIL]${NC} $*"; }
warn() { echo -e " ${YELLOW}[WARN]${NC} $*"; }
header() { echo -e "\n${CYAN}══════════════════════════════════════════${NC}"; echo -e "${CYAN} $*${NC}"; echo -e "${CYAN}══════════════════════════════════════════${NC}"; }
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REGISTRY="${HOME}/.config/tinyboard/spokes"
COMPOSE="$SCRIPT_DIR/spoke/compose.yaml"
RCLONE_CONF="${HOME}/.config/rclone/rclone.conf"
IS_SPOKE=false
IS_HUB=false
if docker ps --format '{{.Names}}' 2>/dev/null | grep -qi autossh || [ -f "$COMPOSE" ]; then
IS_SPOKE=true
fi
if [ -f "$REGISTRY" ]; then
IS_HUB=true
fi
if [ "$IS_SPOKE" = false ] && [ "$IS_HUB" = false ]; then
echo -e "${YELLOW}Could not detect hub or spoke configuration. Is tinyboard set up?${NC}"
exit 1
fi
check_common() {
header "System"
if command -v docker >/dev/null 2>&1; then
ok "docker installed"
else
fail "docker not found"
fi
if docker info >/dev/null 2>&1; then
ok "docker daemon running"
else
fail "docker daemon not running"
fi
local ssh_svc=""
if systemctl list-unit-files ssh.service >/dev/null 2>&1; then
ssh_svc="ssh"
elif systemctl list-unit-files sshd.service >/dev/null 2>&1; then
ssh_svc="sshd"
fi
if [ -n "$ssh_svc" ]; then
if systemctl is-active "$ssh_svc" >/dev/null 2>&1; then
ok "SSH server running ($ssh_svc)"
else
fail "SSH server not running ($ssh_svc)"
fi
else
warn "Could not detect SSH service"
fi
local st_container
st_container=$(docker ps --format '{{.Names}}' 2>/dev/null | grep -i syncthing | head -1 || true)
if [ -n "$st_container" ]; then
ok "Syncthing container running ($st_container)"
if curl -sf http://127.0.0.1:8384 >/dev/null 2>&1; then
ok "Syncthing API reachable"
else
warn "Syncthing container running but API not reachable on :8384"
fi
else
warn "No Syncthing container running"
fi
}
check_spoke() {
header "Spoke"
local autossh_container
autossh_container=$(docker ps --format '{{.Names}}' 2>/dev/null | grep -i autossh | head -1 || true)
if [ -n "$autossh_container" ]; then
ok "autossh container running ($autossh_container)"
local logs
logs=$(docker logs "$autossh_container" 2>&1 | tail -20 || true)
if echo "$logs" | grep -q "remote port forwarding failed"; then
fail "Tunnel reports port forwarding failed — check hub authorized_keys"
else
ok "No tunnel errors in recent logs"
fi
else
fail "No autossh container running"
fi
if [ -n "$autossh_container" ]; then
local tunnel_port hub_host
tunnel_port=$(docker inspect "$autossh_container" 2>/dev/null | python3 -c "
import sys, json
data = json.load(sys.stdin)
cmd = ' '.join(data[0].get('Config', {}).get('Cmd', []))
import re
m = re.search(r'-R (\d+):localhost', cmd)
print(m.group(1) if m else '')
" 2>/dev/null || true)
hub_host=$(docker inspect "$autossh_container" 2>/dev/null | python3 -c "
import sys, json
data = json.load(sys.stdin)
cmd = ' '.join(data[0].get('Config', {}).get('Cmd', []))
import re
m = re.search(r'[a-zA-Z0-9._-]+@([a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+)', cmd)
print(m.group(1) if m else '')
" 2>/dev/null || true)
if [ -n "$tunnel_port" ] && [ -n "$hub_host" ]; then
ok "Tunnel configured: port $tunnel_port$hub_host"
else
warn "Could not parse tunnel config from running container"
fi
fi
local st_data="/home/armbian/st/data"
if [ -d "$st_data" ]; then
ok "Syncthing data directory exists ($st_data)"
else
warn "Syncthing data directory not found ($st_data)"
fi
}
check_hub() {
header "Hub"
local spoke_count
spoke_count=$(wc -l < "$REGISTRY" 2>/dev/null || echo 0)
ok "$spoke_count spoke(s) in registry"
while IFS= read -r line; do
[ -n "$line" ] || continue
local spoke_name tunnel_port key_path mount_point
spoke_name=$(echo "$line" | awk '{print $1}')
tunnel_port=$(echo "$line" | awk '{print $2}')
key_path=$(echo "$line" | awk '{print $3}')
mount_point=$(echo "$line" | awk '{print $4}')
echo ""
echo -e " ${CYAN}Spoke: $spoke_name${NC}"
if ss -tlnp 2>/dev/null | grep -q ":${tunnel_port}"; then
ok "Tunnel port $tunnel_port is listening"
else
fail "Tunnel port $tunnel_port not listening — is the spoke connected?"
fi
if [ -f "$key_path" ]; then
ok "Hub key exists ($key_path)"
else
fail "Hub key missing ($key_path)"
fi
if mountpoint -q "$mount_point" 2>/dev/null; then
ok "Mounted at $mount_point"
else
fail "Not mounted at $mount_point"
fi
if grep -q "\[${spoke_name}-remote\]" "$RCLONE_CONF" 2>/dev/null; then
ok "rclone remote [${spoke_name}-remote] configured"
else
fail "rclone remote [${spoke_name}-remote] not found in rclone.conf"
fi
if crontab -l 2>/dev/null | grep -q "${spoke_name}-remote:"; then
ok "Auto-mount crontab entry present"
else
warn "No auto-mount crontab entry for $spoke_name"
fi
done < "$REGISTRY"
echo ""
local union_remotes
union_remotes=$(grep -A1 'type = union' "$RCLONE_CONF" 2>/dev/null | grep -v 'type = union' | grep -v '^--$' || true)
if [ -n "$union_remotes" ]; then
ok "Union remote(s) configured in rclone.conf"
else
warn "No union remotes configured"
fi
}
check_common
if [ "$IS_SPOKE" = true ]; then
check_spoke
fi
if [ "$IS_HUB" = true ]; then
check_hub
fi
echo ""