Know the moment a systemd unit fails — from your Omarchy bar.
Invisible while everything is fine. One click to restart, read the log, or clear the state.
Install · Using it · Configuration · Standalone · How it works
A backup timer that quietly stopped running two weeks ago is the classic Linux
failure: nothing tells you, because nothing is broken — a unit just sits in
the failed state until the day you need what it was supposed to produce. On a
graphical session there is no equivalent of the systemctl --failed you'd
never think to type.
This widget is that check, running for you. It stays off the bar entirely while everything is healthy, and shows up the moment something isn't.
- Both managers — system units and your own user units, which is where syncthing, pipewire and your personal timers live
- Quiet by default — takes no bar space at all until something fails
- One notification per failure — the first time each unit breaks, not once a minute forever, and never a storm about units already failed at startup
- Act from the panel —
Enterrestarts,lopens the journal in a floating terminal,xclears the failed state without restarting,cclears them all - Native authentication — a system unit restart goes through plain
systemctl, so systemd asks polkit and Omarchy's own agent puts up the prompt; no second password path, nopkexecwrapper - Right action for timer jobs — a oneshot job that failed once and will run again on schedule wants its state cleared, not restarted; both are one key
- Ignore list — silence the units you have already decided not to care
about, with
prefix*matching - Keyboard-navigable panel —
j/kto move,rto re-check,Escto close - Cheap — one short-lived process a minute, covering both scopes and the overall manager state in a single call
- Theme-native — built from Omarchy's own bar and panel components
- Reusable core —
UnitsCore.qmlhas no Omarchy dependency and works in any Quickshell config
omarchy plugin add https://github.com/tpatzelt/omarchy-systemd-widget.git --enable --yesThat clones into ~/.config/omarchy/plugins/tpatzelt.systemd/ and adds the
widget to the right-hand bar section. Move it wherever you like:
omarchy bar move tpatzelt.systemd --section rightPlugins run as unsandboxed code inside
omarchy-shell. Omarchy deliberately installs them disabled unless you pass--enable, so you can read the source first. It's two short QML files.
Nothing on the bar after installing is the expected result — it means no
unit has failed. Set hideWhenHealthy: false if you'd rather see the all-clear
check, or make something fail to try it:
systemd-run --user --unit=oops-test /bin/falseThen clear it again with x in the panel.
git clone https://github.com/tpatzelt/omarchy-systemd-widget.git \
~/.config/omarchy/plugins/tpatzelt.systemd
omarchy-shell shell rescanPlugins
omarchy plugin enable tpatzelt.systemdomarchy plugin update tpatzelt.systemdomarchy plugin remove tpatzelt.systemd| Action | What it does |
|---|---|
| Left-click | Open the panel |
| Right-click | Re-check now (or run your own command — see onRightClick) |
| Middle-click | Re-check now |
| Hover | What failed, or how many things did |
j / k, ↓ / ↑ |
Move between failed units |
Enter |
Restart the selected unit |
l |
Open its journal in a floating terminal |
x |
Clear its failed state without restarting it |
c |
Clear every failed state |
r |
Re-check now |
Tab |
Move to the next bar panel |
Esc |
Close |
The panel can also be opened from anywhere:
omarchy-shell tpatzelt.systemd toggle- Restart (
Enter) for anything that is supposed to be running now — a daemon that crashed, a mount that dropped. - Clear (
x) for a oneshot or timer-driven job that failed once and will run again on its own schedule. Restarting it runs the job immediately, which is usually not what you want at 3pm for a job that belongs at 3am.
Settings live inline in the widget's entry in ~/.config/omarchy/shell.json.
Changes there hot-reload; you do not need to restart the shell.
{
"id": "tpatzelt.systemd",
"hideWhenHealthy": false,
"ignore": ["snapper-cleanup.service", "user@*"],
"notify": true
}| Key | Default | What it does |
|---|---|---|
interval |
60 |
Seconds between checks |
hideWhenHealthy |
true |
Take no bar space while nothing has failed |
watchSystem |
true |
Watch the system manager |
watchUser |
true |
Watch your user manager |
notify |
true |
One desktop notification the first time each unit fails |
showCount |
true |
Append the number of failures when there is more than one |
ignore |
[] |
Unit names to skip. A trailing * matches a prefix |
onJournal |
"omarchy-launch-floating-terminal-with-presentation" |
Run with a journalctl invocation appended. Empty hides the log button |
onRightClick |
"" |
Empty means right-click re-checks |
failedIcon |
"" |
Nerd Font glyph shown when something has failed |
healthyIcon |
"" |
Glyph for the all-clear state |
UnitsCore.qml is plain QtQuick plus Quickshell.Io — no Omarchy imports, no
compositor assumptions. Copy it next to your own shell.qml and bind to it:
UnitsCore {
id: sysd
intervalMs: 60000
onUnitFailed: function (unit, scope, description) {
console.log("failed:", scope, unit, description)
}
}A runnable example lives in examples/standalone:
./scripts/run-example.shQuickshell only scans QML inside the config directory it is pointed at, so
UnitsCore.qml has to sit next to the shell.qml that uses it — the script
assembles a temporary directory containing both.
| Property | Type | Notes |
|---|---|---|
ready |
bool |
False until the first check has completed |
units |
array |
{ unit, load, active, sub, description, scope }, system scope first |
count |
int |
Number of failed units |
healthy |
bool |
Ready and nothing failed |
systemState |
string |
running, degraded, starting, … |
busy / actionUnit / actionKind |
An action in flight, attributed to its unit | |
lastError / lastErrorUnit |
string |
Last failure, cleared after 8s |
unitFailed(unit, scope, description) |
signal | Fires once per newly failed unit |
restart(unit, scope) |
||
resetFailed(unit, scope) / resetAll() |
||
journalCommand(unit, scope) |
string |
A ready-to-run journalctl invocation |
One process, both scopes. systemctl --failed for the system manager, the
same for --user, and is-system-running, all in a single bash -c separated
by a control character no unit name or description can contain. Three separate
processes could land out of order and paint a frame where one scope's results
are applied against the other's.
Four fields, then the rest. Each line is UNIT LOAD ACTIVE SUB DESCRIPTION,
and only the description can contain spaces — so the parser splits off exactly
four fields and keeps the remainder whole rather than splitting on every space.
Notifications are edge-triggered. The set of failed unit keys from the
previous poll is kept, and unitFailed fires only for keys that weren't in it.
The first poll only seeds that set, so restarting the shell on an
already-degraded machine doesn't re-announce everything.
Restarts go through polkit, not pkexec. systemctl restart on a system unit
asks polkit, and the session's agent — Omarchy ships one — puts up the prompt.
Wrapping it in pkexec would work too, but it would mean a second, different
authentication dialog for the same operation.
An action's exit only counts while an action is running. Process teardown
during a settings hot-reload also fires exited(); treating that as a failure
would latch the bar icon in the theme's urgent red.
- Omarchy 4 (Quattro) or newer, with
omarchy-shell systemctlandjournalctl— you already have themnotify-sendfor notifications (libnotify), or setnotify: false- A Nerd Font for the glyphs (Omarchy's default bar font has them)
- omarchy-sysmon — CPU, memory and network throughput
- omarchy-diskspace — filesystem usage with a warning threshold
- omarchy-vpn-widget — NetworkManager VPN status and switching
MIT — see LICENSE.