From 5b89ebf89a006a83097b965de342bae93bc95f5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80?= <78488229+viktor138irk@users.noreply.github.com> Date: Wed, 6 May 2026 19:13:37 +0900 Subject: [PATCH] Add one-command Virtuality healthcheck --- scripts/virtuality_healthcheck.sh | 296 ++++++++++++++++++++++++++++++ 1 file changed, 296 insertions(+) create mode 100644 scripts/virtuality_healthcheck.sh diff --git a/scripts/virtuality_healthcheck.sh b/scripts/virtuality_healthcheck.sh new file mode 100644 index 0000000..f3ff364 --- /dev/null +++ b/scripts/virtuality_healthcheck.sh @@ -0,0 +1,296 @@ +#!/usr/bin/env bash +set -uo pipefail + +# Virtuality Healthcheck +# One-command diagnostics for node, network, services, libvirt, storage and VM state. + +ERRORS=0 +WARNINGS=0 + +ESC="\033" +RESET="${ESC}[0m" +BOLD="${ESC}[1m" +DIM="${ESC}[2m" +GREEN="${ESC}[32m" +YELLOW="${ESC}[33m" +RED="${ESC}[31m" +CYAN="${ESC}[36m" +GRAY="${ESC}[90m" + +ok() { echo -e "${GREEN}[OK]${RESET} $*"; } +warn() { echo -e "${YELLOW}[WARN]${RESET} $*"; WARNINGS=$((WARNINGS+1)); } +err() { echo -e "${RED}[ERROR]${RESET} $*"; ERRORS=$((ERRORS+1)); } +info() { echo -e "${CYAN}[INFO]${RESET} $*"; } +section() { echo; echo -e "${BOLD}${CYAN}== $* ==${RESET}"; } + +has_cmd() { command -v "$1" >/dev/null 2>&1; } +service_exists() { systemctl list-unit-files "$1" >/dev/null 2>&1; } +service_active() { systemctl is-active "$1" >/dev/null 2>&1; } + +print_kv() { + printf " ${GRAY}%-22s${RESET} %s\n" "$1" "$2" +} + +check_service() { + local unit="$1" + local required="${2:-yes}" + if service_exists "$unit"; then + local state + state="$(systemctl is-active "$unit" 2>/dev/null || echo inactive)" + local enabled + enabled="$(systemctl is-enabled "$unit" 2>/dev/null || echo disabled)" + if [[ "$state" == "active" ]]; then + ok "$unit active / $enabled" + else + if [[ "$required" == "yes" ]]; then + err "$unit is $state / $enabled" + else + warn "$unit is $state / $enabled" + fi + fi + else + if [[ "$required" == "yes" ]]; then + err "$unit not found" + else + warn "$unit not found" + fi + fi +} + +check_socket_or_service() { + local socket="$1" + local service="$2" + if service_exists "$socket" && service_active "$socket"; then + ok "$socket active" + elif service_exists "$service" && service_active "$service"; then + ok "$service active" + else + err "neither $socket nor $service is active" + fi +} + +require_root_note() { + if [[ "$EUID" -ne 0 ]]; then + warn "running without root; some checks may be incomplete. Recommended: sudo vhealth" + fi +} + +section "Virtuality Healthcheck" +print_kv "Date" "$(date '+%Y-%m-%d %H:%M:%S')" +print_kv "Hostname" "$(hostname)" +if [[ -f /etc/os-release ]]; then + . /etc/os-release + print_kv "OS" "${PRETTY_NAME}" +fi +print_kv "Kernel" "$(uname -r)" +print_kv "Uptime" "$(uptime -p 2>/dev/null || true)" +require_root_note + +section "CPU / Virtualization" +if grep -E -q '(vmx|svm)' /proc/cpuinfo; then + ok "hardware virtualization flag found: vmx/svm" +else + err "hardware virtualization flag not found; enable virtualization in BIOS/UEFI" +fi +if [[ -e /dev/kvm ]]; then + ok "/dev/kvm exists" +else + err "/dev/kvm missing" +fi + +section "Required Commands" +for cmd in virsh qemu-system-x86_64 virt-install ip ss df free systemctl; do + if has_cmd "$cmd"; then + ok "$cmd -> $(command -v "$cmd")" + else + err "$cmd missing" + fi +done +for cmd in btop jq tree ncdu; do + if has_cmd "$cmd"; then + ok "$cmd installed" + else + warn "$cmd not installed" + fi +done + +section "Services" +check_socket_or_service "cockpit.socket" "cockpit.service" +check_socket_or_service "libvirtd.socket" "libvirtd.service" +check_socket_or_service "virtlogd.socket" "virtlogd.service" +check_service "virtuality-console-dashboard.service" "no" +check_service "libvirt-guests.service" "no" +if service_exists "fwupd-refresh.service"; then + state="$(systemctl is-active fwupd-refresh.service 2>/dev/null || echo inactive)" + failed="$(systemctl is-failed fwupd-refresh.service 2>/dev/null || echo unknown)" + if [[ "$failed" == "failed" ]]; then + warn "fwupd-refresh.service failed; not critical for Virtuality" + else + ok "fwupd-refresh.service state: $state" + fi +else + warn "fwupd-refresh.service not found; firmware refresh checks skipped" +fi + +section "Network" +if has_cmd ip; then + echo "$(ip -br a)" | sed 's/^/ /' + default_route="$(ip route | awk '/default/ {print $0; exit}')" + if [[ -n "$default_route" ]]; then + ok "default route: $default_route" + else + err "default route missing" + fi + + if ip link show br0 >/dev/null 2>&1; then + br_state="$(cat /sys/class/net/br0/operstate 2>/dev/null || echo unknown)" + br_ip="$(ip -4 addr show br0 | awk '/inet / {print $2; exit}')" + if [[ -n "$br_ip" ]]; then + ok "br0 exists, state=$br_state, ip=$br_ip" + else + warn "br0 exists but has no IPv4 address" + fi + if ip route | grep -q 'default .* br0'; then + ok "default route uses br0" + else + warn "default route does not use br0" + fi + else + warn "br0 not found; VMs may use NAT virbr0 only" + fi + + if ip link show virbr0 >/dev/null 2>&1; then + ok "virbr0 exists" + else + warn "virbr0 not found" + fi +else + err "ip command missing" +fi + +section "Netplan" +if [[ -d /etc/netplan ]]; then + ls -la /etc/netplan | sed 's/^/ /' + if compgen -G "/etc/netplan/*.yaml" >/dev/null; then + bad_perm=0 + for file in /etc/netplan/*.yaml; do + perm="$(stat -c '%a' "$file" 2>/dev/null || echo unknown)" + if [[ "$perm" != "600" ]]; then + warn "$file permission is $perm, recommended 600" + bad_perm=1 + fi + done + if [[ "$bad_perm" -eq 0 ]]; then + ok "netplan yaml permissions look safe" + fi + if netplan generate >/tmp/virtuality-netplan-check.log 2>&1; then + ok "netplan generate passed" + else + err "netplan generate failed" + sed 's/^/ /' /tmp/virtuality-netplan-check.log + fi + else + err "no netplan yaml files found" + fi +else + warn "/etc/netplan not found" +fi + +section "Firewall" +if has_cmd ufw; then + ufw_status="$(ufw status | head -n 1 || true)" + if echo "$ufw_status" | grep -qi active; then + ok "ufw active" + else + warn "ufw not active: $ufw_status" + fi + ufw status | sed 's/^/ /' | head -n 20 +else + warn "ufw not installed" +fi + +section "Cockpit" +if ss -tulpn 2>/dev/null | grep -q ':9090'; then + ok "port 9090 is listening" +else + warn "port 9090 is not listening; cockpit may still be socket-activated" +fi +host_ip="$(hostname -I 2>/dev/null | awk '{print $1}')" +if [[ -n "$host_ip" ]]; then + info "Cockpit URL: https://${host_ip}:9090" +fi + +section "libvirt" +if has_cmd virsh; then + if virsh list --all >/tmp/virtuality-virsh-list.log 2>&1; then + ok "virsh can list VMs" + cat /tmp/virtuality-virsh-list.log | sed 's/^/ /' + else + err "virsh list failed" + sed 's/^/ /' /tmp/virtuality-virsh-list.log + fi + + if virsh pool-list --all >/tmp/virtuality-pool-list.log 2>&1; then + ok "virsh can list storage pools" + cat /tmp/virtuality-pool-list.log | sed 's/^/ /' + if virsh pool-info virtuality-images >/dev/null 2>&1; then + ok "pool virtuality-images exists" + else + warn "pool virtuality-images missing" + fi + if virsh pool-info virtuality-iso >/dev/null 2>&1; then + ok "pool virtuality-iso exists" + else + warn "pool virtuality-iso missing" + fi + else + err "virsh pool-list failed" + sed 's/^/ /' /tmp/virtuality-pool-list.log + fi + + if virsh net-list --all >/tmp/virtuality-net-list.log 2>&1; then + ok "virsh can list networks" + cat /tmp/virtuality-net-list.log | sed 's/^/ /' + else + warn "virsh net-list failed" + sed 's/^/ /' /tmp/virtuality-net-list.log + fi +else + err "virsh missing" +fi + +section "Virtuality Paths" +for dir in /opt/virtuality /var/lib/virtuality /var/lib/virtuality/iso /var/lib/virtuality/images /var/lib/virtuality/backups /var/log/virtuality; do + if [[ -d "$dir" ]]; then + size="$(du -sh "$dir" 2>/dev/null | awk '{print $1}')" + ok "$dir exists, size=$size" + else + err "$dir missing" + fi +done + +section "Disk / Memory" +free -h | sed 's/^/ /' +df -h / /var/lib/virtuality 2>/dev/null | sed 's/^/ /' +root_use="$(df / | awk 'NR==2 {gsub("%", "", $5); print $5}')" +if [[ "$root_use" =~ ^[0-9]+$ ]]; then + if (( root_use >= 90 )); then + err "root filesystem usage is ${root_use}%" + elif (( root_use >= 75 )); then + warn "root filesystem usage is ${root_use}%" + else + ok "root filesystem usage is ${root_use}%" + fi +fi + +section "Summary" +if (( ERRORS > 0 )); then + echo -e "${RED}${BOLD}Virtuality health: ERROR${RESET} — errors=${ERRORS}, warnings=${WARNINGS}" + exit 2 +elif (( WARNINGS > 0 )); then + echo -e "${YELLOW}${BOLD}Virtuality health: WARNING${RESET} — warnings=${WARNINGS}" + exit 1 +else + echo -e "${GREEN}${BOLD}Virtuality health: OK${RESET}" + exit 0 +fi