Skip to content

Load the elevator in headless mode - #5545

Open
Gabriel Dufresne (GabrielDuf) wants to merge 3 commits into
mainfrom
fix/headless-elevator-5541
Open

Gabriel Dufresne (GabrielDuf) wants to merge 3 commits into
mainfrom
fix/headless-elevator-5541

Conversation

@GabrielDuf

@GabrielDuf Gabriel Dufresne (GabrielDuf) commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Root cause

HeadlessDaemonHost only prepared the environment, loaded the loaders and managers, and started the maintenance scheduler. The elevator was loaded only from AvaloniaBootstrapper's shared-services startup, which headless mode never runs. CoreData.ElevatorPath therefore stayed empty, and every operation that needed administrator rights stopped in AbstractProcessOperation with "the elevation tool could not be found" before winget ever started.

Fix

  • Elevator resolution moves out of AvaloniaBootstrapper into a new ElevatorLoader, so the GUI and headless hosts share one copy.
  • Headless now loads the elevator at the same time as the managers, before the scheduler and the IPC server start.

Running the loader on more hosts exposed two gaps in the loader itself:

  • macOS had no branch, so release builds set ElevatorPath to a nonexistent UniGetUI Elevator.exe. That skipped the empty-path guard and failed in process.Start with a raw Win32Exception. macOS now leaves the path empty and logs a warning.
  • Missing bundled elevator (quarantined, or a partial portable copy): ResolveBundledElevatorPath returned the expected path even when the file wasn't there, with the same effect. It now returns null. The loader then falls back to gsudo on PATH, as the TUI loader already does, and logs an error if that isn't found either.

Headless also skipped AutoUpdatesMigration.RunOnce, which the GUI runs after loading managers. A first-ever headless launch could therefore run scheduled updates without the migrated per-package auto-update preferences. It now runs the migration before starting the scheduler.

Verification

  • A/B on a Debug headless daemon: before the change, an elevated winget install logged FileName: "" and the error from the report. After it, the operation launched through gsudo.
  • Tests: new ElevatorLoaderTests cover the bundled-elevator resolution: next to the executable, in the parent directory, and missing. All 3 pass.
  • Builds and formatting: Debug and Release build clean. dotnet format whitespace and dotnet format style both pass.

Not covered

  • No automated test covers the headless startup sequence itself.
  • The TUI keeps its own elevator loader; merging it would change TUI behaviour, so it's left for a separate change.
  • I didn't verify on macOS or Linux, or test the Release elevator in headless mode.

Closes #5541

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The automatic gsudo fallback can execute an untrusted binary from the current working directory.

1 open issue
What changed in this PR

Fixes headless administrator operations by sharing elevator initialization with the GUI, addressing #5541.

Changes:

  • Loads the elevator and migrates update preferences before headless scheduling starts.
  • Handles missing bundled elevators and unsupported macOS elevation.
  • Adds three bundled-elevator path resolution tests.
File Description
src/​UniGetUI.Tests/​ElevatorLoaderTests.cs Tests local, parent-directory, and missing elevator resolution.
src/​UniGetUI.Avalonia/​Infrastructure/​HeadlessDaemonHost.cs Initializes elevation and migrates preferences before scheduling.
src/​UniGetUI.Avalonia/​Infrastructure/​ElevatorLoader.cs Extracts shared elevator loading and fallback handling.
src/​UniGetUI.Avalonia/​Infrastructure/​AvaloniaBootstrapper.cs Uses the shared loader instead of embedded logic.

Review effort: Balanced

Comment thread src/UniGetUI.Avalonia/Infrastructure/ElevatorLoader.cs Outdated
On Windows, CoreTools.Which searched Environment.CurrentDirectory before
any PATH entry, and blank, quoted-empty or relative PATH entries also
resolved against the current directory. Starting UniGetUI from a folder
holding a planted gsudo.exe, winget.exe, choco.exe or python.exe made
that file win over the real one. This was the root of the gsudo concern
on the elevator fallback, and it still applied to the ForceUserGSudo
opt-in and to every manager executable lookup. The search now covers only
fully qualified PATH entries.

The Windows elevator decision moves into ResolveWindowsElevatorPathAsync,
which takes the executable directory and the two gsudo switches as
inputs. The release no-fallback guarantee is no longer hidden behind
#if !DEBUG and is now covered by tests. The DEBUG branch also stops
logging "Using system GSudo" when gsudo is not installed.

The parent-directory lookup in ResolveBundledElevatorPath is kept: #4719
added it for the nested publish layout. Its parent is admin-only on a
Program Files install, and on a user-writable install whoever can plant
there can already replace UniGetUI.exe. The Linux sudo/pkexec/SSH_ASKPASS
resolution is unchanged for the same reason: PATH and environment are the
user's own, and a fixed /usr/bin path would break NixOS's
/run/wrappers/bin/sudo.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Privilege-sensitive startup changes need human validation because Release headless elevation and cross-platform runtime behavior remain unverified.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

@randy-but-a-ro randy-but-a-ro Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Pull request was approved automatically: the AI review is complete and all its review threads are resolved. 🎉

Integration Details
{
	"deliveryId": "693943c0-cbd1-11f1-9c09-9b4426c83208",
	"headSha": "1c418265ee482715292b41f406cdc9d306bce527",
	"reviewer": "copilot-pull-request-reviewer[bot]"
}

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Headless mode never loads the elevator, so admin updates fail with "the elevation tool could not be found"

2 participants