swaylock reads ~/.swaylock/config, $XDG_CONFIG_HOME/swaylock/config and SYSCONFDIR/swaylock/config — never /etc/xdg/, where the generic XDG sweep put it. The config was made effective by seeding it into user homes, but both seeds are conditional, so a machine that pulled no moonarch-git update since swaylock arrived ran with no config at all: no wallpaper, no Catppuccin ring. The config now lives in defaults/etc/swaylock/config and is owned at /etc/swaylock/config, which the package alone makes effective. Home copies still win by search order, so the .install hook retires the pristine ones; an edited copy stays. Upstream swaylock has no blur (effect-blur is swaylock-effects only), so the blur is baked into a second image: wallpaper-blur.jpg, generated with downsample-blur-upsample. Regenerate it when the wallpaper changes. moonarch-doctor gains check_swaylock_config, which names the config file that actually wins and fails when none exists.
333 lines
9.0 KiB
Bash
Executable File
333 lines
9.0 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
|
|
}
|
|
|
|
# swaylock reads ~/.swaylock/config, $XDG_CONFIG_HOME/swaylock/config and
|
|
# /etc/swaylock/config, in that order. Moonarch owns the last one, so a copy in
|
|
# the home shadows it — silently, and a stale one from the retired seeding drops
|
|
# the blurred wallpaper. Report the file that actually wins.
|
|
check_swaylock_config() {
|
|
local system="/etc/swaylock/config"
|
|
local home_cfg
|
|
for home_cfg in "$HOME/.swaylock/config" "${XDG_CONFIG_HOME:-$HOME/.config}/swaylock/config"; do
|
|
if [[ -f "$home_cfg" ]]; then
|
|
warn "swaylock config: $home_cfg shadows $system"
|
|
return
|
|
fi
|
|
done
|
|
|
|
if [[ -f "$system" ]]; then
|
|
pass "swaylock config: $system"
|
|
else
|
|
fail "swaylock config: $system (missing — swaylock starts unconfigured)"
|
|
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"
|
|
|
|
# Hoist INSTALLED so the AUR block below can use it even if OFFICIAL is absent —
|
|
# otherwise `set -u` aborts the script when $INSTALLED is referenced unset.
|
|
INSTALLED=$(pacman -Qq 2>/dev/null)
|
|
|
|
if [[ -f "$OFFICIAL" ]]; then
|
|
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
|
|
|
|
# Camera switch-off service (only on machines with a camera)
|
|
if compgen -G "/sys/class/video4linux/video*" >/dev/null; then
|
|
check_system_service moonarch-camera
|
|
else
|
|
pass "moonarch-camera (skipped — no camera)"
|
|
fi
|
|
|
|
# --- 3. User Services ---
|
|
|
|
section "User Services"
|
|
|
|
if [[ $EUID -eq 0 ]]; then
|
|
warn "Running as root — skipping user service checks"
|
|
elif pacman -Qq moonarch-git &>/dev/null; then
|
|
EXPECTED_SVCS=()
|
|
while IFS= read -r svc_file; do
|
|
EXPECTED_SVCS+=("$(basename "$svc_file" .service)")
|
|
done < <(pacman -Qql moonarch-git | grep -E '^/etc/systemd/user/[^/]+\.service$')
|
|
# Services enabled by post-install.sh from other packages
|
|
EXPECTED_SVCS+=(stasis wayland-pipewire-idle-inhibit)
|
|
|
|
for svc in "${EXPECTED_SVCS[@]}"; do
|
|
check_user_service "$svc"
|
|
done
|
|
else
|
|
warn "moonarch-git not installed — skipping user service checks"
|
|
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/greetd/regreet.toml" "$SRC/greetd/regreet.toml"
|
|
check_config_match "/etc/greetd/regreet.css" "$SRC/greetd/regreet.css"
|
|
|
|
check_swaylock_config
|
|
|
|
# --- 5. Helper Scripts ---
|
|
|
|
section "Helper Scripts"
|
|
|
|
if pacman -Qq moonarch-git &>/dev/null; then
|
|
EXPECTED_SCRIPTS=()
|
|
while IFS= read -r script; do
|
|
EXPECTED_SCRIPTS+=("$(basename "$script")")
|
|
done < <(pacman -Qql moonarch-git | grep -E '^/usr/bin/moonarch-[^/]+$')
|
|
|
|
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
|
|
else
|
|
warn "moonarch-git not installed — skipping helper script checks"
|
|
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
|
|
|
|
# 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
|
|
|
|
# --- 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
|