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 daemon, a 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 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.


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 daemon, 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.