testsAndMisc/linux_configuration/scripts/periodic_background/check_and_enable_services.sh
Krzysztof Rudnicki b83ce5d3c7
Some checks failed
Pre-commit checks / pre-commit (push) Waiting to run
Python tests / test (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
billsplit CI / quality (push) Has been cancelled
reverse-survivors CI / check (push) Has been cancelled
Fail the build on any shellcheck finding; fix the 222 the old gate hid
The shellcheck hook ran with --severity=warning, so info- and style-level
findings never failed a commit. 222 had accumulated across 94 of 244 shell
files. They were not all style noise -- six were bugs:

  - Five scripts in single_use/misc/testsAndMisc-bash/ sourced
    $SCRIPT_DIR/../../lib/common.sh, one directory too shallow. Nothing exists
    at that path, so all five failed at runtime.
  - setup_personal_website.sh and setup_kcd2_dice_solver.sh called usage()
    (which ends in `exit 0`) and then `exit 1` for an unknown command. The
    exit 1 was unreachable, so a typo'd subcommand reported SUCCESS.
  - Six i3blocks scripts had a `# shellcheck source=` directive missing the
    scripts/periodic_background/ path segment. The stale path had silently
    disabled source-following for all of them.
  - diagnose_arch_performance.sh word-split a newline-joined file list; when
    the find came back empty, awk got no file operands, fell through to
    reading stdin, and the diagnostic hung.
  - lib/common.sh's handle_arg_help_or_unknown ended in an unreachable
    `return 0` while documenting "Returns: 0 if handled" -- a contract no code
    path could satisfy.
  - deploy.sh's SCRIPT_DIR is reassigned by config.sh, which it sources at top
    level, and config.sh can repoint it at the on-device path
    /data/local/tmp/focus_mode. Renamed to DEPLOY_DIR.

The SC1091 bulk (81 findings) was resolved rather than silenced: .shellcheckrc
sets external-sources + source-path=SCRIPTDIR so sourced files are actually
followed and analysed. That is what surfaced the i3blocks and deploy.sh bugs.

17 `# shellcheck disable=` directives remain, each with a written reason, and
only where the finding is provably wrong: injection-test payloads that must not
expand, literal source text being grepped for, POSIX sh scripts on the phone
where arrays do not exist, and source paths that only exist at runtime.
Everything else was fixed in code.

Verified in a CLEAN worktree, not just my own tree. config.sh sources the
gitignored config_secrets.sh, which exists locally but not in a fresh checkout;
a working-tree-only check passed while ci_mirror.sh -- which lints HEAD in a
clean worktree, exactly as CI does -- failed on it.

The gate is verified to bite: a file whose only finding is SC2016 (info) fails
the hook, while the old --severity=warning gate exits 0 on it.

third_party/ is excluded -- vendored upstream, matching the prettier hook.

Not fixed, reported: check_polling_antipatterns.sh carries a pre-existing
SC2034 disable that hides a real bug -- its `patterns` array is never used, so
the checker detects nothing. Confirmed unchanged by this commit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 19:49:11 +02:00

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%s' "$RED"
printf '=%.0s' {1..74}
printf "\n SELF-REPAIR IS BROKEN — %d repair script(s) MISSING\n" "${#MISSING_SCRIPTS[@]}"
printf '=%.0s' {1..74}
printf '%s\n' "$NC"
for missing in "${MISSING_SCRIPTS[@]}"; do
err "$missing"
done
printf '%sThe services above are NOT being repaired — this tool found the\n' "$RED"
printf 'problem and then could not run the fix. Correct these paths first.%s\n' "$NC"
printf '%sAlso logged at error priority: journalctl -p err -t check-and-enable-services%s\n\n' "$CYAN" "$NC"
exit 2
fi
exit 0