testsAndMisc/linux_configuration/periodic_background/digital_wellbeing/lib/payloads/shutdown-config-unlock.sh.in
Krzysztof kuhy Rudnicki 4f2b0ccdf7 Dissolve linux_configuration/scripts/ and single_use/
Two levels of pure taxonomy carrying no information, and a 2-directory
cap leaves no room for them. Categories now sit directly under
linux_configuration/: features, fixes, utils, misc, fresh-install,
installers, diagnostics, gaming, boot_recovery, periodic_background, lib,
meta, tests. A script's own lib/ and tests/ are exempt from the cap, so
those stay put.

Depth violations 248 -> 139; roughly 40 of the remainder are inside
digital_wellbeing and hosts, deferred until the guard-lib repoint.

The moves are the easy half. What breaks is every path that counted
levels, and three classes of it had to be found by resolving paths rather
than reading diffs:

  - 52 relative `source` lines for lib/common.sh and lib/android.sh. The
    correct prefix differs per file: 40 were ../../ and 12 were ../../../,
    so a blanket replace would have fixed one set and broken the other.
  - REPO_ROOT in 7 fixes/lib/tests harnesses and setup_syncyomi_guard.sh,
    each walking up four levels from a directory now two levels shallower.
    This caused a real regression -- the fixes suite went red with
    "log_warn: command not found" -- caught by running it and comparing
    against the pre-move baseline worktree.
  - conftest.py's sys.path entries, which took the pytest gate down with
    14 collection errors, and the `# shellcheck source=` directives, which
    took shellcheck down with SC1091.

16 symlinks in $HOME broke, including the entire i3blocks bar. All
repointed, and cpu_monitor/memory/disk/time/volume were run to confirm
they emit real output rather than merely resolving.

Verified: 1798 tests pass at 100% coverage, 8 of 9 lib/tests suites pass
(the ninth needs the coverage jail, identically at baseline), pre-commit
is clean, and the two untracked secret configs are still ignored after
their gitignore paths changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJrqKERAo5QUeEHWS5Asgp
2026-08-23 18:53:30 +02:00

225 lines
8.1 KiB
Bash
Executable File

