testsAndMisc/linux_configuration/periodic_background/digital_wellbeing/pacman/llm-notes/verification-and-cleanup.md
Krzysztof kuhy Rudnicki 4f2b0ccdf7 Dissolve linux_configuration/scripts/ and single_use/
Two levels of pure taxonomy carrying no information, and a 2-directory
cap leaves no room for them. Categories now sit directly under
linux_configuration/: features, fixes, utils, misc, fresh-install,
installers, diagnostics, gaming, boot_recovery, periodic_background, lib,
meta, tests. A script's own lib/ and tests/ are exempt from the cap, so
those stay put.

Depth violations 248 -> 139; roughly 40 of the remainder are inside
digital_wellbeing and hosts, deferred until the guard-lib repoint.

The moves are the easy half. What breaks is every path that counted
levels, and three classes of it had to be found by resolving paths rather
than reading diffs:

  - 52 relative `source` lines for lib/common.sh and lib/android.sh. The
    correct prefix differs per file: 40 were ../../ and 12 were ../../../,
    so a blanket replace would have fixed one set and broken the other.
  - REPO_ROOT in 7 fixes/lib/tests harnesses and setup_syncyomi_guard.sh,
    each walking up four levels from a directory now two levels shallower.
    This caused a real regression -- the fixes suite went red with
    "log_warn: command not found" -- caught by running it and comparing
    against the pre-move baseline worktree.
  - conftest.py's sys.path entries, which took the pytest gate down with
    14 collection errors, and the `# shellcheck source=` directives, which
    took shellcheck down with SC1091.

16 symlinks in $HOME broke, including the entire i3blocks bar. All
repointed, and cpu_monitor/memory/disk/time/volume were run to confirm
they emit real output rather than merely resolving.

Verified: 1798 tests pass at 100% coverage, 8 of 9 lib/tests suites pass
(the ninth needs the coverage jail, identically at baseline), pre-commit
is clean, and the two untracked secret configs are still ignored after
their gitignore paths changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJrqKERAo5QUeEHWS5Asgp
2026-08-23 18:53:30 +02:00

4.6 KiB

Integrity verification, cleanup and stale locks

Integrity Verification

On every invocation, the wrapper verifies policy files haven't been tampered with:

verify_policy_integrity() {
  # Reads /var/lib/pacman-wrapper/policy.sha256
  # Compares SHA256 of each policy file
  # If mismatch: BLOCKS all operations
}

If tampering detected:

SECURITY WARNING: Policy file integrity check failed!
CRITICAL: Policy files have been tampered with!
Wrapper operation DENIED. Please reinstall using: sudo install_pacman_wrapper.sh

Hosts Integration

The wrapper integrates with the hosts guard system:

pre_unlock_hosts() {
  # Called before any transaction (-S, -U, -R)
  /usr/local/share/hosts-guard/pacman-pre-unlock-hosts.sh
}

post_relock_hosts() {
  # Called after transaction completes
  /usr/local/share/hosts-guard/pacman-post-relock-hosts.sh
}

This allows package installations to modify /etc/hosts temporarily (e.g., for network setup) while maintaining protection.

Common Tasks

Adding a Blocked Package

  1. Edit pacman_blocked_keywords.txt:
echo "newkeyword" >> pacman_blocked_keywords.txt
  1. Reinstall wrapper to update checksums:
sudo ./install_pacman_wrapper.sh

Whitelisting a Package

If a legitimate package is being blocked (e.g., python-firefox-sync blocked by "firefox" keyword):

  1. Edit pacman_whitelist.txt:
echo "python-firefox-sync" >> pacman_whitelist.txt
  1. Reinstall wrapper:
sudo ./install_pacman_wrapper.sh

Adding a Challenge Requirement

  1. Edit pacman_greylist.txt:
echo "suspicious-package" >> pacman_greylist.txt
  1. Reinstall wrapper.

Bypassing the Wrapper (Emergency)

If wrapper is broken and you need real pacman:

sudo /usr/bin/pacman.orig -S package

Warning: This bypasses all security checks.

Post-Transaction Cleanup

After every transaction, the wrapper:

  1. Scans installed packages for blocked keywords
  2. Removes any that match (shouldn't happen normally)
  3. Scans for greylisted packages and removes them
  4. Checks if VirtualBox is installed and enforces hosts
remove_installed_blocked_packages() {
  mapfile -t installed_names < <("$PACMAN_BIN" -Qq)
  for name in "${installed_names[@]}"; do
    if is_blocked_package_name "$name"; then
      pacman -Rns --noconfirm "$name"
    fi
  done
}

Stale Lock Handling

The stale-lock logic lives in pacman_lock_lib.sh (shared, sourced by both pacman_wrapper.sh and makepkg_wrapper.sh — single source of truth). The wrapper sources it only AFTER verify_policy_integrity, and the lib is listed in the integrity manifest, so a tampered lib is rejected before it runs.

If /var/lib/pacman/db.lck exists but no pacman is running:

  • Interactive: Prompts user to remove (15s timeout)
  • Non-interactive (--noconfirm): Auto-removes; also auto-removes if lock is

    10 minutes old

  • If a real pacman/pamac process is running: Blocks with error. Detection uses fuser/lsof on the lock file AND a system-wide pgrep -x pacman guard (pacman_process_running) so an UNPRIVILEGED caller (makepkg is never root) can still see a ROOT pacman -Syu that its own fuser cannot.

Makepkg wrapper

/usr/bin/makepkg is symlinked to makepkg_wrapper (real binary at /usr/bin/makepkg.orig). Vendored makepkg's run_pacman() has its own lock-wait that checks ONLY file existence with no timeout, so an orphaned db.lck hangs makepkg -i forever ("Pacman is currently in use, please wait...") before pacman is ever called — the pacman wrapper's cleanup never gets a turn. makepkg_wrapper closes this: for install-bound invocations (-i/--install) it clears an orphaned lock up front, then execs real makepkg. It bypasses (execs real makepkg unchanged) inside a fakeroot build sandbox (FAKEROOTKEY set) or for non-install invocations, and fails open — if the shared lib is missing it execs real makepkg rather than break all builds.

Upgrade survival

A pacman-git upgrade reinstalls /usr/bin/pacman and /usr/bin/makepkg as stock binaries, clobbering the wrapper symlinks. Two mechanisms restore them:

  • PostTransaction hook /etc/pacman.d/hooks/96-restore-pkg-wrappers.hook (Target = pacman/pacman-git) runs rewrap_pkg_managers.sh (file-ops only, never calls pacman) to re-establish both symlinks and refresh the .orig backups. NOTE: alpm Operation accepts only Install/Upgrade/Remove — an invalid value aborts EVERY transaction; validate with a real -S install.
  • Drift verifier check_makepkg_wrapper() in check_and_enable_services.sh plus the hourly periodic driver re-running both installers.