|
1 | 1 | #!/bin/bash |
2 | | -# MiOS v0.2.0 — Enhanced MOTD Dashboard |
3 | | -# Natively supports Role, MOK, and Update status (v0.2.0 baseline). |
| 2 | +# MiOS v0.2.2 -- System Dashboard (MOTD) |
| 3 | +# ASCII-safe, tty0/console compatible. |
| 4 | +# Suppress: export MIOS_NO_MOTD=1 |
4 | 5 | [[ $- != *i* ]] && exit 0 |
5 | | -[[ -n "$MIOS_NO_MOTD" ]] && exit 0 |
| 6 | +[[ -n "${MIOS_NO_MOTD:-}" ]] && exit 0 |
6 | 7 |
|
7 | | -_ip() { ip -4 route get v0.2.0.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="src") print $(i+1)}' | head -1; } |
8 | | -IP=$(_ip); [[ -z "$IP" ]] && IP="v0.2.0.1" |
9 | | -HN=$(hostname -s 2>/dev/null || echo "MiOS") |
10 | | -_l() { printf '\e]8;;%s\e\\%s\e]8;;\e\\' "$1" "$2"; } |
11 | | -C=$'\033[0;36m';W=$'\033[1;37m';G=$'\033[0;32m';Y=$'\033[0;33m';R=$'\033[0;31m';D=$'\033[0;90m';N=$'\033[0m' |
| 8 | +# ── Colors (ANSI basic -- safe for all TTYs) ───────────────────────────────── |
| 9 | +C=$'\033[0;36m' # cyan |
| 10 | +W=$'\033[1;37m' # bold white |
| 11 | +G=$'\033[0;32m' # green |
| 12 | +Y=$'\033[0;33m' # yellow |
| 13 | +R=$'\033[0;31m' # red |
| 14 | +D=$'\033[0;90m' # dark gray |
| 15 | +N=$'\033[0m' # reset |
12 | 16 |
|
13 | | -_s() { |
| 17 | +# ── Helpers ─────────────────────────────────────────────────────────────────── |
| 18 | +_ip() { |
| 19 | + ip -4 route get 1.1.1.1 2>/dev/null \ |
| 20 | + | awk '{for(i=1;i<=NF;i++) if($i=="src") print $(i+1)}' \ |
| 21 | + | head -1 |
| 22 | +} |
| 23 | +_svc() { # _svc <unit> -- prints [OK] or [ ] in color |
14 | 24 | if systemctl is-active --quiet "$1" 2>/dev/null; then |
15 | | - printf '%s' "${G}●${N}" |
| 25 | + printf '%s[OK]%s' "$G" "$N" |
16 | 26 | else |
17 | | - printf '%s' "${D}○${N}" |
| 27 | + printf '%s[ ]%s' "$D" "$N" |
18 | 28 | fi |
19 | 29 | } |
| 30 | +_cnt() { printf '%d' "$(systemctl is-active --quiet "$1" 2>/dev/null && echo 1 || echo 0)"; } |
| 31 | +_bar() { # _bar <used_pct> <width> |
| 32 | + local pct=$1 w=$2 filled empty |
| 33 | + filled=$(( pct * w / 100 )) |
| 34 | + empty=$(( w - filled )) |
| 35 | + printf '['; printf '%*s' "$filled" '' | tr ' ' '='; printf '%*s' "$empty" '' | tr ' ' '-'; printf '] %3d%%' "$pct" |
| 36 | +} |
20 | 37 |
|
21 | | -# ── Header ────────────────────────────────────────────────────────────────── |
22 | | -echo "" |
23 | | -printf '%s\n' "${C}════════════════════════════════════════════════════════════${N}" |
24 | | -printf '%s\n' "${W} MiOS v0.2.0${N} ${D}${HN} — ${IP}${N}" |
25 | | -printf '%s\n' "${C}════════════════════════════════════════════════════════════${N}" |
| 38 | +IP=$( _ip); [[ -z "$IP" ]] && IP="(no route)" |
| 39 | +HN=$( hostname -s 2>/dev/null || echo "mios") |
| 40 | +VIRT=$(systemd-detect-virt 2>/dev/null || echo "none") |
26 | 41 |
|
27 | | -# ── Status Row (Role, Update, MOK) ────────────────────────────────────────── |
28 | | -ROLE=$(grep "^ROLE=" /var/lib/mios/role.active 2>/dev/null | cut -d= -f2 || echo "unknown") |
29 | | -case "$ROLE" in |
30 | | - desktop) ROLE_STR="${G}Desktop${N}" ;; |
31 | | - headless) ROLE_STR="${W}Headless${N}" ;; |
32 | | - k3s*) ROLE_STR="${C}K3s Master${N}" ;; |
33 | | - *) ROLE_STR="${Y}${ROLE}${N}" ;; |
34 | | -esac |
| 42 | +# ── Hardware stats ──────────────────────────────────────────────────────────── |
| 43 | +CPU_PCT=$(top -bn1 2>/dev/null | awk '/^%Cpu/{printf "%.0f", 100-$8}' || echo "?") |
| 44 | +MEM_TOTAL=$(awk '/MemTotal/{print int($2/1024)}' /proc/meminfo 2>/dev/null || echo "?") |
| 45 | +MEM_AVAIL=$(awk '/MemAvailable/{print int($2/1024)}' /proc/meminfo 2>/dev/null || echo "?") |
| 46 | +MEM_USED=$(( MEM_TOTAL - MEM_AVAIL )) |
| 47 | +MEM_PCT=$(( MEM_TOTAL > 0 ? MEM_USED * 100 / MEM_TOTAL : 0 )) |
| 48 | +DISK_PCT=$(df / 2>/dev/null | awk 'NR==2{sub(/%/,"",$5); print $5}' || echo "?") |
| 49 | +UPTIME=$(uptime -p 2>/dev/null | sed 's/^up //' || echo "?") |
35 | 50 |
|
36 | | -MOK_STATUS="${R}✗ Unverified${N}" |
37 | | -if command -v mokutil &>/dev/null; then |
38 | | - if mokutil --sb-state 2>/dev/null | grep -q "enabled"; then |
39 | | - MOK_STATUS="${G}✓ Secure Boot${N}" |
| 51 | +# ── MiOS-specific state ─────────────────────────────────────────────────────── |
| 52 | +ROLE=$( grep -m1 "^ROLE=" /var/lib/mios/role.active 2>/dev/null | cut -d= -f2 || echo "default") |
| 53 | +IMG_REF=$(bootc status --json 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('booted',{}).get('image',{}).get('image',{}).get('image',''))" 2>/dev/null || echo "") |
| 54 | +[[ -z "$IMG_REF" ]] && IMG_REF=$(cat /usr/lib/os-release 2>/dev/null | grep PRETTY | cut -d= -f2 | tr -d '"' || echo "MiOS $MiosVersion") |
| 55 | + |
| 56 | +UPDATE_STR="${D}[ ] current${N}" |
| 57 | +if command -v bootc &>/dev/null; then |
| 58 | + STAGED=$(bootc status --json 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('staged') or '')" 2>/dev/null) |
| 59 | + if [[ -n "$STAGED" && "$STAGED" != "None" ]]; then |
| 60 | + UPDATE_STR="${Y}[!!] update staged${N}" |
40 | 61 | else |
41 | | - MOK_STATUS="${Y}○ SB Disabled${N}" |
| 62 | + UPDATE_STR="${G}[OK] up-to-date${N}" |
42 | 63 | fi |
43 | 64 | fi |
44 | 65 |
|
45 | | -UP_STATUS="${D}Check with 'mios update'${N}" |
46 | | -if command -v bootc &>/dev/null; then |
47 | | - STAGED=$(bootc status --json 2>/dev/null | jq -r '.staged.image.image.repository' 2>/dev/null) |
48 | | - if [[ "$STAGED" != "null" && -n "$STAGED" ]]; then |
49 | | - UP_STATUS="${Y}⚠ Update Staged${N}" |
| 66 | +BOOT_STR="${D}[ ] standard${N}" |
| 67 | +if command -v mokutil &>/dev/null; then |
| 68 | + if mokutil --sb-state 2>/dev/null | grep -q "enabled"; then |
| 69 | + BOOT_STR="${G}[OK] Secure Boot${N}" |
50 | 70 | else |
51 | | - UP_STATUS="${G}✓ Up-to-date${N}" |
| 71 | + BOOT_STR="${Y}[ ] SB off${N}" |
52 | 72 | fi |
53 | 73 | fi |
54 | 74 |
|
55 | | -printf ' Role: %-15s Update: %-15s Boot: %s\n' "$ROLE_STR" "$UP_STATUS" "$MOK_STATUS" |
56 | | -echo "" |
57 | | - |
58 | | -# ── Management ────────────────────────────────────────────────────────────── |
59 | | -printf '%s\n' "${W} Management Interfaces${N}" |
60 | | -printf '%s\n' " $(_s cockpit.socket) Cockpit $(_l "https://${IP}:9090" "https://${IP}:9090")" |
61 | | -printf '%s\n' " $(_s sshd) SSH $(_l "ssh://${IP}" "${IP}:22")" |
62 | | - |
63 | | -if systemctl is-active --quiet gnome-remote-desktop 2>/dev/null; then |
64 | | - printf '%s\n' " ${G}●${N} Remote Login $(_l "rdp://${IP}:3389" "${IP}:3389") ${D}(GRD)${N}" |
| 75 | +# ── AI stack ────────────────────────────────────────────────────────────────── |
| 76 | +AI_EP=$(grep -m1 "^endpoint" /etc/mios/ai/config.json 2>/dev/null | grep -oP 'http[^"]+' || echo "http://localhost:8080/v1") |
| 77 | +AI_MODEL=$(grep -m1 "^MIOS_AI_MODEL" /etc/mios/install.env 2>/dev/null | cut -d= -f2 | tr -d '"' || echo "qwen2.5-coder:7b") |
| 78 | +AI_PING="${D}[ ] offline${N}" |
| 79 | +if curl -sf --max-time 2 "${AI_EP}/models" &>/dev/null; then |
| 80 | + AI_PING="${G}[OK] online${N}" |
65 | 81 | fi |
66 | 82 |
|
67 | | -if podman ps --format '{{.Names}}' 2>/dev/null | grep -q mios-guacamole; then |
68 | | - printf '%s\n' " ${G}●${N} Browser Desktop $(_l "http://${IP}:8080/guacamole/" "http://${IP}:8080/guacamole/")" |
69 | | -fi |
| 83 | +CONTAINERS=$(podman ps -q 2>/dev/null | wc -l) |
| 84 | + |
| 85 | +# ── Draw ────────────────────────────────────────────────────────────────────── |
| 86 | +W60="============================================================" |
| 87 | +S60="------------------------------------------------------------" |
70 | 88 |
|
71 | | -# ── Infrastructure ────────────────────────────────────────────────────────── |
72 | 89 | echo "" |
73 | | -printf '%s\n' "${W} Infrastructure Status${N}" |
74 | | -printf '%s\n' " $(_s firewalld) Firewall ${D}firewall-cmd --list-all${N}" |
75 | | -printf '%s\n' " $(_s podman.socket) Podman ${D}$(podman ps -q 2>/dev/null | wc -l) containers${N}" |
76 | | -printf '%s\n' " $(_s libvirtd.socket) Libvirt ${D}virt-manager / virsh${N}" |
| 90 | +printf '%s\n' "${C}+${W60}+${N}" |
| 91 | +printf '%s\n' "${W}| MiOS v0.2.2 -- ${HN}$(printf '%*s' $((42 - ${#HN})) '')${D}${IP}${N} ${W}|${N}" |
| 92 | +printf '%s\n' "${C}+${S60}+${N}" |
| 93 | +printf " %-12s %s\n" "Role:" "$ROLE" |
| 94 | +printf " %-12s %s\n" "Image:" "$IMG_REF" |
| 95 | +printf " %-12s %s %-12s %s\n" "Update:" "$UPDATE_STR" "Boot:" "$BOOT_STR" |
| 96 | +printf " %-12s %s\n" "Virt:" "$VIRT" |
| 97 | +printf '%s\n' "${C}+${S60}+${N}" |
| 98 | +echo "" |
| 99 | +printf ' %s\n' "${W}System Resources${N}" |
| 100 | +printf ' %-10s ' "CPU:"; _bar "${CPU_PCT:-0}" 30; echo "" |
| 101 | +printf ' %-10s ' "Memory:"; _bar "$MEM_PCT" 30; printf ' %dM / %dM\n' "$MEM_USED" "$MEM_TOTAL" |
| 102 | +printf ' %-10s ' "Disk /": _bar "${DISK_PCT:-0}" 30; echo " $(df -h / 2>/dev/null | awk 'NR==2{print $3"/"$2}')" |
| 103 | +printf ' %-10s %s\n' "Uptime:" "$UPTIME" |
| 104 | +echo "" |
| 105 | +printf '%s\n' "${C}+${S60}+${N}" |
| 106 | +printf ' %s\n' "${W}Services${N}" |
| 107 | +printf ' %s %-20s %s\n' "$(_svc cockpit.socket)" "Cockpit" "https://${IP}:9090" |
| 108 | +printf ' %s %-20s %s\n' "$(_svc sshd)" "SSH" "${IP}:22" |
| 109 | +printf ' %s %-20s %s\n' "$(_svc podman.socket)" "Podman" "${CONTAINERS} containers running" |
| 110 | +printf ' %s %-20s %s\n' "$(_svc firewalld)" "Firewall" "firewall-cmd --list-all" |
| 111 | +printf ' %s %-20s %s\n' "$(_svc libvirtd.socket)" "Libvirt" "virsh / virt-manager" |
| 112 | +printf ' %s %-20s %s\n' "$(_svc ollama.service)" "Ollama" "ollama list" |
| 113 | +printf ' %s %-20s %s\n' "$(_svc mios-ai.service)" "LocalAI" "$AI_EP model: $AI_MODEL" |
77 | 114 |
|
78 | | -# Optional |
79 | | -if systemctl is-active --quiet k3s.service 2>/dev/null; then |
80 | | - printf '%s\n' " ${G}●${N} K3s API $(_l "https://${IP}:6443" "https://${IP}:6443")" |
81 | | -fi |
| 115 | +# AI status |
| 116 | +printf ' %-24s %s\n' " AI endpoint:" "$AI_PING" |
| 117 | + |
| 118 | +# Optional services |
| 119 | +systemctl is-active --quiet k3s.service 2>/dev/null && \ |
| 120 | + printf ' %s %-20s %s\n' "${G}[OK]${N}" "K3s API" "https://${IP}:6443" |
| 121 | + |
| 122 | +systemctl is-active --quiet gnome-remote-desktop 2>/dev/null && \ |
| 123 | + printf ' %s %-20s %s\n' "${G}[OK]${N}" "Remote Desktop (GRD)" "${IP}:3389 (RDP)" |
82 | 124 |
|
83 | 125 | echo "" |
84 | | -printf '%s\n' " ${D}Type 'mios' for management options. Output hidden via MIOS_NO_MOTD=1${N}" |
| 126 | +printf '%s\n' "${C}+${S60}+${N}" |
| 127 | +printf ' %s\n' "${D}Commands: mios | just build | bootc upgrade | MIOS_NO_MOTD=1 to suppress${N}" |
| 128 | +printf '%s\n' "${C}+${W60}+${N}" |
85 | 129 | echo "" |
0 commit comments