testsAndMisc/linux_configuration/scripts/periodic_background/lib/services_units.sh
Krzysztof kuhy Rudnicki 08f3c764b2
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
check_and_enable_services: a warning must never mask an error
Eight checks assigned status="warning" unconditionally, so a check running
later in the same function overwrote the status="error" an earlier one had
set. The five other sites in this family already guard the assignment with
`if [[ $status != "error" ]]`, so this was an inconsistency, not a design.

Only half of it was load-bearing, and the two halves fail differently:

  services_units.sh (4 sites) reports through report_and_fix, which repairs
  ONLY on "error". A downgrade there meant no repair ran at all. The is-active
  check is the LAST status assignment in each of the three functions, so it
  could mask any error above it -- in check_periodic_systems, three separate
  is-enabled checks. A timer that was both disabled and inactive, which is the
  ordinary shape of a broken timer, was reported and then left broken.

  services_hosts.sh (4 sites) gates its own repair on `status != "ok"`, so the
  repair still ran. There the bug printed a real error as a yellow [!] warning
  and understated the summary row.

Demonstrated rather than argued, against a simulated broken machine (timer
disabled AND inactive, unit files present):

  without the guards:  status=warning, repair ran 0 times
  with the guards:     status=error,   repair ran 1 time

The seven assertions that previously pinned the broken behaviour now assert
the correct behaviour; they were rewritten first and failed, then the guards
made them pass. Two further cases cover the masking shape directly: a broken
guard-lib instance must still be repaired when a later check warns.

`--status --dry-run` output is still byte-identical to rev 6691cb26, because
this machine has no disabled timer and no missing pacman hook -- the buggy
path is never taken here, which is precisely why it went unnoticed. The A/B
above is the evidence the live diff cannot provide.

222 assertions across 10 files. services_units.sh holds 100% coverage;
services_hosts.sh rises from 86.05% to 93.02%. test_services_hosts.sh needed
splitting twice to stay under the 250-line cap (hosts_fix, resolved), and
stage_hosts_ok moved into the harness so both new files can use it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F3PzGc75gBcdxP3PkHXGcV
2026-08-21 14:16:01 +02:00

170 lines
5.3 KiB
Bash
Executable File

#!/usr/bin/env bash
# lib/services_units.sh — systemd timer/service checks for the three units that
# are installed by their own setup scripts (midnight shutdown, PC startup
# monitor, periodic maintenance).
#
# Sourced by check_and_enable_services.sh. All three funnel their findings
# through report_and_fix, which owns the reinstall-and-reverify path.
# Absolute paths the checks below probe are prefixed with $SYSROOT, which is
# empty in production and a fixture tree under test. It is deliberately NOT
# defaulted here: several repairs in this family write outside `run` (chattr,
# find -delete, an append to resolved.conf), so a test that forgot to set it
# would edit the real /etc. Unset is a hard error; empty is the real filesystem.
SYSROOT="${SERVICES_ROOT?SERVICES_ROOT must be set (empty = the real filesystem)}"
check_midnight_shutdown() {
header "Midnight Shutdown (Day-Specific Auto-Shutdown)"
local status="ok"
local issues=()
# Check timer
if systemctl is-enabled day-specific-shutdown.timer &>/dev/null; then
msg "day-specific-shutdown.timer is enabled"
else
issues+=("day-specific-shutdown.timer is not enabled")
status="error"
fi
if systemctl is-active day-specific-shutdown.timer &>/dev/null; then
msg "day-specific-shutdown.timer is active"
else
issues+=("day-specific-shutdown.timer is not active")
if [[ $status != "error" ]]; then status="warning"; fi
fi
# Check service file exists
if [[ -f "${SYSROOT}/etc/systemd/system/day-specific-shutdown.service" ]]; then
msg "day-specific-shutdown.service file exists"
else
issues+=("day-specific-shutdown.service file missing")
status="error"
fi
# Check management script
if [[ -f "${SYSROOT}/usr/local/bin/day-specific-shutdown-manager.sh" ]]; then
msg "Shutdown manager script exists"
else
issues+=("day-specific-shutdown-manager.sh not found")
status="error"
fi
report_and_fix issues status "midnight_shutdown" \
"Setting up midnight shutdown..." \
"$MIDNIGHT_SHUTDOWN_SCRIPT" \
"day-specific-shutdown.timer" \
enable
}
check_startup_monitor() {
header "PC Startup Monitor"
local status="ok"
local issues=()
# Check timer (the timer triggers the service, so we check the timer)
if systemctl is-enabled pc-startup-monitor.timer &>/dev/null; then
msg "pc-startup-monitor.timer is enabled"
else
issues+=("pc-startup-monitor.timer is not enabled")
status="error"
fi
if systemctl is-active pc-startup-monitor.timer &>/dev/null; then
msg "pc-startup-monitor.timer is active"
else
issues+=("pc-startup-monitor.timer is not active")
if [[ $status != "error" ]]; then status="warning"; fi
fi
# Check service file exists
if [[ -f "${SYSROOT}/etc/systemd/system/pc-startup-monitor.service" ]]; then
msg "pc-startup-monitor.service file exists"
else
issues+=("pc-startup-monitor.service file missing")
status="error"
fi
# Check monitor script
if [[ -f "${SYSROOT}/usr/local/bin/pc-startup-check.sh" ]]; then
msg "Startup check script exists"
else
issues+=("pc-startup-check.sh not found")
status="error"
fi
report_and_fix issues status "startup_monitor" \
"Setting up startup monitor..." \
"$STARTUP_MONITOR_SCRIPT" \
"pc-startup-monitor.timer"
}
check_periodic_systems() {
header "Periodic System Maintenance"
local status="ok"
local issues=()
# Check timer
if systemctl is-enabled periodic-system-maintenance.timer &>/dev/null; then
msg "periodic-system-maintenance.timer is enabled"
else
issues+=("periodic-system-maintenance.timer is not enabled")
status="error"
fi
if systemctl is-active periodic-system-maintenance.timer &>/dev/null; then
msg "periodic-system-maintenance.timer is active"
else
issues+=("periodic-system-maintenance.timer is not active")
if [[ $status != "error" ]]; then status="warning"; fi
fi
# Check startup service
if systemctl is-enabled periodic-system-startup.service &>/dev/null; then
msg "periodic-system-startup.service is enabled"
else
issues+=("periodic-system-startup.service is not enabled")
status="error"
fi
# Check hosts file monitor
if systemctl is-enabled hosts-file-monitor.service &>/dev/null; then
msg "hosts-file-monitor.service is enabled"
else
issues+=("hosts-file-monitor.service is not enabled")
status="error"
fi
if systemctl is-active hosts-file-monitor.service &>/dev/null; then
msg "hosts-file-monitor.service is active"
else
issues+=("hosts-file-monitor.service is not active")
if [[ $status != "error" ]]; then status="warning"; fi
fi
# Check maintenance script
if [[ -f "${SYSROOT}/usr/local/bin/periodic-system-maintenance.sh" ]]; then
msg "Maintenance script exists"
else
issues+=("periodic-system-maintenance.sh not found")
status="error"
fi
# report_and_fix takes `status` by nameref and both reads and rewrites it,
# so passing the bare name is the whole interface. shellcheck cannot see
# through a nameref, though, and standalone it reports the assignments
# above as write-only (SC2034). This early return is a genuine read: an
# "ok" service has nothing to report and nothing to fix, so skipping the
# call is also marginally cheaper than having it decide the same thing.
if [[ $status == "ok" ]]; then
set_service_status "periodic_systems" "ok"
return 0
fi
report_and_fix issues status "periodic_systems" \
"Setting up periodic systems..." \
"$PERIODIC_SYSTEM_SCRIPT" \
"periodic-system-maintenance.timer"
}