feat!: modern-only platform floor, windows/linux parity and the crashes found getting there - #53
Merged
Merged
Conversation
Three small helpers that the Linux tools all needed and each solved differently (or not at all): - Get-PcCommandOutput runs a native command and returns $null instead of throwing when it is missing or prints nothing. The bare `(& cmd ...).Trim()` idiom throws on $null, which aborts the caller. - Get-PcDesktopUser resolves the human behind `sudo pwsh`. Tools that act on the desktop session must not use $env:USER or $env:HOME, which point at root. It also validates DBUS_SESSION_BUS_ADDRESS centrally, which until now only the audio tool did. - Open-PcUrl opens a URL through xdg-open/gio on Linux, where Start-Process cannot launch one, and drops privileges so the browser lands in the user's session rather than root's. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bb5QHLEcZUhQSFseYRnQEX
On any system without a usable systemd -- containers, WSL, OpenRC distros -- `timedatectl show` prints nothing, so `.Trim()` threw and the tool produced no output at all beyond "[!!] Tool error: You cannot call a method on a null-valued expression". The same pattern guarded uname, uptime, mokutil and `systemctl is-active`. Route those through Get-PcCommandOutput so a missing command degrades to N/A per field. Start.ps1 runs before the helpers are loaded and guards its two calls inline instead. Also fixes the User field, which was blank whenever $env:USER was unset. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bb5QHLEcZUhQSFseYRnQEX
Start-Process cannot open a URL on Linux -- it tries to exec the address as a file and fails with "No such file or directory". That broke all four cross-platform link options: both main menu entries and both BIOS Password Recovery entries. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bb5QHLEcZUhQSFseYRnQEX
pcHealth requires root on Linux, so $env:USER and $env:HOME describe root, not the person at the keyboard. Three tools got this wrong: - Log Off ran `loginctl terminate-user root`, terminating the wrong session (or nothing). - Disk Cleanup cleared /root/.cache/thumbnails, never the user's. - Topgrade resolved the session bus without validating it, unlike the audio tool, and spliced it into a `bash -c` string along with DISPLAY and WAYLAND_DISPLAY. Those now go to `env` as separate argv tokens, so the shell command is a fixed literal. All four now share Get-PcDesktopUser, which also removes the duplicated user/uid/dbus resolution from the audio and topgrade tools. One unrelated bug fixed on the way: Invoke-Cleanup judged each step by a stale $LASTEXITCODE when that step ran no native command, so the thumbnail cleanup reported a failure inherited from the step before it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bb5QHLEcZUhQSFseYRnQEX
Windows marks installed packages; Linux did not, so the menu gave no clue whether picking an entry would install anything. It now probes the package's binary on PATH -- one lookup, rather than a different "is this installed" query per package manager -- and skips reinstalling. Get-LinuxPackageManager moves to Helpers.ps1 as Get-PcPackageManager and gains the refresh/list/update verbs the system update tool needs. The menu is now driven off its own package table instead of a hardcoded second copy of the option list. Write-PcHeader greeted an empty name whenever SUDO_USER and USER were both unset; it uses Get-PcDesktopUser now. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bb5QHLEcZUhQSFseYRnQEX
Windows had three things Linux did not: - Update all packages. Linux only offered Topgrade, which most systems do not have installed. The new tool drives whichever of apt, dnf, pacman or zypper is present: refresh, list what is pending, confirm, upgrade, then report whether a reboot is needed. - Battery Report, read straight from /sys/class/power_supply. Handles both driver flavours (energy_* in uWh and charge_* in uAh), reports health against design capacity, and says plainly when a battery is worn. - Temperatures. Hardware Information now has a Sensors section fed by the kernel's hwmon class -- the same source lm-sensors reads, so nothing extra needs installing. View System Logs also gains a failed-units listing, which is the first thing worth checking on a machine that misbehaves. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bb5QHLEcZUhQSFseYRnQEX
pcHealth already exits unless it is running as uid 0 on Linux, so elevating each package-manager and systemctl call again achieved nothing -- and broke outright on systems that ship no sudo at all, which is common on minimal Debian, Alpine and container images. app.ps1 now repeats the root check the way it already repeats the kernel check, so tools invoked directly can rely on it too. The `sudo -u` calls that drop privileges to the desktop user are untouched. Hardware Information's smartmontools installer was a second copy of the package-manager chain; it uses Get-PcPackageManager now. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bb5QHLEcZUhQSFseYRnQEX
Both were bare `catch { }`, which AGENTS.md rules out:
- HealthPage's battery tick ran every 3 seconds and silently froze the
live charge and power-draw readings if the report call ever threw.
Logged at Debug so a removed battery cannot flood the log.
- TracerouteViewModel discarded DNS failures, leaving no trace of why
the destination line came out blank. Narrowed to SocketException and
ArgumentException so a genuinely unexpected fault still surfaces
through the outer handler.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bb5QHLEcZUhQSFseYRnQEX
README gains the three Linux entries added this round and the [installed] marker in the Programs menu. AGENTS.md gains three rows in the PowerShell table for the traps that produced most of the bugs fixed here: `.Trim()` on a command that may not exist, re-elevating with sudo when the app already requires root, and reading $env:USER/$env:HOME under sudo. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bb5QHLEcZUhQSFseYRnQEX
PSScriptAnalyzer reads `2>$null` inside an if-condition subexpression as a mistyped redirection. Same shape as the check in Start.ps1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bb5QHLEcZUhQSFseYRnQEX
Completes tool-level parity for every Windows entry that has a meaningful Linux equivalent: - Scan + Repair. SFC compares system files against the component store; the package database is the same idea, so verify against that (debsums / rpm -Va / pacman -Qkk) and report what no longer matches. Also surfaces filesystem errors the kernel has already logged, since fsck cannot safely touch a mounted root. - Disk Optimization. Linux filesystems do not need defragmenting, so the useful half of dfrgui's job is fstrim on solid-state devices. Skips outright when every disk is rotational, and says so when fstrim.timer already handles it weekly. - Firmware Update. The counterpart to HP Image Assistant, but vendor-neutral: fwupd ships BIOS, dock and SSD firmware via LVFS. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bb5QHLEcZUhQSFseYRnQEX
BREAKING CHANGE: pcHealth now exits on Windows builds below 26200 and kernels below 7.0. The previous floors admitted Windows 10 22H2, which reached end of life in October 2025, and kernel 6.0. The warn-and-continue tier is gone: the recommended versions were already 26200 and 7.0, so raising the floor to meet them made that branch unreachable. One check per platform now, not two. This is a deliberate trade. Kernel 7.0 excludes Debian 13 stable and RHEL 10, which both ship 6.12 LTS, so pcHealth no longer runs there. In exchange every supported target is guaranteed UEFI + GPT, which is what lets the boot repair tools drop their MBR/CSM paths entirely. TargetPlatformMinVersion follows the same floor. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bb5QHLEcZUhQSFseYRnQEX
Windows 11 requires UEFI + GPT and the floor is now build 26200, so the MBR-era steps were dead code on every supported system: bootrec /fixmbr wrote boot code nothing on a GPT disk reads, and /fixboot returns "Access is denied" on EFI, which is why the bcdboot fallback underneath it was doing the actual repair. The tool now refuses on non-UEFI firmware, runs bcdboot against the ESP with /f UEFI rather than /f ALL, and unmounts the ESP afterwards. The Linux counterpart supports systemd-boot, GRUB and Limine. It refuses on legacy BIOS, will not mount an ESP itself -- guessing wrong about which partition that is, on the one filesystem where a wrong guess is fatal, is not worth the convenience -- detects which loaders are actually installed, prints the exact commands, and asks twice before running any of them. Each repair is the loader's own official command; pcHealth never writes boot sectors itself. Limine has no upstream UEFI installer, so it prefers a distro helper and otherwise copies the EFI binary onto the ESP as the docs describe. `limine bios-install` is never used: it is documented as BIOS-only. The EFI binary name and GRUB target follow the firmware's bitness rather than the CPU's, since 32-bit UEFI on a 64-bit CPU would not boot BOOTX64. Renamed both entries to "Boot Repair" -- neither repairs a boot record any more. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bb5QHLEcZUhQSFseYRnQEX
Detection walked apt, dnf, pacman, zypper in order and took the first binary it found. On Fedora Atomic that is wrong in a way that bites immediately: Distrobox exports and Homebrew readily put apt and pacman on PATH, so a Bazzite host reporting pkg managers : apt dnf dnf5 pacman rpm-ostree bootc flatpak brew resolved to apt, and Update all packages would have run `apt update && apt upgrade -y` against an rpm system. Scan + Repair would have reached for debsums, and the Programs menu for apt install. The family in /etc/os-release decides now; PATH order is only a fallback for distros pcHealth does not recognise. Image-based systems are handled ahead of that, because none of the managers they ship can write to a read-only /usr: - Update all packages stages a deployment and says a reboot is needed. - Disk Cleanup uses `rpm-ostree cleanup -bm`, which clears temp files and cached metadata only. Never -r or -p: those delete the rollback and pending deployments, which are the safety net there. - Programs points at Homebrew or Distrobox, since installing layers the package into the image. - Boot Repair refuses outright. Bootloader entries are generated from the deployments, so reinstalling GRUB or systemd-boot by hand fights whatever produced them; rolling back is the real repair. bootc-only images are covered too: it has no package or cleanup verbs, so those come back $null and each caller says so instead of guessing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bb5QHLEcZUhQSFseYRnQEX
Replaces the rpm-ostree/bootc support added a commit ago. That taught pcHealth a second package-management dialect -- upgrade --preview, cleanup -bm, ostree fsck, plus a bootc fallback with half its verbs missing -- for four tools. It worked, but it was a dialect that has to be chased as bootc replaces rpm-ostree, and one step of it was never tested on a real deployment. Hiding them is both smaller and more honest. Four Linux tools depend on a writable /usr or a hand-managed bootloader: Update all packages, Scan + Repair, Disk Cleanup, Boot Repair They carry NeedsMutableOS and drop out of the menu on ostree, using the Platforms filter that was already there. Numbering stays sequential, so Bazzite shows 14 tools instead of 18 with no gaps. The other 14 -- every diagnostic, plus Firmware Update and Disk Optimization, which work fine against fwupd and fstrim on any system -- are untouched. Installing packages is refused in the Programs menu and in Hardware Information's smartmontools prompt, pointing at Homebrew or Distrobox. Boot Repair keeps its own ostree refusal even though it is hidden: it is the one tool where a direct invocation costs a bootable machine. Net 52 lines lighter, and nothing left that a bootc release can break. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bb5QHLEcZUhQSFseYRnQEX
Boot Repair hardcoded S: for the EFI System Partition and decided whether the mount worked with Test-Path afterwards. When S: is already taken -- a USB stick, a mapped network drive -- mountvol fails but Test-Path still succeeds, so bcdboot would write the EFI boot files onto that volume and the cleanup step would then dismount the user's drive. Picks a genuinely free letter, checks mountvol's exit code instead of inferring success, and releases the ESP in a finally block so it is not left mounted when bcdboot throws. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bb5QHLEcZUhQSFseYRnQEX
Repair Winget printed "[OK] Winget repair complete." directly under the error "The term 'winget-install' is not recognized". Two separate bugs met there. IProcessRunner.RunAsync returned Task and discarded the exit code, so no page could tell success from failure. Seven view models use it; three printed an unconditional [OK] -- Repair Winget, Reset Network Stack, and Boot Repair, where a false all-clear is worst. It returns Task<int> now; callers that ignore the value are unaffected. Repair Winget also called `winget-install` by bare name. Install-Script puts winget-install.ps1 in the CurrentUser scripts folder, which is not on PATH for the spawned process, so it could never resolve. The CLI tool already looked up InstalledLocation; the GUI now does the same. Reset Network Stack reports which of winsock/IPv4/IPv6 failed, and Boot Repair names the failed steps -- noting that /fixmbr and /fixboot are expected to fail on UEFI/GPT. ANSI escapes are stripped in ProcessRunner rather than per page: pwsh colours its output and a TextBlock renders the escape codes literally, which is why the error came out as "[31;1mwinget-install:". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bb5QHLEcZUhQSFseYRnQEX
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
An audit and modernisation pass over the CLI and GUI, the Linux work that closes tool-level parity with Windows, and a move to a modern-only supported range. Every finding below was reproduced by running the code in a PowerShell 7.5.4 sandbox, not by reading it.
Seven Linux tools added, eight bugs fixed, one shell-injection surface removed, and all MBR/CSM boot code retired.
Type of change
Conventional Commit Types - choose one:
feat- #0075ca new functionality visible to the userfix- #d73a4a bug fixrefactor- #fbca04 code restructure without behavior changeperf- #d4c5f9 performance improvementdocs- #0075ca documentation onlychore- #e4e669 maintenance, cleanup, configurationstyle- #bfd4f2 formatting only, no logic changetest- #c2e0c6 test additions or changesci- #006b75 CI/CD workflow changesdeps- #0366d6 dependency or action version updatebuild- #fdb913 build system or tooling changerevert- #f9d0c4 reverts a previous commitBreaking change?
feat!:orBREAKING CHANGEin my commitBoot repair, rebuilt for UEFI
On Windows the MBR-era steps were already dead code:
bootrec /fixmbrwrote boot code nothing on a GPT disk reads, and/fixbootreturns "Access is denied" on EFI — which is why thebcdbootfallback underneath it was doing the real repair all along. It now refuses on non-UEFI firmware, runsbcdbootagainst the ESP with/f UEFIrather than/f ALL, and releases the ESP afterwards.The new Linux counterpart supports systemd-boot, GRUB and Limine. Design constraints, given that a mistake here costs a bootable machine:
Checklist
fix: resolve startup crash)