Skip to content

Releases: zShaD0w7x/linux-doctor

v0.6.1

Choose a tag to compare

@github-actions github-actions released this 17 Sep 11:47
v0.6.1

Fixed

  • hardware/ecc fired on machines with no ECC. The check matched the EDAC
    driver's own init lines (EDAC MC: Ver: ... and EDAC 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/mce fired on the MCE banks boot line. mce: CPU supports N MCE banks is 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-errors fired on the btrfs module load banner. Btrfs loaded, zoned=yes, fsverity=yes is printed at boot on any kernel with btrfs compiled
    in, mounted or not. The pattern requires a real event and still catches
    BTRFS critical, failed and corrupt.
  • An uncorrected memory error was filed as "corrected". An EDAC UE line
    raised hardware/ecc at medium; it is high now, with its own wording.
  • packages/broken fired on apt's own lock refusal. Without root,
    apt-get check cannot 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/broken fired on pacman -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.
  • updates doubled the count on dnf. dnf check-update prints a header,
    indented obsoletion lines and one entry per enabled repo, so 314 pending
    updates read as 630. Unique package names are counted now.
  • journal counted 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 current main. 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

Choose a tag to compare

@github-actions github-actions released this 13 Sep 17:37
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 in tauri.conf.json, private key a CI secret),
    the updater manifest is assembled by scripts/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=1 disables 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_NODE still 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.
  • --debug command 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-endpoint for --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
    full rpm -Va verifications whose result was never used; the apt orphan
    check runs apt-get -s autoremove once (count + sample); glxinfo is
    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:dev fetch 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/.rpm contain runtime/node and 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/failing could never fire. smartctl -H exits 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
    overlay on Bazzite/Silverblue, so immutable disagreed with imageBased
    and atomic skips plus the report note were suppressed; immutable now also
    derives from the os-release imageBased/bootc signals.
  • The locales and fds checks could never fire. locales runs with
    LC_ALL unset for its one probe (the forced LC_ALL=C masked the exact error
    it looks for); fds now measures per-process pressure against
    RLIMIT_NOFILE (the real "too many open files" mode) because fs.file-max is
    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 ruleset needs
    CAP_NET_ADMIN; when the ruleset cannot be read and no firewall service is
    active the result is a new security/firewall-unknown (info, no fix) instead
    of claiming there is no firewall, and ports says "firewall status unknown".
  • Fedora-family updates had no safe fix. The catalog switched on a
    family === "rhel" case that detectDistro never returns (it normalizes to
    fedora); Fedora/RHEL now get sudo dnf upgrade.
  • Alpine/BusyBox no longer go quiet. Missing getent is network/skipped
    (was a false "DNS is failing"); an unusable df/df -i is
    disk/skipped/inodes/skipped; free without an available column falls
    back to /proc/meminfo; fstrim no longer counts zram/loop devices as SSDs.
  • durations matches its schema. --json --profile emitted 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 via tauri://localhost too and
    surfaces the real service error; the static --html export 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/Connection into 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_PATH alongside LD_*, 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 one atomicWrite() helper: a unique temp sibling opened
    O_CREAT|O_EXCL (a planted symlink fails instead of being written through),
    0600 files in 0700 directories, replaced by rename.
  • Egress is checked by destination, not just scheme. --push, --alert,
    --heartbeat and --ai refuse private/LAN address literals (RFC1918,
    link-local including the cloud metadata address, CGNAT, IPv6
    ULA/link-local) unless --allow-private-endpoint is passed, and all four
    refuse HTTP redirects. LLM_BASE_URL goes 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/--push carry scrubbed
    finding text; --html is 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/media and /media user paths are redacted.
  • --html can 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-...
Read more

v0.5.0

Choose a tag to compare

