Watch
1
0
Fork
You've already forked souveraine
0

quickshell: converge live trees without root replacement

This commit is contained in:
Fimeg 2026-07-21 19:11:51 -04:00
commit 9c27504009
3 changed files with 75 additions and 16 deletions

View file

@ -158,7 +158,7 @@ if [[ "${1:-}" == "--manifest" ]]; then
fi
if [[ "${1:-}" == "--phone" ]]; then
ssh_i=(ssh -i "$HOME/.ssh/ani" -o ConnectTimeout=5)
ssh_i=(ssh -F /dev/null -i "$HOME/.ssh/ani" -o ConnectTimeout=5)
deployed_revision="unknown"
if repo_root="$(git -C "$SRC" rev-parse --show-toplevel 2>/dev/null)"; then
deployed_revision="$(git -C "$repo_root" rev-parse HEAD)"
@ -168,7 +168,7 @@ if [[ "${1:-}" == "--phone" ]]; then
fi
fi
"${ssh_i[@]}" "$PHONE_USB" "mkdir -p ~/$PHONE_DEST"
rsync -a --delete -e "ssh -i $HOME/.ssh/ani" "$SRC/" "$PHONE_USB:$PHONE_DEST/"
rsync -a --delete -e "ssh -F /dev/null -i $HOME/.ssh/ani" "$SRC/" "$PHONE_USB:$PHONE_DEST/"
printf '%s\n' "$deployed_revision" \
| "${ssh_i[@]}" "$PHONE_USB" "cat > ~/$PHONE_DEST/DEPLOYED-REVISION"
"${ssh_i[@]}" "$PHONE_USB" "bash ~/$PHONE_DEST/deploy.sh"
@ -201,17 +201,60 @@ if [[ "${1:-}" == "--legacy-clean" ]]; then
fi
# --- Sync the pinned ii base --------------------------------------------
# --exclude .git: the pin carries no VCS dirs, and any nested .git already
# on a device (e.g. widgets/shapes) is left alone rather than deleted.
# Build both trees away from their live paths, then converge the stable live
# directories with delayed renames. Quickshell's watcher follows the config
# directory inode and does not tolerate replacing that root, even atomically.
# The three-phase order keeps every symlink valid throughout the update:
# add/update ii files, switch the composed surface, then delete stale ii files.
mkdir -p "$QS"
rsync -a --delete --exclude='.git' "$SRC/ii-base/" "$II/"
clear_generated_tree() {
local path="$1"
case "$path" in
"$QS/.ii-previous"|"$QS/.souveraine-previous"|"$QS"/.ii-next.*|"$QS"/.souveraine-next.*)
[[ ! -e "$path" && ! -L "$path" ]] || find "$path" -depth -delete
;;
*)
echo "refusing to clear unexpected rollback path: $path" >&2
return 1
;;
esac
}
snapshot_tree() { # live rollback
local live="$1" rollback="$2"
clear_generated_tree "$rollback"
if [[ -e "$live" || -L "$live" ]]; then
mkdir -p "$rollback"
rsync -a --delete --exclude='.git' "$live/" "$rollback/"
fi
}
II_NEXT="$QS/.ii-next.$$"
SV_NEXT="$QS/.souveraine-next.$$"
[[ ! -e "$II_NEXT" && ! -e "$SV_NEXT" ]] || {
echo "staging path already exists; refusing: $II_NEXT or $SV_NEXT" >&2
exit 1
}
cleanup_staging() {
clear_generated_tree "$II_NEXT"
clear_generated_tree "$SV_NEXT"
}
trap cleanup_staging EXIT
mkdir -p "$II_NEXT"
rsync -a --delete --exclude='.git' "$SRC/ii-base/" "$II_NEXT/"
if [[ "$TARGET_ARCH" == "aarch64" ]]; then
rsync -a "$SRC/ii-phone/" "$II/"
echo "ii synced from pin + phone overlay"
rsync -a "$SRC/ii-phone/" "$II_NEXT/"
ii_description="pin + phone overlay"
else
echo "ii synced from pin"
ii_description="pin"
fi
install -m 0644 "$SRC/ii-base.pin" "$II/.souveraine-upstream-pin"
install -m 0644 "$SRC/ii-base.pin" "$II_NEXT/.souveraine-upstream-pin"
snapshot_tree "$II" "$QS/.ii-previous"
mkdir -p "$II"
# Phase 1: make every path the new composition can reference available, but
# retain old extras until their Souveraine override/symlink has switched.
rsync -a --delay-updates "$II_NEXT/" "$II/"
# --- Compose -------------------------------------------------------------
# Targets under souveraine/, relative to $SV
@ -226,8 +269,8 @@ is_touched() { # dir contains an override somewhere below
compose_dir() { # $1 = path relative to ii root ("" for root)
local rel="$1" entry name erel
mkdir -p "$SV${rel:+/$rel}"
for entry in "$II${rel:+/$rel}"/*; do
mkdir -p "$SV_NEXT${rel:+/$rel}"
for entry in "$II_NEXT${rel:+/$rel}"/*; do
[[ -e "$entry" ]] || continue
name="$(basename "$entry")"
[[ "$name" == *.upstream ]] && continue
@ -236,19 +279,18 @@ compose_dir() { # $1 = path relative to ii root ("" for root)
if is_touched "$erel"; then
compose_dir "$erel"
else
ln -sfn "$II/$erel" "$SV/$erel"
ln -sfn "$II/$erel" "$SV_NEXT/$erel"
fi
else
# root files and files inside touched dirs: link individually,
# skipping ones our manifest replaces
is_replaced "$erel" && continue
ln -sf "$II/$erel" "$SV/$erel"
ln -sf "$II/$erel" "$SV_NEXT/$erel"
fi
done
}
# Rebuild from scratch each run: cheap, and guarantees no stale links.
rm -rf "$SV"
# Build from scratch at the staging path: cheap, with no stale links.
compose_dir ""
# Our files on top (this also creates dirs that exist only in our tree,
@ -256,12 +298,25 @@ compose_dir ""
mkdir -p "$QS/pill"
while read -r rel target; do
[[ -z "$rel" ]] && continue
src="$SRC/$rel"; t="$QS/$target"
src="$SRC/$rel"; t="$SV_NEXT/${target#souveraine/}"
[[ -f "$src" ]] || { echo "missing $src" >&2; exit 1; }
mkdir -p "$(dirname "$t")"
ln -sf "$src" "$t"
done < <(manifest_lines)
snapshot_tree "$SV" "$QS/.souveraine-previous"
mkdir -p "$SV"
# Phase 2: switch the composed tree using delayed per-entry renames while its
# root directory remains stable for Quickshell's recursive watcher.
rsync -a --delete-delay --delay-updates "$SV_NEXT/" "$SV/"
# Phase 3: the composition no longer points at obsolete ii-only paths, so it
# is now safe to remove them and finish exact convergence with the pin.
rsync -a --delete-delay --delay-updates "$II_NEXT/" "$II/"
clear_generated_tree "$II_NEXT"
clear_generated_tree "$SV_NEXT"
trap - EXIT
echo "ii synced from $ii_description (rollback: $QS/.ii-previous)"
n_ours=$(manifest_lines | grep -c .)
n_borrowed_dirs=$(find "$SV" -maxdepth 3 -type l -xtype d | wc -l)
echo "souveraine config composed at $SV ($n_ours files ours, $n_borrowed_dirs dirs borrowed from ii)"

View file

@ -33,8 +33,10 @@ pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
import qs
import qs.services
import qs.modules.common
import qs.modules.common.functions
Singleton {
id: root

View file

@ -35,8 +35,10 @@ pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
import qs
import qs.services
import qs.modules.common
import qs.modules.common.functions
Singleton {
id: root