Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,40 @@ month) from `config.mk`. A pre-release appends `-alpha.N`, `-beta.N` or
stamped. Nothing else about the install path changed; this is the
foundation the rest of Phase 6's update path (`lyona-update`) builds on.

- Add `lyona-update` (UPDATE-002, `docs/P6-UPDATE-HELPER.md`): a `check` /
`apply` / `rollback` / `backups` helper that lets an installed machine move
to a newer release and back again, on top of UPDATE-001's provenance
record. `check` compares the installed version against a `stable` or
`preview` GitHub release (calendar-version ordering, with a short-lived
cache so a panel indicator does not hammer the API) and reports `current`,
`behind`, `ahead`, `downgrade-offered`, `unknown`, or `offline` — never an
error for an unreachable network. `apply` downloads and SHA-256-verifies a
release tarball *before* unpacking it, builds unprivileged, backs up the
live install, then runs one confirmed privileged step
(`scripts/lyona-update-root`, installed via
`config/polkit/com.lyona.update.policy`) before verifying the result and
restamping provenance last — a build failure or a declined privileged step
costs nothing but time, never a half-applied system. `rollback` is the
missing half of `scripts/dev-sync-install.sh`'s existing backup machinery
(now reusable as a library via a `DEV_SYNC_INSTALL_LIB_ONLY` sourcing
guard that leaves its own direct-invocation behavior unchanged): it
refuses on any checksum or environment mismatch, and is designed to work
from a bare TTY with no desktop running by falling back from `pkexec` to
`sudo` when no agent is reachable — not yet exercised from an actual bare
TTY; that scenario is pending the disposable-VM verification pass in
`docs/P6-UPDATE-HELPER.md`. Channel and backup retention are configured in
`~/.config/lyona/update.conf`, seeded on first use and never overwritten.
The privileged step re-verifies the release tarball's checksum immediately
before use and then extracts, rebuilds, and installs from a scratch
directory the invoking user never has write access to, rather than running
a Makefile from a directory that was still writable by that user at the
moment root acted on it; `rollback`'s restore likewise validates every
backup archive member's path, type, and mode before extracting — refusing
anything outside the managed install locations, any non-regular member
(symlink, hardlink, device, FIFO, socket), and any setuid, setgid, or
sticky bit — rather than trusting GNU tar's own default root-extraction
behavior against a directory the invoking user could have replaced.

- Persist workspace, volume, Bluetooth, network, and power panel visibility in
one versioned user-owned state file shared by every monitor, Control Center,
and Settings. An absent file migrates from the prior implicit all-on state;
Expand Down
35 changes: 26 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ INSTALL_COMMANDS = \
scripts/active-audio \
scripts/dwm-accessibility-settings \
scripts/check-deps.sh \
scripts/dev-sync-install.sh \
scripts/disable-powersaving \
scripts/dwm-controlcenter \
scripts/dwm-default-apps \
Expand Down Expand Up @@ -81,6 +82,7 @@ INSTALL_COMMANDS = \
scripts/lyona-console-theme \
scripts/lyona-grub-theme \
scripts/lyona-plymouth-theme \
scripts/lyona-update \
scripts/lyona-version \
scripts/nvidia-gpu \
scripts/nvidia-suspend-test.sh \
Expand All @@ -94,9 +96,10 @@ INSTALL_COMMANDS = \
scripts/xdg-enable-autostart.sh \
scripts/xscreensaver-setup.sh
INSTALL_COMMAND_NAMES = $(notdir ${INSTALL_COMMANDS})
PRIVILEGED_HELPERS = scripts/dwm-settings-display-root
PRIVILEGED_HELPERS = scripts/dwm-settings-display-root scripts/lyona-update-root
PRIVILEGED_HELPER_DIR = ${PREFIX}/libexec/lyona
POLKIT_ACTIONS = config/polkit/com.lyona.settings-display.policy
POLKIT_ACTIONS = config/polkit/com.lyona.settings-display.policy \
config/polkit/com.lyona.update.policy
# polkit does not search PREFIX-relative paths; this is a fixed system path
# regardless of PREFIX.
POLKIT_ACTIONS_DIR = /usr/share/polkit-1/actions
Expand Down Expand Up @@ -207,7 +210,9 @@ install-system:
done
@echo "==> Installing privileged helpers..."
for f in ${PRIVILEGED_HELPERS}; do \
sed "s|@PREFIX@|${PREFIX}|g" "$$f" | \
sed -e "s|@PREFIX@|${PREFIX}|g" -e "s|@MANPREFIX@|${MANPREFIX}|g" \
-e "s|@DATADIR@|${DATADIR}|g" -e "s|@XSESSIONSDIR@|${XSESSIONSDIR}|g" \
"$$f" | \
install -Dm755 /dev/stdin ${DESTDIR}${PRIVILEGED_HELPER_DIR}/$$(basename "$$f"); \
done
@echo "==> Installing polkit actions..."
Expand Down Expand Up @@ -397,7 +402,9 @@ uninstall:
for name in ${INSTALL_COMMAND_NAMES}; do \
rm -f ${DESTDIR}${PREFIX}/bin/$$name; \
done
rm -f ${DESTDIR}${PRIVILEGED_HELPER_DIR}/dwm-settings-display-root
for name in $(notdir ${PRIVILEGED_HELPERS}); do \
rm -f ${DESTDIR}${PRIVILEGED_HELPER_DIR}/$$name; \
done
for name in $(notdir ${POLKIT_ACTIONS}); do \
rm -f ${DESTDIR}${POLKIT_ACTIONS_DIR}/$$name; \
done
Expand All @@ -420,10 +427,10 @@ release: dwm
echo "==> Created ${RELEASE_ARCHIVE}"

