#!/usr/bin/env bash # Helper for the sid.scheduled-jobs bar widget. Read-only throughout. # list-jobs --list print all scheduled jobs as JSON for the panel # list-jobs peek SCOPE UNIT show a unit's recent journal in the terminal # list-jobs full plain systemctl list-timers for both scopes set -euo pipefail # Hard bounds imposed at the point of capture, before anything is held in a # shell variable or handed to the panel: at most MAX_BYTES of systemctl JSON # per scope, MAX_TIMERS timers per scope (and Result lookups), MAX_CRON_LINES # crontab lines of at most MAX_CRON_LINE_LEN characters each. MAX_BYTES=262144 MAX_TIMERS=150 MAX_CRON_LINES=100 MAX_CRON_LINE_LEN=300 timers_json() { local scope="$1" local flag=() [[ $scope == user ]] && flag=(--user) local raw raw=$(systemctl "${flag[@]}" list-timers --all --output=json 2>/dev/null | head -c "$MAX_BYTES") || raw="[]" # A byte-truncated capture is invalid JSON; treat it (and empty output) as # a bounded, well-formed list rather than passing garbage downstream. raw=$(jq --argjson n "$MAX_TIMERS" '.[0:$n]' <<<"$raw" 2>/dev/null) || raw="[]" [[ -n $raw ]] || raw="[]" # Attach each activated service's last Result (success/failed/…) so the # panel can show whether the last run worked. `systemctl show` is read-only; # the loop is bounded by MAX_TIMERS via the truncation above. local results="{}" local unit result while IFS= read -r unit; do [[ -n $unit ]] || continue result=$(systemctl "${flag[@]}" show -p Result --value "$unit" 2>/dev/null | head -c 64 || echo "") results=$(jq --arg u "$unit" --arg r "$result" '. + {($u): $r}' <<<"$results") done < <(jq -r '.[].activates // empty' <<<"$raw" | sort -u | head -n "$MAX_TIMERS") jq --arg scope "$scope" --argjson results "$results" ' map(. + {scope: $scope, result: ($results[.activates // ""] // "")})' <<<"$raw" } case "${1:-}" in --list) system=$(timers_json system) user=$(timers_json user) cron="[]" if command -v crontab >/dev/null 2>&1; then cron=$(crontab -l 2>/dev/null | grep -vE '^\s*(#|$)' | head -n "$MAX_CRON_LINES" | cut -c1-"$MAX_CRON_LINE_LEN" | jq -Rs 'split("\n") | map(select(length > 0) | {line: ., source: "crontab"})' || echo "[]") fi jq -n --argjson system "$system" --argjson user "$user" --argjson cron "$cron" \ '{timers: ($system + $user), cron: $cron}' ;; peek) scope="${2:-system}" unit="${3:-}" [[ -n $unit ]] || { echo "usage: list-jobs peek SCOPE UNIT" >&2; exit 1; } flag=() [[ $scope == user ]] && flag=(--user) systemctl "${flag[@]}" status "$unit" --no-pager -n 0 2>&1 | head -12 || true echo journalctl "${flag[@]}" -u "$unit" -n 40 --no-pager 2>&1 | tail -40 || true echo read -r -p "Press Enter to close..." ;; full) echo "── system timers ──────────────────────────────" systemctl list-timers --all --no-pager 2>&1 || true echo echo "── user timers ────────────────────────────────" systemctl --user list-timers --all --no-pager 2>&1 || true echo read -r -p "Press Enter to close..." ;; *) echo "usage: list-jobs --list | peek SCOPE UNIT | full" >&2 exit 1 ;; esac