first functional commit
This commit is contained in:
parent
5e961d70f4
commit
37efa43d0a
3 changed files with 422 additions and 101 deletions
20
CLAUDE.md
20
CLAUDE.md
|
|
@ -18,25 +18,31 @@ No build step. The script requires `bash`, `systemctl`, and optionally `notify-s
|
||||||
|
|
||||||
The script has two modes controlled by CLI flags:
|
The script has two modes controlled by CLI flags:
|
||||||
|
|
||||||
- **Job creation** (`-t <time> [-c <cmd> | -f <script> | stdin]`): Generates a systemd `.service` + `.timer` pair with a 6-char hex short ID, reloads the daemon, and enables/starts the timer. Time specs are parsed via `date -d` or passed through as systemd OnCalendar values. One-time jobs get `Persistent=false` and `RemainAfterElapse=no` (auto-unload after firing).
|
- **Job creation** (`-t <time> [-c <cmd> | -f <script> | stdin]`): Generates a systemd `.service` + `.timer` pair with a 6-char hex short ID, reloads the daemon, and enables/starts the timer. Time specs are parsed via `parse_time` which handles natural language (`every 5 minutes`), `date -d` relative/absolute times, and raw systemd OnCalendar values. One-time jobs get `Persistent=false` and `RemainAfterElapse=no` (auto-unload after firing). All jobs log stdout/stderr to the journal via `SyslogIdentifier`.
|
||||||
|
|
||||||
- **Management** (`-E`, `-L`, `-S`, `-C` — mutually exclusive):
|
- **Management** (`-P`, `-R`, `-E`, `-L`, `-S`, `-C` — mutually exclusive):
|
||||||
- `-E`: Opens `$EDITOR` with a tab-separated crontab (`ID SCHEDULE COMMAND`). On save, diffs against the original to apply creates (ID=`new`), deletes (removed lines), and updates (changed schedule/command). Legacy jobs without IDs get one auto-assigned.
|
- `-P <id>` / `-R <id>`: Pause (stop+disable) or resume (enable+start) a job's timer.
|
||||||
- `-L [filter]`: Query `journalctl` logs for managed jobs.
|
- `-E`: Opens `$EDITOR` with a pipe-separated crontab (`ID | SCHEDULE | COMMAND`). On save, diffs against the original to apply creates (ID=`new`), deletes (removed lines), updates (changed schedule/command), and pause/resume (comment/uncomment lines).
|
||||||
- `-S`: Show timer status via `systemctl`, including short IDs.
|
- `-L [filter]`: Query `journalctl` logs for managed jobs (both unit messages and command output).
|
||||||
|
- `-S`: Show timer status via `systemctl`, including short IDs and disabled state.
|
||||||
- `-C`: Interactively clean up elapsed one-time timers (removes unit files from disk).
|
- `-C`: Interactively clean up elapsed one-time timers (removes unit files from disk).
|
||||||
|
|
||||||
Key functions: `parse_time` (time spec → OnCalendar), `create_job` (generates unit files), `edit_jobs` (crontab-style edit with diff-and-apply), `get_managed_services`/`get_managed_timers` (find tagged units), `clean_jobs` (remove elapsed one-time timers).
|
Key functions: `parse_time` (time spec → OnCalendar), `create_job` (generates unit files), `edit_jobs` (crontab-style edit with diff-and-apply), `get_managed_services`/`get_managed_timers` (find tagged units), `clean_jobs` (remove elapsed one-time timers), `pause_job`/`resume_job` (disable/enable timers).
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
There are no automated tests. Test manually with systemd user timers:
|
There are no automated tests. Test manually with systemd user timers:
|
||||||
```bash
|
```bash
|
||||||
./systab -t "in 1 minute" -c "echo test"
|
./systab -t "every 5 minutes" -c "echo test"
|
||||||
./systab -S
|
./systab -S
|
||||||
|
./systab -L
|
||||||
|
./systab -P <id>
|
||||||
|
./systab -R <id>
|
||||||
./systab -C
|
./systab -C
|
||||||
```
|
```
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- ShellCheck can be used for linting: `shellcheck systab`.
|
- ShellCheck can be used for linting: `shellcheck systab`.
|
||||||
|
- Edit mode uses `|` as the field delimiter (not tabs or spaces) to allow multi-word schedules.
|
||||||
|
- Journal logs are queried with `USER_UNIT` OR `SYSLOG_IDENTIFIER` to capture both systemd messages and command output.
|
||||||
|
|
|
||||||
144
README.md
Normal file
144
README.md
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
# systab
|
||||||
|
|
||||||
|
A cron/at-like interface for systemd user timers. Create, manage, and monitor scheduled jobs without writing unit files by hand.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
Copy the `systab` script somewhere on your `$PATH`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp systab ~/.local/bin/
|
||||||
|
```
|
||||||
|
|
||||||
|
Requires `bash`, `systemctl`, and optionally `notify-send` (for `-i`) and `mail` (for `-m`).
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run a command every 5 minutes
|
||||||
|
systab -t "every 5 minutes" -c "curl -s https://example.com/health"
|
||||||
|
|
||||||
|
# Run a backup script every day at 2am
|
||||||
|
systab -t "every day at 2am" -f ~/backup.sh
|
||||||
|
|
||||||
|
# Run a one-time command in 30 minutes
|
||||||
|
systab -t "in 30 minutes" -c "echo reminder"
|
||||||
|
|
||||||
|
# Check status of all jobs
|
||||||
|
systab -S
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
systab -L
|
||||||
|
```
|
||||||
|
|
||||||
|
## Time formats
|
||||||
|
|
||||||
|
systab accepts several time formats:
|
||||||
|
|
||||||
|
| Format | Example | Type |
|
||||||
|
|--------|---------|------|
|
||||||
|
| Natural recurring | `every 5 minutes` | Recurring |
|
||||||
|
| Natural recurring | `every 2 hours` | Recurring |
|
||||||
|
| Natural recurring | `every 30 seconds` | Recurring |
|
||||||
|
| Natural recurring | `every day at 2am` | Recurring |
|
||||||
|
| Natural recurring | `every monday at 9am` | Recurring |
|
||||||
|
| Natural recurring | `every month` | Recurring |
|
||||||
|
| Relative | `in 5 minutes` | One-time |
|
||||||
|
| Relative | `tomorrow` | One-time |
|
||||||
|
| Absolute | `2025-06-15 14:30` | One-time |
|
||||||
|
| Absolute | `next tuesday at noon` | One-time |
|
||||||
|
| Systemd keyword | `hourly`, `daily`, `weekly`, `monthly` | Recurring |
|
||||||
|
| Systemd OnCalendar | `*:0/15` (every 15 min) | Recurring |
|
||||||
|
| Systemd OnCalendar | `*-*-* 02:00:00` (daily at 2am) | Recurring |
|
||||||
|
| Systemd OnCalendar | `Mon *-*-* 09:00` (Mondays at 9am) | Recurring |
|
||||||
|
|
||||||
|
Relative and absolute formats are parsed by `date -d`. Systemd OnCalendar values are passed through directly.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Creating jobs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Command string
|
||||||
|
systab -t "every 5 minutes" -c "echo hello"
|
||||||
|
|
||||||
|
# Script file
|
||||||
|
systab -t "every day at 2am" -f ~/backup.sh
|
||||||
|
|
||||||
|
# From stdin
|
||||||
|
echo "ls -la /tmp" | systab -t daily
|
||||||
|
|
||||||
|
# With desktop notification on completion
|
||||||
|
systab -t "in 1 hour" -c "make build" -i
|
||||||
|
|
||||||
|
# With email notification
|
||||||
|
systab -t "every day at 6am" -c "df -h" -m user@example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
### Managing jobs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Edit all jobs in your $EDITOR (crontab-style)
|
||||||
|
systab -E
|
||||||
|
|
||||||
|
# Show status of all jobs
|
||||||
|
systab -S
|
||||||
|
|
||||||
|
# View logs (all jobs)
|
||||||
|
systab -L
|
||||||
|
|
||||||
|
# View logs (filtered)
|
||||||
|
systab -L error
|
||||||
|
|
||||||
|
# Pause a job
|
||||||
|
systab -P <id>
|
||||||
|
|
||||||
|
# Resume a paused job
|
||||||
|
systab -R <id>
|
||||||
|
|
||||||
|
# Clean up completed one-time jobs
|
||||||
|
systab -C
|
||||||
|
```
|
||||||
|
|
||||||
|
### Edit mode
|
||||||
|
|
||||||
|
`systab -E` opens your editor with a pipe-delimited job list:
|
||||||
|
|
||||||
|
```
|
||||||
|
a1b2c3 | daily | /home/user/backup.sh
|
||||||
|
d4e5f6 | *:0/15 | curl -s https://example.com
|
||||||
|
# g7h8i9 | hourly | echo "this job is paused"
|
||||||
|
```
|
||||||
|
|
||||||
|
- Edit the schedule or command to update a job
|
||||||
|
- Delete a line to remove a job
|
||||||
|
- Add a line with `new` as the ID to create a job: `new | every 5 minutes | echo hello`
|
||||||
|
- Comment out a line (`#`) to pause, uncomment to resume
|
||||||
|
|
||||||
|
### Job IDs
|
||||||
|
|
||||||
|
Each job gets a 6-character hex ID (e.g., `a1b2c3`) displayed on creation and in status output. Use this ID with `-P`, `-R`, and `-L`.
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
|
||||||
|
systab creates systemd `.service` and `.timer` unit file pairs in `~/.config/systemd/user/`. Each managed unit is tagged with a `# SYSTAB_MANAGED` marker comment. One-time jobs auto-unload after firing. Job output (stdout/stderr) is captured in the systemd journal and viewable via `systab -L`.
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
```
|
||||||
|
Job Creation:
|
||||||
|
-t <time> Time specification (required for job creation)
|
||||||
|
-c <command> Command string to execute
|
||||||
|
-f <script> Script file to execute (reads stdin if neither -c nor -f)
|
||||||
|
-i Send desktop notification on completion
|
||||||
|
-m <email> Send email notification to address
|
||||||
|
|
||||||
|
Management:
|
||||||
|
-P <id> Pause (disable) a job
|
||||||
|
-R <id> Resume (enable) a paused job
|
||||||
|
-E Edit jobs in crontab-like format
|
||||||
|
-L [filter] List job logs (optionally filtered)
|
||||||
|
-S Show status of all managed jobs
|
||||||
|
-C Clean up completed one-time jobs
|
||||||
|
-h Show help
|
||||||
|
```
|
||||||
345
systab
345
systab
|
|
@ -13,12 +13,13 @@ opt_time=""
|
||||||
opt_command=""
|
opt_command=""
|
||||||
opt_file=""
|
opt_file=""
|
||||||
opt_notify=false
|
opt_notify=false
|
||||||
opt_syslog=false
|
|
||||||
opt_email=""
|
opt_email=""
|
||||||
opt_edit=false
|
opt_edit=false
|
||||||
opt_list=false
|
opt_list=false
|
||||||
opt_clean=false
|
opt_clean=false
|
||||||
opt_status=false
|
opt_status=false
|
||||||
|
opt_pause=""
|
||||||
|
opt_resume=""
|
||||||
opt_filter=""
|
opt_filter=""
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
|
|
@ -32,36 +33,42 @@ Job Creation Options:
|
||||||
-c <command> Command string to execute
|
-c <command> Command string to execute
|
||||||
-f <script> Script file to execute
|
-f <script> Script file to execute
|
||||||
-i Send desktop notification on completion
|
-i Send desktop notification on completion
|
||||||
-s Log output to system journal
|
|
||||||
-m <email> Send email notification to address
|
-m <email> Send email notification to address
|
||||||
|
|
||||||
Management Options:
|
Management Options:
|
||||||
|
-P <id> Pause (disable) a job
|
||||||
|
-R <id> Resume (enable) a paused job
|
||||||
-E Edit jobs in crontab-like format
|
-E Edit jobs in crontab-like format
|
||||||
-L [filter] List job logs (optionally filtered)
|
-L [filter] List job logs (optionally filtered)
|
||||||
-S Show status of all managed jobs and timers
|
-S Show status of all managed jobs and timers
|
||||||
-C Clean up completed one-time jobs
|
-C Clean up completed one-time jobs
|
||||||
|
|
||||||
TIME FORMATS:
|
TIME FORMATS:
|
||||||
- Relative: "in 5 minutes", "in 2 hours", "tomorrow"
|
Natural: "every 5 minutes", "every day at 2am", "every monday at 9am"
|
||||||
- Absolute: "2025-01-21 14:30", "next tuesday at noon"
|
Relative: "in 5 minutes", "in 2 hours", "tomorrow"
|
||||||
- Systemd: "daily", "weekly", "hourly", "*:0/15" (every 15 min)
|
Absolute: "2025-01-21 14:30", "next tuesday at noon"
|
||||||
|
Systemd: "daily", "weekly", "hourly", "*:0/15" (every 15 min)
|
||||||
|
|
||||||
EXAMPLES:
|
EXAMPLES:
|
||||||
# Run command in 5 minutes
|
# Run command every 5 minutes
|
||||||
|
$SCRIPT_NAME -t "every 5 minutes" -c "echo Hello"
|
||||||
|
|
||||||
|
# Run script every day at 2am with notification
|
||||||
|
$SCRIPT_NAME -t "every day at 2am" -f ~/backup.sh -i
|
||||||
|
|
||||||
|
# Run command in 5 minutes (one-time)
|
||||||
$SCRIPT_NAME -t "in 5 minutes" -c "echo Hello"
|
$SCRIPT_NAME -t "in 5 minutes" -c "echo Hello"
|
||||||
|
|
||||||
# Run script tomorrow with notification
|
|
||||||
$SCRIPT_NAME -t tomorrow -f ~/backup.sh -i
|
|
||||||
|
|
||||||
# Run command every hour
|
|
||||||
$SCRIPT_NAME -t hourly -c "curl -s https://example.com"
|
|
||||||
|
|
||||||
# Read command from stdin
|
# Read command from stdin
|
||||||
echo "ls -la" | $SCRIPT_NAME -t "next monday at 9am"
|
echo "ls -la" | $SCRIPT_NAME -t "next monday at 9am"
|
||||||
|
|
||||||
# Edit existing jobs
|
# Edit existing jobs
|
||||||
$SCRIPT_NAME -E
|
$SCRIPT_NAME -E
|
||||||
|
|
||||||
|
# Pause and resume a job
|
||||||
|
$SCRIPT_NAME -P <id>
|
||||||
|
$SCRIPT_NAME -R <id>
|
||||||
|
|
||||||
# View logs for backup jobs
|
# View logs for backup jobs
|
||||||
$SCRIPT_NAME -L backup
|
$SCRIPT_NAME -L backup
|
||||||
|
|
||||||
|
|
@ -88,13 +95,51 @@ warn() {
|
||||||
parse_time() {
|
parse_time() {
|
||||||
local time_spec="$1"
|
local time_spec="$1"
|
||||||
|
|
||||||
# Common systemd calendar formats
|
# Common systemd calendar formats (pass through)
|
||||||
case "${time_spec,,}" in
|
case "${time_spec,,}" in
|
||||||
hourly|daily|weekly|monthly|yearly) echo "$time_spec"; return ;;
|
hourly|daily|weekly|monthly|yearly) echo "$time_spec"; return ;;
|
||||||
*:*) echo "$time_spec"; return ;; # Assume systemd time format
|
*:*|*-*-*) echo "$time_spec"; return ;; # Assume systemd time format
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# Try to parse with date command
|
# Natural language recurring patterns
|
||||||
|
local lower="${time_spec,,}"
|
||||||
|
if [[ "$lower" =~ ^every[[:space:]]+(.+) ]]; then
|
||||||
|
local rest="${BASH_REMATCH[1]}"
|
||||||
|
case "$rest" in
|
||||||
|
# "every N minutes/hours/seconds"
|
||||||
|
[0-9]*' minute'*) echo "*:0/${rest%% *}"; return ;;
|
||||||
|
[0-9]*' hour'*) echo "0/${rest%% *}:00"; return ;;
|
||||||
|
[0-9]*' second'*) echo "*:*:0/${rest%% *}"; return ;;
|
||||||
|
# "every minute/hour"
|
||||||
|
'minute'*) echo "*:*"; return ;;
|
||||||
|
'hour'*) echo "hourly"; return ;;
|
||||||
|
# "every day at <time>"
|
||||||
|
'day at '*)
|
||||||
|
local at_time="${rest#day at }"
|
||||||
|
local parsed
|
||||||
|
if parsed=$(date -d "$at_time" '+%H:%M:%S' 2>/dev/null); then
|
||||||
|
echo "*-*-* $parsed"; return
|
||||||
|
fi ;;
|
||||||
|
# "every <weekday> [at <time>]"
|
||||||
|
mon|monday|tue|tuesday|wed|wednesday|thu|thursday|fri|friday|sat|saturday|sun|sunday)
|
||||||
|
local day="${rest:0:3}"; day="${day^}"
|
||||||
|
echo "$day *-*-*"; return ;;
|
||||||
|
mon' at '*|monday' at '*|tue' at '*|tuesday' at '*|wed' at '*|wednesday' at '*|thu' at '*|thursday' at '*|fri' at '*|friday' at '*|sat' at '*|saturday' at '*|sun' at '*|sunday' at '*)
|
||||||
|
local day_part="${rest%% at *}" at_time="${rest#* at }"
|
||||||
|
local day="${day_part:0:3}"; day="${day^}"
|
||||||
|
local parsed
|
||||||
|
if parsed=$(date -d "$at_time" '+%H:%M:%S' 2>/dev/null); then
|
||||||
|
echo "$day *-*-* $parsed"; return
|
||||||
|
fi ;;
|
||||||
|
# "every day/week/month/year"
|
||||||
|
'day'*) echo "daily"; return ;;
|
||||||
|
'week'*) echo "weekly"; return ;;
|
||||||
|
'month'*) echo "monthly"; return ;;
|
||||||
|
'year'*) echo "yearly"; return ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Try to parse with date command (one-time specs)
|
||||||
local parsed_date
|
local parsed_date
|
||||||
if parsed_date=$(date -d "$time_spec" '+%Y-%m-%d %H:%M:%S' 2>/dev/null); then
|
if parsed_date=$(date -d "$time_spec" '+%Y-%m-%d %H:%M:%S' 2>/dev/null); then
|
||||||
echo "$parsed_date"
|
echo "$parsed_date"
|
||||||
|
|
@ -105,10 +150,13 @@ parse_time() {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if time spec is recurring
|
# Check if time spec is recurring
|
||||||
|
# One-time specs are absolute timestamps (from date -d): "2026-02-14 09:33:10"
|
||||||
|
# Everything else (calendar specs with wildcards, keywords, intervals) is recurring
|
||||||
is_recurring() {
|
is_recurring() {
|
||||||
local time_spec="$1"
|
local time_spec="$1"
|
||||||
case "${time_spec,,}" in
|
case "${time_spec,,}" in
|
||||||
hourly|daily|weekly|monthly|yearly|*:*/*|*/*) return 0 ;;
|
hourly|daily|weekly|monthly|yearly) return 0 ;;
|
||||||
|
*'*'*|*/*) return 0 ;; # Contains wildcard or interval
|
||||||
*) return 1 ;;
|
*) return 1 ;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
@ -123,22 +171,70 @@ job_id() {
|
||||||
echo "${1#"${SCRIPT_NAME}"_}"
|
echo "${1#"${SCRIPT_NAME}"_}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Check if a job's timer is enabled
|
||||||
|
is_job_enabled() {
|
||||||
|
systemctl --user is-enabled "${1}.timer" &>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Disable a job (stop + disable timer)
|
||||||
|
disable_job() {
|
||||||
|
systemctl --user stop "${1}.timer" 2>/dev/null || true
|
||||||
|
systemctl --user disable "${1}.timer" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Enable a job (enable + start timer)
|
||||||
|
enable_job() {
|
||||||
|
systemctl --user enable "${1}.timer" 2>/dev/null || true
|
||||||
|
systemctl --user start "${1}.timer" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate a short ID refers to a managed job, sets _job_name
|
||||||
|
validate_job_id() {
|
||||||
|
local id="$1"
|
||||||
|
_job_name="${SCRIPT_NAME}_${id}"
|
||||||
|
local timer_file="$SYSTEMD_USER_DIR/${_job_name}.timer"
|
||||||
|
[[ -f "$timer_file" ]] || error "No job found with ID: $id"
|
||||||
|
grep -q "^$MARKER" "$timer_file" 2>/dev/null || error "Not a managed job: $id"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Pause a job by short ID
|
||||||
|
pause_job() {
|
||||||
|
validate_job_id "$1"
|
||||||
|
if ! is_job_enabled "$_job_name"; then
|
||||||
|
echo "Already paused: $1"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
disable_job "$_job_name"
|
||||||
|
echo "Paused: $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Resume a job by short ID
|
||||||
|
resume_job() {
|
||||||
|
validate_job_id "$1"
|
||||||
|
if is_job_enabled "$_job_name"; then
|
||||||
|
echo "Already running: $1"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
enable_job "$_job_name"
|
||||||
|
echo "Resumed: $1"
|
||||||
|
}
|
||||||
|
|
||||||
# Get all managed service files
|
# Get all managed service files
|
||||||
get_managed_services() {
|
get_managed_services() {
|
||||||
if [[ ! -d "$SYSTEMD_USER_DIR" ]]; then
|
if [[ ! -d "$SYSTEMD_USER_DIR" ]]; then
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local file seen=()
|
local file
|
||||||
|
local -A seen=()
|
||||||
for file in "$SYSTEMD_USER_DIR"/*.service; do
|
for file in "$SYSTEMD_USER_DIR"/*.service; do
|
||||||
[[ -f "$file" ]] || continue
|
[[ -f "$file" ]] || continue
|
||||||
if grep -q "^$MARKER" "$file" 2>/dev/null; then
|
if grep -q "^$MARKER" "$file" 2>/dev/null; then
|
||||||
local jobname
|
local jobname
|
||||||
jobname=$(basename "$file" .service)
|
jobname=$(basename "$file" .service)
|
||||||
# Only output each job once
|
if [[ -z "${seen[$jobname]+x}" ]]; then
|
||||||
if [[ ! " ${seen[*]} " =~ " ${jobname} " ]]; then
|
|
||||||
echo "$jobname"
|
echo "$jobname"
|
||||||
seen+=("$jobname")
|
seen["$jobname"]=1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
@ -150,16 +246,16 @@ get_managed_timers() {
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local file seen=()
|
local file
|
||||||
|
local -A seen=()
|
||||||
for file in "$SYSTEMD_USER_DIR"/*.timer; do
|
for file in "$SYSTEMD_USER_DIR"/*.timer; do
|
||||||
[[ -f "$file" ]] || continue
|
[[ -f "$file" ]] || continue
|
||||||
if grep -q "^$MARKER" "$file" 2>/dev/null; then
|
if grep -q "^$MARKER" "$file" 2>/dev/null; then
|
||||||
local jobname
|
local jobname
|
||||||
jobname=$(basename "$file" .timer)
|
jobname=$(basename "$file" .timer)
|
||||||
# Only output each job once
|
if [[ -z "${seen[$jobname]+x}" ]]; then
|
||||||
if [[ ! " ${seen[*]} " =~ " ${jobname} " ]]; then
|
|
||||||
echo "$jobname"
|
echo "$jobname"
|
||||||
seen+=("$jobname")
|
seen["$jobname"]=1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
@ -200,16 +296,10 @@ Description=$SCRIPT_NAME job: $job_name
|
||||||
[Service]
|
[Service]
|
||||||
Type=oneshot
|
Type=oneshot
|
||||||
ExecStart=${SHELL:-/bin/bash} -c '$command_to_run'
|
ExecStart=${SHELL:-/bin/bash} -c '$command_to_run'
|
||||||
EOF
|
|
||||||
|
|
||||||
# Add logging options
|
|
||||||
if $opt_syslog; then
|
|
||||||
cat >> "$service_file" <<EOF
|
|
||||||
StandardOutput=journal
|
StandardOutput=journal
|
||||||
StandardError=journal
|
StandardError=journal
|
||||||
SyslogIdentifier=$job_name
|
SyslogIdentifier=$job_name
|
||||||
EOF
|
EOF
|
||||||
fi
|
|
||||||
|
|
||||||
# Add notification wrapper if needed
|
# Add notification wrapper if needed
|
||||||
if $opt_notify || [[ -n "$opt_email" ]]; then
|
if $opt_notify || [[ -n "$opt_email" ]]; then
|
||||||
|
|
@ -272,7 +362,8 @@ remove_job() {
|
||||||
|
|
||||||
# Create a job from edit mode (schedule + command, prints short ID)
|
# Create a job from edit mode (schedule + command, prints short ID)
|
||||||
create_job_from_edit() {
|
create_job_from_edit() {
|
||||||
local schedule="$1" command_to_run="$2"
|
local schedule command_to_run="$2"
|
||||||
|
schedule=$(parse_time "$1")
|
||||||
local job_name
|
local job_name
|
||||||
|
|
||||||
job_name="${SCRIPT_NAME}_$(generate_id)"
|
job_name="${SCRIPT_NAME}_$(generate_id)"
|
||||||
|
|
@ -288,6 +379,9 @@ Description=$SCRIPT_NAME job: $job_name
|
||||||
[Service]
|
[Service]
|
||||||
Type=oneshot
|
Type=oneshot
|
||||||
ExecStart=${SHELL:-/bin/bash} -c '$command_to_run'
|
ExecStart=${SHELL:-/bin/bash} -c '$command_to_run'
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
SyslogIdentifier=$job_name
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Timer file
|
# Timer file
|
||||||
|
|
@ -331,9 +425,10 @@ edit_jobs() {
|
||||||
{
|
{
|
||||||
cat <<'HEADER'
|
cat <<'HEADER'
|
||||||
# systab jobs — edit schedule/command, add/remove lines
|
# systab jobs — edit schedule/command, add/remove lines
|
||||||
# Format: ID SCHEDULE COMMAND (whitespace-separated)
|
# Format: ID | SCHEDULE | COMMAND (pipe-separated)
|
||||||
# Lines starting with # are ignored. Remove a line to delete a job.
|
# Remove a line to delete a job.
|
||||||
# Add a line with "new" as ID to create a job: new daily /path/to/cmd
|
# Add a line with "new" as ID to create a job: new | daily | /path/to/cmd
|
||||||
|
# Comment out a line to disable, uncomment to re-enable.
|
||||||
#
|
#
|
||||||
# Schedule formats (systemd OnCalendar):
|
# Schedule formats (systemd OnCalendar):
|
||||||
# hourly, daily, weekly, monthly, yearly
|
# hourly, daily, weekly, monthly, yearly
|
||||||
|
|
@ -355,7 +450,11 @@ HEADER
|
||||||
id=$(job_id "$job")
|
id=$(job_id "$job")
|
||||||
schedule=$(grep "^OnCalendar=" "$timer_file" | cut -d= -f2-)
|
schedule=$(grep "^OnCalendar=" "$timer_file" | cut -d= -f2-)
|
||||||
command=$(grep "^ExecStart=" "$service_file" | sed 's/^ExecStart=.*-c //' | sed "s/^'//" | sed "s/'$//")
|
command=$(grep "^ExecStart=" "$service_file" | sed 's/^ExecStart=.*-c //' | sed "s/^'//" | sed "s/'$//")
|
||||||
printf '%s\t%s\t%s\n' "$id" "$schedule" "$command"
|
if is_job_enabled "$job"; then
|
||||||
|
printf '%s | %s | %s\n' "$id" "$schedule" "$command"
|
||||||
|
else
|
||||||
|
printf '# %s | %s | %s\n' "$id" "$schedule" "$command"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
done < <(get_managed_timers)
|
done < <(get_managed_timers)
|
||||||
} > "$temp_file"
|
} > "$temp_file"
|
||||||
|
|
@ -377,45 +476,66 @@ HEADER
|
||||||
|
|
||||||
# Parse files into associative arrays: id -> "schedule\tcmd"
|
# Parse files into associative arrays: id -> "schedule\tcmd"
|
||||||
# "new" lines are collected separately since there can be multiple
|
# "new" lines are collected separately since there can be multiple
|
||||||
declare -A orig_jobs edited_jobs
|
# commented_ids tracks disabled (commented-out) job lines
|
||||||
|
declare -A orig_jobs edited_jobs orig_commented edited_commented
|
||||||
declare -a new_jobs=()
|
declare -a new_jobs=()
|
||||||
|
|
||||||
# Parse a crontab line into id, sched, cmd (split on first two whitespace runs)
|
# Parse a crontab line into id, sched, cmd (split on first two | delimiters)
|
||||||
parse_crontab_line() {
|
parse_crontab_line() {
|
||||||
local line="$1"
|
local line="$1"
|
||||||
_parsed_id="" _parsed_sched="" _parsed_cmd=""
|
_parsed_id="" _parsed_sched="" _parsed_cmd=""
|
||||||
if [[ "$line" =~ ^([^[:space:]]+)[[:space:]]+([^[:space:]]+)[[:space:]]+(.*) ]]; then
|
if [[ "$line" == *"|"* ]]; then
|
||||||
_parsed_id="${BASH_REMATCH[1]}"
|
_parsed_id="${line%%|*}"
|
||||||
_parsed_sched="${BASH_REMATCH[2]}"
|
local rest="${line#*|}"
|
||||||
_parsed_cmd="${BASH_REMATCH[3]}"
|
_parsed_sched="${rest%%|*}"
|
||||||
|
_parsed_cmd="${rest#*|}"
|
||||||
|
# Trim leading/trailing whitespace
|
||||||
|
_parsed_id="${_parsed_id#"${_parsed_id%%[![:space:]]*}"}" _parsed_id="${_parsed_id%"${_parsed_id##*[![:space:]]}"}"
|
||||||
|
_parsed_sched="${_parsed_sched#"${_parsed_sched%%[![:space:]]*}"}" _parsed_sched="${_parsed_sched%"${_parsed_sched##*[![:space:]]}"}"
|
||||||
|
_parsed_cmd="${_parsed_cmd#"${_parsed_cmd%%[![:space:]]*}"}" _parsed_cmd="${_parsed_cmd%"${_parsed_cmd##*[![:space:]]}"}"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Parse original file
|
# Parse a file, populating jobs and commented arrays
|
||||||
while IFS= read -r line; do
|
# Usage: parse_crontab_file <file> <jobs_var> <commented_var> [new_jobs_var]
|
||||||
[[ "$line" =~ ^#.*$ || -z "$line" ]] && continue
|
parse_crontab_file() {
|
||||||
parse_crontab_line "$line"
|
local file="$1" jobs_ref="$2" commented_ref="$3" new_ref="${4-}"
|
||||||
[[ -n "$_parsed_id" && -n "$_parsed_sched" ]] || continue
|
local line
|
||||||
orig_jobs["$_parsed_id"]="${_parsed_sched} ${_parsed_cmd}"
|
while IFS= read -r line; do
|
||||||
done < "$orig_file"
|
[[ -z "$line" ]] && continue
|
||||||
|
# Check for commented job line: "# <id> <sched> <cmd>"
|
||||||
|
if [[ "$line" =~ ^#[[:space:]]+(.*) ]]; then
|
||||||
|
local uncommented="${BASH_REMATCH[1]}"
|
||||||
|
parse_crontab_line "$uncommented"
|
||||||
|
# Only treat as disabled job if ID looks like a hex short ID
|
||||||
|
if [[ -n "$_parsed_id" && -n "$_parsed_sched" && "$_parsed_id" =~ ^[0-9a-f]{6}$ ]]; then
|
||||||
|
eval "${commented_ref}[\"\$_parsed_id\"]=\"\${_parsed_sched}|\${_parsed_cmd}\""
|
||||||
|
fi
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
parse_crontab_line "$line"
|
||||||
|
[[ -n "$_parsed_id" && -n "$_parsed_sched" ]] || continue
|
||||||
|
if [[ "$_parsed_id" == "new" && -n "$new_ref" ]]; then
|
||||||
|
eval "${new_ref}+=(\"\${_parsed_sched}|\${_parsed_cmd}\")"
|
||||||
|
else
|
||||||
|
eval "${jobs_ref}[\"\$_parsed_id\"]=\"\${_parsed_sched}|\${_parsed_cmd}\""
|
||||||
|
fi
|
||||||
|
done < "$file"
|
||||||
|
}
|
||||||
|
|
||||||
# Parse edited file
|
parse_crontab_file "$orig_file" orig_jobs orig_commented
|
||||||
while IFS= read -r line; do
|
parse_crontab_file "$temp_file" edited_jobs edited_commented new_jobs
|
||||||
[[ "$line" =~ ^#.*$ || -z "$line" ]] && continue
|
|
||||||
parse_crontab_line "$line"
|
|
||||||
[[ -n "$_parsed_id" && -n "$_parsed_sched" ]] || continue
|
|
||||||
if [[ "$_parsed_id" == "new" ]]; then
|
|
||||||
new_jobs+=("${_parsed_sched} ${_parsed_cmd}")
|
|
||||||
else
|
|
||||||
edited_jobs["$_parsed_id"]="${_parsed_sched} ${_parsed_cmd}"
|
|
||||||
fi
|
|
||||||
done < "$temp_file"
|
|
||||||
|
|
||||||
local created=0 deleted=0 updated=0 needs_reload=false
|
# Collect all known IDs (from both active and commented in original)
|
||||||
|
declare -A all_orig_ids
|
||||||
|
for id in "${!orig_jobs[@]}"; do all_orig_ids["$id"]=1; done
|
||||||
|
for id in "${!orig_commented[@]}"; do all_orig_ids["$id"]=1; done
|
||||||
|
|
||||||
# Deletions: IDs in original but not in edited
|
local created=0 deleted=0 updated=0 enabled=0 disabled=0 needs_reload=false
|
||||||
for id in "${!orig_jobs[@]}"; do
|
|
||||||
if [[ -z "${edited_jobs[$id]+x}" ]]; then
|
# Deletions: IDs in original (active or commented) but absent from edited entirely
|
||||||
|
for id in "${!all_orig_ids[@]}"; do
|
||||||
|
if [[ -z "${edited_jobs[$id]+x}" && -z "${edited_commented[$id]+x}" ]]; then
|
||||||
remove_job "${SCRIPT_NAME}_${id}"
|
remove_job "${SCRIPT_NAME}_${id}"
|
||||||
echo "Deleted: $id"
|
echo "Deleted: $id"
|
||||||
deleted=$((deleted + 1))
|
deleted=$((deleted + 1))
|
||||||
|
|
@ -426,8 +546,8 @@ HEADER
|
||||||
# Creations: "new" lines from edited file
|
# Creations: "new" lines from edited file
|
||||||
for entry in "${new_jobs[@]}"; do
|
for entry in "${new_jobs[@]}"; do
|
||||||
local sched cmd
|
local sched cmd
|
||||||
sched=$(printf '%s' "$entry" | cut -f1)
|
sched="${entry%%|*}"
|
||||||
cmd=$(printf '%s' "$entry" | cut -f2-)
|
cmd="${entry#*|}"
|
||||||
local result
|
local result
|
||||||
result=$(create_job_from_edit "$sched" "$cmd")
|
result=$(create_job_from_edit "$sched" "$cmd")
|
||||||
echo "Created: $result"
|
echo "Created: $result"
|
||||||
|
|
@ -437,27 +557,65 @@ HEADER
|
||||||
|
|
||||||
# Handle unknown IDs (in edited but not in original and not "new")
|
# Handle unknown IDs (in edited but not in original and not "new")
|
||||||
for id in "${!edited_jobs[@]}"; do
|
for id in "${!edited_jobs[@]}"; do
|
||||||
if [[ -z "${orig_jobs[$id]+x}" ]]; then
|
if [[ -z "${all_orig_ids[$id]+x}" ]]; then
|
||||||
warn "Ignoring unknown ID '$id' — use 'new' to create jobs"
|
warn "Ignoring unknown ID '$id' — use 'new' to create jobs"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
for id in "${!edited_commented[@]}"; do
|
||||||
|
if [[ -z "${all_orig_ids[$id]+x}" ]]; then
|
||||||
|
warn "Ignoring unknown commented ID '$id'"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
# Updates: IDs in both but with changed values
|
# Enable/disable transitions and updates
|
||||||
for id in "${!edited_jobs[@]}"; do
|
for id in "${!all_orig_ids[@]}"; do
|
||||||
if [[ -n "${orig_jobs[$id]+x}" && "${orig_jobs[$id]}" != "${edited_jobs[$id]}" ]]; then
|
local jname="${SCRIPT_NAME}_${id}"
|
||||||
local jname="${SCRIPT_NAME}_${id}"
|
local was_commented=false now_commented=false
|
||||||
|
[[ -n "${orig_commented[$id]+x}" ]] && was_commented=true
|
||||||
|
[[ -n "${edited_commented[$id]+x}" ]] && now_commented=true
|
||||||
|
|
||||||
local old_entry="${orig_jobs[$id]}" new_entry="${edited_jobs[$id]}"
|
# Skip if deleted
|
||||||
|
[[ -z "${edited_jobs[$id]+x}" && -z "${edited_commented[$id]+x}" ]] && continue
|
||||||
|
|
||||||
|
# Get old and new values
|
||||||
|
local old_entry="" new_entry=""
|
||||||
|
if $was_commented; then
|
||||||
|
old_entry="${orig_commented[$id]}"
|
||||||
|
elif [[ -n "${orig_jobs[$id]+x}" ]]; then
|
||||||
|
old_entry="${orig_jobs[$id]}"
|
||||||
|
fi
|
||||||
|
if $now_commented; then
|
||||||
|
new_entry="${edited_commented[$id]}"
|
||||||
|
elif [[ -n "${edited_jobs[$id]+x}" ]]; then
|
||||||
|
new_entry="${edited_jobs[$id]}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Handle enable/disable transitions
|
||||||
|
if ! $was_commented && $now_commented; then
|
||||||
|
disable_job "$jname"
|
||||||
|
echo "Disabled: $id"
|
||||||
|
disabled=$((disabled + 1))
|
||||||
|
needs_reload=true
|
||||||
|
elif $was_commented && ! $now_commented; then
|
||||||
|
enable_job "$jname"
|
||||||
|
echo "Enabled: $id"
|
||||||
|
enabled=$((enabled + 1))
|
||||||
|
needs_reload=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Handle schedule/command changes (whether active or commented)
|
||||||
|
if [[ "$old_entry" != "$new_entry" ]]; then
|
||||||
local old_sched new_sched old_cmd new_cmd
|
local old_sched new_sched old_cmd new_cmd
|
||||||
old_sched=$(printf '%s' "$old_entry" | cut -f1)
|
old_sched="${old_entry%%|*}"
|
||||||
new_sched=$(printf '%s' "$new_entry" | cut -f1)
|
new_sched="${new_entry%%|*}"
|
||||||
old_cmd=$(printf '%s' "$old_entry" | cut -f2-)
|
old_cmd="${old_entry#*|}"
|
||||||
new_cmd=$(printf '%s' "$new_entry" | cut -f2-)
|
new_cmd="${new_entry#*|}"
|
||||||
|
|
||||||
local timer_file="$SYSTEMD_USER_DIR/${jname}.timer"
|
local timer_file="$SYSTEMD_USER_DIR/${jname}.timer"
|
||||||
local service_file="$SYSTEMD_USER_DIR/${jname}.service"
|
local service_file="$SYSTEMD_USER_DIR/${jname}.service"
|
||||||
|
|
||||||
if [[ "$old_sched" != "$new_sched" && -f "$timer_file" ]]; then
|
if [[ "$old_sched" != "$new_sched" && -f "$timer_file" ]]; then
|
||||||
|
new_sched=$(parse_time "$new_sched")
|
||||||
sed -i "s|^OnCalendar=.*|OnCalendar=$new_sched|" "$timer_file"
|
sed -i "s|^OnCalendar=.*|OnCalendar=$new_sched|" "$timer_file"
|
||||||
|
|
||||||
# Update one-time vs recurring settings
|
# Update one-time vs recurring settings
|
||||||
|
|
@ -475,7 +633,11 @@ HEADER
|
||||||
sed -i "s|^ExecStart=.*|ExecStart=${SHELL:-/bin/bash} -c '$new_cmd'|" "$service_file"
|
sed -i "s|^ExecStart=.*|ExecStart=${SHELL:-/bin/bash} -c '$new_cmd'|" "$service_file"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
systemctl --user restart "${jname}.timer" 2>/dev/null || true
|
# Only restart if the job is active (not being disabled)
|
||||||
|
if ! $now_commented; then
|
||||||
|
systemctl --user restart "${jname}.timer" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Updated: $id"
|
echo "Updated: $id"
|
||||||
updated=$((updated + 1))
|
updated=$((updated + 1))
|
||||||
needs_reload=true
|
needs_reload=true
|
||||||
|
|
@ -487,7 +649,7 @@ HEADER
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Summary: $created created, $updated updated, $deleted deleted"
|
echo "Summary: $created created, $updated updated, $deleted deleted, $enabled enabled, $disabled disabled"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Show status of all managed jobs
|
# Show status of all managed jobs
|
||||||
|
|
@ -538,7 +700,9 @@ show_status() {
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Timer status
|
# Timer status
|
||||||
if systemctl --user is-active "${job}.timer" &>/dev/null; then
|
if ! is_job_enabled "$job"; then
|
||||||
|
echo " Timer: Disabled"
|
||||||
|
elif systemctl --user is-active "${job}.timer" &>/dev/null; then
|
||||||
echo " Timer: Active"
|
echo " Timer: Active"
|
||||||
|
|
||||||
# Get next run time
|
# Get next run time
|
||||||
|
|
@ -619,11 +783,11 @@ list_logs() {
|
||||||
fi
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Show service logs
|
# Show service logs (unit messages + command output)
|
||||||
if [[ -n "$opt_filter" ]]; then
|
if [[ -n "$opt_filter" ]]; then
|
||||||
journalctl --user -u "${job}.service" --no-pager 2>/dev/null | $grep_cmd "$opt_filter" || echo "no matching logs"
|
journalctl --user USER_UNIT="${job}.service" + SYSLOG_IDENTIFIER="$job" --no-pager 2>/dev/null | $grep_cmd "$opt_filter" || echo "no matching logs"
|
||||||
else
|
else
|
||||||
journalctl --user -u "${job}.service" --no-pager 2>/dev/null || echo "no logs yet"
|
journalctl --user USER_UNIT="${job}.service" + SYSLOG_IDENTIFIER="$job" --no-pager 2>/dev/null || echo "no logs yet"
|
||||||
fi
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
done
|
done
|
||||||
|
|
@ -673,14 +837,15 @@ clean_jobs() {
|
||||||
|
|
||||||
# Parse command-line options
|
# Parse command-line options
|
||||||
parse_options() {
|
parse_options() {
|
||||||
while getopts "t:c:f:ism:ELSCh" opt; do
|
while getopts "t:c:f:im:P:R:ELSCh" opt; do
|
||||||
case $opt in
|
case $opt in
|
||||||
t) opt_time="$OPTARG" ;;
|
t) opt_time="$OPTARG" ;;
|
||||||
c) opt_command="$OPTARG" ;;
|
c) opt_command="$OPTARG" ;;
|
||||||
f) opt_file="$OPTARG" ;;
|
f) opt_file="$OPTARG" ;;
|
||||||
i) opt_notify=true ;;
|
i) opt_notify=true ;;
|
||||||
s) opt_syslog=true ;;
|
|
||||||
m) opt_email="$OPTARG" ;;
|
m) opt_email="$OPTARG" ;;
|
||||||
|
P) opt_pause="$OPTARG" ;;
|
||||||
|
R) opt_resume="$OPTARG" ;;
|
||||||
E) opt_edit=true ;;
|
E) opt_edit=true ;;
|
||||||
L) opt_list=true ;;
|
L) opt_list=true ;;
|
||||||
S) opt_status=true ;;
|
S) opt_status=true ;;
|
||||||
|
|
@ -697,17 +862,19 @@ parse_options() {
|
||||||
|
|
||||||
# Validate mutually exclusive options
|
# Validate mutually exclusive options
|
||||||
local manage_count=0
|
local manage_count=0
|
||||||
|
[[ -n "$opt_pause" ]] && manage_count=$((manage_count + 1))
|
||||||
|
[[ -n "$opt_resume" ]] && manage_count=$((manage_count + 1))
|
||||||
$opt_edit && manage_count=$((manage_count + 1))
|
$opt_edit && manage_count=$((manage_count + 1))
|
||||||
$opt_list && manage_count=$((manage_count + 1))
|
$opt_list && manage_count=$((manage_count + 1))
|
||||||
$opt_status && manage_count=$((manage_count + 1))
|
$opt_status && manage_count=$((manage_count + 1))
|
||||||
$opt_clean && manage_count=$((manage_count + 1))
|
$opt_clean && manage_count=$((manage_count + 1))
|
||||||
|
|
||||||
if [[ $manage_count -gt 1 ]]; then
|
if [[ $manage_count -gt 1 ]]; then
|
||||||
error "Options -E, -L, -S, and -C are mutually exclusive"
|
error "Options -P, -R, -E, -L, -S, and -C are mutually exclusive"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $manage_count -gt 0 && -n "$opt_time$opt_command$opt_file" ]]; then
|
if [[ $manage_count -gt 0 && -n "$opt_time$opt_command$opt_file" ]]; then
|
||||||
error "Management options -E, -L, -S, and -C cannot be used with job creation options"
|
error "Management options -P, -R, -E, -L, -S, and -C cannot be used with job creation options"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -n "$opt_command" && -n "$opt_file" ]]; then
|
if [[ -n "$opt_command" && -n "$opt_file" ]]; then
|
||||||
|
|
@ -716,7 +883,7 @@ parse_options() {
|
||||||
|
|
||||||
# Validate create mode requirements
|
# Validate create mode requirements
|
||||||
local is_manage_mode=false
|
local is_manage_mode=false
|
||||||
if $opt_edit || $opt_list || $opt_status || $opt_clean; then
|
if [[ -n "$opt_pause$opt_resume" ]] || $opt_edit || $opt_list || $opt_status || $opt_clean; then
|
||||||
is_manage_mode=true
|
is_manage_mode=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -740,7 +907,11 @@ main() {
|
||||||
parse_options "$@"
|
parse_options "$@"
|
||||||
|
|
||||||
# Determine mode based on options
|
# Determine mode based on options
|
||||||
if $opt_edit; then
|
if [[ -n "$opt_pause" ]]; then
|
||||||
|
pause_job "$opt_pause"
|
||||||
|
elif [[ -n "$opt_resume" ]]; then
|
||||||
|
resume_job "$opt_resume"
|
||||||
|
elif $opt_edit; then
|
||||||
edit_jobs
|
edit_jobs
|
||||||
elif $opt_list; then
|
elif $opt_list; then
|
||||||
list_logs
|
list_logs
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue