mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-11 22:14:01 +01:00
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
177 lines
6.0 KiB
Bash
Executable File
177 lines
6.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# lib/services_hosts.sh — the /etc/hosts blocking stack check: the hosts file
|
|
# itself, its immutable attribute, the three guard-lib file-guard instances
|
|
# (hosts/nsswitch/resolved), the pacman unlock/relock hooks, and the
|
|
# systemd-resolved settings that can silently bypass /etc/hosts.
|
|
#
|
|
# Sourced by check_and_enable_services.sh. The repair half lives in
|
|
# services_hosts_fix.sh so both files stay under the 250-line cap.
|
|
|
|
# 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_hosts() {
|
|
header "Hosts File and Guards"
|
|
|
|
local status="ok"
|
|
local issues=()
|
|
|
|
# Check /etc/hosts exists and has content
|
|
if [[ -f "${SYSROOT}/etc/hosts" ]]; then
|
|
local line_count
|
|
line_count=$(wc -l <"${SYSROOT}/etc/hosts")
|
|
if [[ $line_count -gt 100 ]]; then
|
|
msg "/etc/hosts exists with $line_count lines (StevenBlack list likely installed)"
|
|
else
|
|
issues+=("/etc/hosts has only $line_count lines (StevenBlack list may not be installed)")
|
|
if [[ $status != "error" ]]; then status="warning"; fi
|
|
fi
|
|
else
|
|
issues+=("/etc/hosts does not exist")
|
|
status="error"
|
|
fi
|
|
|
|
# Check if hosts file is immutable
|
|
local attrs
|
|
attrs=$(lsattr "${SYSROOT}/etc/hosts" 2>/dev/null | cut -d' ' -f1 || echo "")
|
|
if [[ $attrs == *"i"* ]]; then
|
|
msg "/etc/hosts has immutable attribute set"
|
|
else
|
|
issues+=("/etc/hosts is not immutable")
|
|
if [[ $status != "error" ]]; then status="warning"; fi
|
|
fi
|
|
|
|
# Check cached hosts file
|
|
if [[ -f "${SYSROOT}/etc/hosts.stevenblack" ]]; then
|
|
msg "StevenBlack cache exists at ${SYSROOT}/etc/hosts.stevenblack"
|
|
else
|
|
issues+=("StevenBlack cache not found")
|
|
if [[ $status != "error" ]]; then status="warning"; fi
|
|
fi
|
|
|
|
# Check the guard-lib "hosts" file-guard instance (path unit active +
|
|
# immutable attribute on the target). Replaces the legacy hosts-guard.path
|
|
# / hosts-bind-mount.service / enforce-hosts.sh checks now that this
|
|
# machine's migration to guard-lib is complete — see
|
|
# migrate_hosts_guard_to_guard_lib.sh.
|
|
if guard_lib_instance_healthy hosts; then
|
|
msg "guard-lib 'hosts' instance is active and enforced"
|
|
else
|
|
issues+=("guard-lib 'hosts' instance is missing or unhealthy")
|
|
status="error"
|
|
fi
|
|
|
|
# Check pacman hooks
|
|
if hosts_pacman_hooks_installed; then
|
|
msg "Pacman hooks installed"
|
|
else
|
|
issues+=("Pacman hooks not installed")
|
|
if [[ $status != "error" ]]; then status="warning"; fi
|
|
fi
|
|
|
|
# Check nsswitch.conf has 'files' in hosts line
|
|
if [[ -f "${SYSROOT}/etc/nsswitch.conf" ]]; then
|
|
local nsswitch_hosts
|
|
nsswitch_hosts=$(grep '^hosts:' "${SYSROOT}/etc/nsswitch.conf" 2>/dev/null || echo "")
|
|
if echo "$nsswitch_hosts" | grep -qw 'files'; then
|
|
msg "nsswitch.conf hosts line includes 'files'"
|
|
else
|
|
issues+=("nsswitch.conf hosts line missing 'files' — ${SYSROOT}/etc/hosts is bypassed!")
|
|
status="error"
|
|
fi
|
|
else
|
|
issues+=("/etc/nsswitch.conf does not exist")
|
|
status="error"
|
|
fi
|
|
|
|
# Check the guard-lib "nsswitch" file-guard instance
|
|
if guard_lib_instance_healthy nsswitch; then
|
|
msg "guard-lib 'nsswitch' instance is active and enforced"
|
|
else
|
|
issues+=("guard-lib 'nsswitch' instance is missing or unhealthy")
|
|
status="error"
|
|
fi
|
|
|
|
# Check resolved.conf has ReadEtcHosts=yes
|
|
if [[ -f "${SYSROOT}/etc/systemd/resolved.conf" ]]; then
|
|
local read_etc_hosts
|
|
read_etc_hosts=$(grep -E '^\s*ReadEtcHosts\s*=' "${SYSROOT}/etc/systemd/resolved.conf" 2>/dev/null |
|
|
tail -1 | sed 's/.*=\s*//' | tr -d '[:space:]')
|
|
if [[ "$read_etc_hosts" == "yes" ]]; then
|
|
msg "resolved.conf ReadEtcHosts=yes"
|
|
else
|
|
issues+=("resolved.conf ReadEtcHosts='$read_etc_hosts' — ${SYSROOT}/etc/hosts is bypassed by systemd-resolved!")
|
|
status="error"
|
|
fi
|
|
|
|
# Check DNSOverTLS is not enabled
|
|
local dns_over_tls
|
|
dns_over_tls=$(grep -E '^\s*DNSOverTLS\s*=' "${SYSROOT}/etc/systemd/resolved.conf" 2>/dev/null |
|
|
tail -1 | sed 's/.*=\s*//' | tr -d '[:space:]') || true
|
|
if [[ -z "$dns_over_tls" || "$dns_over_tls" == "no" ]]; then
|
|
msg "resolved.conf DNSOverTLS is disabled"
|
|
else
|
|
issues+=("resolved.conf DNSOverTLS='$dns_over_tls' — can bypass ${SYSROOT}/etc/hosts!")
|
|
status="error"
|
|
fi
|
|
|
|
# Check for drop-in overrides
|
|
if [[ -d "${SYSROOT}/etc/systemd/resolved.conf.d" ]]; then
|
|
local dropin_count
|
|
dropin_count=$(find "${SYSROOT}/etc/systemd/resolved.conf.d" -name '*.conf' -type f 2>/dev/null | wc -l)
|
|
if [[ "$dropin_count" -gt 0 ]]; then
|
|
issues+=("Found $dropin_count resolved.conf drop-in override(s) — potential bypass!")
|
|
status="error"
|
|
fi
|
|
fi
|
|
|
|
# Check immutable attribute
|
|
if command -v lsattr &>/dev/null; then
|
|
if lsattr "${SYSROOT}/etc/systemd/resolved.conf" 2>/dev/null | grep -q '.*i.*e.*'; then
|
|
msg "resolved.conf has immutable attribute"
|
|
else
|
|
issues+=("resolved.conf missing immutable attribute")
|
|
[[ "$status" == "ok" ]] && status="warning"
|
|
fi
|
|
fi
|
|
else
|
|
issues+=("/etc/systemd/resolved.conf does not exist")
|
|
[[ "$status" == "ok" ]] && status="warning"
|
|
fi
|
|
|
|
# Check the guard-lib "resolved" file-guard instance
|
|
if guard_lib_instance_healthy resolved; then
|
|
msg "guard-lib 'resolved' instance is active and enforced"
|
|
else
|
|
issues+=("guard-lib 'resolved' instance is missing or unhealthy")
|
|
status="error"
|
|
fi
|
|
|
|
# Report issues
|
|
if [[ $status != "ok" ]]; then
|
|
for issue in "${issues[@]}"; do
|
|
if [[ $status == "error" ]]; then
|
|
err "$issue"
|
|
else
|
|
warn "$issue"
|
|
fi
|
|
done
|
|
((ISSUES_FOUND++)) || true
|
|
|
|
if [[ $STATUS_ONLY -eq 0 ]]; then
|
|
# The repair half lives in services_hosts_fix.sh; it returns 0 only
|
|
# when the post-repair re-verify passes, so a promotion to "ok" here
|
|
# always reflects a re-checked machine rather than a fix attempt.
|
|
if hosts_repair_all; then
|
|
status="ok"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
set_service_status "hosts" "$status"
|
|
}
|