Uptime Kuma Widget widget screenshot

Script

Uptime Kuma Widget

by Lari Kovanen

Uptime Kuma widget

uptime-kuma.sh shows the status of every monitor on an Uptime Kuma server as a desktop widget, using the Terminal Widget CLI.

What it shows

State Background Content
All monitors OK Green ✓ 6/6 OK
One or more down Red ⚠ 5/6 OK, then a Down table listing each down monitor by name
Problem fetching Grey ⚠ an error message (see Troubleshooting)
  • Monitors that are pending or in maintenance count as OK. Only down monitors turn the widget red.
  • Every state shows when the widget was last updated.
  • Clicking the widget opens the Uptime Kuma dashboard.

How it works

The script reads Uptime Kuma’s Prometheus endpoint, $KUMA_URL/metrics, which needs an API key. It reads the monitor_status value for each monitor (0 = down, 1 = up, 2 = pending, 3 = maintenance) and updates the Terminal Widget target uptime-kuma.

It uses only tools that come with macOS: curl, awk, jq and security. No status page has to be published.

The script updates the widget once each time it runs. It’s meant to be run every minute by a LaunchAgent (see Running it every minute).

Requirements

  • Terminal Widget installed in /Applications, with a widget whose Target name is uptime-kuma.
  • An Uptime Kuma server with API keys (version 1.21 or later).
  • macOS 15 or later, which includes /usr/bin/jq.

Configuration

Variable Required Description
KUMA_URL yes Server address, e.g. https://uptime.example.com. A trailing slash is removed.
KUMA_API_KEY no API key. If unset, the key is read from the login keychain (recommended).

Storing the API key in the keychain

  1. In Uptime Kuma, open Settings → API Keys, create a key and copy it.

  2. Add it to your login keychain:

    security add-generic-password -U -s uptime-kuma-widget -a api-key -w
    

    security prompts for the key, so it doesn’t end up in your shell history. -U replaces an existing key, so you can run the same command again to rotate it.

  3. Check that it can be read:

    security find-generic-password -s uptime-kuma-widget -a api-key -w
    

The first time the script reads the key in the background, macOS may ask whether security may use the uptime-kuma-widget item. Click Always Allow.

The script passes the key to curl through standard input, so it never appears on a command line or in ps. To remove the key:

security delete-generic-password -s uptime-kuma-widget -a api-key

Running it every minute

Use a LaunchAgent, not cron. Cron jobs run outside your login session and can’t read the login keychain. A LaunchAgent runs inside your session, so it can.

Save the following as ~/Library/LaunchAgents/local.terminal-widgets.uptime-kuma.plist. Change the script path and KUMA_URL to match your setup.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>Label</key>
	<string>local.terminal-widgets.uptime-kuma</string>
	<key>ProgramArguments</key>
	<array>
		<string>/bin/bash</string>
		<string>/Users/USERNAME/.config/terminal-widgets/uptime-kuma.sh</string>
	</array>
	<key>EnvironmentVariables</key>
	<dict>
		<key>KUMA_URL</key>
		<string>https://uptime.example.com</string>
	</dict>
	<key>StartInterval</key>
	<integer>60</integer>
	<key>RunAtLoad</key>
	<true/>
	<key>ProcessType</key>
	<string>Background</string>
	<key>StandardErrorPath</key>
	<string>/Users/USERNAME/Library/Logs/uptime-kuma-widget.log</string>
</dict>
</plist>

Don’t put the API key in EnvironmentVariables. The plist is plain text that any process running as you can read.

Load it, which also runs it once right away:

launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/local.terminal-widgets.uptime-kuma.plist

After editing the plist, reload it:

launchctl bootout gui/$(id -u)/local.terminal-widgets.uptime-kuma
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/local.terminal-widgets.uptime-kuma.plist

To stop it, run only the bootout line and delete the plist.

Running it by hand

KUMA_URL=https://uptime.example.com ./uptime-kuma.sh

Troubleshooting

Widget message Cause
KUMA_URL not set KUMA_URL is missing from the environment or the plist.
No Uptime Kuma API key The key isn’t in the keychain, or the script runs where the keychain can’t be read (e.g. cron).
Can't reach Uptime Kuma Wrong URL, server down, network problem, or the API key was rejected.
No monitors found /metrics returned no monitors.

To check the LaunchAgent and its error log:

launchctl print gui/$(id -u)/local.terminal-widgets.uptime-kuma | grep -E 'state|last exit'
cat ~/Library/Logs/uptime-kuma-widget.log

A last exit code of 0 means the last run succeeded.

Limitations

  • Monitors are matched by name, so two monitors with the same name count as one.
  • /metrics only reports current status, so the widget shows which monitors are down, not for how long.

Script (uptime-kuma.sh)

#!/bin/bash
# Uptime Kuma status widget.
# Reads the /metrics endpoint, which needs an API key (Settings > API Keys).
# The key is taken from $KUMA_API_KEY, or else from the login keychain:
#   security add-generic-password -U -s uptime-kuma-widget -a api-key -w
# The server address comes from $KUMA_URL (no trailing slash), e.g. set in the
# LaunchAgent plist's EnvironmentVariables.
TW=/Applications/TerminalWidget.app/Contents/MacOS/TerminalWidget
TW_TARGET="uptime-kuma"
KUMA_URL="${KUMA_URL%/}"
KUMA_API_KEY="${KUMA_API_KEY:-$(/usr/bin/security find-generic-password -s uptime-kuma-widget -a api-key -w 2>/dev/null)}"

