Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

crest

A login banner that draws a different mark every time — and is still, recognizably, your machine's mark.

 ╭─ system health   solteris-tower · Fri 19:01     ██▒▒·    ·▒▒██
 │                                                 ▓░█▒·    ·▒█░▓
 │  uptime   8 hours, 42 minutes
 │  load     2.28  2.14  2.16  (24 cores)
 │  pressure cpu 0.00%   mem 0.00%   io 50.80%
 │  cpu      57°C     powersave
 │  memory   25%      16.0 / 62.4 GiB
 │  gpu      44°C     13% util   1.1 / 31.9 GiB   31W   894rpm   Radeon AI PRO R9700
 │  vm       ● win2k22   running   6 vcpu · 7.8 GiB
 │           passthrough: GeForce GTX 1060 6GB  ·  looking-glass 64 MiB
 │  disk     / 73%  ·  /home 90%  ·  ws-hdd1 69%
 │  ssd      ● 5 ok   worst life 98%   (0m ago)
 │  services 0 failed
 │  updates  53 pending
 │  vpn      Disconnected
 ╰─

Runs unprivileged, finishes in ~0.2s, and never blocks a shell from appearing — including the rows backed by the network.

The crest

The two-line block in the top-right regenerates on every render. One shot, pure bash arithmetic, no subprocess. Lineage, honestly stolen from four traditions:

  • OpenSSH randomart — the mechanism. A drunken-bishop walk staggers across a grid; visit counts render as a density ramp. Randomart's purpose was "recognize this machine at a glance," which is a login banner's job description.
  • GitHub identicons — mirror symmetry, the one trick that makes random data look designed rather than like line noise.
  • Urbit sigils — the seed philosophy: pure randomness is undifferentiated mush, so identity has to anchor it. The stable half of the seed hashes /etc/machine-id; the volatile half mixes uptime, loadavg and $RANDOM. Every render differs, every render is still this box. Another machine yields a visibly different family of crests.
  • Roguelike worldgen — the ·░▒▓█ shade ramp.

The crest draws from its own violet palette lane. Green/yellow/red are reserved for data — decoration never wears the colors that mean something, and nothing decorative is shaped like a chart.

Rows

Local — no network, no privileges:

