152 lines
5.4 KiB
Bash
Executable File
152 lines
5.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
# POS: system schedule — Scheduled jobs: run a command on a timer; notify on threshold/change/error/always or silently
|
|
# POS_SUBCMDS: run list config enable disable status migrate menu
|
|
# POS_FLAGS: --dry-run
|
|
|
|
source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh"
|
|
source "$(dirname "$0")/../lib/scheduler-lib.sh" 2>/dev/null || source "$(dirname "$0")/scheduler-lib.sh"
|
|
source "$(dirname "$0")/../lib/notify.sh" 2>/dev/null || source "$(dirname "$0")/notify.sh"
|
|
source "$(dirname "$0")/../lib/menu-lib.sh" 2>/dev/null || source "$(dirname "$0")/menu-lib.sh"
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
Usage: pos system schedule <subcommand> [--dry-run]
|
|
|
|
Scheduled jobs. Each job is a file in $SCHEDULE_DIR/<name>.env:
|
|
|
|
INTERVAL=hourly (5m..59m | 1h..23h | hourly daily weekly | OnCalendar=…)
|
|
NOTIFY=onchange (always | onchange | onerror | threshold | never)
|
|
MSG="NVMe health" optional label; threshold alert text when NOTIFY=threshold
|
|
RULE="> 60c" threshold only: op + threshold (unit suffix fine: 60c, 80%)
|
|
COMMAND=… literal rest of the line — pipes/quotes/sudo fine
|
|
|
|
NOTIFY policies:
|
|
always send the full output on every run
|
|
onchange send only when the output differs from the last run (first run sends)
|
|
onerror send only when the command fails (non-zero exit or empty output)
|
|
threshold compare the command's first numeric output with RULE; alert on
|
|
false→true plus one recovery message
|
|
never run only — no notification (side-effect jobs)
|
|
Default: threshold if RULE is set, otherwise onchange.
|
|
|
|
Bare \`pos system schedule\` on a terminal (or \`pos system schedule menu\`)
|
|
opens an interactive menu wrapping these subcommands; arguments stay
|
|
scriptable — the systemd timers keep calling 'run <name>' directly.
|
|
|
|
Subcommands:
|
|
run [name|all] Execute job(s) now (the systemd timers call 'run <name>')
|
|
list Jobs + notify policy + interval + last run
|
|
config Interactive editor: add / edit / remove / enable / disable
|
|
enable [name|all] Install + start the job's user timer (default: all)
|
|
disable [name|all] Stop the job, keeping the job file (default: all)
|
|
status Per-job timer state + next run
|
|
migrate Convert legacy event.env rules into schedule.d jobs (threshold)
|
|
|
|
Options:
|
|
--dry-run Preview what would run / be written / be sent
|
|
-h, --help This help
|
|
|
|
Jobs dir: $SCHEDULE_DIR (chmod 600 per file)
|
|
|
|
Examples:
|
|
pos system schedule list
|
|
pos system schedule enable
|
|
pos system schedule run nvme-health --dry-run
|
|
pos system schedule migrate
|
|
EOF
|
|
exit 0
|
|
}
|
|
|
|
# ── Interactive menu (opt-in front door, Pattern B via lib/menu-lib.sh) ──
|
|
# Every item maps onto an existing subcommand implementation. Run-now goes
|
|
# through the same sched_run the systemd timers invoke (`run <name>`),
|
|
# behind an explicit y/N confirm.
|
|
|
|
menu_pick_job() { # $1 = prompt → picked job name on stdout · rc 1 = cancelled
|
|
local -a jobs=()
|
|
mapfile -t jobs < <(sched_list_jobs)
|
|
if [ ${#jobs[@]} -eq 0 ]; then
|
|
warn "no jobs in $SCHEDULE_DIR — add one with 'pos system schedule config'"
|
|
return 1
|
|
fi
|
|
local idx
|
|
idx="$(menu_pick "$1" "${jobs[@]}")" || return 1
|
|
printf '%s\n' "${jobs[$((idx - 1))]}"
|
|
}
|
|
|
|
menu_job_action() { # $1 = run|enable|disable — pick a job, call the verb
|
|
local name
|
|
name="$(menu_pick_job "${1} which job?")" || return 0
|
|
case "$1" in
|
|
run)
|
|
confirm "Run job '$name' now (executes its COMMAND, applies its NOTIFY policy)?" n \
|
|
|| { log "Cancelled"; return 0; }
|
|
sched_run "$name"
|
|
;;
|
|
enable) sched_enable "$name" ;;
|
|
disable) sched_disable "$name" ;;
|
|
esac
|
|
}
|
|
|
|
run_menu() {
|
|
menu_guard || exit 1
|
|
while true; do
|
|
local choice
|
|
choice="$(menu_run "Scheduled jobs ($SCHEDULE_DIR)" \
|
|
"List jobs" \
|
|
"Timer status (+ next run)" \
|
|
"Run a job now" \
|
|
"Enable a job" \
|
|
"Disable a job" \
|
|
"Open the interactive job editor")" || return 0
|
|
case "$choice" in
|
|
1) sched_list ;;
|
|
2) sched_status ;;
|
|
3) menu_job_action run ;;
|
|
4) menu_job_action enable ;;
|
|
5) menu_job_action disable ;;
|
|
6) sched_config_editor ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
# Menu door: explicit verb, or zero args on a terminal. Everything below —
|
|
# including zero args without a terminal — stays byte-compatible with the
|
|
# pre-menu CLI; timer invocations (`run <name>`) never enter the menu.
|
|
if [ "${1:-}" = "menu" ]; then
|
|
run_menu
|
|
exit 0
|
|
fi
|
|
if [ $# -eq 0 ] && [ -t 0 ]; then
|
|
run_menu
|
|
exit 0
|
|
fi
|
|
|
|
case "${1:-}" in
|
|
-h|--help|"") usage ;;
|
|
esac
|
|
|
|
DRY_RUN=0
|
|
args=()
|
|
for a in "$@"; do
|
|
case "$a" in
|
|
--dry-run) DRY_RUN=1 ;;
|
|
*) args+=("$a") ;;
|
|
esac
|
|
done
|
|
set -- "${args[@]}"
|
|
|
|
[ -d "$SCHEDULE_DIR" ] || log "no jobs yet — add one with 'pos system schedule config'"
|
|
|
|
case "${1:-}" in
|
|
run) sched_run "${2:-all}" ;;
|
|
list) sched_list ;;
|
|
config) sched_config_editor ;;
|
|
enable) sched_enable "${2:-all}" ;;
|
|
disable) sched_disable "${2:-all}" ;;
|
|
status) sched_status ;;
|
|
migrate) sched_migrate ;;
|
|
*) err "unknown subcommand '${1:-}' (see --help)" ;;
|
|
esac
|