Files
2026-04-06 10:13:21 -04:00

151 lines
8.1 KiB
Bash

#!/bin/bash
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
GRAY='\033[0;90m'
BOLD='\033[1m'
NC='\033[0m'
COLS=${COLUMNS:-$(tput cols 2>/dev/null)}
[[ "$COLS" =~ ^[0-9]+$ ]] || COLS=80
SEP="${GRAY}$(printf '━%.0s' $(seq 1 $COLS))${NC}"
# ── Logo ─────────────────────────────────────────────────────────────────────
echo -e "${CYAN}${BOLD}"
echo '██ ██ ███ ██ ██ ██'
echo '██ ██ ████ ██ ██ ██ '
echo '██ ██ ██ ██ ██ █████ '
echo '██ ██ ██ ██ ██ ██ ██ '
echo '███████ ██ ██ ████ ██ ██'
echo -e "${NC}"
echo -e "$SEP"
# ── Date & Time ───────────────────────────────────────────────────────────────
echo -e " ${PURPLE}${BOLD}$(date '+%A, %B %d %Y')${NC} ${GRAY}·${NC} ${PURPLE}$(date '+%I:%M %p')${NC}"
echo -e "$SEP"
# ── Weather ───────────────────────────────────────────────────────────────────
WEATHER=$(curl -s --max-time 4 "https://api.open-meteo.com/v1/forecast?latitude=39.84&longitude=-82.81&current=temperature_2m,apparent_temperature,relative_humidity_2m,wind_speed_10m,weather_code&temperature_unit=fahrenheit&timezone=America/New_York" 2>/dev/null)
echo -e " ${YELLOW}${BOLD}☁ Canal Winchester${NC}"
if echo "$WEATHER" | python3 -c "import sys,json; json.load(sys.stdin)" &>/dev/null; then
TEMP=$(echo "$WEATHER" | python3 -c "import sys,json; d=json.load(sys.stdin)['current']; print(round(d['temperature_2m']))")
FEELS=$(echo "$WEATHER" | python3 -c "import sys,json; d=json.load(sys.stdin)['current']; print(round(d['apparent_temperature']))")
HUMID=$(echo "$WEATHER" | python3 -c "import sys,json; d=json.load(sys.stdin)['current']; print(round(d['relative_humidity_2m']))")
WIND=$(echo "$WEATHER" | python3 -c "import sys,json; d=json.load(sys.stdin)['current']; print(round(d['wind_speed_10m']))")
CODE=$(echo "$WEATHER" | python3 -c "import sys,json; d=json.load(sys.stdin)['current']; print(d['weather_code'])")
case $CODE in
0) ICON="☀️ Clear";;
1|2) ICON="⛅ Partly Cloudy";;
3) ICON="☁️ Overcast";;
45|48) ICON="🌫️ Fog";;
51|53|55|61|63|65|80|81|82) ICON="🌧️ Rain";;
71|73|75|85|86) ICON="❄️ Snow";;
95|96|99) ICON="⛈️ Storms";;
*) ICON="🌥️ Cloudy";;
esac
echo -e " ${ICON} ${BOLD}${TEMP}°F${NC} ${GRAY}feels ${FEELS}°F · 💧${HUMID}% · 💨 ${WIND} km/h${NC}"
else
echo -e " ${GRAY}weather unavailable${NC}"
fi
echo -e "$SEP"
# ── Docker ────────────────────────────────────────────────────────────────────
echo -e " ${GREEN}${BOLD}🐳 Docker${NC}"
if command -v docker &> /dev/null; then
RUNNING=$(docker ps -q 2>/dev/null | wc -l)
TOTAL=$(docker ps -a -q 2>/dev/null | wc -l)
DOWN=$((TOTAL - RUNNING))
RESTART=$(docker ps --filter "status=restarting" -q 2>/dev/null | wc -l)
echo ""
echo -e " ${GREEN}${NC} ${RUNNING} running ${GRAY}·${NC} ${RED}${NC} ${DOWN} stopped ${GRAY}·${NC} 🔄 ${RESTART} restarting ${GRAY}·${NC} 📦 ${TOTAL} total"
echo ""
# Outdated images — parsed live from Watchtower logs
echo -e " ${CYAN}image updates${NC}"
if docker inspect watchtower &>/dev/null 2>&1; then
LAST_SCAN=$(docker logs watchtower --since 72h 2>&1 | grep "Session done" | tail -1 | grep -oP '\d{4}-\d{2}-\d{2}T\d{2}:\d{2}' || true)
OUTDATED_LIST=$(docker logs watchtower --since 36h 2>&1 | grep "Found new" | grep -oP '(?<=Found new )\S+(?= image)' || true)
if [ -n "$OUTDATED_LIST" ]; then
COUNT=$(echo "$OUTDATED_LIST" | wc -l)
echo -e " ${YELLOW}${COUNT} update(s) available${NC}"
while IFS= read -r img; do
CT=$(docker ps -a --format '{{.Names}}|{{.Image}}' | awk -F'|' -v i="$img" '$2==i {printf "%s ", $1}')
echo -e " ${GRAY} · ${img}${NC} ${CT}"
done <<< "$OUTDATED_LIST"
else
echo -e " ${GREEN}${NC} all images current"
fi
[ -n "$LAST_SCAN" ] && echo -e " ${GRAY}last scan: ${LAST_SCAN}${NC}"
echo -e " ${GRAY}details: bash /opt/stacks/scripts/update-check.sh${NC}"
else
echo -e " ${GRAY}watchtower not running${NC}"
fi
else
echo -e " ${RED}docker not available${NC}"
fi
echo -e "$SEP"
# ── System ────────────────────────────────────────────────────────────────────
echo -e " ${BLUE}${BOLD}💻 System${NC}"
echo ""
LOCAL_IP=$(hostname -I | awk '{print $1}')
EXT_IP=$(curl -s --max-time 3 ifconfig.me 2>/dev/null || echo "unavailable")
CPU_CORES=$(nproc 2>/dev/null || echo 1)
CPU_PCT=$(top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | awk '{printf "%.0f", 100 - $1}')
CPU_FILLED=$((CPU_PCT * 10 / 100))
CPU_EMPTY=$((10 - CPU_FILLED))
CPU_ON="$(printf '█%.0s' $(seq 1 $CPU_FILLED) 2>/dev/null)"
CPU_OFF="$(printf '░%.0s' $(seq 1 $CPU_EMPTY) 2>/dev/null)"
RAM_USED=$(free -m | awk 'NR==2 {printf "%.1f", $3/1024}')
RAM_TOTAL=$(free -m | awk 'NR==2 {printf "%.1f", $2/1024}')
RAM_PCT=$(free | awk 'NR==2 {printf "%.0f", $3/$2*100}')
RAM_FILLED=$((RAM_PCT * 10 / 100))
RAM_EMPTY=$((10 - RAM_FILLED))
BAR_ON="$(printf '█%.0s' $(seq 1 $RAM_FILLED) 2>/dev/null)"
BAR_OFF="$(printf '░%.0s' $(seq 1 $RAM_EMPTY) 2>/dev/null)"
LOAD=$(awk '{print $1" "$2" "$3}' /proc/loadavg)
LOAD1=$(awk '{print $1}' /proc/loadavg)
LOAD_PCT=$(awk -v l="$LOAD1" -v c="$CPU_CORES" 'BEGIN {v=int((l/c)*100); if(v>100) v=100; print v}')
LOAD_FILLED=$((LOAD_PCT * 10 / 100))
LOAD_EMPTY=$((10 - LOAD_FILLED))
LOAD_ON="$(printf '█%.0s' $(seq 1 $LOAD_FILLED) 2>/dev/null)"
LOAD_OFF="$(printf '░%.0s' $(seq 1 $LOAD_EMPTY) 2>/dev/null)"
TOP_NAME=$(ps aux --sort=-%cpu | awk 'NR==2 {split($11,a,"/"); print a[length(a)]}')
TOP_CPU=$(ps aux --sort=-%cpu | awk 'NR==2 {printf "%.1f%%", $3}')
printf " ${GRAY}%-10s${NC} %s\n" "uptime" "$(uptime -p | sed 's/up //')"
printf " ${GRAY}%-10s${NC} %s\n" "local ip" "$LOCAL_IP"
printf " ${GRAY}%-10s${NC} %s\n" "ext ip" "$EXT_IP"
echo ""
printf " ${GRAY}%-10s${NC} ${GREEN}${CPU_ON}${GRAY}${CPU_OFF}${NC} ${CPU_PCT}%%\n" "cpu"
printf " ${GRAY}%-10s${NC} ${GREEN}${BAR_ON}${GRAY}${BAR_OFF}${NC} ${RAM_USED}/${RAM_TOTAL} GB ${RAM_PCT}%%\n" "ram"
printf " ${GRAY}%-10s${NC} ${GREEN}${LOAD_ON}${GRAY}${LOAD_OFF}${NC} ${LOAD} ${GRAY}/ ${CPU_CORES} cores${NC}\n" "load"
echo ""
printf " ${GRAY}%-10s${NC} ${YELLOW}%s${NC} ${GRAY}%s${NC}\n" "top proc" "$TOP_NAME" "$TOP_CPU"
printf " ${GRAY}%-10s${NC} %s\n" "users" "$(who | wc -l) logged in"
echo -e "$SEP"
# ── Quote ─────────────────────────────────────────────────────────────────────
echo -e " ${PURPLE}${BOLD}✦ Quote${NC}"
echo ""
QDATA=$(curl -s --max-time 4 "https://zenquotes.io/api/today" 2>/dev/null)
if echo "$QDATA" | python3 -c "import sys,json; json.load(sys.stdin)" &>/dev/null; then
QUOTE=$(echo "$QDATA" | python3 -c "import sys,json; d=json.load(sys.stdin)[0]; print(d['q'])")
AUTHOR=$(echo "$QDATA" | python3 -c "import sys,json; d=json.load(sys.stdin)[0]; print(d['a'])")
echo -e " ${GRAY}\"${QUOTE}\"${NC}" | fold -s -w $((COLS - 4))
echo -e " ${PURPLE}${AUTHOR}${NC}"
else
echo -e " ${GRAY}unavailable${NC}"
fi
echo -e "$SEP"
echo ""