Add shutdown timer monitor service to prevent disabling

- Remove 'disable' option from setup_midnight_shutdown.sh
- Add shutdown-timer-monitor.sh that watches the timer every 30s
- Re-enables timer automatically if someone tries to disable it
- Monitor service installed alongside the timer
- Makes it significantly harder to bypass the shutdown schedule
- Similar pattern to hosts-file-monitor.service
This commit is contained in:
Krzysztof kuhy Rudnicki 2025-12-07 14:08:13 +01:00
parent 4cb3a62491
commit a1b9200d19
3 changed files with 559 additions and 337 deletions

View File

@ -10,17 +10,19 @@ set -e # Exit on any error
show_usage() {
echo "Day-Specific Auto-Shutdown Setup for Arch Linux"
echo "==============================================="
echo "Usage: $0 [enable|disable|status]"
echo "Usage: $0 [enable|status]"
echo ""
echo "Commands:"
echo " enable - Set up automatic shutdown with day-specific windows (default)"
echo " disable - Remove automatic shutdown"
echo " status - Show current status"
echo ""
echo "Shutdown Schedule:"
echo " Monday-Wednesday: 21:00-05:00"
echo " Thursday-Sunday: 22:00-05:00"
echo ""
echo "NOTE: There is no 'disable' option. This is intentional."
echo " The shutdown timer is protected by a monitor service."
echo ""
}
# Function to check and request sudo privileges
@ -41,86 +43,6 @@ else
USER_HOME="$HOME"
fi
# Function to disable and remove midnight shutdown
disable_midnight_shutdown() {
echo "Disabling Day-Specific Auto-Shutdown"
echo "===================================="
echo "Current Date: $(date)"
echo "User: $ACTUAL_USER"
echo ""
local timer_file="/etc/systemd/system/day-specific-shutdown.timer"
local service_file="/etc/systemd/system/day-specific-shutdown.service"
local script_file="/usr/local/bin/day-specific-shutdown-manager.sh"
local check_script="/usr/local/bin/day-specific-shutdown-check.sh"
local removed_files=()
# Stop and disable timer if it exists
if systemctl is-active day-specific-shutdown.timer &> /dev/null; then
echo "Stopping day-specific-shutdown timer..."
systemctl stop day-specific-shutdown.timer
echo "✓ Timer stopped"
fi
if systemctl is-enabled day-specific-shutdown.timer &> /dev/null; then
echo "Disabling day-specific-shutdown timer..."
systemctl disable day-specific-shutdown.timer
echo "✓ Timer disabled"
fi
# Remove timer file
if [[ -f $timer_file ]]; then
rm -f "$timer_file"
removed_files+=("$timer_file")
echo "✓ Removed timer file: $timer_file"
fi
# Remove service file
if [[ -f $service_file ]]; then
rm -f "$service_file"
removed_files+=("$service_file")
echo "✓ Removed service file: $service_file"
fi
# Remove management script
if [[ -f $script_file ]]; then
rm -f "$script_file"
removed_files+=("$script_file")
echo "✓ Removed management script: $script_file"
fi
# Remove check script
if [[ -f $check_script ]]; then
rm -f "$check_script"
removed_files+=("$check_script")
echo "✓ Removed check script: $check_script"
fi
# Reload systemd daemon
if [[ ${#removed_files[@]} -gt 0 ]]; then
echo "Reloading systemd daemon..."
systemctl daemon-reload
echo "✓ Systemd daemon reloaded"
fi
echo ""
echo "============================================="
echo "Day-Specific Auto-Shutdown Removal Complete"
echo "============================================="
if [[ ${#removed_files[@]} -gt 0 ]]; then
echo "Removed files:"
printf " %s\n" "${removed_files[@]}"
echo ""
echo "✓ Automatic day-specific shutdown has been completely disabled"
echo "✓ Your PC will no longer shutdown automatically"
else
echo "No day-specific shutdown configuration was found to remove."
fi
echo ""
}
# Function to show current status
show_current_status() {
echo "Day-Specific Auto-Shutdown Status"
@ -151,17 +73,23 @@ show_current_status() {
echo "✗ Management script missing"
fi
if [[ -f "/usr/local/bin/shutdown-timer-monitor.sh" ]]; then
echo "✓ Monitor script exists"
else
echo "✗ Monitor script missing"
fi
echo ""
# Check systemd status
if $timer_exists; then
if systemctl is-enabled day-specific-shutdown.timer &> /dev/null; then
if systemctl is-enabled day-specific-shutdown.timer &>/dev/null; then
echo "✓ Timer is enabled"
if systemctl is-active day-specific-shutdown.timer &> /dev/null; then
if systemctl is-active day-specific-shutdown.timer &>/dev/null; then
echo "✓ Timer is active"
echo ""
echo "Next scheduled shutdown check:"
systemctl list-timers day-specific-shutdown.timer --no-pager 2> /dev/null | grep day-specific-shutdown || echo "Timer information not available"
systemctl list-timers day-specific-shutdown.timer --no-pager 2>/dev/null | grep day-specific-shutdown || echo "Timer information not available"
else
echo "✗ Timer is not active"
fi
@ -172,11 +100,29 @@ show_current_status() {
echo "Status: NOT CONFIGURED"
fi
echo ""
# Check monitor service status
echo "Monitor Service Status:"
if systemctl is-enabled shutdown-timer-monitor.service &>/dev/null; then
echo "✓ Monitor is enabled"
if systemctl is-active shutdown-timer-monitor.service &>/dev/null; then
echo "✓ Monitor is active (will re-enable timer if disabled)"
else
echo "✗ Monitor is not active"
fi
else
echo "✗ Monitor is not enabled"
fi
echo ""
echo "Shutdown Schedule:"
echo " Monday-Wednesday: 21:00-05:00"
echo " Thursday-Sunday: 22:00-05: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 ""
}
# Function to create the shutdown service
@ -187,7 +133,7 @@ create_shutdown_service() {
local service_file="/etc/systemd/system/day-specific-shutdown.service"
cat > "$service_file" << 'EOF'
cat >"$service_file" <<'EOF'
[Unit]
Description=Automatic PC shutdown with day-specific time windows
DefaultDependencies=false
@ -212,7 +158,7 @@ create_shutdown_timer() {
local timer_file="/etc/systemd/system/day-specific-shutdown.timer"
cat > "$timer_file" << 'EOF'
cat >"$timer_file" <<'EOF'
[Unit]
Description=Timer for automatic PC shutdown with day-specific windows
Requires=day-specific-shutdown.service
@ -255,7 +201,7 @@ create_management_script() {
local script_file="/usr/local/bin/day-specific-shutdown-manager.sh"
cat > "$script_file" << 'EOF'
cat >"$script_file" <<'EOF'
#!/bin/bash
# Day-Specific Auto-Shutdown Manager
# Provides easy management of the day-specific shutdown feature
@ -330,7 +276,7 @@ create_shutdown_check_script() {
local check_script="/usr/local/bin/day-specific-shutdown-check.sh"
cat > "$check_script" << 'EOF'
cat >"$check_script" <<'EOF'
#!/bin/bash
# Smart day-specific shutdown check script
# Different shutdown windows based on day of week:
@ -420,10 +366,133 @@ enable_timer() {
echo "✓ Started day-specific-shutdown timer"
}
# Function to install the monitor service
install_monitor_service() {
echo ""
echo "6. Installing Shutdown Timer Monitor Service..."
echo "=============================================="
local monitor_script="/usr/local/bin/shutdown-timer-monitor.sh"
local monitor_service="/etc/systemd/system/shutdown-timer-monitor.service"
# Create the monitor script
cat >"$monitor_script" <<'EOF'
#!/bin/bash
# Shutdown timer monitor script
# Watches the day-specific-shutdown timer and re-enables it if disabled
set -euo pipefail
LOG_FILE="/var/log/shutdown-timer-monitor.log"
TIMER_NAME="day-specific-shutdown.timer"
SERVICE_NAME="day-specific-shutdown.service"
CHECK_INTERVAL=30
log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE" >&2
}
timer_needs_restoration() {
if ! systemctl is-enabled "$TIMER_NAME" &>/dev/null; then
log_message "Timer $TIMER_NAME is not enabled"
return 0
fi
if ! systemctl is-active "$TIMER_NAME" &>/dev/null; then
log_message "Timer $TIMER_NAME is not active"
return 0
fi
if [[ ! -f "/etc/systemd/system/$TIMER_NAME" ]]; then
log_message "Timer unit file missing"
return 0
fi
if [[ ! -f "/etc/systemd/system/$SERVICE_NAME" ]]; then
log_message "Service unit file missing"
return 0
fi
if [[ ! -f "/usr/local/bin/day-specific-shutdown-check.sh" ]]; then
log_message "Check script missing"
return 0
fi
return 1
}
restore_timer() {
log_message "Shutdown timer tampering detected - initiating restoration"
systemctl daemon-reload
if ! systemctl is-enabled "$TIMER_NAME" &>/dev/null; then
log_message "Re-enabling $TIMER_NAME"
systemctl enable "$TIMER_NAME" 2>/dev/null || true
fi
if ! systemctl is-active "$TIMER_NAME" &>/dev/null; then
log_message "Re-starting $TIMER_NAME"
systemctl start "$TIMER_NAME" 2>/dev/null || true
fi
if systemctl is-active "$TIMER_NAME" &>/dev/null; then
log_message "Timer restoration completed successfully"
else
log_message "WARNING: Timer restoration may have failed"
fi
}
log_message "=== Shutdown Timer Monitor Started ==="
log_message "Monitoring timer: $TIMER_NAME"
if timer_needs_restoration; then
log_message "Initial check: Timer needs restoration"
restore_timer
else
log_message "Initial check: Timer is properly configured"
fi
while true; do
if timer_needs_restoration; then
restore_timer
fi
sleep "$CHECK_INTERVAL"
done
EOF
chmod +x "$monitor_script"
echo "✓ Created monitor script: $monitor_script"
# Create the monitor service
cat >"$monitor_service" <<'EOF'
[Unit]
Description=Shutdown Timer Monitor and Auto-Restore Service
After=network-online.target day-specific-shutdown.timer
Wants=network-online.target
[Service]
Type=simple
User=root
ExecStart=/usr/local/bin/shutdown-timer-monitor.sh
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
NoNewPrivileges=false
PrivateTmp=true
MemoryMax=50M
CPUQuota=10%
[Install]
WantedBy=multi-user.target
EOF
echo "✓ Created monitor service: $monitor_service"
# Reload and enable monitor
systemctl daemon-reload
systemctl enable shutdown-timer-monitor.service
systemctl start shutdown-timer-monitor.service
echo "✓ Enabled and started shutdown-timer-monitor.service"
}
# Function to test the setup
test_setup() {
echo ""
echo "6. Testing Setup..."
echo "7. Testing Setup..."
echo "=================="
echo "Service files:"
@ -439,23 +508,43 @@ test_setup() {
echo "✗ Timer file missing"
fi
if [[ -f "/etc/systemd/system/shutdown-timer-monitor.service" ]]; then
echo "✓ Monitor service file exists"
else
echo "✗ Monitor service file missing"
fi
echo ""
echo "Timer status:"
if systemctl is-enabled day-specific-shutdown.timer &> /dev/null; then
if systemctl is-enabled day-specific-shutdown.timer &>/dev/null; then
echo "✓ Timer is enabled"
else
echo "✗ Timer is not enabled"
fi
if systemctl is-active day-specific-shutdown.timer &> /dev/null; then
if systemctl is-active day-specific-shutdown.timer &>/dev/null; then
echo "✓ Timer is active"
else
echo "✗ Timer is not active"
fi
echo ""
echo "Monitor status:"
if systemctl is-enabled shutdown-timer-monitor.service &>/dev/null; then
echo "✓ Monitor is enabled"
else
echo "✗ Monitor is not enabled"
fi
if systemctl is-active shutdown-timer-monitor.service &>/dev/null; then
echo "✓ Monitor is active"
else
echo "✗ Monitor is not active"
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"
systemctl list-timers day-specific-shutdown.timer --no-pager 2>/dev/null | head -5 | grep day-specific-shutdown || echo "Timer information not available"
}
# Function to show final instructions
@ -470,6 +559,7 @@ show_instructions() {
echo "✓ Management script created (/usr/local/bin/day-specific-shutdown-manager.sh)"
echo "✓ Smart check script created (/usr/local/bin/day-specific-shutdown-check.sh)"
echo "✓ Timer enabled and started"
echo "✓ Monitor service installed and started (protects timer from being disabled)"
echo ""
echo "Shutdown Schedule:"
echo " Monday-Wednesday: 21:00-05:00 (9:00 PM to 5:00 AM)"
@ -482,7 +572,8 @@ show_instructions() {
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 "• Prevents accidental shutdowns outside designated time windows"
echo "• Monitor service watches the timer and re-enables it if disabled"
echo "• There is NO disable option - this is intentional for digital wellbeing"
echo ""
echo "WARNING: This will automatically shutdown your PC during designated hours."
echo "Make sure to save your work before the shutdown windows!"
@ -506,6 +597,7 @@ confirm_setup() {
echo "- Downloads/uploads in progress will be interrupted"
echo "- You'll need to manually power on your PC each day"
echo "- Timer checks every 30 minutes during potential shutdown windows"
echo "- There is NO disable option - this is protected by a monitor service"
echo ""
read -r -p "Do you want to proceed? (y/N): " confirm
@ -521,32 +613,6 @@ confirm_setup() {
esac
}
# Function to confirm disable
confirm_disable() {
echo ""
echo "Disable Day-Specific Auto-Shutdown Confirmation"
echo "==============================================="
echo "This will completely remove the automatic day-specific shutdown configuration."
echo ""
echo "After disabling:"
echo "- Your PC will no longer shutdown automatically during any time windows"
echo "- All related systemd services and timers will be removed"
echo "- The management and check scripts will be deleted"
echo ""
read -r -p "Do you want to proceed with disabling? (y/N): " confirm
case "$confirm" in
[yY] | [yY][eE][sS])
echo "Proceeding with disable..."
return 0
;;
*)
echo "Disable cancelled."
exit 0
;;
esac
}
# Main execution flow for enable
enable_midnight_shutdown() {
echo "Day-Specific Auto-Shutdown Setup for Arch Linux"
@ -568,6 +634,9 @@ enable_midnight_shutdown() {
# Enable and start timer
enable_timer
# Install monitor service (protects timer from being disabled)
install_monitor_service
# Test setup
test_setup
@ -577,23 +646,18 @@ enable_midnight_shutdown() {
# Parse command line arguments
case "${1:-enable}" in
"enable")
"enable")
check_sudo "$@"
enable_midnight_shutdown
;;
"disable")
check_sudo "$@"
confirm_disable
disable_midnight_shutdown
;;
"status")
"status")
check_sudo "$@"
show_current_status
;;
"help" | "-h" | "--help")
"help" | "-h" | "--help")
show_usage
;;
*)
*)
echo "Error: Unknown command '$1'"
echo ""
show_usage

View File

@ -0,0 +1,131 @@
#!/bin/bash
# Shutdown timer monitor script
# Watches the day-specific-shutdown timer and re-enables it if disabled
# This file is installed by setup_midnight_shutdown.sh
set -euo pipefail
LOG_FILE="/var/log/shutdown-timer-monitor.log"
TIMER_NAME="day-specific-shutdown.timer"
SERVICE_NAME="day-specific-shutdown.service"
CHECK_INTERVAL=30
# Function to log with timestamp
log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE" >&2
}
# Function to check if timer needs to be re-enabled
timer_needs_restoration() {
# Check if timer is enabled
if ! systemctl is-enabled "$TIMER_NAME" &>/dev/null; then
log_message "Timer $TIMER_NAME is not enabled"
return 0
fi
# Check if timer is active
if ! systemctl is-active "$TIMER_NAME" &>/dev/null; then
log_message "Timer $TIMER_NAME is not active"
return 0
fi
# Check if timer unit file exists
if [[ ! -f "/etc/systemd/system/$TIMER_NAME" ]]; then
log_message "Timer unit file missing: /etc/systemd/system/$TIMER_NAME"
return 0
fi
# Check if service unit file exists
if [[ ! -f "/etc/systemd/system/$SERVICE_NAME" ]]; then
log_message "Service unit file missing: /etc/systemd/system/$SERVICE_NAME"
return 0
fi
# Check if check script exists
if [[ ! -f "/usr/local/bin/day-specific-shutdown-check.sh" ]]; then
log_message "Check script missing: /usr/local/bin/day-specific-shutdown-check.sh"
return 0
fi
return 1 # Timer is properly configured
}
# Function to restore timer
restore_timer() {
log_message "Shutdown timer tampering detected - initiating restoration"
# Reload systemd daemon in case unit files were modified
systemctl daemon-reload
# Re-enable timer if disabled
if ! systemctl is-enabled "$TIMER_NAME" &>/dev/null; then
log_message "Re-enabling $TIMER_NAME"
systemctl enable "$TIMER_NAME" 2>/dev/null || true
fi
# Re-start timer if not active
if ! systemctl is-active "$TIMER_NAME" &>/dev/null; then
log_message "Re-starting $TIMER_NAME"
systemctl start "$TIMER_NAME" 2>/dev/null || true
fi
# Verify restoration
if systemctl is-active "$TIMER_NAME" &>/dev/null; then
log_message "Timer restoration completed successfully"
else
log_message "WARNING: Timer restoration may have failed"
fi
}
# Function to monitor timer with systemd events
monitor_with_dbus() {
log_message "Starting shutdown timer monitoring with D-Bus events"
# Use busctl to monitor systemd unit changes
# Fall back to polling if this fails
if command -v busctl &>/dev/null; then
# Monitor for unit state changes
busctl monitor --system org.freedesktop.systemd1 2>/dev/null |
while read -r line; do
# Check if the line mentions our timer
if echo "$line" | grep -q "$TIMER_NAME\|$SERVICE_NAME"; then
log_message "Systemd event detected for shutdown timer"
sleep 2
if timer_needs_restoration; then
restore_timer
fi
fi
done
else
log_message "busctl not available, falling back to polling"
monitor_with_polling
fi
}
# Function to monitor with polling (primary method for reliability)
monitor_with_polling() {
log_message "Starting shutdown timer monitoring with polling (interval: ${CHECK_INTERVAL}s)"
while true; do
if timer_needs_restoration; then
restore_timer
fi
sleep "$CHECK_INTERVAL"
done
}
# Main execution
log_message "=== Shutdown Timer Monitor Started ==="
log_message "Monitoring timer: $TIMER_NAME"
log_message "Monitoring service: $SERVICE_NAME"
# Initial check
if timer_needs_restoration; then
log_message "Initial check: Timer needs restoration"
restore_timer
else
log_message "Initial check: Timer is properly configured"
fi
# Use polling for reliability (D-Bus monitoring can miss events)
monitor_with_polling

View File

@ -0,0 +1,27 @@
[Unit]
Description=Shutdown Timer Monitor and Auto-Restore Service
After=network-online.target day-specific-shutdown.timer
Wants=network-online.target
[Service]
Type=simple
User=root
ExecStart=/usr/local/bin/shutdown-timer-monitor.sh
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
# Environment
Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# Security settings
NoNewPrivileges=false
PrivateTmp=true
# Resource limits
MemoryMax=50M
CPUQuota=10%
[Install]
WantedBy=multi-user.target