moonarch/scripts/moonarch-doctor
nevaforget 0433f08f08
All checks were successful
Update PKGBUILD version / update-pkgver (push) Successful in 4s
feat: manage wlsunset via systemd user service
Move nightlight from niri spawn-at-startup to a systemd user service
with After=kanshi.service to ensure all outputs are configured before
wlsunset starts. Toggle now uses enable/disable --now for persistent
state across reboots.
2026-04-14 17:42:24 +02:00

319 lines
7.9 KiB
Bash
Executable File

#!/bin/bash
# ABOUTME: Moonarch system health checker — verifies services, configs, packages and paths.
# ABOUTME: Shipped as /usr/bin/moonarch-doctor (alias: moondoc) by the moonarch-git package.
set -uo pipefail
# --- Output helpers ---
PASS=0
FAIL=0
WARN=0
pass() {
echo -e " \e[1;32m✓\e[0m $*"
((PASS++))
}
fail() {
echo -e " \e[1;31m✗\e[0m $*"
((FAIL++))
}
warn() {
echo -e " \e[1;33m⚠\e[0m $*"
((WARN++))
}
section() {
echo
echo -e "\e[1;34m[$1]\e[0m"
}
# --- Check functions ---
check_system_service() {
local svc="$1"
local type
type=$(systemctl show "$svc" --property=Type 2>/dev/null | cut -d= -f2)
if systemctl is-enabled "$svc" &>/dev/null; then
if systemctl is-active "$svc" &>/dev/null; then
pass "$svc (enabled, active)"
elif [[ "$type" == "oneshot" ]]; then
# Oneshot services are inactive after completion — check if they succeeded
local result
result=$(systemctl show "$svc" --property=Result 2>/dev/null | cut -d= -f2)
if [[ "$result" == "success" ]]; then
pass "$svc (enabled, oneshot completed)"
else
fail "$svc (enabled, oneshot failed: $result)"
fi
else
fail "$svc (enabled, NOT active)"
fi
else
fail "$svc (NOT enabled)"
fi
}
check_user_service() {
local svc="$1"
if systemctl --user is-enabled "$svc" &>/dev/null; then
if systemctl --user is-active "$svc" &>/dev/null; then
pass "$svc (enabled, active)"
else
# User services may be inactive if not in a graphical session
warn "$svc (enabled, not active — may need graphical session)"
fi
else
fail "$svc (NOT enabled)"
fi
}
check_config_match() {
local deployed="$1"
local source="$2"
local label="${deployed}"
if [[ ! -f "$deployed" ]]; then
fail "$label (missing)"
return
fi
if [[ ! -f "$source" ]]; then
warn "$label (exists, but no source to compare at $source)"
return
fi
local hash_deployed hash_source
hash_deployed=$(sha256sum "$deployed" | cut -d' ' -f1)
hash_source=$(sha256sum "$source" | cut -d' ' -f1)
if [[ "$hash_deployed" == "$hash_source" ]]; then
pass "$label"
else
warn "$label (differs from moonarch default)"
fi
}
# --- Header ---
echo -e "\e[1;34m"
echo " Moonarch Doctor"
echo -e "\e[0m"
# --- 1. Packages ---
section "Packages"
OFFICIAL="/usr/share/moonarch/official.txt"
AUR="/usr/share/moonarch/aur.txt"
if [[ -f "$OFFICIAL" ]]; then
INSTALLED=$(pacman -Qq 2>/dev/null)
MISSING_OFFICIAL=()
while IFS= read -r pkg; do
[[ "$pkg" =~ ^[[:space:]]*# ]] && continue
[[ -z "${pkg// }" ]] && continue
if ! echo "$INSTALLED" | grep -qx "$pkg"; then
MISSING_OFFICIAL+=("$pkg")
fi
done < "$OFFICIAL"
if [[ ${#MISSING_OFFICIAL[@]} -eq 0 ]]; then
pass "All official packages installed"
else
fail "Missing official packages: ${MISSING_OFFICIAL[*]}"
fi
else
warn "$OFFICIAL not found (moonarch-git not installed?)"
fi
if [[ -f "$AUR" ]]; then
MISSING_AUR=()
while IFS= read -r pkg; do
[[ "$pkg" =~ ^[[:space:]]*# ]] && continue
[[ -z "${pkg// }" ]] && continue
if ! echo "$INSTALLED" | grep -qx "$pkg"; then
MISSING_AUR+=("$pkg")
fi
done < "$AUR"
if [[ ${#MISSING_AUR[@]} -eq 0 ]]; then
pass "All AUR packages installed"
else
fail "Missing AUR packages: ${MISSING_AUR[*]}"
fi
else
warn "$AUR not found (moonarch-git not installed?)"
fi
ORPHANS=$(pacman -Qdtq 2>/dev/null || true)
if [[ -z "$ORPHANS" ]]; then
pass "No orphaned packages"
else
ORPHAN_COUNT=$(echo "$ORPHANS" | wc -l)
warn "$ORPHAN_COUNT orphaned package(s) (run moonup to clean)"
fi
# --- 2. System Services ---
section "System Services"
for svc in NetworkManager bluetooth greetd systemd-timesyncd ufw auto-cpufreq; do
check_system_service "$svc"
done
# Battery conservation service (laptop only)
if [[ -f /sys/class/power_supply/BAT0/charge_control_end_threshold ]]; then
check_system_service moonarch-batsaver
else
pass "moonarch-batsaver (skipped — no battery threshold support)"
fi
# --- 3. User Services ---
section "User Services"
if [[ $EUID -eq 0 ]]; then
warn "Running as root — skipping user service checks"
else
for svc in kanshi wlsunset stasis cliphist-text cliphist-image; do
check_user_service "$svc"
done
fi
# --- 4. Config Files ---
section "Config Files"
SRC="/usr/share/moonarch"
check_config_match "/etc/xdg/foot/foot.ini" "$SRC/foot/foot.ini"
check_config_match "/etc/xdg/waybar/style.css" "$SRC/waybar/style.css"
check_config_match "/etc/xdg/swaync/config.json" "$SRC/swaync/config.json"
check_config_match "/etc/xdg/swaync/style.css" "$SRC/swaync/style.css"
check_config_match "/etc/greetd/config.toml" "$SRC/greetd/config.toml"
check_config_match "/etc/greetd/niri-greeter.kdl" "$SRC/greetd/niri-greeter.kdl"
check_config_match "/etc/moongreet/moongreet.toml" "$SRC/moongreet/moongreet.toml"
if [[ -f /etc/zsh/zshrc.moonarch ]]; then
pass "/etc/zsh/zshrc.moonarch"
else
fail "/etc/zsh/zshrc.moonarch (missing)"
fi
# --- 5. Helper Scripts ---
section "Helper Scripts"
EXPECTED_SCRIPTS=(
moonarch-batsaver-toggle
moonarch-btnote
moonarch-capsnote
moonarch-cpugov
moonarch-doctor
moonarch-nightlight
moonarch-sink-switcher
moonarch-update
moonarch-vpn
moonarch-waybar
moonarch-waybar-batsaver
moonarch-waybar-cpugov
moonarch-waybar-gpustat
moonarch-waybar-hidpp
moonarch-waybar-nightlight
moonarch-waybar-updates
)
MISSING_SCRIPTS=()
for script in "${EXPECTED_SCRIPTS[@]}"; do
if [[ ! -x "/usr/bin/$script" ]]; then
MISSING_SCRIPTS+=("$script")
fi
done
if [[ ${#MISSING_SCRIPTS[@]} -eq 0 ]]; then
pass "All ${#EXPECTED_SCRIPTS[@]} helper scripts present"
else
fail "Missing scripts: ${MISSING_SCRIPTS[*]}"
fi
# Symlinks
for link in moonup moondoc; do
if [[ -L "/usr/bin/$link" ]]; then
pass "$link symlink"
else
fail "$link symlink (missing)"
fi
done
# --- 6. System Config ---
section "System Config"
# UFW (check via systemd, no sudo needed)
if command -v ufw &>/dev/null; then
if systemctl is-active ufw &>/dev/null; then
pass "UFW active"
else
fail "UFW not active"
fi
else
fail "UFW not installed"
fi
# Pacman moonarch repo
if grep -q '^\[moonarch\]' /etc/pacman.conf 2>/dev/null; then
pass "Pacman [moonarch] repo configured"
else
fail "Pacman [moonarch] repo missing from /etc/pacman.conf"
fi
# Paru PKGBUILD repo
if grep -q '\[moonarch-pkgbuilds\]' /etc/paru.conf 2>/dev/null; then
pass "Paru [moonarch-pkgbuilds] repo configured"
else
fail "Paru [moonarch-pkgbuilds] repo missing from /etc/paru.conf"
fi
# Default shell
USER_SHELL=$(getent passwd "$USER" | cut -d: -f7)
if [[ "$USER_SHELL" == */zsh ]]; then
pass "Default shell: zsh"
else
warn "Default shell: $USER_SHELL (expected zsh)"
fi
# --- 7. Directories ---
section "Directories"
if [[ -d /var/lib/moonarch ]]; then
DIR_PERMS=$(stat -c '%a' /var/lib/moonarch)
DIR_GROUP=$(stat -c '%G' /var/lib/moonarch)
if [[ "$DIR_GROUP" == "wheel" ]]; then
pass "/var/lib/moonarch/ (group: wheel, mode: $DIR_PERMS)"
else
warn "/var/lib/moonarch/ (group: $DIR_GROUP, expected wheel)"
fi
else
warn "/var/lib/moonarch/ missing (created on first battery toggle)"
fi
if [[ -d /usr/share/moonarch ]]; then
pass "/usr/share/moonarch/"
else
fail "/usr/share/moonarch/ missing (moonarch-git not installed?)"
fi
# --- Summary ---
echo
TOTAL=$((PASS + FAIL + WARN))
echo -e "\e[1;34m[Summary]\e[0m $TOTAL checks: \e[1;32m$PASS passed\e[0m, \e[1;31m$FAIL failed\e[0m, \e[1;33m$WARN warnings\e[0m"
if [[ $FAIL -gt 0 ]]; then
exit 1
fi