Terminal System Diagnostics: A Complete Picture Without GUI Tools

How to get a single complete picture of a machine's OS, boot mode, disk layout, memory, GPU, and network from one terminal pass, without guessing. A structured approach to hardware and OS discovery.

A computer as stacked translucent layers of firmware, disks, memory, graphics and network, with one scan line passing up through them

Goal: get a single complete picture of the machine — OS, boot mode, disk layout, partitions, bootloader, filesystems, swap, memory, GPU, network — from one terminal pass, without a GUI tool and without guessing at any of it. The kind of snapshot you want before repartitioning, before touching a bootloader, or before filing a bug report against a distro.

This document records every command, what it actually returned, and which ones failed and why.


The approach

Five stacked layers covered by one terminal pass: boot, disk, memory, GPU, network
What one pass covers, from firmware up to the network.

One shell script, split into labelled sections with echo "===== NAME =====" headers. That framing matters more than it looks: when a dozen commands dump output back to back, unlabelled output is unreadable, and you cannot tell a command that printed nothing from a command that failed silently. Headers make gaps visible.

Two structural decisions that paid off:

  1. Fallback chains with ||. lsblk was invoked with a long column list and a shorter list as a fallback (lsblk -o A,B,C ... || lsblk -o A,B). This turned out to be necessary — see “Invalid column” below.
  2. 2>/dev/null || true on the optional probes. LVM and btrfs tooling is absent or irrelevant on many systems. Without the guard, a missing btrfs binary aborts the run.

Section by section: command, and what it found

OS and kernel

cat /etc/os-release
uname -a
hostnamectl

/etc/os-release is the machine-readable source of truth: ID=pop, ID_LIKE="ubuntu debian", VERSION_CODENAME=noble. That ID_LIKE line is the useful one — it tells a script that Debian and Ubuntu instructions apply to Pop!_OS.

hostnamectl gave what the other two could not: hardware vendor and model (HP EliteBook 840 G8), firmware version (T76 Ver. 01.23.00), firmware date, and firmware age. Firmware age is a genuinely useful field that almost nobody checks — a BIOS three years stale is a plausible cause for a whole class of suspend, thermal, and NVMe bugs.

Kernel: 7.0.11-76070011-generic — the -76 marks it as a System76-built kernel, not stock Ubuntu.

Boot mode

[ -d /sys/firmware/efi ] && echo "UEFI" || echo "Legacy BIOS"

The cleanest UEFI-vs-BIOS test there is. The kernel only creates /sys/firmware/efi when it booted via EFI. No parsing, no tool to install. Result: UEFI.

This single line decides how every later answer should be read. On a UEFI system, “which bootloader” is a question about EFI variables and files on an ESP, not about bytes in an MBR.

Disks and partitions

lsblk -e7 -o NAME,SIZE,TYPE,FSTYPE,FSVER,LABEL,UUID,PARTUUID,MOUNTPOINTS

-e7 excludes loop devices (major number 7), which otherwise bury real disks under a wall of snap mounts. The explicit -o column list is the point of the exercise — the default lsblk output omits UUID, PARTUUID and filesystem version, which are exactly the fields you need when editing /etc/fstab or debugging a boot failure.

What it found — a textbook dual-boot layout on nvme0n1 (476.9 GB):

Partition Size Type Role
p1 200M vfat FAT32 Original Windows ESP
p2 16M (none) Microsoft Reserved (MSR)
p3 217.2G ntfs Windows C:
p4 735M ntfs Windows Recovery
p5 1024M vfat FAT32 ESP in use, mounted at /boot/efi
p6 150.4G ext4 Pop!_OS root, mounted at /

The interesting find: two EFI System Partitions. p1 (200M) is the one Windows created; p5 (1024M) is the one the Pop!_OS installer created and the one actually mounted. This is normal Pop!_OS behaviour — its installer prefers its own large ESP rather than crowding into Windows’ 200 MB — but it is a trap. Anyone who later runs a bootloader repair tool and lets it pick “the” ESP can write to the wrong one and produce a system that boots neither OS. Knowing there are two, and which is live, is the payoff for running the diagnostic at all.

