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 ·
A quote at the top of every terminal

I wanted a daily quote at the top of every new terminal. A one-line shell hook with curl and a public quotes API handled it in 30 seconds. 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 the different shell initialisation on my machines. So I built herald, the same idea implemented as a small CLI, a systemd timer, and a cache file. Choosing those few parts was most of the design work.


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 /var/cache/herald/today.json. The implementation checked on 15 October 2026 fetches a random ZenQuotes quote with a 1.5-second timeout, then falls back to the bundled pool on failure. Terminals share the cached quote until the next refresh; it is not guaranteed to stay the same all day. Normal display reads the cache, though a root invocation with no cache can trigger a fetch.

The refresh cadence is configurable in hours. sudo herald set-refresh 24 schedules a daily refresh and sudo herald set-refresh 168 a weekly one. sudo herald set-refresh 0 disables the timer; it does not enable offline rotation or block network requests. sudo herald refresh still attempts an online fetch. The local pool is a fallback when fetching fails, not an offline-only mode selected by that command.


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 and console logins may use PAM's message-of-the-day integration, depending on the distribution and configuration. Graphical display managers do not necessarily show a text MOTD. Herald installs a script in /etc/update-motd.d/ for that integration. The CLI checks for the supporting package when enabling 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 its content. Disabling the timer stops scheduled fetching, but it is not a network prohibition. If you need a strict offline setup, do not rely on set-refresh 0 alone; the implementation still has a manual fetch path.

Online fetching is enabled by the default timer. This is a trade-off between fresh content and a third-party dependency. The cached display keeps ordinary terminal startup independent of that request once a cache exists.


Small utilities grow once their non-obvious failure modes receive proper attention. The first version of herald was 20 lines of shell. The shipped version is a timer, a cache, two integration paths, a fallback pool, and a CLI. Each part addresses a real condition: shell startup matters, networks fail, login banners and shell greetings have different plumbing, and a feature that breaks on someone else's headless server still needs work.

Herald decides when to fetch, when to display, what to do when the network fails, and how to keep two display surfaces in sync. Those ordinary problems make up most software that lives on people's machines. If your terminal greets you with a Knuth quote in the morning and you never have to think about the machinery, herald is doing its job.