mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 19:07:04 +01:00
pacman intermittently freezes at its FIRST PreTransaction hook - 5 abandoned
transactions since 2026-07-29, stalls of 4 minutes to overnight, always fine on
retry. This investigates it and repairs the real defects found on the way.
The stall is NOT fixed, and deliberately so: no verified defect was found, so
nothing pacman-side was changed.
- Not guard-lib. /etc/guard-lib/targets was empty, so that hook script
provably exits in milliseconds. The failures share POSITION (first hook of
the transaction), not identity: one stall hit the hosts hook back when that
one ran first.
- Not reproducible on demand. 65 real transactions (40 idle, 25 under 2.6 GB
of memory pressure) produced zero stalls. Against the historical 4-in-56
rate that is ~0.9% likely, so the trigger is conditional on something a
synthetic load does not recreate. Memory pressure is untested at the load
that actually occurred - not refuted.
So diagnose_pacman_hook_stall.sh drives real transactions with a watchdog, and
its --watch mode passively captures /proc/<pid>/{stack,wchan,syscall}, the
process tree, meminfo and PSI from the next REAL stall. No pacman-side fix
should ship before that dump exists.
Three real defects were found and fixed:
1. The deployed pacman wrapper was 19916 B against a 27510 B source, never
sourcing the shared pacman_lock_lib.sh that makepkg_wrapper already used.
It hid because check_pacman_wrapper() only tested that files EXIST. Both
installers now write a drift manifest covering source AND installed hashes;
the checker replays it with sha256sum -c and fails closed when it cannot
verify. Manifest paths are absolute - relative ones resolve to nothing under
systemd's cwd=/ and would have looped reinstalling forever.
2. The guard-lib migration (66c4698) was never finished here: guard-lib was
installed with zero registered targets while the legacy hosts-guard layer did
all the work, so every transaction ran two PreTransaction hooks and the first
one did nothing. migrate_hosts_guard_to_guard_lib.sh registers hosts,
nsswitch and resolved, retires the legacy hooks and units, and has a
--rollback that was actually exercised. Plugins install to a stable path
because guardctl records absolute plugin paths. Side effect: one hook pair
instead of two, transactions ~5-6s -> ~2-3s.
3. "One heavy job at a time" was a rule a human had to remember. heavy_job_lock.sh
makes the overlap impossible: one system-wide flock shared between root and
the user (a per-user lock would give root's pacman and the user's builds
separate locks and no exclusion at all). Wired into pacman TRANSACTIONS only
- read-only queries stay instant - plus makepkg_capped and the coverage hook.
Always fails open.
Two bugs surfaced only by running --rollback, both of which briefly left
/etc/hosts with no immutable flag: fg_uninstall clears +i on its way out while
legacy watchers only fire on PathModified, and `((i > 20)) && break` left the
loop body's status at 1, which under set -e aborted the rollback silently - but
only when the loop actually ran, so --dry-run looked healthy.
Not migrated: the shutdown-schedule guard (/etc/shutdown-schedule.conf does not
exist here). Not wired: Flutter/Gradle builds, which have no single entry point.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1338 lines
42 KiB
Bash
Executable File
1338 lines
42 KiB
Bash
Executable File
#!/bin/bash
|
|
# Script to check and enable all digital wellbeing services
|
|
# Checks: pacman wrapper, midnight shutdown, startup monitor, periodic systems, hosts and hosts guard
|
|
#
|
|
# Usage:
|
|
# sudo ./check_and_enable_services.sh [options]
|
|
# Options:
|
|
# --dry-run Show what would be done without making changes
|
|
# --status Only show status, don't enable anything
|
|
# -h|--help Show help
|
|
|
|
set -euo pipefail
|
|
|
|
######################################################################
|
|
# Configuration
|
|
######################################################################
|
|
DRY_RUN=0
|
|
STATUS_ONLY=0
|
|
|
|
# Colors
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[0;33m'
|
|
BLUE='\033[0;34m'
|
|
CYAN='\033[0;36m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# Get script and config directories
|
|
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
|
CONFIG_DIR="$(dirname "$(dirname "$SCRIPT_DIR")")"
|
|
|
|
# Script paths
|
|
PACMAN_WRAPPER_INSTALL="$CONFIG_DIR/scripts/periodic_background/digital_wellbeing/pacman/install_pacman_wrapper.sh"
|
|
MAKEPKG_WRAPPER_INSTALL="$CONFIG_DIR/scripts/periodic_background/digital_wellbeing/pacman/install_makepkg_wrapper.sh"
|
|
# Drift manifests written by the two installers above (see deployment_drift).
|
|
PACMAN_WRAPPER_MANIFEST="/var/lib/pacman-wrapper/source.sha256"
|
|
MAKEPKG_WRAPPER_MANIFEST="/var/lib/pacman-wrapper/makepkg-source.sha256"
|
|
MIDNIGHT_SHUTDOWN_SCRIPT="$CONFIG_DIR/scripts/periodic_background/digital_wellbeing/setup_midnight_shutdown.sh"
|
|
STARTUP_MONITOR_SCRIPT="$CONFIG_DIR/scripts/periodic_background/digital_wellbeing/setup_pc_startup_monitor.sh"
|
|
PERIODIC_SYSTEM_SCRIPT="$CONFIG_DIR/scripts/periodic_background/setup_periodic_system.sh"
|
|
HOSTS_INSTALL_SCRIPT="$CONFIG_DIR/scripts/periodic_background/hosts/install.sh"
|
|
HOSTS_GUARD_SCRIPT="$CONFIG_DIR/scripts/periodic_background/hosts/guard/setup_hosts_guard.sh"
|
|
HOSTS_PACMAN_HOOKS_SCRIPT="$CONFIG_DIR/scripts/periodic_background/hosts/guard/install_pacman_hooks.sh"
|
|
COMPULSIVE_BLOCK_SCRIPT="$CONFIG_DIR/scripts/periodic_background/digital_wellbeing/block_compulsive_opening.sh"
|
|
THORIUM_STARTUP_SCRIPT="$CONFIG_DIR/scripts/single_use/setup_thorium_startup.sh"
|
|
LEECHBLOCK_SCRIPT="$CONFIG_DIR/scripts/periodic_background/digital_wellbeing/install_leechblock.sh"
|
|
REMOVE_GUEST_MODE_SCRIPT="$CONFIG_DIR/scripts/periodic_background/digital_wellbeing/remove_guest_mode.sh"
|
|
VBOX_HOSTS_SCRIPT="$CONFIG_DIR/scripts/periodic_background/digital_wellbeing/virtualbox/enforce_vbox_hosts.sh"
|
|
# screen-locker was EXTRACTED out of this monorepo into its own repo
|
|
# (github.com/kuhyx/screen-locker, checked out at ~/screen-locker), so these
|
|
# paths deliberately live outside testsAndMisc. They used to point at
|
|
# python_pkg/screen_locker/, which stopped existing at extraction time — the
|
|
# result was check_workout_locker reporting a red "error" for a service that was
|
|
# installed and enabled the whole time, while its "fix" silently did nothing.
|
|
# Resolve the invoking user's home: this script re-execs itself via sudo, so
|
|
# $HOME would be /root here.
|
|
REAL_USER="${SUDO_USER:-${USER:-$(id -un)}}"
|
|
REAL_HOME="$(getent passwd "$REAL_USER" 2>/dev/null | cut -d: -f6)"
|
|
[[ -n $REAL_HOME ]] || REAL_HOME="/home/$REAL_USER"
|
|
WORKOUT_LOCKER_REPO="$REAL_HOME/screen-locker"
|
|
WORKOUT_LOCKER_INSTALL_SCRIPT="$WORKOUT_LOCKER_REPO/install_systemd.sh"
|
|
WORKOUT_LOCKER_SCRIPT="$WORKOUT_LOCKER_REPO/screen_locker/screen_lock.py"
|
|
|
|
######################################################################
|
|
# Helpers
|
|
######################################################################
|
|
msg() { printf "${GREEN}[✓]${NC} %s\n" "$*"; }
|
|
note() { printf "${BLUE}[i]${NC} %s\n" "$*"; }
|
|
warn() { printf "${YELLOW}[!]${NC} %s\n" "$*"; }
|
|
err() { printf "${RED}[✗]${NC} %s\n" "$*"; }
|
|
header() { printf "\n${CYAN}=== %s ===${NC}\n" "$*"; }
|
|
|
|
# A MISSING repair script means THIS TOOL is broken: it silently stops repairing
|
|
# the thing it exists to repair. That is exactly how the 2026-05-15 reorg left a
|
|
# stale $CONFIG_DIR/hosts path here — check_hosts detected the disabled guards
|
|
# every run, failed to find setup_hosts_guard.sh, printed one line among a
|
|
# hundred, and still exited 0. Five months of dead self-repair, zero symptoms.
|
|
# So: record it, log at journal ERROR priority, and exit non-zero with a banner.
|
|
declare -a MISSING_SCRIPTS=()
|
|
err_missing_script() {
|
|
err "$*"
|
|
logger -t check-and-enable-services -p user.err "MISSING REPAIR SCRIPT: $*"
|
|
MISSING_SCRIPTS+=("$*")
|
|
}
|
|
|
|
# Query a USER systemd unit from root.
|
|
# `sudo -u <user> systemctl --user ...` does NOT work here: sudo gives it no
|
|
# XDG_RUNTIME_DIR / DBUS_SESSION_BUS_ADDRESS, so it cannot reach the user bus and
|
|
# fails with "Failed to connect to user scope bus via local transport". Every
|
|
# check using it therefore reported EVERY user service as "not enabled" — a false
|
|
# error for services that were enabled and running (workout-locker was reported
|
|
# broken for months while working fine). systemd's own error suggests the fix:
|
|
# --machine=<user>@.host connects to another user's bus correctly.
|
|
user_systemctl() { # <user> <systemctl args...>
|
|
local u="$1"
|
|
shift
|
|
systemctl --user --machine="${u}@.host" "$@"
|
|
}
|
|
|
|
# Are the pacman hooks that unlock/relock the guarded hosts files installed?
|
|
# The hosts guard migrated to guard-lib's GENERIC unlock-all/relock-all hooks
|
|
# (which cover every registered file-guard: hosts, nsswitch, resolved, ...), and
|
|
# the old per-file 10-unlock-etc-hosts.hook / 90-relock-etc-hosts.hook names have
|
|
# not existed since. This check was never updated, so it reported a phantom
|
|
# "Pacman hooks not installed" warning forever. pacman_wrapper.sh treats the
|
|
# guard-lib pair as authoritative (see pacman_hooks_manage_guard_lib) — match it,
|
|
# while still accepting the legacy pair if an older install is present.
|
|
hosts_pacman_hooks_installed() {
|
|
{ [[ -f /etc/pacman.d/hooks/10-guard-lib-unlock-all.hook ]] &&
|
|
[[ -f /etc/pacman.d/hooks/90-guard-lib-relock-all.hook ]]; } ||
|
|
{ [[ -f /etc/pacman.d/hooks/10-unlock-etc-hosts.hook ]] &&
|
|
[[ -f /etc/pacman.d/hooks/90-relock-etc-hosts.hook ]]; }
|
|
}
|
|
|
|
# Replay a drift manifest written at install time. The manifest holds plain
|
|
# sha256sum lines covering BOTH the repo sources an installer copied from and
|
|
# the installed copies it produced, so one `sha256sum -c` answers both "has the
|
|
# repo moved on?" and "did someone edit the deployed file?".
|
|
#
|
|
# This exists because an existence check is not a deployment check. On
|
|
# 2026-08-03 /usr/local/bin/pacman_wrapper was 19916 B against a 27510 B source
|
|
# — missing the policy integrity manifest, the pacman_lock_lib stale-lock fix
|
|
# and the guard-lib fallbacks — while every check here reported "ok" hourly,
|
|
# because the file was present and /usr/bin/pacman was a symlink. Both of those
|
|
# facts stayed true the entire time the wrapper was a week out of date.
|
|
#
|
|
# Return: 0 verified, 1 drift detected, 2 no manifest (install predates this).
|
|
# Callers must treat 2 as actionable rather than a pass: "cannot verify" is
|
|
# precisely the state that hid the stale wrapper.
|
|
deployment_drift() { # <manifest-path>
|
|
local manifest="$1"
|
|
[[ -f $manifest ]] || return 2
|
|
sha256sum -c --status "$manifest" 2>/dev/null || return 1
|
|
return 0
|
|
}
|
|
|
|
run() {
|
|
if [[ $DRY_RUN -eq 1 ]]; then
|
|
echo -e "${YELLOW}DRY-RUN:${NC} $*"
|
|
return 0
|
|
else
|
|
"$@"
|
|
fi
|
|
}
|
|
|
|
require_root() {
|
|
if [[ $EUID -ne 0 ]]; then
|
|
echo "This script requires root privileges."
|
|
echo "Re-executing with sudo..."
|
|
exec sudo -E bash "$0" "$@"
|
|
fi
|
|
}
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Check and Enable Digital Wellbeing Services
|
|
============================================
|
|
|
|
Usage: sudo ./check_and_enable_services.sh [options]
|
|
|
|
Options:
|
|
--dry-run Show what would be done without making changes
|
|
--status Only show status, don't enable anything
|
|
-h, --help Show this help message
|
|
|
|
Services checked:
|
|
1. Pacman wrapper - Policy-aware pacman wrapper with friction mechanics
|
|
2. Midnight shutdown - Day-specific automatic shutdown timer
|
|
3. Startup monitor - PC startup time monitoring service
|
|
4. Periodic systems - Hourly maintenance timer and hosts monitor
|
|
5. Hosts and guards - /etc/hosts blocking and protection layers
|
|
6. Compulsive blocker - Limits messaging apps to one launch per hour
|
|
7. Thorium startup - Auto-launch Thorium with Fitatu on boot
|
|
8. LeechBlock - Browser extension for site blocking
|
|
9. Guest mode removal - Disable Chromium guest mode via policy
|
|
10. VirtualBox hosts - Enforce /etc/hosts inside VMs
|
|
11. Workout lock screen - Requires workout logging to unlock screen (user service)
|
|
EOF
|
|
}
|
|
|
|
######################################################################
|
|
# Parse arguments
|
|
######################################################################
|
|
ORIGINAL_ARGS=("$@")
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--dry-run)
|
|
DRY_RUN=1
|
|
shift
|
|
;;
|
|
--status)
|
|
STATUS_ONLY=1
|
|
shift
|
|
;;
|
|
-h | --help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
err "Unknown option: $1"
|
|
usage
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
require_root "${ORIGINAL_ARGS[@]}"
|
|
|
|
######################################################################
|
|
# Status tracking
|
|
######################################################################
|
|
declare -A SERVICE_STATUS
|
|
ISSUES_FOUND=0
|
|
FIXES_APPLIED=0
|
|
|
|
######################################################################
|
|
# Report issues and optionally run fix script
|
|
# Usage: report_and_fix issues_array status_var status_key fix_note setup_script verify_service [args...]
|
|
######################################################################
|
|
report_and_fix() {
|
|
local -n _issues=$1
|
|
local -n _status=$2
|
|
local status_key="$3"
|
|
local fix_note="$4"
|
|
local setup_script="$5"
|
|
local verify_service="${6:-}"
|
|
shift 6
|
|
local script_args=("$@")
|
|
|
|
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 && $_status == "error" ]]; then
|
|
note "$fix_note"
|
|
if [[ -f $setup_script ]]; then
|
|
run bash "$setup_script" "${script_args[@]}"
|
|
((FIXES_APPLIED++)) || true
|
|
# Re-verify after fix
|
|
if [[ $DRY_RUN -eq 0 && -n $verify_service ]] && systemctl is-enabled "$verify_service" &>/dev/null; then
|
|
_status="ok"
|
|
fi
|
|
else
|
|
err_missing_script "Setup script not found: $setup_script"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
SERVICE_STATUS["$status_key"]=$_status
|
|
}
|
|
|
|
######################################################################
|
|
# Check functions
|
|
######################################################################
|
|
|
|
check_pacman_wrapper() {
|
|
header "Pacman Wrapper"
|
|
|
|
local status="ok"
|
|
local issues=()
|
|
|
|
# Check if wrapper is installed
|
|
if [[ -L /usr/bin/pacman ]]; then
|
|
local target
|
|
target=$(readlink -f /usr/bin/pacman)
|
|
if [[ $target == "/usr/local/bin/pacman_wrapper" ]]; then
|
|
msg "Pacman symlink points to wrapper"
|
|
else
|
|
issues+=("Pacman symlink points to: $target (expected /usr/local/bin/pacman_wrapper)")
|
|
status="error"
|
|
fi
|
|
else
|
|
issues+=("Pacman is not a symlink (wrapper not installed)")
|
|
status="error"
|
|
fi
|
|
|
|
# Check if original pacman is backed up
|
|
if [[ -f /usr/bin/pacman.orig ]]; then
|
|
msg "Original pacman backed up at /usr/bin/pacman.orig"
|
|
else
|
|
issues+=("Original pacman backup not found at /usr/bin/pacman.orig")
|
|
status="error"
|
|
fi
|
|
|
|
# Check if wrapper script exists
|
|
if [[ -f /usr/local/bin/pacman_wrapper ]]; then
|
|
msg "Wrapper script exists at /usr/local/bin/pacman_wrapper"
|
|
else
|
|
issues+=("Wrapper script not found at /usr/local/bin/pacman_wrapper")
|
|
status="error"
|
|
fi
|
|
|
|
# Check supporting files
|
|
for file in words.txt pacman_blocked_keywords.txt pacman_whitelist.txt; do
|
|
if [[ -f "/usr/local/bin/$file" ]]; then
|
|
msg "Supporting file exists: /usr/local/bin/$file"
|
|
else
|
|
warn "Supporting file missing: /usr/local/bin/$file"
|
|
fi
|
|
done
|
|
|
|
# Content drift — the check everything above misses. A correct symlink and a
|
|
# present file say nothing about WHICH version is deployed.
|
|
local drift_rc=0
|
|
deployment_drift "$PACMAN_WRAPPER_MANIFEST" || drift_rc=$?
|
|
case $drift_rc in
|
|
0) msg "Wrapper matches the source it was installed from (no drift)" ;;
|
|
1)
|
|
issues+=("Deployed pacman wrapper differs from its install manifest (stale or tampered)")
|
|
status="error"
|
|
;;
|
|
2)
|
|
issues+=("No drift manifest at $PACMAN_WRAPPER_MANIFEST — deployed version unverifiable")
|
|
status="error"
|
|
;;
|
|
esac
|
|
|
|
# Report and fix
|
|
if [[ $status == "error" ]]; then
|
|
for issue in "${issues[@]}"; do
|
|
err "$issue"
|
|
done
|
|
((ISSUES_FOUND++)) || true
|
|
|
|
if [[ $STATUS_ONLY -eq 0 ]]; then
|
|
note "Installing pacman wrapper..."
|
|
if [[ -f $PACMAN_WRAPPER_INSTALL ]]; then
|
|
run bash "$PACMAN_WRAPPER_INSTALL"
|
|
((FIXES_APPLIED++)) || true
|
|
# Re-verify after fix. Content is part of "fixed": a reinstall
|
|
# that leaves the manifest failing has repaired nothing.
|
|
if [[ $DRY_RUN -eq 0 ]] && [[ -L /usr/bin/pacman ]] && [[ -f /usr/bin/pacman.orig ]] &&
|
|
[[ -f /usr/local/bin/pacman_wrapper ]] && deployment_drift "$PACMAN_WRAPPER_MANIFEST"; then
|
|
status="ok"
|
|
fi
|
|
else
|
|
err_missing_script "Installer script not found: $PACMAN_WRAPPER_INSTALL"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
SERVICE_STATUS["pacman_wrapper"]=$status
|
|
}
|
|
|
|
check_makepkg_wrapper() {
|
|
header "Makepkg Wrapper"
|
|
|
|
local status="ok"
|
|
local issues=()
|
|
|
|
# Wrapper symlink
|
|
if [[ -L /usr/bin/makepkg ]]; then
|
|
local target
|
|
target=$(readlink -f /usr/bin/makepkg)
|
|
if [[ $target == "/usr/local/bin/makepkg_wrapper" ]]; then
|
|
msg "Makepkg symlink points to wrapper"
|
|
else
|
|
issues+=("Makepkg symlink points to: $target (expected /usr/local/bin/makepkg_wrapper)")
|
|
status="error"
|
|
fi
|
|
else
|
|
issues+=("Makepkg is not a symlink (wrapper not installed)")
|
|
status="error"
|
|
fi
|
|
|
|
# Original makepkg backup
|
|
if [[ -f /usr/bin/makepkg.orig ]]; then
|
|
msg "Original makepkg backed up at /usr/bin/makepkg.orig"
|
|
else
|
|
issues+=("Original makepkg backup not found at /usr/bin/makepkg.orig")
|
|
status="error"
|
|
fi
|
|
|
|
# Wrapper, shared lib, rewrap helper, survival hook
|
|
if [[ -f /usr/local/bin/makepkg_wrapper ]]; then
|
|
msg "Wrapper script exists at /usr/local/bin/makepkg_wrapper"
|
|
else
|
|
issues+=("Wrapper script not found at /usr/local/bin/makepkg_wrapper")
|
|
status="error"
|
|
fi
|
|
if [[ -f /usr/local/bin/pacman_lock_lib.sh ]]; then
|
|
msg "Shared lock library exists at /usr/local/bin/pacman_lock_lib.sh"
|
|
else
|
|
issues+=("Shared lock library not found at /usr/local/bin/pacman_lock_lib.sh")
|
|
status="error"
|
|
fi
|
|
if [[ -f /usr/local/bin/rewrap_pkg_managers.sh ]]; then
|
|
msg "Rewrap helper exists at /usr/local/bin/rewrap_pkg_managers.sh"
|
|
else
|
|
issues+=("Rewrap helper not found at /usr/local/bin/rewrap_pkg_managers.sh")
|
|
status="error"
|
|
fi
|
|
if [[ -f /etc/pacman.d/hooks/96-restore-pkg-wrappers.hook ]]; then
|
|
msg "Upgrade-survival hook installed"
|
|
else
|
|
issues+=("Upgrade-survival hook not installed at /etc/pacman.d/hooks/96-restore-pkg-wrappers.hook")
|
|
status="error"
|
|
fi
|
|
|
|
# Content drift (see check_pacman_wrapper for why existence is not enough)
|
|
local drift_rc=0
|
|
deployment_drift "$MAKEPKG_WRAPPER_MANIFEST" || drift_rc=$?
|
|
case $drift_rc in
|
|
0) msg "Makepkg wrapper matches the source it was installed from (no drift)" ;;
|
|
1)
|
|
issues+=("Deployed makepkg wrapper differs from its install manifest (stale or tampered)")
|
|
status="error"
|
|
;;
|
|
2)
|
|
issues+=("No drift manifest at $MAKEPKG_WRAPPER_MANIFEST — deployed version unverifiable")
|
|
status="error"
|
|
;;
|
|
esac
|
|
|
|
# Report and fix
|
|
if [[ $status == "error" ]]; then
|
|
for issue in "${issues[@]}"; do
|
|
err "$issue"
|
|
done
|
|
((ISSUES_FOUND++)) || true
|
|
|
|
if [[ $STATUS_ONLY -eq 0 ]]; then
|
|
note "Installing makepkg wrapper..."
|
|
if [[ -f $MAKEPKG_WRAPPER_INSTALL ]]; then
|
|
run bash "$MAKEPKG_WRAPPER_INSTALL"
|
|
((FIXES_APPLIED++)) || true
|
|
if [[ $DRY_RUN -eq 0 ]] && [[ -L /usr/bin/makepkg ]] && [[ -f /usr/bin/makepkg.orig ]] && [[ -f /usr/local/bin/makepkg_wrapper ]]; then
|
|
status="ok"
|
|
fi
|
|
else
|
|
err_missing_script "Installer script not found: $MAKEPKG_WRAPPER_INSTALL"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
SERVICE_STATUS["makepkg_wrapper"]=$status
|
|
}
|
|
|
|
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")
|
|
status="warning"
|
|
fi
|
|
|
|
# Check service file exists
|
|
if [[ -f /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 /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")
|
|
status="warning"
|
|
fi
|
|
|
|
# Check service file exists
|
|
if [[ -f /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 /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")
|
|
status="warning"
|
|
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")
|
|
status="warning"
|
|
fi
|
|
|
|
# Check maintenance script
|
|
if [[ -f /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 issues status "periodic_systems" \
|
|
"Setting up periodic systems..." \
|
|
"$PERIODIC_SYSTEM_SCRIPT" \
|
|
"periodic-system-maintenance.timer"
|
|
}
|
|
|
|
check_hosts() {
|
|
header "Hosts File and Guards"
|
|
|
|
local status="ok"
|
|
local issues=()
|
|
|
|
# Check /etc/hosts exists and has content
|
|
if [[ -f /etc/hosts ]]; then
|
|
local line_count
|
|
line_count=$(wc -l </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)")
|
|
status="warning"
|
|
fi
|
|
else
|
|
issues+=("/etc/hosts does not exist")
|
|
status="error"
|
|
fi
|
|
|
|
# Check if hosts file is immutable
|
|
local attrs
|
|
attrs=$(lsattr /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")
|
|
status="warning"
|
|
fi
|
|
|
|
# Check cached hosts file
|
|
if [[ -f /etc/hosts.stevenblack ]]; then
|
|
msg "StevenBlack cache exists at /etc/hosts.stevenblack"
|
|
else
|
|
issues+=("StevenBlack cache not found")
|
|
status="warning"
|
|
fi
|
|
|
|
# Check hosts guard path watcher
|
|
if systemctl is-enabled hosts-guard.path &>/dev/null; then
|
|
msg "hosts-guard.path is enabled"
|
|
else
|
|
issues+=("hosts-guard.path is not enabled")
|
|
status="error"
|
|
fi
|
|
|
|
if systemctl is-active hosts-guard.path &>/dev/null; then
|
|
msg "hosts-guard.path is active"
|
|
else
|
|
issues+=("hosts-guard.path is not active")
|
|
status="warning"
|
|
fi
|
|
|
|
# Check hosts bind mount service
|
|
if systemctl is-enabled hosts-bind-mount.service &>/dev/null; then
|
|
msg "hosts-bind-mount.service is enabled"
|
|
else
|
|
issues+=("hosts-bind-mount.service is not enabled")
|
|
status="warning"
|
|
fi
|
|
|
|
# Check enforcement script
|
|
if [[ -f /usr/local/sbin/enforce-hosts.sh ]]; then
|
|
msg "Enforcement script exists at /usr/local/sbin/enforce-hosts.sh"
|
|
else
|
|
issues+=("enforce-hosts.sh not found")
|
|
status="error"
|
|
fi
|
|
|
|
# Check unlock script
|
|
if [[ -f /usr/local/sbin/unlock-hosts ]]; then
|
|
msg "Unlock script exists at /usr/local/sbin/unlock-hosts"
|
|
else
|
|
issues+=("unlock-hosts not found")
|
|
status="warning"
|
|
fi
|
|
|
|
# Check locked hosts snapshot
|
|
if [[ -f /usr/local/share/locked-hosts ]]; then
|
|
msg "Canonical hosts snapshot exists at /usr/local/share/locked-hosts"
|
|
else
|
|
issues+=("Canonical hosts snapshot not found")
|
|
status="error"
|
|
fi
|
|
|
|
# Check pacman hooks
|
|
if hosts_pacman_hooks_installed; then
|
|
msg "Pacman hooks installed"
|
|
else
|
|
issues+=("Pacman hooks not installed")
|
|
status="warning"
|
|
fi
|
|
|
|
# Check nsswitch.conf has 'files' in hosts line
|
|
if [[ -f /etc/nsswitch.conf ]]; then
|
|
local nsswitch_hosts
|
|
nsswitch_hosts=$(grep '^hosts:' /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' — /etc/hosts is bypassed!")
|
|
status="error"
|
|
fi
|
|
else
|
|
issues+=("/etc/nsswitch.conf does not exist")
|
|
status="error"
|
|
fi
|
|
|
|
# Check nsswitch guard
|
|
if systemctl is-enabled nsswitch-guard.path &>/dev/null; then
|
|
msg "nsswitch-guard.path is enabled"
|
|
else
|
|
issues+=("nsswitch-guard.path is not enabled")
|
|
status="error"
|
|
fi
|
|
|
|
# Check nsswitch enforcement script
|
|
if [[ -f /usr/local/sbin/enforce-nsswitch.sh ]]; then
|
|
msg "nsswitch enforcement script exists at /usr/local/sbin/enforce-nsswitch.sh"
|
|
else
|
|
issues+=("enforce-nsswitch.sh not found")
|
|
status="error"
|
|
fi
|
|
|
|
# Check canonical nsswitch snapshot
|
|
if [[ -f /usr/local/share/locked-nsswitch.conf ]]; then
|
|
msg "Canonical nsswitch snapshot exists at /usr/local/share/locked-nsswitch.conf"
|
|
else
|
|
issues+=("Canonical nsswitch snapshot not found at /usr/local/share/locked-nsswitch.conf")
|
|
status="error"
|
|
fi
|
|
|
|
# Check resolved.conf has ReadEtcHosts=yes
|
|
if [[ -f /etc/systemd/resolved.conf ]]; then
|
|
local read_etc_hosts
|
|
read_etc_hosts=$(grep -E '^\s*ReadEtcHosts\s*=' /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' — /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*=' /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 /etc/hosts!")
|
|
status="error"
|
|
fi
|
|
|
|
# Check for drop-in overrides
|
|
if [[ -d /etc/systemd/resolved.conf.d ]]; then
|
|
local dropin_count
|
|
dropin_count=$(find /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 /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 resolved guard
|
|
if systemctl is-enabled resolved-guard.path &>/dev/null; then
|
|
msg "resolved-guard.path is enabled"
|
|
else
|
|
issues+=("resolved-guard.path is not enabled")
|
|
status="error"
|
|
fi
|
|
|
|
# Check resolved enforcement script
|
|
if [[ -f /usr/local/sbin/enforce-resolved.sh ]]; then
|
|
msg "resolved enforcement script exists at /usr/local/sbin/enforce-resolved.sh"
|
|
else
|
|
issues+=("enforce-resolved.sh not found")
|
|
status="error"
|
|
fi
|
|
|
|
# Check canonical resolved.conf snapshot
|
|
if [[ -f /usr/local/share/locked-resolved.conf ]]; then
|
|
msg "Canonical resolved.conf snapshot exists at /usr/local/share/locked-resolved.conf"
|
|
else
|
|
issues+=("Canonical resolved.conf snapshot not found at /usr/local/share/locked-resolved.conf")
|
|
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
|
|
# Fix nsswitch.conf if 'files' is missing (critical — hosts bypass)
|
|
if [[ -f /etc/nsswitch.conf ]]; then
|
|
local nsswitch_hosts_fix
|
|
nsswitch_hosts_fix=$(grep '^hosts:' /etc/nsswitch.conf 2>/dev/null || echo "")
|
|
if [[ -n $nsswitch_hosts_fix ]] && ! echo "$nsswitch_hosts_fix" | grep -qw 'files'; then
|
|
note "Fixing nsswitch.conf — adding 'files' to hosts line..."
|
|
if echo "$nsswitch_hosts_fix" | grep -qw 'resolve'; then
|
|
run sed -i 's/^hosts:\(.*\)resolve/hosts: files\1resolve/' /etc/nsswitch.conf
|
|
elif echo "$nsswitch_hosts_fix" | grep -qw 'dns'; then
|
|
run sed -i 's/^hosts:\(.*\)dns/hosts:\1files dns/' /etc/nsswitch.conf
|
|
else
|
|
run sed -i 's/^hosts:/hosts: files/' /etc/nsswitch.conf
|
|
fi
|
|
((FIXES_APPLIED++)) || true
|
|
msg "nsswitch.conf fixed: $(grep '^hosts:' /etc/nsswitch.conf)"
|
|
fi
|
|
fi
|
|
|
|
# Fix resolved.conf if ReadEtcHosts is not yes
|
|
if [[ -f /etc/systemd/resolved.conf ]]; then
|
|
local resolved_reh
|
|
resolved_reh=$(grep -E '^\s*ReadEtcHosts\s*=' /etc/systemd/resolved.conf 2>/dev/null |
|
|
tail -1 | sed 's/.*=\s*//' | tr -d '[:space:]')
|
|
if [[ "$resolved_reh" != "yes" ]]; then
|
|
note "Fixing resolved.conf — setting ReadEtcHosts=yes..."
|
|
chattr -i /etc/systemd/resolved.conf 2>/dev/null || true
|
|
if grep -qE '^\s*ReadEtcHosts\s*=' /etc/systemd/resolved.conf; then
|
|
run sed -i -E 's/^\s*ReadEtcHosts\s*=.*/ReadEtcHosts=yes/' /etc/systemd/resolved.conf
|
|
elif grep -q '^\[Resolve\]' /etc/systemd/resolved.conf; then
|
|
run sed -i '/^\[Resolve\]/a ReadEtcHosts=yes' /etc/systemd/resolved.conf
|
|
else
|
|
printf '\n[Resolve]\nReadEtcHosts=yes\n' >>/etc/systemd/resolved.conf
|
|
fi
|
|
chattr +i /etc/systemd/resolved.conf 2>/dev/null || true
|
|
run systemctl restart systemd-resolved
|
|
((FIXES_APPLIED++)) || true
|
|
msg "resolved.conf ReadEtcHosts fixed"
|
|
fi
|
|
|
|
# Fix DNSOverTLS if enabled
|
|
local resolved_dot
|
|
resolved_dot=$(grep -E '^\s*DNSOverTLS\s*=' /etc/systemd/resolved.conf 2>/dev/null |
|
|
tail -1 | sed 's/.*=\s*//' | tr -d '[:space:]') || true
|
|
if [[ -n "$resolved_dot" && "$resolved_dot" != "no" ]]; then
|
|
note "Fixing resolved.conf — disabling DNSOverTLS..."
|
|
chattr -i /etc/systemd/resolved.conf 2>/dev/null || true
|
|
run sed -i -E 's/^\s*DNSOverTLS\s*=.*/#DNSOverTLS=no/' /etc/systemd/resolved.conf
|
|
chattr +i /etc/systemd/resolved.conf 2>/dev/null || true
|
|
run systemctl restart systemd-resolved
|
|
((FIXES_APPLIED++)) || true
|
|
msg "resolved.conf DNSOverTLS disabled"
|
|
fi
|
|
|
|
# Remove drop-in overrides
|
|
if [[ -d /etc/systemd/resolved.conf.d ]]; then
|
|
local dropin_fix_count
|
|
dropin_fix_count=$(find /etc/systemd/resolved.conf.d -name '*.conf' -type f 2>/dev/null | wc -l)
|
|
if [[ "$dropin_fix_count" -gt 0 ]]; then
|
|
note "Removing $dropin_fix_count resolved.conf drop-in override(s)..."
|
|
chattr -i /etc/systemd/resolved.conf.d 2>/dev/null || true
|
|
find /etc/systemd/resolved.conf.d -name '*.conf' -type f -delete
|
|
chattr +i /etc/systemd/resolved.conf.d 2>/dev/null || true
|
|
run systemctl restart systemd-resolved
|
|
((FIXES_APPLIED++)) || true
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# Run hosts install first
|
|
if [[ ! -f /etc/hosts ]] || [[ $(wc -l </etc/hosts) -lt 100 ]]; then
|
|
note "Installing hosts file..."
|
|
if [[ -f $HOSTS_INSTALL_SCRIPT ]]; then
|
|
run bash "$HOSTS_INSTALL_SCRIPT"
|
|
((FIXES_APPLIED++)) || true
|
|
else
|
|
err_missing_script "Hosts install script not found: $HOSTS_INSTALL_SCRIPT"
|
|
fi
|
|
fi
|
|
|
|
# Run hosts guard setup (also installs nsswitch-guard and resolved-guard)
|
|
if ! systemctl is-enabled hosts-guard.path &>/dev/null ||
|
|
! systemctl is-enabled nsswitch-guard.path &>/dev/null ||
|
|
! systemctl is-enabled resolved-guard.path &>/dev/null ||
|
|
[[ ! -f /usr/local/sbin/enforce-hosts.sh ]] ||
|
|
[[ ! -f /usr/local/sbin/enforce-nsswitch.sh ]] ||
|
|
[[ ! -f /usr/local/sbin/enforce-resolved.sh ]] ||
|
|
[[ ! -f /usr/local/share/locked-nsswitch.conf ]] ||
|
|
[[ ! -f /usr/local/share/locked-resolved.conf ]]; then
|
|
note "Setting up hosts guard (includes nsswitch-guard and resolved-guard)..."
|
|
if [[ -f $HOSTS_GUARD_SCRIPT ]]; then
|
|
run bash "$HOSTS_GUARD_SCRIPT"
|
|
((FIXES_APPLIED++)) || true
|
|
else
|
|
err_missing_script "Hosts guard script not found: $HOSTS_GUARD_SCRIPT"
|
|
fi
|
|
fi
|
|
|
|
# Install pacman hooks if missing
|
|
if ! hosts_pacman_hooks_installed; then
|
|
note "Installing pacman hooks..."
|
|
if [[ -f $HOSTS_PACMAN_HOOKS_SCRIPT ]]; then
|
|
run bash "$HOSTS_PACMAN_HOOKS_SCRIPT"
|
|
((FIXES_APPLIED++)) || true
|
|
else
|
|
err_missing_script "Pacman hooks script not found: $HOSTS_PACMAN_HOOKS_SCRIPT"
|
|
fi
|
|
fi
|
|
|
|
# Re-verify after fixes
|
|
if [[ $DRY_RUN -eq 0 ]]; then
|
|
if systemctl is-enabled hosts-guard.path &>/dev/null &&
|
|
systemctl is-enabled nsswitch-guard.path &>/dev/null &&
|
|
systemctl is-enabled resolved-guard.path &>/dev/null &&
|
|
[[ -f /usr/local/sbin/enforce-hosts.sh ]] &&
|
|
[[ -f /usr/local/sbin/enforce-nsswitch.sh ]] &&
|
|
[[ -f /usr/local/sbin/enforce-resolved.sh ]] &&
|
|
[[ -f /usr/local/share/locked-hosts ]] &&
|
|
[[ -f /usr/local/share/locked-nsswitch.conf ]] &&
|
|
[[ -f /usr/local/share/locked-resolved.conf ]] &&
|
|
hosts_pacman_hooks_installed; then
|
|
# Downgrade to warning if only minor issues remain (immutable attr, etc.)
|
|
status="ok"
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
SERVICE_STATUS["hosts"]=$status
|
|
}
|
|
|
|
check_compulsive_blocker() {
|
|
header "Compulsive Opening Blocker"
|
|
|
|
local status="ok"
|
|
local issues=()
|
|
|
|
# Check if main script is installed
|
|
if [[ -f /usr/local/bin/block-compulsive-opening.sh ]]; then
|
|
msg "Blocker script installed at /usr/local/bin/block-compulsive-opening.sh"
|
|
else
|
|
issues+=("block-compulsive-opening.sh not found in /usr/local/bin")
|
|
status="error"
|
|
fi
|
|
|
|
# Check if wrappers are installed for known apps
|
|
local checked_any=false
|
|
for app in beeper signal-desktop discord; do
|
|
local wrapper_path="/usr/bin/$app"
|
|
if [[ -f "${wrapper_path}.orig" ]] || [[ -L "$wrapper_path" ]]; then
|
|
if [[ -f "${wrapper_path}.orig" ]]; then
|
|
msg "$app wrapper installed (original backed up)"
|
|
checked_any=true
|
|
fi
|
|
elif command -v "$app" &>/dev/null; then
|
|
issues+=("$app is installed but wrapper not applied")
|
|
if [[ $status != "error" ]]; then status="warning"; fi
|
|
checked_any=true
|
|
fi
|
|
done
|
|
|
|
if [[ $checked_any == false && $status == "ok" ]]; then
|
|
note "No target apps (beeper, signal-desktop, discord) found on system"
|
|
fi
|
|
|
|
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 && $status == "error" ]]; then
|
|
note "Installing compulsive opening blocker..."
|
|
if [[ -f $COMPULSIVE_BLOCK_SCRIPT ]]; then
|
|
run bash "$COMPULSIVE_BLOCK_SCRIPT" install
|
|
((FIXES_APPLIED++)) || true
|
|
else
|
|
err_missing_script "Install script not found: $COMPULSIVE_BLOCK_SCRIPT"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
SERVICE_STATUS["compulsive_blocker"]=$status
|
|
}
|
|
|
|
check_thorium_startup() {
|
|
header "Thorium Browser Auto-Startup (Fitatu)"
|
|
|
|
local status="ok"
|
|
local issues=()
|
|
|
|
# Check system service
|
|
if systemctl is-enabled thorium-fitatu-startup.service &>/dev/null; then
|
|
msg "thorium-fitatu-startup.service is enabled (system)"
|
|
else
|
|
# Check user service as fallback
|
|
local user="${SUDO_USER:-$USER}"
|
|
if user_systemctl "$user" is-enabled thorium-fitatu-startup.service &>/dev/null 2>&1; then
|
|
msg "thorium-fitatu-startup.service is enabled (user service)"
|
|
else
|
|
issues+=("thorium-fitatu-startup.service is not enabled")
|
|
status="error"
|
|
fi
|
|
fi
|
|
|
|
# Check if thorium is available
|
|
if command -v thorium-browser &>/dev/null || [[ -x /opt/thorium/thorium ]] || [[ -x /opt/thorium-browser/thorium-browser ]]; then
|
|
msg "Thorium browser is installed"
|
|
else
|
|
issues+=("Thorium browser not found")
|
|
if [[ $status != "error" ]]; then status="warning"; fi
|
|
fi
|
|
|
|
report_and_fix issues status "thorium_startup" \
|
|
"Setting up Thorium startup..." \
|
|
"$THORIUM_STARTUP_SCRIPT" \
|
|
"thorium-fitatu-startup.service"
|
|
}
|
|
|
|
check_leechblock() {
|
|
header "LeechBlock Browser Extension"
|
|
|
|
local status="ok"
|
|
local issues=()
|
|
local user="${SUDO_USER:-$USER}"
|
|
local user_home
|
|
user_home="/home/$user"
|
|
|
|
# Check if LeechBlock is installed for any browser
|
|
local leechblock_dir="$user_home/.local/share/leechblockng"
|
|
if [[ -d $leechblock_dir ]]; then
|
|
msg "LeechBlock directory exists at $leechblock_dir"
|
|
else
|
|
issues+=("LeechBlock not found at $leechblock_dir")
|
|
status="error"
|
|
fi
|
|
|
|
# Check for browser wrappers with LeechBlock
|
|
local found_wrapper=false
|
|
for desktop_file in "$user_home/.local/share/applications/"*leechblock* "$user_home/.local/share/applications/"*LeechBlock*; do
|
|
if [[ -f $desktop_file ]]; then
|
|
msg "LeechBlock desktop entry found: $(basename "$desktop_file")"
|
|
found_wrapper=true
|
|
fi
|
|
done
|
|
|
|
if [[ $found_wrapper == false && -d $leechblock_dir ]]; then
|
|
issues+=("No LeechBlock desktop entries found")
|
|
if [[ $status != "error" ]]; then status="warning"; fi
|
|
fi
|
|
|
|
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 && $status == "error" ]]; then
|
|
note "Installing LeechBlock..."
|
|
if [[ -f $LEECHBLOCK_SCRIPT ]]; then
|
|
run sudo -u "$user" bash "$LEECHBLOCK_SCRIPT"
|
|
((FIXES_APPLIED++)) || true
|
|
else
|
|
err_missing_script "Install script not found: $LEECHBLOCK_SCRIPT"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
SERVICE_STATUS["leechblock"]=$status
|
|
}
|
|
|
|
check_guest_mode_removal() {
|
|
header "Chromium Guest Mode Removal"
|
|
|
|
local status="ok"
|
|
local issues=()
|
|
|
|
# Check if managed policy files exist for any browser
|
|
local policy_found=false
|
|
for policy_dir in \
|
|
/etc/chromium/policies/managed \
|
|
/etc/opt/chrome/policies/managed \
|
|
/etc/thorium/policies/managed \
|
|
/etc/brave/policies/managed; do
|
|
if [[ -d $policy_dir ]] && ls "$policy_dir"/*.json &>/dev/null 2>&1; then
|
|
# Check for guest mode policy
|
|
if grep -rl 'BrowserGuestModeEnabled' "$policy_dir" &>/dev/null 2>&1; then
|
|
msg "Guest mode policy found in $policy_dir"
|
|
policy_found=true
|
|
fi
|
|
fi
|
|
done
|
|
|
|
if [[ $policy_found == false ]]; then
|
|
# Only flag as issue if a Chromium browser is actually installed
|
|
if command -v thorium-browser &>/dev/null || command -v chromium &>/dev/null || command -v google-chrome &>/dev/null || command -v brave-browser &>/dev/null; then
|
|
issues+=("No guest mode removal policies found for installed browsers")
|
|
status="error"
|
|
else
|
|
note "No Chromium-based browsers detected, skipping"
|
|
fi
|
|
fi
|
|
|
|
if [[ $status != "ok" ]]; then
|
|
for issue in "${issues[@]}"; do
|
|
err "$issue"
|
|
done
|
|
((ISSUES_FOUND++)) || true
|
|
|
|
if [[ $STATUS_ONLY -eq 0 ]]; then
|
|
note "Removing guest mode..."
|
|
if [[ -f $REMOVE_GUEST_MODE_SCRIPT ]]; then
|
|
run bash "$REMOVE_GUEST_MODE_SCRIPT"
|
|
((FIXES_APPLIED++)) || true
|
|
else
|
|
err_missing_script "Script not found: $REMOVE_GUEST_MODE_SCRIPT"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
SERVICE_STATUS["guest_mode_removal"]=$status
|
|
}
|
|
|
|
check_vbox_hosts() {
|
|
header "VirtualBox Hosts Enforcement"
|
|
|
|
local status="ok"
|
|
local issues=()
|
|
|
|
# Only check if VirtualBox is installed.
|
|
# This is NOT "skipped" in the sense of ducking a check: VirtualBox is absent
|
|
# AND deliberately blocked by pacman policy — pacman_blocked_keywords.txt lists
|
|
# virtualbox/vbox with the comment "can bypass /etc/hosts restrictions", which
|
|
# is the very thing this check enforces. With no VirtualBox there are no VMs to
|
|
# enforce /etc/hosts inside, so the check is genuinely not applicable. Report
|
|
# n/a so it reads as deliberate rather than something that got ducked.
|
|
if ! command -v VBoxManage &>/dev/null; then
|
|
note "VirtualBox not installed (blocked by pacman policy) — not applicable"
|
|
SERVICE_STATUS["vbox_hosts"]="n/a"
|
|
return
|
|
fi
|
|
|
|
# Check if enforcement marker exists
|
|
if [[ -f /var/lib/vbox-hosts-enforced ]]; then
|
|
msg "VirtualBox hosts enforcement marker exists"
|
|
else
|
|
issues+=("VirtualBox hosts enforcement not applied")
|
|
status="error"
|
|
fi
|
|
|
|
if [[ $status != "ok" ]]; then
|
|
for issue in "${issues[@]}"; do
|
|
err "$issue"
|
|
done
|
|
((ISSUES_FOUND++)) || true
|
|
|
|
if [[ $STATUS_ONLY -eq 0 ]]; then
|
|
note "Enforcing hosts in VirtualBox VMs..."
|
|
if [[ -f $VBOX_HOSTS_SCRIPT ]]; then
|
|
run bash "$VBOX_HOSTS_SCRIPT"
|
|
((FIXES_APPLIED++)) || true
|
|
else
|
|
err_missing_script "Script not found: $VBOX_HOSTS_SCRIPT"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
SERVICE_STATUS["vbox_hosts"]=$status
|
|
}
|
|
|
|
check_workout_locker() {
|
|
header "Workout Lock Screen"
|
|
|
|
local status="ok"
|
|
local issues=()
|
|
local user="${SUDO_USER:-$USER}"
|
|
|
|
# Check screen_lock.py script exists
|
|
if [[ -f $WORKOUT_LOCKER_SCRIPT ]]; then
|
|
msg "screen_lock.py exists at $WORKOUT_LOCKER_SCRIPT"
|
|
else
|
|
issues+=("screen_lock.py not found at $WORKOUT_LOCKER_SCRIPT")
|
|
status="error"
|
|
fi
|
|
|
|
# Check user service is enabled
|
|
if user_systemctl "$user" is-enabled workout-locker.service &>/dev/null 2>&1; then
|
|
msg "workout-locker.service is enabled (user service)"
|
|
else
|
|
issues+=("workout-locker.service is not enabled (user service)")
|
|
status="error"
|
|
fi
|
|
|
|
# Check user service is active (advisory — it only runs at login)
|
|
if user_systemctl "$user" is-active workout-locker.service &>/dev/null 2>&1; then
|
|
msg "workout-locker.service is active"
|
|
else
|
|
issues+=("workout-locker.service is not active (expected at login time)")
|
|
if [[ $status != "error" ]]; then status="warning"; fi
|
|
fi
|
|
|
|
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 && $status == "error" ]]; then
|
|
note "Installing workout locker service..."
|
|
if [[ -f $WORKOUT_LOCKER_INSTALL_SCRIPT ]]; then
|
|
run sudo -u "$user" bash "$WORKOUT_LOCKER_INSTALL_SCRIPT"
|
|
((FIXES_APPLIED++)) || true
|
|
# Re-verify
|
|
if [[ $DRY_RUN -eq 0 ]] && user_systemctl "$user" is-enabled workout-locker.service &>/dev/null 2>&1; then
|
|
status="ok"
|
|
fi
|
|
else
|
|
err_missing_script "Install script not found: $WORKOUT_LOCKER_INSTALL_SCRIPT"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
SERVICE_STATUS["workout_locker"]=$status
|
|
}
|
|
print_summary() {
|
|
header "Summary"
|
|
|
|
echo ""
|
|
printf "%-25s %s\n" "Service" "Status"
|
|
printf "%-25s %s\n" "-------" "------"
|
|
|
|
for service in pacman_wrapper makepkg_wrapper midnight_shutdown startup_monitor periodic_systems hosts compulsive_blocker thorium_startup leechblock guest_mode_removal vbox_hosts workout_locker; do
|
|
local status="${SERVICE_STATUS[$service]:-unknown}"
|
|
local color
|
|
case "$status" in
|
|
ok) color=$GREEN ;;
|
|
warning) color=$YELLOW ;;
|
|
error) color=$RED ;;
|
|
"n/a" | skipped) color=$BLUE ;;
|
|
*) color=$NC ;;
|
|
esac
|
|
printf "%-25s ${color}%s${NC}\n" "$service" "$status"
|
|
done
|
|
|
|
echo ""
|
|
if [[ $DRY_RUN -eq 1 ]]; then
|
|
note "DRY RUN - No changes were made"
|
|
fi
|
|
|
|
if [[ $ISSUES_FOUND -eq 0 ]]; then
|
|
msg "All services are properly configured!"
|
|
else
|
|
if [[ $STATUS_ONLY -eq 1 ]]; then
|
|
warn "Found $ISSUES_FOUND service(s) with issues"
|
|
note "Run without --status to fix issues"
|
|
else
|
|
if [[ $FIXES_APPLIED -gt 0 ]]; then
|
|
msg "Applied $FIXES_APPLIED fix(es)"
|
|
else
|
|
warn "Found $ISSUES_FOUND issue(s) but no fixes were applied"
|
|
fi
|
|
fi
|
|
fi
|
|
}
|
|
|
|
######################################################################
|
|
# Main
|
|
######################################################################
|
|
main() {
|
|
echo ""
|
|
echo "Digital Wellbeing Services Status Check"
|
|
echo "========================================"
|
|
echo "Date: $(date)"
|
|
echo "User: ${SUDO_USER:-$USER}"
|
|
if [[ $DRY_RUN -eq 1 ]]; then
|
|
echo "Mode: DRY RUN (no changes will be made)"
|
|
elif [[ $STATUS_ONLY -eq 1 ]]; then
|
|
echo "Mode: STATUS ONLY (no changes will be made)"
|
|
else
|
|
echo "Mode: CHECK AND FIX"
|
|
fi
|
|
|
|
check_pacman_wrapper
|
|
check_makepkg_wrapper
|
|
check_midnight_shutdown
|
|
check_startup_monitor
|
|
check_periodic_systems
|
|
check_hosts
|
|
check_compulsive_blocker
|
|
check_thorium_startup
|
|
check_leechblock
|
|
check_guest_mode_removal
|
|
check_vbox_hosts
|
|
check_workout_locker
|
|
|
|
print_summary
|
|
}
|
|
|
|
main
|
|
|
|
######################################################################
|
|
# A missing repair script means this tool cannot do the one job it exists for.
|
|
# Exiting 0 there is indistinguishable from "everything is fine" — which is how
|
|
# it hid dead hosts self-repair for five months. Make it impossible to miss.
|
|
######################################################################
|
|
if [[ ${#MISSING_SCRIPTS[@]} -gt 0 ]]; then
|
|
printf "\n${RED}"
|
|
printf '=%.0s' {1..74}
|
|
printf "\n SELF-REPAIR IS BROKEN — %d repair script(s) MISSING\n" "${#MISSING_SCRIPTS[@]}"
|
|
printf '=%.0s' {1..74}
|
|
printf "${NC}\n"
|
|
for missing in "${MISSING_SCRIPTS[@]}"; do
|
|
err "$missing"
|
|
done
|
|
printf "${RED}The services above are NOT being repaired — this tool found the\n"
|
|
printf "problem and then could not run the fix. Correct these paths first.${NC}\n"
|
|
printf "${CYAN}Also logged at error priority: journalctl -p err -t check-and-enable-services${NC}\n\n"
|
|
exit 2
|
|
fi
|
|
|
|
exit 0
|