#!/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¤t=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 ""