testsAndMisc/linux_configuration/periodic_background/check_and_enable_services.sh
Krzysztof kuhy Rudnicki 6b828d29aa Extract digital-wellbeing; the directory-depth cap now enforces
The last unit over the cap is now github.com/kuhyx/digital-wellbeing, cloned
at ~/digital-wellbeing, and `--warn` is gone from the directory-depth-cap
hook. `check_directory_depth.sh --all` exits 0 with zero violations, down from
46 at the start of the session. That was the point of the campaign.

History was filtered across all three paths the unit has lived at (63
commits); the current path alone holds 2.

The six suites that test this code lived in linux_configuration/tests/ rather
than beside it, so they were filtered separately (24 commits) and merged in as
a second root. A path filter of the unit alone would have left them here,
testing code that had left the repo -- and their names would still have been
in shell-tests.yml's hardcoded arrays, failing CI on missing files.

linux_configuration/lib/common.sh is vendored into vendor/ rather than trimmed
to the four functions actually used: set_actual_user_vars calls two of the
others, so a trim breaks silently. It is in vendor/ and not lib/ because
test_music_parallelism.sh copies the unit's own lib/ into a scratch worktree
while stubbing the shared lib; in one directory the real common.sh overwrote
the stub, and the suite silently measured the wrong library.

Running the callers caught what shellcheck and grep both missed:
check_and_enable_services.sh died with "DW_REPO: unbound variable" because the
definition sat below its first use. It now runs clean and reports ok for every
extracted-unit check.

Two long-standing live defects fixed as a consequence. The pacman-wrapper
drift manifest embeds ABSOLUTE paths and had named deleted scripts/ paths
since the flatten, so the check reported a permanent false "stale or
tampered"; it now verifies, with unchanged hashes proving the extracted bytes
are identical. And the last two stale live references are gone: 8 -> 0.

The two pre-commit excludes for setup_midnight_shutdown.sh are dropped rather
than moved. lsattr shows nothing in the unit is chattr +i any more, and the
fixer hooks leave the file alone when allowed to run.

Removing 80 shell files also shrank the jscpd denominator enough that the
virtualenv/tqdm clones inside the GENERATED .ci-mirror-venv/ tipped the repo
to 2.09% and blocked this commit, which adds no duplication. That directory is
the same vendored third-party shell the ignore list already excludes as
.venv/, just under another name; jscpd walks the working tree rather than the
index, so being untracked did not spare it. Excluded by name: 1.74%.

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

199 lines
7.3 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
# Prefix for every absolute path the checks probe. Empty means the real
# filesystem, which is the only value production ever uses; the test harness
# points it at a fixture tree instead. The libs read it via `${SERVICES_ROOT?}`
# with no default, so this assignment is required rather than optional: several
# repairs write outside `run` (chattr, find -delete, an append to
# resolved.conf), and a caller that forgot to set it would edit the real /etc.
export SERVICES_ROOT=""
# Every check lives in a lib beside this file; this script keeps only the
# configuration, argument parsing and the order the checks run in.
LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib"
# shellcheck source=lib/services_common.sh
. "$LIB_DIR/services_common.sh"
# shellcheck source=lib/services_wrappers.sh
. "$LIB_DIR/services_wrappers.sh"
# shellcheck source=lib/services_units.sh
. "$LIB_DIR/services_units.sh"
# shellcheck source=lib/services_hosts.sh
. "$LIB_DIR/services_hosts.sh"
# shellcheck source=lib/services_hosts_fix.sh
. "$LIB_DIR/services_hosts_fix.sh"
# shellcheck source=lib/services_apps.sh
. "$LIB_DIR/services_apps.sh"
# shellcheck source=lib/services_browser.sh
. "$LIB_DIR/services_browser.sh"
# shellcheck source=lib/services_report.sh
. "$LIB_DIR/services_report.sh"
######################################################################
# 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")")"
# 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"
HOSTS_BLOCKER_REPO="${HOSTS_BLOCKER_DIR:-$REAL_HOME/hosts-blocker}"
# digital-wellbeing was EXTRACTED too (github.com/kuhyx/digital-wellbeing).
DW_REPO="${DIGITAL_WELLBEING_DIR:-$REAL_HOME/digital-wellbeing}"
# Script paths
PACMAN_WRAPPER_INSTALL="$DW_REPO/pacman/install_pacman_wrapper.sh"
MAKEPKG_WRAPPER_INSTALL="$DW_REPO/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="$DW_REPO/setup_midnight_shutdown.sh"
STARTUP_MONITOR_SCRIPT="$DW_REPO/setup_pc_startup_monitor.sh"
PERIODIC_SYSTEM_SCRIPT="$CONFIG_DIR/periodic_background/setup_periodic_system.sh"
# hosts-blocker was EXTRACTED (github.com/kuhyx/hosts-blocker). Resolved
# below, after REAL_HOME is computed, for the same reason screen-locker is.
HOSTS_INSTALL_SCRIPT=""
GUARD_LIB_MIGRATE_SCRIPT="$CONFIG_DIR/./fixes/migrate_hosts_guard_to_guard_lib.sh"
COMPULSIVE_BLOCK_SCRIPT="$DW_REPO/block_compulsive_opening.sh"
LEECHBLOCK_SCRIPT="$DW_REPO/install_leechblock.sh"
REMOVE_GUEST_MODE_SCRIPT="$DW_REPO/remove_guest_mode.sh"
VBOX_HOSTS_SCRIPT="$DW_REPO/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.
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"
HOSTS_INSTALL_SCRIPT="$HOSTS_BLOCKER_REPO/install.sh"
# 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=()
######################################################################
# 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
######################################################################
# Check functions
######################################################################
######################################################################
# 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_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