From 458510a0a835bec5a90609afdec4a9ecd62371a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Wed, 12 Aug 2026 18:17:17 +0000 Subject: [PATCH] scope sleep to homelab, and port the boot hang fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sleep and suspend is homelab only now. The two exclusions are for different reasons and both are stated in the run rather than left implicit: dev — a laptop should sleep; disabling it is a hot bag and a flat battery. vps — not merely unnecessary, harmful. A virtual machine has no lid and no power button, but the provider's Shut down control works by sending an ACPI power button event. HandlePowerKey=ignore makes the VM ignore it, so graceful shutdown requests silently do nothing and the instance is hard-killed instead. systemd defaults that key to poweroff for exactly this reason. The boot hang fix is everything except vps, where systemd-networkd genuinely manages the network and the unit is load-bearing. Rather than asking whether boot "feels slow" — a question people answer from memory of the worst time it happened — the step prints what the unit actually cost on this boot, from systemd's own accounting. On this host that is 14ms, which ends the discussion. On a NetworkManager desktop it is two minutes, which also ends it. On dev the wording says outright that a small number here means there is nothing to do. The original's live guard is kept and is what actually decides: NetworkManager active and networkd not. Anything else, including "cannot tell", is left alone, and the reason is printed. The warning against disabling systemd-networkd outright is carried across into the library, where the alternative would be attempted. Verified all three roles on this host, which is a networkd machine: homelab and dev both correctly refuse and explain, vps skips as not applicable, and the timing helper reads 14ms out of systemd-analyze. Co-Authored-By: Claude Opus 5 (1M context) --- scripts/setup/machine-setup/lib/system.sh | 46 +++++++++++ scripts/setup/machine-setup/machine-setup.sh | 80 +++++++++++++++++++- 2 files changed, 123 insertions(+), 3 deletions(-) diff --git a/scripts/setup/machine-setup/lib/system.sh b/scripts/setup/machine-setup/lib/system.sh index 20ca708a..0b0e71f9 100644 --- a/scripts/setup/machine-setup/lib/system.sh +++ b/scripts/setup/machine-setup/lib/system.sh @@ -438,6 +438,52 @@ disable_sleep() { systemctl restart systemd-logind } +# ----------------------------------------------------------------------------- +# Boot hang +# ----------------------------------------------------------------------------- +# +# systemd-networkd-wait-online blocks boot until the network is up. Where +# systemd-networkd actually manages the network — a server or cloud image, via +# cloud-init and netplan — it does that in milliseconds and is load-bearing. +# +# Where NetworkManager owns the network instead, systemd-networkd runs nothing, +# but the wait-online unit is still enabled and waits for a link that will never +# be configured. It gives up after its full timeout, on every boot. +# +# So the fix is masking one unit, and only on the second stack. Do NOT +# `systemctl disable --now systemd-networkd` to achieve the same thing: on a +# networkd-managed box that brings it up with no network on the next boot, no +# ssh, and nothing but the provider's rescue console. + +WAIT_ONLINE_UNIT=systemd-networkd-wait-online.service + +network_manager_name() { + if systemctl is-active --quiet NetworkManager.service 2>/dev/null; then + echo "NetworkManager" + elif systemctl is-active --quiet systemd-networkd.service 2>/dev/null; then + echo "systemd-networkd" + else + echo "neither — unclear" + fi +} + +# Is the wait actually pointless here? NetworkManager in charge and networkd not. +# Anything else, including "cannot tell", is left alone. +wait_online_is_spurious() { + systemctl is-active --quiet NetworkManager.service 2>/dev/null && + ! systemctl is-active --quiet systemd-networkd.service 2>/dev/null +} + +# What that unit actually cost on this boot, straight from systemd's own +# accounting. Worth printing rather than asking somebody whether boot "feels +# slow": the answer is either 14ms or two minutes, and there is no arguing with +# it. Empty when the unit did not run. +wait_online_boot_time() { + systemd-analyze blame 2>/dev/null | awk -v u="$WAIT_ONLINE_UNIT" '$NF == u { $NF = ""; sub(/[[:space:]]+$/, ""); print; exit }' +} + +mask_wait_online() { systemctl mask --now "$WAIT_ONLINE_UNIT" >/dev/null 2>&1; } + # ----------------------------------------------------------------------------- # Timezone # ----------------------------------------------------------------------------- diff --git a/scripts/setup/machine-setup/machine-setup.sh b/scripts/setup/machine-setup/machine-setup.sh index e98fcfb8..fc4bd05f 100755 --- a/scripts/setup/machine-setup/machine-setup.sh +++ b/scripts/setup/machine-setup/machine-setup.sh @@ -622,8 +622,16 @@ fi # ============================================================================= 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. +# Homelab only, and the two exclusions are for different reasons. +# +# dev: a laptop should sleep. Disabling it on the machine somebody carries is how +# you get a hot bag and a flat battery. +# +# vps: not merely unnecessary but harmful. A virtual machine has no lid and no +# power button, but the provider's "Shut down" control works by sending an ACPI +# power button event — HandlePowerKey=ignore makes the VM ignore it, so a +# graceful shutdown request does nothing and the provider hard-kills the instance +# instead. systemd defaults that key to poweroff for exactly this reason. if ! skip && is_role dev; then echo "" info "Sleep and suspend — left alone on a ${MACHINE_ROLE} machine" @@ -631,6 +639,16 @@ if ! skip && is_role dev; then echo " to be stopped from it." SUMMARY+=("Sleep: left alone on ${MACHINE_ROLE}") step_ok +elif ! skip && is_role vps; then + echo "" + info "Sleep and suspend — left alone on a ${MACHINE_ROLE}" + echo " A virtual machine has no lid and no power button to disable. What it" + echo " does have is the provider's Shut down control, which works by sending" + echo " an ACPI power button event — telling this machine to ignore that would" + echo " mean graceful shutdowns silently do nothing and the instance gets" + echo " hard-killed instead." + SUMMARY+=("Sleep: left alone on ${MACHINE_ROLE} — would break provider shutdown") + step_ok elif ! skip; then echo "" info "Sleep and suspend — stop this machine putting itself to sleep" @@ -667,13 +685,69 @@ elif ! skip; then step_ok fi +# ============================================================================= +# 13. Boot hang +# ============================================================================= + +step "Boot hang" +# Not on a VPS. There the network IS systemd-networkd — cloud-init and netplan +# put it in charge — so the unit below is load-bearing and completes in +# milliseconds. Masking it on that stack is how a box comes up with no network +# and no ssh. +if ! skip && is_role vps; then + echo "" + info "Boot hang — not applicable on a ${MACHINE_ROLE}" + echo " systemd-networkd is what configures the network here, so the unit" + echo " this would mask is doing real work." + SUMMARY+=("Boot hang: not applicable on ${MACHINE_ROLE}") + step_ok +elif ! skip; then + WAIT_ONLINE_TIME="$(wait_online_boot_time)" + + echo "" + info "Boot hang — a unit that can add two minutes to every boot" + echo " On a machine where NetworkManager owns the network, systemd-networkd" + echo " runs nothing — but systemd-networkd-wait-online is still enabled, and" + echo " waits for a link that will never come up. It gives up after its full" + echo " timeout, every single boot." + echo "" + echo " network managed by: $(network_manager_name)" + echo " that unit took: ${WAIT_ONLINE_TIME:-it did not run} on this boot" + + if ! wait_online_is_spurious; then + echo "" + echo " systemd-networkd is live here, so that unit is doing real work." + echo " Masking it would be how this machine comes up with no network." + SUMMARY+=("Boot hang: nothing to fix — networkd is in charge") + else + echo "" + if is_role dev; then + echo " Only worth doing if you actually feel this — if the machine sits" + echo " there for a couple of minutes before the login screen. The number" + echo " above is the honest answer for this boot; if it is small, there is" + echo " nothing here for you." + fi + echo " This masks systemd-networkd-wait-online.service only. NetworkManager" + echo " keeps configuring the network exactly as it does now." + if confirm "Mask it?"; then + mask_wait_online + ok "systemd-networkd-wait-online masked" + SUMMARY+=("Boot hang: wait-online masked (NetworkManager stack)") + else + warn "skipped by request" + SUMMARY+=("Boot hang: 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: # -# boot-hang fix · user creation · +# 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