git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

commit3a07f8c3842a99e30d11626fa64a9334310fe66c
parent2a4b48f0d6
authorLucas Galante <[email protected]>
date2026-09-19 00:35
install-system: plan as the user, apply in one root batch

The old shape refused to run at all here. It guarded with `sudo -n true`,
and fingerprint sudo never caches a timestamp on this machine, so the
guard failed every time and printed "run this from a terminal" — which is
a large part of why the root-owned artifacts drifted weeks behind the rest
of the install.

Replace it with a plan built as the normal user (every target is
world-readable, so comparing needs no privilege) and applied by a single
`sudo bash -c`. One prompt however sudo authenticates, rather than one per
file as a chain of separate sudo calls would give. The plan runs under
`set -e`, so it is all-or-nothing: no half-applied update with one binary
replaced and the rest stale, which is exactly what the old guard was
there to prevent and did not.

--dry-run prints the root commands instead of running them, so what is
about to happen to /usr/bin and /etc/pam.d can be read first.

This was sitting uncommitted in the work tree. Landing it on its own so
the pkexec path that builds on it reads as a separate change.

Co-Authored-By: Claude Opus 5 <[email protected]>

 scripts/ccebuild | 66 +++++++++++++++++++++++++++++++++++++-------------------
 1 file changed, 44 insertions(+), 22 deletions(-)

diff --git a/scripts/ccebuild b/scripts/ccebuild
index b0edc4f..666839f 100755
--- a/scripts/ccebuild
+++ b/scripts/ccebuild
@@ -618,29 +618,39 @@ cmd_restart() {
 
 # The root-owned install paths. Separate command because it needs sudo, which is
 # exactly why these drifted three weeks behind everything else.
+#
+# Planned here as the normal user (every target is world-readable, so the
+# compare needs no privilege), then applied by ONE `sudo bash -c` call. One
+# prompt however sudo authenticates: fingerprint sudo never caches a timestamp
+# on this machine, so the former `sudo -n true` guard refused every run and a
+# chain of separate `sudo` calls would prompt once per file. A single script
+# under `set -e` is also all-or-nothing — no half-applied update.
 cmd_install_system() {
+    local dry=0
+    case "${1:-}" in
+        -n|--dry-run) dry=1 ;;
+        "") ;;
+        *) die "install-system takes only --dry-run" ;;
+    esac
     local stamp; stamp=$(date +%F)
-    # Fail early and legibly rather than dying mid-way through a partial update:
-    # under `set -e` a password prompt on a non-interactive run aborts after the
-    # first backup, leaving one binary replaced and the rest stale.
-    if ! sudo -n true 2>/dev/null; then
-        printf 'ccebuild: sudo needs a password — run this from a terminal:\n' >&2
-        printf '  sudo -v && ccebuild install-system\n' >&2
-        exit 1
-    fi
+    local plan="set -e"
+    # Append one root command to the plan, each word shell-quoted.
+    queue() { plan+=$'\n'"$(printf '%q ' "$@")"; }
+
     local pairs=(
         "cce-display-manager:/usr/bin/cce-display-manager"
     )
-    local entry bin dest
+    local entry bin dest src
     for entry in "${pairs[@]}"; do
         bin=${entry%%:*}; dest=${entry#*:}
-        [ -f "$WS/target/release/$bin" ] || die "$bin not built — run: ccebuild build"
+        src="$WS/target/release/$bin"
+        [ -f "$src" ] || die "$bin not built — run: ccebuild build"
         [ -e "$dest" ] || { printf 'ccebuild: %s absent, skipping\n' "$dest"; continue; }
-        if cmp -s "$WS/target/release/$bin" "$dest"; then continue; fi
+        if cmp -s "$src" "$dest"; then continue; fi
         printf '  %s -> %s\n' "$bin" "$dest"
-        sudo cp -a "$dest" "$dest.bak-$stamp"
+        queue cp -a "$dest" "$dest.bak-$stamp"
         # `install` unlinks first: safe even though these are running as root.
-        sudo install -m 755 "$WS/target/release/$bin" "$dest"
+        queue install -m 755 "$src" "$dest"
     done
 
     # System units and PAM stacks ship from the crates too (discovered, not
@@ -651,20 +661,30 @@ cmd_install_system() {
         dest="/etc/systemd/system/$(basename "$file")"
         if [ -e "$dest" ] && cmp -s "$file" "$dest"; then continue; fi
         printf '  %s -> %s\n' "$(basename "$file")" "$dest"
-        [ -e "$dest" ] && sudo cp -a "$dest" "$dest.bak-$stamp"
-        sudo install -m 644 "$file" "$dest"
+        if [ -e "$dest" ]; then queue cp -a "$dest" "$dest.bak-$stamp"; fi
+        queue install -m 644 "$file" "$dest"
         reload=1
     done < <(system_units)
-    if [ "$reload" -eq 1 ]; then sudo systemctl daemon-reload; fi
+    if [ "$reload" -eq 1 ]; then queue systemctl daemon-reload; fi
 
     while read -r file; do
         dest="/etc/pam.d/$(basename "$file")"
         if [ -e "$dest" ] && cmp -s "$file" "$dest"; then continue; fi
         printf '  %s -> %s\n' "$(basename "$file")" "$dest"
-        [ -e "$dest" ] && sudo cp -a "$dest" "$dest.bak-$stamp"
-        sudo install -m 644 "$file" "$dest"
+        if [ -e "$dest" ]; then queue cp -a "$dest" "$dest.bak-$stamp"; fi
+        queue install -m 644 "$file" "$dest"
     done < <(pam_files)
 
+    if [ "$plan" = "set -e" ]; then
+        printf '==> system artifacts already up to date\n'
+        return 0
+    fi
+    if [ "$dry" -eq 1 ]; then
+        printf '==> dry run; would run as root:\n%s\n' "${plan#set -e}"
+        return 0
+    fi
+    printf '==> applying as root (one sudo prompt)\n'
+    sudo bash -c "$plan"
     printf '==> system artifacts updated (binaries take effect at next login; nothing restarted)\n'
 }
 
@@ -684,9 +704,11 @@ usage: ccebuild <command>
                         sessions are working). Never the compositor.
   prune [--apply]       delete target/ artifacts of crates cargo no longer
                         knows about (renamed/retired); dry-run by default
-  install-system        update the root-owned artifacts: binaries, system
-                        units, /etc/pam.d stacks (needs sudo); backs up each
-                        changed file and restarts nothing
+  install-system [--dry-run]
+                        update the root-owned artifacts: binaries, system
+                        units, /etc/pam.d stacks — one sudo prompt for the
+                        whole batch; backs up each changed file and restarts
+                        nothing (--dry-run: print the root commands only)
 
 environment:
   CCE_WORKSPACE  workspace root (default: located from the current directory)
@@ -703,7 +725,7 @@ case "${1:-}" in
     status)         cmd_status ;;
     restart)        shift; cmd_restart "$@" ;;
     prune)          shift; cmd_prune "$@" ;;
-    install-system) cmd_install_system ;;
+    install-system) shift; cmd_install_system "$@" ;;
     -h|--help|help|"") usage ;;
     *) die "unknown command '$1' (try: ccebuild --help)" ;;
 esac