Rename all functions to camelCase
All checks were successful
CI / shellcheck (push) Successful in 17s
All checks were successful
CI / shellcheck (push) Successful in 17s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b71ef735af
commit
041f893242
3 changed files with 134 additions and 134 deletions
10
CLAUDE.md
10
CLAUDE.md
|
|
@ -19,17 +19,17 @@ 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> [-n <name>] [-c <cmd> | -f <script> | stdin]` or `-s [-n <name>] [-c <cmd> | -f <script> | stdin]`): Generates a systemd unit pair (or single unit for services) with a 6-char hex short ID, reloads the daemon, and enables/starts the unit. An optional `-n <name>` assigns a human-readable name that can be used interchangeably with hex IDs in all operations.
|
- **Job creation** (`-t <time> [-n <name>] [-c <cmd> | -f <script> | stdin]` or `-s [-n <name>] [-c <cmd> | -f <script> | stdin]`): Generates a systemd unit pair (or single unit for services) with a 6-char hex short ID, reloads the daemon, and enables/starts the unit. An optional `-n <name>` assigns a human-readable name that can be used interchangeably with hex IDs in all operations.
|
||||||
- **Timer jobs** (`-t`): Creates a `.service` + `.timer` pair. 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). Notifications (`-i` desktop, `-m` email, `-o` include output) use `ExecStopPost` so they fire on both success and failure with status-aware icons/messages. The `-o [N]` flag fetches the last N lines of journal output (default 10). Notification flags are persisted in the service file as a `# SYSTAB_FLAGS=` comment.
|
- **Timer jobs** (`-t`): Creates a `.service` + `.timer` pair. Time specs are parsed via `parseTime` 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). Notifications (`-i` desktop, `-m` email, `-o` include output) use `ExecStopPost` so they fire on both success and failure with status-aware icons/messages. The `-o [N]` flag fetches the last N lines of journal output (default 10). Notification flags are persisted in the service file as a `# SYSTAB_FLAGS=` comment.
|
||||||
- **Service jobs** (`-s`): Creates a single `.service` file with `Type=simple`, `Restart=on-failure`, and `WantedBy=default.target`. No `.timer` file is created. A `daemon-reload` runs before `enable`/`start` so systemd registers the unit first. Service jobs are tagged with `# SYSTAB_TYPE=service` in the service file. Mutually exclusive with `-t`, `-i`, `-m`, `-o`.
|
- **Service jobs** (`-s`): Creates a single `.service` file with `Type=simple`, `Restart=on-failure`, and `WantedBy=default.target`. No `.timer` file is created. A `daemon-reload` runs before `enable`/`start` so systemd registers the unit first. Service jobs are tagged with `# SYSTAB_TYPE=service` in the service file. Mutually exclusive with `-t`, `-i`, `-m`, `-o`.
|
||||||
|
|
||||||
- **Management** (`-D`, `-E`, `-e`, `-L`, `-S`, `-C`, `-h` — mutually exclusive):
|
- **Management** (`-D`, `-E`, `-e`, `-L`, `-S`, `-C`, `-h` — mutually exclusive):
|
||||||
- `-D <id|name>` / `-E <id|name>`: Disable (stop+disable) or enable (enable+start) a job's timer. Accepts hex ID or name.
|
- `-D <id|name>` / `-E <id|name>`: Disable (stop+disable) or enable (enable+start) a job's timer. Accepts hex ID or name.
|
||||||
- `-e`: Opens `$EDITOR` with a pipe-separated crontab (`ID[:FLAGS] | SCHEDULE | COMMAND`). Flags are appended to the ID with `:` (`s` = service, `i` = desktop, `e=addr` = email, `o` = output 10 lines, `o=N` = output N lines, `n=name` = job name, comma-separated). Service jobs appear with `service` as the schedule column. On save, diffs against the original to apply creates (ID=`new`), deletes (removed lines), updates (changed schedule/command/flags), and disable/enable (comment/uncomment lines). The `service` schedule keyword skips `parse_time` validation and routes to `_write_unit_files` with `job_type=service`.
|
- `-e`: Opens `$EDITOR` with a pipe-separated crontab (`ID[:FLAGS] | SCHEDULE | COMMAND`). Flags are appended to the ID with `:` (`s` = service, `i` = desktop, `e=addr` = email, `o` = output 10 lines, `o=N` = output N lines, `n=name` = job name, comma-separated). Service jobs appear with `service` as the schedule column. On save, diffs against the original to apply creates (ID=`new`), deletes (removed lines), updates (changed schedule/command/flags), and disable/enable (comment/uncomment lines). The `service` schedule keyword skips `parseTime` validation and routes to `_writeUnitFiles` with `job_type=service`.
|
||||||
- `-L [id|name] [filter]`: Query `journalctl` logs for managed jobs (both unit messages and command output). Optional job ID or name to filter to a single job.
|
- `-L [id|name] [filter]`: Query `journalctl` logs for managed jobs (both unit messages and command output). Optional job ID or name to filter to a single job.
|
||||||
- `-S [id|name]`: Show timer status via `systemctl`, including short IDs, names, and disabled state. Optional job ID or name to show a single job.
|
- `-S [id|name]`: Show timer status via `systemctl`, including short IDs, names, and disabled state. Optional job ID or name to show a single job.
|
||||||
- `-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), `_write_unit_files` (shared service+timer creation; 4th param `job_type` selects timer vs service path), `create_job`/`create_job_from_edit` (thin wrappers), `edit_jobs` (crontab-style edit with diff-and-apply), `get_managed_units` (find tagged units by type), `get_managed_service_jobs` (find service-only jobs by `# SYSTAB_TYPE=service` marker), `is_job_service` (detect service vs timer job), `clean_jobs` (remove elapsed one-time timers), `disable_job`/`enable_job` (stop+disable / enable+start, branching on `is_job_service`), `write_notify_lines` (append `ExecStopPost` notification lines), `build_flags_string`/`parse_flags` (convert between CLI options and flags format, including `s` flag), `resolve_job_id` (resolve hex ID or name to hex ID, accepts service-only jobs).
|
Key functions: `parseTime` (time spec → OnCalendar), `_writeUnitFiles` (shared service+timer creation; 4th param `job_type` selects timer vs service path), `createJob`/`createJobFromEdit` (thin wrappers), `editJobs` (crontab-style edit with diff-and-apply), `getManagedUnits` (find tagged units by type), `getManagedServiceJobs` (find service-only jobs by `# SYSTAB_TYPE=service` marker), `isJobService` (detect service vs timer job), `cleanJobs` (remove elapsed one-time timers), `disableJob`/`enableJob` (stop+disable / enable+start, branching on `isJobService`), `writeNotifyLines` (append `ExecStopPost` notification lines), `buildFlagsString`/`parseFlags` (convert between CLI options and flags format, including `s` flag), `resolveJobId` (resolve hex ID or name to hex ID, accepts service-only jobs).
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
|
|
@ -49,5 +49,5 @@ After cloning, enable the hooks: `git config core.hooksPath .githooks`
|
||||||
- Edit mode uses `|` as the field delimiter (not tabs or spaces) to allow multi-word schedules. Flags use `:` after the ID (e.g., `a1b2c3:n=backup,i,o,e=user@host`). Service jobs use the literal string `service` as the schedule column.
|
- Edit mode uses `|` as the field delimiter (not tabs or spaces) to allow multi-word schedules. Flags use `:` after the ID (e.g., `a1b2c3:n=backup,i,o,e=user@host`). Service jobs use the literal string `service` as the schedule column.
|
||||||
- Flags (`s` = service, `i` = desktop, `o`/`o=N` = include output, `e=addr` = email, `n=name` = job name) are persisted as `# SYSTAB_FLAGS=...` comments in service files. Names are additionally stored as `# SYSTAB_NAME=...` comments. Service jobs are additionally tagged with `# SYSTAB_TYPE=service`. `ExecStopPost=` lines use `$SERVICE_RESULT`/`$EXIT_STATUS` for status-aware messages. Unit file `printf` format strings must use `%%s` (not `%s`) since systemd expands `%s` as a specifier before the shell runs.
|
- Flags (`s` = service, `i` = desktop, `o`/`o=N` = include output, `e=addr` = email, `n=name` = job name) are persisted as `# SYSTAB_FLAGS=...` comments in service files. Names are additionally stored as `# SYSTAB_NAME=...` comments. Service jobs are additionally tagged with `# SYSTAB_TYPE=service`. `ExecStopPost=` lines use `$SERVICE_RESULT`/`$EXIT_STATUS` for status-aware messages. Unit file `printf` format strings must use `%%s` (not `%s`) since systemd expands `%s` as a specifier before the shell runs.
|
||||||
- Journal logs are queried with `USER_UNIT` OR `SYSLOG_IDENTIFIER` to capture both systemd messages and command output.
|
- Journal logs are queried with `USER_UNIT` OR `SYSLOG_IDENTIFIER` to capture both systemd messages and command output.
|
||||||
- `show_status` uses `systemctl show -p ActiveState,SubState` (not `is-active`) for service jobs to correctly report states like `activating` that `is-active` would misreport as inactive.
|
- `showStatus` uses `systemctl show -p ActiveState,SubState` (not `is-active`) for service jobs to correctly report states like `activating` that `is-active` would misreport as inactive.
|
||||||
- `daemon-reload` must run before `enable`/`start` for new service units; it is called inside `_write_unit_files` for the service path.
|
- `daemon-reload` must run before `enable`/`start` for new service units; it is called inside `_writeUnitFiles` for the service path.
|
||||||
|
|
|
||||||
238
systab
238
systab
|
|
@ -107,7 +107,7 @@ warn() {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Extract the command string from a service file's ExecStart line
|
# Extract the command string from a service file's ExecStart line
|
||||||
get_job_command() {
|
getJobCommand() {
|
||||||
sed -n "s/^ExecStart=.*-c '\\(.*\\)'$/\\1/p" "$1"
|
sed -n "s/^ExecStart=.*-c '\\(.*\\)'$/\\1/p" "$1"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -119,7 +119,7 @@ trim() {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Parse time specification into systemd OnCalendar format
|
# Parse time specification into systemd OnCalendar format
|
||||||
parse_time() {
|
parseTime() {
|
||||||
local time_spec="$1"
|
local time_spec="$1"
|
||||||
|
|
||||||
# Common systemd calendar formats (pass through)
|
# Common systemd calendar formats (pass through)
|
||||||
|
|
@ -184,7 +184,7 @@ 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"
|
# 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
|
# Everything else (calendar specs with wildcards, keywords, intervals) is recurring
|
||||||
is_recurring() {
|
isRecurring() {
|
||||||
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 ;;
|
||||||
|
|
@ -194,18 +194,18 @@ is_recurring() {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Generate a random 6-char hex ID
|
# Generate a random 6-char hex ID
|
||||||
generate_id() {
|
generateId() {
|
||||||
od -An -tx1 -N3 /dev/urandom | tr -d ' \n'
|
od -An -tx1 -N3 /dev/urandom | tr -d ' \n'
|
||||||
}
|
}
|
||||||
|
|
||||||
# Extract short ID from job name (systab_<id> → <id>)
|
# Extract short ID from job name (systab_<id> → <id>)
|
||||||
job_id() {
|
jobId() {
|
||||||
echo "${1#"${SCRIPT_NAME}"_}"
|
echo "${1#"${SCRIPT_NAME}"_}"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if a job's timer (or service) is enabled
|
# Check if a job's timer (or service) is enabled
|
||||||
is_job_enabled() {
|
isJobEnabled() {
|
||||||
if is_job_service "$1"; then
|
if isJobService "$1"; then
|
||||||
systemctl --user is-enabled "${1}.service" &>/dev/null
|
systemctl --user is-enabled "${1}.service" &>/dev/null
|
||||||
else
|
else
|
||||||
systemctl --user is-enabled "${1}.timer" &>/dev/null
|
systemctl --user is-enabled "${1}.timer" &>/dev/null
|
||||||
|
|
@ -213,8 +213,8 @@ is_job_enabled() {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Disable a job (stop + disable timer or service)
|
# Disable a job (stop + disable timer or service)
|
||||||
disable_job() {
|
disableJob() {
|
||||||
if is_job_service "$1"; then
|
if isJobService "$1"; then
|
||||||
systemctl --user stop "${1}.service" 2>/dev/null || true
|
systemctl --user stop "${1}.service" 2>/dev/null || true
|
||||||
systemctl --user disable "${1}.service" 2>/dev/null || true
|
systemctl --user disable "${1}.service" 2>/dev/null || true
|
||||||
else
|
else
|
||||||
|
|
@ -224,8 +224,8 @@ disable_job() {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Enable a job (enable + start timer or service)
|
# Enable a job (enable + start timer or service)
|
||||||
enable_job() {
|
enableJob() {
|
||||||
if is_job_service "$1"; then
|
if isJobService "$1"; then
|
||||||
systemctl --user enable "${1}.service" 2>/dev/null || true
|
systemctl --user enable "${1}.service" 2>/dev/null || true
|
||||||
systemctl --user start "${1}.service" 2>/dev/null || true
|
systemctl --user start "${1}.service" 2>/dev/null || true
|
||||||
else
|
else
|
||||||
|
|
@ -236,7 +236,7 @@ enable_job() {
|
||||||
|
|
||||||
# Resolve a job identifier (hex ID or name) to a 6-char hex ID
|
# Resolve a job identifier (hex ID or name) to a 6-char hex ID
|
||||||
# Sets _resolved_id; errors if not found
|
# Sets _resolved_id; errors if not found
|
||||||
resolve_job_id() {
|
resolveJobId() {
|
||||||
local input="$1"
|
local input="$1"
|
||||||
# Try as hex ID first
|
# Try as hex ID first
|
||||||
if [[ "$input" =~ ^[0-9a-f]{6}$ ]]; then
|
if [[ "$input" =~ ^[0-9a-f]{6}$ ]]; then
|
||||||
|
|
@ -254,7 +254,7 @@ resolve_job_id() {
|
||||||
if grep -q "^# SYSTAB_NAME=${input}$" "$file" 2>/dev/null; then
|
if grep -q "^# SYSTAB_NAME=${input}$" "$file" 2>/dev/null; then
|
||||||
local base
|
local base
|
||||||
base=$(basename "$file" .service)
|
base=$(basename "$file" .service)
|
||||||
_resolved_id=$(job_id "$base")
|
_resolved_id=$(jobId "$base")
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
@ -262,18 +262,18 @@ resolve_job_id() {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get the human-readable name from a service file (empty string if none)
|
# Get the human-readable name from a service file (empty string if none)
|
||||||
get_job_name() {
|
getJobName() {
|
||||||
local service_file="$1"
|
local service_file="$1"
|
||||||
sed -n 's/^# SYSTAB_NAME=//p' "$service_file" 2>/dev/null || true
|
sed -n 's/^# SYSTAB_NAME=//p' "$service_file" 2>/dev/null || true
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if a job is a service-only job (not timer-backed)
|
# Check if a job is a service-only job (not timer-backed)
|
||||||
is_job_service() {
|
isJobService() {
|
||||||
grep -q "^# SYSTAB_TYPE=service$" "$SYSTEMD_USER_DIR/${1}.service" 2>/dev/null
|
grep -q "^# SYSTAB_TYPE=service$" "$SYSTEMD_USER_DIR/${1}.service" 2>/dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get managed service-only units (no timer)
|
# Get managed service-only units (no timer)
|
||||||
get_managed_service_jobs() {
|
getManagedServiceJobs() {
|
||||||
[[ -d "$SYSTEMD_USER_DIR" ]] || return
|
[[ -d "$SYSTEMD_USER_DIR" ]] || return
|
||||||
local file
|
local file
|
||||||
for file in "${SYSTEMD_USER_DIR}/${SCRIPT_NAME}"_*.service; do
|
for file in "${SYSTEMD_USER_DIR}/${SCRIPT_NAME}"_*.service; do
|
||||||
|
|
@ -284,7 +284,7 @@ get_managed_service_jobs() {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Format a job identifier for display: "id (name)" or just "id"
|
# Format a job identifier for display: "id (name)" or just "id"
|
||||||
format_job_id() {
|
formatJobId() {
|
||||||
local id="$1" name="$2"
|
local id="$1" name="$2"
|
||||||
if [[ -n "$name" ]]; then
|
if [[ -n "$name" ]]; then
|
||||||
echo "$id ($name)"
|
echo "$id ($name)"
|
||||||
|
|
@ -294,8 +294,8 @@ format_job_id() {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Validate a short ID refers to a managed job, sets _job_name
|
# Validate a short ID refers to a managed job, sets _job_name
|
||||||
validate_job_id() {
|
validateJobId() {
|
||||||
resolve_job_id "$1"
|
resolveJobId "$1"
|
||||||
local id="$_resolved_id"
|
local id="$_resolved_id"
|
||||||
_job_name="${SCRIPT_NAME}_${id}"
|
_job_name="${SCRIPT_NAME}_${id}"
|
||||||
local service_file="$SYSTEMD_USER_DIR/${_job_name}.service"
|
local service_file="$SYSTEMD_USER_DIR/${_job_name}.service"
|
||||||
|
|
@ -304,32 +304,32 @@ validate_job_id() {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Toggle a job's enabled state by short ID or name
|
# Toggle a job's enabled state by short ID or name
|
||||||
# Usage: toggle_job_by_id <id|name> <disable|enable>
|
# Usage: toggleJobById <id|name> <disable|enable>
|
||||||
toggle_job_by_id() {
|
toggleJobById() {
|
||||||
local input="$1" action="$2"
|
local input="$1" action="$2"
|
||||||
validate_job_id "$input"
|
validateJobId "$input"
|
||||||
local id="$_resolved_id"
|
local id="$_resolved_id"
|
||||||
local name
|
local name
|
||||||
name=$(get_job_name "$SYSTEMD_USER_DIR/${_job_name}.service")
|
name=$(getJobName "$SYSTEMD_USER_DIR/${_job_name}.service")
|
||||||
local label
|
local label
|
||||||
label=$(format_job_id "$id" "$name")
|
label=$(formatJobId "$id" "$name")
|
||||||
if [[ "$action" == "disable" ]]; then
|
if [[ "$action" == "disable" ]]; then
|
||||||
if ! is_job_enabled "$_job_name"; then
|
if ! isJobEnabled "$_job_name"; then
|
||||||
echo "Already disabled: $label"; return
|
echo "Already disabled: $label"; return
|
||||||
fi
|
fi
|
||||||
disable_job "$_job_name"
|
disableJob "$_job_name"
|
||||||
echo "Disabled: $label"
|
echo "Disabled: $label"
|
||||||
else
|
else
|
||||||
if is_job_enabled "$_job_name"; then
|
if isJobEnabled "$_job_name"; then
|
||||||
echo "Already enabled: $label"; return
|
echo "Already enabled: $label"; return
|
||||||
fi
|
fi
|
||||||
enable_job "$_job_name"
|
enableJob "$_job_name"
|
||||||
echo "Enabled: $label"
|
echo "Enabled: $label"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get all managed unit files of a given type (service or timer)
|
# Get all managed unit files of a given type (service or timer)
|
||||||
get_managed_units() {
|
getManagedUnits() {
|
||||||
local ext="$1"
|
local ext="$1"
|
||||||
[[ -d "$SYSTEMD_USER_DIR" ]] || return
|
[[ -d "$SYSTEMD_USER_DIR" ]] || return
|
||||||
local file
|
local file
|
||||||
|
|
@ -340,7 +340,7 @@ get_managed_units() {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Build flags string from opt_service, opt_notify, opt_email, and opt_name
|
# Build flags string from opt_service, opt_notify, opt_email, and opt_name
|
||||||
build_flags_string() {
|
buildFlagsString() {
|
||||||
local flags=""
|
local flags=""
|
||||||
if $opt_service; then
|
if $opt_service; then
|
||||||
flags="s"
|
flags="s"
|
||||||
|
|
@ -365,7 +365,7 @@ build_flags_string() {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Parse flags string into _notify_flag (bool), _email_addr (string), _name (string)
|
# Parse flags string into _notify_flag (bool), _email_addr (string), _name (string)
|
||||||
parse_flags() {
|
parseFlags() {
|
||||||
local flags="$1"
|
local flags="$1"
|
||||||
_notify_flag=false
|
_notify_flag=false
|
||||||
_email_addr=""
|
_email_addr=""
|
||||||
|
|
@ -386,8 +386,8 @@ parse_flags() {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Write ExecStopPost notification lines to a service file
|
# Write ExecStopPost notification lines to a service file
|
||||||
# Usage: write_notify_lines <short_id> <notify_flag> <email_addr> <file> [output_lines] [job_name]
|
# Usage: writeNotifyLines <short_id> <notify_flag> <email_addr> <file> [output_lines] [job_name]
|
||||||
write_notify_lines() {
|
writeNotifyLines() {
|
||||||
local short_id="$1" notify="$2" email="$3" file="$4"
|
local short_id="$1" notify="$2" email="$3" file="$4"
|
||||||
local output_lines="${5-}" job_name="${6-}"
|
local output_lines="${5-}" job_name="${6-}"
|
||||||
local out_cmd=""
|
local out_cmd=""
|
||||||
|
|
@ -427,15 +427,15 @@ write_notify_lines() {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Write service + timer unit files (or service-only), enable and start
|
# Write service + timer unit files (or service-only), enable and start
|
||||||
# Usage: _write_unit_files <command> <schedule> [flags] [job_type]
|
# Usage: _writeUnitFiles <command> <schedule> [flags] [job_type]
|
||||||
# job_type: "timer" (default) or "service"
|
# job_type: "timer" (default) or "service"
|
||||||
# Sets _created_id to the short ID of the new job
|
# Sets _created_id to the short ID of the new job
|
||||||
_write_unit_files() {
|
_writeUnitFiles() {
|
||||||
local command_to_run="$1" schedule="$2" flags="${3-}" job_type="${4-timer}"
|
local command_to_run="$1" schedule="$2" flags="${3-}" job_type="${4-timer}"
|
||||||
local job_name short_id
|
local job_name short_id
|
||||||
|
|
||||||
job_name="${SCRIPT_NAME}_$(generate_id)"
|
job_name="${SCRIPT_NAME}_$(generateId)"
|
||||||
short_id=$(job_id "$job_name")
|
short_id=$(jobId "$job_name")
|
||||||
|
|
||||||
mkdir -p "$SYSTEMD_USER_DIR"
|
mkdir -p "$SYSTEMD_USER_DIR"
|
||||||
|
|
||||||
|
|
@ -459,7 +459,7 @@ Restart=on-failure
|
||||||
EOF
|
EOF
|
||||||
if [[ -n "$flags" ]]; then
|
if [[ -n "$flags" ]]; then
|
||||||
echo "# SYSTAB_FLAGS=$flags" >> "$service_file"
|
echo "# SYSTAB_FLAGS=$flags" >> "$service_file"
|
||||||
parse_flags "$flags"
|
parseFlags "$flags"
|
||||||
[[ -n "$_name" ]] && echo "# SYSTAB_NAME=$_name" >> "$service_file"
|
[[ -n "$_name" ]] && echo "# SYSTAB_NAME=$_name" >> "$service_file"
|
||||||
fi
|
fi
|
||||||
cat >> "$service_file" <<EOF
|
cat >> "$service_file" <<EOF
|
||||||
|
|
@ -487,9 +487,9 @@ EOF
|
||||||
|
|
||||||
if [[ -n "$flags" ]]; then
|
if [[ -n "$flags" ]]; then
|
||||||
echo "# SYSTAB_FLAGS=$flags" >> "$service_file"
|
echo "# SYSTAB_FLAGS=$flags" >> "$service_file"
|
||||||
parse_flags "$flags"
|
parseFlags "$flags"
|
||||||
[[ -n "$_name" ]] && echo "# SYSTAB_NAME=$_name" >> "$service_file"
|
[[ -n "$_name" ]] && echo "# SYSTAB_NAME=$_name" >> "$service_file"
|
||||||
write_notify_lines "$short_id" "$_notify_flag" "$_email_addr" "$service_file" "$_output_lines" "$job_name"
|
writeNotifyLines "$short_id" "$_notify_flag" "$_email_addr" "$service_file" "$_output_lines" "$job_name"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Timer file
|
# Timer file
|
||||||
|
|
@ -503,7 +503,7 @@ Description=Timer for $SCRIPT_NAME job: $job_name
|
||||||
OnCalendar=$schedule
|
OnCalendar=$schedule
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
if ! is_recurring "$schedule"; then
|
if ! isRecurring "$schedule"; then
|
||||||
cat >> "$timer_file" <<'ONETIME'
|
cat >> "$timer_file" <<'ONETIME'
|
||||||
Persistent=false
|
Persistent=false
|
||||||
RemainAfterElapse=no
|
RemainAfterElapse=no
|
||||||
|
|
@ -524,7 +524,7 @@ EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check that a job name is unique across all managed jobs
|
# Check that a job name is unique across all managed jobs
|
||||||
check_name_unique() {
|
checkNameUnique() {
|
||||||
local name="$1"
|
local name="$1"
|
||||||
[[ -d "$SYSTEMD_USER_DIR" ]] || return
|
[[ -d "$SYSTEMD_USER_DIR" ]] || return
|
||||||
local file
|
local file
|
||||||
|
|
@ -537,7 +537,7 @@ check_name_unique() {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Create a job from CLI options
|
# Create a job from CLI options
|
||||||
create_job() {
|
createJob() {
|
||||||
local command_to_run
|
local command_to_run
|
||||||
|
|
||||||
if [[ -n "$opt_command" ]]; then
|
if [[ -n "$opt_command" ]]; then
|
||||||
|
|
@ -556,14 +556,14 @@ create_job() {
|
||||||
|
|
||||||
# Validate name uniqueness
|
# Validate name uniqueness
|
||||||
if [[ -n "$opt_name" ]]; then
|
if [[ -n "$opt_name" ]]; then
|
||||||
check_name_unique "$opt_name"
|
checkNameUnique "$opt_name"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local label job_name
|
local label job_name
|
||||||
if $opt_service; then
|
if $opt_service; then
|
||||||
_write_unit_files "$command_to_run" "service" "$(build_flags_string)" service
|
_writeUnitFiles "$command_to_run" "service" "$(buildFlagsString)" service
|
||||||
job_name="${SCRIPT_NAME}_${_created_id}"
|
job_name="${SCRIPT_NAME}_${_created_id}"
|
||||||
label=$(format_job_id "$_created_id" "$opt_name")
|
label=$(formatJobId "$_created_id" "$opt_name")
|
||||||
echo "Service created: $label"
|
echo "Service created: $label"
|
||||||
if systemctl --user is-active "${job_name}.service" &>/dev/null; then
|
if systemctl --user is-active "${job_name}.service" &>/dev/null; then
|
||||||
echo "Status: Active (running)"
|
echo "Status: Active (running)"
|
||||||
|
|
@ -572,20 +572,20 @@ create_job() {
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
local time_spec
|
local time_spec
|
||||||
time_spec=$(parse_time "$opt_time")
|
time_spec=$(parseTime "$opt_time")
|
||||||
_write_unit_files "$command_to_run" "$time_spec" "$(build_flags_string)"
|
_writeUnitFiles "$command_to_run" "$time_spec" "$(buildFlagsString)"
|
||||||
systemctl --user daemon-reload
|
systemctl --user daemon-reload
|
||||||
job_name="${SCRIPT_NAME}_${_created_id}"
|
job_name="${SCRIPT_NAME}_${_created_id}"
|
||||||
label=$(format_job_id "$_created_id" "$opt_name")
|
label=$(formatJobId "$_created_id" "$opt_name")
|
||||||
echo "Job created: $label"
|
echo "Job created: $label"
|
||||||
echo "Next run: $(systemctl --user show "$job_name.timer" -p NextElapseUSecRealtime --value 2>/dev/null)"
|
echo "Next run: $(systemctl --user show "$job_name.timer" -p NextElapseUSecRealtime --value 2>/dev/null)"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Remove a managed job (stop, disable, delete unit files)
|
# Remove a managed job (stop, disable, delete unit files)
|
||||||
remove_job() {
|
removeJob() {
|
||||||
local job_name="$1"
|
local job_name="$1"
|
||||||
if is_job_service "$job_name"; then
|
if isJobService "$job_name"; then
|
||||||
systemctl --user stop "${job_name}.service" 2>/dev/null || true
|
systemctl --user stop "${job_name}.service" 2>/dev/null || true
|
||||||
systemctl --user disable "${job_name}.service" 2>/dev/null || true
|
systemctl --user disable "${job_name}.service" 2>/dev/null || true
|
||||||
rm -f "$SYSTEMD_USER_DIR/${job_name}.service"
|
rm -f "$SYSTEMD_USER_DIR/${job_name}.service"
|
||||||
|
|
@ -597,20 +597,20 @@ remove_job() {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Create a job from edit mode (schedule + command + flags, prints short ID)
|
# Create a job from edit mode (schedule + command + flags, prints short ID)
|
||||||
create_job_from_edit() {
|
createJobFromEdit() {
|
||||||
local sched="$1" cmd="$2" flags="${3-}"
|
local sched="$1" cmd="$2" flags="${3-}"
|
||||||
if [[ "$sched" == "service" ]]; then
|
if [[ "$sched" == "service" ]]; then
|
||||||
_write_unit_files "$cmd" "service" "$flags" service
|
_writeUnitFiles "$cmd" "service" "$flags" service
|
||||||
else
|
else
|
||||||
local schedule
|
local schedule
|
||||||
schedule=$(parse_time "$sched")
|
schedule=$(parseTime "$sched")
|
||||||
_write_unit_files "$cmd" "$schedule" "$flags"
|
_writeUnitFiles "$cmd" "$schedule" "$flags"
|
||||||
fi
|
fi
|
||||||
echo "$_created_id"
|
echo "$_created_id"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Edit jobs in crontab-like format
|
# Edit jobs in crontab-like format
|
||||||
edit_jobs() {
|
editJobs() {
|
||||||
local temp_file orig_file
|
local temp_file orig_file
|
||||||
temp_file="${TMPDIR:-/dev/shm}/systab_edit_$$"
|
temp_file="${TMPDIR:-/dev/shm}/systab_edit_$$"
|
||||||
orig_file="${TMPDIR:-/dev/shm}/systab_orig_$$"
|
orig_file="${TMPDIR:-/dev/shm}/systab_orig_$$"
|
||||||
|
|
@ -655,22 +655,22 @@ HEADER
|
||||||
|
|
||||||
if [[ -f "$timer_file" && -f "$service_file" ]]; then
|
if [[ -f "$timer_file" && -f "$service_file" ]]; then
|
||||||
local id schedule command flags id_field
|
local id schedule command flags id_field
|
||||||
id=$(job_id "$job")
|
id=$(jobId "$job")
|
||||||
schedule=$(grep "^OnCalendar=" "$timer_file" | cut -d= -f2-)
|
schedule=$(grep "^OnCalendar=" "$timer_file" | cut -d= -f2-)
|
||||||
command=$(get_job_command "$service_file")
|
command=$(getJobCommand "$service_file")
|
||||||
flags=$(grep "^# SYSTAB_FLAGS=" "$service_file" 2>/dev/null | sed 's/^# SYSTAB_FLAGS=//' || true)
|
flags=$(grep "^# SYSTAB_FLAGS=" "$service_file" 2>/dev/null | sed 's/^# SYSTAB_FLAGS=//' || true)
|
||||||
if [[ -n "$flags" ]]; then
|
if [[ -n "$flags" ]]; then
|
||||||
id_field="$id:$flags"
|
id_field="$id:$flags"
|
||||||
else
|
else
|
||||||
id_field="$id"
|
id_field="$id"
|
||||||
fi
|
fi
|
||||||
if is_job_enabled "$job"; then
|
if isJobEnabled "$job"; then
|
||||||
printf '%s | %s | %s\n' "$id_field" "$schedule" "$command"
|
printf '%s | %s | %s\n' "$id_field" "$schedule" "$command"
|
||||||
else
|
else
|
||||||
printf '# %s | %s | %s\n' "$id_field" "$schedule" "$command"
|
printf '# %s | %s | %s\n' "$id_field" "$schedule" "$command"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done < <(get_managed_units timer)
|
done < <(getManagedUnits timer)
|
||||||
|
|
||||||
while IFS= read -r job; do
|
while IFS= read -r job; do
|
||||||
[[ -z "$job" ]] && continue
|
[[ -z "$job" ]] && continue
|
||||||
|
|
@ -678,21 +678,21 @@ HEADER
|
||||||
local service_file="$SYSTEMD_USER_DIR/${job}.service"
|
local service_file="$SYSTEMD_USER_DIR/${job}.service"
|
||||||
if [[ -f "$service_file" ]]; then
|
if [[ -f "$service_file" ]]; then
|
||||||
local id command flags id_field
|
local id command flags id_field
|
||||||
id=$(job_id "$job")
|
id=$(jobId "$job")
|
||||||
command=$(get_job_command "$service_file")
|
command=$(getJobCommand "$service_file")
|
||||||
flags=$(grep "^# SYSTAB_FLAGS=" "$service_file" 2>/dev/null | sed 's/^# SYSTAB_FLAGS=//' || true)
|
flags=$(grep "^# SYSTAB_FLAGS=" "$service_file" 2>/dev/null | sed 's/^# SYSTAB_FLAGS=//' || true)
|
||||||
if [[ -n "$flags" ]]; then
|
if [[ -n "$flags" ]]; then
|
||||||
id_field="$id:$flags"
|
id_field="$id:$flags"
|
||||||
else
|
else
|
||||||
id_field="$id:s"
|
id_field="$id:s"
|
||||||
fi
|
fi
|
||||||
if is_job_enabled "$job"; then
|
if isJobEnabled "$job"; then
|
||||||
printf '%s | service | %s\n' "$id_field" "$command"
|
printf '%s | service | %s\n' "$id_field" "$command"
|
||||||
else
|
else
|
||||||
printf '# %s | service | %s\n' "$id_field" "$command"
|
printf '# %s | service | %s\n' "$id_field" "$command"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done < <(get_managed_service_jobs)
|
done < <(getManagedServiceJobs)
|
||||||
} > "$temp_file"
|
} > "$temp_file"
|
||||||
|
|
||||||
# Save original for diffing
|
# Save original for diffing
|
||||||
|
|
@ -700,7 +700,7 @@ HEADER
|
||||||
|
|
||||||
# Validate all time specs in a crontab file, returns 0 if valid
|
# Validate all time specs in a crontab file, returns 0 if valid
|
||||||
# Prints error messages for each bad schedule
|
# Prints error messages for each bad schedule
|
||||||
validate_crontab_schedules() {
|
validateCrontabSchedules() {
|
||||||
local file="$1" errors=0 line
|
local file="$1" errors=0 line
|
||||||
while IFS= read -r line; do
|
while IFS= read -r line; do
|
||||||
[[ -z "$line" ]] && continue
|
[[ -z "$line" ]] && continue
|
||||||
|
|
@ -727,7 +727,7 @@ HEADER
|
||||||
trim "$sched"; sched="$_trimmed"
|
trim "$sched"; sched="$_trimmed"
|
||||||
[[ -z "$sched" ]] && continue
|
[[ -z "$sched" ]] && continue
|
||||||
[[ "$sched" == "service" ]] && continue
|
[[ "$sched" == "service" ]] && continue
|
||||||
if ! (parse_time "$sched") &>/dev/null; then
|
if ! (parseTime "$sched") &>/dev/null; then
|
||||||
echo "Bad schedule: \"$sched\"" >&2
|
echo "Bad schedule: \"$sched\"" >&2
|
||||||
errors=$((errors + 1))
|
errors=$((errors + 1))
|
||||||
fi
|
fi
|
||||||
|
|
@ -749,7 +749,7 @@ HEADER
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Validate schedules before applying
|
# Validate schedules before applying
|
||||||
if validate_crontab_schedules "$temp_file"; then
|
if validateCrontabSchedules "$temp_file"; then
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -770,7 +770,7 @@ HEADER
|
||||||
|
|
||||||
# Parse a crontab line into id, flags, sched, cmd (split on first two | delimiters)
|
# Parse a crontab line into id, flags, sched, cmd (split on first two | delimiters)
|
||||||
# _parsed_id gets the bare ID, _parsed_flags gets any flags after ':'
|
# _parsed_id gets the bare ID, _parsed_flags gets any flags after ':'
|
||||||
parse_crontab_line() {
|
parseCrontabLine() {
|
||||||
local line="$1"
|
local line="$1"
|
||||||
_parsed_id="" _parsed_flags="" _parsed_sched="" _parsed_cmd=""
|
_parsed_id="" _parsed_flags="" _parsed_sched="" _parsed_cmd=""
|
||||||
if [[ "$line" == *"|"* ]]; then
|
if [[ "$line" == *"|"* ]]; then
|
||||||
|
|
@ -794,8 +794,8 @@ HEADER
|
||||||
|
|
||||||
# Parse a file, populating jobs and commented arrays
|
# Parse a file, populating jobs and commented arrays
|
||||||
# Values stored as "flags<TAB>schedule|cmd"
|
# Values stored as "flags<TAB>schedule|cmd"
|
||||||
# Usage: parse_crontab_file <file> <jobs_var> <commented_var> [new_jobs_var]
|
# Usage: parseCrontabFile <file> <jobs_var> <commented_var> [new_jobs_var]
|
||||||
parse_crontab_file() {
|
parseCrontabFile() {
|
||||||
local file="$1" new_ref="${4-}"
|
local file="$1" new_ref="${4-}"
|
||||||
local -n _pcf_jobs="$2" _pcf_commented="$3"
|
local -n _pcf_jobs="$2" _pcf_commented="$3"
|
||||||
local line tab=$'\t'
|
local line tab=$'\t'
|
||||||
|
|
@ -804,14 +804,14 @@ HEADER
|
||||||
# Check for commented job line: "# <id> <sched> <cmd>"
|
# Check for commented job line: "# <id> <sched> <cmd>"
|
||||||
if [[ "$line" =~ ^#[[:space:]]+(.*) ]]; then
|
if [[ "$line" =~ ^#[[:space:]]+(.*) ]]; then
|
||||||
local uncommented="${BASH_REMATCH[1]}"
|
local uncommented="${BASH_REMATCH[1]}"
|
||||||
parse_crontab_line "$uncommented"
|
parseCrontabLine "$uncommented"
|
||||||
# Only treat as disabled job if ID looks like a hex short ID (with optional :flags)
|
# Only treat as disabled job if ID looks like a hex short ID (with optional :flags)
|
||||||
if [[ -n "$_parsed_id" && -n "$_parsed_sched" && "$_parsed_id" =~ ^[0-9a-f]{6}$ ]]; then
|
if [[ -n "$_parsed_id" && -n "$_parsed_sched" && "$_parsed_id" =~ ^[0-9a-f]{6}$ ]]; then
|
||||||
_pcf_commented["$_parsed_id"]="${_parsed_flags}${tab}${_parsed_sched}|${_parsed_cmd}"
|
_pcf_commented["$_parsed_id"]="${_parsed_flags}${tab}${_parsed_sched}|${_parsed_cmd}"
|
||||||
fi
|
fi
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
parse_crontab_line "$line"
|
parseCrontabLine "$line"
|
||||||
[[ -n "$_parsed_id" && -n "$_parsed_sched" ]] || continue
|
[[ -n "$_parsed_id" && -n "$_parsed_sched" ]] || continue
|
||||||
local val="${_parsed_flags}${tab}${_parsed_sched}|${_parsed_cmd}"
|
local val="${_parsed_flags}${tab}${_parsed_sched}|${_parsed_cmd}"
|
||||||
if [[ "$_parsed_id" == "new" && -n "$new_ref" ]]; then
|
if [[ "$_parsed_id" == "new" && -n "$new_ref" ]]; then
|
||||||
|
|
@ -822,8 +822,8 @@ HEADER
|
||||||
done < "$file"
|
done < "$file"
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_crontab_file "$orig_file" orig_jobs orig_commented
|
parseCrontabFile "$orig_file" orig_jobs orig_commented
|
||||||
parse_crontab_file "$temp_file" edited_jobs edited_commented new_jobs
|
parseCrontabFile "$temp_file" edited_jobs edited_commented new_jobs
|
||||||
|
|
||||||
# Collect all known IDs (from both active and commented in original)
|
# Collect all known IDs (from both active and commented in original)
|
||||||
declare -A all_orig_ids
|
declare -A all_orig_ids
|
||||||
|
|
@ -835,7 +835,7 @@ HEADER
|
||||||
# Deletions: IDs in original (active or commented) but absent from edited entirely
|
# Deletions: IDs in original (active or commented) but absent from edited entirely
|
||||||
for id in "${!all_orig_ids[@]}"; do
|
for id in "${!all_orig_ids[@]}"; do
|
||||||
if [[ -z "${edited_jobs[$id]+x}" && -z "${edited_commented[$id]+x}" ]]; then
|
if [[ -z "${edited_jobs[$id]+x}" && -z "${edited_commented[$id]+x}" ]]; then
|
||||||
remove_job "${SCRIPT_NAME}_${id}"
|
removeJob "${SCRIPT_NAME}_${id}"
|
||||||
echo "Deleted: $id"
|
echo "Deleted: $id"
|
||||||
deleted=$((deleted + 1))
|
deleted=$((deleted + 1))
|
||||||
needs_reload=true
|
needs_reload=true
|
||||||
|
|
@ -850,7 +850,7 @@ HEADER
|
||||||
sched="${new_rest%%|*}"
|
sched="${new_rest%%|*}"
|
||||||
cmd="${new_rest#*|}"
|
cmd="${new_rest#*|}"
|
||||||
local result
|
local result
|
||||||
result=$(create_job_from_edit "$sched" "$cmd" "$new_flags")
|
result=$(createJobFromEdit "$sched" "$cmd" "$new_flags")
|
||||||
echo "Created: $result"
|
echo "Created: $result"
|
||||||
created=$((created + 1))
|
created=$((created + 1))
|
||||||
needs_reload=true
|
needs_reload=true
|
||||||
|
|
@ -900,12 +900,12 @@ HEADER
|
||||||
|
|
||||||
# Handle enable/disable transitions
|
# Handle enable/disable transitions
|
||||||
if ! $was_commented && $now_commented; then
|
if ! $was_commented && $now_commented; then
|
||||||
disable_job "$jname"
|
disableJob "$jname"
|
||||||
echo "Disabled: $id"
|
echo "Disabled: $id"
|
||||||
disabled=$((disabled + 1))
|
disabled=$((disabled + 1))
|
||||||
needs_reload=true
|
needs_reload=true
|
||||||
elif $was_commented && ! $now_commented; then
|
elif $was_commented && ! $now_commented; then
|
||||||
enable_job "$jname"
|
enableJob "$jname"
|
||||||
echo "Enabled: $id"
|
echo "Enabled: $id"
|
||||||
enabled=$((enabled + 1))
|
enabled=$((enabled + 1))
|
||||||
needs_reload=true
|
needs_reload=true
|
||||||
|
|
@ -923,11 +923,11 @@ HEADER
|
||||||
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")
|
new_sched=$(parseTime "$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
|
||||||
if is_recurring "$new_sched"; then
|
if isRecurring "$new_sched"; then
|
||||||
sed -i '/^Persistent=false$/d; /^RemainAfterElapse=no$/d' "$timer_file"
|
sed -i '/^Persistent=false$/d; /^RemainAfterElapse=no$/d' "$timer_file"
|
||||||
else
|
else
|
||||||
if ! grep -q "^Persistent=false" "$timer_file"; then
|
if ! grep -q "^Persistent=false" "$timer_file"; then
|
||||||
|
|
@ -947,15 +947,15 @@ HEADER
|
||||||
# Add new flags and notification lines
|
# Add new flags and notification lines
|
||||||
if [[ -n "$new_flags" ]]; then
|
if [[ -n "$new_flags" ]]; then
|
||||||
echo "# SYSTAB_FLAGS=$new_flags" >> "$service_file"
|
echo "# SYSTAB_FLAGS=$new_flags" >> "$service_file"
|
||||||
parse_flags "$new_flags"
|
parseFlags "$new_flags"
|
||||||
[[ -n "$_name" ]] && echo "# SYSTAB_NAME=$_name" >> "$service_file"
|
[[ -n "$_name" ]] && echo "# SYSTAB_NAME=$_name" >> "$service_file"
|
||||||
write_notify_lines "$id" "$_notify_flag" "$_email_addr" "$service_file" "$_output_lines" "$jname"
|
writeNotifyLines "$id" "$_notify_flag" "$_email_addr" "$service_file" "$_output_lines" "$jname"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Only restart if the job is active (not being disabled)
|
# Only restart if the job is active (not being disabled)
|
||||||
if ! $now_commented; then
|
if ! $now_commented; then
|
||||||
if is_job_service "$jname"; then
|
if isJobService "$jname"; then
|
||||||
systemctl --user restart "${jname}.service" 2>/dev/null || true
|
systemctl --user restart "${jname}.service" 2>/dev/null || true
|
||||||
else
|
else
|
||||||
systemctl --user restart "${jname}.timer" 2>/dev/null || true
|
systemctl --user restart "${jname}.timer" 2>/dev/null || true
|
||||||
|
|
@ -976,20 +976,20 @@ HEADER
|
||||||
echo "Summary: $created created, $updated updated, $deleted deleted, $enabled enabled, $disabled disabled"
|
echo "Summary: $created created, $updated updated, $deleted deleted, $enabled enabled, $disabled disabled"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Build a job list from opt_jobid (single job) or get_managed_units (all jobs)
|
# Build a job list from opt_jobid (single job) or getManagedUnits (all jobs)
|
||||||
# Usage: build_job_list <unit_type>
|
# Usage: buildJobList <unit_type>
|
||||||
# Sets _job_list array; returns 1 if empty
|
# Sets _job_list array; returns 1 if empty
|
||||||
build_job_list() {
|
buildJobList() {
|
||||||
_job_list=()
|
_job_list=()
|
||||||
if [[ -n "$opt_jobid" ]]; then
|
if [[ -n "$opt_jobid" ]]; then
|
||||||
resolve_job_id "$opt_jobid"
|
resolveJobId "$opt_jobid"
|
||||||
_job_list+=("${SCRIPT_NAME}_${_resolved_id}")
|
_job_list+=("${SCRIPT_NAME}_${_resolved_id}")
|
||||||
else
|
else
|
||||||
local job
|
local job
|
||||||
while IFS= read -r job; do
|
while IFS= read -r job; do
|
||||||
[[ -z "$job" ]] && continue
|
[[ -z "$job" ]] && continue
|
||||||
_job_list+=("$job")
|
_job_list+=("$job")
|
||||||
done < <(get_managed_units "$1")
|
done < <(getManagedUnits "$1")
|
||||||
fi
|
fi
|
||||||
if [[ ${#_job_list[@]} -eq 0 ]]; then
|
if [[ ${#_job_list[@]} -eq 0 ]]; then
|
||||||
echo "No managed jobs found."
|
echo "No managed jobs found."
|
||||||
|
|
@ -998,18 +998,18 @@ build_job_list() {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Show status of all managed jobs (or a single job if opt_jobid is set)
|
# Show status of all managed jobs (or a single job if opt_jobid is set)
|
||||||
show_status() {
|
showStatus() {
|
||||||
# Build combined list: timer jobs + service-only jobs
|
# Build combined list: timer jobs + service-only jobs
|
||||||
_job_list=()
|
_job_list=()
|
||||||
if [[ -n "$opt_jobid" ]]; then
|
if [[ -n "$opt_jobid" ]]; then
|
||||||
resolve_job_id "$opt_jobid"
|
resolveJobId "$opt_jobid"
|
||||||
_job_list+=("${SCRIPT_NAME}_${_resolved_id}")
|
_job_list+=("${SCRIPT_NAME}_${_resolved_id}")
|
||||||
else
|
else
|
||||||
local job
|
local job
|
||||||
while IFS= read -r job; do
|
while IFS= read -r job; do
|
||||||
[[ -z "$job" ]] && continue
|
[[ -z "$job" ]] && continue
|
||||||
_job_list+=("$job")
|
_job_list+=("$job")
|
||||||
done < <(get_managed_units timer; get_managed_service_jobs)
|
done < <(getManagedUnits timer; getManagedServiceJobs)
|
||||||
fi
|
fi
|
||||||
if [[ ${#_job_list[@]} -eq 0 ]]; then
|
if [[ ${#_job_list[@]} -eq 0 ]]; then
|
||||||
echo "No managed jobs found."
|
echo "No managed jobs found."
|
||||||
|
|
@ -1026,20 +1026,20 @@ show_status() {
|
||||||
local service_file="$SYSTEMD_USER_DIR/${job}.service"
|
local service_file="$SYSTEMD_USER_DIR/${job}.service"
|
||||||
|
|
||||||
local id name label
|
local id name label
|
||||||
id=$(job_id "$job")
|
id=$(jobId "$job")
|
||||||
name=$(get_job_name "$SYSTEMD_USER_DIR/${job}.service")
|
name=$(getJobName "$SYSTEMD_USER_DIR/${job}.service")
|
||||||
label=$(format_job_id "$id" "$name")
|
label=$(formatJobId "$id" "$name")
|
||||||
echo "Job: $label"
|
echo "Job: $label"
|
||||||
|
|
||||||
if is_job_service "$job"; then
|
if isJobService "$job"; then
|
||||||
# Service-only job display
|
# Service-only job display
|
||||||
echo " Type: Service"
|
echo " Type: Service"
|
||||||
if [[ -f "$service_file" ]]; then
|
if [[ -f "$service_file" ]]; then
|
||||||
local command
|
local command
|
||||||
command=$(get_job_command "$service_file")
|
command=$(getJobCommand "$service_file")
|
||||||
echo " Command: $command"
|
echo " Command: $command"
|
||||||
fi
|
fi
|
||||||
if ! is_job_enabled "$job"; then
|
if ! isJobEnabled "$job"; then
|
||||||
echo " Service: Disabled"
|
echo " Service: Disabled"
|
||||||
else
|
else
|
||||||
local svc_active svc_sub
|
local svc_active svc_sub
|
||||||
|
|
@ -1062,11 +1062,11 @@ show_status() {
|
||||||
|
|
||||||
if [[ -f "$service_file" ]]; then
|
if [[ -f "$service_file" ]]; then
|
||||||
local command
|
local command
|
||||||
command=$(get_job_command "$service_file")
|
command=$(getJobCommand "$service_file")
|
||||||
echo " Command: $command"
|
echo " Command: $command"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! is_job_enabled "$job"; then
|
if ! isJobEnabled "$job"; then
|
||||||
echo " Timer: Disabled"
|
echo " Timer: Disabled"
|
||||||
elif systemctl --user is-active "${job}.timer" &>/dev/null; then
|
elif systemctl --user is-active "${job}.timer" &>/dev/null; then
|
||||||
echo " Timer: Active"
|
echo " Timer: Active"
|
||||||
|
|
@ -1104,8 +1104,8 @@ show_status() {
|
||||||
}
|
}
|
||||||
|
|
||||||
# List logs (or logs for a single job if opt_jobid is set)
|
# List logs (or logs for a single job if opt_jobid is set)
|
||||||
list_logs() {
|
listLogs() {
|
||||||
build_job_list service || return
|
buildJobList service || return
|
||||||
|
|
||||||
echo "Found ${#_job_list[@]} managed jobs"
|
echo "Found ${#_job_list[@]} managed jobs"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
@ -1115,12 +1115,12 @@ list_logs() {
|
||||||
|
|
||||||
for job in "${_job_list[@]}"; do
|
for job in "${_job_list[@]}"; do
|
||||||
local id name label
|
local id name label
|
||||||
id=$(job_id "$job")
|
id=$(jobId "$job")
|
||||||
name=$(get_job_name "$SYSTEMD_USER_DIR/${job}.service")
|
name=$(getJobName "$SYSTEMD_USER_DIR/${job}.service")
|
||||||
label=$(format_job_id "$id" "$name")
|
label=$(formatJobId "$id" "$name")
|
||||||
echo "=== Logs for $label ==="
|
echo "=== Logs for $label ==="
|
||||||
|
|
||||||
if is_job_service "$job"; then
|
if isJobService "$job"; then
|
||||||
local svc_active svc_sub
|
local svc_active svc_sub
|
||||||
svc_active=$(systemctl --user show "${job}.service" -p ActiveState --value 2>/dev/null)
|
svc_active=$(systemctl --user show "${job}.service" -p ActiveState --value 2>/dev/null)
|
||||||
svc_sub=$(systemctl --user show "${job}.service" -p SubState --value 2>/dev/null)
|
svc_sub=$(systemctl --user show "${job}.service" -p SubState --value 2>/dev/null)
|
||||||
|
|
@ -1141,7 +1141,7 @@ list_logs() {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Clean up completed jobs
|
# Clean up completed jobs
|
||||||
clean_jobs() {
|
cleanJobs() {
|
||||||
local job cleaned=0
|
local job cleaned=0
|
||||||
|
|
||||||
while IFS= read -r job; do
|
while IFS= read -r job; do
|
||||||
|
|
@ -1159,18 +1159,18 @@ clean_jobs() {
|
||||||
if [[ "$timer_state" == "elapsed" || "$timer_state" == "dead" ]]; then
|
if [[ "$timer_state" == "elapsed" || "$timer_state" == "dead" ]]; then
|
||||||
# Get the command for display
|
# Get the command for display
|
||||||
local command
|
local command
|
||||||
command=$(get_job_command "$SYSTEMD_USER_DIR/${job}.service" 2>/dev/null || echo "unknown")
|
command=$(getJobCommand "$SYSTEMD_USER_DIR/${job}.service" 2>/dev/null || echo "unknown")
|
||||||
|
|
||||||
read -p "Remove completed job '$job' (command: $command)? [y/N] " -n 1 -r </dev/tty
|
read -p "Remove completed job '$job' (command: $command)? [y/N] " -n 1 -r </dev/tty
|
||||||
echo
|
echo
|
||||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||||
remove_job "$job"
|
removeJob "$job"
|
||||||
echo "Removed: $job"
|
echo "Removed: $job"
|
||||||
cleaned=$((cleaned + 1))
|
cleaned=$((cleaned + 1))
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done < <(get_managed_units timer)
|
done < <(getManagedUnits timer)
|
||||||
|
|
||||||
if [[ $cleaned -gt 0 ]]; then
|
if [[ $cleaned -gt 0 ]]; then
|
||||||
systemctl --user daemon-reload
|
systemctl --user daemon-reload
|
||||||
|
|
@ -1181,7 +1181,7 @@ clean_jobs() {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Parse command-line options
|
# Parse command-line options
|
||||||
parse_options() {
|
parseOptions() {
|
||||||
while getopts "t:sc:f:n:im:oD:E:eLSCh" opt; do
|
while getopts "t:sc:f:n:im:oD:E:eLSCh" opt; do
|
||||||
case $opt in
|
case $opt in
|
||||||
t) opt_time="$OPTARG" ;;
|
t) opt_time="$OPTARG" ;;
|
||||||
|
|
@ -1218,7 +1218,7 @@ parse_options() {
|
||||||
if ($opt_list || $opt_status) && [[ $OPTIND -le $# ]]; then
|
if ($opt_list || $opt_status) && [[ $OPTIND -le $# ]]; then
|
||||||
local trailing="${!OPTIND}"
|
local trailing="${!OPTIND}"
|
||||||
# Try to resolve as job ID or name (run in subshell to catch error exit)
|
# Try to resolve as job ID or name (run in subshell to catch error exit)
|
||||||
if (resolve_job_id "$trailing") 2>/dev/null; then
|
if (resolveJobId "$trailing") 2>/dev/null; then
|
||||||
opt_jobid="$trailing"
|
opt_jobid="$trailing"
|
||||||
# For -L, check for a second trailing arg as text filter
|
# For -L, check for a second trailing arg as text filter
|
||||||
if $opt_list; then
|
if $opt_list; then
|
||||||
|
|
@ -1278,23 +1278,23 @@ parse_options() {
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
[[ $# -eq 0 ]] && usage 0
|
[[ $# -eq 0 ]] && usage 0
|
||||||
parse_options "$@"
|
parseOptions "$@"
|
||||||
|
|
||||||
# Determine mode based on options
|
# Determine mode based on options
|
||||||
if [[ -n "$opt_disable" ]]; then
|
if [[ -n "$opt_disable" ]]; then
|
||||||
toggle_job_by_id "$opt_disable" disable
|
toggleJobById "$opt_disable" disable
|
||||||
elif [[ -n "$opt_enable" ]]; then
|
elif [[ -n "$opt_enable" ]]; then
|
||||||
toggle_job_by_id "$opt_enable" enable
|
toggleJobById "$opt_enable" enable
|
||||||
elif $opt_edit; then
|
elif $opt_edit; then
|
||||||
edit_jobs
|
editJobs
|
||||||
elif $opt_list; then
|
elif $opt_list; then
|
||||||
list_logs
|
listLogs
|
||||||
elif $opt_status; then
|
elif $opt_status; then
|
||||||
show_status
|
showStatus
|
||||||
elif $opt_clean; then
|
elif $opt_clean; then
|
||||||
clean_jobs
|
cleanJobs
|
||||||
else
|
else
|
||||||
create_job
|
createJob
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
20
test.sh
20
test.sh
|
|
@ -245,23 +245,23 @@ fi
|
||||||
echo ""
|
echo ""
|
||||||
echo "${BOLD}--- Time format parsing ---${RESET}"
|
echo "${BOLD}--- Time format parsing ---${RESET}"
|
||||||
|
|
||||||
# Source parse_time from systab (we need the function)
|
# Source parseTime from systab (we need the function)
|
||||||
# We can call it by running systab in a subshell that only defines the function
|
# We can call it by running systab in a subshell that only defines the function
|
||||||
parse_time_test() {
|
parse_time_test() {
|
||||||
local input="$1" expected="$2"
|
local input="$1" expected="$2"
|
||||||
# Run parse_time via bash sourcing
|
# Run parseTime via bash sourcing
|
||||||
local result
|
local result
|
||||||
if result=$(bash -c '
|
if result=$(bash -c '
|
||||||
source <(sed -n "/^parse_time()/,/^}/p" ./systab; sed -n "/^error()/,/^}/p" ./systab; sed -n "/^is_recurring()/,/^}/p" ./systab)
|
source <(sed -n "/^parseTime()/,/^}/p" ./systab; sed -n "/^error()/,/^}/p" ./systab; sed -n "/^isRecurring()/,/^}/p" ./systab)
|
||||||
parse_time "$1"
|
parseTime "$1"
|
||||||
' _ "$input" 2>&1); then
|
' _ "$input" 2>&1); then
|
||||||
if [[ "$result" == "$expected" ]]; then
|
if [[ "$result" == "$expected" ]]; then
|
||||||
pass "parse_time '$input' -> '$expected'"
|
pass "parseTime '$input' -> '$expected'"
|
||||||
else
|
else
|
||||||
fail "parse_time '$input'" "got '$result', expected '$expected'"
|
fail "parseTime '$input'" "got '$result', expected '$expected'"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
fail "parse_time '$input'" "failed: $result"
|
fail "parseTime '$input'" "failed: $result"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -277,9 +277,9 @@ parse_time_test "weekly" "weekly"
|
||||||
parse_time_test "monthly" "monthly"
|
parse_time_test "monthly" "monthly"
|
||||||
|
|
||||||
# "in 5 minutes" produces an absolute timestamp — just check it doesn't fail
|
# "in 5 minutes" produces an absolute timestamp — just check it doesn't fail
|
||||||
assert_success "parse_time 'in 5 minutes' succeeds" bash -c '
|
assert_success "parseTime 'in 5 minutes' succeeds" bash -c '
|
||||||
source <(sed -n "/^parse_time()/,/^}/p" ./systab; sed -n "/^error()/,/^}/p" ./systab)
|
source <(sed -n "/^parseTime()/,/^}/p" ./systab; sed -n "/^error()/,/^}/p" ./systab)
|
||||||
parse_time "in 5 minutes"
|
parseTime "in 5 minutes"
|
||||||
'
|
'
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue