feat: pos system event-trigger — state-based threshold monitors (eventer)
Each line of event.env is an independent rule: ["msg" if ] <check> <op> <thr>. Check runs on every pass; first numeric output compared float-safe; op is the rightmost 'op threshold' pair so checks with their own >/< parse fine. Alerts once on false->true + one recovery message on true->false (no repeats while the condition holds); per-rule state keyed by rule-line hash in ~/.local/share/linux_post_install/eventer/state/. Subcommands: run (timer entrypoint), config (interactive add/remove/edit with check-validation), list (rules + live values), enable [interval] (systemd user timer pos-event-trigger.timer; 5m..weekly or OnCalendar; graceful without a user manager, loginctl enable-linger attempt), disable, status. --dry-run honors the DEV.md convention. Alerts via lib/notify.sh (Telegram default). New: bin/pos-system-event-trigger, lib/eventer-lib.sh, config/event.env template (no-clobber via postinstall), install.sh lib install, INTERACTIVE_CMDS entry. Docs: POS.md system row, HOWTO.md index, howto/event-trigger.md. make gen && make check green; functional tests cover trigger/recovery/no-repeat, float+unit parsing, editor add/remove/edit + validation + dry-run, timer enable/disable/status, dispatcher routing.
This commit is contained in:
@@ -16,6 +16,8 @@ summary (newest last).
|
||||
|
||||
## Done
|
||||
|
||||
- **2026-08-09** — New `pos system event-trigger` — state-based threshold rule monitors (eventer). Each line of `~/.config/linux_post_install/event.env` is an independent rule: `["<msg>" if ] <check-command> <op> <threshold>` (op `> < >= <= == !=`, unit suffix ok `60c`/`80%`). The check command is run on every pass and its **first numeric output** compared float-safe; operator detected as the rightmost `op threshold` pair so checks containing their own `>`/`<` (awk, redirection) parse fine. Alerts once on false→true plus one recovery message on true→false (no repeats while a condition holds); per-rule state in `~/.local/share/linux_post_install/eventer/state/` keyed by rule-line hash (editing a rule resets its state). Subcommands: `run` (timer entrypoint), `config` (interactive add/remove/edit with validation by test-running the check), `list` (rules + live values), `enable [interval]` (systemd **user** timer `pos-event-trigger.timer` + oneshot service; `5m…weekly` or `OnCalendar=…`; graceful warnings when no user systemd manager, `loginctl enable-linger` attempt), `disable`, `status`. `--dry-run` honors the DEV.md dry-run convention. Alerts via `lib/notify.sh` (Telegram default; other platforms via `NOTIFY_PLATFORM`). New: `bin/pos-system-event-trigger`, `lib/eventer-lib.sh`, `config/event.env` template (installed no-clobber by postinstall), `lib/eventer-lib.sh` installed by install.sh, `system-event-trigger` added to `INTERACTIVE_CMDS`, usage EXAMPLES row. Docs: POS.md system row, HOWTO.md index row, howto/event-trigger.md; `make gen && make check` green; functional tests covered trigger/recovery/no-repeat, float + unit parsing, editor add/remove/edit + validation + dry-run, timer enable/disable/status (graceful), dispatcher routing.
|
||||
|
||||
- **2026-08-09** — `pos media mp3`/`mp4` hardened + smart format selection. Both tools: yt-dlp calls go through `spawn` (honor `$DRY_RUN`; `--dry-run` prints the exact command and skips dep checks), `-o/--output`, `--no-playlist`, `--cookies` (file existence check), clean ffmpeg/yt-dlp guards, `# POS_FLAGS:` for completion, full metadata (`--embed-metadata --embed-chapters --embed-thumbnail --no-overwrites`, mp3 also `--convert-thumbnails jpg` + `--parse-metadata "%(artist,uploader)s:%(artist)s"` so the uploader fills the artist tag). mp3 gains `--by-artist` (`~/Music/<artist>/<title>.mp3`). mp4 gains `-f <id>` / `--best` / `--worst` (no prompt), conflict validation, and an interactive picker that shows a **curated** `-F` table (`[audio]`/`[video]`/`[combo]` grouping, raw clutter dropped) on stderr — stdout carries only the chosen id (ui_pick lesson) — with id validation against the real table and empty/best default. Docs: howto/media.md rewritten (flags, metadata, by-artist, troubleshooting); `make gen && make check` green.
|
||||
|
||||
- **2026-08-09** — Telegram `ai …` now answers about a message you reply to: the listener extracts `reply_to_message.text` (falls back to `caption`) from each update and passes it to `handle_message`; the AI bridge prefixes the prompt with `[Reply context — the message you are replying to]`. So replying to a `/status` output and asking `ai check this details` gives the model the actual output. Applies only to the AI bridge (mapped `/commands` untouched); reply context rides in the user turn so the session records what was analyzed. Docs: howto/ai.md bridge section.
|
||||
|
||||
@@ -10,19 +10,19 @@
|
||||
|
||||
<!-- GEN:START docmap -->
|
||||
| ## 1. Project Overview | 28–43 |
|
||||
| ## 2. Directory Structure | 44–183 |
|
||||
| ## 3. Installation Flow | 184–236 |
|
||||
| ## 4. The `pos` CLI System | 237–300 |
|
||||
| ## 5. Shared Library — `lib/common.sh` | 301–332 |
|
||||
| ## 6. Docker Compose / ScaleTail | 333–375 |
|
||||
| ## 7. Optional Apps (`apps/`) | 376–405 |
|
||||
| ## 8. Entertainment Module | 406–419 |
|
||||
| ## 9. Systemd Services | 420–432 |
|
||||
| ## 10. Configuration Files | 433–459 |
|
||||
| ## 11. Coding Conventions | 460–492 |
|
||||
| ## 12. Development Workflow | 493–545 |
|
||||
| ## 13. Key File Quick Reference | 546–595 |
|
||||
| ## 14. Common Tasks for Agents | 596–621 |
|
||||
| ## 2. Directory Structure | 44–184 |
|
||||
| ## 3. Installation Flow | 185–237 |
|
||||
| ## 4. The `pos` CLI System | 238–302 |
|
||||
| ## 5. Shared Library — `lib/common.sh` | 303–334 |
|
||||
| ## 6. Docker Compose / ScaleTail | 335–377 |
|
||||
| ## 7. Optional Apps (`apps/`) | 378–407 |
|
||||
| ## 8. Entertainment Module | 408–421 |
|
||||
| ## 9. Systemd Services | 422–434 |
|
||||
| ## 10. Configuration Files | 435–461 |
|
||||
| ## 11. Coding Conventions | 462–494 |
|
||||
| ## 12. Development Workflow | 495–547 |
|
||||
| ## 13. Key File Quick Reference | 548–599 |
|
||||
| ## 14. Common Tasks for Agents | 600–625 |
|
||||
<!-- GEN:END docmap -->
|
||||
|
||||
## 1. Project Overview
|
||||
@@ -79,6 +79,7 @@ Linux_post_install/
|
||||
│ ├── pos-network-scan # Parallel ping sweep of CIDR
|
||||
│ ├── pos-ssh-load-keys # Load all SSH keys into the agent
|
||||
│ ├── pos-system-backup # Encrypted (AES-256) folder snapshots (tar + gpg)
|
||||
│ ├── pos-system-event-trigger # State-based rule monitors; alerts via notify when a check crosses a threshold
|
||||
│ ├── pos-system-firewall # Interactive UFW management
|
||||
│ ├── pos-system-health # Host health dashboard (disk, RAM, services, backup age, fail2ban, docker); exit 1 if any FAIL
|
||||
│ ├── pos-system-nfs-client # Mount NFS shares (ephemeral or persistent systemd mount units)
|
||||
@@ -274,6 +275,7 @@ All non-interactive `pos` commands log output to `~/.local/share/linux_post_inst
|
||||
| network | scan | `pos-network-scan` | Parallel ping sweep of CIDR |
|
||||
| ssh | load-keys | `pos-ssh-load-keys` | Load all SSH keys into the agent |
|
||||
| system | backup | `pos-system-backup` | Encrypted (AES-256) folder snapshots (tar + gpg) |
|
||||
| system | event-trigger | `pos-system-event-trigger` | State-based rule monitors; alerts via notify when a check crosses a threshold |
|
||||
| system | firewall | `pos-system-firewall` | Interactive UFW management |
|
||||
| system | health | `pos-system-health` | Host health dashboard (disk, RAM, services, backup age, fail2ban, docker); exit 1 if any FAIL |
|
||||
| system | nfs-client | `pos-system-nfs-client` | Mount NFS shares (ephemeral or persistent systemd mount units) |
|
||||
@@ -554,12 +556,13 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:`
|
||||
| `lib/flags.sh` | 60 | Feature flag store (set/clear/is_set/value/list/status) |
|
||||
| `lib/notify.sh` | 76 | Multi-platform alerting (`notify_send`) — opt-in source, silent-fails |
|
||||
| `lib/entertainment-lib.sh` | 350 | Entertainment module lib (ENABLED parsing, scheduler sync) |
|
||||
| `lib/eventer-lib.sh` | 312 | Eventer lib (rule parsing, float compare, per-rule state, user-timer sync) |
|
||||
| `bin/flag-reader` | 58 | Inspect flags (list/status/`--raw`) |
|
||||
| `bin/flag-set` | 21 | Set a flag (optionally with a value) |
|
||||
| `bin/flag-clear` | 21 | Unset a flag |
|
||||
| `features/autostart.sh` | 14 | Boot-time feature (moved from `bin/`, flag-gated service) |
|
||||
<!-- GEN:START filetable -->
|
||||
| `bin/pos` | 277 | CLI dispatcher with smart arg matching + logging + category help |
|
||||
| `bin/pos` | 280 | CLI dispatcher with smart arg matching + logging + category help |
|
||||
| `bin/pos-ai-gemini` | 311 | Chat with Google Gemini (ask, chat, models, sessions) |
|
||||
| `bin/pos-communication-telegram-listener` | 563 | Telegram bot listener: map /command → bash, run them on chat messages |
|
||||
| `bin/pos-communication-telegram-sender` | 221 | Send Telegram messages/files/links/stickers via Bot API (send, test) |
|
||||
@@ -581,13 +584,14 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:`
|
||||
| `bin/pos-network-scan` | 271 | Parallel ping sweep of CIDR |
|
||||
| `bin/pos-ssh-load-keys` | 31 | Load all SSH keys into the agent |
|
||||
| `bin/pos-system-backup` | 126 | Encrypted (AES-256) folder snapshots (tar + gpg) |
|
||||
| `bin/pos-system-event-trigger` | 212 | State-based rule monitors; alerts via notify when a check crosses a threshold |
|
||||
| `bin/pos-system-firewall` | 291 | Interactive UFW management |
|
||||
| `bin/pos-system-health` | 209 | Host health dashboard (disk, RAM, services, backup age, fail2ban, docker); exit 1 if any FAIL |
|
||||
| `bin/pos-system-nfs-client` | 138 | Mount NFS shares (ephemeral or persistent systemd mount units) |
|
||||
| `bin/pos-system-nfs-server` | 134 | Manage the NFS kernel server (status, share/unshare exports, enable/disable) |
|
||||
| `bin/pos-tree` | 110 | Show the pos CLI command tree: categories, commands, and subcommands |
|
||||
| `bin/pos-usb-server` | 218 | USB Redirector server control (--ls, --share; prompts when args omitted) |
|
||||
| `completions/pos.bash` | 284 | Dynamic bash completion |
|
||||
| `completions/pos.bash` | 286 | Dynamic bash completion |
|
||||
<!-- GEN:END filetable -->
|
||||
| `apps/install.sh` | 171 | App install/uninstall picker/orchestrator |
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ authoritative one-line reference (every command + flag), see
|
||||
| `pos docker` | Compose services, container dashboards, disposable VMs | [docker](howto/docker.md) |
|
||||
| `pos media` | Download audio/video via yt-dlp | [media](howto/media.md) |
|
||||
| `pos system` | Backups, firewall, health dashboard | [system](howto/system.md) |
|
||||
| `pos system event-trigger` | Threshold-rule monitors that alert on crossing | [event-trigger](howto/event-trigger.md) |
|
||||
| `pos ssh` | Load keys into the agent | [ssh](howto/ssh.md) |
|
||||
| `pos usb` | Share USB devices over the network | [usb](howto/usb.md) |
|
||||
| `pos communication` | Send Telegram messages/files/alerts | [communication](howto/communication.md) |
|
||||
|
||||
@@ -187,6 +187,7 @@ The standalone `vbox` command still works and forwards to `pos docker vbox` (see
|
||||
| `pos system health [--send] [--markdown]` | `bin/pos-system-health` | Host health dashboard: disk per mount, RAM/swap, failed systemd units, backup age, fail2ban, docker containers. Exits 1 if any check FAILs | `--send`/`--markdown` are notification-only: they send the summary via `lib/notify.sh` to every platform in `NOTIFY_PLATFORM` and do NOT print the dashboard (so wrappers like the Telegram listener don't echo it back — pair with the listener's `@quiet` marker). `HEALTH_BACKUP_MAX_AGE_DAYS` (default 2) and `BACKUP_SERVICE_ROOTS` come from `~/.config/linux_post_install/system.env`; `--help` shows the effective values. Platform list from `~/.config/linux_post_install/notify.env` |
|
||||
| `pos system nfs-server <cmd>` | `bin/pos-system-nfs-server` | Manage the NFS kernel server: `status`, `share <path> [client]`, `unshare <path>`, `list`, `reload`, `enable`, `disable` | Requires `nfs-kernel-server` (added to `preinstall.sh` PACKAGES). Exports live in `/etc/exports`; `share` is idempotent (replaces any existing line for the path) and runs `exportfs -ra`. Default client `*(rw,sync,no_subtree_check)` — the tool warns you to restrict it; help prints Tailscale CGNAT (`100.64.0.0/10`), WireGuard (`10.10.0.0/24`) and LAN examples. Mutating commands announce via `lib/notify.sh` |
|
||||
| `pos system nfs-client <cmd>` | `bin/pos-system-nfs-client` | Mount and manage NFS shares: `mount <server:export> <local-dir>`, `unmount <local-dir>`, `list`, `persist <server:export> <local-dir>`, `unpersist <local-dir>` | Requires `nfs-common` (added to `preinstall.sh` PACKAGES). `persist` writes a systemd `.mount` unit (`systemd-escape --path --suffix=mount`) with `After=network-online.target` / `Wants=network-online.target` — mounts only once all interfaces are up, no fstab edits to break boot — then `daemon-reload` + `enable --now`. `unpersist` stops/disables/removes the unit. `mount`/`persist` announce via `lib/notify.sh` |
|
||||
| `pos system event-trigger <cmd>` | `bin/pos-system-event-trigger` | State-based rule monitors: `run`, `config`, `list`, `enable [interval]`, `disable`, `status`. Each line of `~/.config/linux_post_install/event.env` is an independent rule `["msg" if ] <check-command> <op> <threshold>` (op `> < >= <= == !=`, unit suffix ok: `60c`, `80%`); the check command's first numeric output is compared float-safe. Alerts once on false→true, plus one recovery message on true→false — no repeats while the condition holds, via `lib/notify.sh` | `config` is an interactive editor that validates rules by test-running the check; `run` is what the systemd user timer (`pos-event-trigger.timer` + oneshot `.service`, interval set at `enable`) executes; supports `--dry-run`; rules are arbitrary shell commands (chmod 600, same trust model as the Telegram map); template `config/event.env` auto-installed no-clobber by postinstall |
|
||||
|
||||
`systemd/pos-health.service` + `systemd/pos-health.timer` run `pos system health --send --markdown` daily at 08:00 as the installing user. `postinstall.sh` enables the timer automatically once `~/.config/linux_post_install/telegram.env` exists — re-run postinstall after configuring a notify platform to pick it up. The service also loads `system.env` + `notify.env` via `EnvironmentFile=`.
|
||||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
# How-To: `pos system event-trigger`
|
||||
|
||||
State-based threshold monitors: each line of `event.env` is an independent rule;
|
||||
when a rule's check crosses its threshold you get one alert (plus one recovery
|
||||
message when it clears). Alerts go through `lib/notify.sh` — Telegram by
|
||||
default, `NOTIFY_PLATFORM` for more.
|
||||
|
||||
```bash
|
||||
pos system event-trigger config # interactive rule editor
|
||||
pos system event-trigger list # rules + live check values
|
||||
pos system event-trigger enable 5m # evaluate every 5 minutes via systemd
|
||||
pos system event-trigger status # timer + rule count
|
||||
pos system event-trigger disable # stop monitoring
|
||||
pos system event-trigger run --dry-run # preview what would fire
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rule format
|
||||
|
||||
One rule per line in `~/.config/linux_post_install/event.env` (chmod 600):
|
||||
|
||||
```
|
||||
["<message>" if ] <check-command> <op> <threshold>
|
||||
```
|
||||
|
||||
| Part | Meaning |
|
||||
|------|---------|
|
||||
| `"<message>" if` | Optional custom alert text (quote-stripped); without it the message is auto-composed |
|
||||
| `<check-command>` | Any shell command; its **first numeric output** is the value (pipes/args fine) |
|
||||
| `<op>` | `>` `<` `>=` `<=` `==` `!=` |
|
||||
| `<threshold>` | Number with optional unit suffix — `60c`, `80%`, `10g` all work |
|
||||
|
||||
The operator is detected as the rightmost `op threshold` pair in the line, so
|
||||
check commands containing their own `>`/`<` (redirection, awk) don't confuse it.
|
||||
|
||||
Examples:
|
||||
|
||||
```
|
||||
# event.env
|
||||
"CPU too hot" if sensors -u | grep -m1 temp1_input | awk '{print $2}' > 60c
|
||||
"Disk nearly full" if df -P / | awk 'NR==2{print $5+0}' > 80%
|
||||
"Load high" if uptime | sed 's/.*load average: //; s/,.*//' >= 4
|
||||
```
|
||||
|
||||
Behavior:
|
||||
|
||||
- The check runs on every pass. Non-numeric/empty output, or an unparseable
|
||||
line → the rule is skipped with a warning (other rules still run).
|
||||
- Alerts fire **once** when the condition turns true, and once more when it
|
||||
recovers — a hot CPU for two hours is one message, not twenty.
|
||||
- State is tracked per rule in `~/.local/share/linux_post_install/eventer/state/`
|
||||
(keyed by a hash of the rule line — editing a rule resets its state).
|
||||
|
||||
## Scheduling
|
||||
|
||||
`enable [interval]` installs a systemd **user timer** (`pos-event-trigger.timer`
|
||||
+ oneshot `.service` that runs `pos system event-trigger run`). Intervals:
|
||||
`5m 10m 15m 30m 45m hourly 2h 6h 12h daily weekly`, or a raw `OnCalendar=…`.
|
||||
Requires a reachable user systemd manager; run
|
||||
`sudo loginctl enable-linger $USER` once so timers fire without login (the tool
|
||||
tries this and warns if it can't).
|
||||
|
||||
## Alerting
|
||||
|
||||
`run` sends via `lib/notify.sh notify_send`, which delivers to every platform in
|
||||
`NOTIFY_PLATFORM` (default `telegram`; comma-separated = fan out). Adding a
|
||||
Matrix/Synapse sender later needs no changes here — see DOC/DEV.md → Alerting.
|
||||
|
||||
---
|
||||
|
||||
## Related
|
||||
|
||||
- Reference: [DOC/POS.md → system](../POS.md#system)
|
||||
- Notifications: [communication](howto/communication.md) / `lib/notify.sh`
|
||||
@@ -173,6 +173,9 @@ EXAMPLES
|
||||
pos entertainment enable weather 5m Auto-send weather to Telegram every 5 min
|
||||
pos entertainment status Show enabled plugins + next run
|
||||
|
||||
pos system event-trigger config Edit threshold rules (event.env)
|
||||
pos system event-trigger enable 5m Monitor rules on a 5-minute timer
|
||||
|
||||
pos ai gemini ask "Explain DNS in one line"
|
||||
Ask Google Gemini a one-shot question
|
||||
pos ai gemini chat Interactive multi-turn chat
|
||||
@@ -241,7 +244,7 @@ MAIN_LOG="$LOG_DIR/pos.log"
|
||||
log_cmd() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $* → exit $2" >> "$MAIN_LOG"; }
|
||||
|
||||
# Commands that read from stdin interactively — only log invocation
|
||||
INTERACTIVE_CMDS="system-firewall media-mp4 system-backup usb-server communication-telegram-listener ai-gemini config"
|
||||
INTERACTIVE_CMDS="system-firewall media-mp4 system-backup usb-server communication-telegram-listener ai-gemini system-event-trigger config"
|
||||
|
||||
for ((i=n-1; i>=0; i--)); do
|
||||
cmd="pos"
|
||||
|
||||
Executable
+212
@@ -0,0 +1,212 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
# POS: system event-trigger — State-based rule monitors; alerts via notify when a check crosses a threshold
|
||||
# POS_SUBCMDS: run config list enable disable status
|
||||
# POS_FLAGS: --dry-run
|
||||
|
||||
source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh"
|
||||
source "$(dirname "$0")/../lib/eventer-lib.sh" 2>/dev/null || source "$(dirname "$0")/eventer-lib.sh"
|
||||
source "$(dirname "$0")/../lib/notify.sh" 2>/dev/null || source "$(dirname "$0")/notify.sh"
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: pos system event-trigger <subcommand> [--dry-run]
|
||||
|
||||
State-based rule monitors. Each line in event.env is an independent rule:
|
||||
|
||||
"<message>" if <check-command> <op> <threshold>
|
||||
<check-command> <op> <threshold>
|
||||
|
||||
The check command is run on every pass; when its first numeric output
|
||||
crosses the threshold the message is sent via lib/notify.sh (Telegram by
|
||||
default). Rules fire on false→true only, plus one recovery message.
|
||||
|
||||
Subcommands:
|
||||
run Evaluate every rule once (the systemd timer calls this)
|
||||
config Interactive editor: add / remove <n> / edit <n> / quit
|
||||
list Show rules with their live check values
|
||||
enable [interval] Install + start the user timer (default 5m; also:
|
||||
10m 15m 30m 45m hourly 2h 6h 12h daily weekly, OnCalendar=…)
|
||||
disable Stop + remove the timer
|
||||
status Timer + rule count
|
||||
|
||||
Options:
|
||||
--dry-run Preview what would fire / be written, without sending
|
||||
-h, --help This help
|
||||
|
||||
Rules file: $CONFIG_FILE (chmod 600)
|
||||
|
||||
Examples:
|
||||
pos system event-trigger list
|
||||
pos system event-trigger config
|
||||
pos system event-trigger enable 5m
|
||||
pos system event-trigger run --dry-run
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
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[@]}"
|
||||
|
||||
[ -f "$CONFIG_FILE" ] || { log "no rules file yet — run 'pos system event-trigger config'"; }
|
||||
|
||||
# ── run ──────────────────────────────────────────────────────────
|
||||
run_rules() {
|
||||
eventer_read_rules
|
||||
local n fired hash msg line
|
||||
n=0
|
||||
for line in "${RULES[@]}"; do
|
||||
case "$line" in
|
||||
""|\#*) continue ;;
|
||||
esac
|
||||
eventer_parse_rule "$line" || { warn "skipping unparseable rule: $line"; continue; }
|
||||
eventer_eval_check || { warn "check produced no number — $RULE_CMD"; continue; }
|
||||
n=$((n + 1))
|
||||
if eventer_compare "$VALUE_NUM" "$RULE_OP" "$RULE_NUM"; then fired=1; else fired=0; fi
|
||||
hash="$(eventer_hash "$line")"
|
||||
if [ "$fired" -eq 1 ] && ! eventer_state_is_firing "$hash"; then
|
||||
msg="$(eventer_alert_msg)"
|
||||
if [ "$DRY_RUN" -eq 1 ]; then
|
||||
log "(dry-run) TRIGGER: $msg"
|
||||
else
|
||||
notify_send "$msg"
|
||||
eventer_state_set "$hash" 1
|
||||
fi
|
||||
elif [ "$fired" -eq 0 ] && eventer_state_is_firing "$hash"; then
|
||||
msg="$(eventer_recover_msg)"
|
||||
if [ "$DRY_RUN" -eq 1 ]; then
|
||||
log "(dry-run) RECOVER: $msg"
|
||||
else
|
||||
notify_send "$msg"
|
||||
eventer_state_set "$hash" 0
|
||||
fi
|
||||
elif [ "$DRY_RUN" -eq 1 ]; then
|
||||
log "(dry-run) OK: $RULE_CMD = $VALUE_DISPLAY (no change)"
|
||||
fi
|
||||
done
|
||||
log "evaluated $n rule(s)"
|
||||
}
|
||||
|
||||
# ── list ─────────────────────────────────────────────────────────
|
||||
list_rules() {
|
||||
eventer_read_rules
|
||||
local i=0 line
|
||||
for line in "${RULES[@]}"; do
|
||||
i=$((i + 1))
|
||||
case "$line" in
|
||||
""|\#*) printf '%2d (comment/blank)\n' "$i"; continue ;;
|
||||
esac
|
||||
if eventer_parse_rule "$line"; then
|
||||
if eventer_eval_check; then
|
||||
printf '%2d [%s %s %s] %s = %s\n' "$i" "$RULE_CMD" "$RULE_OP" "$RULE_THRESH" \
|
||||
"${RULE_MSG:+msg: $RULE_MSG; }" "$VALUE_DISPLAY"
|
||||
else
|
||||
printf '%2d [%s %s %s] (check produced no number)\n' "$i" "$RULE_CMD" "$RULE_OP" "$RULE_THRESH"
|
||||
fi
|
||||
else
|
||||
printf '%2d INVALID — %s\n' "$i" "$line"
|
||||
fi
|
||||
done
|
||||
[ "$i" -gt 0 ] || echo "(no rules — run 'pos system event-trigger config')"
|
||||
}
|
||||
|
||||
# ── config editor ────────────────────────────────────────────────
|
||||
config_editor() {
|
||||
eventer_read_rules
|
||||
local n rule action ok
|
||||
while true; do
|
||||
echo "── rules in $CONFIG_FILE ──" >&2
|
||||
if [ "${#RULES[@]}" -eq 0 ]; then
|
||||
echo " (empty)" >&2
|
||||
else
|
||||
for i in "${!RULES[@]}"; do
|
||||
printf ' %2d %s\n' "$((i + 1))" "${RULES[i]}" >&2
|
||||
done
|
||||
fi
|
||||
read -rp "action [add / remove <n> / edit <n> / quit]: " action
|
||||
case "$action" in
|
||||
quit|q) break ;;
|
||||
add)
|
||||
read -rp "Rule (\"msg\" if <cmd> <op> <thr>): " rule
|
||||
[ -n "$rule" ] || continue
|
||||
if ! eventer_parse_rule "$rule"; then
|
||||
warn "not a valid rule — need: <command> <op> <threshold> (op: > < >= <= == !=)"
|
||||
continue
|
||||
fi
|
||||
if ! eventer_eval_check; then
|
||||
warn "check '$RULE_CMD' produced no number — rule added anyway (it will warn on run)"
|
||||
else
|
||||
ok "check '$RULE_CMD' currently reports $VALUE_DISPLAY"
|
||||
fi
|
||||
if grep -Fxq "$rule" "$CONFIG_FILE" 2>/dev/null; then
|
||||
warn "rule already present, not added"
|
||||
elif [ "$DRY_RUN" -eq 1 ]; then
|
||||
log "(dry-run) would add: $rule"
|
||||
else
|
||||
mkdir -p "$(dirname "$CONFIG_FILE")"
|
||||
printf '%s\n' "$rule" >>"$CONFIG_FILE"
|
||||
chmod 600 "$CONFIG_FILE"
|
||||
eventer_read_rules
|
||||
fi
|
||||
;;
|
||||
remove*)
|
||||
n="${action#remove }"
|
||||
[[ "$n" =~ ^[0-9]+$ ]] || { warn "use: remove <line-number>"; continue; }
|
||||
if [ "$n" -lt 1 ] || [ "$n" -gt "${#RULES[@]}" ]; then
|
||||
warn "no line $n"; continue
|
||||
fi
|
||||
read -rp "remove line $n: '${RULES[$((n - 1))]}'? [y/N]: " ok
|
||||
[[ "$ok" =~ ^[Yy] ]] || continue
|
||||
if [ "$DRY_RUN" -eq 1 ]; then
|
||||
log "(dry-run) would remove line $n"
|
||||
else
|
||||
unset "RULES[$((n - 1))]"
|
||||
RULES=("${RULES[@]}")
|
||||
eventer_write_rules
|
||||
fi
|
||||
;;
|
||||
edit*)
|
||||
n="${action#edit }"
|
||||
[[ "$n" =~ ^[0-9]+$ ]] || { warn "use: edit <line-number>"; continue; }
|
||||
if [ "$n" -lt 1 ] || [ "$n" -gt "${#RULES[@]}" ]; then
|
||||
warn "no line $n"; continue
|
||||
fi
|
||||
printf ' current: %s\n' "${RULES[$((n - 1))]}" >&2
|
||||
read -rp " new rule (blank = keep): " rule
|
||||
[ -n "$rule" ] || continue
|
||||
if ! eventer_parse_rule "$rule"; then
|
||||
warn "not a valid rule — edit cancelled"
|
||||
continue
|
||||
fi
|
||||
if [ "$DRY_RUN" -eq 1 ]; then
|
||||
log "(dry-run) would replace line $n"
|
||||
else
|
||||
RULES[$((n - 1))]="$rule"
|
||||
eventer_write_rules
|
||||
fi
|
||||
;;
|
||||
*) warn "unknown action (add | remove <n> | edit <n> | quit)" ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
run) run_rules ;;
|
||||
config) config_editor ;;
|
||||
list) list_rules ;;
|
||||
enable) eventer_enable "${2:-5m}" ;;
|
||||
disable) eventer_disable ;;
|
||||
status) eventer_status ;;
|
||||
*) err "unknown subcommand '${1:-}' (see --help)" ;;
|
||||
esac
|
||||
@@ -11,6 +11,7 @@ _pos_flags[media-mp3]="--output --no-playlist --cookies --by-artist --dry-run"
|
||||
_pos_flags[media-mp4]="--format --best --worst --output --no-playlist --cookies --dry-run"
|
||||
_pos_flags[network-hotspot]="--foreground"
|
||||
_pos_flags[system-backup]="--service"
|
||||
_pos_flags[system-event-trigger]="--dry-run"
|
||||
_pos_flags[tree]="--depth"
|
||||
_pos_flags[usb-server]="--ls --ls-shared --share --unshare --auto-share --callback --close-callback --auto-connect --disconnect --nickname --timeout --port --info --version"
|
||||
# GEN:END posflags
|
||||
@@ -20,6 +21,7 @@ _pos_subcmds[ai-gemini]="ask chat models sessions"
|
||||
_pos_subcmds[communication-telegram-sender]="send test"
|
||||
_pos_subcmds[docker-compose]="ls installed up down restart logs update config"
|
||||
_pos_subcmds[docker-vbox]="create enter stop start rm ls"
|
||||
_pos_subcmds[system-event-trigger]="run config list enable disable status"
|
||||
# GEN:END possubcmds
|
||||
# GEN:START posconfigscopes
|
||||
declare -a _pos_config_scopes=(ai compose entertainment notify system telegram)
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# eventer rules — evaluated by 'pos system event-trigger run' (systemd timer).
|
||||
# Copied to ~/.config/linux_post_install/event.env on install (no clobber).
|
||||
# One independent rule per line. Grammar:
|
||||
#
|
||||
# "<message>" if <check-command> <op> <threshold>
|
||||
# <check-command> <op> <threshold> (no message → auto-composed)
|
||||
#
|
||||
# The check command is run; its first numeric output is compared against the
|
||||
# threshold (float-safe, unit suffix on the threshold is fine: 60c, 80%, 10g).
|
||||
# Operators: > < >= <= == !=
|
||||
#
|
||||
# Rules alert once on false→true (plus one recovery message when the
|
||||
# condition clears) via lib/notify.sh — Telegram by default.
|
||||
#
|
||||
# Examples:
|
||||
# "CPU too hot" if sensors -u | grep -m1 temp1_input | awk '{print $2}' > 60c
|
||||
# "Disk nearly full" if df -P / | awk 'NR==2{print $5+0}' > 80%
|
||||
# "Load high" if uptime | sed 's/.*load average: //; s/,.*//' >= 4
|
||||
#
|
||||
# Manage rules interactively: pos system event-trigger config
|
||||
# Schedule the timer: pos system event-trigger enable 5m
|
||||
@@ -117,6 +117,7 @@ if should_run 2 scripts; then
|
||||
run sudo install -m 644 lib/flags.sh /usr/local/bin/flags.sh
|
||||
run sudo install -m 644 lib/notify.sh /usr/local/bin/notify.sh
|
||||
run sudo install -m 644 lib/entertainment-lib.sh /usr/local/bin/entertainment-lib.sh
|
||||
run sudo install -m 644 lib/eventer-lib.sh /usr/local/bin/eventer-lib.sh
|
||||
run sudo install -m 644 lib/config-ui.sh /usr/local/bin/config-ui.sh
|
||||
|
||||
# ── Entertainment plugins ────────────────────────────────
|
||||
|
||||
@@ -0,0 +1,312 @@
|
||||
# lib/eventer-lib.sh — shared library for `pos system event-trigger`.
|
||||
# Sourced by bin/pos-system-event-trigger AFTER lib/common.sh.
|
||||
#
|
||||
# eventer reads a list of independent RULES from event.env, one per line:
|
||||
#
|
||||
# "High CPU temp" if sensors -u | grep -m1 temp1_input | awk '{print $2}' > 60c
|
||||
# disk root > 80%
|
||||
# loadavg >= 4
|
||||
#
|
||||
# Grammar: ["<message>" if ] <inline-check-command> <op> <threshold>
|
||||
# - optional message before the last ' if ' (quote-stripped) = alert text
|
||||
# - the operator is the RIGHTMOST '>= <= == != > <' whose RHS parses as a
|
||||
# number (a unit suffix like 60c / 80% is fine) — everything before it is
|
||||
# the check command, the number is the threshold
|
||||
# - the check command is run and its FIRST numeric token is compared (float).
|
||||
# State-based alerting: notify on false→true, one recovery on true→false.
|
||||
#
|
||||
# NOTE: rules are arbitrary shell commands (the user's own config, chmod 600),
|
||||
# the same trust model as the Telegram listener's command map.
|
||||
|
||||
CONFIG_FILE="${EVENT_CONFIG:-$HOME/.config/linux_post_install/event.env}"
|
||||
EVENT_STATE_DIR="${EVENT_STATE_DIR:-$HOME/.local/share/linux_post_install/eventer/state}"
|
||||
USER_SYSTEMD_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user"
|
||||
EVENT_UNIT="pos-event-trigger"
|
||||
EVENT_RUNNER="$(command -v pos-system-event-trigger 2>/dev/null || echo /usr/local/bin/pos-system-event-trigger)"
|
||||
|
||||
# common.sh helpers (guarded so the lib is safe if common.sh wasn't loaded)
|
||||
declare -F err >/dev/null || err() { echo "ERROR: $*" >&2; exit 1; }
|
||||
declare -F warn >/dev/null || warn() { echo "[!] $*"; }
|
||||
declare -F ok >/dev/null || ok() { echo " OK $*"; }
|
||||
declare -F log >/dev/null || log() { echo "[+] $*"; }
|
||||
|
||||
# ── Rule file helpers ────────────────────────────────────────────
|
||||
# Fills the global array RULES with every line of the file (raw).
|
||||
eventer_read_rules() {
|
||||
RULES=()
|
||||
[ -f "$CONFIG_FILE" ] || return 0
|
||||
local line
|
||||
while IFS= read -r line || [ -n "$line" ]; do
|
||||
RULES+=("$line")
|
||||
done < "$CONFIG_FILE"
|
||||
}
|
||||
|
||||
eventer_write_rules() {
|
||||
mkdir -p "$(dirname "$CONFIG_FILE")"
|
||||
local tmp
|
||||
tmp="$(mktemp)"
|
||||
printf '%s\n' "${RULES[@]}" >"$tmp"
|
||||
mv "$tmp" "$CONFIG_FILE"
|
||||
chmod 600 "$CONFIG_FILE"
|
||||
}
|
||||
|
||||
eventer_count_rules() {
|
||||
local i n=0 line
|
||||
for line in "${RULES[@]}"; do
|
||||
line="${line#"${line%%[![:space:]]*}"}" # trim leading whitespace
|
||||
[ -n "$line" ] && [[ "$line" != \#* ]] && n=$((n + 1))
|
||||
done
|
||||
printf '%s' "$n"
|
||||
}
|
||||
|
||||
# ── Rule parsing ─────────────────────────────────────────────────
|
||||
# eventer_parse_rule "<line>" — on success sets RULE_MSG, RULE_CMD,
|
||||
# RULE_OP, RULE_THRESH, RULE_NUM and returns 0; otherwise returns 1.
|
||||
# Also supports bare "CMD OP THRESH" (no message) and "MSG if CMD OP THRESH".
|
||||
eventer_parse_rule() {
|
||||
local line="$1" cond="" msg="" s m parts="" i
|
||||
s="$line"
|
||||
while [[ "$s" == *" if "* ]]; do
|
||||
m="${s%% if *}"
|
||||
s="${s#*" if "}"
|
||||
if [[ "$s" == *" if "* ]]; then
|
||||
parts+="${m} if "
|
||||
else
|
||||
parts+="${m}"
|
||||
fi
|
||||
done
|
||||
msg="$parts"
|
||||
cond="$s"
|
||||
[ -n "$cond" ] || return 1
|
||||
|
||||
local -a tokens=()
|
||||
read -r -a tokens <<< "$cond"
|
||||
local n=${#tokens[@]}
|
||||
[ "$n" -ge 2 ] || return 1
|
||||
for ((i=n-2; i>=0; i--)); do
|
||||
case "${tokens[i]}" in
|
||||
">="|"<="|"=="|"!="|">"|"<")
|
||||
if eventer_numeric "${tokens[i+1]}" >/dev/null 2>&1; then
|
||||
RULE_OP="${tokens[i]}"
|
||||
RULE_THRESH="${tokens[i+1]}"
|
||||
RULE_NUM="$(eventer_numeric "${tokens[i+1]}")"
|
||||
if [ "$i" -eq 0 ]; then
|
||||
RULE_CMD="${tokens[0]}"
|
||||
else
|
||||
RULE_CMD="$(printf '%s ' "${tokens[@]:0:i}")"
|
||||
RULE_CMD="${RULE_CMD% }"
|
||||
fi
|
||||
RULE_MSG="$(eventer_strip_quotes "$msg")"
|
||||
return 0
|
||||
fi ;;
|
||||
esac
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# First numeric prefix of a string (allows a unit suffix: 60c, 80%, 10g).
|
||||
eventer_numeric() {
|
||||
if [[ "$1" =~ ^[+-]?([0-9]+([.][0-9]+)?|[.][0-9]+) ]]; then
|
||||
printf '%s' "${BASH_REMATCH[0]}"
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
eventer_strip_quotes() {
|
||||
local v="$1"
|
||||
v="${v#\"}"; v="${v%\"}"
|
||||
v="${v#\'}"; v="${v%\'}"
|
||||
printf '%s' "$v"
|
||||
}
|
||||
|
||||
# Float-safe numeric comparison. Returns 0 if $1 $2 $3 is TRUE.
|
||||
eventer_compare() {
|
||||
awk -v a="$1" -v op="$2" -v b="$3" 'BEGIN{
|
||||
if (op==">") exit (a>b) ? 0 : 1
|
||||
if (op=="<") exit (a<b) ? 0 : 1
|
||||
if (op==">=") exit (a>=b) ? 0 : 1
|
||||
if (op=="<=") exit (a<=b) ? 0 : 1
|
||||
if (op=="==") exit (a==b) ? 0 : 1
|
||||
if (op=="!=") exit (a!=b) ? 0 : 1
|
||||
exit 1
|
||||
}'
|
||||
}
|
||||
|
||||
# ── Running a check ──────────────────────────────────────────────
|
||||
# eventer_eval_check — runs RULE_CMD, sets VALUE_DISPLAY (first numeric
|
||||
# token as printed) and VALUE_NUM (its numeric prefix). Returns 0 on success.
|
||||
eventer_eval_check() {
|
||||
local out tok line
|
||||
out="$(bash -c "$RULE_CMD" 2>/dev/null || true)"
|
||||
[ -n "$out" ] || return 1
|
||||
line="$(printf '%s' "$out" | sed -n '1p')"
|
||||
read -r -a tokens <<< "$line"
|
||||
for tok in "${tokens[@]}"; do
|
||||
if eventer_numeric "$tok" >/dev/null 2>&1; then
|
||||
VALUE_NUM="$(eventer_numeric "$tok")"
|
||||
VALUE_DISPLAY="$tok"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# ── Alert messages ───────────────────────────────────────────────
|
||||
eventer_alert_msg() {
|
||||
if [ -n "$RULE_MSG" ]; then
|
||||
printf '%s (now %s)' "$RULE_MSG" "$VALUE_DISPLAY"
|
||||
else
|
||||
printf '%s now %s is %s %s' "${RULE_CMD%% *}" "$VALUE_DISPLAY" "$RULE_OP" "$RULE_THRESH"
|
||||
fi
|
||||
}
|
||||
|
||||
eventer_recover_msg() {
|
||||
if [ -n "$RULE_MSG" ]; then
|
||||
printf '%s recovered (now %s)' "$RULE_MSG" "$VALUE_DISPLAY"
|
||||
else
|
||||
printf '%s OK (now %s)' "${RULE_CMD%% *}" "$VALUE_DISPLAY"
|
||||
fi
|
||||
}
|
||||
|
||||
# ── State (per rule, keyed by a short hash of the rule line) ─────
|
||||
eventer_hash() { printf '%s' "$1" | sha256sum | cut -c1-16; }
|
||||
|
||||
eventer_state_is_firing() { # $1 = hash
|
||||
[ -f "$EVENT_STATE_DIR/$1" ] && grep -q '^1' "$EVENT_STATE_DIR/$1"
|
||||
}
|
||||
|
||||
eventer_state_set() { # $1 = hash, $2 = 0|1
|
||||
mkdir -p "$EVENT_STATE_DIR"
|
||||
printf '%s\n' "$2" >"$EVENT_STATE_DIR/$1"
|
||||
}
|
||||
|
||||
# ── Interval → systemd OnCalendar (same list as entertainment) ──
|
||||
eventer_interval_to_oncalendar() {
|
||||
local i="$1"
|
||||
case "$i" in
|
||||
OnCalendar=*) printf '%s' "${i#OnCalendar=}"; return 0 ;;
|
||||
esac
|
||||
if [[ "$i" =~ ^([0-9]+)m$ ]]; then
|
||||
local n="${BASH_REMATCH[1]}"
|
||||
[ "$n" -ge 1 ] && [ "$n" -le 59 ] || return 1
|
||||
printf '*:00/%s:00' "$n"; return 0
|
||||
fi
|
||||
if [[ "$i" =~ ^([0-9]+)h$ ]]; then
|
||||
local n="${BASH_REMATCH[1]}"
|
||||
[ "$n" -ge 1 ] && [ "$n" -le 23 ] || return 1
|
||||
printf '*-*-* 00/%s:00:00' "$n"; return 0
|
||||
fi
|
||||
case "$i" in
|
||||
hourly) printf '*-*-* *:00:00' ;;
|
||||
daily) printf '*-*-* 08:00:00' ;;
|
||||
weekly) printf 'Mon *-*-* 08:00:00' ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
return 0
|
||||
}
|
||||
|
||||
eventer_interval_label() {
|
||||
case "$1" in
|
||||
daily) echo "daily (08:00)" ;;
|
||||
weekly) echo "weekly (Mon 08:00)" ;;
|
||||
hourly) echo "hourly" ;;
|
||||
OnCalendar=*) echo "${1#OnCalendar=}" ;;
|
||||
*m|*h) echo "every $1" ;;
|
||||
*) echo "$1" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ── systemd user timer ───────────────────────────────────────────
|
||||
eventer_ensure_linger() {
|
||||
local user
|
||||
user="$(id -un)"
|
||||
[ "$user" = "root" ] && { warn "running as root — enable linger for your real user: sudo loginctl enable-linger <user>"; return 0; }
|
||||
command -v loginctl >/dev/null 2>&1 || return 0
|
||||
if loginctl show-user "$user" 2>/dev/null | grep -q '^Linger=yes'; then
|
||||
return 0
|
||||
fi
|
||||
if sudo -n loginctl enable-linger "$user" 2>/dev/null; then
|
||||
ok "enabled linger for $user (timers fire without login)"
|
||||
else
|
||||
warn "run once so timers fire without login: sudo loginctl enable-linger $user"
|
||||
fi
|
||||
}
|
||||
|
||||
eventer_write_units() {
|
||||
local oncal="$1"
|
||||
mkdir -p "$USER_SYSTEMD_DIR"
|
||||
cat >"$USER_SYSTEMD_DIR/$EVENT_UNIT.service" <<EOF
|
||||
[Unit]
|
||||
Description=pos system event-trigger — evaluate event.env rules
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=$EVENT_RUNNER run
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
EOF
|
||||
cat >"$USER_SYSTEMD_DIR/$EVENT_UNIT.timer" <<EOF
|
||||
[Unit]
|
||||
Description=Schedule: pos system event-trigger
|
||||
|
||||
[Timer]
|
||||
OnCalendar=$oncal
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
EOF
|
||||
chmod 644 "$USER_SYSTEMD_DIR/$EVENT_UNIT.service" "$USER_SYSTEMD_DIR/$EVENT_UNIT.timer"
|
||||
}
|
||||
|
||||
eventer_enable() {
|
||||
local interval="${1:-5m}" oncal
|
||||
oncal="$(eventer_interval_to_oncalendar "$interval")" || {
|
||||
warn "invalid interval '$interval' (use 5m 10m 15m 30m 45m hourly 2h 6h 12h daily weekly, or OnCalendar=…)"
|
||||
return 1
|
||||
}
|
||||
if [ "${DRY_RUN:-0}" -eq 1 ]; then
|
||||
log "(dry-run) write $USER_SYSTEMD_DIR/$EVENT_UNIT.timer (OnCalendar=$oncal)"
|
||||
return 0
|
||||
fi
|
||||
eventer_write_units "$oncal"
|
||||
systemctl --user daemon-reload >/dev/null 2>&1 || warn "daemon-reload failed (user systemd running?)"
|
||||
if systemctl --user enable --now "$EVENT_UNIT.timer" >/dev/null 2>&1; then
|
||||
ok "$EVENT_UNIT.timer enabled ($(eventer_interval_label "$interval"))"
|
||||
eventer_ensure_linger
|
||||
else
|
||||
warn "could not enable $EVENT_UNIT.timer (is the user systemd manager running?)"
|
||||
fi
|
||||
}
|
||||
|
||||
eventer_disable() {
|
||||
if [ "${DRY_RUN:-0}" -eq 1 ]; then
|
||||
log "(dry-run) remove $USER_SYSTEMD_DIR/$EVENT_UNIT.timer + .service"
|
||||
return 0
|
||||
fi
|
||||
if [ -f "$USER_SYSTEMD_DIR/$EVENT_UNIT.timer" ]; then
|
||||
systemctl --user disable --now "$EVENT_UNIT.timer" >/dev/null 2>&1 || true
|
||||
rm -f "$USER_SYSTEMD_DIR/$EVENT_UNIT.timer" "$USER_SYSTEMD_DIR/$EVENT_UNIT.service"
|
||||
systemctl --user daemon-reload >/dev/null 2>&1 || true
|
||||
ok "$EVENT_UNIT.timer disabled"
|
||||
else
|
||||
warn "$EVENT_UNIT.timer is not enabled"
|
||||
fi
|
||||
}
|
||||
|
||||
eventer_status() {
|
||||
if [ -f "$USER_SYSTEMD_DIR/$EVENT_UNIT.timer" ] && systemctl --user show-environment >/dev/null 2>&1 \
|
||||
&& systemctl --user is-enabled "$EVENT_UNIT.timer" >/dev/null 2>&1; then
|
||||
local oncal next
|
||||
oncal="$(sed -n 's/^OnCalendar=//p' "$USER_SYSTEMD_DIR/$EVENT_UNIT.timer")"
|
||||
next="$(systemctl --user list-timers "$EVENT_UNIT.timer" --no-legend 2>/dev/null | awk '{print $1, $2}' | head -1)"
|
||||
echo "enabled — schedule: ${oncal}${next:+ (next: $next)}"
|
||||
echo "rules: $(eventer_count_rules) in $CONFIG_FILE"
|
||||
else
|
||||
echo "not enabled (run: pos system event-trigger enable [interval])"
|
||||
fi
|
||||
}
|
||||
+1
-1
@@ -37,7 +37,7 @@ fi
|
||||
# ── system + notify config templates ───────────────────────────
|
||||
# Copied only if the user has not already created their own (no clobber).
|
||||
run mkdir -p "$ENT_DIR"
|
||||
for tpl in system.env notify.env ai.env; do
|
||||
for tpl in system.env notify.env ai.env event.env; do
|
||||
if [ -f "config/$tpl" ]; then
|
||||
if [ -f "$ENT_DIR/$tpl" ]; then
|
||||
log "$tpl already exists, keeping it"
|
||||
|
||||
Reference in New Issue
Block a user