mirror of
https://github.com/kuhyx/scripts.git
synced 2026-07-04 15:03:09 +02:00
feat: more restrictive midnight shutdown
This commit is contained in:
parent
29f6fa61dc
commit
4ebd6c52be
@ -1,57 +1,53 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Shutdown countdown status script for i3blocks
|
# Shutdown countdown status script for i3blocks
|
||||||
# Shows time remaining until the next shutdown window
|
# 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
|
# Function to show error state in i3blocks and exit
|
||||||
# Parses lines like "if [[ $current_time_minutes -ge 1260 ]]" where 1260 = 21*60
|
show_error() {
|
||||||
get_shutdown_hours() {
|
local message="$1"
|
||||||
if [[ ! -f "$SHUTDOWN_CHECK_SCRIPT" ]]; then
|
echo "⏻ $message"
|
||||||
# Fallback defaults if script not found
|
echo "⏻"
|
||||||
echo "21 22"
|
echo "#FF79C6" # Pink/magenta for config errors
|
||||||
return
|
exit 0
|
||||||
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"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 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
|
# Get current time info
|
||||||
current_hour=$(date +%H)
|
current_hour=$(date +%H)
|
||||||
current_minute=$(date +%M)
|
current_minute=$(date +%M)
|
||||||
current_time_minutes=$((10#$current_hour * 60 + 10#$current_minute))
|
current_time_minutes=$((10#$current_hour * 60 + 10#$current_minute))
|
||||||
day_of_week=$(date +%u) # 1=Monday, 7=Sunday
|
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
|
# Determine shutdown hour based on day of week
|
||||||
if [[ $day_of_week -ge 1 ]] && [[ $day_of_week -le 3 ]]; then
|
if [[ $day_of_week -ge 1 ]] && [[ $day_of_week -le 3 ]]; then
|
||||||
# Monday-Wednesday
|
# Monday-Wednesday
|
||||||
shutdown_hour=$mon_wed_hour
|
shutdown_hour=$MON_WED_HOUR
|
||||||
else
|
else
|
||||||
# Thursday-Sunday
|
# Thursday-Sunday
|
||||||
shutdown_hour=$thu_sun_hour
|
shutdown_hour=$THU_SUN_HOUR
|
||||||
fi
|
fi
|
||||||
|
|
||||||
shutdown_time_minutes=$((shutdown_hour * 60))
|
shutdown_time_minutes=$((shutdown_hour * 60))
|
||||||
|
|||||||
@ -11,6 +11,113 @@ SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
|||||||
# shellcheck source=../lib/common.sh
|
# shellcheck source=../lib/common.sh
|
||||||
source "$SCRIPT_DIR/../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
|
# Function to show usage
|
||||||
show_usage() {
|
show_usage() {
|
||||||
echo "Day-Specific Auto-Shutdown Setup for Arch Linux"
|
echo "Day-Specific Auto-Shutdown Setup for Arch Linux"
|
||||||
@ -22,8 +129,8 @@ show_usage() {
|
|||||||
echo " status - Show current status"
|
echo " status - Show current status"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Shutdown Schedule:"
|
echo "Shutdown Schedule:"
|
||||||
echo " Monday-Wednesday: 21:00-05:00"
|
echo " Monday-Wednesday: ${SCHEDULE_MON_WED_HOUR}:00-0${SCHEDULE_MORNING_END_HOUR}:00"
|
||||||
echo " Thursday-Sunday: 22:00-05:00"
|
echo " Thursday-Sunday: ${SCHEDULE_THU_SUN_HOUR}:00-0${SCHEDULE_MORNING_END_HOUR}:00"
|
||||||
echo ""
|
echo ""
|
||||||
echo "NOTE: There is no 'disable' option. This is intentional."
|
echo "NOTE: There is no 'disable' option. This is intentional."
|
||||||
echo " The shutdown timer is protected by a monitor service."
|
echo " The shutdown timer is protected by a monitor service."
|
||||||
@ -114,20 +221,328 @@ show_current_status() {
|
|||||||
echo "✗ Monitor is not enabled"
|
echo "✗ Monitor is not enabled"
|
||||||
fi
|
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 ""
|
||||||
echo "Shutdown Schedule:"
|
echo "Shutdown Schedule:"
|
||||||
echo " Monday-Wednesday: 21:00-05:00"
|
echo " Monday-Wednesday: ${SCHEDULE_MON_WED_HOUR}:00-0${SCHEDULE_MORNING_END_HOUR}:00"
|
||||||
echo " Thursday-Sunday: 22:00-05:00"
|
echo " Thursday-Sunday: ${SCHEDULE_THU_SUN_HOUR}:00-0${SCHEDULE_MORNING_END_HOUR}:00"
|
||||||
echo ""
|
echo ""
|
||||||
echo "NOTE: The shutdown timer is protected by a monitor service."
|
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 " If you try to disable the timer, it will be automatically re-enabled."
|
||||||
echo ""
|
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" <<EOF
|
||||||
|
# Shutdown schedule configuration
|
||||||
|
# This file is managed by setup_midnight_shutdown.sh
|
||||||
|
# Used by: day-specific-shutdown-check.sh, shutdown_countdown.sh (i3blocks)
|
||||||
|
#
|
||||||
|
# WARNING: This file is protected by:
|
||||||
|
# 1. Immutable attribute (chattr +i)
|
||||||
|
# 2. Canonical copy at /usr/local/share/locked-shutdown-schedule.conf
|
||||||
|
# 3. Path watcher service that auto-restores if modified
|
||||||
|
#
|
||||||
|
# To modify this file, you need to:
|
||||||
|
# 1. Run: sudo /usr/local/sbin/unlock-shutdown-schedule
|
||||||
|
# 2. Wait through the psychological delay
|
||||||
|
# 3. Edit the file during the brief unlock window
|
||||||
|
# 4. The file will be re-locked automatically
|
||||||
|
|
||||||
|
# Shutdown hour for Monday-Wednesday (24-hour format)
|
||||||
|
MON_WED_HOUR=${SCHEDULE_MON_WED_HOUR}
|
||||||
|
|
||||||
|
# Shutdown hour for Thursday-Sunday (24-hour format)
|
||||||
|
THU_SUN_HOUR=${SCHEDULE_THU_SUN_HOUR}
|
||||||
|
|
||||||
|
# Morning end hour (shutdown window ends at this hour)
|
||||||
|
MORNING_END_HOUR=${SCHEDULE_MORNING_END_HOUR}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod 644 "$config_file"
|
||||||
|
echo "✓ Created shutdown schedule config: $config_file"
|
||||||
|
|
||||||
|
# Create canonical (protected) copy
|
||||||
|
install -m 644 -D "$config_file" "$canonical_file"
|
||||||
|
echo "✓ Created canonical copy: $canonical_file"
|
||||||
|
|
||||||
|
# Set immutable attribute on both files
|
||||||
|
chattr +i "$config_file" || echo "⚠ Warning: Could not set immutable attribute on $config_file"
|
||||||
|
chattr +i "$canonical_file" || echo "⚠ Warning: Could not set immutable attribute on $canonical_file"
|
||||||
|
echo "✓ Set immutable attribute (chattr +i) on config files"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to create config guard (path watcher + enforcement + unlock script)
|
||||||
|
create_config_guard() {
|
||||||
|
echo ""
|
||||||
|
echo "2. Creating Config Guard (Path Watcher + Enforcement)..."
|
||||||
|
echo "========================================================"
|
||||||
|
|
||||||
|
local enforce_script="/usr/local/sbin/enforce-shutdown-schedule.sh"
|
||||||
|
local unlock_script="/usr/local/sbin/unlock-shutdown-schedule"
|
||||||
|
local guard_service="/etc/systemd/system/shutdown-schedule-guard.service"
|
||||||
|
local guard_path="/etc/systemd/system/shutdown-schedule-guard.path"
|
||||||
|
|
||||||
|
# Create enforcement script
|
||||||
|
cat >"$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
|
# Function to create the shutdown service
|
||||||
create_shutdown_service() {
|
create_shutdown_service() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "1. Creating Systemd Shutdown Service..."
|
echo "3. Creating Systemd Shutdown Service..."
|
||||||
echo "======================================"
|
echo "======================================"
|
||||||
|
|
||||||
local service_file="/etc/systemd/system/day-specific-shutdown.service"
|
local service_file="/etc/systemd/system/day-specific-shutdown.service"
|
||||||
@ -152,7 +567,7 @@ EOF
|
|||||||
# Function to create the shutdown timer
|
# Function to create the shutdown timer
|
||||||
create_shutdown_timer() {
|
create_shutdown_timer() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "2. Creating Systemd Shutdown Timer..."
|
echo "4. Creating Systemd Shutdown Timer..."
|
||||||
echo "==================================="
|
echo "==================================="
|
||||||
|
|
||||||
local timer_file="/etc/systemd/system/day-specific-shutdown.timer"
|
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
|
Requires=day-specific-shutdown.service
|
||||||
|
|
||||||
[Timer]
|
[Timer]
|
||||||
OnCalendar=*-*-* 21:00:00
|
|
||||||
OnCalendar=*-*-* 21:30:00
|
|
||||||
OnCalendar=*-*-* 22:00:00
|
|
||||||
OnCalendar=*-*-* 22:30:00
|
|
||||||
OnCalendar=*-*-* 23:00:00
|
OnCalendar=*-*-* 23:00:00
|
||||||
OnCalendar=*-*-* 23:30:00
|
OnCalendar=*-*-* 23:30:00
|
||||||
OnCalendar=*-*-* 00:00:00
|
OnCalendar=*-*-* 00:00:00
|
||||||
@ -195,7 +606,7 @@ EOF
|
|||||||
# Function to create management script
|
# Function to create management script
|
||||||
create_management_script() {
|
create_management_script() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "3. Creating Management Script..."
|
echo "5. Creating Management Script..."
|
||||||
echo "=============================="
|
echo "=============================="
|
||||||
|
|
||||||
local script_file="/usr/local/bin/day-specific-shutdown-manager.sh"
|
local script_file="/usr/local/bin/day-specific-shutdown-manager.sh"
|
||||||
@ -207,6 +618,27 @@ create_management_script() {
|
|||||||
|
|
||||||
TIMER_NAME="day-specific-shutdown.timer"
|
TIMER_NAME="day-specific-shutdown.timer"
|
||||||
SERVICE_NAME="day-specific-shutdown.service"
|
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() {
|
show_status() {
|
||||||
echo "Day-Specific Auto-Shutdown Status"
|
echo "Day-Specific Auto-Shutdown Status"
|
||||||
@ -224,9 +656,7 @@ show_status() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Shutdown Schedule:"
|
print_schedule
|
||||||
echo " Monday-Wednesday: 21:00-05:00"
|
|
||||||
echo " Thursday-Sunday: 22:00-05:00"
|
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Next scheduled checks:"
|
echo "Next scheduled checks:"
|
||||||
@ -254,9 +684,7 @@ case "$1" in
|
|||||||
echo " status - Show current status and next shutdown checks"
|
echo " status - Show current status and next shutdown checks"
|
||||||
echo " logs - Show recent shutdown logs"
|
echo " logs - Show recent shutdown logs"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Shutdown Schedule:"
|
print_schedule
|
||||||
echo " Monday-Wednesday: 21:00-05:00"
|
|
||||||
echo " Thursday-Sunday: 22:00-05:00"
|
|
||||||
echo ""
|
echo ""
|
||||||
show_status
|
show_status
|
||||||
;;
|
;;
|
||||||
@ -270,7 +698,7 @@ EOF
|
|||||||
# Function to create smart shutdown check script
|
# Function to create smart shutdown check script
|
||||||
create_shutdown_check_script() {
|
create_shutdown_check_script() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "4. Creating Smart Shutdown Check Script..."
|
echo "6. Creating Smart Shutdown Check Script..."
|
||||||
echo "========================================"
|
echo "========================================"
|
||||||
|
|
||||||
local check_script="/usr/local/bin/day-specific-shutdown-check.sh"
|
local check_script="/usr/local/bin/day-specific-shutdown-check.sh"
|
||||||
@ -278,9 +706,23 @@ create_shutdown_check_script() {
|
|||||||
cat >"$check_script" <<'EOF'
|
cat >"$check_script" <<'EOF'
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Smart day-specific shutdown check script
|
# Smart day-specific shutdown check script
|
||||||
# Different shutdown windows based on day of week:
|
# Reads shutdown windows from /etc/shutdown-schedule.conf
|
||||||
# Monday-Wednesday: 21:00-05:00
|
|
||||||
# Thursday-Sunday: 22:00-05:00
|
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
|
# Get current time and day
|
||||||
current_hour=$(date +%H)
|
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_of_week=$(date +%u) # 1=Monday, 7=Sunday
|
||||||
day_name=$(date +%A)
|
day_name=$(date +%A)
|
||||||
|
|
||||||
# Convert time to minutes for easier comparison
|
# Calculate minute thresholds from config
|
||||||
# 21:00 = 1260 minutes, 22:00 = 1320 minutes, 05:00 = 300 minutes
|
mon_wed_minutes=$((MON_WED_HOUR * 60))
|
||||||
# 00:00 = 0 minutes, 05:00 = 300 minutes
|
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"
|
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
|
should_shutdown=false
|
||||||
|
|
||||||
if [[ $day_of_week -ge 1 ]] && [[ $day_of_week -le 3 ]]; then
|
if [[ $day_of_week -ge 1 ]] && [[ $day_of_week -le 3 ]]; then
|
||||||
# Monday (1), Tuesday (2), Wednesday (3): shutdown window 21:00-05:00
|
# Monday (1), Tuesday (2), Wednesday (3)
|
||||||
logger -t day-specific-shutdown "Today is $day_name - checking 21:00-05:00 window"
|
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)
|
if [[ $current_time_minutes -ge $shutdown_start ]] || [[ $current_time_minutes -le $morning_end_minutes ]]; then
|
||||||
# OR between 00:00 (0 minutes) and 05:00 (300 minutes)
|
|
||||||
if [[ $current_time_minutes -ge 1260 ]] || [[ $current_time_minutes -le 300 ]]; then
|
|
||||||
should_shutdown=true
|
should_shutdown=true
|
||||||
if [[ $current_time_minutes -ge 1260 ]]; then
|
if [[ $current_time_minutes -ge $shutdown_start ]]; then
|
||||||
logger -t day-specific-shutdown "Time $current_hour:$current_minute is within evening shutdown window (21:00-23:59)"
|
logger -t day-specific-shutdown "Time $current_hour:$current_minute is within evening shutdown window (${MON_WED_HOUR}:00-23:59)"
|
||||||
else
|
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
|
fi
|
||||||
else
|
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
|
fi
|
||||||
else
|
else
|
||||||
# Thursday (4), Friday (5), Saturday (6), Sunday (7): shutdown window 22:00-05:00
|
# Thursday (4), Friday (5), Saturday (6), Sunday (7)
|
||||||
logger -t day-specific-shutdown "Today is $day_name - checking 22:00-05:00 window"
|
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)
|
if [[ $current_time_minutes -ge $shutdown_start ]] || [[ $current_time_minutes -le $morning_end_minutes ]]; then
|
||||||
# OR between 00:00 (0 minutes) and 05:00 (300 minutes)
|
|
||||||
if [[ $current_time_minutes -ge 1320 ]] || [[ $current_time_minutes -le 300 ]]; then
|
|
||||||
should_shutdown=true
|
should_shutdown=true
|
||||||
if [[ $current_time_minutes -ge 1320 ]]; then
|
if [[ $current_time_minutes -ge $shutdown_start ]]; then
|
||||||
logger -t day-specific-shutdown "Time $current_hour:$current_minute is within evening shutdown window (22:00-23:59)"
|
logger -t day-specific-shutdown "Time $current_hour:$current_minute is within evening shutdown window (${THU_SUN_HOUR}:00-23:59)"
|
||||||
else
|
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
|
fi
|
||||||
else
|
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
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -368,7 +809,7 @@ enable_timer() {
|
|||||||
# Function to install the monitor service
|
# Function to install the monitor service
|
||||||
install_monitor_service() {
|
install_monitor_service() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "6. Installing Shutdown Timer Monitor Service..."
|
echo "7. Installing Shutdown Timer Monitor Service..."
|
||||||
echo "=============================================="
|
echo "=============================================="
|
||||||
|
|
||||||
local monitor_script="/usr/local/bin/shutdown-timer-monitor.sh"
|
local monitor_script="/usr/local/bin/shutdown-timer-monitor.sh"
|
||||||
@ -533,7 +974,7 @@ EOF
|
|||||||
# Function to test the setup
|
# Function to test the setup
|
||||||
test_setup() {
|
test_setup() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "7. Testing Setup..."
|
echo "8. Testing Setup..."
|
||||||
echo "=================="
|
echo "=================="
|
||||||
|
|
||||||
echo "Service files:"
|
echo "Service files:"
|
||||||
@ -597,6 +1038,46 @@ test_setup() {
|
|||||||
echo "✗ Watchdog timer is not active"
|
echo "✗ Watchdog timer is not active"
|
||||||
fi
|
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 ""
|
||||||
echo "Next scheduled checks:"
|
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"
|
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)
|
# Display the shutdown schedule (used in multiple places)
|
||||||
print_shutdown_schedule() {
|
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 "Shutdown Schedule:"
|
||||||
echo " Monday-Wednesday: 21:00-05:00 (9: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: 22:00-05:00 (10:00 PM to 5:00 AM)"
|
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
|
# Function to show final instructions
|
||||||
@ -623,6 +1118,7 @@ show_instructions() {
|
|||||||
echo "✓ Timer enabled and started"
|
echo "✓ Timer enabled and started"
|
||||||
echo "✓ Monitor service installed (protects timer from being disabled)"
|
echo "✓ Monitor service installed (protects timer from being disabled)"
|
||||||
echo "✓ Watchdog timer installed (restarts monitor if stopped)"
|
echo "✓ Watchdog timer installed (restarts monitor if stopped)"
|
||||||
|
echo "✓ Config file protected (immutable + path watcher + canonical copy)"
|
||||||
echo ""
|
echo ""
|
||||||
print_shutdown_schedule
|
print_shutdown_schedule
|
||||||
echo ""
|
echo ""
|
||||||
@ -630,12 +1126,19 @@ show_instructions() {
|
|||||||
echo " sudo day-specific-shutdown-manager.sh status - Check status"
|
echo " sudo day-specific-shutdown-manager.sh status - Check status"
|
||||||
echo " sudo day-specific-shutdown-manager.sh logs - View shutdown logs"
|
echo " sudo day-specific-shutdown-manager.sh logs - View shutdown logs"
|
||||||
echo ""
|
echo ""
|
||||||
|
echo "To modify shutdown hours (with psychological friction):"
|
||||||
|
echo " sudo /usr/local/sbin/unlock-shutdown-schedule"
|
||||||
|
echo ""
|
||||||
echo "How it works:"
|
echo "How it works:"
|
||||||
echo "• Timer checks every 30 minutes during potential shutdown windows"
|
echo "• Timer checks every 30 minutes during potential shutdown windows"
|
||||||
echo "• Smart logic determines shutdown eligibility based on day and time"
|
echo "• Smart logic determines shutdown eligibility based on day and time"
|
||||||
echo "• Monitor service watches the timer and re-enables it if disabled"
|
echo "• Monitor service watches the timer and re-enables it if disabled"
|
||||||
echo "• Watchdog timer restarts the monitor every 60 seconds if stopped"
|
echo "• Watchdog timer restarts the monitor every 60 seconds if stopped"
|
||||||
echo "• Monitor has RefuseManualStop=true to prevent easy stopping"
|
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 "• There is NO disable option - this is intentional for digital wellbeing"
|
||||||
echo ""
|
echo ""
|
||||||
echo "WARNING: This will automatically shutdown your PC during designated hours."
|
echo "WARNING: This will automatically shutdown your PC during designated hours."
|
||||||
@ -683,9 +1186,18 @@ enable_midnight_shutdown() {
|
|||||||
echo "Target user: $ACTUAL_USER"
|
echo "Target user: $ACTUAL_USER"
|
||||||
echo "User home: $USER_HOME"
|
echo "User home: $USER_HOME"
|
||||||
|
|
||||||
|
# Check if trying to cheat by making schedule more lenient
|
||||||
|
check_schedule_protection
|
||||||
|
|
||||||
# Confirm setup
|
# Confirm setup
|
||||||
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 systemd files
|
||||||
create_shutdown_service
|
create_shutdown_service
|
||||||
create_shutdown_timer
|
create_shutdown_timer
|
||||||
|
|||||||
377
scripts/fixes/fix_thorium.sh
Executable file
377
scripts/fixes/fix_thorium.sh
Executable file
@ -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 <<EOF
|
||||||
|
fix_thorium.sh - Fix Thorium Browser crashes and startup issues
|
||||||
|
|
||||||
|
Usage: $(basename "$0") [OPTIONS]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--aggressive Clear additional caches (IndexedDB, Service Worker, etc.)
|
||||||
|
May cause loss of some site data but more thorough fix
|
||||||
|
--test Test if Thorium starts successfully after applying fixes
|
||||||
|
--dry-run Show what would be done without making changes
|
||||||
|
-h, --help Show this help message
|
||||||
|
|
||||||
|
Common issues fixed:
|
||||||
|
- Corrupted 'Local State' file (causes immediate segfault)
|
||||||
|
- Stale singleton lock files (prevents startup)
|
||||||
|
- Corrupted GPU/shader cache
|
||||||
|
- Crashpad errors
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
$(basename "$0") # Apply standard fixes
|
||||||
|
$(basename "$0") --test # Fix and verify browser starts
|
||||||
|
$(basename "$0") --aggressive # Deep clean (use if standard fix fails)
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
DRY_RUN=false
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--aggressive)
|
||||||
|
AGGRESSIVE=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--test)
|
||||||
|
TEST_AFTER=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--dry-run)
|
||||||
|
DRY_RUN=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h | --help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
log_error "Unknown option: $1"
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check if Thorium is installed
|
||||||
|
check_thorium_installed() {
|
||||||
|
if ! command -v thorium-browser &>/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 "$@"
|
||||||
Loading…
Reference in New Issue
Block a user