diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index c4e7510..4cd30a3 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -19,4 +19,14 @@ jobs: - name: Install selected Rust run: rustup toolchain install "${{ matrix.rust }}" --profile minimal --component clippy --component rustfmt - name: Validate owned code - run: sh scripts/validate.sh + run: | + # App Center fails closed when a same-user process cannot be inspected. + # Exclude unrelated runner processes while keeping all fixture processes + # visible. Use the unprivileged runner user and private fixture modes. + sudo unshare --pid --fork --mount-proc --kill-child \ + sudo --user="$(id -un)" -- \ + env HOME="$HOME" PATH="$PATH" \ + RUSTUP_HOME="${RUSTUP_HOME:-$HOME/.rustup}" \ + CARGO_HOME="${CARGO_HOME:-$HOME/.cargo}" \ + RUSTUP_TOOLCHAIN="$RUSTUP_TOOLCHAIN" \ + sh -c 'umask 077; exec sh scripts/validate.sh' diff --git a/README.md b/README.md index 85443f8..16749c1 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,85 @@ # Vitrallis Shell -**Small screens. Big possibilities.** - -A compact Rust + SDL2 launcher for small Linux screens. Open a terminal, jot down a note, browse your files, and make a small screen feel useful again. Vitrallis runs inside your existing desktop session. +A compact Rust + SDL2 launcher for small Linux screens, built around PocketCHIP +and its 480×272 display. Open a terminal, take notes, browse files, and return to +your running apps with the Home key. Vitrallis runs inside the existing desktop +session, with controls for both keyboard and touch. [![Validate](https://github.com/csd113/Vitrallis-Shell/actions/workflows/validate.yml/badge.svg)](https://github.com/csd113/Vitrallis-Shell/actions/workflows/validate.yml) [![Releases](https://img.shields.io/github/v/release/csd113/Vitrallis-Shell?include_prereleases&label=release)](https://github.com/csd113/Vitrallis-Shell/releases) -![Vitrallis at 480×272: Terminal, Notepad, Files and App Center, with Terminal selected](docs/images/shell-480x272.png) +![Vitrallis launcher with Terminal selected, alongside Notepad, Files, System Settings and App Center](docs/images/launcher-480x272.png) + +*Launcher · Vitrallis 1.0.4 on a physical PocketCHIP, 480×272; live battery and Wi-Fi status.* + +For PocketCHIP owners and small-screen Linux tinkerers who want a readable, +keyboard-friendly starting point for everyday tools. **The project is still +pre-release:** check the platform and hardware-validation limits below before +installing. + +## What you can do + +- **Use three offline native apps:** Terminal runs a real PTY with ANSI colors and scrollback; Notepad opens, edits, finds and saves plain text; Files browses, copies, moves, renames and deletes with confirmations. +- **Keep your tools close:** return Home to background an app, then select its tile to resume it. Organize tiles in folders or add shortcuts for ordinary Linux programs and scripts. +- **Browse App Center:** search and filter GitHub catalogs, read package details and release notes, then install, update or remove apps. Source trust checks, checksums and transaction recovery protect managed installs and local edits. +- **Tune the session:** Settings includes display and sound, date and time, wireless and Tor, background-app policy, storage, device controls, software updates and About. Device controls depend on the supported platform and available utilities. +- **Update the complete build:** Software Updates checks for compatible releases and updates the shell and three native apps together with the shared Arti executable. A verified retained build can be restored from the same screen. + +## A look inside + +Each image is an unaltered **480×272** display capture from a physical PocketCHIP +running the current 1.0.4 build, with isolated sample files. This short screenshot +session is not a full release certification. [Capture details](docs/images/README.md) +record the build, method and limits. + +**Terminal** — run shell commands, with ANSI colors and bounded scrollback. + +![Terminal running a Bash PTY, listing sample folders and reading a to-do text file](docs/images/terminal-480x272.png) + +*Terminal · live Bash session, physical PocketCHIP.* + +**Notepad** — a small plain-text editor with save, find and unsaved-change protection. + +![Notepad editing a sample weekend checklist, with New, Open, Save, Save as, Find and Close controls](docs/images/notepad-480x272.png) + +*Notepad · sample notes, physical PocketCHIP.* + +**Files** — browse folders and open text in Notepad; file actions live in the menu. -*Current desktop build rendered at 480×272; hardware status is unavailable.* +![Files showing sample Notes and Projects folders plus notes.txt and todo.txt](docs/images/files-480x272.png) -- **Three native essentials:** Terminal with a real PTY, a text-editing Notepad, and Files for browsing and everyday file operations. All ship with the shell and work offline. -- **Room to explore:** App Center checks GitHub catalogs and installs selected manifest packages, with source trust, checksums, and local-edit protection. -- **Keys or touch:** visible selection and shared activation across native controls, with confirmations for destructive actions. -- **Optional system integration:** brightness, volume, status, Wi-Fi utility access, and a supervised session that restores the original Home binding on exit. -- **Whole-build updates:** System Settings updates the shell and all three native utilities together when a compatible newer release is available. -- **Compact Settings:** display and sound, clock format and time zone, wireless and Tor, background-app policy, storage, device controls, software updates and About. A verified retained build can be restored from Software Updates. +*Files · isolated sample Documents folder, physical PocketCHIP.* + +**Settings** — large category buttons with visible keyboard selection. + +![System Settings overview with Display and Sound, Date and Time, Wireless Network, Applications, Storage, Device, Software Updates and About](docs/images/settings-480x272.png) + +*Settings · physical PocketCHIP; category overview.* + +**App Center** — search, filter and manage packages from configured sources. + +![App Center before its first refresh, with search, filter, sources and package controls](docs/images/app-center-480x272.png) + +*App Center · before first refresh in an isolated profile, physical PocketCHIP; no third-party apps shown.* ## Installation -**[Vitrallis Shell 1.0.4 reliability hardening release](https://github.com/csd113/Vitrallis-Shell/releases/tag/v1.0.4)** -hardens App Center updates, process cleanup, file operations and input boundaries, -with permanent strict Rust 1.99 lint enforcement. Host and Linux simulator checks -pass; see the [changelog](docs/releases.md#104--reliability-hardening-release) -and [hardening report](docs/rust-lint-hardening.md). This release has no new physical -PocketCHIP certification. The earlier 1.0.3 installation, cold-start, keyboard, -touch and audible-audio results remain qualified to that candidate in the -[readiness report](docs/release-readiness-2026-10-02.md) and -[certification record](docs/release-certification-2026-10-02.md#final-public-candidate). -Install the complete bundle containing the shell, Terminal, Notepad, -Files and the shared Arti executable. -See [device installation and recovery](docs/devices/pocketchip.md) for supported -OS/runtime requirements, the single copy-and-paste setup command and hardware -validation limits. Setup prepares missing Debian packages and installs a verified -complete bundle with all five executables. Run it as your normal desktop user; -sudo is used for package and platform preparation. - -Run this as your normal user on a supported PocketCHIP (requires `curl` and -working HTTPS certificates): +The current published bundle is [Vitrallis Shell 1.0.4](https://github.com/csd113/Vitrallis-Shell/releases/tag/v1.0.4). +It contains the shell, Terminal, Notepad, Files and the shared Arti executable. +See the [changelog](docs/releases.md#104--reliability-hardening-release) for the +reliability hardening in this release. + +**PocketCHIP setup requires Debian 12 or 13 ARMv7 hard-float (`armhf`), the existing +Awesome 4/PocketHome desktop, a systemd user session and sudo access. Original +Jessie is unsupported.** Keep at least 128 MiB free on the system, home and +temporary filesystems; missing packages may need more. Save your work and exit +any running Vitrallis session before setup. + +Run the command below as your **normal desktop user**, in Terminal or SSH, +with `curl`, working HTTPS certificates and access to GitHub and Debian repositories. +It prepares missing Debian packages and platform integration with sudo, then +verifies and installs the complete bundle as your user. Read the +[device installation and recovery guide](docs/devices/pocketchip.md) for the +prerequisites, system changes and recovery procedure. ```sh @@ -45,12 +87,12 @@ working HTTPS certificates): ``` Setup replaces PocketHome's launch command with Vitrallis at login. PocketHome -does not run behind the Shell. Uninstall restores its original launch command; +does not run behind the shell. Uninstall restores its original launch command; SSH and serial login remain available for recovery. ## First launch and controls -Reboot after setup to start Vitrallis in place of PocketHome. Vitrallis starts +Reboot after setup to start Vitrallis in place of PocketHome. It starts automatically at subsequent desktop logins. For later launches within that session, use `~/.local/share/vitrallis/launch` or the Home key. @@ -60,43 +102,63 @@ session, use `~/.local/share/vitrallis/launch` or the Home key. | Open or resume it | Enter, click, or tap | | Change page | Page Up / Page Down or header arrows | | Return from an app | Home in a supervised session | -| Leave Vitrallis for Awesome | Select **Exit Vitrallis** in a supervised session | -| Update the native build | System Settings → Software Updates → Check for Updates | - -Returning Home backgrounds an app. Apps stay open by default; Settings → Applications can set an automatic background timeout or exempt individual apps. Save and close them before stopping or removing the session. Native utility menus and dialogs have visible keyboard focus; see [Terminal, Notepad and Files controls](docs/native-apps.md). App Center has [its own navigation and package guide](docs/app-center.md). - -Use **Manage [F10] → Add shortcut** (F2) to launch ordinary Linux programs or scripts without an -App Center package. See [desktop shortcuts](docs/desktop-shortcuts.md) for command -quoting, terminal mode, icons, editing, and removal rules. - -## Compatibility and beta limits - -Linux release packaging targets x86-64 and ARMv7, with glibc 2.36+ and SDL2 -2.26.5+. macOS is a development host; Linux artifacts cannot install there. -The shell uses SDL hardware acceleration when available, with automatic software -fallback and a GLES2 compatibility floor. See [renderer selection and diagnostics](docs/shell.md#sdl-renderer-selection) -for overrides and GPU validation limits. Hardware support requires a matching adapter and validation. A matching screen -size alone is not support. See the [device guide](docs/devices/pocketchip.md) for -recorded evidence and checks still requiring hardware. - -Apps run with your user's permissions: **Vitrallis is not an app sandbox**. Catalog availability and runtime dependencies belong to each publisher. Publisher-disabled packages appear as **Unavailable** with their compatibility note. Fonts do not provide full Unicode shaping; Bluetooth hardware qualification, a public Python SDK, and signed publisher packages are future work. See the [trust model](docs/security.md) and [design roadmap](docs/design.md). +| Manage shortcuts and folders | **Manage [F10]**; F2 adds a shortcut | +| Navigate native dialogs | Tab or Left/Right, then Enter; Escape cancels | +| Leave Vitrallis for Awesome | **Exit Vitrallis** in a supervised session | +| Update or restore the native build | System Settings → Software Updates | + +Returning Home backgrounds an app. Apps stay open by default; **Settings → +Applications** can set an automatic background timeout or exempt individual apps. +Save and close them before stopping or removing the session. Destructive dialogs +start on Cancel. Native text entry uses a physical keyboard; these apps do not +supply an on-screen typing keyboard. + +See [Terminal, Notepad and Files controls](docs/native-apps.md), +[App Center navigation](docs/app-center.md), and +[desktop shortcuts](docs/desktop-shortcuts.md) for app-specific keys, command +quoting, terminal mode, icons and editing rules. + +## Platforms and current limits + +- **Linux builds:** release bundles target x86-64 and ARMv7, with glibc 2.36+ and SDL2 2.26.5+. The one-line installer above is specifically for the supported PocketCHIP desktop, not arbitrary Linux machines. +- **Development hosts:** macOS can build and preview the shell and native apps; Linux artifacts cannot install there. Desktop/simulator captures establish software behavior, not device support. +- **Rendering:** SDL hardware acceleration is used when available, with automatic software fallback and a GLES2 compatibility floor. A matching screen size or GPU API alone does not establish hardware support. See [renderer selection](docs/shell.md#sdl-renderer-selection) and [hardware acceleration evidence](docs/hardware-acceleration.md). +- **Physical validation:** 1.0.4 has host and Linux simulator checks plus the limited physical screenshot session above, but no new full PocketCHIP certification. Earlier 1.0.3 installation, cold-start, keyboard, touch and audible-audio results apply to that candidate; see the [readiness report](docs/release-readiness-2026-10-02.md) and [certification record](docs/release-certification-2026-10-02.md#final-public-candidate). The [device guide](docs/devices/pocketchip.md) records OS/runtime boundaries and outstanding hardware checks. +- **App and editor limits:** catalog availability and runtime dependencies belong to each publisher; disabled packages show **Unavailable** with a compatibility note. Notepad handles UTF-8 text up to 1 MiB and has no undo/redo or syntax highlighting. Bitmap fonts do not provide full Unicode shaping. Bluetooth hardware qualification, a public Python SDK and signed publisher packages remain future work. + +**Vitrallis is not an app sandbox.** Apps run with your user's permissions; +checksums verify catalog content, not an independent publisher signature. Review +sources before installing. See the [trust model](docs/security.md), +[security reporting policy](SECURITY.md) and [design roadmap](docs/design.md). ## Documentation and development -Start with the [documentation index](docs/README.md), [device guide](docs/devices/pocketchip.md), or [app developer guide](docs/app-development.md). +Start with the [documentation index](docs/README.md), +[device guide](docs/devices/pocketchip.md), or +[app developer guide](docs/app-development.md). +Report bugs and suggestions through the [issue forms](https://github.com/csd113/Vitrallis-Shell/issues/new/choose); +use [SECURITY.md](SECURITY.md) for security concerns. -Host development requires the pinned Rust 1.99.0 toolchain, SDL2 development libraries, and pkg-config. CI also validates latest stable; release pins advance after the complete validation gates pass, including ARMv7 cross-builds. See [compiler policy](docs/dependencies.md). +Host development requires the pinned Rust 1.99.0 toolchain, SDL2 development +libraries and pkg-config. CI also validates latest stable. See +[contributor setup](CONTRIBUTING.md) and [compiler policy](docs/dependencies.md). +Build the workspace so the three native apps are available beside the shell: ```sh cargo build --workspace --locked -cargo run --locked +cargo run --locked -- --size 480x272 sh scripts/validate.sh cargo build --workspace --release --locked ``` -The workspace build includes all native utilities. The validation script runs formatting, strict Clippy, Rust/Python tests, release builds and SDL smoke checks; [validation guidance](docs/validation.md) covers visual checks and Linux simulators. [Contributor guidance](CONTRIBUTING.md) covers setup, focused changes, validation, and pull requests. Please use the [bug and feature forms](https://github.com/csd113/Vitrallis-Shell/issues/new/choose) for feedback and the [security reporting policy](SECURITY.md) for security concerns. +The validation script runs formatting, strict Clippy, Rust/Python tests, release +builds and SDL smoke checks. [Validation guidance](docs/validation.md) covers +visual checks and Linux simulators; system controls in a desktop preview may be +unavailable. -**License:** project-owned code and documentation use [MIT](LICENSE). Third-party terms and artwork provenance are listed in [third-party notices](THIRD_PARTY_NOTICES.md); MIT does not relicense those items. +**License:** project-owned code and documentation use [MIT](LICENSE). +[Third-party notices](THIRD_PARTY_NOTICES.md) record dependency terms and artwork +provenance; MIT does not relicense those items. ## Uninstall diff --git a/docs/images/README.md b/docs/images/README.md new file mode 100644 index 0000000..cd811c2 --- /dev/null +++ b/docs/images/README.md @@ -0,0 +1,72 @@ +# README screenshot provenance + +The six images linked from the [project README](../../README.md) were captured on +2026-10-06 from **Vitrallis 1.0.4 running on a physical PocketCHIP** over USB SSH. +They are real 480×272 display captures, not simulator images or mockups. + +## Build and environment + +- PocketCHIP, Debian 13 ARMv7 hard-float; X11/Awesome, native 480×272 display. +- Published `v1.0.4` ARMv7 complete bundle, downloaded from the + [release](https://github.com/csd113/Vitrallis-Shell/releases/tag/v1.0.4). + Bundle SHA-256: `eb2b9be59066112668f2802a91bfb5205ba3c7ded6f181dbda2b5d378b62212c`. + The sidecar checksum and all five embedded executable hashes were verified before + extraction and execution. +- Release source: `874526879bf0defb25d68fd493d066623c0f3fd8`. Current `main` at + capture time was `1fd29aecd26546866f002a176207d4b0ed7fca9e`; only README/release + documentation differs between those commits. Application code is identical. +- The release binaries ran from a private temporary directory. The existing + installed 1.0.3 generation, session, preferences and apps were preserved. +- Auto selected SDL `opengles2`; startup reported Mesa `Mali400`, OpenGL ES 2.0 + Mesa 25.0.7, 480×272 window/output/display and requested SDL VSync. + These diagnostics and still images do not measure frame pacing or endurance. + +## Actual states + +| Asset | State shown | +| --- | --- | +| [Launcher](launcher-480x272.png) | Terminal, Notepad, Files, App Center and System Settings. Battery/charging, Wi-Fi and clock readings come from the live device. | +| [Terminal](terminal-480x272.png) | Real Bash PTY: a colored greeting, `ls` and `cat todo.txt` against sample files. The command waits for input after displaying its output. Terminal's static `--screenshot` preview fixture was not used. | +| [Notepad](notepad-480x272.png) | Sample `notes.txt` containing a weekend checklist, opened without edits. | +| [Files](files-480x272.png) | Sample Documents directory with empty Notes/Projects folders, `notes.txt` and `todo.txt`. | +| [Settings](settings-480x272.png) | Category overview with live device status; no setting or power action was activated. | +| [App Center](app-center-480x272.png) | Before first refresh in an isolated profile. No catalog entries or third-party apps are shown; no package operation was performed. | + +A private temporary HOME and XDG configuration/data directories kept existing app +installs, catalogs, folders and personal data out of the captures. A temporary +PocketHome-format menu contained no third-party entries and retained the device's +existing Wi-Fi utility command for the System Settings tile. Native apps and App +Center were discovered normally, without `--demo`. Sample documents lived in the +same temporary workspace; their displayed paths contain no personal files. + +The running windows used `--size 480x272` and fullscreen (the shell selected +`--linux-handheld`). Each capture used Pillow's X11 `ImageGrab.grab()` against the +physical device's entire 480×272 display. The frames were saved as lossless PNG +with compression optimization and transferred unchanged. No image was cropped, +resized, composited, annotated, retouched or AI-generated. Temporary capture +processes were closed and the original session regained focus. + +## Repeating captures and evidence limits + +Use an isolated profile and reviewed matching binaries; preserve the installed +session and user data. Wait for the shell's `event=ready` and live status refresh +before capturing the launcher. Open Settings from its tile, then close it and open +App Center without refreshing its empty profile. For native utilities, open only +sample documents/directories and run harmless commands through Terminal's +`--command` option. Capture each active fullscreen window on the device, verifying +that the resulting image is 480×272. + +For desktop-only documentation work, the existing SDL `--screenshot NEW.bmp` +exports and [Linux simulator](../../tests/simulator/README.md) remain useful. +Label those environments explicitly; Terminal's static export is not evidence +of a live PTY. Lossless BMP-to-PNG conversion should preserve identical RGB pixels. + +This bounded hardware session establishes the displayed states on this device. +It is **not full 1.0.4 hardware certification** and does not retest public +installation, cold boot, physical keyboard/touch, audio, power-loss durability or +long-running behavior. Earlier release-qualified results remain in the +[device guide](../devices/pocketchip.md) and [validation records](../validation.md). + +The earlier [desktop launcher image](shell-480x272.png) is retained at its original +path for existing references. Historical physical-device screenshots under +`docs/devices/pocketchip/evidence/` are unchanged. diff --git a/docs/images/app-center-480x272.png b/docs/images/app-center-480x272.png new file mode 100644 index 0000000..3f55a85 Binary files /dev/null and b/docs/images/app-center-480x272.png differ diff --git a/docs/images/files-480x272.png b/docs/images/files-480x272.png new file mode 100644 index 0000000..c5baf17 Binary files /dev/null and b/docs/images/files-480x272.png differ diff --git a/docs/images/launcher-480x272.png b/docs/images/launcher-480x272.png new file mode 100644 index 0000000..1123c62 Binary files /dev/null and b/docs/images/launcher-480x272.png differ diff --git a/docs/images/notepad-480x272.png b/docs/images/notepad-480x272.png new file mode 100644 index 0000000..4aa9708 Binary files /dev/null and b/docs/images/notepad-480x272.png differ diff --git a/docs/images/settings-480x272.png b/docs/images/settings-480x272.png new file mode 100644 index 0000000..bfc92d7 Binary files /dev/null and b/docs/images/settings-480x272.png differ diff --git a/docs/images/terminal-480x272.png b/docs/images/terminal-480x272.png new file mode 100644 index 0000000..8794c28 Binary files /dev/null and b/docs/images/terminal-480x272.png differ diff --git a/docs/shell.md b/docs/shell.md index 9875183..8a667c4 100644 --- a/docs/shell.md +++ b/docs/shell.md @@ -22,8 +22,9 @@ SDL_VIDEODRIVER=dummy cargo run --locked -- --smoke-test `--app-config` accepts an explicit PocketHome JSON path, and `--assets` an explicit asset root. `--list-apps` prints normalized discovery diagnostics without a window. `--screenshot NEW.bmp` captures the initial frame and exits, refusing to overwrite -an existing file. The README screenshot was captured from the current desktop -release build at 480×272 through this real renderer, with no device connection. +an existing file. The README screenshot tour instead uses live 480×272 display +captures on a physical PocketCHIP running 1.0.4. +[Capture provenance](images/README.md) describes the build, method and limits. The handheld backend reads stock PocketHome's `/usr/share/pocket-home/config.json`. `--app-config` overrides that path explicitly; no per-user launcher config is diff --git a/docs/validation.md b/docs/validation.md index 2dc7610..0d8ab6d 100644 --- a/docs/validation.md +++ b/docs/validation.md @@ -119,9 +119,12 @@ is recorded. ## Visual and device scope -The README image is a genuine current desktop-build SDL render at 480×272, -exported through `--screenshot`. Preview its Markdown at desktop and narrow/mobile -widths; the image must scale and command blocks must remain copyable. +The README screenshot tour contains genuine 480×272 display captures from a +physical PocketCHIP running 1.0.4. [Capture provenance](images/README.md) records +the environment and method for each image. The bounded screenshot session does +not extend earlier release certifications to 1.0.4. Preview the Markdown at +desktop and narrow/mobile widths; images must scale and command blocks must +remain copyable. The [USB hardware follow-up](devices/pocketchip/validation-usb-session.md) records fresh ARM installation, native app, session, control and removal tests on the device. diff --git a/src/app_center/tests.rs b/src/app_center/tests.rs index 531a83e..c88bf5f 100644 --- a/src/app_center/tests.rs +++ b/src/app_center/tests.rs @@ -2074,7 +2074,9 @@ impl GateFetch { }; drop(first_request); self.entered.send(()).map_err(|error| error.to_string())?; - receiver.recv().map_err(|error| error.to_string()) + receiver + .recv_timeout(std::time::Duration::from_secs(30)) + .map_err(|error| format!("download gate was not released: {error}")) } } impl network::Fetch for GateFetch { @@ -2221,7 +2223,9 @@ fn check_commit_fails_cleanly_if_lock_taken_mid_fetch() -> Result<(), String> { let (release, locked) = mpsc::channel(); let state = loc.state.clone(); let holder = std::thread::spawn(move || -> Result { - started.recv().map_err(|e| e.to_string())?; + started + .recv_timeout(std::time::Duration::from_secs(30)) + .map_err(|error| format!("catalog fetch did not start: {error}"))?; let lock = storage::Lock::take(&state)?; release.send(()).map_err(|e| e.to_string())?; Ok(lock) @@ -2232,6 +2236,7 @@ fn check_commit_fails_cleanly_if_lock_taken_mid_fetch() -> Result<(), String> { release: std::sync::Mutex::new(Some(locked)), }; let commit_messages = run_service(&loc, &next_fetch, vec![Command::Check]); + drop(next_fetch); match commit_messages.as_slice() { [Ok(_), Err(error)] => assert!( error.starts_with("Another Vitrallis storage operation is active"), @@ -2292,7 +2297,9 @@ fn concurrent_install_between_download_and_commit_is_detected() -> Result<(), St let install_package = p.clone(); let install_files = files.clone(); std::thread::spawn(move || -> Result<(), String> { - started.recv().map_err(|e| e.to_string())?; + started + .recv_timeout(std::time::Duration::from_secs(30)) + .map_err(|error| format!("download did not start: {error}"))?; let prepared = install::prepare(&install_locations, install_package.clone(), install_files)?; install::install(&install_locations, &prepared)?; @@ -2305,9 +2312,15 @@ fn concurrent_install_between_download_and_commit_is_detected() -> Result<(), St release: std::sync::Mutex::new(Some(unblock)), }; let next_messages = run_service(&loc, &fetch, vec![Command::Install(vec![p.key()])]); + // An early service error can skip fetching. Disconnect the start signal + // before joining so that error cannot leave the installer waiting forever. + drop(fetch); installer .join() - .map_err(|payload| format!("installer panicked: {payload:?}"))??; + .map_err(|payload| format!("installer panicked: {payload:?}"))? + .map_err(|error| { + format!("concurrent installer: {error}; service updates: {next_messages:?}") + })?; match next_messages.as_slice() { [Ok(_), Err(error)] => assert!(error.contains("No available update"), "{error}"), other => return Err(format!("unexpected updates: {other:?}")),