utility: checking all dependencies

This commit is contained in:
2026-08-27 00:38:50 +01:00
parent 3601f52600
commit d3856ada0d

View File

@@ -0,0 +1,259 @@
#!/bin/bash
# Pre-flight check for everything this quickshell config depends on outside
# its own QML — fonts, the compositor, backing D-Bus services, and the
# external CLIs individual bar modules shell out to. Run this after
# installing/stowing quickshell on a new machine, before assuming the bar
# is just going to work.
#
# Usage: ./CheckQuickshellSetup.sh [path-to-quickshell-config]
# Defaults to $XDG_CONFIG_HOME/quickshell or ~/.config/quickshell.
set -uo pipefail
QS_DIR="${1:-${XDG_CONFIG_HOME:-$HOME/.config}/quickshell}"
PASS=0
WARN=0
FAIL=0
c_green="\033[0;32m"; c_yellow="\033[0;33m"; c_red="\033[0;31m"; c_reset="\033[0m"
pass() { printf " ${c_green}✓${c_reset} %s\n" "$1"; PASS=$((PASS+1)); }
warn() { printf " ${c_yellow}!${c_reset} %s\n" "$1"; WARN=$((WARN+1)); }
fail() { printf " ${c_red}✗${c_reset} %s\n" "$1"; FAIL=$((FAIL+1)); }
section() { printf "\n%s\n" "$1"; }
svc_state() {
# active / inactive / missing / no-systemctl. $1 = unit, $2 = --user (optional)
# NB: capture-then-here-string, not `cmd | grep -q`. Under `set -o
# pipefail`, grep -q exits the instant it finds a match, SIGPIPE-ing a
# still-writing producer — that producer's non-zero exit (not grep's
# success) becomes the pipeline's status, which reads as "not found"
# even when it was. Verified this actually happens with fc-list below.
if ! command -v systemctl >/dev/null 2>&1; then echo "no-systemctl"; return; fi
local unit_files
unit_files=$(systemctl ${2:-} list-unit-files "$1" 2>/dev/null)
if grep -q "$1" <<< "$unit_files"; then
systemctl ${2:-} is-active "$1" 2>/dev/null || echo "inactive"
else
echo "missing"
fi
}
# --- 0. Quickshell itself + config files present ----------------------------
section "Quickshell core"
if command -v quickshell >/dev/null 2>&1; then
ver=$(quickshell --version 2>&1 | head -1)
pass "quickshell found ($(command -v quickshell)) — $ver"
else
fail "quickshell binary not found on PATH — install it before anything else here matters"
fi
for f in "shell.qml" "config/Appearance.qml" "config/Colors.qml" "config/qmldir"; do
if [ -f "$QS_DIR/$f" ]; then
pass "found $f"
else
fail "missing $QS_DIR/$f — dotfiles not fully stowed/symlinked here?"
fi
done
# Nearly every module (ActiveWindow, Workspaces, PowerButton, PanelWindow's
# layer-shell surfaces, HyprlandFocusGrab) assumes a live Hyprland session.
if [ -n "${HYPRLAND_INSTANCE_SIGNATURE:-}" ] && command -v hyprctl >/dev/null 2>&1 && hyprctl monitors >/dev/null 2>&1; then
pass "Hyprland is the running compositor"
else
fail "Hyprland doesn't appear to be running — Quickshell.Hyprland and the layer-shell bar will not function"
fi
# --- 1. Fonts -----------------------------------------------------------------
section "Fonts"
if command -v fc-list >/dev/null 2>&1; then
# Captured once, then grepped via here-string — see the pipefail/SIGPIPE
# note on svc_state() above; fc-list's output is exactly what triggers it.
fc_out=$(fc-list)
if grep -qi "Material Symbols Outlined" <<< "$fc_out"; then
pass "Material Symbols Outlined installed"
else
fail "Material Symbols Outlined not found — install 'ttf-material-symbols-variable' (AUR); every glyph icon in the bar/panels will render as fallback text without it"
fi
if grep -qi "CaskaydiaCove Nerd Font Mono" <<< "$fc_out"; then
pass "CaskaydiaCove Nerd Font Mono installed"
else
fail "CaskaydiaCove Nerd Font Mono not found — install a Cascadia Code Nerd Font package; clock/labels/window title use it"
fi
else
warn "fc-list not found (fontconfig missing?) — can't verify fonts"
fi
# --- 2. Backing services for native Quickshell modules ----------------------
# These modules talk to system services directly over D-Bus rather than
# shelling out — the service being up is the actual dependency, not a CLI.
section "Backing services (native QML modules)"
st=$(svc_state NetworkManager.service)
case "$st" in
active) pass "NetworkManager.service active — Quickshell.Networking (NetworkIndicator)" ;;
missing) fail "NetworkManager not installed — required by NetworkIndicator.qml" ;;
*) fail "NetworkManager.service is $st — run: sudo systemctl enable --now NetworkManager" ;;
esac
st=$(svc_state bluetooth.service)
case "$st" in
active) pass "bluetooth.service active — Quickshell.Bluetooth (BluetoothIndicator)" ;;
missing) fail "bluez not installed — required by BluetoothIndicator.qml" ;;
*) fail "bluetooth.service is $st — run: sudo systemctl enable --now bluetooth" ;;
esac
# Pipewire/wireplumber are user-session services, not system ones.
st=$(svc_state pipewire.service --user)
case "$st" in
active) pass "pipewire.service (user) active — Quickshell.Services.Pipewire (Volume/VolumeOsd)" ;;
missing) fail "pipewire not installed — required by Volume.qml/VolumeOsd.qml" ;;
*) fail "pipewire.service (user) is $st — run: systemctl --user enable --now pipewire pipewire-pulse wireplumber" ;;
esac
st=$(svc_state wireplumber.service --user)
case "$st" in
active) pass "wireplumber.service (user) active" ;;
missing) warn "wireplumber not installed — pipewire falls back to pipewire-media-session if present, otherwise volume control may misbehave" ;;
*) fail "wireplumber.service (user) is $st — run: systemctl --user enable --now wireplumber" ;;
esac
# --- 3. External CLIs shelled out to via Process ----------------------------
section "External CLIs (Quickshell.Io Process calls)"
check_bin() {
# $1 = binary, $2 = which module(s) use it, $3 = fail|warn on missing
if command -v "$1" >/dev/null 2>&1; then
pass "$1 found — used by $2"
elif [ "$3" = "warn" ]; then
warn "$1 not found — used by $2 (optional on this machine)"
else
fail "$1 not found — used by $2"
fi
}
check_bin nmcli "NetworkIndicator.qml (DNS provider panel, connection queries)" fail
check_bin ip "NetworkIndicator.qml (gateway/IP lookups)" fail
check_bin ping "NetworkIndicator.qml (latency/packet-loss stat)" fail
check_bin cat "NetworkIndicator.qml (/sys/class/net throughput counters)" fail
check_bin systemctl "PowerButton.qml (suspend/hibernate/reboot/poweroff)" fail
check_bin tailscale "TailscaleIndicator.qml / VpnExclusivity" warn
check_bin mullvad "VpnIndicator.qml / VpnExclusivity" warn
check_bin protonvpn "VpnIndicator.qml / VpnExclusivity" warn
have_tailscale=0; have_mullvad=0; have_proton=0
command -v tailscale >/dev/null 2>&1 && have_tailscale=1
command -v mullvad >/dev/null 2>&1 && have_mullvad=1
command -v protonvpn >/dev/null 2>&1 && have_proton=1
if [ $have_mullvad -eq 0 ] && [ $have_proton -eq 0 ]; then
warn "neither Mullvad nor ProtonVPN CLI is installed — VpnIndicator.qml will show both as 'Not installed'"
fi
# --- 4. VPN daemons + functional status (Mullvad/ProtonVPN/Tailscale) -------
# The exclusivity logic (VpnExclusivity.qml backing off Tailscale when a
# commercial VPN connects) is pure QML and ships with the dotfiles — it
# needs no setup of its own. What varies per machine is whether these three
# are actually installed, enabled, and callable without a password prompt.
section "VPN dual-setup (Mullvad/ProtonVPN + Tailscale)"
if [ $have_tailscale -eq 1 ]; then
st=$(svc_state tailscaled.service)
case "$st" in
active) pass "tailscaled.service is active" ;;
missing) fail "tailscaled.service not found — reinstall tailscale" ;;
*) fail "tailscaled.service is $st — run: sudo systemctl enable --now tailscaled" ;;
esac
if out=$(timeout 5 tailscale status --json 2>&1); then
if python3 -c "import json,sys; json.load(sys.stdin)" >/dev/null 2>&1 <<< "$out"; then
pass "tailscale status --json returns valid JSON"
else
warn "tailscale status --json returned non-JSON output"
fi
else
fail "tailscale status --json failed: $(echo "$out" | head -1)"
fi
fi
if [ $have_mullvad -eq 1 ]; then
st=$(svc_state mullvad-daemon.service)
case "$st" in
active) pass "mullvad-daemon.service is active" ;;
missing) fail "mullvad-daemon.service not found — reinstall the Mullvad package" ;;
*) fail "mullvad-daemon.service is $st — run: sudo systemctl enable --now mullvad-daemon" ;;
esac
if out=$(timeout 5 mullvad status --json 2>&1); then
if python3 -c "import json,sys; json.load(sys.stdin)" >/dev/null 2>&1 <<< "$out"; then
pass "mullvad status --json returns valid JSON"
else
warn "mullvad status --json returned non-JSON output"
fi
else
fail "mullvad status --json failed: $(echo "$out" | head -1)"
fi
fi
if [ $have_proton -eq 1 ]; then
# ProtonVPN's CLI drives connections through NetworkManager rather than
# its own daemon — already checked above, just re-verified here in context.
if out=$(timeout 5 protonvpn status 2>&1); then
if grep -qi "^Status:" <<< "$out"; then
pass "protonvpn status returns a parseable 'Status:' line"
else
warn "protonvpn status has no 'Status:' line — applyProtonStatus() in VpnIndicator.qml may misread it: $(echo "$out" | head -1)"
fi
else
fail "protonvpn status failed: $(echo "$out" | head -1)"
fi
fi
ts_up=0; mv_up=0; pv_up=0
# Same capture-then-here-string reasoning as above — these outputs are small
# enough that the SIGPIPE race is unlikely, but there's no upside to relying
# on that instead of just being consistent.
[ $have_tailscale -eq 1 ] && grep -q '"BackendState":"Running"' <<< "$(tailscale status --json 2>/dev/null)" && ts_up=1
[ $have_mullvad -eq 1 ] && grep -qi "^Connected" <<< "$(mullvad status 2>/dev/null)" && mv_up=1
[ $have_proton -eq 1 ] && grep -qi "^Status:\s*Connected" <<< "$(protonvpn status 2>/dev/null)" && pv_up=1
active_count=$((ts_up + mv_up + pv_up))
if [ $active_count -le 1 ]; then
pass "at most one tunnel active right now (tailscale=$ts_up mullvad=$mv_up protonvpn=$pv_up)"
else
warn "more than one tunnel is up right now (tailscale=$ts_up mullvad=$mv_up protonvpn=$pv_up) — bring extras down manually; quickshell only enforces exclusivity when you toggle from its own UI"
fi
# --- 5. System tray (currently disabled in Bar.qml) -------------------------
section "System tray (Tray.qml)"
if grep -q "^\s*//\s*Tray\s*{}" "$QS_DIR/modules/bar/Bar.qml" 2>/dev/null; then
warn "Tray {} is commented out in Bar.qml — skipping icon-theme check (nothing to render)"
elif [ -f "$QS_DIR/modules/bar/Bar.qml" ]; then
if command -v gtk-update-icon-cache >/dev/null 2>&1 || [ -d /usr/share/icons/hicolor ]; then
pass "an icon theme (at least hicolor) is present for Quickshell.Services.SystemTray icon lookups"
else
warn "no icon theme found — tray icons may render blank"
fi
fi
# --- Summary -----------------------------------------------------------------
section "Summary"
printf " ${c_green}%d passed${c_reset}, ${c_yellow}%d warnings${c_reset}, ${c_red}%d failed${c_reset}\n" "$PASS" "$WARN" "$FAIL"
if [ $FAIL -gt 0 ]; then
exit 1
fi
exit 0