mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 19:07:04 +01:00
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
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>
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%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
|