Roughly 107 GB of the 477 GB disk is unallocated or otherwise unaccounted for by the partition sizes above — worth confirming with a partition-table dump (see the sudo section).

Filesystem usage

df -hT

-T adds the filesystem type column, which turns a usage report into a mount-type audit. Result: root /dev/nvme0n1p6 ext4, 147G total, 85G used, 55G free, 61% used. ESP /dev/nvme0n1p5 at 437M of 1022M (43%) — that number matters, because a full ESP is a classic cause of failed kernel updates.

Everything else in the list is tmpfs, which is a useful reminder that df output is mostly RAM by line count: /run, /dev/shm, /run/lock, /run/qemu, /run/user/1000.

The EFI System Partition, specifically

findmnt /boot/efi
ls -lah /boot/efi

findmnt is the right tool over mount | grep — it takes a path argument and prints the source device, filesystem type, and the full option string. It confirmed p5 as the live ESP with fmask=0077,dmask=0077, i.e. root-only access, which is why the ls produced nothing readable as a normal user.

Mounts and fstab

findmnt
cat /etc/fstab

Bare findmnt prints the whole mount tree as an actual tree, which beats mount for reading. Root is mounted rw,noatime,errors=remount-ro — noatime is a deliberate SSD-longevity choice the Pop installer makes.

/etc/fstab held the more interesting detail: the ESP is referenced by PARTUUID, while root is referenced by UUID. That is not an inconsistency, it is correct — a partition UUID for a FAT partition (whose filesystem UUID is a weak 32-bit volume serial), and a filesystem UUID for ext4. Third line: /swapfile none swap sw 0 0.

Swap and memory

swapon --show
free -h

The sharpest finding of the whole run. swapon --show reported two swap devices:

/swapfile  file         8G   0B   -1
/dev/zram0 partition 15.3G 5.6G 1000

zram — compressed swap in RAM — at priority 1000, and an 8 GB disk swapfile at priority -1. The priorities mean zram fills completely before the disk file is touched at all, which is why the swapfile shows 0 B used while zram holds 5.6 GB.

And the memory state explains why that matters: 15 GB total, 12 GB used, 711 MB free, only 2.6 GB available. This machine is under real memory pressure, and 5.6 GB of compressed pages are sitting in zram. The free -h “Swap: 23Gi” total is the sum of both devices and is misleading on its own — it looks like generous headroom, but 15.3 GB of it consumes the very RAM that is already short.

This is a diagnostic worth its own blog section: on a zram system, free alone will mislead you. Always pair it with swapon --show.

Windows partitions

lsblk -o NAME,SIZE,FSTYPE,LABEL,PARTLABEL,PARTTYPE,MOUNTPOINTS | grep -Ei 'ntfs|Microsoft|Windows|basic'

PARTTYPE is the field that makes this section work — it prints the GPT partition type GUID, which is authoritative where a label is just a string someone can rename:

  • c12a7328-f81f-11d2-ba4b-00a0c93ec93b — EFI System Partition
  • e3c9e316-0b5c-4db8-817d-f92df00215ae — Microsoft Reserved
  • ebd0a0a2-b9e5-4433-87c0-68b6b72699c7 — Basic Data (the Windows install)
  • de94bba4-06d1-4d40-a16a-bfd50179d6ac — Windows Recovery

None of the Windows partitions are mounted, which is the correct and safe state for a dual-boot machine using Windows fast startup.

GPU and network

lspci | grep -Ei 'vga|3d|display'
ip -br addr

GPU: Intel TigerLake-LP GT2 (Iris Xe Graphics), integrated only — no discrete GPU, so no NVIDIA driver or hybrid-graphics complications to account for.

ip -br addr (brief mode) is the underused one here. It collapses per-interface output to one line each, which is what you want in a diagnostic dump rather than the eight-line-per-interface default. It showed Wi-Fi <WIFI_INTERFACE> up on 192.168.1.x/24 with two global IPv6 addresses, an inactive wwan0 (the WWAN modem), and — notably — tun0 at 10.x.x.x/27, an active VPN tunnel. A VPN interface showing up in a routine diagnostic is exactly the sort of thing you want to know before debugging anything network-shaped.


What failed, and why

This is the half of a diagnostic run that people leave out of writeups, and it is the more instructive half.