#!/bin/bash
# Unlock shutdown schedule config for editing with smart friction
# This script:
# - NO delay if making schedule STRICTER (earlier shutdown hours)
# - DELAY if making schedule more LENIENT (later shutdown hours)
# - BLOCKS lowering MORNING_END_HOUR (that would shorten the shutdown window)
set -euo pipefail
DELAY_SECONDS=45
GUARD_NAME="shutdown-schedule"
CONFIG_FILE="/etc/shutdown-schedule.conf"
LOG_FILE="/var/log/shutdown-schedule-guard.log"
EDITOR="${EDITOR:-nano}"
TEMP_FILE="/tmp/shutdown-schedule-edit.$$"
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
CANONICAL_FILE="$(guardctl file-guard canonical-path "$GUARD_NAME")"
if [[ -z "$CANONICAL_FILE" ]]; then
echo "Error: guard-lib instance '$GUARD_NAME' is not installed (guardctl file-guard canonical-path returned empty)" >&2
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') ==="
# Load current values
OLD_MON_WED=""
OLD_THU_SUN=""
OLD_MORNING_END=""
if [[ -f "$CANONICAL_FILE" ]]; then
# shellcheck source=/dev/null
source "$CANONICAL_FILE" 2>/dev/null || true
OLD_MON_WED="${MON_WED_HOUR:-}"
OLD_THU_SUN="${THU_SUN_HOUR:-}"
OLD_MORNING_END="${MORNING_END_HOUR:-}"
fi
echo ""
echo "╔══════════════════════════════════════════════════════════════════╗"
echo "║ SHUTDOWN SCHEDULE CONFIG UNLOCK ║"
echo "╚══════════════════════════════════════════════════════════════════╝"
echo ""
echo "Current schedule:"
echo " Monday-Wednesday: ${OLD_MON_WED:-??}:00 - 0${OLD_MORNING_END:-?}:00"
echo " Thursday-Sunday: ${OLD_THU_SUN:-??}:00 - 0${OLD_MORNING_END:-?}:00"
echo ""
echo "Rules:"
echo " ✓ Making shutdown EARLIER (stricter) = No delay"
echo " ⏳ Making shutdown LATER (lenient) = ${DELAY_SECONDS}s delay required"
echo " ❌ Lowering MORNING_END_HOUR = BLOCKED (would shorten shutdown window)"
echo ""
# Stop the path watcher temporarily. This is NOT optional: `chattr -i`
# below is itself enough to fire guard-file@shutdown-schedule.path (its
# PathModified reacts to attribute changes, not just content writes), and
# that watcher's enforce pass unconditionally re-locks the target at the
# end even when no drift is found - which would silently re-lock the file
# out from under us during the 45s delay below. Confirmed live: without
# this stop, the delayed-apply cp failed with "Operation not permitted".
systemctl stop "guard-file@${GUARD_NAME}.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
# Copy config to temp file for editing
cp "$CONFIG_FILE" "$TEMP_FILE"
echo "Opening editor..."
echo ""
# Open editor on temp file
$EDITOR "$TEMP_FILE"
# Load new values from edited file
NEW_MON_WED=""
NEW_THU_SUN=""
NEW_MORNING_END=""
# shellcheck source=/dev/null
source "$TEMP_FILE" 2>/dev/null || true
NEW_MON_WED="${MON_WED_HOUR:-}"
NEW_THU_SUN="${THU_SUN_HOUR:-}"
NEW_MORNING_END="${MORNING_END_HOUR:-}"
echo ""
echo "Checking changes..."
# Check for blocked changes (lowering MORNING_END_HOUR)
if [[ -n "$OLD_MORNING_END" ]] && [[ -n "$NEW_MORNING_END" ]]; then
if [[ "$NEW_MORNING_END" -lt "$OLD_MORNING_END" ]]; then
echo ""
echo "╔══════════════════════════════════════════════════════════════════╗"
echo "║ ❌ CHANGE BLOCKED - CANNOT LOWER MORNING_END_HOUR ❌ ║"
echo "╚══════════════════════════════════════════════════════════════════╝"
echo ""
echo "You tried to lower MORNING_END_HOUR from $OLD_MORNING_END to $NEW_MORNING_END"
echo "This would SHORTEN the shutdown window, making it more lenient."
echo ""
echo "This change is NOT allowed. The shutdown window must end at"
echo "0${OLD_MORNING_END}:00 or later."
echo ""
rm -f "$TEMP_FILE"
# Re-apply protection
chattr +i "$CONFIG_FILE" 2>/dev/null || true
chattr +i "$CANONICAL_FILE" 2>/dev/null || true
systemctl start "guard-file@${GUARD_NAME}.path" 2>/dev/null || true
log "BLOCKED: User tried to lower MORNING_END_HOUR from $OLD_MORNING_END to $NEW_MORNING_END"
exit 1
fi
fi
# Check if changes require delay (making schedule more lenient)
NEEDS_DELAY=false
LENIENT_CHANGES=()
if [[ -n "$OLD_MON_WED" ]] && [[ -n "$NEW_MON_WED" ]]; then
if [[ "$NEW_MON_WED" -gt "$OLD_MON_WED" ]]; then
NEEDS_DELAY=true
LENIENT_CHANGES+=("Mon-Wed: ${OLD_MON_WED}:00 → ${NEW_MON_WED}:00 (later)")
fi
fi
if [[ -n "$OLD_THU_SUN" ]] && [[ -n "$NEW_THU_SUN" ]]; then
if [[ "$NEW_THU_SUN" -gt "$OLD_THU_SUN" ]]; then
NEEDS_DELAY=true
LENIENT_CHANGES+=("Thu-Sun: ${OLD_THU_SUN}:00 → ${NEW_THU_SUN}:00 (later)")
fi
fi
# Check for stricter changes (allowed without delay)
STRICTER_CHANGES=()
if [[ -n "$OLD_MON_WED" ]] && [[ -n "$NEW_MON_WED" ]]; then
if [[ "$NEW_MON_WED" -lt "$OLD_MON_WED" ]]; then
STRICTER_CHANGES+=("Mon-Wed: ${OLD_MON_WED}:00 → ${NEW_MON_WED}:00 (earlier)")
fi
fi
if [[ -n "$OLD_THU_SUN" ]] && [[ -n "$NEW_THU_SUN" ]]; then
if [[ "$NEW_THU_SUN" -lt "$OLD_THU_SUN" ]]; then
STRICTER_CHANGES+=("Thu-Sun: ${OLD_THU_SUN}:00 → ${NEW_THU_SUN}:00 (earlier)")
fi
fi
if [[ -n "$OLD_MORNING_END" ]] && [[ -n "$NEW_MORNING_END" ]]; then
if [[ "$NEW_MORNING_END" -gt "$OLD_MORNING_END" ]]; then
STRICTER_CHANGES+=("Morning end: 0${OLD_MORNING_END}:00 → 0${NEW_MORNING_END}:00 (later = longer window)")
fi
fi
# Report stricter changes
if [[ ${#STRICTER_CHANGES[@]} -gt 0 ]]; then
echo ""
echo "✓ Stricter changes detected (no delay needed):"
for s in "${STRICTER_CHANGES[@]}"; do
echo " • $s"
done
fi
# Handle lenient changes
if [[ "$NEEDS_DELAY" == true ]]; then
echo ""
echo "⚠️ More lenient changes detected:"
for l in "${LENIENT_CHANGES[@]}"; do
echo " • $l"
done
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 for lenient changes: ${LENIENT_CHANGES[*]}"
else
echo ""
echo "✓ No delay required (schedule is same or stricter)"
fi
# Apply the changes
cp "$TEMP_FILE" "$CONFIG_FILE"
cp "$TEMP_FILE" "$CANONICAL_FILE"
rm -f "$TEMP_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 (stopped near the start of this script)
systemctl start "guard-file@${GUARD_NAME}.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):"
# shellcheck source=/dev/null
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 ""