Linux & UX

A quote at the top of every terminal

The idea was small enough to look easy. The implementation, like a lot of small ideas, turned out to need more care than expected.

Ramazan Yavuz
Ramazan Yavuz ·

I wanted a daily quote at the top of every new terminal. There is nothing technically interesting about that. You can do it in a one-line shell hook with curl and a public quotes API in 30 seconds, which I did, and within a week I had three problems. Opening a terminal sometimes blocked for two seconds because the API was slow. On planes I had no quote at all. My one-liner did not survive my different shell init across different machines. So I built herald, which is the same idea done the boring way: a small daemon, a CLI, a systemd timer, and a cache file. None of those parts are clever. The interesting work was in deciding which parts to have at all.


The naive shell hook makes a network request every time you open a terminal. Dozens of requests a day for a piece of information that changes once. The API is free and the operator is generous, but this is rude. The request can also fail or be slow: either you block the shell waiting on it, which is bad, or you background it and the quote appears halfway through your prompt, which is worse. And the quote should be the same all day across every terminal. If I open three terminals and get three different quotes, the experience is wrong. The quote is supposed to be a small, calm consistency.

Separating the fetch from the display fixes all three. Fetch happens on a timer, in the background, into a cache file. Display reads the cache file, which is fast and offline-friendly and consistent across shells.


A systemd timer (herald-refresh.timer) runs every three hours by default and writes a JSON object to /var/cache/herald/today.json. The fetch hits zenquotes.io with a 1.5-second timeout. If it fails, it falls back to a bundled local pool of about 30 curated quotes shipped with the package, so the tool always works offline. The terminal greeting reads the cache. The login MOTD reads the cache. Neither does any network work. Both are fast, both are consistent, both are deterministic.

The refresh cadence is configurable in hours. sudo herald set-refresh 24 for once a day, sudo herald set-refresh 168 for once a week, sudo herald set-refresh 0 to disable online fetching entirely and rotate through the local pool only. sudo herald refresh forces it. The timer is a single-line override of the unit file, no clever scheduling logic.


Showing a quote at the top of every interactive shell sounds like the same problem as showing it at SSH login. It is not. Interactive shells on Ubuntu source /etc/profile.d/ via the login flow, but a new GUI terminal tab is often not a login shell, so /etc/profile.d/ is not sourced. I had to inject a small marker block into the user's ~/.bashrc that explicitly sources the profile.d snippet. That bashrc block is owned by herald, and sudo herald terminal off removes it.

SSH, console, and display-manager logins use the update-motd system, which means dropping a script in /etc/update-motd.d/. That requires the update-motd package, which on Debian and Ubuntu is a Suggests, not Recommends. So laptop-only users do not pull it in by default. The CLI warns at runtime if you enable MOTD on a system without it.

Both surfaces are off by default. sudo herald terminal on and sudo herald motd on toggle them independently. Their independence matters: I want the quote on every new terminal but not in my SSH banner. Someone else might want the opposite.


Herald fetches from a third-party service. I do not control the content. If zenquotes serves a quote I find tasteless, it will show up on someone's terminal, and the README is loud about that. Disable online fetching with set-refresh 0 if you want to rely on the bundled pool only.

Online fetching is on by default, with a clear opt-out, rather than the other way around. The offline pool by itself gets stale quickly: 30 quotes rotating start to feel familiar within a week, and the online refresh is what keeps things fresh. The compromise is that the install is loud about what it does, and turning the network off is one command.


The thing I keep coming back to is how much complexity hides inside small utilities once you take their non-obvious failure modes seriously. The first version of herald was 20 lines of shell. The shipped version is a daemon, a timer, a cache, two integration paths, a fallback pool, and a CLI. None of that is overkill. The complexity exists because shell startup matters, network failures happen, login banners and shell greetings have different plumbing, and a feature that works on my laptop but breaks on someone else's headless server is not really a feature.

There is no novel algorithm in herald. The interesting work was deciding when to fetch, when to display, what to do when the network fails, and how to keep two display surfaces in sync. Boring problems, well solved, are the bulk of any software that lives on people's machines. If your terminal greets you with a Knuth quote in the morning and you do not have to think about it ever, herald is doing its job.