From dc3ffb68b764c354052f0140e535bc9012ce8f5e Mon Sep 17 00:00:00 2001 From: The_miro Date: Sat, 13 Jun 2026 22:13:26 +0200 Subject: [PATCH] fix(sysupdate): make hypr config sync atomic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hypr/ sync removed each live item with `rm -rf` before `cp`. For hyprland.lua this opened a window where the file was absent; if Hyprland's live config watcher reloaded during it, it errored ("cannot open hyprland.lua") and wrote out its fallback hyprland.conf stub, which then leaked into the repo. Stage each item into a temp dir on the same filesystem and `mv` it into place — for files this is an atomic rename(2), so the watcher never sees hyprland.lua missing. Directories are cleared first (mv can't replace a non-empty dir), but those aren't watched config files. Co-Authored-By: Claude Opus 4.8 --- sysupdate.sh | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/sysupdate.sh b/sysupdate.sh index 615a18b..9179bd4 100755 --- a/sysupdate.sh +++ b/sysupdate.sh @@ -616,15 +616,30 @@ sync_configs() { [[ "$skip" == true ]] && continue if [[ "$name" == "hypr" ]]; then - # Copy hypr/ contents but preserve ~/.config/hypr/usr/ + # Copy hypr/ contents but preserve ~/.config/hypr/usr/. + # Stage each item into a temp dir on the same filesystem, then mv it + # into place — for files this is an atomic rename(2), so Hyprland's + # live config watcher never sees hyprland.lua momentarily missing + # (which otherwise makes it emit its fallback hyprland.conf stub). mkdir -p "$target/hypr" local hypr_ok=true - while IFS= read -r -d '' hitem; do - local hname; hname="$(basename "$hitem")" - [[ "$hname" == "usr" ]] && continue - rm -rf "${target}/hypr/${hname}" && cp -r "$hitem" "$target/hypr/" \ - || hypr_ok=false - done < <(find "$item" -maxdepth 1 -mindepth 1 -print0 | sort -z) + local stage; stage="$(mktemp -d "${target}/hypr/.sync.XXXXXX")" || hypr_ok=false + if $hypr_ok; then + while IFS= read -r -d '' hitem; do + local hname; hname="$(basename "$hitem")" + [[ "$hname" == "usr" ]] && continue + if cp -r "$hitem" "$stage/"; then + # mv can't atomically replace a non-empty dir, so clear + # an existing directory target first; files swap atomically. + [[ -d "$stage/$hname" && -d "${target}/hypr/${hname}" ]] \ + && rm -rf "${target}/hypr/${hname}" + mv -f "$stage/$hname" "${target}/hypr/${hname}" || hypr_ok=false + else + hypr_ok=false + fi + done < <(find "$item" -maxdepth 1 -mindepth 1 -print0 | sort -z) + rm -rf "$stage" + fi if $hypr_ok; then ok "synced hypr ${DI}(usr/ preserved)${RS}" (( synced++ )) || true