mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-14 11:30:52 +01:00
913 lines down to 150. The ten functions were only 202 lines; the other 712 were a single top-level block, now eight phase functions across six libs. As in the previous split the bodies were wrapped WITHOUT re-indenting, because five heredocs use quoted terminators that must stay at column 0 (shfmt indented them afterwards, which is safe). The ~320-line custom-blocklist heredoc became custom_entries.hosts. It was 321 of the 370 lines of hosts_write.sh and cannot be split, so that lib could not otherwise fit the cap; a hosts-format data file is also far easier to audit and diff than a quoted heredoc. That move broke a guard, which is the important part of this commit: extract_custom_entries_from_script sliced the blocklist out of the script between "# Custom blocking entries" and the heredoc terminator. With the entries in a data file there is no terminator, so it would have extracted NOTHING -- and check_custom_entries_protection, the guard whose entire job is refusing to drop blocked domains, would have compared against an empty list and passed everything. It now reads the data file, and the extracted set is identical: 254 domains, diffed. A second, pre-existing bug surfaced while testing: extract_unblock_entries_from_script matched its OWN marker lines. PROTECTED_UNBLOCK_LIST_START and _END are word characters, so the '^# [a-zA-Z0-9._-]+$' pattern pulled both into the whitelist as if they were domains. Both are in this machine's live state file right now. Renaming either marker would have read as the whitelist growing and blocked the install outright. There is no dry-run or status mode, so this file cannot be proven by running it: an aborted run leaves /etc/hosts mutable with the guard's bind mount down. The proof is textual instead. Every one of the 8 wrapped phase bodies is byte-identical to its original line range; a line-multiset check found 0 lines lost; and an indentation-insensitive comparison against the pre-split file leaves exactly 8 absent lines, each an intentional edit (the rewritten extractor, three `readlink -f "$0"` made overridable, the /etc/hosts fallback made overridable, and the heredoc markers). /etc/hosts is byte-unchanged across the whole session, and `--help` output is identical. 36 assertions. Both anti-tamper guards at 100% line coverage (75/75 and 49/49), covering both directions of each: removing a blocked domain is refused and adding one allowed; growing the whitelist is refused and shrinking it allowed. A same-count swap is refused too, which a count-only check would have let through. The install phases themselves are deliberately untested -- running them would take down the protection they install. That is weaker evidence than the two previous splits had, and it is inherent to this file rather than a gap. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F3PzGc75gBcdxP3PkHXGcV
126 lines
5.3 KiB
Bash
Executable File
126 lines
5.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# lib/hosts_guard.sh — the guard lifecycle around the write.
|
|
#
|
|
# stop_hosts_guard takes down the path watcher, the bind mount and the
|
|
# immutable attribute so /etc/hosts can be written; restart_hosts_guard puts
|
|
# them back; save_protection_state records the baseline the next run compares
|
|
# against. Sourced by install.sh.
|
|
|
|
# Enable systemd-resolved and make it read /etc/hosts. Without
|
|
# ReadEtcHosts=yes it ignores the file entirely and every blocked domain
|
|
# resolves normally, which defeats the whole install.
|
|
enable_resolved_reads_hosts() {
|
|
# Enable systemd-resolved
|
|
sudo systemctl enable systemd-resolved
|
|
|
|
# ============================================================================
|
|
# ENSURE systemd-resolved READS /etc/hosts
|
|
# ============================================================================
|
|
# Without this, systemd-resolved ignores /etc/hosts entries entirely,
|
|
# allowing blocked domains to resolve via DNS.
|
|
RESOLVED_CONF="/etc/systemd/resolved.conf"
|
|
if grep -q '^ReadEtcHosts=no' "$RESOLVED_CONF" 2>/dev/null; then
|
|
echo "Fixing systemd-resolved: setting ReadEtcHosts=yes..."
|
|
sudo sed -i 's/^ReadEtcHosts=no/ReadEtcHosts=yes/' "$RESOLVED_CONF"
|
|
sudo systemctl restart systemd-resolved
|
|
elif ! grep -q '^ReadEtcHosts=yes' "$RESOLVED_CONF" 2>/dev/null; then
|
|
echo "Enabling ReadEtcHosts=yes in systemd-resolved..."
|
|
if grep -q '^#ReadEtcHosts=' "$RESOLVED_CONF" 2>/dev/null; then
|
|
sudo sed -i 's/^#ReadEtcHosts=.*/ReadEtcHosts=yes/' "$RESOLVED_CONF"
|
|
else
|
|
echo 'ReadEtcHosts=yes' | sudo tee -a "$RESOLVED_CONF" >/dev/null
|
|
fi
|
|
sudo systemctl restart systemd-resolved
|
|
fi
|
|
|
|
}
|
|
|
|
# Take down the guard so /etc/hosts can be written: stop the path watcher,
|
|
# unmount the read-only bind mount, and clear the immutable/append-only
|
|
# attributes. restart_hosts_guard puts all three back.
|
|
stop_hosts_guard() {
|
|
# ============================================================================
|
|
# TEMPORARILY DISABLE HOSTS GUARD PROTECTIONS FOR INSTALLATION
|
|
# ============================================================================
|
|
# The guard system uses a read-only bind mount and path watcher that prevent
|
|
# any writes to /etc/hosts. We must stop them before installing, then restart.
|
|
GUARD_SERVICES_STOPPED=0
|
|
|
|
for svc in hosts-bind-mount.service hosts-guard.path; do
|
|
if systemctl is-active --quiet "$svc" 2>/dev/null; then
|
|
echo "Stopping $svc for installation..."
|
|
systemctl stop "$svc" 2>/dev/null || true
|
|
GUARD_SERVICES_STOPPED=1
|
|
fi
|
|
done
|
|
|
|
# If bind mount is still active, unmount it
|
|
if findmnt /etc/hosts >/dev/null 2>&1; then
|
|
echo "Unmounting read-only bind mount on /etc/hosts..."
|
|
umount /etc/hosts 2>/dev/null || mount -o remount,rw,bind /etc/hosts 2>/dev/null || true
|
|
fi
|
|
|
|
# Remove all attributes from /etc/hosts to allow modifications
|
|
sudo chattr -i -a /etc/hosts 2>/dev/null || true
|
|
|
|
}
|
|
|
|
# Bring the guard back up after the write: restart the services stopped by
|
|
# stop_hosts_guard so /etc/hosts is protected again.
|
|
restart_hosts_guard() {
|
|
|
|
# ============================================================================
|
|
# RESTART HOSTS GUARD SERVICES
|
|
# ============================================================================
|
|
if [[ $GUARD_SERVICES_STOPPED -eq 1 ]]; then
|
|
echo "Restarting hosts guard services..."
|
|
# Update the canonical copy so the guard doesn't revert our changes
|
|
if [[ -f /usr/local/share/locked-hosts ]]; then
|
|
cp /etc/hosts /usr/local/share/locked-hosts
|
|
echo " Updated canonical snapshot."
|
|
fi
|
|
for svc in hosts-bind-mount.service hosts-guard.path; do
|
|
if systemctl is-enabled --quiet "$svc" 2>/dev/null; then
|
|
systemctl start "$svc" 2>/dev/null || true
|
|
echo " Restarted $svc"
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# ============================================================================
|
|
}
|
|
|
|
# Record the custom and unblock entry lists that were just installed, so the
|
|
# next run's protection checks have a baseline to compare against.
|
|
save_protection_state() {
|
|
# SAVE CUSTOM ENTRIES STATE FOR FUTURE PROTECTION CHECKS
|
|
# ============================================================================
|
|
echo "Saving custom entries state for protection mechanism..."
|
|
# The install script whose entry lists are being checked. Defaults to the
|
|
# running script, which is what production always wants; a test points it
|
|
# at a fixture instead, since these guards are the part of this file where
|
|
# a silent regression matters most.
|
|
script_path="$(readlink -f "${HOSTS_INSTALL_SCRIPT_PATH:-$0}")"
|
|
current_custom_entries=$(extract_custom_entries_from_script "$script_path")
|
|
# Remove immutable from state file if it exists
|
|
chattr -i "$CUSTOM_ENTRIES_STATE_FILE" 2>/dev/null || true
|
|
save_custom_entries_state "$current_custom_entries"
|
|
echo "✅ Custom entries state saved to $CUSTOM_ENTRIES_STATE_FILE"
|
|
|
|
# Save unblock entries state for future protection checks
|
|
current_unblock_entries=$(extract_unblock_entries_from_script "$script_path")
|
|
save_unblock_entries_state "$current_unblock_entries"
|
|
echo "✅ Unblock entries state saved to $UNBLOCK_STATE_FILE"
|
|
|
|
# Optionally flush DNS caches
|
|
if [[ $FLUSH_DNS -eq 1 ]]; then
|
|
echo "Flushing DNS caches..."
|
|
sudo systemd-resolve --flush-caches
|
|
sudo systemctl restart NetworkManager.service
|
|
else
|
|
echo "DNS cache flush skipped (use --flush-dns to enable)."
|
|
fi
|
|
|
|
# ============================================================================
|
|
}
|