From 09e85cd91439a1a21a6bc26695871ad6b7d7b6c2 Mon Sep 17 00:00:00 2001 From: Krzysztof Rudnicki Date: Fri, 20 Feb 2026 20:24:13 +0100 Subject: [PATCH] feat: LeechBlock default config, Chrome repo, nsswitch fixes, extended checker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add leechblock_defaults.js with pre-configured blocking rules matching hosts/install.sh (YouTube, food delivery, fast food — 3 block sets) - install_leechblock.sh: switch to LeechBlockNG-chrome repo, download jQuery UI, inject defaults.js into extension, patch background.js to seed storage on first run, replace browser binary in-place - remove_guest_mode.sh: fix associative array key spacing - enforce-nsswitch.sh: handle 'resolve' without 'dns' in emergency fix - setup_hosts_guard.sh: ensure 'files' in nsswitch hosts line before snapshotting, remove erroneous 'local' outside function - check_and_enable_services.sh: extend from 5 to 12 services, add nsswitch.conf validation and auto-fix --- .../hosts/guard/enforce-nsswitch.sh | 10 +- .../hosts/guard/setup_hosts_guard.sh | 16 + .../scripts/check_and_enable_services.sh | 1273 +++++++++++------ .../digital_wellbeing/install_leechblock.sh | 495 ++++--- .../digital_wellbeing/leechblock_defaults.js | 151 ++ .../digital_wellbeing/remove_guest_mode.sh | 162 +-- 6 files changed, 1390 insertions(+), 717 deletions(-) create mode 100644 linux_configuration/scripts/digital_wellbeing/leechblock_defaults.js diff --git a/linux_configuration/hosts/guard/enforce-nsswitch.sh b/linux_configuration/hosts/guard/enforce-nsswitch.sh index ec01fd0..e751a8a 100755 --- a/linux_configuration/hosts/guard/enforce-nsswitch.sh +++ b/linux_configuration/hosts/guard/enforce-nsswitch.sh @@ -69,9 +69,15 @@ if ! validate_hosts_line "$current_hosts_line"; then log "ERROR: Canonical source not found at $CANONICAL_SOURCE" # Emergency fix: add "files" back to hosts line chattr -i "$TARGET" 2>/dev/null || true - sed -i 's/^hosts:\(.*\)dns/hosts:\1files dns/' "$TARGET" + if grep -q '^hosts:.*dns' "$TARGET"; then + sed -i 's/^hosts:\(.*\)dns/hosts:\1files dns/' "$TARGET" + elif grep -q '^hosts:.*resolve' "$TARGET"; then + sed -i 's/^hosts:\(.*\)resolve/hosts: files\1resolve/' "$TARGET" + else + sed -i 's/^hosts:/hosts: files/' "$TARGET" + fi chattr +i "$TARGET" 2>/dev/null || true - log "Emergency fix applied: added 'files' before 'dns'" + log "Emergency fix applied: added 'files' to hosts line" fi exit 0 fi diff --git a/linux_configuration/hosts/guard/setup_hosts_guard.sh b/linux_configuration/hosts/guard/setup_hosts_guard.sh index e31dbde..61c08b8 100755 --- a/linux_configuration/hosts/guard/setup_hosts_guard.sh +++ b/linux_configuration/hosts/guard/setup_hosts_guard.sh @@ -394,6 +394,22 @@ if [[ $ENABLE_NSSWITCH -eq 1 ]]; then msg "Installing nsswitch enforcement script -> $INSTALL_ENFORCE_NSSWITCH" run install -m 755 "$TEMPLATE_ENFORCE_NSSWITCH" "$INSTALL_ENFORCE_NSSWITCH" + # Ensure 'files' is present in the hosts line before snapshotting + if [[ -f "$NSSWITCH" ]]; then + hosts_line=$(grep '^hosts:' "$NSSWITCH" 2>/dev/null || echo "") + if [[ -n "$hosts_line" ]] && ! echo "$hosts_line" | grep -qw 'files'; then + msg "Adding 'files' to nsswitch.conf hosts line (was: $hosts_line)" + if echo "$hosts_line" | grep -qw 'resolve'; then + run sed -i 's/^hosts:\(.*\)resolve/hosts: files\1resolve/' "$NSSWITCH" + elif echo "$hosts_line" | grep -qw 'dns'; then + run sed -i 's/^hosts:\(.*\)dns/hosts:\1files dns/' "$NSSWITCH" + else + run sed -i 's/^hosts:/hosts: files/' "$NSSWITCH" + fi + msg "nsswitch.conf hosts line fixed: $(grep '^hosts:' "$NSSWITCH")" + fi + fi + # Create nsswitch canonical snapshot if needed if [[ -f "$NSSWITCH" ]]; then if [[ ! -f "$CANON_NSSWITCH" ]]; then diff --git a/linux_configuration/scripts/check_and_enable_services.sh b/linux_configuration/scripts/check_and_enable_services.sh index dd75461..7c1b54d 100755 --- a/linux_configuration/scripts/check_and_enable_services.sh +++ b/linux_configuration/scripts/check_and_enable_services.sh @@ -37,6 +37,13 @@ PERIODIC_SYSTEM_SCRIPT="$CONFIG_DIR/scripts/setup_periodic_system.sh" HOSTS_INSTALL_SCRIPT="$CONFIG_DIR/hosts/install.sh" HOSTS_GUARD_SCRIPT="$CONFIG_DIR/hosts/guard/setup_hosts_guard.sh" HOSTS_PACMAN_HOOKS_SCRIPT="$CONFIG_DIR/hosts/guard/install_pacman_hooks.sh" +THESIS_TRACKER_SCRIPT="$CONFIG_DIR/scripts/digital_wellbeing/setup_thesis_work_tracker.sh" +FOCUS_MODE_SCRIPT="$CONFIG_DIR/scripts/digital_wellbeing/install_focus_mode_daemon.sh" +COMPULSIVE_BLOCK_SCRIPT="$CONFIG_DIR/scripts/digital_wellbeing/block_compulsive_opening.sh" +THORIUM_STARTUP_SCRIPT="$CONFIG_DIR/scripts/setup_thorium_startup.sh" +LEECHBLOCK_SCRIPT="$CONFIG_DIR/scripts/digital_wellbeing/install_leechblock.sh" +REMOVE_GUEST_MODE_SCRIPT="$CONFIG_DIR/scripts/digital_wellbeing/remove_guest_mode.sh" +VBOX_HOSTS_SCRIPT="$CONFIG_DIR/scripts/digital_wellbeing/virtualbox/enforce_vbox_hosts.sh" ###################################################################### # Helpers @@ -48,24 +55,24 @@ err() { printf "${RED}[✗]${NC} %s\n" "$*"; } header() { printf "\n${CYAN}=== %s ===${NC}\n" "$*"; } run() { - if [[ $DRY_RUN -eq 1 ]]; then - echo -e "${YELLOW}DRY-RUN:${NC} $*" - return 0 - else - "$@" - fi + 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 + 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' + cat <<'EOF' Check and Enable Digital Wellbeing Services ============================================ @@ -77,11 +84,18 @@ Options: -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 + 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. Thesis work tracker - Work quota enforcement with distraction blocking + 7. Focus mode daemon - Steam/Browser mutual exclusion (user service) + 8. Compulsive blocker - Limits messaging apps to one launch per hour + 9. Thorium startup - Auto-launch Thorium with Fitatu on boot + 10. LeechBlock - Browser extension for site blocking + 11. Guest mode removal - Disable Chromium guest mode via policy + 12. VirtualBox hosts - Enforce /etc/hosts inside VMs EOF } @@ -90,25 +104,25 @@ EOF ###################################################################### 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 + 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[@]}" @@ -125,41 +139,41 @@ FIXES_APPLIED=0 # 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=("$@") + 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 != "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 "Setup script not found: $setup_script" - fi - fi - fi + 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 "Setup script not found: $setup_script" + fi + fi + fi - SERVICE_STATUS["$status_key"]=$_status + SERVICE_STATUS["$status_key"]=$_status } ###################################################################### @@ -167,443 +181,842 @@ report_and_fix() { ###################################################################### check_pacman_wrapper() { - header "Pacman Wrapper" + header "Pacman Wrapper" - local status="ok" - local issues=() + 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 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 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 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 + # 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 - # Report and fix - if [[ $status == "error" ]]; then - for issue in "${issues[@]}"; do - err "$issue" - done - ((ISSUES_FOUND++)) || true + # 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 - if [[ $DRY_RUN -eq 0 ]] && [[ -L /usr/bin/pacman ]] && [[ -f /usr/bin/pacman.orig ]] && [[ -f /usr/local/bin/pacman_wrapper ]]; then - status="ok" - fi - else - err "Installer script not found: $PACMAN_WRAPPER_INSTALL" - fi - fi - fi + 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 + if [[ $DRY_RUN -eq 0 ]] && [[ -L /usr/bin/pacman ]] && [[ -f /usr/bin/pacman.orig ]] && [[ -f /usr/local/bin/pacman_wrapper ]]; then + status="ok" + fi + else + err "Installer script not found: $PACMAN_WRAPPER_INSTALL" + fi + fi + fi - SERVICE_STATUS["pacman_wrapper"]=$status + SERVICE_STATUS["pacman_wrapper"]=$status } check_midnight_shutdown() { - header "Midnight Shutdown (Day-Specific Auto-Shutdown)" + header "Midnight Shutdown (Day-Specific Auto-Shutdown)" - local status="ok" - local issues=() + 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 + # 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 + 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 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 + # 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 + 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" + header "PC Startup Monitor" - local status="ok" - local issues=() + 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 + # 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 + 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 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 + # 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" + 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" + header "Periodic System Maintenance" - local status="ok" - local issues=() + 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 + # 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 + 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 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 + # 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 + 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 + # 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" + 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" + header "Hosts File and Guards" - local status="ok" - local issues=() + 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 /etc/hosts exists and has content + if [[ -f /etc/hosts ]]; then + local line_count + line_count=$(wc -l /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 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 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 + # 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 + 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 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 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 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 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 [[ -f /etc/pacman.d/hooks/10-unlock-etc-hosts.hook ]] && [[ -f /etc/pacman.d/hooks/90-relock-etc-hosts.hook ]]; then - msg "Pacman hooks installed" - else - issues+=("Pacman hooks not installed") - status="warning" - fi + # Check pacman hooks + if [[ -f /etc/pacman.d/hooks/10-unlock-etc-hosts.hook ]] && [[ -f /etc/pacman.d/hooks/90-relock-etc-hosts.hook ]]; then + msg "Pacman hooks installed" + else + issues+=("Pacman hooks not installed") + status="warning" + 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 + # 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 - if [[ $STATUS_ONLY -eq 0 ]]; then - # 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 "Hosts install script not found: $HOSTS_INSTALL_SCRIPT" - fi - 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="warning" + fi - # Run hosts guard setup - if ! systemctl is-enabled hosts-guard.path &> /dev/null || [[ ! -f /usr/local/sbin/enforce-hosts.sh ]]; then - note "Setting up hosts guard..." - if [[ -f $HOSTS_GUARD_SCRIPT ]]; then - run bash "$HOSTS_GUARD_SCRIPT" - ((FIXES_APPLIED++)) || true - else - err "Hosts guard script not found: $HOSTS_GUARD_SCRIPT" - fi - 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 - # Install pacman hooks if missing - if [[ ! -f /etc/pacman.d/hooks/10-unlock-etc-hosts.hook ]]; then - note "Installing pacman hooks..." - if [[ -f $HOSTS_PACMAN_HOOKS_SCRIPT ]]; then - run bash "$HOSTS_PACMAN_HOOKS_SCRIPT" - ((FIXES_APPLIED++)) || true - else - err "Pacman hooks script not found: $HOSTS_PACMAN_HOOKS_SCRIPT" - fi - fi + 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 - # Re-verify after fixes - if [[ $DRY_RUN -eq 0 ]]; then - if systemctl is-enabled hosts-guard.path &> /dev/null && - [[ -f /usr/local/sbin/enforce-hosts.sh ]] && - [[ -f /usr/local/share/locked-hosts ]] && - [[ -f /etc/pacman.d/hooks/10-unlock-etc-hosts.hook ]]; then - # Downgrade to warning if only minor issues remain (immutable attr, etc.) - status="ok" - fi - fi - fi - fi + # Run hosts install first + if [[ ! -f /etc/hosts ]] || [[ $(wc -l /dev/null || [[ ! -f /usr/local/sbin/enforce-hosts.sh ]]; then + note "Setting up hosts guard..." + if [[ -f $HOSTS_GUARD_SCRIPT ]]; then + run bash "$HOSTS_GUARD_SCRIPT" + ((FIXES_APPLIED++)) || true + else + err "Hosts guard script not found: $HOSTS_GUARD_SCRIPT" + fi + fi + + # Install pacman hooks if missing + if [[ ! -f /etc/pacman.d/hooks/10-unlock-etc-hosts.hook ]]; then + note "Installing pacman hooks..." + if [[ -f $HOSTS_PACMAN_HOOKS_SCRIPT ]]; then + run bash "$HOSTS_PACMAN_HOOKS_SCRIPT" + ((FIXES_APPLIED++)) || true + else + err "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 && + [[ -f /usr/local/sbin/enforce-hosts.sh ]] && + [[ -f /usr/local/share/locked-hosts ]] && + [[ -f /etc/pacman.d/hooks/10-unlock-etc-hosts.hook ]]; then + # Downgrade to warning if only minor issues remain (immutable attr, etc.) + status="ok" + fi + fi + fi + fi + + SERVICE_STATUS["hosts"]=$status +} + +check_thesis_tracker() { + header "Thesis Work Tracker" + + local status="ok" + local issues=() + local user="${SUDO_USER:-$USER}" + + # Check service + if systemctl is-enabled "thesis-work-tracker@${user}.service" &>/dev/null; then + msg "thesis-work-tracker@${user}.service is enabled" + else + issues+=("thesis-work-tracker@${user}.service is not enabled") + status="error" + fi + + if systemctl is-active "thesis-work-tracker@${user}.service" &>/dev/null; then + msg "thesis-work-tracker@${user}.service is active" + else + issues+=("thesis-work-tracker@${user}.service is not active") + if [[ $status != "error" ]]; then status="warning"; fi + fi + + # Check tracker script + if [[ -f /usr/local/bin/thesis_work_tracker.sh ]]; then + msg "Tracker script exists at /usr/local/bin/thesis_work_tracker.sh" + else + issues+=("thesis_work_tracker.sh not found in /usr/local/bin") + status="error" + fi + + # Check status script + if [[ -f /usr/local/bin/thesis_work_status.sh ]]; then + msg "Status script exists at /usr/local/bin/thesis_work_status.sh" + else + issues+=("thesis_work_status.sh not found in /usr/local/bin") + if [[ $status != "error" ]]; then status="warning"; fi + fi + + # Check state directory + if [[ -d /var/lib/thesis-work-tracker ]]; then + msg "State directory exists" + else + issues+=("State directory /var/lib/thesis-work-tracker missing") + status="error" + fi + + report_and_fix issues status "thesis_tracker" \ + "Setting up thesis work tracker..." \ + "$THESIS_TRACKER_SCRIPT" \ + "thesis-work-tracker@${user}.service" +} + +check_focus_mode() { + header "Focus Mode Daemon (Steam/Browser Mutual Exclusion)" + + local status="ok" + local issues=() + + # This is a user service, so check as the actual user + local user="${SUDO_USER:-$USER}" + + # Check if daemon script is installed + if [[ -f /usr/local/bin/focus-mode-daemon ]]; then + msg "Focus mode daemon installed at /usr/local/bin/focus-mode-daemon" + else + issues+=("focus-mode-daemon not found in /usr/local/bin") + status="error" + fi + + # Check user service (must run as actual user) + if sudo -u "$user" systemctl --user is-enabled focus-mode.service &>/dev/null 2>&1; then + msg "focus-mode.service is enabled (user service)" + else + issues+=("focus-mode.service is not enabled (user service)") + status="error" + fi + + if sudo -u "$user" systemctl --user is-active focus-mode.service &>/dev/null 2>&1; then + msg "focus-mode.service is active" + else + issues+=("focus-mode.service is not active") + if [[ $status != "error" ]]; then status="warning"; fi + fi + + # Report and fix - focus mode install needs to run as user + 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 focus mode daemon..." + if [[ -f $FOCUS_MODE_SCRIPT ]]; then + run sudo -u "$user" bash "$FOCUS_MODE_SCRIPT" install + ((FIXES_APPLIED++)) || true + else + err "Install script not found: $FOCUS_MODE_SCRIPT" + fi + fi + fi + + SERVICE_STATUS["focus_mode"]=$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 "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 sudo -u "$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 "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 "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 + if ! command -v VBoxManage &>/dev/null; then + note "VirtualBox not installed, skipping" + SERVICE_STATUS["vbox_hosts"]="skipped" + 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 "Script not found: $VBOX_HOSTS_SCRIPT" + fi + fi + fi + + SERVICE_STATUS["vbox_hosts"]=$status } ###################################################################### # Summary ###################################################################### print_summary() { - header "Summary" + header "Summary" - echo "" - printf "%-25s %s\n" "Service" "Status" - printf "%-25s %s\n" "-------" "------" + echo "" + printf "%-25s %s\n" "Service" "Status" + printf "%-25s %s\n" "-------" "------" - for service in pacman_wrapper midnight_shutdown startup_monitor periodic_systems hosts; do - local status="${SERVICE_STATUS[$service]:-unknown}" - local color - case "$status" in - ok) color=$GREEN ;; - warning) color=$YELLOW ;; - error) color=$RED ;; - *) color=$NC ;; - esac - printf "%-25s ${color}%s${NC}\n" "$service" "$status" - done + for service in pacman_wrapper midnight_shutdown startup_monitor periodic_systems hosts thesis_tracker focus_mode compulsive_blocker thorium_startup leechblock guest_mode_removal vbox_hosts; do + local status="${SERVICE_STATUS[$service]:-unknown}" + local color + case "$status" in + ok) color=$GREEN ;; + warning) color=$YELLOW ;; + error) color=$RED ;; + 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 + 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 + 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 + 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_midnight_shutdown - check_startup_monitor - check_periodic_systems - check_hosts + check_pacman_wrapper + check_midnight_shutdown + check_startup_monitor + check_periodic_systems + check_hosts + check_thesis_tracker + check_focus_mode + check_compulsive_blocker + check_thorium_startup + check_leechblock + check_guest_mode_removal + check_vbox_hosts - print_summary + print_summary } main diff --git a/linux_configuration/scripts/digital_wellbeing/install_leechblock.sh b/linux_configuration/scripts/digital_wellbeing/install_leechblock.sh index e097cc6..f81aa30 100755 --- a/linux_configuration/scripts/digital_wellbeing/install_leechblock.sh +++ b/linux_configuration/scripts/digital_wellbeing/install_leechblock.sh @@ -15,14 +15,14 @@ warn() { printf "\033[1;33m[WARN]\033[0m %s\n" "$*"; } err() { printf "\033[1;31m[ERR ]\033[0m %s\n" "$*"; } require_cmd() { - if ! command -v "$1" > /dev/null 2>&1; then - err "Missing dependency: $1" - MISSING=1 - fi + if ! command -v "$1" >/dev/null 2>&1; then + err "Missing dependency: $1" + MISSING=1 + fi } usage() { - cat << EOF + cat < /dev/null 2>&1; then - warn "jq not found — will fall back to a simpler tag detection method." +if ! command -v jq >/dev/null 2>&1; then + warn "jq not found — will fall back to a simpler tag detection method." fi [[ $MISSING -eq 1 ]] && { - err "Please install missing tools and re-run." - exit 1 + err "Please install missing tools and re-run." + exit 1 } REPO_OWNER="proginosko" -REPO_NAME="LeechBlockNG" +REPO_NAME_CHROME="LeechBlockNG-chrome" +# Firefox repo (for reference): LeechBlockNG + +# Use Chrome repo for Chromium-based browsers (the default target) +REPO_NAME="$REPO_NAME_CHROME" get_latest_tag() { - local tag - if command -v jq > /dev/null 2>&1; then - tag=$(curl -fsSL "https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest" | jq -r '.tag_name // empty' || true) - if [[ -n $tag && $tag != "null" ]]; then - echo "$tag" - return 0 - fi - fi - # Fallback: follow redirect for /releases/latest to extract tag - tag=$(curl -fsSLI "https://github.com/${REPO_OWNER}/${REPO_NAME}/releases/latest" | awk -F'/tag/' '/^location:/I {print $2}' | tr -d '\r\n' || true) - if [[ -n $tag ]]; then - echo "$tag" - return 0 - fi - return 1 + local repo="$1" + local tag + if command -v jq >/dev/null 2>&1; then + tag=$(curl -fsSL "https://api.github.com/repos/${REPO_OWNER}/${repo}/releases/latest" | jq -r '.tag_name // empty' || true) + if [[ -n $tag && $tag != "null" ]]; then + echo "$tag" + return 0 + fi + # Fallback: try tags endpoint + tag=$(curl -fsSL "https://api.github.com/repos/${REPO_OWNER}/${repo}/tags?per_page=1" | jq -r '.[0].name // empty' || true) + if [[ -n $tag && $tag != "null" ]]; then + echo "$tag" + return 0 + fi + fi + # Fallback: follow redirect for /releases/latest to extract tag + tag=$(curl -fsSLI "https://github.com/${REPO_OWNER}/${repo}/releases/latest" | awk -F'/tag/' '/^location:/I {print $2}' | tr -d '\r\n' || true) + if [[ -n $tag ]]; then + echo "$tag" + return 0 + fi + return 1 } if [[ -z $VERSION ]]; then - info "Resolving latest release tag from GitHub…" - if ! VERSION=$(get_latest_tag); then - err "Failed to determine latest version tag" - exit 1 - fi + info "Resolving latest release tag from GitHub…" + if ! VERSION=$(get_latest_tag "$REPO_NAME"); then + err "Failed to determine latest version tag" + exit 1 + fi fi if [[ ! $VERSION =~ ^v?[0-9]+(\.[0-9]+)*$ ]]; then - warn "Version tag '$VERSION' doesn't look like vX[.Y[.Z]] — continuing anyway." + warn "Version tag '$VERSION' doesn't look like vX[.Y[.Z]] — continuing anyway." fi VERSION=${VERSION#v} # strip leading v for folder names @@ -126,142 +137,218 @@ VERSION_DIR="$INSTALL_ROOT/$VERSION" CURRENT_LINK="$INSTALL_ROOT/current" if [[ -d $VERSION_DIR && $FORCE -ne 1 ]]; then - info "LeechBlockNG $VERSION already present at $VERSION_DIR (use --force to reinstall)." + info "LeechBlockNG $VERSION already present at $VERSION_DIR (use --force to reinstall)." else - info "Downloading LeechBlockNG $TAG source from GitHub…" - tmpdir=$(mktemp -d) - trap 'rm -rf "$tmpdir"' EXIT - ARCHIVE_URL="https://github.com/${REPO_OWNER}/${REPO_NAME}/archive/refs/tags/${TAG}.tar.gz" - ARCHIVE_FILE="$tmpdir/${REPO_NAME}-${TAG}.tar.gz" - curl -fL --retry 3 -o "$ARCHIVE_FILE" "$ARCHIVE_URL" - info "Extracting…" - mkdir -p "$tmpdir/extract" - tar -xzf "$ARCHIVE_FILE" -C "$tmpdir/extract" - # The archive usually extracts to REPO_NAME-TAG/ … - src_root=$(find "$tmpdir/extract" -maxdepth 1 -type d -name "${REPO_NAME}-*" | head -n1 || true) - [[ -z $src_root ]] && { - err "Could not locate extracted source root" - exit 1 - } + info "Downloading LeechBlockNG $TAG source from GitHub…" + tmpdir=$(mktemp -d) + trap 'rm -rf "$tmpdir"' EXIT + ARCHIVE_URL="https://github.com/${REPO_OWNER}/${REPO_NAME}/archive/refs/tags/${TAG}.tar.gz" + ARCHIVE_FILE="$tmpdir/${REPO_NAME}-${TAG}.tar.gz" + curl -fL --retry 3 -o "$ARCHIVE_FILE" "$ARCHIVE_URL" + info "Extracting…" + mkdir -p "$tmpdir/extract" + tar -xzf "$ARCHIVE_FILE" -C "$tmpdir/extract" + # The archive usually extracts to REPO_NAME-TAG/ … + src_root=$(find "$tmpdir/extract" -maxdepth 1 -type d -name "${REPO_NAME}-*" | head -n1 || true) + [[ -z $src_root ]] && { + err "Could not locate extracted source root" + exit 1 + } - # Find the extension manifest (support a couple of common layouts) - manifest_path=$(find "$src_root" -maxdepth 5 -type f -name manifest.json | head -n1 || true) - if [[ -z $manifest_path ]]; then - err "manifest.json not found in the extracted archive. The project layout may have changed." - exit 1 - fi - ext_dir=$(dirname "$manifest_path") + # Find the extension manifest (support a couple of common layouts) + manifest_path=$(find "$src_root" -maxdepth 5 -type f -name manifest.json | head -n1 || true) + if [[ -z $manifest_path ]]; then + err "manifest.json not found in the extracted archive. The project layout may have changed." + exit 1 + fi + ext_dir=$(dirname "$manifest_path") - mkdir -p "$INSTALL_ROOT" - rm -rf "$VERSION_DIR" - info "Installing to $VERSION_DIR…" - mkdir -p "$VERSION_DIR" - # Copy the extension directory as-is (avoid bringing tests or build scripts) - rsync -a --delete "$ext_dir/" "$VERSION_DIR/" 2> /dev/null || cp -a "$ext_dir/." "$VERSION_DIR/" + mkdir -p "$INSTALL_ROOT" + rm -rf "$VERSION_DIR" + info "Installing to $VERSION_DIR…" + mkdir -p "$VERSION_DIR" + # Copy the extension directory as-is (avoid bringing tests or build scripts) + rsync -a --delete "$ext_dir/" "$VERSION_DIR/" 2>/dev/null || cp -a "$ext_dir/." "$VERSION_DIR/" - ln -sfn "$VERSION_DIR" "$CURRENT_LINK" + # Download jQuery UI (not included in repo — listed in .gitignore) + # The extension's options.html expects: + # jquery-ui/jquery-ui.min.css + # jquery-ui/external/jquery/jquery.js + # jquery-ui/jquery-ui.min.js + info "Downloading jQuery UI…" + jqui_version="1.14.1" + jqui_url="https://jqueryui.com/resources/download/jquery-ui-${jqui_version}.zip" + jqui_zip="$tmpdir/jquery-ui.zip" + curl -fL --retry 3 -o "$jqui_zip" "$jqui_url" + mkdir -p "$tmpdir/jqui-extract" + unzip -q "$jqui_zip" -d "$tmpdir/jqui-extract" + jqui_src=$(find "$tmpdir/jqui-extract" -maxdepth 1 -type d -name "jquery-ui-*" | head -n1 || true) + if [[ -n $jqui_src ]]; then + mkdir -p "$VERSION_DIR/jquery-ui/external/jquery" + cp "$jqui_src/jquery-ui.min.css" "$VERSION_DIR/jquery-ui/" 2>/dev/null || true + cp "$jqui_src/jquery-ui.min.js" "$VERSION_DIR/jquery-ui/" 2>/dev/null || true + cp "$jqui_src/external/jquery/jquery.js" "$VERSION_DIR/jquery-ui/external/jquery/" 2>/dev/null || true + info "✓ jQuery UI ${jqui_version} installed into extension" + else + warn "Could not extract jQuery UI — options page may not work correctly" + fi + + ln -sfn "$VERSION_DIR" "$CURRENT_LINK" fi EXT_PATH="$CURRENT_LINK" # stable path used by wrappers +# ── Inject default blocking configuration ───────────────────────────── +# Copy leechblock_defaults.js alongside the extension and patch +# background.js to import it and seed storage on first run. +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DEFAULTS_SRC="$SCRIPT_DIR/leechblock_defaults.js" + +if [[ -f $DEFAULTS_SRC ]]; then + cp "$DEFAULTS_SRC" "$VERSION_DIR/defaults.js" + info "Copied default blocking configuration into extension" + + BG_JS="$VERSION_DIR/background.js" + if [[ -f $BG_JS ]]; then + # 1) Add importScripts("defaults.js") right after importScripts("common.js") + if ! grep -q 'importScripts("defaults.js")' "$BG_JS"; then + sed -i 's|importScripts("common.js");|importScripts("common.js");\nimportScripts("defaults.js");|' "$BG_JS" + info "Patched background.js to import defaults.js" + fi + + # 2) Inject first-run seeding logic after cleanTimeData(gOptions) + if ! grep -q 'LEECHBLOCK_DEFAULTS' "$BG_JS"; then + sed -i '/cleanTimeData(gOptions);/a\ +\ +\t\t// ── Seed default blocking rules on first run ──\ +\t\tif (typeof LEECHBLOCK_DEFAULTS !== "undefined") {\ +\t\t\tlet hasAnySites = false;\ +\t\t\tfor (let s = 1; s <= +gOptions["numSets"]; s++) {\ +\t\t\t\tif (gOptions["sites" + s]) { hasAnySites = true; break; }\ +\t\t\t}\ +\t\t\tif (!hasAnySites) {\ +\t\t\t\tfor (let key in LEECHBLOCK_DEFAULTS) {\ +\t\t\t\t\tgOptions[key] = LEECHBLOCK_DEFAULTS[key];\ +\t\t\t\t}\ +\t\t\t\tcleanOptions(gOptions);\ +\t\t\t\tcleanTimeData(gOptions);\ +\t\t\t\tgNumSets = +gOptions["numSets"];\ +\t\t\t\tgStorage.set(gOptions).catch(\ +\t\t\t\t\tfunction (e) { warn("Cannot seed defaults: " + e); }\ +\t\t\t\t);\ +\t\t\t\tlog("Seeded default blocking configuration");\ +\t\t\t}\ +\t\t}' "$BG_JS" + info "Patched background.js with first-run seeding logic" + fi + fi +else + warn "leechblock_defaults.js not found at $DEFAULTS_SRC — skipping default config" +fi + # Detect browsers declare -A BROWSERS BROWSERS=( - [chromium]="Chromium" - [google - chrome - stable]="Google Chrome" - [google - chrome]="Google Chrome" - [brave - browser]="Brave" - [vivaldi - stable]="Vivaldi" - [vivaldi]="Vivaldi" - [opera]="Opera" - [thorium - browser]="Thorium" + [chromium]="Chromium" + [google - chrome - stable]="Google Chrome" + [google - chrome]="Google Chrome" + [brave - browser]="Brave" + [vivaldi - stable]="Vivaldi" + [vivaldi]="Vivaldi" + [opera]="Opera" + [thorium - browser]="Thorium" ) declare -A FIREFOXES FIREFOXES=( - [firefox]="Firefox" - [firefox - developer - edition]="Firefox Developer Edition" - [librewolf]="LibreWolf" + [firefox]="Firefox" + [firefox - developer - edition]="Firefox Developer Edition" + [librewolf]="LibreWolf" ) found_any=0 -wrap_bin_dir="$HOME/.local/bin" -mkdir -p "$wrap_bin_dir" # Create a user desktop entry user_apps_dir="${XDG_DATA_HOME:-$HOME/.local/share}/applications" mkdir -p "$user_apps_dir" -create_wrapper_and_desktop() { - local bin="$1" - shift - local pretty="$1" - shift - local wrapper="$wrap_bin_dir/${bin}-with-leechblock" +# Replace the system browser launcher in-place so every launch includes LeechBlock. +# The original script/binary is backed up as .orig. +# Requires sudo for system paths (/usr/bin). +replace_browser_in_place() { + local bin="$1" + shift + local pretty="$1" + shift - local real_bin - real_bin=$(command -v "$bin" || true) - [[ -z $real_bin ]] && return + local real_bin + real_bin=$(command -v "$bin" || true) + [[ -z $real_bin ]] && return - cat > "$wrapper" << WRAP + # Resolve to absolute path (handles symlinks etc.) + real_bin=$(readlink -f "$real_bin") + + local orig_backup="${real_bin}.orig" + + # If already wrapped, skip (idempotent) + if grep -q '__LEECHBLOCK_WRAPPER__' "$real_bin" 2>/dev/null; then + info "$pretty ($bin) already wrapped — skipping" + found_any=1 + return + fi + + # Kill running instances so the new wrapper takes effect + info "Killing running $pretty instances…" + pkill -f "$real_bin" 2>/dev/null || true + pkill -f "$(basename "$real_bin")" 2>/dev/null || true + sleep 1 + + # Back up original + if [[ ! -f $orig_backup ]]; then + info "Backing up $real_bin → $orig_backup" + sudo cp -a "$real_bin" "$orig_backup" + else + info "Backup already exists: $orig_backup" + fi + + # Write replacement wrapper + info "Replacing $real_bin with LeechBlock wrapper…" + sudo tee "$real_bin" >/dev/null < /dev/null | head -n1 || true) - if [[ -n $sys_desktop ]]; then - existing_icon=$(awk -F= '/^Icon=/{print $2; exit}' "$sys_desktop" || true) - existing_name=$(awk -F= '/^Name=/{print $2; exit}' "$sys_desktop" || true) - categories=$(awk -F= '/^Categories=/{print $2; exit}' "$sys_desktop" || true) - fi - [[ -z $existing_icon ]] && existing_icon="$bin" - [[ -z $existing_name ]] && existing_name="$pretty" - [[ -z $categories ]] && categories="Network;WebBrowser;" - - local desktop_file="$user_apps_dir/${bin}-with-leechblock.desktop" - cat > "$desktop_file" << DESK -[Desktop Entry] -Name=${existing_name} (LeechBlock) -Exec=${wrapper} %U -Terminal=false -Type=Application -Icon=${existing_icon} -Categories=${categories} -StartupNotify=true -DESK - - info "Created wrapper: $wrapper" - info "Created launcher: $desktop_file" - found_any=1 + info "✓ $pretty now always launches with LeechBlock" + found_any=1 } info "Detecting installed browsers…" for bin in "${!BROWSERS[@]}"; do - if command -v "$bin" > /dev/null 2>&1; then - create_wrapper_and_desktop "$bin" "${BROWSERS[$bin]}" - fi + if command -v "$bin" >/dev/null 2>&1; then + replace_browser_in_place "$bin" "${BROWSERS[$bin]}" + fi done ff_found=0 for bin in "${!FIREFOXES[@]}"; do - if command -v "$bin" > /dev/null 2>&1; then - ff_found=1 - fi + if command -v "$bin" >/dev/null 2>&1; then + ff_found=1 + fi done echo if [[ $found_any -eq 1 ]]; then - info "Chromium-based integration complete. Launch the browser via its '(LeechBlock)' launcher." - warn "Chromium will mark it as a developer extension; this is expected for unpacked installs." + info "Chromium-based integration complete. Launch the browser via its '(LeechBlock)' launcher." + warn "Chromium will mark it as a developer extension; this is expected for unpacked installs." fi if [[ $ff_found -eq 1 ]]; then - echo - warn "Detected Firefox-based browser(s). Permanent install from GitHub source isn't possible on stable builds due to required signing." - cat << FF + echo + warn "Detected Firefox-based browser(s). Permanent install from GitHub source isn't possible on stable builds due to required signing." + cat < /dev/null 2>&1; then - POLICY_DIRS+=("/etc/firefox/policies" "/usr/lib/firefox/distribution") - fi - if command -v firefox-developer-edition > /dev/null 2>&1; then - POLICY_DIRS+=("/etc/firefox-developer-edition/policies" "/usr/lib/firefox-developer-edition/distribution") - fi - if command -v librewolf > /dev/null 2>&1; then - POLICY_DIRS+=("/etc/librewolf/policies" "/usr/lib/librewolf/distribution") - fi - # Generic mozilla path as fallback - POLICY_DIRS+=("/usr/lib/mozilla/distribution") + # Determine policy directories for detected Firefox-like browsers + declare -a POLICY_DIRS + POLICY_DIRS=() + if command -v firefox >/dev/null 2>&1; then + POLICY_DIRS+=("/etc/firefox/policies" "/usr/lib/firefox/distribution") + fi + if command -v firefox-developer-edition >/dev/null 2>&1; then + POLICY_DIRS+=("/etc/firefox-developer-edition/policies" "/usr/lib/firefox-developer-edition/distribution") + fi + if command -v librewolf >/dev/null 2>&1; then + POLICY_DIRS+=("/etc/librewolf/policies" "/usr/lib/librewolf/distribution") + fi + # Generic mozilla path as fallback + POLICY_DIRS+=("/usr/lib/mozilla/distribution") - updated_any=0 - for pol_target in "${POLICY_DIRS[@]}"; do - tmp_pol=$(mktemp) - existing="${pol_target}/policies.json" - if sudo test -f "$existing"; then - info "Merging into existing policies.json at $existing" - sudo cp "$existing" "$tmp_pol" - if command -v jq > /dev/null 2>&1; then - merged=$(jq --arg id "$ADDON_ID" --arg url "$ADDON_AMO_URL" ' + updated_any=0 + for pol_target in "${POLICY_DIRS[@]}"; do + tmp_pol=$(mktemp) + existing="${pol_target}/policies.json" + if sudo test -f "$existing"; then + info "Merging into existing policies.json at $existing" + sudo cp "$existing" "$tmp_pol" + if command -v jq >/dev/null 2>&1; then + merged=$(jq --arg id "$ADDON_ID" --arg url "$ADDON_AMO_URL" ' .policies |= (. // {}) | .policies.ExtensionSettings |= (. // {}) | .policies.ExtensionSettings."*" |= (. // {"installation_mode":"allowed"}) | @@ -322,17 +409,17 @@ if [[ $AUTO_FIREFOX -eq 1 && $ff_found -eq 1 ]]; then .policies.ExtensionSettings[$id].installation_mode = "force_installed" | .policies.ExtensionSettings[$id].install_url = $url ' "$tmp_pol") || merged="" - if [[ -n $merged ]]; then - printf '%s\n' "$merged" > "$tmp_pol" - else - warn "jq merge failed; skipping $pol_target" - rm -f "$tmp_pol" - continue - fi - else - warn "jq not available; creating minimal policies.json (existing file will be backed up)." - sudo cp "$existing" "${existing}.bak.$(date +%s)" - cat > "$tmp_pol" << JSON + if [[ -n $merged ]]; then + printf '%s\n' "$merged" >"$tmp_pol" + else + warn "jq merge failed; skipping $pol_target" + rm -f "$tmp_pol" + continue + fi + else + warn "jq not available; creating minimal policies.json (existing file will be backed up)." + sudo cp "$existing" "${existing}.bak.$(date +%s)" + cat >"$tmp_pol" < "$tmp_pol" << JSON + fi + else + info "Creating new policies.json at $pol_target" + cat >"$tmp_pol" < "$tmp" - install -m 0644 "$tmp" "$file" - rm -f "$tmp" + mkdir -p "$target_dir" + # Write atomically + local tmp + tmp=$(mktemp) + printf '%s +' "$POLICY_JSON" >"$tmp" + install -m 0644 "$tmp" "$file" + rm -f "$tmp" } remove_policy() { - local target_dir="$1" - shift - local file="$target_dir/$POLICY_FILENAME" + local target_dir="$1" + shift + local file="$target_dir/$POLICY_FILENAME" - if [[ -f $file ]]; then - echo "[remove] $file" - rm -f -- "$file" - else - echo "[skip] $file (not present)" - fi + if [[ -f $file ]]; then + echo "[remove] $file" + rm -f -- "$file" + else + echo "[skip] $file (not present)" + fi } changed_any=false for key in "${!INSTALLED_KEYS[@]}"; do - # If we somehow lack candidate dirs for a key, skip gracefully - if [[ -z ${CANDIDATE_DIRS[$key]:-} ]]; then - echo "[warn] No known policy directories for '$key'; skipping." - continue - fi + # If we somehow lack candidate dirs for a key, skip gracefully + if [[ -z ${CANDIDATE_DIRS[$key]:-} ]]; then + echo "[warn] No known policy directories for '$key'; skipping." + continue + fi - target_dir=$(choose_target_dir "$key") + target_dir=$(choose_target_dir "$key") - if [[ $UNDO == true ]]; then - remove_policy "$target_dir" - else - apply_policy "$target_dir" - fi + if [[ $UNDO == true ]]; then + remove_policy "$target_dir" + else + apply_policy "$target_dir" + fi - changed_any=true + changed_any=true done if [[ $changed_any == false ]]; then - echo "[info] Nothing to do." + echo "[info] Nothing to do." fi if [[ $UNDO == true ]]; then - echo "[done] Guest mode policy files removed where present. You may need to restart the browsers." + echo "[done] Guest mode policy files removed where present. You may need to restart the browsers." else - echo "[done] Guest mode disabled via managed policies. Please fully restart affected browsers." - echo " If the Guest option still appears, it should be disabled/greyed out." + echo "[done] Guest mode disabled via managed policies. Please fully restart affected browsers." + echo " If the Guest option still appears, it should be disabled/greyed out." fi