diff --git a/i3-configuration/i3blocks/shutdown_countdown.sh b/i3-configuration/i3blocks/shutdown_countdown.sh index ec686b1..ed57d57 100755 --- a/i3-configuration/i3blocks/shutdown_countdown.sh +++ b/i3-configuration/i3blocks/shutdown_countdown.sh @@ -1,57 +1,53 @@ #!/bin/bash # Shutdown countdown status script for i3blocks # Shows time remaining until the next shutdown window -# Dynamically reads shutdown times from the systemd check script +# Reads shutdown times from shared config file written by setup_midnight_shutdown.sh -SHUTDOWN_CHECK_SCRIPT="/usr/local/bin/day-specific-shutdown-check.sh" +SHUTDOWN_CONFIG="/etc/shutdown-schedule.conf" -# Function to extract shutdown hour from the check script -# Parses lines like "if [[ $current_time_minutes -ge 1260 ]]" where 1260 = 21*60 -get_shutdown_hours() { - if [[ ! -f "$SHUTDOWN_CHECK_SCRIPT" ]]; then - # Fallback defaults if script not found - echo "21 22" - return - fi - - # Extract the minute thresholds from the script (e.g., 1260 for 21:00, 1320 for 22:00) - # The script checks: if [[ $current_time_minutes -ge XXXX ]] - # Get unique values - first is Mon-Wed (1260=21:00), second is Thu-Sun (1320=22:00) - local thresholds - thresholds=$(grep -oP 'current_time_minutes -ge \K\d{4}' "$SHUTDOWN_CHECK_SCRIPT" 2>/dev/null | sort -u) - - if [[ -z "$thresholds" ]]; then - echo "21 22" - return - fi - - local mon_wed_minutes thu_sun_minutes - mon_wed_minutes=$(echo "$thresholds" | head -1) # 1260 (smaller = earlier = Mon-Wed) - thu_sun_minutes=$(echo "$thresholds" | tail -1) # 1320 (larger = later = Thu-Sun) - - # Convert minutes to hours - local mon_wed_hour=$((mon_wed_minutes / 60)) - local thu_sun_hour=$((thu_sun_minutes / 60)) - - echo "$mon_wed_hour $thu_sun_hour" +# Function to show error state in i3blocks and exit +show_error() { + local message="$1" + echo "⏻ $message" + echo "⏻" + echo "#FF79C6" # Pink/magenta for config errors + exit 0 } +# Validate and load config file +if [[ ! -f "$SHUTDOWN_CONFIG" ]]; then + show_error "NO CONFIG" +fi + +# Source the config file to get MON_WED_HOUR and THU_SUN_HOUR +# shellcheck source=/dev/null +if ! source "$SHUTDOWN_CONFIG" 2>/dev/null; then + show_error "BAD CONFIG" +fi + +# Validate that required variables are set +if [[ -z "${MON_WED_HOUR:-}" ]] || [[ -z "${THU_SUN_HOUR:-}" ]]; then + show_error "MISSING VARS" +fi + +# Validate that values are numbers +if ! [[ "$MON_WED_HOUR" =~ ^[0-9]+$ ]] || ! [[ "$THU_SUN_HOUR" =~ ^[0-9]+$ ]]; then + show_error "INVALID HOURS" +fi + # Get current time info current_hour=$(date +%H) current_minute=$(date +%M) current_time_minutes=$((10#$current_hour * 60 + 10#$current_minute)) day_of_week=$(date +%u) # 1=Monday, 7=Sunday -# Get shutdown hours dynamically -read -r mon_wed_hour thu_sun_hour <<<"$(get_shutdown_hours)" - # Determine shutdown hour based on day of week if [[ $day_of_week -ge 1 ]] && [[ $day_of_week -le 3 ]]; then # Monday-Wednesday - shutdown_hour=$mon_wed_hour + shutdown_hour=$MON_WED_HOUR else # Thursday-Sunday - shutdown_hour=$thu_sun_hour + shutdown_hour=$THU_SUN_HOUR fi shutdown_time_minutes=$((shutdown_hour * 60)) diff --git a/scripts/digital_wellbeing/setup_midnight_shutdown.sh b/scripts/digital_wellbeing/setup_midnight_shutdown.sh index 6d3da1d..0ba80aa 100755 --- a/scripts/digital_wellbeing/setup_midnight_shutdown.sh +++ b/scripts/digital_wellbeing/setup_midnight_shutdown.sh @@ -11,6 +11,113 @@ SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" # shellcheck source=../lib/common.sh source "$SCRIPT_DIR/../lib/common.sh" +# Schedule constants (single source of truth for this script) +# These values are written to /etc/shutdown-schedule.conf during setup +SCHEDULE_MON_WED_HOUR=21 +SCHEDULE_THU_SUN_HOUR=22 +SCHEDULE_MORNING_END_HOUR=5 + +# ============================================================================ +# SCHEDULE PROTECTION MECHANISM +# ============================================================================ +# This prevents easy "cheating" by modifying the script values and re-running. +# If a canonical config already exists, the script compares against it and +# BLOCKS installation if the new values would make the schedule MORE LENIENT +# (i.e., later shutdown hours or earlier morning end). +# To legitimately change the schedule, use: sudo /usr/local/sbin/unlock-shutdown-schedule +# ============================================================================ + +CANONICAL_CONFIG="/usr/local/share/locked-shutdown-schedule.conf" + +# Check if trying to make schedule more lenient (later shutdown / earlier morning end) +check_schedule_protection() { + # Skip check if no canonical config exists (first install) + if [[ ! -f "$CANONICAL_CONFIG" ]]; then + return 0 + fi + + # Load canonical values + local canonical_mon_wed canonical_thu_sun canonical_morning_end + # shellcheck source=/dev/null + source "$CANONICAL_CONFIG" 2>/dev/null || return 0 + canonical_mon_wed="${MON_WED_HOUR:-}" + canonical_thu_sun="${THU_SUN_HOUR:-}" + canonical_morning_end="${MORNING_END_HOUR:-}" + + # If canonical values are empty, skip check + if [[ -z "$canonical_mon_wed" ]] || [[ -z "$canonical_thu_sun" ]] || [[ -z "$canonical_morning_end" ]]; then + return 0 + fi + + local violations=() + + # Check if Mon-Wed hour is being made LATER (more lenient) + if [[ $SCHEDULE_MON_WED_HOUR -gt $canonical_mon_wed ]]; then + violations+=("Mon-Wed shutdown: ${canonical_mon_wed}:00 → ${SCHEDULE_MON_WED_HOUR}:00 (later)") + fi + + # Check if Thu-Sun hour is being made LATER (more lenient) + if [[ $SCHEDULE_THU_SUN_HOUR -gt $canonical_thu_sun ]]; then + violations+=("Thu-Sun shutdown: ${canonical_thu_sun}:00 → ${SCHEDULE_THU_SUN_HOUR}:00 (later)") + fi + + # Check if morning end is being made EARLIER (more lenient - shorter shutdown window) + if [[ $SCHEDULE_MORNING_END_HOUR -lt $canonical_morning_end ]]; then + violations+=("Morning end: 0${canonical_morning_end}:00 → 0${SCHEDULE_MORNING_END_HOUR}:00 (earlier)") + fi + + if [[ ${#violations[@]} -gt 0 ]]; then + echo "" + echo "╔══════════════════════════════════════════════════════════════════╗" + echo "║ ❌ SCHEDULE MODIFICATION BLOCKED - CHEATING DETECTED! ❌ ║" + echo "╚══════════════════════════════════════════════════════════════════╝" + echo "" + echo "You modified the script to make the shutdown schedule MORE LENIENT:" + echo "" + for v in "${violations[@]}"; do + echo " • $v" + done + echo "" + echo "Current protected schedule:" + echo " Monday-Wednesday: ${canonical_mon_wed}:00 - 0${canonical_morning_end}:00" + echo " Thursday-Sunday: ${canonical_thu_sun}:00 - 0${canonical_morning_end}:00" + echo "" + echo "Nice try! But this is exactly the kind of late-night bargaining" + echo "that this protection is designed to prevent. 😉" + echo "" + echo "If you REALLY need to change the schedule, use the proper unlock:" + echo " sudo /usr/local/sbin/unlock-shutdown-schedule" + echo "" + echo "This requires waiting through a psychological delay to give you" + echo "time to reconsider whether you actually need more screen time." + echo "" + exit 1 + fi + + # Making schedule STRICTER is always allowed + local stricter=() + if [[ $SCHEDULE_MON_WED_HOUR -lt $canonical_mon_wed ]]; then + stricter+=("Mon-Wed: ${canonical_mon_wed}:00 → ${SCHEDULE_MON_WED_HOUR}:00 (earlier)") + fi + if [[ $SCHEDULE_THU_SUN_HOUR -lt $canonical_thu_sun ]]; then + stricter+=("Thu-Sun: ${canonical_thu_sun}:00 → ${SCHEDULE_THU_SUN_HOUR}:00 (earlier)") + fi + if [[ $SCHEDULE_MORNING_END_HOUR -gt $canonical_morning_end ]]; then + stricter+=("Morning end: 0${canonical_morning_end}:00 → 0${SCHEDULE_MORNING_END_HOUR}:00 (later)") + fi + + if [[ ${#stricter[@]} -gt 0 ]]; then + echo "" + echo "ℹ️ Schedule is being made STRICTER (allowed without unlock):" + for s in "${stricter[@]}"; do + echo " • $s" + done + echo "" + fi + + return 0 +} + # Function to show usage show_usage() { echo "Day-Specific Auto-Shutdown Setup for Arch Linux" @@ -22,8 +129,8 @@ show_usage() { echo " status - Show current status" echo "" echo "Shutdown Schedule:" - echo " Monday-Wednesday: 21:00-05:00" - echo " Thursday-Sunday: 22:00-05:00" + echo " Monday-Wednesday: ${SCHEDULE_MON_WED_HOUR}:00-0${SCHEDULE_MORNING_END_HOUR}:00" + echo " Thursday-Sunday: ${SCHEDULE_THU_SUN_HOUR}:00-0${SCHEDULE_MORNING_END_HOUR}:00" echo "" echo "NOTE: There is no 'disable' option. This is intentional." echo " The shutdown timer is protected by a monitor service." @@ -114,20 +221,328 @@ show_current_status() { echo "✗ Monitor is not enabled" fi + echo "" + + # Check config file protection status + echo "Config File Protection Status:" + local config_file="/etc/shutdown-schedule.conf" + local canonical_file="/usr/local/share/locked-shutdown-schedule.conf" + + if [[ -f "$config_file" ]]; then + echo "✓ Config file exists" + # Check immutable attribute + if lsattr "$config_file" 2>/dev/null | grep -q '^....i'; then + echo "✓ Config file is immutable (chattr +i)" + else + echo "✗ Config file is NOT immutable" + fi + else + echo "✗ Config file missing" + fi + + if [[ -f "$canonical_file" ]]; then + echo "✓ Canonical copy exists" + else + echo "✗ Canonical copy missing" + fi + + if systemctl is-enabled shutdown-schedule-guard.path &>/dev/null; then + echo "✓ Config path watcher is enabled" + if systemctl is-active shutdown-schedule-guard.path &>/dev/null; then + echo "✓ Config path watcher is active" + else + echo "✗ Config path watcher is not active" + fi + else + echo "✗ Config path watcher is not enabled" + fi + + if [[ -f "/usr/local/sbin/unlock-shutdown-schedule" ]]; then + echo "✓ Unlock script exists" + else + echo "✗ Unlock script missing" + fi + echo "" echo "Shutdown Schedule:" - echo " Monday-Wednesday: 21:00-05:00" - echo " Thursday-Sunday: 22:00-05:00" + echo " Monday-Wednesday: ${SCHEDULE_MON_WED_HOUR}:00-0${SCHEDULE_MORNING_END_HOUR}:00" + echo " Thursday-Sunday: ${SCHEDULE_THU_SUN_HOUR}:00-0${SCHEDULE_MORNING_END_HOUR}:00" echo "" echo "NOTE: The shutdown timer is protected by a monitor service." echo " If you try to disable the timer, it will be automatically re-enabled." echo "" + echo "NOTE: The config file is protected by:" + echo " - Immutable attribute (chattr +i)" + echo " - Canonical copy that auto-restores on modification" + echo " - Path watcher service" + echo " To modify: sudo /usr/local/sbin/unlock-shutdown-schedule" + echo "" +} + +# Function to create shutdown schedule config file (shared with i3blocks countdown) +# Also creates a canonical (protected) copy and sets immutable attribute +create_shutdown_config() { + echo "" + echo "1. Creating Shutdown Schedule Config..." + echo "=======================================" + + local config_file="/etc/shutdown-schedule.conf" + local canonical_file="/usr/local/share/locked-shutdown-schedule.conf" + + # Remove immutable attribute if it exists (to allow update) + chattr -i "$config_file" 2>/dev/null || true + chattr -i "$canonical_file" 2>/dev/null || true + + cat >"$config_file" <"$enforce_script" <<'EOF' +#!/bin/bash +# Enforce canonical /etc/shutdown-schedule.conf contents +# This script restores the config from canonical copy if tampered + +set -euo pipefail + +CANONICAL_SOURCE="/usr/local/share/locked-shutdown-schedule.conf" +TARGET="/etc/shutdown-schedule.conf" +LOG_FILE="/var/log/shutdown-schedule-guard.log" + +log() { + printf '%s - %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" | tee -a "$LOG_FILE" >&2 +} + +if [[ ! -f $CANONICAL_SOURCE ]]; then + log "Canonical config not found at $CANONICAL_SOURCE; aborting enforcement" + exit 0 +fi + +# Remove immutable attr to check/restore +chattr -i -a "$TARGET" 2>/dev/null || true + +if ! cmp -s "$CANONICAL_SOURCE" "$TARGET"; then + log "CONFIG TAMPERING DETECTED – restoring $TARGET from canonical copy" + cp "$CANONICAL_SOURCE" "$TARGET" + chmod 644 "$TARGET" + log "Config restored successfully" +else + log "No drift detected (contents identical)" +fi + +# Re-apply immutable attribute +chattr +i "$TARGET" || log "Failed to set immutable attribute" + +log "Enforcement complete" +EOF + + chmod +x "$enforce_script" + echo "✓ Created enforcement script: $enforce_script" + + # Create unlock script with psychological delay + cat >"$unlock_script" <<'EOF' +#!/bin/bash +# Unlock shutdown schedule config for editing with psychological friction +# This script: +# 1. Makes you wait (psychological friction to discourage casual changes) +# 2. Temporarily removes protection +# 3. Opens the config in an editor +# 4. Re-applies protection after editing + +set -euo pipefail + +DELAY_SECONDS=45 +CONFIG_FILE="/etc/shutdown-schedule.conf" +CANONICAL_FILE="/usr/local/share/locked-shutdown-schedule.conf" +LOG_FILE="/var/log/shutdown-schedule-guard.log" +EDITOR="${EDITOR:-nano}" + +log() { + printf '%s - %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" | tee -a "$LOG_FILE" >&2 +} + +# Must be root +if [[ $EUID -ne 0 ]]; then + echo "This script must be run as root (sudo)" + exit 1 +fi + +# Log the unlock attempt +log "=== UNLOCK ATTEMPT by $(logname 2>/dev/null || echo 'unknown') from TTY $(tty 2>/dev/null || echo 'unknown') ===" + +echo "" +echo "╔══════════════════════════════════════════════════════════════════╗" +echo "║ SHUTDOWN SCHEDULE CONFIG UNLOCK - PSYCHOLOGICAL FRICTION ║" +echo "╚══════════════════════════════════════════════════════════════════╝" +echo "" +echo "You are about to modify the shutdown schedule configuration." +echo "This file controls when your PC automatically shuts down for" +echo "digital wellbeing purposes." +echo "" +echo "Current schedule:" +if [[ -f "$CONFIG_FILE" ]]; then + chattr -i "$CONFIG_FILE" 2>/dev/null || true + source "$CONFIG_FILE" 2>/dev/null || true + chattr +i "$CONFIG_FILE" 2>/dev/null || true + echo " Monday-Wednesday: ${MON_WED_HOUR:-??}:00 - 0${MORNING_END_HOUR:-?}:00" + echo " Thursday-Sunday: ${THU_SUN_HOUR:-??}:00 - 0${MORNING_END_HOUR:-?}:00" +fi +echo "" +echo "Are you making this change for a good reason, or are you just" +echo "trying to stay up later? Remember why you set these limits." +echo "" +echo "To proceed, you must wait $DELAY_SECONDS seconds..." +echo "" + +# Countdown with opportunity to cancel +for ((i=DELAY_SECONDS; i>0; i--)); do + printf "\r ⏳ Waiting: %2d seconds remaining... (Ctrl+C to cancel)" "$i" + sleep 1 +done +echo "" +echo "" + +log "User waited through delay, proceeding with unlock" + +# Stop the path watcher temporarily +systemctl stop shutdown-schedule-guard.path 2>/dev/null || true + +# Remove immutable attributes +chattr -i -a "$CONFIG_FILE" 2>/dev/null || true +chattr -i -a "$CANONICAL_FILE" 2>/dev/null || true + +echo "Config file unlocked. Opening editor..." +echo "After saving, protection will be re-applied automatically." +echo "" + +# Open editor +$EDITOR "$CONFIG_FILE" + +echo "" +echo "Re-applying protection..." + +# Copy to canonical +cp "$CONFIG_FILE" "$CANONICAL_FILE" +chmod 644 "$CONFIG_FILE" +chmod 644 "$CANONICAL_FILE" + +# Re-apply immutable +chattr +i "$CONFIG_FILE" || echo "Warning: Could not set immutable attribute" +chattr +i "$CANONICAL_FILE" || echo "Warning: Could not set immutable attribute" + +# Restart path watcher +systemctl start shutdown-schedule-guard.path 2>/dev/null || true + +log "Config updated and re-locked by user" + +echo "" +echo "✓ Config file updated and re-protected" +echo "✓ Canonical copy updated" +echo "✓ Path watcher re-enabled" +echo "" +echo "New schedule (will take effect on next timer check):" +source "$CONFIG_FILE" 2>/dev/null || true +echo " Monday-Wednesday: ${MON_WED_HOUR:-??}:00 - 0${MORNING_END_HOUR:-?}:00" +echo " Thursday-Sunday: ${THU_SUN_HOUR:-??}:00 - 0${MORNING_END_HOUR:-?}:00" +echo "" +EOF + + chmod +x "$unlock_script" + echo "✓ Created unlock script: $unlock_script" + + # Create path watcher unit + cat >"$guard_path" <<'EOF' +[Unit] +Description=Watch /etc/shutdown-schedule.conf and trigger enforcement + +[Path] +PathChanged=/etc/shutdown-schedule.conf +Unit=shutdown-schedule-guard.service + +[Install] +WantedBy=multi-user.target +EOF + + echo "✓ Created path watcher: $guard_path" + + # Create enforcement service + cat >"$guard_service" <<'EOF' +[Unit] +Description=Enforce canonical /etc/shutdown-schedule.conf contents +After=local-fs.target + +[Service] +Type=oneshot +ExecStart=/usr/local/sbin/enforce-shutdown-schedule.sh +Nice=10 +IOSchedulingClass=idle + +[Install] +WantedBy=multi-user.target +EOF + + echo "✓ Created guard service: $guard_service" + + # Reload and enable + systemctl daemon-reload + systemctl enable --now shutdown-schedule-guard.path + echo "✓ Enabled and started shutdown-schedule-guard.path" + + # Run initial enforcement + "$enforce_script" || echo "⚠ Warning: Initial enforcement returned non-zero" + echo "✓ Ran initial enforcement" } # Function to create the shutdown service create_shutdown_service() { echo "" - echo "1. Creating Systemd Shutdown Service..." + echo "3. Creating Systemd Shutdown Service..." echo "======================================" local service_file="/etc/systemd/system/day-specific-shutdown.service" @@ -152,7 +567,7 @@ EOF # Function to create the shutdown timer create_shutdown_timer() { echo "" - echo "2. Creating Systemd Shutdown Timer..." + echo "4. Creating Systemd Shutdown Timer..." echo "===================================" local timer_file="/etc/systemd/system/day-specific-shutdown.timer" @@ -163,10 +578,6 @@ Description=Timer for automatic PC shutdown with day-specific windows Requires=day-specific-shutdown.service [Timer] -OnCalendar=*-*-* 21:00:00 -OnCalendar=*-*-* 21:30:00 -OnCalendar=*-*-* 22:00:00 -OnCalendar=*-*-* 22:30:00 OnCalendar=*-*-* 23:00:00 OnCalendar=*-*-* 23:30:00 OnCalendar=*-*-* 00:00:00 @@ -195,7 +606,7 @@ EOF # Function to create management script create_management_script() { echo "" - echo "3. Creating Management Script..." + echo "5. Creating Management Script..." echo "==============================" local script_file="/usr/local/bin/day-specific-shutdown-manager.sh" @@ -207,6 +618,27 @@ create_management_script() { TIMER_NAME="day-specific-shutdown.timer" SERVICE_NAME="day-specific-shutdown.service" +CONFIG_FILE="/etc/shutdown-schedule.conf" + +# Load config for schedule display +load_config() { + if [[ -f "$CONFIG_FILE" ]]; then + # shellcheck source=/dev/null + source "$CONFIG_FILE" + else + echo "Warning: Config file $CONFIG_FILE not found" + MON_WED_HOUR="??" + THU_SUN_HOUR="??" + MORNING_END_HOUR="??" + fi +} + +print_schedule() { + load_config + echo "Shutdown Schedule:" + echo " Monday-Wednesday: ${MON_WED_HOUR}:00-0${MORNING_END_HOUR}:00" + echo " Thursday-Sunday: ${THU_SUN_HOUR}:00-0${MORNING_END_HOUR}:00" +} show_status() { echo "Day-Specific Auto-Shutdown Status" @@ -224,9 +656,7 @@ show_status() { fi echo "" - echo "Shutdown Schedule:" - echo " Monday-Wednesday: 21:00-05:00" - echo " Thursday-Sunday: 22:00-05:00" + print_schedule echo "" echo "Next scheduled checks:" @@ -254,9 +684,7 @@ case "$1" in echo " status - Show current status and next shutdown checks" echo " logs - Show recent shutdown logs" echo "" - echo "Shutdown Schedule:" - echo " Monday-Wednesday: 21:00-05:00" - echo " Thursday-Sunday: 22:00-05:00" + print_schedule echo "" show_status ;; @@ -270,7 +698,7 @@ EOF # Function to create smart shutdown check script create_shutdown_check_script() { echo "" - echo "4. Creating Smart Shutdown Check Script..." + echo "6. Creating Smart Shutdown Check Script..." echo "========================================" local check_script="/usr/local/bin/day-specific-shutdown-check.sh" @@ -278,9 +706,23 @@ create_shutdown_check_script() { cat >"$check_script" <<'EOF' #!/bin/bash # Smart day-specific shutdown check script -# Different shutdown windows based on day of week: -# Monday-Wednesday: 21:00-05:00 -# Thursday-Sunday: 22:00-05:00 +# Reads shutdown windows from /etc/shutdown-schedule.conf + +CONFIG_FILE="/etc/shutdown-schedule.conf" + +# Load config +if [[ ! -f "$CONFIG_FILE" ]]; then + logger -t day-specific-shutdown "ERROR: Config file $CONFIG_FILE not found" + exit 1 +fi +# shellcheck source=/dev/null +source "$CONFIG_FILE" + +# Validate config +if [[ -z "${MON_WED_HOUR:-}" ]] || [[ -z "${THU_SUN_HOUR:-}" ]] || [[ -z "${MORNING_END_HOUR:-}" ]]; then + logger -t day-specific-shutdown "ERROR: Config file missing required variables" + exit 1 +fi # Get current time and day current_hour=$(date +%H) @@ -289,9 +731,10 @@ current_time_minutes=$((10#$current_hour * 60 + 10#$current_minute)) day_of_week=$(date +%u) # 1=Monday, 7=Sunday day_name=$(date +%A) -# Convert time to minutes for easier comparison -# 21:00 = 1260 minutes, 22:00 = 1320 minutes, 05:00 = 300 minutes -# 00:00 = 0 minutes, 05:00 = 300 minutes +# Calculate minute thresholds from config +mon_wed_minutes=$((MON_WED_HOUR * 60)) +thu_sun_minutes=$((THU_SUN_HOUR * 60)) +morning_end_minutes=$((MORNING_END_HOUR * 60)) logger -t day-specific-shutdown "Checking shutdown conditions at $(date) - Day: $day_name ($day_of_week), Time: $current_hour:$current_minute" @@ -299,36 +742,34 @@ logger -t day-specific-shutdown "Checking shutdown conditions at $(date) - Day: should_shutdown=false if [[ $day_of_week -ge 1 ]] && [[ $day_of_week -le 3 ]]; then - # Monday (1), Tuesday (2), Wednesday (3): shutdown window 21:00-05:00 - logger -t day-specific-shutdown "Today is $day_name - checking 21:00-05:00 window" + # Monday (1), Tuesday (2), Wednesday (3) + shutdown_start=$mon_wed_minutes + logger -t day-specific-shutdown "Today is $day_name - checking ${MON_WED_HOUR}:00-0${MORNING_END_HOUR}:00 window" - # Check if time is between 21:00 (1260 minutes) and 23:59 (1439 minutes) - # OR between 00:00 (0 minutes) and 05:00 (300 minutes) - if [[ $current_time_minutes -ge 1260 ]] || [[ $current_time_minutes -le 300 ]]; then + if [[ $current_time_minutes -ge $shutdown_start ]] || [[ $current_time_minutes -le $morning_end_minutes ]]; then should_shutdown=true - if [[ $current_time_minutes -ge 1260 ]]; then - logger -t day-specific-shutdown "Time $current_hour:$current_minute is within evening shutdown window (21:00-23:59)" + if [[ $current_time_minutes -ge $shutdown_start ]]; then + logger -t day-specific-shutdown "Time $current_hour:$current_minute is within evening shutdown window (${MON_WED_HOUR}:00-23:59)" else - logger -t day-specific-shutdown "Time $current_hour:$current_minute is within morning shutdown window (00:00-05:00)" + logger -t day-specific-shutdown "Time $current_hour:$current_minute is within morning shutdown window (00:00-0${MORNING_END_HOUR}:00)" fi else - logger -t day-specific-shutdown "Time $current_hour:$current_minute is outside shutdown window (21:00-05:00)" + logger -t day-specific-shutdown "Time $current_hour:$current_minute is outside shutdown window (${MON_WED_HOUR}:00-0${MORNING_END_HOUR}:00)" fi else - # Thursday (4), Friday (5), Saturday (6), Sunday (7): shutdown window 22:00-05:00 - logger -t day-specific-shutdown "Today is $day_name - checking 22:00-05:00 window" + # Thursday (4), Friday (5), Saturday (6), Sunday (7) + shutdown_start=$thu_sun_minutes + logger -t day-specific-shutdown "Today is $day_name - checking ${THU_SUN_HOUR}:00-0${MORNING_END_HOUR}:00 window" - # Check if time is between 22:00 (1320 minutes) and 23:59 (1439 minutes) - # OR between 00:00 (0 minutes) and 05:00 (300 minutes) - if [[ $current_time_minutes -ge 1320 ]] || [[ $current_time_minutes -le 300 ]]; then + if [[ $current_time_minutes -ge $shutdown_start ]] || [[ $current_time_minutes -le $morning_end_minutes ]]; then should_shutdown=true - if [[ $current_time_minutes -ge 1320 ]]; then - logger -t day-specific-shutdown "Time $current_hour:$current_minute is within evening shutdown window (22:00-23:59)" + if [[ $current_time_minutes -ge $shutdown_start ]]; then + logger -t day-specific-shutdown "Time $current_hour:$current_minute is within evening shutdown window (${THU_SUN_HOUR}:00-23:59)" else - logger -t day-specific-shutdown "Time $current_hour:$current_minute is within morning shutdown window (00:00-05:00)" + logger -t day-specific-shutdown "Time $current_hour:$current_minute is within morning shutdown window (00:00-0${MORNING_END_HOUR}:00)" fi else - logger -t day-specific-shutdown "Time $current_hour:$current_minute is outside shutdown window (22:00-05:00)" + logger -t day-specific-shutdown "Time $current_hour:$current_minute is outside shutdown window (${THU_SUN_HOUR}:00-0${MORNING_END_HOUR}:00)" fi fi @@ -368,7 +809,7 @@ enable_timer() { # Function to install the monitor service install_monitor_service() { echo "" - echo "6. Installing Shutdown Timer Monitor Service..." + echo "7. Installing Shutdown Timer Monitor Service..." echo "==============================================" local monitor_script="/usr/local/bin/shutdown-timer-monitor.sh" @@ -533,7 +974,7 @@ EOF # Function to test the setup test_setup() { echo "" - echo "7. Testing Setup..." + echo "8. Testing Setup..." echo "==================" echo "Service files:" @@ -597,6 +1038,46 @@ test_setup() { echo "✗ Watchdog timer is not active" fi + echo "" + echo "Config file protection status:" + local config_file="/etc/shutdown-schedule.conf" + local canonical_file="/usr/local/share/locked-shutdown-schedule.conf" + + if [[ -f "$config_file" ]]; then + echo "✓ Config file exists" + if lsattr "$config_file" 2>/dev/null | grep -q '^....i'; then + echo "✓ Config file is immutable" + else + echo "✗ Config file is NOT immutable" + fi + else + echo "✗ Config file missing" + fi + + if [[ -f "$canonical_file" ]]; then + echo "✓ Canonical copy exists" + else + echo "✗ Canonical copy missing" + fi + + if systemctl is-enabled shutdown-schedule-guard.path &>/dev/null; then + echo "✓ Config guard path watcher is enabled" + else + echo "✗ Config guard path watcher is not enabled" + fi + + if systemctl is-active shutdown-schedule-guard.path &>/dev/null; then + echo "✓ Config guard path watcher is active" + else + echo "✗ Config guard path watcher is not active" + fi + + if [[ -f "/usr/local/sbin/unlock-shutdown-schedule" ]]; then + echo "✓ Unlock script exists" + else + echo "✗ Unlock script missing" + fi + echo "" echo "Next scheduled checks:" systemctl list-timers day-specific-shutdown.timer --no-pager 2>/dev/null | head -5 | grep day-specific-shutdown || echo "Timer information not available" @@ -604,9 +1085,23 @@ test_setup() { # Display the shutdown schedule (used in multiple places) print_shutdown_schedule() { + # Convert 24h to 12h format for display + local mon_wed_12h thu_sun_12h morning_12h + if [[ $SCHEDULE_MON_WED_HOUR -gt 12 ]]; then + mon_wed_12h="$((SCHEDULE_MON_WED_HOUR - 12)):00 PM" + else + mon_wed_12h="${SCHEDULE_MON_WED_HOUR}:00 AM" + fi + if [[ $SCHEDULE_THU_SUN_HOUR -gt 12 ]]; then + thu_sun_12h="$((SCHEDULE_THU_SUN_HOUR - 12)):00 PM" + else + thu_sun_12h="${SCHEDULE_THU_SUN_HOUR}:00 AM" + fi + morning_12h="${SCHEDULE_MORNING_END_HOUR}:00 AM" + echo "Shutdown Schedule:" - echo " Monday-Wednesday: 21:00-05:00 (9:00 PM to 5:00 AM)" - echo " Thursday-Sunday: 22:00-05:00 (10:00 PM to 5:00 AM)" + echo " Monday-Wednesday: ${SCHEDULE_MON_WED_HOUR}:00-0${SCHEDULE_MORNING_END_HOUR}:00 (${mon_wed_12h} to ${morning_12h})" + echo " Thursday-Sunday: ${SCHEDULE_THU_SUN_HOUR}:00-0${SCHEDULE_MORNING_END_HOUR}:00 (${thu_sun_12h} to ${morning_12h})" } # Function to show final instructions @@ -623,6 +1118,7 @@ show_instructions() { echo "✓ Timer enabled and started" echo "✓ Monitor service installed (protects timer from being disabled)" echo "✓ Watchdog timer installed (restarts monitor if stopped)" + echo "✓ Config file protected (immutable + path watcher + canonical copy)" echo "" print_shutdown_schedule echo "" @@ -630,12 +1126,19 @@ show_instructions() { echo " sudo day-specific-shutdown-manager.sh status - Check status" echo " sudo day-specific-shutdown-manager.sh logs - View shutdown logs" echo "" + echo "To modify shutdown hours (with psychological friction):" + echo " sudo /usr/local/sbin/unlock-shutdown-schedule" + echo "" echo "How it works:" echo "• Timer checks every 30 minutes during potential shutdown windows" echo "• Smart logic determines shutdown eligibility based on day and time" echo "• Monitor service watches the timer and re-enables it if disabled" echo "• Watchdog timer restarts the monitor every 60 seconds if stopped" echo "• Monitor has RefuseManualStop=true to prevent easy stopping" + echo "• Config file is protected by:" + echo " - Immutable attribute (chattr +i)" + echo " - Canonical copy at /usr/local/share/locked-shutdown-schedule.conf" + echo " - Path watcher that auto-restores if you modify the file" echo "• There is NO disable option - this is intentional for digital wellbeing" echo "" echo "WARNING: This will automatically shutdown your PC during designated hours." @@ -683,9 +1186,18 @@ enable_midnight_shutdown() { echo "Target user: $ACTUAL_USER" echo "User home: $USER_HOME" + # Check if trying to cheat by making schedule more lenient + check_schedule_protection + # Confirm setup confirm_setup + # Create config file (shared with i3blocks countdown script) + create_shutdown_config + + # Create config guard (path watcher, enforcement, unlock script) + create_config_guard + # Create systemd files create_shutdown_service create_shutdown_timer diff --git a/scripts/fixes/fix_thorium.sh b/scripts/fixes/fix_thorium.sh new file mode 100755 index 0000000..41183e8 --- /dev/null +++ b/scripts/fixes/fix_thorium.sh @@ -0,0 +1,377 @@ +#!/usr/bin/env bash + +# Fix Thorium Browser crashes and startup issues +# +# Common causes addressed: +# - Corrupted Local State file (most common) +# - Stale singleton lock files +# - Corrupted GPU/shader cache +# - Profile database corruption +# +# Usage: +# ./fix_thorium.sh # Auto-fix common issues +# ./fix_thorium.sh --aggressive # Also clear more caches (may lose some settings) +# ./fix_thorium.sh --test # Test if Thorium starts after fix + +set -euo pipefail + +SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" +# shellcheck source=../lib/common.sh +source "$SCRIPT_DIR/../lib/common.sh" + +# Configuration +THORIUM_CONFIG_DIR="${HOME}/.config/thorium" +BACKUP_SUFFIX=".bak.$(date +%Y%m%d_%H%M%S)" +AGGRESSIVE=false +TEST_AFTER=false + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +usage() { + cat </dev/null; then + log_error "thorium-browser not found in PATH" + echo -e "${YELLOW}Install with: yay -S thorium-browser-bin${NC}" + exit 1 + fi + log_info "Found Thorium: $(thorium-browser --version 2>/dev/null | head -1)" +} + +# Check if config directory exists +check_config_exists() { + if [[ ! -d "$THORIUM_CONFIG_DIR" ]]; then + log_warn "Thorium config directory not found: $THORIUM_CONFIG_DIR" + log_info "This may be a fresh install - try running thorium-browser directly" + exit 0 + fi +} + +# Kill any running Thorium processes +kill_thorium() { + local count + count=$(pgrep -c thorium 2>/dev/null || true) + count=${count:-0} + + if [[ $count -gt 0 ]]; then + log_info "Stopping $count running Thorium process(es)..." + if [[ $DRY_RUN == true ]]; then + echo " [dry-run] Would kill thorium processes" + else + pkill -9 thorium 2>/dev/null || true + sleep 1 + fi + fi +} + +# Backup a file/directory if it exists +backup_if_exists() { + local path="$1" + local name + name=$(basename "$path") + + if [[ -e "$path" ]]; then + local backup_path="${path}${BACKUP_SUFFIX}" + if [[ $DRY_RUN == true ]]; then + echo " [dry-run] Would backup: $name" + else + mv "$path" "$backup_path" + log_ok "Backed up: $name -> $(basename "$backup_path")" + fi + return 0 + fi + return 1 +} + +# Remove file/directory if it exists +remove_if_exists() { + local path="$1" + local name + name=$(basename "$path") + + if [[ -e "$path" ]]; then + if [[ $DRY_RUN == true ]]; then + echo " [dry-run] Would remove: $name" + else + rm -rf "$path" + log_ok "Removed: $name" + fi + return 0 + fi + return 1 +} + +# Fix 1: Handle corrupted Local State file (most common crash cause) +fix_local_state() { + log_info "Checking Local State file..." + local local_state="$THORIUM_CONFIG_DIR/Local State" + + if [[ -f "$local_state" ]]; then + # Check if it's valid JSON + if ! python3 -c "import json; json.load(open('$local_state'))" 2>/dev/null; then + log_warn "Local State file appears corrupted" + backup_if_exists "$local_state" + else + # Even if valid JSON, back it up as it can still cause crashes + log_info "Local State exists - backing up (common crash source)" + backup_if_exists "$local_state" + fi + else + log_info "No Local State file found (OK for fresh install)" + fi +} + +# Fix 2: Clear singleton lock files +fix_singleton_locks() { + log_info "Clearing singleton lock files..." + local locks=( + "$THORIUM_CONFIG_DIR/SingletonLock" + "$THORIUM_CONFIG_DIR/SingletonSocket" + "$THORIUM_CONFIG_DIR/SingletonCookie" + ) + + local cleared=0 + for lock in "${locks[@]}"; do + if remove_if_exists "$lock"; then + ((cleared++)) || true + fi + done + + if [[ $cleared -eq 0 ]]; then + log_info "No stale lock files found" + fi +} + +# Fix 3: Clear GPU cache +fix_gpu_cache() { + log_info "Clearing GPU cache..." + local gpu_paths=( + "$THORIUM_CONFIG_DIR/GPUCache" + "$THORIUM_CONFIG_DIR/Default/GPUCache" + "$THORIUM_CONFIG_DIR/ShaderCache" + "$THORIUM_CONFIG_DIR/Default/ShaderCache" + ) + + local cleared=0 + for cache in "${gpu_paths[@]}"; do + if remove_if_exists "$cache"; then + ((cleared++)) || true + fi + done + + if [[ $cleared -eq 0 ]]; then + log_info "No GPU cache to clear" + fi +} + +# Fix 4: Clear crash reports (can accumulate and cause issues) +fix_crash_reports() { + log_info "Clearing old crash reports..." + local crash_dir="$THORIUM_CONFIG_DIR/Crash Reports" + + if [[ -d "$crash_dir" ]]; then + local crash_count + crash_count=$(find "$crash_dir" -type f 2>/dev/null | wc -l) + if [[ $crash_count -gt 0 ]]; then + if [[ $DRY_RUN == true ]]; then + echo " [dry-run] Would clear $crash_count crash report(s)" + else + rm -rf "$crash_dir" + log_ok "Cleared $crash_count crash report(s)" + fi + fi + fi +} + +# Fix 5: Aggressive cleaning (optional) +fix_aggressive() { + if [[ $AGGRESSIVE != true ]]; then + return + fi + + log_warn "Applying aggressive fixes (may lose some site data)..." + + local aggressive_paths=( + "$THORIUM_CONFIG_DIR/Default/Service Worker" + "$THORIUM_CONFIG_DIR/Default/Cache" + "$THORIUM_CONFIG_DIR/Default/Code Cache" + "$THORIUM_CONFIG_DIR/Default/IndexedDB" + "$THORIUM_CONFIG_DIR/BrowserMetrics" + "$THORIUM_CONFIG_DIR/component_crx_cache" + ) + + for path in "${aggressive_paths[@]}"; do + remove_if_exists "$path" + done + + # Backup potentially corrupted databases + local db_files=( + "$THORIUM_CONFIG_DIR/Default/Web Data" + "$THORIUM_CONFIG_DIR/Default/History" + ) + + for db in "${db_files[@]}"; do + if [[ -f "$db" ]]; then + log_info "Checking database: $(basename "$db")" + # Simple corruption check - if sqlite3 can't open it, back it up + if command -v sqlite3 &>/dev/null; then + if ! sqlite3 "$db" "PRAGMA integrity_check;" &>/dev/null; then + log_warn "Database may be corrupted: $(basename "$db")" + backup_if_exists "$db" + fi + fi + fi + done +} + +# Test if Thorium starts successfully +test_thorium() { + if [[ $TEST_AFTER != true ]]; then + return + fi + + log_info "Testing Thorium startup..." + + if [[ $DRY_RUN == true ]]; then + echo " [dry-run] Would test thorium-browser startup" + return + fi + + # Start Thorium in background + thorium-browser &>/dev/null & + local pid=$! + + # Wait a few seconds and check if it's still running + sleep 4 + + if kill -0 "$pid" 2>/dev/null; then + log_ok "Thorium started successfully! (PID: $pid)" + echo -e "${GREEN}Fix successful!${NC} Thorium is now running." + + # Offer to keep it running or kill it + read -r -p "Keep browser running? [Y/n] " response + case "$response" in + [nN]*) + kill "$pid" 2>/dev/null || true + log_info "Browser closed" + ;; + *) + log_info "Browser left running" + ;; + esac + else + log_error "Thorium still crashing after fixes" + echo -e "${RED}Standard fixes did not resolve the issue.${NC}" + echo "" + echo "Try these additional steps:" + echo " 1. Run with --aggressive flag for deeper cleaning" + echo " 2. Test with fresh profile: thorium-browser --user-data-dir=/tmp/thorium-test" + echo " 3. Reinstall: yay -S thorium-browser-bin" + echo " 4. Check NVIDIA drivers: nvidia-smi" + exit 1 + fi +} + +# Main execution +main() { + echo "========================================" + echo " Thorium Browser Fix Script" + echo "========================================" + echo "" + + if [[ $DRY_RUN == true ]]; then + echo -e "${YELLOW}[DRY RUN MODE - no changes will be made]${NC}" + echo "" + fi + + check_thorium_installed + check_config_exists + + echo "" + log_info "Applying fixes to: $THORIUM_CONFIG_DIR" + echo "" + + kill_thorium + fix_local_state + fix_singleton_locks + fix_gpu_cache + fix_crash_reports + fix_aggressive + + echo "" + echo "========================================" + log_ok "Fixes applied!" + echo "========================================" + + if [[ $DRY_RUN != true ]]; then + echo "" + echo "Backups created with suffix: $BACKUP_SUFFIX" + echo "To restore: mv ~/.config/thorium/Local\\ State${BACKUP_SUFFIX} ~/.config/thorium/Local\\ State" + fi + + test_thorium + + if [[ $TEST_AFTER != true ]]; then + echo "" + echo "Run 'thorium-browser' to test, or use: $(basename "$0") --test" + fi +} + +main "$@"