OK_BG="#15803d"
DOWN_BG="#b91c1c"
ERR_BG="#475569"
FG="#f8fafc"

show_error() {
  "$TW" --target $TW_TARGET \
    --icon exclamationmark.triangle.fill \
    --text "$1" \
    --fit-text \
    --timestamp \
    --action-kind open-url --action-value "$KUMA_URL/dashboard" \
    --bg "$ERR_BG" \
    --fg "$FG"
  exit 1
}

[ -n "$KUMA_URL" ] || show_error "KUMA_URL not set"
[ -n "$KUMA_API_KEY" ] || show_error "No Uptime Kuma API key"

# Key is passed via a curl config on stdin so it doesn't show up in `ps`.
metrics=$(printf 'user = ":%s"\n' "$KUMA_API_KEY" \
  | /usr/bin/curl -fsS --max-time 15 -K - "$KUMA_URL/metrics") \
  || show_error "Can't reach Uptime Kuma"

# Output records:  S <ok> <total>  |  D <name>
# monitor_status values: 0 down, 1 up, 2 pending, 3 maintenance.
records=$(printf '%s\n' "$metrics" | /usr/bin/awk '
  BEGIN { OFS = "\t" }
  /^monitor_status\{/ {
    if (!match($0, /monitor_name="([^"\\]|\\.)*"/)) next
    name = substr($0, RSTART + 14, RLENGTH - 15)
    gsub(/\\"/, "\"", name)
    gsub(/\\\\/, "\\", name)
    if (name in seen) next
    seen[name] = 1
    value = $0
    sub(/.*[ \t]/, "", value)
    total++
    if (value + 0 != 0) ok++
    else print "D", name
  }
  END { print "S", ok + 0, total + 0 }
')

read -r _ ok total <<< "$(grep '^S' <<< "$records")"
down_rows=$(grep '^D' <<< "$records" | cut -f 2- | sort -f)

[ "$total" -gt 0 ] || show_error "No monitors found"

if [ -z "$down_rows" ]; then
  "$TW" --target $TW_TARGET \
    --icon checkmark.circle.fill \
    --text "$ok/$total OK" \
    --fit-text \
    --timestamp \
    --action-kind open-url --action-value "$KUMA_URL/dashboard" \
    --bg "$OK_BG" \
    --fg "$FG"
else
  # One-column table as JSON; TSV/CSV detection needs a tab or comma.
  printf '%s\n' "$down_rows" \
  | /usr/bin/jq -Rn '[["Down"]] + [inputs | [.]]' \
  | "$TW" --target $TW_TARGET \
      --table - \
      --grid zebra-row \
      --table-layout fill \
      --icon exclamationmark.triangle.fill \
      --text "$ok/$total OK" \
      --timestamp \
      --action-kind open-url --action-value "$KUMA_URL/dashboard" \
      --bg "$DOWN_BG" \
      --fg "$FG"
fi

Download script

Running in the background with launchd

Suggested interval: 1 minute (StartInterval = 60 seconds).

Save the script from this recipe to ~/bin/uptime-kuma-widget.sh, then create a Launch Agent plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>com.terminalwidget.uptime-kuma-widget</string>
  <key>ProgramArguments</key>
  <array>
    <string>/bin/bash</string>
    <string>~/bin/uptime-kuma-widget.sh</string>
  </array>
  <key>RunAtLoad</key>
  <true/>
  <key>StartInterval</key>
  <integer>60</integer>
  <key>StandardOutPath</key>
  <string>/tmp/com.terminalwidget.uptime-kuma-widget.log</string>
  <key>StandardErrorPath</key>
  <string>/tmp/com.terminalwidget.uptime-kuma-widget.err</string>
</dict>
</plist>

From Terminal:

mkdir -p ~/bin ~/Library/LaunchAgents
# Save your script to ~/bin/uptime-kuma-widget.sh
cat > ~/Library/LaunchAgents/com.terminalwidget.uptime-kuma-widget.plist <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>com.terminalwidget.uptime-kuma-widget</string>
  <key>ProgramArguments</key>
  <array>
    <string>/bin/bash</string>
    <string>~/bin/uptime-kuma-widget.sh</string>
  </array>
  <key>RunAtLoad</key>
  <true/>
  <key>StartInterval</key>
  <integer>60</integer>
  <key>StandardOutPath</key>
  <string>/tmp/com.terminalwidget.uptime-kuma-widget.log</string>
  <key>StandardErrorPath</key>
  <string>/tmp/com.terminalwidget.uptime-kuma-widget.err</string>
</dict>
</plist>
EOF
launchctl bootstrap "gui/$(id -u)" ~/Library/LaunchAgents/com.terminalwidget.uptime-kuma-widget.plist

For a GUI editor and troubleshooting, see LaunchControl from soma-zone.

← All recipes