Everything behind sudo returned nothing

Five sections came back with only sudo: a password is required:

  • sudo fdisk -l — partition tables
  • sudo efibootmgr -v — EFI boot entries and their order
  • sudo find /boot -maxdepth 2 -type f — bootloader files on disk
  • sudo pvs / sudo vgs / sudo lvs — LVM
  • sudo btrfs filesystem show / subvolume list / — btrfs

Cause: the script ran in a non-interactive context with no TTY available for a password prompt. sudo had no way to ask. Using sudo -n (non-interactive) makes this failure explicit and immediate rather than hanging, which is the right behaviour in a script — it fails in a millisecond with a clear message instead of blocking on an invisible prompt.

The loss is real. efibootmgr -v was the one that would have answered the two-ESP question definitively: it lists the firmware’s actual boot entries, in order, with the EFI binary path each one points to. That is how you learn whether the firmware is booting systemd-boot from p5 or something stale on p1. The workaround is simply to run those four commands yourself in an interactive shell:

sudo fdisk -l
sudo efibootmgr -v
sudo find /boot -maxdepth 2 -type f | sort

Lesson for the blog: split a diagnostic script into a no-privilege half and a sudo half. Run the first anywhere; run the second interactively. Mixing them means a single missing password silently guts a third of your report.

Invalid lsblk column

The first lsblk invocation requested a PARTTUUIDS column. No such column exists — the real one is PARTUUID (and PARTTYPE for the type GUID). lsblk rejects the whole invocation when any single column name is unknown, so that command would have produced nothing at all.

The || fallback to a shorter column list is what saved the section. Worth knowing: lsblk --help lists every valid column name, and checking there first is cheaper than debugging an empty section.

sudo du -sh / — deliberately skipped

The script included a full-filesystem disk usage walk. It was dropped on purpose: du over an entire root filesystem stats every inode on the disk, which takes minutes on a 147 GB filesystem and produces one number that df -hT already reported instantly and accurately.

du earns its keep when you need a breakdown — du -xh --max-depth=1 / | sort -h is the version actually worth running, where -x keeps it from wandering into other filesystems. A bare du -sh / is the worst of both: slow and uninformative.

/etc/default/grub matched nothing

grep -E '^(GRUB|KERNEL|BOOT|ROOT|ESP|SWAP)=' /etc/default/grub

Zero matches — and that null result is itself a finding. Pop!_OS uses systemd-boot, not GRUB, on UEFI installs. The file either does not exist or is a vestigial stub. A blank result here was the first hint; bootctl status (not in the original script, and the right tool for a systemd-boot machine) would confirm it.

Lesson: a diagnostic tuned for one distro’s assumptions quietly returns nothing on another. Probe for what is actually there (bootctl alongside grub-install --version) rather than assuming.


Summary of findings

  • Dual-boot HP EliteBook 840 G8: Windows (217.2 GB NTFS) + Pop!_OS 24.04 (150.4 GB ext4), UEFI.
  • Two ESPs — a 200 MB Windows one and a 1 GB Pop one; only the latter is mounted and live. The main hazard on this machine.
  • Bootloader is systemd-boot, not GRUB, so GRUB-based repair advice does not apply.
  • ESP is 43% full — fine now, worth watching across kernel updates.
  • Memory pressure is the live problem: 12 GB of 15 GB used, 2.6 GB available, 5.6 GB of compressed pages in zram. The 8 GB disk swapfile is completely untouched because zram’s priority 1000 beats its -1.
  • Root filesystem 61% full (85 GB of 147 GB).
  • Intel Iris Xe integrated graphics only.
  • An active VPN tunnel (tun0, 10.x.x.x/27) alongside Wi-Fi.
  • Roughly 107 GB of the disk unaccounted for by partition sizes — needs sudo fdisk -l to confirm.

Note on provenance: the command output documented here was captured on the Pop!_OS 24.04 install described above (kernel 7.0.11-76070011-generic). This file was written from a later shell reporting a Fedora kernel (6.19.10-300.fc44), consistent with an in-progress Pop!_OS to Fedora migration on the same hardware. Re-run the script after the migration settles if you want a current baseline — the two-ESP and zram findings in particular are worth re-checking on Fedora.