Releases: zShaD0w7x/linux-doctor
Release list
v0.6.1
Fixed
hardware/eccfired on machines with no ECC. The check matched the EDAC
driver's own init lines (EDAC MC: Ver: ...andEDAC ie31200: No ECC support), so a healthy box reported a corrected memory error; on some systems
that was the top item in the report. It needs a real CE/UE event now.hardware/mcefired on the MCE banks boot line.mce: CPU supports N MCE banksis printed once per CPU at boot on every Intel machine, and it was
reported as a machine check exception (high). Routine init lines are rejected.fs/btrfs-errorsfired on the btrfs module load banner.Btrfs loaded, zoned=yes, fsverity=yesis printed at boot on any kernel with btrfs compiled
in, mounted or not. The pattern requires a real event and still catches
BTRFS critical,failedandcorrupt.- An uncorrected memory error was filed as "corrected". An EDAC
UEline
raisedhardware/eccat medium; it is high now, with its own wording. packages/brokenfired on apt's own lock refusal. Without root,
apt-get checkcannot take the dpkg frontend lock, and that refusal was read
as broken dependencies (high). The healthy finding says "skipped (needs root)"
instead of claiming the check ran.packages/brokenfired onpacman -Dk's success line. "No database
errors have been found!" contains the word "errors", so a clean Arch system
was reported as a broken package database.updatesdoubled the count on dnf.dnf check-updateprints a header,
indented obsoletion lines and one entry per enabled repo, so 314 pending
updates read as 630. Unique package names are counted now.journalcounted stack-trace lines as separate entries. One multi-line
entry was counted once per continuation line, which turned a few crashes into
"3062 unrecognized log entries".- The desktop webview could come up blank where WebKit's DMA-BUF renderer
fails; it is disabled by default now.
Changed
- Install instructions lead with
npx linux-doctor, the published package,
with the GitHub form kept for the currentmain. The Arch section no longer
claims an AUR package that does not exist yet. - CI gates the checks against clean images and recorded machines. The engine
runs inside Fedora, Debian, Alpine and Arch containers, and replays fixtures
recorded on real machines; both fail on an unjustified high or medium finding.
v0.6.0
Highlights: the desktop grows into a real app — bundled Node runtime, tray + single-instance + autostart, auto-update, adaptive window — and the dashboard gets a wide-screen workbench (master-detail, status bar, deep-linkable state). A correctness pass fixes checks that were silently wrong or could never fire, and a hardening pass covers state files, outbound data and the release pipeline. No breaking changes; the JSON schema stays v1.
Added
- Desktop app: auto-update. The app checks GitHub Releases for a newer
signed build ~12s after startup and from a tray Check for updates item;
a native dialog offers to install and restart. Artifacts are
minisign-signed (public key intauri.conf.json, private key a CI secret),
the updater manifest is assembled byscripts/make-latest-json.mjs, and a
release fails fast if the signing secret is missing. On Linux the updatable
artifact is the AppImage (deb/rpm stay package-manager updates).
LINUX_DOCTOR_NO_UPDATE=1disables the check. - The desktop app is a tray app. A second launch surfaces the existing
window (single-instance, no second report server losing the fixed port); the
tray offers Open / Run checks now / Start at login (official autostart
plugin) / Check for updates / Quit, all handled Rust-side (Tauri IPC is not
used in this stack). Missing-tray systems fail soft. - The desktop app bundles its own Node runtime. .deb/.AppImage/.rpm ship a
pinned Node 22 LTS binary under<resources>/runtime/node, hash-checked
against the official SHASUMS256.txt at build time — end users need nothing
on their PATH. The fetch is architecture-aware (x64 + arm64), so a local
arm64 build embeds the right runtime.LINUX_DOCTOR_NODEstill overrides. - Wide-screen desktop workbench (≥1440px). The app shell uncaps and fills
the monitor; Overview becomes a master-detail view — the findings list stays
put while the selected finding renders in a pinned detail pane. Findings
groups open, rows densify, and a persistent status bar shows the active view,
freshness and the keyboard map. Below 1440px everything is unchanged. - Deep-linkable dashboard state. View, severity filter, grouping, theme and
density travel in the URL (?view=checks&sev=high&group=category&theme=terminal),
so a refresh or a pasted link restores the exact workbench. Free-text search
is deliberately never written to the URL. --debugcommand tracing and a per-check deadline.--debug(or
LINUX_DOCTOR_DEBUG=1) traces every spawned command, its duration, status
and (on failure) the stdout/stderr tail to stderr — stdout stays
machine-clean. Each check is now capped at 45s wall-clock (recorded in
checkErrors), so a check with many sequential commands cannot stretch a run.--allow-private-endpointfor--push/--alert/--heartbeat/--ai:
opt in to a self-hosted LAN server that the new destination guard would
otherwise refuse.
Changed
- Dashboard "Re-run checks" records the run in history. The trend and the
new/fixed diff advance when the user asks for a run (with a confirmation
toast); the 20s background auto-refresh still never writes history. - Redundant probes removed. The Fedora package check no longer runs two
fullrpm -Vaverifications whose result was never used; the apt orphan
check runsapt-get -s autoremoveonce (count + sample);glxinfois
memoized per run (was spawned by both the gpu and wayland checks); the
hardware check reads the kernel log once instead of twice. - The desktop window fits the screen instead of a fixed 1500×950: centered,
clamped to the monitor minus a margin, never below 900×640, shown only after
sizing (no resize flash). npm run gui:build/gui:devfetch the bundled Node runtime first (a
no-op when the right version is present), so a local build cannot package a
stale or missing interpreter.- Releases smoke-check the packages before publishing: the gui job asserts
the built.deb/.rpmcontainruntime/nodeand runs the bundled
interpreter. - Docs: support-bundle privacy claims corrected; the plugin/Pro trust model
is stated in the README and configuration.md; README notes the bundled
runtime and its ~130 MB size.
Fixed
smart/failingcould never fire.smartctl -Hexits non-zero when a
drive is FAILING, and the code skipped any non-zero result before reading the
health string — a failing disk was silently ignored. The health string is now
read first; a dying disk is reported as high.- Desktop machines were classified as servers. The profile probe read
loginctl's UID column instead of the seat, so every battery-less desktop was
"headless" and all desktop checks (wifi, gpu, bluetooth, wayland, audio,
cache) were skipped. The probe now finds the seat token regardless of column
layout (shared with the wayland check). - The crash check reported the lifetime boot count.
journalctl --list-boots
ignores--since; the boot count is now computed from the JSON form and
filtered to the last 7 days — "62 reboots in the last 7 days" became the true
count. - Atomic detection on composefs-overlay distros.
findmnt -T /reports
overlayon Bazzite/Silverblue, soimmutabledisagreed withimageBased
and atomic skips plus the report note were suppressed;immutablenow also
derives from the os-releaseimageBased/bootcsignals. - The
localesandfdschecks could never fire.localesruns with
LC_ALLunset for its one probe (the forcedLC_ALL=Cmasked the exact error
it looks for);fdsnow measures per-process pressure against
RLIMIT_NOFILE(the real "too many open files" mode) becausefs.file-maxis
effectively unlimited on modern kernels. - One OOM kill looked like a pattern. The oom check counted log lines; one
kill writes both a "Killed process" and a "reaped process" line, so it was
reported as two kills at high. It now counts distinct PIDs. - No more false "no firewall" without root.
nft list rulesetneeds
CAP_NET_ADMIN; when the ruleset cannot be read and no firewall service is
active the result is a newsecurity/firewall-unknown(info, no fix) instead
of claiming there is no firewall, andportssays "firewall status unknown". - Fedora-family updates had no safe fix. The catalog switched on a
family === "rhel"case thatdetectDistronever returns (it normalizes to
fedora); Fedora/RHEL now getsudo dnf upgrade. - Alpine/BusyBox no longer go quiet. Missing
getentisnetwork/skipped
(was a false "DNS is failing"); an unusabledf/df -iis
disk/skipped/inodes/skipped;freewithout anavailablecolumn falls
back to/proc/meminfo;fstrimno longer counts zram/loop devices as SSDs. durationsmatches its schema.--json --profileemitted an array while
the schema (and the dashboard) used a check→ms object; both channels now emit
the object.- A malformed threshold can no longer become 0.
Number("")/Number([])
are 0; only a real number or non-empty numeric string is accepted now (CLI,
dashboard and config share one coercion). - Dashboard Re-run/history, URL state, and the wide layout:
?view=deep
links win over the remembered view; search text is no longer written to the
URL; the desktop shell detects the webview viatauri://localhosttoo and
surfaces the real service error; the static--htmlexport keeps the Skipped
section; sticky chrome is opaque (the toolbar/status bar no longer show
content through them) and the detail pane sticks below the toolbar. - Desktop app: the dashboard could never parse a report. The loopback
report server wrote an extra CRLF after the CORS block, leaking
Content-Length/Connectioninto the JSON body. Fixed with a regression
test. - Start-at-login no longer lies. The toggle re-reads the real autostart
state, reflects it in the checkbox, and logs the actual outcome; it is also
panic-safe. - Rust children drop
NODE_OPTIONS/NODE_PATHalongsideLD_*, so a
poisoned environment cannot inject a preload module into the Node checks.
Security
- State files are written atomically and privately. config.json (which may
hold the Pro license key), history, cache, the support bundle and systemd
units go through oneatomicWrite()helper: a unique temp sibling opened
O_CREAT|O_EXCL(a planted symlink fails instead of being written through),
0600files in0700directories, replaced by rename. - Egress is checked by destination, not just scheme.
--push,--alert,
--heartbeatand--airefuse private/LAN address literals (RFC1918,
link-local including the cloud metadata address, CGNAT, IPv6
ULA/link-local) unless--allow-private-endpointis passed, and all four
refuse HTTP redirects.LLM_BASE_URLgoes through the same guard: an API key
can no longer be sent to a plaintext non-loopback endpoint. - Destructive safe-fixes are now
[manual]. Enabling a firewall (ufw can
lock out SSH), package autoremove, container prune and Trash deletion are
printed but never auto-executed by--fix --yes— a false positive can no
longer delete packages/data or cut a remote session. - Outbound and shared data is scrubbed.
--alert/--pushcarry scrubbed
finding text;--htmlis scrubbed like--md(whole payload + hostname);
the support bundle walks every field (plugin extras included); endpoint
credentials are redacted from messages;scrub()is linear (a crafted colon
run could hang a run);/run/mediaand/mediauser paths are redacted. --htmlcan no longer be weaponized. The payload is embedded through
jsonForInlineScript()(<→\u003c), so a</script>in any field
cannot close the tag; the dashboard header escapes its payload fields.- **Loopback report endpoints are cached and single-...
v0.5.0
Highlights: 8 new server checks (TLS certs, exposed ports, fd pressure, RAID, containers, service restart loops, GPU memory, stale backups) — the catalog grows to 49 checks / 161 codes;
--initguided first-run setup;--heartbeat <url>dead-man's switch [Pro];--ai-localprivate offline AI summaries; the dashboard becomes a five-view app (Overview / History / Checks / System / Schedule) with a Terminal theme and a machine wiki. No breaking changes — the JSON schema stays v1.
Added
-
Terminal theme for the dashboard. A phosphor-console look (near-black green-tinted background, monospace throughout, sharp corners, flat surfaces, green accents) as a fourth theme option behind the header button — same layout, same contrast budget, variables only.
-
Cockpit-style app navigation. The five views move into a sidebar rail with icons and live count badges on wide screens (scrollable tab bar below that width); secondary panels share a two-column card grid beside START HERE instead of stacking full-width. Same renderers, same keyboard flows, same remembered choice.
-
Product-look pass on findings and hero. Informational findings now render as compact one-line rows (dot + title + code, full body on open) instead of full cards; evidence summaries carry their line count; the hero's empty right side is now four stat tiles (problems, checks, clean streak, last check with the live checked-ago indicator).
-
Dashboard content order. START HERE takes the full width first; the daily-check strip and the security posture share a two-column notices row beneath it (spanning full width when only one is visible) — the Overview spine reads hero → START HERE → findings with no information or action removed.
-
System view is a full machine wiki. The report payload now carries architecture, hostname, CPU model, total RAM, desktop, and session type (additive schema fields, still v1); the view renders them as Operating system / Hardware / Session sections next to the report summary.
-
Dashboard app views: Overview / History / Checks / System / Schedule. The single scroll now has homes: today's report stays exactly as-is under Overview (the default), History gains a newest-first run ledger beside the trend charts, the all-checks matrix renders inline under Checks, System shows machine facts plus the report summary, and Schedule shows timer status, cadence, notification state, and copy-paste setup/alert/heartbeat commands. Tabs carry live count badges; tab bar,
1–5shortcuts, roving arrow-key tabs, remembered choice — existing filters, keyboard flows, and the auto-refresh pause behavior are untouched. -
--init— guided first-run setup (Free). Detects the environment (profile, systemd, node), then offers the three steps that turn a one-off run into set-and-forget monitoring: a starter config, the daily systemd timer, and a desktop-notification test. Every mutating step asks first; without a TTY it prints the same steps as copy-paste commands. -
--heartbeat <url>[Pro] — dead-man's switch. Pings a heartbeat URL (Healthchecks.io, BetterStack) with a bare GET after every completed run, in one-off and--daemonmode. The ping carries no body — liveness only — and failures are warnings, never exit-code changes. Same Pro gate and URL validation as--alert. -
ntfy examples for
--alertin docs/integrations.md. Phone push via ntfy (self-hostable, no account) plus the--heartbeatcomplement, so scheduled runs reach a human without a fleet server. -
4 new server checks — the catalog grows to 49 checks / 161 codes:
certs— TLS certificate expiry, the classic silent outage: reads certbot state (/etc/letsencrypt/live/*/cert.pem) and the actually-deployed cert vias_clientagainst locally-listening TLS ports (catches "renewed on disk but never reloaded"). Expired or <7 days → high, <30 days → medium. TunablecertWarnDays/certCritDays.ports— risky services (MySQL, PostgreSQL, Redis, MongoDB, FTP/Telnet…) listening on non-loopback interfaces, cross-checked against the firewall (same signals as the security check). Exposed with no firewall → medium; firewalled or clean → informational.fds— file-descriptor pressure from/proc/sys/fs/file-nr: the "server dies mysteriously with no metric spiking" cause. ≥95% of the kernel limit → high, ≥90% → medium, healthy stays silent.backup/stale— extends the backup check: a scheduled backup timer that never triggered, or last ran overbackupStaleDays(30) ago, is medium. A backup that never runs protects nothing.
-
Dashboard "Daily check" strip. The report now opens with the scheduling
state: whether the user timer (--install-timer) is installed and active,
plus the browser-alerts state — with a one-click copy of the setup command
when the timer is off. Served read-only from the newGET /api/schedule
endpoint (no report-schema change); hidden for static--htmlexports. -
New
--ai-localflag — private, offline AI summaries. Points the existing AI summary at a local Ollama instance (http://localhost:11434/v1, modelllama3.2, any key) so the plain-English explanation runs entirely on your machine with no cloud and noLLM_API_KEYto a third party. Finding text is still redacted with the samescrub()before it ever leaves the box. -
New
gpu-usagecheck: GPU memory pressure for AI/homelab rigs — reads VRAM used/total for NVIDIA (nvidia-smi) and AMD (amdgpusysfs, no tool needed). Stays silent on an idle card, reports usage as informational when the GPU is working (≥50%), and flags VRAM nearly full (≥90%) as medium — the OOM risk when you try to load a bigger model. Complements the existinggpudriver-health check. -
3 new server checks:
raid— software RAID health: a degraded mdadm array (parsed from/proc/mdstat, no binary needed) or aDEGRADEDZFS pool is flagged high (data-loss risk), a resyncing/scrubbing array is medium, healthy arrays get an informational line. Server-scoped (pass--check=raidto force it anywhere).containers/dead+containers/oom+containers/restarting— extend the container check: a container that exited non-zero is medium, one killed by the OOM killer (exit 137) is high, and one stuck in a restart loop is medium. Read-only viapodman ps -a/docker ps -a.services/restart-loop— extend the services check: a unit inauto-restartsubstate is "active" but never actually stays up, so it hides behind a green status; flagged high with itsNRestartscount.
Fixed
- AppImage runtime mounts no longer report as full disks. FUSE mounts at
/tmp/.mount_*(device*.AppImage) always read 100% because they are fixed-size images, not filling disks — same false-positive class as the excluded squashfs layers. Skipped by mount shape in the disk and inode checks.
v0.4.0
Added
--md <path>— share-ready Markdown export. One flag writes a
paste-ready Markdown report (START HERE, severity sections with stable
codes, evidence, fixes, since-last-run diff) with the samescrub()used
by support bundles applied to every text field — IPs, home paths, and UIDs
are redacted before the file is written, so it is safe to post in public
forums or issues. Exit codes match the normal report.--install-timer/--uninstall-timer— one-command scheduling.
Writes user-level systemd units (no sudo) under~/.config/systemd/user/
with resolved absolute paths (survives npm relocation), enables the timer,
and attaches--notify— the machine only speaks when something NEW
appears. Idempotent install, forgiving uninstall, honest exit 2 when
systemd is not running. Management commands never run checks.
Changed
pkgInstall()no longer guessesdnfon unknown distributions. Void
and Gentoo now get correct native commands (xbps-install -Sy,emerge);
any other unrecognized distro gets an honest manual-step line instead of a
wrongsudo dnf install(e.g. on NixOS).
v0.3.5
Highlights: 6 new health checks, stronger fleet/AI privacy, safer
--fix, and a more robust dashboard. No breaking changes.
Security
- Fleet reporting now blocks plaintext HTTP for authenticated pushes. When
FLEET_API_KEYis set,--pushand--alertrequirehttps://(loopbackhttp://127.0.0.1,localhost,[::1]exempt for local dev). The check runs at CLI validation and again beforepushReport/sendAlert; a misconfiguredhttp://endpoint now fails fast with exit 2 instead of leaking the Bearer token. Includes tests infleet.test.jsandalert.test.js. - AI summaries redact sensitive data before egress. Finding titles and details are now scrubbed with the same
scrub()used for support bundles — IPv4/IPv6 literals and/home/<user>paths never reach the LLM endpoint. Verified byllm.test.js.
Added
- 6 new checks + 1 Flatpak extension (44 checks / 145 codes total):
inodes— inode exhaustion (df -i): the classic “No space left on device” whendf -hstill shows free space. TunableinodeFullPct/inodeWarnPct(90/80).orphans— orphaned packages:pacman -Qtdq,apt autoremove --dry-run,dnf repoquery --unneeded/zypper packages --unneeded.boot— boot partition health: space on/bootand/boot/efiplus missinggrub.cfg/systemd-bootentry.cache— user cache and trash bloat:~/.cacheand~/.local/share/Trash(5/10 GB thresholds, desktop/laptop).wifi— WiFi state: rfkill soft/hard block,nmcli radio wifi, adapter presence (wifi/blocked,wifi/disabled,wifi/no-adapter,wifi/ok).packages— package-manager health:dpkg --audit,apt-get check,dnf check,pacman -Dk(packages/broken,packages/locked,packages/ok).flatpak/unused-runtimes—flatpak uninstall --unused --dry-runfor stale SDKs.
- Documentation:
docs/checks.md— auto-generated catalog of all 44 checks and 145 codes (scripts/generate-check-docs.mjs). - Man page:
packaging/linux-doctor.1now documents every flag, including--history-json,--thresholds-set,--alert,--daemon, and--interval. - Thresholds:
inodeFullPct/inodeWarnPctdocumented indocs/configuration.mdandDEFAULT_THRESHOLDS.
Changed
- Safer
--fixcatalog — no more accidental SSH drops.network/no-route(interface down/up cycle) is nowmanualtier — it would have killed the SSH session running--fix --yes. On Debian,security/no-firewallnow runssudo ufw allow OpenSSHbeforesudo ufw --force enable(plus--forceto avoid the interactive y/n hang; existing sessions survive via conntrack). - Threshold validation is now strict.
loadThresholdsand bothPOST /api/thresholdshandlers drop non-numeric values ("90%"→ ignored, keeps default) instead of storingNaNand breaking comparisons. - Scrubbing now covers compressed IPv6.
scrub()also redacts::1andfe80::without breaking12:34:56timestamps (guarded by::/[A-Fa-f]). Used by both support bundles and--ai.
Fixed
- Processes: header row no longer shifts the top-3.
ps -o rssleaves aRSSheader even withargs=; the parser now filters it explicitly.processes/okcorrectly shows the real top consumers.
v0.3.4
What's Changed
- first-run history explainer + motion and number polish
Full Changelog: v0.3.2...v0.3.4.1
v0.3.2
Full Changelog: v0.3.1...v0.3.2
Full Changelog: v0.3.1...v0.3.2
v0.3.1
Full Changelog: v0.3.0...v0.3.1
Full Changelog: v0.3.0...v0.3.1
v0.3.0
Full Changelog: v0.2.0...v0.3.0
Full Changelog: v0.2.0...v0.3.0