Row Source
uptime load memory /proc
pressure /proc/pressure/* — PSI some avg10
cpu sensors; AMD Tctl/Tdie and Intel Package id 0
gpu amdgpu sysfs: temp, util, VRAM, watts, fan. One row per card; cards bound to vfio-pci are skipped — they belong to a guest
vm libvirt over the read-only socket, plus passthrough GPUs and the looking-glass buffer
disk df over SH_DISKS
ssd SMART from a root-written cache (see smart/)
services systemctl --failed
updates checkupdates, cached
reboot shown only when needed
vpn mullvad status

Two notes on why those last few are shaped as they are.

pressure exists because load average lies. On a 24-core box a load of 2 looks idle while I/O is stalled half the time — PSI measures the share of time work spent waiting on a resource, which is the number you actually want when the machine feels bad but load looks fine.

reboot renders only when a reboot is genuinely required, because a row that says "no" every day trains you to stop reading it. The load-bearing check is whether /usr/lib/modules/$(uname -r) still exists: if the running kernel's modules are gone you can no longer modprobe anything, which on a vfio/amdgpu box you will discover at the worst possible moment. A pacman version comparison backs it up.

The wire — network-backed, all off by default:

Row Source Default TTL
sites any URL; status code + latency 300s
weather open-meteo — no API key, no account 1800s
live Twitch via decapi, YouTube via channel page 120s / 1800s

Remote content, and why the wire is built the way it is

Three rules, none of them optional.

Rendering never touches the network. A wire row prints whatever is in its cache file and, if that file is stale, forks a bounded refresh whose result lands on the next login. The fetch cost is never paid at the prompt. This is the same pattern the updates row has always used.

A failed fetch must not destroy a good cache. The refresh captures output first and only replaces the cache if the fetch produced something. Piping straight into the sanitizer would hand the pipeline the sanitizer's exit status, so a failed fetch would cheerfully truncate a perfectly good value. On failure the previous reading stays: a reading 40 minutes old beats no reading, and a network error must never appear where data belongs. Because the cache's mtime only advances on success, the age the volatile rows print is honest — live shows (3m ago), since "live" checked 25 minutes ago is a materially different claim from "live" checked a minute ago.

Sanitize remote bytes before they reach a terminal. Everything fetched is stripped of control characters on write, at the single point where untrusted input enters. Terminals execute escape sequences: piping a remote server's raw bytes into every login shell hands that server your cursor, your title bar, and on some emulators considerably more. This is a real and well-documented class of bug, not a hypothetical. If you add a wire row, route it through wire_sane — and if you fetch something that isn't plain text, sanitize the extracted field, not the envelope.

Two honest caveats about the live row:

  • The YouTube check is a scrape, keyed on a marker ~700 KB into the channel page, so a partial fetch can't shortcut it (~150–190 KB compressed per channel per refresh — mind SH_TTL_YOUTUBE before adding many channels). If the page still parses as a channel page but the live marker is absent, that's a real "offline"; if the page doesn't parse at all, the row reports unknown rather than confidently claiming offline. A scrape that silently lies is worse than one that admits confusion.
  • decapi is a third party. It never learns who you are, but it does learn which channels you watch. Swap in the official Helix API if that matters to you.

Install

install -m 755 sys-health ~/.local/bin/

Call it from ~/.bashrc for top-level interactive shells only:

if [ -z "$TMUX" ] && [ "${SHLVL:-1}" -eq 1 ] && command -v sys-health >/dev/null 2>&1; then
  sys-health
fi

For the SMART row (optional, needs root once via a timer):

sudo install -m 755 smart/sys-health-smart-collect /usr/local/bin/
sudo install -m 644 smart/sys-health-smart.{service,timer} /etc/systemd/system/
sudo install -d /var/lib/sys-health
sudo systemctl enable --now sys-health-smart.timer

Disks are autodetected (ATA and NVMe both understood); override with SMART_DEVS="sda nvme0n1" in /etc/sys-health/smart.conf.

Configuration

Everything lives in ~/.config/sys-health/config, sourced as shell — so the script itself never needs editing and stays a git pull from upstream.

SH_DISKS=(/ /home /mnt/bulk)

SH_WIRE=1
SH_SITES=("https://example.com")
SH_WEATHER="47.61,-122.33"          # lat,lon
SH_TWITCH=("channelname")
SH_YOUTUBE=("UCxxxxxxxxxxxxxxxxxxxxxx=LofiGirl")   # channel ID, not @handle

Live entries accept name=Label; the label is what renders. Worth using for YouTube, whose channel IDs are 24 unreadable characters. The live row wraps onto continuation rows rather than overrunning the pane, so following several channels stays readable at any width.

Variable Default Effect
SH_CREST 1 generative header mark
SH_VM 1 libvirt guests + passthrough
SH_SMART 1 SMART row
SH_DISKS (/ /home) df targets
SH_WIRE 0 master switch for all network rows
SH_WIRE_TIMEOUT 8 hard ceiling on any one request
SH_TTL_SITES 300 per-row refresh intervals — these endpoints
SH_TTL_WEATHER 1800 differ by ~1000× in weight, so one global
SH_TTL_TWITCH 120 TTL would be wrong in both directions at once
SH_TTL_YOUTUBE 1800

SYS_HEALTH_CREST / SYS_HEALTH_VM still work as env overrides.

Narrow terminals

The layout measures the pane and sheds optional segments narrowest-first rather than wrapping and shredding the gutter:

Width Behavior
< 92 per-drive SMART collapses to ● 5 ok · worst life 98%
< 88 GPU card name drops
< 78 site latency drops
< 74 looking-glass segment drops
< 66 fan rpm drops; crest sits out — a wrapped crest is worse than none

No line ever exceeds the terminal width.

Adapting it

This is my banner, built for my machine. A fork will want to look at the vpn row (expects mullvad) and the news row (expects informant) — both self-skip when the command is absent. Everything else is config.

See ROADMAP.md for what's next.

License

MIT

About

A login banner that draws a different mark every time — and is still, recognizably, your machine's mark

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages