From 61861b78a836f797d514195fb0ec8bb43cdf9809 Mon Sep 17 00:00:00 2001 From: Allen Houchins Date: Thu, 30 Apr 2026 09:14:58 -0500 Subject: [PATCH 1/7] Drop sudo -i when launching fleet-desktop on Linux sudo -i runs the target user's shell as a login shell and wraps the rest of the command in `bash --login -c ''`, sourcing /etc/profile and /etc/profile.d/*. On openSUSE Leap 16, that indirection causes our `env KEY=val ... fleet-desktop` invocation to lose env vars, so fleet-desktop exits with "missing URL environment FLEET_DESKTOP_DEVICE_IDENTIFIER_PATH" and Orbit respawns it every 15s. We don't need a login shell here: -H sets HOME to the target user, sudo's default env_reset sets USER/LOGNAME/SHELL, and all session vars (WAYLAND_DISPLAY, DISPLAY, DBUS_SESSION_BUS_ADDRESS, LD_LIBRARY_PATH) plus FLEET_DESKTOP_* are already passed inline via env. Dropping -i makes sudo execve env directly without a shell layer in between. --- orbit/changes/fleet-desktop-linux-no-login-shell | 1 + orbit/pkg/execuser/execuser_linux.go | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 orbit/changes/fleet-desktop-linux-no-login-shell diff --git a/orbit/changes/fleet-desktop-linux-no-login-shell b/orbit/changes/fleet-desktop-linux-no-login-shell new file mode 100644 index 00000000000..78bbffae18e --- /dev/null +++ b/orbit/changes/fleet-desktop-linux-no-login-shell @@ -0,0 +1 @@ +* Fixed Fleet Desktop failing to start on openSUSE Leap 16 (and other distributions whose login-shell profile scripts interfere with the inline command), by dropping the `-i` (login shell) flag from the sudo invocation used to launch Fleet Desktop and key-escrow dialogs as the logged-in user. diff --git a/orbit/pkg/execuser/execuser_linux.go b/orbit/pkg/execuser/execuser_linux.go index e0b1282262c..ed39b78a5fc 100644 --- a/orbit/pkg/execuser/execuser_linux.go +++ b/orbit/pkg/execuser/execuser_linux.go @@ -175,7 +175,15 @@ func getConfigForCommand(user string, path string) (args []string, env []string, Str("session_type", userDisplaySession.Type.String()). Msg("running sudo") - args = []string{"-n", "-i", "-u", user, "-H"} + // We intentionally avoid -i (login shell). With -i, sudo runs the target user's + // shell as a login shell and passes the rest of the command via `bash --login -c`, + // which sources /etc/profile and /etc/profile.d/* and shell-escapes the inline + // command. On some distributions (e.g. openSUSE Leap 16) that environment + // indirection causes our `env KEY=val ... fleet-desktop` invocation to lose env + // vars, so fleet-desktop exits with "missing URL environment ..." and Orbit + // respawns it in a tight loop. -H sets HOME to the target user; sudo's default + // env_reset already sets USER/LOGNAME/SHELL. + args = []string{"-n", "-u", user, "-H"} env = make([]string, 0) if userDisplaySession.Type == userpkg.GuiSessionTypeWayland { From dad8efd55d6179b7e0d5222f1d1c28393b182f71 Mon Sep 17 00:00:00 2001 From: Allen Houchins Date: Thu, 30 Apr 2026 10:05:47 -0500 Subject: [PATCH 2/7] Fall back to upstream tarball when GNOME extension install can't deliver The install-fleet-desktop-required-extension.sh script relies on org.gnome.Shell.Extensions.InstallRemoteExtension, which fetches from extensions.gnome.org. On openSUSE Leap 16 there is no compatible build listed there and the extension never lands, so the original wait loop spins forever. Cap the wait at 90s, and on timeout fall back to fetching the upstream extension tarball directly from GitHub via curl + tar (both present in any Leap 16 / Fedora / Debian base install), extracting into the user's extensions directory, and compiling the gschemas. No new dependency on git, which isn't installed by default on Leap 16. --- ...nstall-fleet-desktop-required-extension.sh | 48 ++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/it-and-security/lib/linux/scripts/install-fleet-desktop-required-extension.sh b/it-and-security/lib/linux/scripts/install-fleet-desktop-required-extension.sh index 3def14e124b..5a6f30f6339 100755 --- a/it-and-security/lib/linux/scripts/install-fleet-desktop-required-extension.sh +++ b/it-and-security/lib/linux/scripts/install-fleet-desktop-required-extension.sh @@ -81,10 +81,56 @@ if [ "$extension_installed" = false ]; then "$extension_name" # Wait until the extension is accepted by the user ("gdbus call" command above is asynchronous). - while [ ! -d "/home/$username/.local/share/gnome-shell/extensions/$extension_name" ]; do + # Cap the wait so we don't hang forever if InstallRemoteExtension can't deliver — for + # example on openSUSE Leap 16, where extensions.gnome.org doesn't list a compatible + # build of this extension and the install never completes. + timeout=90 + while [ ! -d "$extension_path" ] && [ "$timeout" -gt 0 ]; do sleep 1 + timeout=$((timeout - 1)) done + # If the official install path didn't deliver, fall back to fetching the extension + # tarball from upstream. This is required on openSUSE Leap 16, which neither packages + # the extension via zypper nor serves a compatible build through extensions.gnome.org. + # We use curl + tar (both present on a base Leap/Fedora/Debian install) rather than + # git, which isn't installed by default on Leap 16. + if [ ! -d "$extension_path" ]; then + extensions_dir="/home/$username/.local/share/gnome-shell/extensions" + tarball_url="https://github.com/ubuntu/gnome-shell-extension-appindicator/archive/refs/heads/master.tar.gz" + tmp_dir=$(mktemp -d /tmp/fleet-appindicator.XXXXXX) + tarball="$tmp_dir/extension.tar.gz" + + # Download tarball — curl preferred, wget as fallback. Bail out cleanly if neither + # can fetch it (e.g. no network) so we don't leave a half-installed extension. + fetched=false + if command -v curl >/dev/null 2>&1; then + if curl -fsSL --max-time 60 -o "$tarball" "$tarball_url"; then + fetched=true + fi + elif command -v wget >/dev/null 2>&1; then + if wget -q --timeout=60 -O "$tarball" "$tarball_url"; then + fetched=true + fi + fi + + if [ "$fetched" = true ] && [ -s "$tarball" ]; then + sudo -u $username -H mkdir -p "$extensions_dir" + # Extract to a staging dir, then move the inner directory into place under + # the user's UUID-named extension path. Run as the user so file ownership is right. + staging="$tmp_dir/staging" + mkdir -p "$staging" + if tar -xzf "$tarball" -C "$staging" --strip-components=1; then + sudo -u $username -H cp -r "$staging" "$extension_path" + if [ -d "$extension_path/schemas" ] && command -v glib-compile-schemas >/dev/null 2>&1; then + sudo -u $username -H glib-compile-schemas "$extension_path/schemas/" + fi + fi + fi + + rm -rf "$tmp_dir" + fi + # Sleep to give some time for files to be downloaded. sleep 15 fi From dcab354409ff6fea46848cba5c03c7b7376b0ad5 Mon Sep 17 00:00:00 2001 From: Allen Houchins <32207388+allenhouchins@users.noreply.github.com> Date: Thu, 30 Apr 2026 10:18:21 -0500 Subject: [PATCH 3/7] Clean up comment in execuser_linux.go Removed redundant comment about respawning in a tight loop. --- orbit/pkg/execuser/execuser_linux.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/orbit/pkg/execuser/execuser_linux.go b/orbit/pkg/execuser/execuser_linux.go index ed39b78a5fc..6ead3566c65 100644 --- a/orbit/pkg/execuser/execuser_linux.go +++ b/orbit/pkg/execuser/execuser_linux.go @@ -181,8 +181,7 @@ func getConfigForCommand(user string, path string) (args []string, env []string, // command. On some distributions (e.g. openSUSE Leap 16) that environment // indirection causes our `env KEY=val ... fleet-desktop` invocation to lose env // vars, so fleet-desktop exits with "missing URL environment ..." and Orbit - // respawns it in a tight loop. -H sets HOME to the target user; sudo's default - // env_reset already sets USER/LOGNAME/SHELL. + // respawns it in a tight loop. args = []string{"-n", "-u", user, "-H"} env = make([]string, 0) From 13709b18cabd3ee1dd5e39932a675a9c0ef5ea71 Mon Sep 17 00:00:00 2001 From: Allen Houchins Date: Thu, 30 Apr 2026 21:19:27 -0500 Subject: [PATCH 4/7] Handle openSUSE Leap 16+ install/workaround Detect openSUSE Leap 16+ and apply distro-specific workarounds. In the install script, only cap the InstallRemoteExtension wait and fall back to downloading the extension tarball on openSUSE Leap 16+; other distros keep the indefinite wait behavior. In execuser_linux.go, add isOpenSUSELeap16Plus() and omit sudo -i for Leap 16+ (keep -i for other distros) to avoid losing environment variables under sudo on Leap 16. These changes address failures where extensions.gnome.org lacks a compatible build for Leap 16 and where sudo -i causes fleet-desktop to lose required env vars. --- ...nstall-fleet-desktop-required-extension.sh | 112 +++++++++++------- orbit/pkg/execuser/execuser_linux.go | 60 ++++++++-- 2 files changed, 121 insertions(+), 51 deletions(-) diff --git a/it-and-security/lib/linux/scripts/install-fleet-desktop-required-extension.sh b/it-and-security/lib/linux/scripts/install-fleet-desktop-required-extension.sh index 5a6f30f6339..93e63bec95d 100755 --- a/it-and-security/lib/linux/scripts/install-fleet-desktop-required-extension.sh +++ b/it-and-security/lib/linux/scripts/install-fleet-desktop-required-extension.sh @@ -29,9 +29,23 @@ if [ -f /etc/os-release ]; then . /etc/os-release distro_id="$ID" distro_name="$NAME" + distro_version_id="$VERSION_ID" else distro_id="unknown" distro_name="unknown" + distro_version_id="" +fi + +# Detect openSUSE Leap 16+. The capped wait + tarball fallback below is needed +# only on Leap 16, where extensions.gnome.org doesn't list a compatible build of +# this extension, so InstallRemoteExtension never completes. Other distros keep +# the previously QA'd "wait forever for the user to accept" behavior. +is_opensuse_leap_16_plus=false +if [ "$distro_id" = "opensuse-leap" ]; then + major_version="${distro_version_id%%.*}" + if [ -n "$major_version" ] && [ "$major_version" -ge 16 ] 2>/dev/null; then + is_opensuse_leap_16_plus=true + fi fi # Determine extension name and installation method based on distribution @@ -81,54 +95,66 @@ if [ "$extension_installed" = false ]; then "$extension_name" # Wait until the extension is accepted by the user ("gdbus call" command above is asynchronous). - # Cap the wait so we don't hang forever if InstallRemoteExtension can't deliver — for - # example on openSUSE Leap 16, where extensions.gnome.org doesn't list a compatible - # build of this extension and the install never completes. - timeout=90 - while [ ! -d "$extension_path" ] && [ "$timeout" -gt 0 ]; do - sleep 1 - timeout=$((timeout - 1)) - done - - # If the official install path didn't deliver, fall back to fetching the extension - # tarball from upstream. This is required on openSUSE Leap 16, which neither packages - # the extension via zypper nor serves a compatible build through extensions.gnome.org. - # We use curl + tar (both present on a base Leap/Fedora/Debian install) rather than - # git, which isn't installed by default on Leap 16. - if [ ! -d "$extension_path" ]; then - extensions_dir="/home/$username/.local/share/gnome-shell/extensions" - tarball_url="https://github.com/ubuntu/gnome-shell-extension-appindicator/archive/refs/heads/master.tar.gz" - tmp_dir=$(mktemp -d /tmp/fleet-appindicator.XXXXXX) - tarball="$tmp_dir/extension.tar.gz" - - # Download tarball — curl preferred, wget as fallback. Bail out cleanly if neither - # can fetch it (e.g. no network) so we don't leave a half-installed extension. - fetched=false - if command -v curl >/dev/null 2>&1; then - if curl -fsSL --max-time 60 -o "$tarball" "$tarball_url"; then - fetched=true - fi - elif command -v wget >/dev/null 2>&1; then - if wget -q --timeout=60 -O "$tarball" "$tarball_url"; then - fetched=true + if [ "$is_opensuse_leap_16_plus" = true ]; then + # Cap the wait on openSUSE Leap 16+ so we don't hang forever if + # InstallRemoteExtension can't deliver — extensions.gnome.org doesn't list + # a compatible build of this extension on Leap 16, so the install never + # completes there. + timeout=90 + while [ ! -d "$extension_path" ] && [ "$timeout" -gt 0 ]; do + sleep 1 + timeout=$((timeout - 1)) + done + + # If the official install path didn't deliver, fall back to fetching the + # extension tarball from upstream. This is required on openSUSE Leap 16, + # which neither packages the extension via zypper nor serves a compatible + # build through extensions.gnome.org. We use curl + tar (both present on a + # base Leap install) rather than git, which isn't installed by default on + # Leap 16. + if [ ! -d "$extension_path" ]; then + extensions_dir="/home/$username/.local/share/gnome-shell/extensions" + tarball_url="https://github.com/ubuntu/gnome-shell-extension-appindicator/archive/refs/heads/master.tar.gz" + tmp_dir=$(mktemp -d /tmp/fleet-appindicator.XXXXXX) + tarball="$tmp_dir/extension.tar.gz" + + # Download tarball — curl preferred, wget as fallback. Bail out cleanly + # if neither can fetch it (e.g. no network) so we don't leave a + # half-installed extension. + fetched=false + if command -v curl >/dev/null 2>&1; then + if curl -fsSL --max-time 60 -o "$tarball" "$tarball_url"; then + fetched=true + fi + elif command -v wget >/dev/null 2>&1; then + if wget -q --timeout=60 -O "$tarball" "$tarball_url"; then + fetched=true + fi fi - fi - if [ "$fetched" = true ] && [ -s "$tarball" ]; then - sudo -u $username -H mkdir -p "$extensions_dir" - # Extract to a staging dir, then move the inner directory into place under - # the user's UUID-named extension path. Run as the user so file ownership is right. - staging="$tmp_dir/staging" - mkdir -p "$staging" - if tar -xzf "$tarball" -C "$staging" --strip-components=1; then - sudo -u $username -H cp -r "$staging" "$extension_path" - if [ -d "$extension_path/schemas" ] && command -v glib-compile-schemas >/dev/null 2>&1; then - sudo -u $username -H glib-compile-schemas "$extension_path/schemas/" + if [ "$fetched" = true ] && [ -s "$tarball" ]; then + sudo -u $username -H mkdir -p "$extensions_dir" + # Extract to a staging dir, then move the inner directory into place + # under the user's UUID-named extension path. Run as the user so file + # ownership is right. + staging="$tmp_dir/staging" + mkdir -p "$staging" + if tar -xzf "$tarball" -C "$staging" --strip-components=1; then + sudo -u $username -H cp -r "$staging" "$extension_path" + if [ -d "$extension_path/schemas" ] && command -v glib-compile-schemas >/dev/null 2>&1; then + sudo -u $username -H glib-compile-schemas "$extension_path/schemas/" + fi fi fi - fi - rm -rf "$tmp_dir" + rm -rf "$tmp_dir" + fi + else + # Other distributions: preserve the previously QA'd behavior of waiting + # indefinitely for the user to accept the install prompt. + while [ ! -d "$extension_path" ]; do + sleep 1 + done fi # Sleep to give some time for files to be downloaded. diff --git a/orbit/pkg/execuser/execuser_linux.go b/orbit/pkg/execuser/execuser_linux.go index 6ead3566c65..333da5cc360 100644 --- a/orbit/pkg/execuser/execuser_linux.go +++ b/orbit/pkg/execuser/execuser_linux.go @@ -175,14 +175,22 @@ func getConfigForCommand(user string, path string) (args []string, env []string, Str("session_type", userDisplaySession.Type.String()). Msg("running sudo") - // We intentionally avoid -i (login shell). With -i, sudo runs the target user's - // shell as a login shell and passes the rest of the command via `bash --login -c`, - // which sources /etc/profile and /etc/profile.d/* and shell-escapes the inline - // command. On some distributions (e.g. openSUSE Leap 16) that environment - // indirection causes our `env KEY=val ... fleet-desktop` invocation to lose env - // vars, so fleet-desktop exits with "missing URL environment ..." and Orbit - // respawns it in a tight loop. - args = []string{"-n", "-u", user, "-H"} + // On openSUSE Leap 16+ we drop -i (login shell). With -i, sudo runs the target + // user's shell as a login shell and passes the rest of the command via + // `bash --login -c`, which sources /etc/profile and /etc/profile.d/* and + // shell-escapes the inline command. On Leap 16 that environment indirection + // causes our `env KEY=val ... fleet-desktop` invocation to lose env vars, so + // fleet-desktop exits with "missing URL environment ..." and Orbit respawns it + // in a tight loop. -H sets HOME to the target user; sudo's default env_reset + // already sets USER/LOGNAME/SHELL. + // + // We keep -i on every other supported distribution to preserve the previously + // QA'd behavior. + if isOpenSUSELeap16Plus() { + args = []string{"-n", "-u", user, "-H"} + } else { + args = []string{"-n", "-i", "-u", user, "-H"} + } env = make([]string, 0) if userDisplaySession.Type == userpkg.GuiSessionTypeWayland { @@ -207,6 +215,42 @@ func getConfigForCommand(user string, path string) (args []string, env []string, return args, env, nil } +// isOpenSUSELeap16Plus reports whether the host is running openSUSE Leap 16 or +// newer. We scope the no-login-shell sudo workaround to that distribution since +// it is the one observed to break under sudo -i; other distributions retain the +// previous (login-shell) launch path so we don't have to re-QA them. +func isOpenSUSELeap16Plus() bool { + data, err := os.ReadFile("/etc/os-release") + if err != nil { + return false + } + var id, versionID string + for _, line := range strings.Split(string(data), "\n") { + key, value, ok := strings.Cut(line, "=") + if !ok { + continue + } + // /etc/os-release values may be quoted. + value = strings.Trim(value, `"'`) + switch key { + case "ID": + id = value + case "VERSION_ID": + versionID = value + } + } + if id != "opensuse-leap" { + return false + } + // VERSION_ID is typically "16" or "16.0"; compare the major component. + major, _, _ := strings.Cut(versionID, ".") + n, err := strconv.Atoi(major) + if err != nil { + return false + } + return n >= 16 +} + // getUserWaylandDisplay returns the value to set on WAYLAND_DISPLAY for the given user. func getUserWaylandDisplay(uid string) (string, error) { matches, err := filepath.Glob("/run/user/" + uid + "/wayland-*") From 24fdbc8fe6f9785f0f89e6b88d701249a0f0edcc Mon Sep 17 00:00:00 2001 From: Allen Houchins Date: Thu, 30 Apr 2026 21:45:54 -0500 Subject: [PATCH 5/7] Update execuser_linux.go --- orbit/pkg/execuser/execuser_linux.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/orbit/pkg/execuser/execuser_linux.go b/orbit/pkg/execuser/execuser_linux.go index 333da5cc360..82679abf53c 100644 --- a/orbit/pkg/execuser/execuser_linux.go +++ b/orbit/pkg/execuser/execuser_linux.go @@ -225,7 +225,7 @@ func isOpenSUSELeap16Plus() bool { return false } var id, versionID string - for _, line := range strings.Split(string(data), "\n") { + for line := range strings.SplitSeq(string(data), "\n") { key, value, ok := strings.Cut(line, "=") if !ok { continue From cbdaebc6294143c30e67012c99d63ca03d800f6c Mon Sep 17 00:00:00 2001 From: Allen Houchins Date: Thu, 30 Apr 2026 23:03:10 -0500 Subject: [PATCH 6/7] Update install-fleet-desktop-required-extension.sh --- ...nstall-fleet-desktop-required-extension.sh | 190 +++++++++++------- 1 file changed, 122 insertions(+), 68 deletions(-) diff --git a/it-and-security/lib/linux/scripts/install-fleet-desktop-required-extension.sh b/it-and-security/lib/linux/scripts/install-fleet-desktop-required-extension.sh index 93e63bec95d..b2f5f1dd8e9 100755 --- a/it-and-security/lib/linux/scripts/install-fleet-desktop-required-extension.sh +++ b/it-and-security/lib/linux/scripts/install-fleet-desktop-required-extension.sh @@ -1,7 +1,8 @@ #!/bin/bash # Script assumes one user is using the desktop environment (no multi-session). -# It was tested on Fedora 38, 39, Debian 12, and OpenSUSE Leap/Tumbleweed. +# It was tested on Fedora 38, 39, Debian 12, openSUSE Leap 15/Tumbleweed, and +# openSUSE Leap 16. set -x @@ -36,10 +37,11 @@ else distro_version_id="" fi -# Detect openSUSE Leap 16+. The capped wait + tarball fallback below is needed -# only on Leap 16, where extensions.gnome.org doesn't list a compatible build of -# this extension, so InstallRemoteExtension never completes. Other distros keep -# the previously QA'd "wait forever for the user to accept" behavior. +# Detect openSUSE Leap 16+. On Leap 16 the GNOME extension install path differs +# from other distros: extensions.gnome.org doesn't list a compatible build of +# this extension, and `sudo -i` exec's are denied, so we install the extension +# directly from the upstream tarball and use a `sudo -u ... env` invocation +# instead of `sudo -i`. Other distros keep the previously QA'd behavior. is_opensuse_leap_16_plus=false if [ "$distro_id" = "opensuse-leap" ]; then major_version="${distro_version_id%%.*}" @@ -48,6 +50,22 @@ if [ "$distro_id" = "opensuse-leap" ]; then fi fi +# run_as_user runs a command as the GUI user with the session DBus address set. +# On openSUSE Leap 16+ we drop sudo's -i flag because, in that environment, +# `sudo -i` wraps the command in `bash --login -c ''` and the exec of +# /bin/bash is denied (the same root cause that breaks fleet-desktop launch). +# Other distros keep the previous "-i + DBUS=val command" form so we don't +# change behavior on already-QA'd platforms. +run_as_user() { + if [ "$is_opensuse_leap_16_plus" = true ]; then + sudo -u "$username" -H \ + env DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$uid/bus" "$@" + else + sudo -i -u "$username" -H \ + DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$uid/bus" "$@" + fi +} + # Determine extension name and installation method based on distribution case "$distro_id" in "opensuse-leap"|"opensuse-tumbleweed"|"opensuse") @@ -65,19 +83,24 @@ case "$distro_id" in ;; esac -# Check if the AppIndicator extension is already installed +# Check if the AppIndicator extension is already installed. We look for +# metadata.json (not just the directory) so that a half-baked stub left over +# from an earlier failed install — e.g. one where gnome-shell created the +# directory but bailed before writing metadata.json — is treated as +# "not installed" and gets re-installed properly. Without this, downstream +# `gnome-extensions enable` would fail with "Extension does not exist". extension_path="/home/$username/.local/share/gnome-shell/extensions/$extension_name" +extension_metadata="$extension_path/metadata.json" extension_installed=false -if [ -d "$extension_path" ]; then +if [ -f "$extension_metadata" ]; then extension_installed=true fi # If no extension is installed, install the appropriate one if [ "$extension_installed" = false ]; then # Show notification to user before the prompt. - sudo -i -u $username -H DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$uid/bus \ - gdbus call --session \ + run_as_user gdbus call --session \ --dest org.freedesktop.Notifications \ --object-path /org/freedesktop/Notifications \ --method org.freedesktop.Notifications.Notify \ @@ -86,83 +109,114 @@ if [ "$extension_installed" = false ]; then # Give some time to user to see notification. sleep 10 - # Use GNOME Extensions for all distributions (including OpenSUSE) - sudo -i -u $username -H DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$uid/bus \ - gdbus call --session \ - --dest org.gnome.Shell.Extensions \ - --object-path /org/gnome/Shell/Extensions \ - --method org.gnome.Shell.Extensions.InstallRemoteExtension \ - "$extension_name" - - # Wait until the extension is accepted by the user ("gdbus call" command above is asynchronous). if [ "$is_opensuse_leap_16_plus" = true ]; then - # Cap the wait on openSUSE Leap 16+ so we don't hang forever if - # InstallRemoteExtension can't deliver — extensions.gnome.org doesn't list - # a compatible build of this extension on Leap 16, so the install never - # completes there. - timeout=90 - while [ ! -d "$extension_path" ] && [ "$timeout" -gt 0 ]; do - sleep 1 - timeout=$((timeout - 1)) - done + # On openSUSE Leap 16+, skip the dbus InstallRemoteExtension call and go + # straight to the upstream tarball: extensions.gnome.org doesn't list a + # compatible build of this extension for Leap 16's GNOME, so the dbus + # call returns "Remote peer disconnected" immediately and at best leaves + # a half-baked stub directory behind. Skipping it avoids ~90s of + # dead-end waiting and a useless install prompt the user can't complete. + # + # Clear any stub directory left from a previous run so our copy below + # isn't laying files on top of a half-baked install. + if [ -d "$extension_path" ] && [ ! -f "$extension_metadata" ]; then + sudo rm -rf "$extension_path" + fi - # If the official install path didn't deliver, fall back to fetching the - # extension tarball from upstream. This is required on openSUSE Leap 16, - # which neither packages the extension via zypper nor serves a compatible - # build through extensions.gnome.org. We use curl + tar (both present on a - # base Leap install) rather than git, which isn't installed by default on - # Leap 16. - if [ ! -d "$extension_path" ]; then - extensions_dir="/home/$username/.local/share/gnome-shell/extensions" - tarball_url="https://github.com/ubuntu/gnome-shell-extension-appindicator/archive/refs/heads/master.tar.gz" - tmp_dir=$(mktemp -d /tmp/fleet-appindicator.XXXXXX) - tarball="$tmp_dir/extension.tar.gz" - - # Download tarball — curl preferred, wget as fallback. Bail out cleanly - # if neither can fetch it (e.g. no network) so we don't leave a - # half-installed extension. - fetched=false - if command -v curl >/dev/null 2>&1; then - if curl -fsSL --max-time 60 -o "$tarball" "$tarball_url"; then - fetched=true - fi - elif command -v wget >/dev/null 2>&1; then - if wget -q --timeout=60 -O "$tarball" "$tarball_url"; then - fetched=true - fi + extensions_dir="/home/$username/.local/share/gnome-shell/extensions" + tarball_url="https://github.com/ubuntu/gnome-shell-extension-appindicator/archive/refs/heads/master.tar.gz" + tmp_dir=$(mktemp -d /tmp/fleet-appindicator.XXXXXX) + tarball="$tmp_dir/extension.tar.gz" + + # Download tarball — curl preferred, wget as fallback. Bail out cleanly + # if neither can fetch it (e.g. no network) so we don't leave a + # half-installed extension. + fetched=false + if command -v curl >/dev/null 2>&1; then + if curl -fsSL --max-time 60 -o "$tarball" "$tarball_url"; then + fetched=true + fi + elif command -v wget >/dev/null 2>&1; then + if wget -q --timeout=60 -O "$tarball" "$tarball_url"; then + fetched=true + fi + fi + + if [ "$fetched" = true ] && [ -s "$tarball" ]; then + # tar isn't part of a minimal openSUSE Leap 16 install, so pull it + # in via zypper if it's missing. We're already running as root here + # (this whole branch runs from the root-detached script invocation + # at the top of this file). + if ! command -v tar >/dev/null 2>&1; then + zypper --non-interactive install --no-recommends tar \ + >/dev/null 2>&1 || true fi - if [ "$fetched" = true ] && [ -s "$tarball" ]; then + if command -v tar >/dev/null 2>&1; then sudo -u $username -H mkdir -p "$extensions_dir" - # Extract to a staging dir, then move the inner directory into place - # under the user's UUID-named extension path. Run as the user so file - # ownership is right. + # Extract into a staging dir under our root-owned tmp_dir, then + # copy the contents into the user's UUID-named extension path + # and hand ownership to the user. We do the copy as root rather + # than `sudo -u $username` because mktemp's tmp_dir is mode 700 + # owned by root, which the user can't traverse. staging="$tmp_dir/staging" mkdir -p "$staging" if tar -xzf "$tarball" -C "$staging" --strip-components=1; then - sudo -u $username -H cp -r "$staging" "$extension_path" + mkdir -p "$extension_path" + cp -r "$staging/." "$extension_path/" + chown -R "$username":"$username" "$extension_path" if [ -d "$extension_path/schemas" ] && command -v glib-compile-schemas >/dev/null 2>&1; then sudo -u $username -H glib-compile-schemas "$extension_path/schemas/" fi fi fi - - rm -rf "$tmp_dir" fi + + rm -rf "$tmp_dir" else - # Other distributions: preserve the previously QA'd behavior of waiting - # indefinitely for the user to accept the install prompt. - while [ ! -d "$extension_path" ]; do + # Other distributions: prompt the user via gnome-shell's + # InstallRemoteExtension and wait indefinitely for them to accept. + # This is the previously QA'd behavior on Fedora / Debian / Ubuntu / + # openSUSE Tumbleweed. + run_as_user gdbus call --session \ + --dest org.gnome.Shell.Extensions \ + --object-path /org/gnome/Shell/Extensions \ + --method org.gnome.Shell.Extensions.InstallRemoteExtension \ + "$extension_name" + + while [ ! -f "$extension_metadata" ]; do sleep 1 done - fi - # Sleep to give some time for files to be downloaded. - sleep 15 + # Give gnome-shell a moment to finish writing extension files after + # the directory shows up. Not needed on the Leap 16 path above, where + # we already wrote everything synchronously via curl + tar. + sleep 15 + fi fi -# Enable the extension -if [ -d "$extension_path" ]; then - sudo -i -u $username -H DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$uid/bus \ - gnome-extensions enable "$extension_name" +# Enable the extension. +if [ -f "$extension_metadata" ]; then + if [ "$is_opensuse_leap_16_plus" = true ]; then + # gnome-shell on Leap 16 (Wayland) doesn't rescan + # ~/.local/share/gnome-shell/extensions while running, so + # `gnome-extensions enable` (which talks to the live gnome-shell) + # reports the extension as missing. Pre-seed the dconf list directly; + # gnome-shell will pick it up and enable it on the user's next login. + current_extensions=$(run_as_user gsettings get org.gnome.shell enabled-extensions) + case "$current_extensions" in + *"'$extension_name'"*) + ;; + "@as []"|"[]") + run_as_user gsettings set org.gnome.shell enabled-extensions \ + "['$extension_name']" + ;; + *) + run_as_user gsettings set org.gnome.shell enabled-extensions \ + "${current_extensions%]}, '$extension_name']" + ;; + esac + else + run_as_user gnome-extensions enable "$extension_name" + fi fi From 6080dbcd74cc90d08c9c4382030d2e1a1c0cfba2 Mon Sep 17 00:00:00 2001 From: Allen Houchins <32207388+allenhouchins@users.noreply.github.com> Date: Fri, 1 May 2026 10:19:36 -0500 Subject: [PATCH 7/7] Clarify fix for Fleet Desktop startup on openSUSE Leap 16 Removed the mention of other distributions to focus on openSUSE Leap 16. --- orbit/changes/fleet-desktop-linux-no-login-shell | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/orbit/changes/fleet-desktop-linux-no-login-shell b/orbit/changes/fleet-desktop-linux-no-login-shell index 78bbffae18e..afbccc1db95 100644 --- a/orbit/changes/fleet-desktop-linux-no-login-shell +++ b/orbit/changes/fleet-desktop-linux-no-login-shell @@ -1 +1 @@ -* Fixed Fleet Desktop failing to start on openSUSE Leap 16 (and other distributions whose login-shell profile scripts interfere with the inline command), by dropping the `-i` (login shell) flag from the sudo invocation used to launch Fleet Desktop and key-escrow dialogs as the logged-in user. +* Fixed Fleet Desktop failing to start on openSUSE Leap 16, by dropping the `-i` (login shell) flag from the sudo invocation used to launch Fleet Desktop and key-escrow dialogs as the logged-in user.