check-shell:
shellcheck install.sh scripts/dwm-accessibility-settings scripts/lyona-gtk-theme scripts/lyona-console-theme scripts/lyona-grub-theme scripts/lyona-plymouth-theme scripts/dwm-settings-toolkit scripts/dwm-session-launch scripts/dwm-default-apps scripts/dwm-diagnostics scripts/dwm-display-profile scripts/dwm-display-setup scripts/dwm-lock scripts/dwm-lock-watch scripts/dwm-keybinds scripts/dwm-panel-settings scripts/dwm-quickshell-launcher scripts/webapp-launch scripts/dwm-quickshell-controls scripts/dwm-quickshell-controlcenter scripts/dwm-quickshell-network scripts/dwm-quickshell-pointer scripts/dwm-quickshell-state scripts/dwm-quickshell-version-check scripts/dwm-settings scripts/dwm-settings-appearance scripts/dwm-settings-font scripts/dwm-settings-wallpaper scripts/dwm-settings-theme scripts/dwm-settings-provider scripts/dwm-status scripts/dwm-system-health scripts/dwm-terminal scripts/dwm-xdg-autostart scripts/install-herdr scripts/install-mybash scripts/lyona-cachyos scripts/lyona-version scripts/quickshell-qmllint scripts/run-tests scripts/*.sh tests/*.sh
shellcheck install.sh scripts/dwm-accessibility-settings scripts/lyona-gtk-theme scripts/lyona-console-theme scripts/lyona-grub-theme scripts/lyona-plymouth-theme scripts/dwm-settings-toolkit scripts/dwm-session-launch scripts/dwm-default-apps scripts/dwm-diagnostics scripts/dwm-display-profile scripts/dwm-display-setup scripts/dwm-lock scripts/dwm-lock-watch scripts/dwm-keybinds scripts/dwm-panel-settings scripts/dwm-quickshell-launcher scripts/webapp-launch scripts/dwm-quickshell-controls scripts/dwm-quickshell-controlcenter scripts/dwm-quickshell-network scripts/dwm-quickshell-pointer scripts/dwm-quickshell-state scripts/dwm-quickshell-version-check scripts/dwm-settings scripts/dwm-settings-appearance scripts/dwm-settings-font scripts/dwm-settings-wallpaper scripts/dwm-settings-theme scripts/dwm-settings-provider scripts/dwm-status scripts/dwm-system-health scripts/dwm-terminal scripts/dwm-xdg-autostart scripts/install-herdr scripts/install-mybash scripts/lyona-cachyos scripts/lyona-update scripts/lyona-update-root scripts/lyona-version scripts/quickshell-qmllint scripts/run-tests scripts/*.sh tests/*.sh

check-format:
shfmt -d install.sh scripts/dwm-accessibility-settings scripts/lyona-gtk-theme scripts/lyona-console-theme scripts/lyona-grub-theme scripts/lyona-plymouth-theme scripts/dwm-settings-toolkit scripts/dwm-session-launch scripts/dwm-default-apps scripts/dwm-diagnostics scripts/dwm-display-profile scripts/dwm-display-setup scripts/dwm-lock scripts/dwm-lock-watch scripts/dwm-keybinds scripts/dwm-panel-settings scripts/dwm-quickshell-launcher scripts/webapp-launch scripts/dwm-quickshell-controls scripts/dwm-quickshell-controlcenter scripts/dwm-quickshell-network scripts/dwm-quickshell-pointer scripts/dwm-quickshell-state scripts/dwm-quickshell-version-check scripts/dwm-settings scripts/dwm-settings-appearance scripts/dwm-settings-font scripts/dwm-settings-wallpaper scripts/dwm-settings-theme scripts/dwm-settings-provider scripts/dwm-status scripts/dwm-system-health scripts/dwm-terminal scripts/dwm-xdg-autostart scripts/install-herdr scripts/install-mybash scripts/lyona-cachyos scripts/lyona-version scripts/quickshell-qmllint scripts/run-tests scripts/*.sh tests/*.sh
shfmt -d install.sh scripts/dwm-accessibility-settings scripts/lyona-gtk-theme scripts/lyona-console-theme scripts/lyona-grub-theme scripts/lyona-plymouth-theme scripts/dwm-settings-toolkit scripts/dwm-session-launch scripts/dwm-default-apps scripts/dwm-diagnostics scripts/dwm-display-profile scripts/dwm-display-setup scripts/dwm-lock scripts/dwm-lock-watch scripts/dwm-keybinds scripts/dwm-panel-settings scripts/dwm-quickshell-launcher scripts/webapp-launch scripts/dwm-quickshell-controls scripts/dwm-quickshell-controlcenter scripts/dwm-quickshell-network scripts/dwm-quickshell-pointer scripts/dwm-quickshell-state scripts/dwm-quickshell-version-check scripts/dwm-settings scripts/dwm-settings-appearance scripts/dwm-settings-font scripts/dwm-settings-wallpaper scripts/dwm-settings-theme scripts/dwm-settings-provider scripts/dwm-status scripts/dwm-system-health scripts/dwm-terminal scripts/dwm-xdg-autostart scripts/install-herdr scripts/install-mybash scripts/lyona-cachyos scripts/lyona-update scripts/lyona-update-root scripts/lyona-version scripts/quickshell-qmllint scripts/run-tests scripts/*.sh tests/*.sh

check-session-guards:
tests/test-autostart.sh
Expand Down Expand Up @@ -632,13 +639,15 @@ check-install-manifest: all
printf '%s\n' \
pre-existing \
usr/bin/dwm \
usr/libexec/lyona/dwm-settings-display-root \
usr/share/man/man1/dwm.1 \
usr/share/xsessions/dwm.desktop \
etc/lyona-release; \
for name in ${INSTALL_COMMAND_NAMES}; do \
printf 'usr/bin/%s\n' "$$name"; \
done; \
for name in $(notdir ${PRIVILEGED_HELPERS}); do \
printf 'usr/libexec/lyona/%s\n' "$$name"; \
done; \
find "assets/cursors/${CAPITAINE_DARK_THEME}" \
\( -type f -o -type l \) \
-printf 'usr/share/icons/${CAPITAINE_DARK_THEME}/%P\n'; \
Expand All @@ -661,9 +670,13 @@ check-install-manifest: all
for name in dwm ${INSTALL_COMMAND_NAMES}; do \
test -x "$$stage/usr/bin/$$name"; \
done; \
test -x "$$stage/usr/libexec/lyona/dwm-settings-display-root"; \
for name in $(notdir ${PRIVILEGED_HELPERS}); do \
test -x "$$stage/usr/libexec/lyona/$$name"; \
done; \
grep -Fq 'org.freedesktop.policykit.exec.path">/usr/libexec/lyona/dwm-settings-display-root' \
"$$stage/usr/share/polkit-1/actions/com.lyona.settings-display.policy"; \
grep -Fq 'org.freedesktop.policykit.exec.path">/usr/libexec/lyona/lyona-update-root' \
"$$stage/usr/share/polkit-1/actions/com.lyona.update.policy"; \
grep -Fqx 'Exec=/usr/bin/dwm' \
"$$stage/usr/share/xsessions/dwm.desktop"; \
test -f "$$stage/usr/share/icons/${CAPITAINE_DARK_THEME}/cursors/default"; \
Expand All @@ -681,6 +694,9 @@ check-install-preservation:
check-lyona-version:
tests/test-lyona-version.sh

check-lyona-update:
tests/test-lyona-update.sh

check-test-runner:
@$(call run_managed_test,tests/test-run-tests.sh)

Expand Down Expand Up @@ -773,14 +789,15 @@ check:
$(MAKE) check-install
$(MAKE) check-install-preservation
$(MAKE) check-lyona-version
$(MAKE) check-lyona-update
$(MAKE) check-test-runner
$(MAKE) check-lightdm-config
$(MAKE) release-check

.PHONY: clean all check check-accessibility check-appearance check-build-config check-build-deps check-default-apps check-xdg-autostart check-dev-sync-install \
check-test-runner \
check-display-profile check-display-setup check-archiso check-arch-packages check-arch-platform check-format check-install \
check-gearlever-install check-herdr-install check-mybash-install check-install-manifest check-install-preservation check-lyona-version check-lock \
check-gearlever-install check-herdr-install check-mybash-install check-install-manifest check-install-preservation check-lyona-version check-lyona-update check-lock \
check-session-guards check-session-migration check-webapp-launch check-screenshot check-release-helper check-shell check-diagnostics check-status check-test-lib check-shell-contracts check-gtk-theme check-plymouth-theme check-grub-theme check-session-launch check-dwm-roundtrips check-system-health check-settings \
check-quickshell-launcher check-quickshell-controls check-quickshell-audio check-quickshell-controlcenter check-quickshell-power check-quickshell-power-backend check-quickshell-power-model check-quickshell-session-actions check-quickshell-defaults-model check-quickshell-appearance-model check-quickshell-design-system check-quickshell-large-surfaces check-quickshell-large-surfaces-xvfb check-quickshell-panel-menus check-quickshell-panel-settings check-quickshell-command-menu check-quickshell-notifications check-quickshell-tray check-quickshell-health-xvfb check-quickshell-settings-xvfb check-quickshell-network check-quickshell-connectivity check-quickshell-qml check-lightdm-config check-terminal check-xvfb-runtime install install-system install-user \
install-cursors install-grub-theme install-gtk-themes stamp-system stamp-user native release release-check uninstall
83 changes: 66 additions & 17 deletions TASKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,35 +72,84 @@ Acceptance:

### UPDATE-002: `lyona-update` Helper

- [ ] Ship `lyona-update check|apply|rollback`, staging to
`$XDG_STATE_HOME/lyona/updates/<version>/`, verifying a signed release
tarball's SHA-256 against the GitHub release asset digest before unpacking,
and never swapping the live tree in place (`Commands.helperCommand`
resolves helpers from the data dir of a *running* session).
- [ ] Reuse `scripts/dev-sync-install.sh`'s existing backup/verify machinery
- [x] Ship `lyona-update check|apply|rollback|backups`, staging to
`$XDG_STATE_HOME/lyona/updates/<version>/`, verifying a release tarball's
SHA-256 against the GitHub release asset digest before unpacking, and never
swapping the live tree in place. — **Met**, `scripts/lyona-update`,
`tests/test-lyona-update.sh`.
- [x] Reuse `scripts/dev-sync-install.sh`'s existing backup/verify machinery
(`backup_live_install()`, `verify_install()`, `verify_tree()`,
`runtime_verify()`) rather than rebuilding it, and add the missing restore
path so `rollback` actually reads a backup back — provably from a bare TTY
with no desktop running, per `docs/P6-UPDATE-HELPER.md`'s acceptance.
- [ ] One confirmed privileged step (the existing `${PREFIX}/libexec/lyona` +
`dwm-polkit` pattern) for `make install-system` alone; everything else —
check, download, verify, build, stage — runs unprivileged. Declining leaves
a staged, verified, uninstalled update and a non-zero exit, never a
half-applied system.
- [ ] `check`/`apply` support a channel (`stable`/`preview`) recorded in
`~/.config/lyona/update.conf`, seeded but never overwritten.
with no desktop running (falls back from `pkexec` to `sudo` when no
graphical session/agent is reachable). — **Met**: a
`DEV_SYNC_INSTALL_LIB_ONLY`/`DEV_SYNC_INSTALL_REPO_DIR` sourcing guard added
to `dev-sync-install.sh` (its own direct-invocation behavior unchanged,
`tests/test-dev-sync-install.sh`); `scripts/lyona-update-root`'s
`restore-system` verb accepts either `PKEXEC_UID` or `SUDO_UID`. The
power-loss-mid-install and bare-TTY scenarios themselves need the
disposable-VM pass in `docs/P6-UPDATE-HELPER.md`'s Verification section —
no root is available to exercise them in the automated suite.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
- [x] One confirmed privileged step (the existing `${PREFIX}/libexec/lyona`
polkit pattern) for `make install-system` alone; everything else — check,
download, verify, build, stage — runs unprivileged. Declining leaves a
staged, verified, uninstalled update and a non-zero exit, never a
half-applied system. — **Met**, `scripts/lyona-update-root`,
`config/polkit/com.lyona.update.policy`; the "declined" path is exercised
in `tests/test-lyona-update.sh` (no trusted root-owned helper exists in the
unprivileged test sandbox, which is itself the natural "unavailable" case).
Hardened after review: the privileged helper originally ran `make -C
<staging-dir> install-system` against a directory the invoking user could
still write to at that point — a Makefile/`config.mk` executes arbitrary
shell during GNU Make's own variable expansion (`$(shell ...)`), not only
through the recipe someone thinks they're invoking, so this was arbitrary
root code execution behind an "Install a lyona update" auth prompt. Fixed
by having `install-system release` re-verify the tarball's SHA-256
immediately before use, then extract, rebuild, and install from a fresh
root-owned-only scratch directory the invoking user has never had write
access to (closing the verify-then-mutate window down to nothing, and
ensuring the binary every user on the machine runs is one root itself
built from verified source, not a copy the invoking user could have
swapped after their own unprivileged build finished). `restore-system` had
the same shape (`tar -xpf` onto `/` from a manifest and checksum both
living in the same user-writable backup directory) and now validates every
archive member — path, type, and mode — before extracting: the path must
fall under a fixed set of managed locations (no `..` or absolute escape),
the type must be a regular file or directory (never a symlink, hardlink,
device, FIFO, or socket, any of which GNU tar preserves and creates by
default when run as root), and the mode must carry no setuid, setgid, or
sticky bit (a setuid-root `dwm` is a root shell for every user on the
machine, since dwm can spawn arbitrary configured commands). Verified
against a small harness covering a legitimate backup plus each rejected
shape (symlink, hardlink, setuid, FIFO, a nested path under a directory
that should only ever be flat, and a path outside every managed prefix) —
the legitimate case is accepted and every hostile shape is refused with a
specific reason. `--from-checkout` (`install-system checkout`) is
unaffected — it carries the same trust level as running `sudo make
install-system` directly from a developer's own checkout, not a weaker one
introduced by going through `lyona-update`.
- [x] `check`/`apply` support a channel (`stable`/`preview`) recorded in
`~/.config/lyona/update.conf`, seeded but never overwritten. — **Met**,
`tests/test-lyona-update.sh`.

Acceptance:

- An interrupted `apply` leaves a mixed tree recoverable by `rollback`, never
a silent claim of success — the provenance stamp from UPDATE-001 is written
last, after `rollback` re-verifies.
last, after `rollback` re-verifies. — Ordering is correct by construction
(backup before any write, stamp last, per the nine-step sequence in
`docs/P6-UPDATE-HELPER.md`); the actual power-loss/recovery run needs the
disposable-VM pass, not covered by the unprivileged automated suite.
- A downgrade or offline `check` degrades explicitly (`apply --file PATH`,
`--allow-downgrade`) rather than failing unhelpfully.
`--allow-downgrade`) rather than failing unhelpfully. — **Met**,
`tests/test-lyona-update.sh`.
- Preservation carries over unweakened: everything `tests/test-install-preservation.sh`
already guards (`config.h`, `~/.config/lyona/*.toml`, symlinked config
directories, settings-helper-owned files) survives an update the same way
it survives a fresh install.
it survives a fresh install. — The preservation machinery itself is reused
unmodified (`make install-user`, `dev-sync-install.sh`'s verify functions);
a full `apply`-driven end-to-end preservation run requires real privilege
and is part of the disposable-VM pass, not the automated suite.

### UPDATE-003: Settings and Control Center Surfaces

Expand Down
Loading