From 12cc8a2ae381fe8c216241b557674463cb8f5566 Mon Sep 17 00:00:00 2001 From: Krzysztof Rudnicki Date: Fri, 31 Oct 2025 17:50:11 +0100 Subject: [PATCH] Revert "feat: removed pre unlock hosts script" This reverts commit 0b9503695a22925f4b04b3f229f33d233f24d38b. --- hosts/guard/install_pacman_hooks.sh | 21 ++- .../pacman-hooks/pacman-post-relock-hosts.sh | 2 +- .../pacman-hooks/pacman-pre-unlock-hosts.sh | 66 +++++++++ .../pacman/pacman_wrapper.sh | 136 ++---------------- 4 files changed, 92 insertions(+), 133 deletions(-) create mode 100644 hosts/guard/pacman-hooks/pacman-pre-unlock-hosts.sh diff --git a/hosts/guard/install_pacman_hooks.sh b/hosts/guard/install_pacman_hooks.sh index 63e92e7..c4b63c5 100755 --- a/hosts/guard/install_pacman_hooks.sh +++ b/hosts/guard/install_pacman_hooks.sh @@ -9,8 +9,21 @@ HOOKS_DIR="/etc/pacman.d/hooks" install -d -m 755 "$HOOKS_DIR" -# Ensure any legacy pre-transaction hook is removed so pre-unlock only occurs via the wrapper -rm -f "$HOOKS_DIR/10-unlock-etc-hosts.hook" 2>/dev/null || true +# Pre-transaction hook +cat >"$HOOKS_DIR/10-unlock-etc-hosts.hook" <<'HOOK' +[Trigger] +Operation = Upgrade +Operation = Install +Operation = Remove +Type = Package +Target = * + +[Action] +Description = Temporarily unlocking /etc/hosts for transaction +When = PreTransaction +Exec = /bin/bash /usr/local/share/hosts-guard/pacman-pre-unlock-hosts.sh +NeedsTargets +HOOK # Post-transaction hook cat >"$HOOKS_DIR/90-relock-etc-hosts.hook" <<'HOOK' @@ -30,9 +43,7 @@ HOOK # Place helper scripts into a shared location install -d -m 755 /usr/local/share/hosts-guard +install -m 755 "$SCRIPT_DIR/pacman-hooks/pacman-pre-unlock-hosts.sh" /usr/local/share/hosts-guard/ install -m 755 "$SCRIPT_DIR/pacman-hooks/pacman-post-relock-hosts.sh" /usr/local/share/hosts-guard/ -# Remove legacy pre-unlock helper if present to reduce accidental execution surface -rm -f /usr/local/share/hosts-guard/pacman-pre-unlock-hosts.sh 2>/dev/null || true - echo "Pacman hooks installed into $HOOKS_DIR." diff --git a/hosts/guard/pacman-hooks/pacman-post-relock-hosts.sh b/hosts/guard/pacman-hooks/pacman-post-relock-hosts.sh index 9589bb3..450f4e7 100644 --- a/hosts/guard/pacman-hooks/pacman-post-relock-hosts.sh +++ b/hosts/guard/pacman-hooks/pacman-post-relock-hosts.sh @@ -8,7 +8,7 @@ LOGTAG=hosts-guard-hook mount_layers_count() { awk '$5=="/etc/hosts"{c++} END{print c+0}' /proc/self/mountinfo 2>/dev/null || echo 0; } collapse_mounts() { local i=0 - if command -v mountpoint >/dev/null 2>&1; then + if command -v mountpoint >/devnull 2>&1; then while mountpoint -q "$TARGET"; do umount -l "$TARGET" >/dev/null 2>&1 || break i=$((i+1)) diff --git a/hosts/guard/pacman-hooks/pacman-pre-unlock-hosts.sh b/hosts/guard/pacman-hooks/pacman-pre-unlock-hosts.sh new file mode 100644 index 0000000..e44866a --- /dev/null +++ b/hosts/guard/pacman-hooks/pacman-pre-unlock-hosts.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +# Non-interactive pre-transaction hook to temporarily unlock /etc/hosts + +TARGET=/etc/hosts +LOGTAG=hosts-guard-hook + +stop_units_if_present() { + local units=(hosts-bind-mount.service hosts-guard.path) + for u in "${units[@]}"; do + if command -v systemctl >/dev/null 2>&1; then + if systemctl list-unit-files 2>/dev/null | grep -q "^$u"; then + systemctl stop "$u" >/dev/null 2>&1 || true + fi + fi + done +} + +is_ro_mount() { findmnt -no OPTIONS -T "$TARGET" 2>/dev/null | grep -qw ro; } +is_bind_mount() { findmnt -no OPTIONS -T "$TARGET" 2>/dev/null | grep -qw bind; } + +mount_layers_count() { awk '$5=="/etc/hosts"{c++} END{print c+0}' /proc/self/mountinfo 2>/dev/null || echo 0; } +cleanup_mount_stacks() { + local i=0 + # Unmount bind layers until /etc/hosts is no longer a mountpoint + if command -v mountpoint >/dev/null 2>&1; then + while mountpoint -q "$TARGET"; do + umount -l "$TARGET" >/dev/null 2>&1 || break + i=$((i+1)) + (( i > 20 )) && break + done + else + # Fallback to best-effort using mountinfo count + local cnt + cnt=$(mount_layers_count) + while (( cnt > 1 )); do + umount -l "$TARGET" >/dev/null 2>&1 || break + i=$((i+1)) + (( i > 20 )) && break + cnt=$(mount_layers_count) + done + fi +} + +# Drop protective attributes if present +if command -v lsattr >/dev/null 2>&1; then + attrs=$(lsattr -d "$TARGET" 2>/dev/null || true) + echo "$attrs" | grep -q " i " && chattr -i "$TARGET" >/dev/null 2>&1 || true + echo "$attrs" | grep -q " a " && chattr -a "$TARGET" >/dev/null 2>&1 || true +fi + +stop_units_if_present + +logger -t "$LOGTAG" "pre: unlocking /etc/hosts (starting)" +echo "$(date -Is) pre-unlock" >> /run/hosts-guard-hook.log 2>/dev/null || true + +# Always collapse any existing layers; we'll operate on the plain file +cleanup_mount_stacks + +# If someone managed a ro single-layer mount, ensure rw by remounting or collapsing again +if is_ro_mount; then + mount -o remount,rw "$TARGET" >/dev/null 2>&1 || cleanup_mount_stacks +fi + +logger -t "$LOGTAG" "pre: unlocking /etc/hosts (done)" + +exit 0 diff --git a/scripts/digital_wellbeing/pacman/pacman_wrapper.sh b/scripts/digital_wellbeing/pacman/pacman_wrapper.sh index afdb4f0..70c4046 100755 --- a/scripts/digital_wellbeing/pacman/pacman_wrapper.sh +++ b/scripts/digital_wellbeing/pacman/pacman_wrapper.sh @@ -11,7 +11,7 @@ CYAN='\033[0;36m' BOLD='\033[1m' NC='\033[0m' # No Color -PACMAN_BIN="${PACMAN_BIN:-/usr/bin/pacman}" +PACMAN_BIN="/usr/bin/pacman" declare -a BLOCKED_KEYWORDS_LIST=() declare -a WHITELISTED_NAMES_LIST=() @@ -65,142 +65,24 @@ needs_unlock() { # Run pre/post hooks for /etc/hosts guard if present pre_unlock_hosts() { - # Inline, non-exported logic to temporarily unlock /etc/hosts for the transaction - # This avoids a separate runnable script while keeping the behavior when using this wrapper. - local TARGET=/etc/hosts - local LOGTAG=hosts-guard-wrapper - - # Allow dry run for testing - if [[ -n "$HOSTS_GUARD_DRY_RUN" ]]; then - echo "[dry-run] Would unlock /etc/hosts (stop units, drop attrs, collapse mounts, ensure rw)" >&2 - return 0 - fi - - # Helper functions (kept local to this scope) - stop_units_if_present() { - local units=(hosts-bind-mount.service hosts-guard.path) - for u in "${units[@]}"; do - if command -v systemctl >/dev/null 2>&1; then - if systemctl list-unit-files 2>/dev/null | grep -q "^$u"; then - systemctl stop "$u" >/dev/null 2>&1 || true - fi - fi - done - } - - is_ro_mount() { findmnt -no OPTIONS -T "$TARGET" 2>/dev/null | grep -qw ro; } - mount_layers_count() { awk '$5=="/etc/hosts"{c++} END{print c+0}' /proc/self/mountinfo 2>/dev/null || echo 0; } - - cleanup_mount_stacks() { - local i=0 - if command -v mountpoint >/dev/null 2>&1; then - while mountpoint -q "$TARGET"; do - umount -l "$TARGET" >/dev/null 2>&1 || break - i=$((i+1)) - (( i > 20 )) && break - done - else - local cnt - cnt=$(mount_layers_count) - while (( cnt > 1 )); do - umount -l "$TARGET" >/dev/null 2>&1 || break - i=$((i+1)) - (( i > 20 )) && break - cnt=$(mount_layers_count) - done - fi - } - - # Make any protective attributes writable if present - if command -v lsattr >/dev/null 2>&1; then - local attrs - attrs=$(lsattr -d "$TARGET" 2>/dev/null || true) - echo "$attrs" | grep -q " i " && chattr -i "$TARGET" >/dev/null 2>&1 || true - echo "$attrs" | grep -q " a " && chattr -a "$TARGET" >/dev/null 2>&1 || true - fi - + local pre="/usr/local/share/hosts-guard/pacman-pre-unlock-hosts.sh" + if [[ -x "$pre" ]]; then echo -e "${CYAN}[hosts-guard] Preparing /etc/hosts for transaction...${NC}" >&2 - logger -t "$LOGTAG" "pre: unlocking /etc/hosts (starting)" || true - echo "$(date -Is) pre-unlock(wrapper)" >> /run/hosts-guard-hook.log 2>/dev/null || true - - # Always collapse any existing layers; we'll operate on the plain file - cleanup_mount_stacks - - # Ensure RW if someone left a single-layer RO bind - if is_ro_mount; then - mount -o remount,rw "$TARGET" >/dev/null 2>&1 || cleanup_mount_stacks - fi - - # Stop related units last to avoid races where they remount immediately - stop_units_if_present - - logger -t "$LOGTAG" "pre: unlocking /etc/hosts (done)" || true + /bin/bash "$pre" || true + fi } post_relock_hosts() { - # Inline relock logic (mirrors pacman-post-relock-hosts.sh) - local TARGET=/etc/hosts - local ENFORCE=/usr/local/sbin/enforce-hosts.sh - local LOGTAG=hosts-guard-wrapper - - # Allow dry run for testing - if [[ -n "$HOSTS_GUARD_DRY_RUN" ]]; then - echo "[dry-run] Would reapply protections to /etc/hosts (collapse mounts, bind ro, start path watcher)" >&2 - return 0 - fi - - mount_layers_count() { awk '$5=="/etc/hosts"{c++} END{print c+0}' /proc/self/mountinfo 2>/dev/null || echo 0; } - collapse_mounts() { - local i=0 - if command -v mountpoint >/dev/null 2>&1; then - while mountpoint -q "$TARGET"; do - umount -l "$TARGET" >/dev/null 2>&1 || break - i=$((i+1)) - (( i > 20 )) && break - done - else - local cnt - cnt=$(mount_layers_count) - while (( cnt > 1 )); do - umount -l "$TARGET" >/dev/null 2>&1 || break - i=$((i+1)) - (( i > 20 )) && break - cnt=$(mount_layers_count) - done - fi - } - - logger -t "$LOGTAG" "post: relocking /etc/hosts (starting)" || true - echo "$(date -Is) post-relock(wrapper start)" >> /run/hosts-guard-hook.log 2>/dev/null || true - - collapse_mounts - - if [[ -x "$ENFORCE" ]]; then - "$ENFORCE" >/dev/null 2>&1 || true - fi - - # Apply exactly one ro bind layer - mount --bind "$TARGET" "$TARGET" >/dev/null 2>&1 || true - mount -o remount,ro,bind "$TARGET" >/dev/null 2>&1 || true - - # Start only the path watcher - if command -v systemctl >/dev/null 2>&1; then - systemctl start hosts-guard.path >/dev/null 2>&1 || true - fi - - logger -t "$LOGTAG" "post: relocking /etc/hosts (done)" || true - echo "$(date -Is) post-relock(wrapper done)" >> /run/hosts-guard-hook.log 2>/dev/null || true - + local post="/usr/local/share/hosts-guard/pacman-post-relock-hosts.sh" + if [[ -x "$post" ]]; then + /bin/bash "$post" || true echo -e "${CYAN}[hosts-guard] Protections re-applied to /etc/hosts.${NC}" >&2 + fi } # Ensure periodic system services (timer/monitor) are set up; if not, trigger setup ensure_periodic_maintenance() { - # Allow tests or callers to skip side-effectful setup - if [[ -n "$SKIP_PERIODIC_MAINTENANCE" ]]; then - return 0 - fi # Only proceed if systemd/systemctl is available if ! command -v systemctl >/dev/null 2>&1; then return 0