@github-actions github-actions released this 05 Sep 16:36
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; --init guided first-run setup; --heartbeat <url> dead-man's switch [Pro]; --ai-local private 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, 15 shortcuts, 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 --daemon mode. 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 --alert in docs/integrations.md. Phone push via ntfy (self-hostable, no account) plus the --heartbeat complement, 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 via s_client against locally-listening TLS ports (catches "renewed on disk but never reloaded"). Expired or <7 days → high, <30 days → medium. Tunable certWarnDays / 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 over backupStaleDays (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 new GET /api/schedule
    endpoint (no report-schema change); hidden for static --html exports.

  • New --ai-local flag — private, offline AI summaries. Points the existing AI summary at a local Ollama instance (http://localhost:11434/v1, model llama3.2, any key) so the plain-English explanation runs entirely on your machine with no cloud and no LLM_API_KEY to a third party. Finding text is still redacted with the same scrub() before it ever leaves the box.

  • New gpu-usage check: GPU memory pressure for AI/homelab rigs — reads VRAM used/total for NVIDIA (nvidia-smi) and AMD (amdgpu sysfs, 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 existing gpu driver-health check.

  • 3 new server checks:

    • raid — software RAID health: a degraded mdadm array (parsed from /proc/mdstat, no binary needed) or a DEGRADED ZFS pool is flagged high (data-loss risk), a resyncing/scrubbing array is medium, healthy arrays get an informational line. Server-scoped (pass --check=raid to 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 via podman ps -a / docker ps -a.
    • services/restart-loop — extend the services check: a unit in auto-restart substate is "active" but never actually stays up, so it hides behind a green status; flagged high with its NRestarts count.

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

Choose a tag to compare

@github-actions github-actions released this 28 Aug 11:37
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 same scrub() 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 guesses dnf on 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
    wrong sudo dnf install (e.g. on NixOS).

v0.3.5

Choose a tag to compare

@github-actions github-actions released this 27 Aug 14:29
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_KEY is set, --push and --alert require https:// (loopback http://127.0.0.1, localhost, [::1] exempt for local dev). The check runs at CLI validation and again before pushReport/sendAlert; a misconfigured http:// endpoint now fails fast with exit 2 instead of leaking the Bearer token. Includes tests in fleet.test.js and alert.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 by llm.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” when df -h still shows free space. Tunable inodeFullPct / inodeWarnPct (90/80).
    • orphans — orphaned packages: pacman -Qtdq, apt autoremove --dry-run, dnf repoquery --unneeded / zypper packages --unneeded.
    • boot — boot partition health: space on /boot and /boot/efi plus missing grub.cfg / systemd-boot entry.
    • cache — user cache and trash bloat: ~/.cache and ~/.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-runtimesflatpak uninstall --unused --dry-run for 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.1 now documents every flag, including --history-json, --thresholds-set, --alert, --daemon, and --interval.
  • Thresholds: inodeFullPct / inodeWarnPct documented in docs/configuration.md and DEFAULT_THRESHOLDS.

Changed

  • Safer --fix catalog — no more accidental SSH drops. network/no-route (interface down/up cycle) is now manual tier — it would have killed the SSH session running --fix --yes. On Debian, security/no-firewall now runs sudo ufw allow OpenSSH before sudo ufw --force enable (plus --force to avoid the interactive y/n hang; existing sessions survive via conntrack).
  • Threshold validation is now strict. loadThresholds and both POST /api/thresholds handlers drop non-numeric values ("90%" → ignored, keeps default) instead of storing NaN and breaking comparisons.
  • Scrubbing now covers compressed IPv6. scrub() also redacts ::1 and fe80:: without breaking 12:34:56 timestamps (guarded by :: / [A-Fa-f]). Used by both support bundles and --ai.

Fixed

  • Processes: header row no longer shifts the top-3. ps -o rss leaves a RSS header even with args=; the parser now filters it explicitly. processes/ok correctly shows the real top consumers.

v0.3.4

Choose a tag to compare

@github-actions github-actions released this 26 Aug 16:52
v0.3.4.1

What's Changed

  • first-run history explainer + motion and number polish
    Full Changelog: v0.3.2...v0.3.4.1

v0.3.2

Choose a tag to compare

@github-actions github-actions released this 25 Aug 12:24
v0.3.2

Full Changelog: v0.3.1...v0.3.2

Full Changelog: v0.3.1...v0.3.2

v0.3.1

Choose a tag to compare

@github-actions github-actions released this 24 Aug 23:16
v0.3.1

Full Changelog: v0.3.0...v0.3.1

Full Changelog: v0.3.0...v0.3.1

v0.3.0

Choose a tag to compare

@github-actions github-actions released this 24 Aug 19:19
v0.3.0

Full Changelog: v0.2.0...v0.3.0

Full Changelog: v0.2.0...v0.3.0