diff --git a/dot_config/systemd/user/swww.service b/dot_config/systemd/user/swww.service index 3abbf7e..c19cdf0 100644 --- a/dot_config/systemd/user/swww.service +++ b/dot_config/systemd/user/swww.service @@ -5,7 +5,7 @@ After=graphical-session.target Requisite=graphical-session.target [Service] -ExecStart=swww-daemon +ExecStart=awww-daemon Restart=on-failure [Install] diff --git a/dot_config/waybar/config.jsonc b/dot_config/waybar/config.jsonc index fcd8b9b..2cc5f98 100644 --- a/dot_config/waybar/config.jsonc +++ b/dot_config/waybar/config.jsonc @@ -18,6 +18,7 @@ "privacy", "tray", "custom/systemd-failed-units", + "custom/k8s-alerts", "gamemode", "custom/scrcpy-active", "custom/updates", @@ -51,6 +52,7 @@ "custom/sleep", "custom/garage-longhorn", "custom/garage-velero", + "custom/k8s-alerts", "clock", ], }, diff --git a/dot_config/waybar/executable_k8s-alerts.sh b/dot_config/waybar/executable_k8s-alerts.sh new file mode 100644 index 0000000..8ac9c07 --- /dev/null +++ b/dot_config/waybar/executable_k8s-alerts.sh @@ -0,0 +1,144 @@ +#!/usr/bin/env bash +set -u + +KUBECTL_TIMEOUT="${K8S_ALERTS_TIMEOUT:-8s}" +CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/waybar" +NAMESPACE_CACHE_FILE="$CACHE_DIR/alertmanager-namespace" +ICON_OK="󰄬" +ICON_ALERT="" +ICON_ERROR="" + +json_escape() { + jq -Rs . <<<"$1" +} + +emit_json() { + local text="$1" + local tooltip="$2" + local class_name="$3" + local text_json tooltip_json + + text_json="$(json_escape "$text")" + tooltip_json="$(json_escape "$tooltip")" + printf '{"text":%s,"tooltip":%s,"class":"%s"}\n' "$text_json" "$tooltip_json" "$class_name" +} + +error_out() { + local message="$1" + emit_json "$ICON_ERROR err" "$message" "error" + exit 0 +} + +for cmd in kubectl jq; do + if ! command -v "$cmd" >/dev/null 2>&1; then + error_out "Missing dependency: $cmd" + fi +done + +if ! mkdir -p "$CACHE_DIR" 2>/dev/null; then + CACHE_DIR="/tmp" + NAMESPACE_CACHE_FILE="$CACHE_DIR/waybar-alertmanager-namespace-${UID:-0}" +fi + +context="$(kubectl config current-context 2>/dev/null || true)" +if [[ -z "$context" ]]; then + error_out "No current Kubernetes context configured." +fi + +discover_namespace() { + kubectl --request-timeout="$KUBECTL_TIMEOUT" get svc -A -o json 2>/dev/null \ + | jq -r '[.items[] | select(.metadata.name == "alertmanager-operated") | .metadata.namespace][0] // empty' +} + +get_namespace() { + local ns="${ALERTMANAGER_NAMESPACE:-}" + if [[ -n "$ns" ]]; then + printf '%s\n' "$ns" + return 0 + fi + + if [[ -r "$NAMESPACE_CACHE_FILE" ]]; then + ns="$(<"$NAMESPACE_CACHE_FILE")" + if [[ -n "$ns" ]]; then + printf '%s\n' "$ns" + return 0 + fi + fi + + ns="$(discover_namespace)" + if [[ -n "$ns" ]]; then + printf '%s\n' "$ns" >"$NAMESPACE_CACHE_FILE" 2>/dev/null || true + printf '%s\n' "$ns" + return 0 + fi + + return 1 +} + +fetch_alerts() { + local ns="$1" + kubectl --request-timeout="$KUBECTL_TIMEOUT" get --raw "/api/v1/namespaces/${ns}/services/http:alertmanager-operated:9093/proxy/api/v2/alerts" 2>/dev/null +} + +namespace="$(get_namespace || true)" +if [[ -z "$namespace" ]]; then + error_out "Cannot find Alertmanager service (alertmanager-operated)." +fi + +alerts_json="$(fetch_alerts "$namespace" || true)" +if [[ -z "$alerts_json" ]]; then + refreshed_namespace="$(discover_namespace || true)" + if [[ -n "$refreshed_namespace" && "$refreshed_namespace" != "$namespace" ]]; then + namespace="$refreshed_namespace" + printf '%s\n' "$namespace" >"$NAMESPACE_CACHE_FILE" 2>/dev/null || true + alerts_json="$(fetch_alerts "$namespace" || true)" + fi +fi + +if [[ -z "$alerts_json" ]]; then + error_out "Cannot connect to cluster or Alertmanager API (context: $context)." +fi + +if ! jq -e . >/dev/null 2>&1 <<<"$alerts_json"; then + error_out "Invalid JSON returned by Alertmanager API." +fi + +read -r active_count watchdog_running <<<"$(jq -r ' + [.[] | select(.status.state == "active")] as $active + | ($active | map(select((.labels.alertname // "") != "Watchdog" and (.labels.alertname // "") != "InfoInhibitor")) | length) as $visible + | ($active | map(select((.labels.alertname // "") == "Watchdog")) | length > 0) as $watchdog + | "\($visible) \($watchdog)" +' <<<"$alerts_json")" + +tooltip_lines="$(jq -r ' + [.[] + | select(.status.state == "active" and (.labels.alertname // "") != "Watchdog" and (.labels.alertname // "") != "InfoInhibitor") + | { + sev: (.labels.severity // "unknown"), + name: (.labels.alertname // "unknown"), + ns: (.labels.namespace // "-"), + msg: ((.annotations.summary // .annotations.description // .annotations.message // "") | gsub("\\n"; " ")) + } + ] + | sort_by(.sev, .name, .ns) + | if length == 0 then + "No active alerts" + else + .[] | "- [\(.sev)] \(.name) (\(.ns))\(if .msg == "" then "" else ": \(.msg)" end)" + end +' <<<"$alerts_json")" + +timestamp="$(date +"%H:%M")" +base_tooltip="context: $context\nnamespace: $namespace\nupdated: $timestamp" + +if [[ "$watchdog_running" != "true" ]]; then + emit_json "$ICON_ERROR wd" "Watchdog alert is missing.\n$base_tooltip\n\n$tooltip_lines" "error" + exit 0 +fi + +if [[ "$active_count" -gt 0 ]]; then + emit_json "$ICON_ALERT $active_count" "$base_tooltip\n\n$tooltip_lines" "warning" + exit 0 +fi + +emit_json "$ICON_OK 0" "$base_tooltip\n\n$tooltip_lines" "ok" diff --git a/dot_config/waybar/shared_modules.jsonc b/dot_config/waybar/shared_modules.jsonc index b998291..2fe7743 100644 --- a/dot_config/waybar/shared_modules.jsonc +++ b/dot_config/waybar/shared_modules.jsonc @@ -170,6 +170,13 @@ "on-click": "~/.config/waybar/restart-failed.sh", }, + "custom/k8s-alerts": { + "return-type": "json", + "exec": "~/.config/waybar/k8s-alerts.sh", + "interval": 60, + "tooltip": true, + }, + "custom/notification": { "tooltip": true, "format": "{icon}",