From 6408b028c375ae859fd520479dfcef51c206ad9c Mon Sep 17 00:00:00 2001 From: DeBondor <75030704+DeBondor@users.noreply.github.com> Date: Sun, 26 Jul 2026 01:45:38 +0200 Subject: [PATCH 1/5] fix: drop the double hyphen from the labwc-locked rc.xml comment The header comment mentioned singularity-shortcut's --serve mode. "--" is not allowed inside an XML comment, so libxml2 fails the whole file with "Double hyphen within comment" and labwc discards the locked config: the greeter and OOBE compositor run with labwc defaults, without the keybind lock this file exists to apply. --- rootfs-overlay/usr/share/singularity/labwc-locked/rc.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rootfs-overlay/usr/share/singularity/labwc-locked/rc.xml b/rootfs-overlay/usr/share/singularity/labwc-locked/rc.xml index bab7555..c28fc8e 100644 --- a/rootfs-overlay/usr/share/singularity/labwc-locked/rc.xml +++ b/rootfs-overlay/usr/share/singularity/labwc-locked/rc.xml @@ -18,7 +18,7 @@ cannot call the shell's dev.sinty.desktop directly: this compositor runs as the greeter user and the shell owns that name on the logged-in user's own session bus, which D-Bus refuses to a cross-uid peer. So they go through - /usr/bin/singularity-shortcut, whose per-session --serve half (started by + /usr/bin/singularity-shortcut, whose per-session serve half (started by singularity-desktop-session as the user) forwards over a group-owned socket. Pre-login no session is serving, so those Execute calls no-op and only the lock is in force. (Live per-user rebinds and xkb/theme still need the shell's From 26b025f48a4020ef704fb89d15a92a50b02a6217 Mon Sep 17 00:00:00 2001 From: DeBondor <75030704+DeBondor@users.noreply.github.com> Date: Sun, 26 Jul 2026 01:45:38 +0200 Subject: [PATCH 2/5] fix: keep the OOBE's runtime state out of the greeter's XDG_RUNTIME_DIR In client mode the OOBE runs as root and pointed XDG_RUNTIME_DIR at /run/singularity purely to reach the persistent compositor's socket. Every client library root loads then puts its runtime state there: at-spi, dconf, gvfs and gvfsd land as root-owned 0700 directories, and greeter-ready as root:root 0644, in a directory owned by the greeter (0770 greeter:singularity-session) that the greeter has to write once we hand over. The greeter runs as uid greeter and cannot rewrite any of it, so it dies on "can't create /run/singularity/greeter-ready: Permission denied" and greetd loops on "greeter exited without creating a session": a black screen after the OOBE completes. Keep XDG_RUNTIME_DIR at root's own /run/user/0 and reach the compositor through an absolute WAYLAND_DISPLAY, which libwayland uses verbatim instead of resolving against XDG_RUNTIME_DIR. greeter-ready is the one file that still has to be shared, so mark it with chmod as the own-labwc fallback already does. The fallback starts its own labwc, which binds its socket itself, so the inherited absolute path is unset there. --- rootfs-overlay/usr/bin/atom-oobe-session | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/rootfs-overlay/usr/bin/atom-oobe-session b/rootfs-overlay/usr/bin/atom-oobe-session index d48bd50..04361c1 100755 --- a/rootfs-overlay/usr/bin/atom-oobe-session +++ b/rootfs-overlay/usr/bin/atom-oobe-session @@ -18,8 +18,17 @@ export ATOM_OOBE_APPLY=1 # fine to /run/singularity (0770 greeter:singularity-session) since root bypasses the # mode. exec singularity-installer DIRECT (not atom-installer-launch): the persistent # compositor + boot-splash already paint the logo, so a second splash client just competes. -export XDG_RUNTIME_DIR=/run/singularity -export WAYLAND_DISPLAY=wayland-0 +# +# XDG_RUNTIME_DIR stays root's own (/run/user/0) and the compositor socket is reached +# through an absolute WAYLAND_DISPLAY, which libwayland uses verbatim. Pointing +# XDG_RUNTIME_DIR at /run/singularity instead makes every client library root writes +# its runtime state there (at-spi, dconf, gvfs, ...) as root-owned 0700 dirs, in a +# directory the greeter (uid greeter) owns and has to write after we exit: the greeter +# then dies on "can't create /run/singularity/greeter-ready: Permission denied" and +# greetd loops on "greeter exited without creating a session". +WAYLAND_SOCKET=/run/singularity/wayland-0 +export XDG_RUNTIME_DIR=/run/user/0 +export WAYLAND_DISPLAY="$WAYLAND_SOCKET" export HOME=/run/user/0 export XDG_CACHE_HOME=/run/user/0/.cache XDG_CONFIG_HOME=/run/user/0/.config mkdir -p "$XDG_CACHE_HOME" "$XDG_CONFIG_HOME" 2>/dev/null || true @@ -50,14 +59,15 @@ _try=0 _i=0 while [ -f /run/atom/installed ] && [ "$_try" -lt 5 ]; do _i=0 - while [ ! -S "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" ] && [ "$_i" -lt 300 ]; do + while [ ! -S "$WAYLAND_SOCKET" ] && [ "$_i" -lt 300 ]; do sleep 0.1 _i=$((_i + 1)) done - [ -S "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" ] || break + [ -S "$WAYLAND_SOCKET" ] || break _try=$((_try + 1)) sscommon_log "oobe: compositor socket up (${_i} ticks), running OOBE as client (attempt ${_try})" - sscommon_mark_greeter_ready + # chmod: the greeter rewrites this file after we hand over, and root created it. + sscommon_mark_greeter_ready chmod _rc=0 dbus-run-session -- singularity-installer --oobe || _rc=$? if [ -f "${MARKER}" ]; then @@ -75,6 +85,9 @@ fi # Fallback: no persistent compositor (e.g. a live/uninstalled boot where it is gated # off by ConditionPathExists) -> start our own labwc, as before. Never worse than today. sscommon_log "oobe: compositor socket absent (${_i} ticks), fallback own-labwc" +# Our own labwc binds its socket itself; an inherited absolute WAYLAND_DISPLAY would +# only mislead the clients it starts. +unset WAYLAND_DISPLAY export XDG_RUNTIME_DIR=/run/user/0 export HOME="$XDG_RUNTIME_DIR" sscommon_xdg_dirs From 512e23269988b5a17bfd95f6ef852d1fdac5ad65 Mon Sep 17 00:00:00 2001 From: DeBondor <75030704+DeBondor@users.noreply.github.com> Date: Sun, 26 Jul 2026 03:11:57 +0200 Subject: [PATCH 3/5] fix: reboot after the installer instead of leaving a bare compositor The installer's last page offers a button labelled "Restart now", but its handler is only `this.close ()`. atom-install-session ran labwc with -s, so the compositor outlived the window it was started for: the user pressed "Restart now" and got a black screen with a cursor, with nothing left to interact with and no way out short of a hard power cycle. Make the exit path explicit in the launcher, which is the only place that knows what should happen next: - atom-install drops /run/atom/install-complete. singularity-installer exits 0 both when the install finished and when the window was closed early, so exit status alone cannot tell the two apart. - atom-installer-launch stops exec'ing the installer and acts on its exit: reboot on a finished install (what the button promises), return on a finished OOBE (greetd moves on to the greeter), otherwise relaunch the wizard up to three times. - both session scripts run labwc with -S instead of -s, so the compositor terminates with the launcher and no clientless compositor is ever left on screen. Verified on a live boot: the "is ready" page stays up until the button is pressed, then the machine shuts down cleanly (stopping default.target, sync, rebooting) and comes back into the OOBE on the installed disk. --- rootfs-overlay/usr/bin/atom-install | 6 +++ rootfs-overlay/usr/bin/atom-install-session | 6 ++- rootfs-overlay/usr/bin/atom-installer-launch | 57 ++++++++++++++++++-- rootfs-overlay/usr/bin/atom-oobe-session | 4 +- 4 files changed, 66 insertions(+), 7 deletions(-) diff --git a/rootfs-overlay/usr/bin/atom-install b/rootfs-overlay/usr/bin/atom-install index c3d6796..93381bf 100755 --- a/rootfs-overlay/usr/bin/atom-install +++ b/rootfs-overlay/usr/bin/atom-install @@ -79,4 +79,10 @@ if [ -d /sys/firmware/efi ] && command -v efibootmgr >/dev/null 2>&1; then [ -n "$NUM" ] && efibootmgr -n "$NUM" >/dev/null 2>&1 || true fi +# Completion marker for atom-installer-launch: singularity-installer exits 0 both when +# the install finished and when the user just closed the window, so the launcher cannot +# tell "reboot into the new system" from "user bailed out" by exit status alone. +mkdir -p /run/atom 2>/dev/null || true +: > /run/atom/install-complete 2>/dev/null || true + log "done: $DEV provisioned" diff --git a/rootfs-overlay/usr/bin/atom-install-session b/rootfs-overlay/usr/bin/atom-install-session index 4cbf9b3..05ae224 100755 --- a/rootfs-overlay/usr/bin/atom-install-session +++ b/rootfs-overlay/usr/bin/atom-install-session @@ -26,4 +26,8 @@ sscommon_log "installer: DRM master free after ${_i} ticks (marker=$([ -e /run/s export LIBSEAT_BACKEND=builtin sscommon_log "LIBSEAT_BACKEND=builtin (installer only: seatd may be absent at install time)" -sscommon_exec_labwc -s "atom-installer-launch" +# -S, not -s: terminate the compositor when the startup command exits. With -s labwc +# outlives the installer and the user is left staring at a bare compositor after the +# final "Restart now" button (which only closes the window). atom-installer-launch +# reboots on a finished install, so -S only matters for the give-up path. +sscommon_exec_labwc -S "atom-installer-launch" diff --git a/rootfs-overlay/usr/bin/atom-installer-launch b/rootfs-overlay/usr/bin/atom-installer-launch index e029c21..3739f44 100755 --- a/rootfs-overlay/usr/bin/atom-installer-launch +++ b/rootfs-overlay/usr/bin/atom-installer-launch @@ -1,9 +1,56 @@ #!/bin/sh set -eu -if [ -x /usr/bin/singularity-splash ]; then - rm -f "${XDG_RUNTIME_DIR:-/run/user/0}/singularity-shell-ready" - singularity-splash >/dev/null 2>&1 & -fi +# Wrapper around singularity-installer for both the live disk installer +# (atom-install-session) and the OOBE own-labwc fallback (atom-oobe-session). +# +# It does NOT exec the installer: the installer's own last page ends with a button +# labelled "Restart now" / "Start using ..." that only closes the window, so whatever +# has to happen after it exits has to happen here. Without that, labwc stays up with +# no client left and the user is looking at a bare compositor: a black screen with a +# cursor. Both session scripts run labwc with -S (terminate on exit), so returning +# from here tears the session down. +# +# install mode, install finished -> reboot, which is what the button promises +# oobe mode, account provisioned -> return, greetd moves on to the greeter +# neither -> the user closed the wizard early or it crashed; +# relaunch it, bounded, then give up +MARKER_INSTALL=/run/atom/install-complete +MARKER_OOBE=/var/lib/sinty/.oobe-done -exec singularity-installer "$@" +MODE=install +for a in "$@"; do + [ "$a" = "--oobe" ] && MODE=oobe +done + +_try=0 +while [ "$_try" -lt 3 ]; do + _try=$((_try + 1)) + + if [ -x /usr/bin/singularity-splash ]; then + rm -f "${XDG_RUNTIME_DIR:-/run/user/0}/singularity-shell-ready" + singularity-splash >/dev/null 2>&1 & + fi + + _rc=0 + singularity-installer "$@" || _rc=$? + + if [ "$MODE" = oobe ]; then + if [ -f "$MARKER_OOBE" ]; then + echo "[installer-launch] oobe complete (rc=${_rc}), handing over" >&2 + exit 0 + fi + elif [ -f "$MARKER_INSTALL" ]; then + echo "[installer-launch] install complete (rc=${_rc}), rebooting" >&2 + # Give the compositor a moment to finish painting the last frame, else the + # reboot races the window close and the screen flickers to black first. + sleep 1 + exec reboot + fi + + echo "[installer-launch] installer exited rc=${_rc} without completing (attempt ${_try})" >&2 + sleep 1 +done + +echo "[installer-launch] giving up after ${_try} attempts" >&2 +exit 1 diff --git a/rootfs-overlay/usr/bin/atom-oobe-session b/rootfs-overlay/usr/bin/atom-oobe-session index 04361c1..edbaa21 100755 --- a/rootfs-overlay/usr/bin/atom-oobe-session +++ b/rootfs-overlay/usr/bin/atom-oobe-session @@ -94,4 +94,6 @@ sscommon_xdg_dirs sscommon_wait_splash 50 sscommon_mark_greeter_ready chmod sleep 0.3 -sscommon_exec_labwc -s "atom-installer-launch --oobe" +# -S: labwc terminates when the wizard exits, so greetd gets its session end and starts +# the greeter instead of leaving a clientless compositor on screen. +sscommon_exec_labwc -S "atom-installer-launch --oobe" From d7b079d71afb5dff1d124c10a5e741343ddb281c Mon Sep 17 00:00:00 2001 From: DeBondor <75030704+DeBondor@users.noreply.github.com> Date: Sun, 26 Jul 2026 13:05:37 +0200 Subject: [PATCH 4/5] build: do not abort packaging when fw-verify fails to build The cmd/fw-verify guard covers an AtomLoops checkout that has no such directory, but not one where the directory exists and the build fails (a mid-refactor HEAD, a dependency it cannot resolve offline). Under set -e the bare go build then aborts the whole run, after the erofs, the verity tree and the UKI are already done. build-initramfs.sh already treats a missing verifier as "no firmware verifier, base firmware only", so this is a degraded image rather than a broken build. Warn and carry on. --- scripts/package.sh | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/scripts/package.sh b/scripts/package.sh index 2623f9e..4959e5e 100755 --- a/scripts/package.sh +++ b/scripts/package.sh @@ -85,9 +85,21 @@ if [ -f "${FW_ROOT_PUB}" ]; then # mid-refactor); otherwise build it from source. if { [ -z "${FW_VERIFY_BIN}" ] || [ ! -x "${FW_VERIFY_BIN}" ]; } \ && [ -d "${ATOMLOOPS}/cmd/fw-verify" ]; then - ( cd "${ATOMLOOPS}" && CGO_ENABLED=0 "${ATOMLOOPS_GO:-go}" build -ldflags '-s -w' \ - -o "${REPO_DIR}/artifacts/fw-verify" ./cmd/fw-verify ) - FW_VERIFY_BIN="${REPO_DIR}/artifacts/fw-verify" + # Fail soft on a build error too, not only on an absent cmd/fw-verify. + # build-initramfs.sh already treats a missing verifier as "no firmware + # verifier, base firmware only", so an AtomLoops checkout whose fw-verify + # does not compile (a mid-refactor HEAD, a dependency it cannot resolve + # offline) is a degraded image, not a broken build -- but under set -e the + # bare go build aborts the whole run, after the erofs, the verity tree and + # the UKI are already done. + if ( cd "${ATOMLOOPS}" && CGO_ENABLED=0 "${ATOMLOOPS_GO:-go}" build -ldflags '-s -w' \ + -o "${REPO_DIR}/artifacts/fw-verify" ./cmd/fw-verify ); then + FW_VERIFY_BIN="${REPO_DIR}/artifacts/fw-verify" + else + echo "package: warn: cannot build fw-verify from ${ATOMLOOPS}" >&2 + echo "package: warn: shipping base survival firmware, no firmware add-on verification" >&2 + FW_VERIFY_BIN= + fi fi if [ -n "${FW_VERIFY_BIN}" ] && [ -x "${FW_VERIFY_BIN}" ]; then export FW_VERIFY_BIN FW_ROOT_PUB From db373a1628f1ed9427de9d759ceb0fa2a453c656 Mon Sep 17 00:00:00 2001 From: DeBondor <75030704+DeBondor@users.noreply.github.com> Date: Sun, 26 Jul 2026 13:07:39 +0200 Subject: [PATCH 5/5] build: bump gsettings-desktop-schemas to 48.0 for accent-color Buildroot 2026.02.2, which prepare.sh downloads, ships gsettings-desktop-schemas 45.0, whose org.gnome.desktop.interface has no accent-color key. That key landed in 47. singularity-shell writes it unconditionally on startup. main.vala:100 registers an Idle callback that calls update_accent_color(), which reads dev.sinty.desktop accent-color -- default 'blue', a named colour, so the "skip wallpaper-derived and custom colours" guard does not apply -- and then does, at main.vala:883: try { var iface = new GLib.Settings("org.gnome.desktop.interface"); iface.set_string("accent-color", color_name); } catch (GLib.Error e) { The catch cannot help. A key absent from the schema is a g_error inside GIO, not a returned GError, so the process dies rather than raising: $ gsettings list-keys org.gnome.desktop.interface | grep -c accent-color 0 $ python3 -c 'from gi.repository import Gio s = Gio.Settings.new("org.gnome.desktop.interface") try: s.set_string("accent-color", "blue") except Exception as e: print("caught", e)' GLib-GIO-ERROR **: Settings schema 'org.gnome.desktop.interface' does not contain a key named 'accent-color' $ echo $? 134 134 is SIGABRT. singularity-desktop dies on every shell start, the session supervisor gives up and greetd falls back to the greeter, which is a login loop. Written as buildroot-source-patches/0006-*.patch to match the patch set eec8a89 introduced. Hashes are the upstream sha256sum file for 48.0. Verified that it applies forward on a pristine 2026.02.2, that a reverse dry-run is rejected there, and that the reverse dry-run succeeds on an already-patched tree so prepare.sh reports "already applied" on a rerun. --- .../0006-gsettings-desktop-schemas-48.patch | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 buildroot-source-patches/0006-gsettings-desktop-schemas-48.patch diff --git a/buildroot-source-patches/0006-gsettings-desktop-schemas-48.patch b/buildroot-source-patches/0006-gsettings-desktop-schemas-48.patch new file mode 100644 index 0000000..b883a4a --- /dev/null +++ b/buildroot-source-patches/0006-gsettings-desktop-schemas-48.patch @@ -0,0 +1,14 @@ +diff --git a/package/gsettings-desktop-schemas/gsettings-desktop-schemas.mk b/package/gsettings-desktop-schemas/gsettings-desktop-schemas.mk +--- a/package/gsettings-desktop-schemas/gsettings-desktop-schemas.mk ++++ b/package/gsettings-desktop-schemas/gsettings-desktop-schemas.mk +@@ -7 +7 @@ +-GSETTINGS_DESKTOP_SCHEMAS_VERSION_MAJOR = 45 ++GSETTINGS_DESKTOP_SCHEMAS_VERSION_MAJOR = 48 +diff --git a/package/gsettings-desktop-schemas/gsettings-desktop-schemas.hash b/package/gsettings-desktop-schemas/gsettings-desktop-schemas.hash +--- a/package/gsettings-desktop-schemas/gsettings-desktop-schemas.hash ++++ b/package/gsettings-desktop-schemas/gsettings-desktop-schemas.hash +@@ -1,2 +1,2 @@ +-# From https://download.gnome.org/sources/gsettings-desktop-schemas/45/gsettings-desktop-schemas-45.0.sha256sum +-sha256 365c8d04daf79b38c8b3dc9626349a024f9e4befdd31fede74b42f7a9fbe0ae2 gsettings-desktop-schemas-45.0.tar.xz ++# From https://download.gnome.org/sources/gsettings-desktop-schemas/48/gsettings-desktop-schemas-48.0.sha256sum ++sha256 e68f155813bf18f865a8b2c8e9d473588b6ccadcafbb666ab788857c6c2d1bd3 gsettings-desktop-schemas-48.0.tar.xz