From c87127ff9322f85ee4c0283b61abdb42adfc14b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Wed, 12 Aug 2026 18:11:16 +0000 Subject: [PATCH] port the sleep and suspend section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The original ran unconditionally, so a laptop that went through it stopped suspending — a hot bag and a flat battery. It is a server concern: dev is skipped with the reason printed, like the ballast. Four things wrong with it beyond the role: HandleLidSwitchDocked was never set. A laptop used as a homelab server, docked and closed, still suspends — which is the exact machine this setting exists for. Added. RuntimeDirectorySize=10% was set alongside the sleep handlers. It is the size of /run, has nothing to do with sleeping, and 10% is systemd's own default, so the line never did anything. Dropped. systemd-logind was restarted on every pass whether or not anything changed, disturbing live sessions for nothing. The step now checks first and does not reach the restart when the machine is already configured. (The platform's own scripts/setup-old/setup.sh already had this guard; the machine script did not.) The settings were sed'd into logind.conf in place. They are a drop-in at /etc/systemd/logind.conf.d/99-machine-setup.conf now, so what this script set is one file that can be read or removed on its own. Current state is printed before anything is asked — whether the targets are masked, and what the lid, idle and power-key handlers actually do. logind_effective reads the main file and every drop-in and takes the last match, since a drop-in overrides logind.conf; reading only the main file reports a configured machine as unconfigured. The power-button consequence is stated rather than left to be discovered: after this, pressing power physically does nothing and a clean shutdown is `sudo poweroff`. WSL has no logind and cannot suspend, and says so. Verified both roles on this host, which the old script had already configured — correctly reports the targets masked and the handlers set, and correctly reports itself not fully configured because HandleLidSwitchDocked is missing. Co-Authored-By: Claude Opus 5 (1M context) --- scripts/setup/machine-setup/lib/system.sh | 83 ++++++++++++++++++++ scripts/setup/machine-setup/machine-setup.sh | 52 +++++++++++- 2 files changed, 134 insertions(+), 1 deletion(-) diff --git a/scripts/setup/machine-setup/lib/system.sh b/scripts/setup/machine-setup/lib/system.sh index 2e1ecccc..20ca708a 100644 --- a/scripts/setup/machine-setup/lib/system.sh +++ b/scripts/setup/machine-setup/lib/system.sh @@ -355,6 +355,89 @@ EOF sysctl -q -w "fs.inotify.max_user_instances=${INOTIFY_INSTANCES}" } +# ----------------------------------------------------------------------------- +# Sleep and suspend +# ----------------------------------------------------------------------------- +# +# A server that suspends is a server that is off. The machine stops answering, +# and on a box with no keyboard attached there is nothing to wake it — which is +# the whole failure: it looks like a crash, and the only fix is physical. +# +# Two independent mechanisms, and both have to be dealt with: +# +# the sleep targets what suspend/hibernate hang off. Masking them means +# nothing can trigger a sleep, including a stray +# `systemctl suspend` +# logind's handlers what closing a lid, pressing the power button or going +# idle DO. These are what a desktop image sets, and they +# act before anything reaches a target +# +# Written as a drop-in rather than by editing logind.conf in place, so what this +# script set is one file that can be read or deleted on its own. + +SLEEP_TARGETS=(sleep.target suspend.target hibernate.target hybrid-sleep.target) +LOGIND_DROPIN=/etc/systemd/logind.conf.d/99-machine-setup.conf + +# The value actually in force for a logind setting, or empty for the default. +# Drop-ins override the main file and later ones override earlier, so the last +# match wins — reading only logind.conf would miss a setting made by a drop-in +# and report the machine as unconfigured when it is not. +logind_effective() { + local key="$1" + { + [[ -r /etc/systemd/logind.conf ]] && grep -hE "^${key}=" /etc/systemd/logind.conf + for f in /etc/systemd/logind.conf.d/*.conf; do + [[ -r "$f" ]] && grep -hE "^${key}=" "$f" + done + } 2>/dev/null | tail -1 | cut -d= -f2- +} + +sleep_targets_masked() { + local t + for t in "${SLEEP_TARGETS[@]}"; do + [[ "$(systemctl is-enabled "$t" 2>/dev/null)" == "masked" ]] || return 1 + done +} + +# What the machine should be set to. RuntimeDirectorySize is deliberately NOT +# here: the original set it to 10% alongside these, which is both unrelated to +# sleeping — it is the size of /run — and systemd's own default, so the line +# never did anything. +logind_wanted() { + cat <<'EOF' +HandleLidSwitch=ignore +HandleLidSwitchExternalPower=ignore +HandleLidSwitchDocked=ignore +HandlePowerKey=ignore +IdleAction=none +EOF +} + +# Is every wanted setting already in force? +logind_is_configured() { + local line key value + while IFS= read -r line; do + key="${line%%=*}" + value="${line#*=}" + [[ "$(logind_effective "$key")" == "$value" ]] || return 1 + done < <(logind_wanted) +} + +disable_sleep() { + systemctl mask "${SLEEP_TARGETS[@]}" >/dev/null 2>&1 + + mkdir -p "$(dirname "$LOGIND_DROPIN")" + { + echo "# Written by machine-setup: this machine is a server and must not sleep." + echo "[Login]" + logind_wanted + } >"$LOGIND_DROPIN" + + # Only restart when something actually changed — a needless restart of logind + # disturbs live sessions, and this step runs on every pass. + systemctl restart systemd-logind +} + # ----------------------------------------------------------------------------- # Timezone # ----------------------------------------------------------------------------- diff --git a/scripts/setup/machine-setup/machine-setup.sh b/scripts/setup/machine-setup/machine-setup.sh index 95422092..e98fcfb8 100755 --- a/scripts/setup/machine-setup/machine-setup.sh +++ b/scripts/setup/machine-setup/machine-setup.sh @@ -617,13 +617,63 @@ if ! skip; then step_ok fi +# ============================================================================= +# 12. Sleep and suspend +# ============================================================================= + +step "Sleep and suspend" +# A laptop should sleep. Disabling it on the machine somebody carries around is +# how you get a hot bag and a flat battery, so dev is skipped — visibly. +if ! skip && is_role dev; then + echo "" + info "Sleep and suspend — left alone on a ${MACHINE_ROLE} machine" + echo " Suspending is what a machine you carry should do. Only a server has" + echo " to be stopped from it." + SUMMARY+=("Sleep: left alone on ${MACHINE_ROLE}") + step_ok +elif ! skip; then + echo "" + info "Sleep and suspend — stop this machine putting itself to sleep" + echo " A server that suspends is a server that is off: it stops answering," + echo " and with no keyboard attached there is nothing to wake it. It looks" + echo " like a crash and the fix is a physical visit." + echo "" + echo " sleep targets: $(sleep_targets_masked && echo 'masked' || echo 'available')" + echo " lid closed: $(logind_effective HandleLidSwitch || echo 'default (suspend)')" + echo " idle: $(logind_effective IdleAction || echo 'default (ignore)')" + echo " power button: $(logind_effective HandlePowerKey || echo 'default (power off)')" + + if [[ "$IS_WSL" == true ]]; then + echo " WSL has no logind and cannot suspend — nothing to do" + SUMMARY+=("Sleep: not applicable under WSL") + elif sleep_targets_masked && logind_is_configured; then + echo " already configured, nothing to do" + SUMMARY+=("Sleep: already disabled") + else + echo "" + echo " This masks the four sleep targets and tells logind to ignore a" + echo " closed lid, an idle session and the power button. Note the last" + echo " one: after this, physically pressing power does nothing, so a" + echo " clean shutdown is 'sudo poweroff' rather than the button." + if confirm "Proceed?"; then + disable_sleep + ok "sleep disabled, logind reloaded" + SUMMARY+=("Sleep: disabled (targets masked, logind handlers ignored)") + else + warn "skipped by request" + SUMMARY+=("Sleep: SKIPPED by request") + fi + fi + step_ok +fi + # ============================================================================= # NOT PORTED YET # ============================================================================= # # Sections still to move across from scripts/setup-old/setup-ubuntu.sh, in order: # -# auto-suspend · boot-hang fix · user creation · +# boot-hang fix · user creation · # ssh keys · ssh hardening · dns · static ip · fail2ban · unattended-upgrades · # git config · docker · zsh + prompt · tailscale · neovim · js runtimes · # dev tools · ufw · zshrc