187 lines
6.4 KiB
Bash
187 lines
6.4 KiB
Bash
#!/usr/bin/env bash
|
|
# Collects cron jobs and systemd timers on this host and POSTs them to the
|
|
# Schedule Task Manager API. Intended to run as root via a periodic systemd
|
|
# timer (see install.sh) but can be run manually for testing:
|
|
#
|
|
# API_URL=https://schedule.example.lan API_TOKEN=stm_xxx ./report-tasks.sh --dry-run
|
|
#
|
|
set -euo pipefail
|
|
|
|
ENV_FILE="${ENV_FILE:-/etc/schedule-task-manager-agent.env}"
|
|
if [[ -f "$ENV_FILE" ]]; then
|
|
# shellcheck disable=SC1090
|
|
source "$ENV_FILE"
|
|
fi
|
|
|
|
API_URL="${API_URL:-}"
|
|
API_TOKEN="${API_TOKEN:-}"
|
|
DRY_RUN=0
|
|
[[ "${1:-}" == "--dry-run" ]] && DRY_RUN=1
|
|
|
|
if [[ -z "$API_URL" || -z "$API_TOKEN" ]]; then
|
|
echo "API_URL and API_TOKEN must be set (env vars or $ENV_FILE)" >&2
|
|
exit 1
|
|
fi
|
|
|
|
for bin in curl jq; do
|
|
if ! command -v "$bin" >/dev/null 2>&1; then
|
|
echo "Required dependency '$bin' is not installed." >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
TASKS_JSON="[]"
|
|
|
|
add_task() {
|
|
local schedule_type="$1" name="$2" command="$3" schedule_expression="$4" source="$5" enabled="$6"
|
|
TASKS_JSON=$(jq -c \
|
|
--arg schedule_type "$schedule_type" \
|
|
--arg name "$name" \
|
|
--arg command "$command" \
|
|
--arg schedule_expression "$schedule_expression" \
|
|
--arg source "$source" \
|
|
--argjson enabled "$enabled" \
|
|
'. + [{
|
|
schedule_type: $schedule_type,
|
|
name: $name,
|
|
command: $command,
|
|
schedule_expression: $schedule_expression,
|
|
source: $source,
|
|
enabled: $enabled
|
|
}]' <<<"$TASKS_JSON")
|
|
}
|
|
|
|
add_task_with_next_run() {
|
|
local schedule_type="$1" name="$2" command="$3" schedule_expression="$4" source="$5" enabled="$6" next_run_at="$7"
|
|
TASKS_JSON=$(jq -c \
|
|
--arg schedule_type "$schedule_type" \
|
|
--arg name "$name" \
|
|
--arg command "$command" \
|
|
--arg schedule_expression "$schedule_expression" \
|
|
--arg source "$source" \
|
|
--argjson enabled "$enabled" \
|
|
--arg next_run_at "$next_run_at" \
|
|
'. + [{
|
|
schedule_type: $schedule_type,
|
|
name: $name,
|
|
command: $command,
|
|
schedule_expression: $schedule_expression,
|
|
source: $source,
|
|
enabled: $enabled,
|
|
next_run_at: $next_run_at
|
|
}]' <<<"$TASKS_JSON")
|
|
}
|
|
|
|
parse_crontab_lines() {
|
|
# Reads 5-field-schedule + command cron lines from stdin.
|
|
# $1 = source label, $2 = "system" (7-field, includes a user column) or "user" (6-field)
|
|
local source="$1" mode="$2"
|
|
while IFS= read -r line; do
|
|
line="${line%%$'\r'}"
|
|
[[ -z "$line" ]] && continue
|
|
[[ "$line" =~ ^[[:space:]]*# ]] && continue
|
|
[[ "$line" =~ ^[[:space:]]*[A-Za-z_][A-Za-z0-9_]*= ]] && continue
|
|
|
|
if [[ "$mode" == "system" ]]; then
|
|
# min hour dom mon dow user command...
|
|
if [[ "$line" =~ ^[[:space:]]*([^[:space:]]+)[[:space:]]+([^[:space:]]+)[[:space:]]+([^[:space:]]+)[[:space:]]+([^[:space:]]+)[[:space:]]+([^[:space:]]+)[[:space:]]+[^[:space:]]+[[:space:]]+(.+)$ ]]; then
|
|
local sched="${BASH_REMATCH[1]} ${BASH_REMATCH[2]} ${BASH_REMATCH[3]} ${BASH_REMATCH[4]} ${BASH_REMATCH[5]}"
|
|
local cmd="${BASH_REMATCH[6]}"
|
|
add_task "cron" "$cmd" "$cmd" "$sched" "$source" true
|
|
fi
|
|
else
|
|
# min hour dom mon dow command...
|
|
if [[ "$line" =~ ^[[:space:]]*([^[:space:]]+)[[:space:]]+([^[:space:]]+)[[:space:]]+([^[:space:]]+)[[:space:]]+([^[:space:]]+)[[:space:]]+([^[:space:]]+)[[:space:]]+(.+)$ ]]; then
|
|
local sched="${BASH_REMATCH[1]} ${BASH_REMATCH[2]} ${BASH_REMATCH[3]} ${BASH_REMATCH[4]} ${BASH_REMATCH[5]}"
|
|
local cmd="${BASH_REMATCH[6]}"
|
|
add_task "cron" "$cmd" "$cmd" "$sched" "$source" true
|
|
fi
|
|
fi
|
|
done
|
|
}
|
|
|
|
collect_cron() {
|
|
[[ -r /etc/crontab ]] && parse_crontab_lines "/etc/crontab" "system" < /etc/crontab
|
|
|
|
if [[ -d /etc/cron.d ]]; then
|
|
for f in /etc/cron.d/*; do
|
|
[[ -f "$f" && -r "$f" ]] || continue
|
|
parse_crontab_lines "$f" "system" < "$f"
|
|
done
|
|
fi
|
|
|
|
if command -v crontab >/dev/null 2>&1 && [[ -r /etc/passwd ]]; then
|
|
while IFS=: read -r user _ uid _ _ _ shell; do
|
|
case "$shell" in
|
|
*/nologin|*/false|"") continue ;;
|
|
esac
|
|
[[ "$uid" -lt 1000 && "$uid" != "0" ]] && continue
|
|
local_crontab=$(crontab -l -u "$user" 2>/dev/null || true)
|
|
[[ -z "$local_crontab" ]] && continue
|
|
parse_crontab_lines "crontab:$user" "user" <<<"$local_crontab"
|
|
done < /etc/passwd
|
|
fi
|
|
}
|
|
|
|
collect_systemd_timers() {
|
|
command -v systemctl >/dev/null 2>&1 || return 0
|
|
|
|
local timers_json
|
|
timers_json=$(systemctl list-timers --all --output=json 2>/dev/null || echo "[]")
|
|
|
|
while IFS= read -r entry; do
|
|
local unit activates next_usec enabled_state schedule_expr next_run_at
|
|
unit=$(jq -r '.unit' <<<"$entry")
|
|
activates=$(jq -r '.activates // ""' <<<"$entry")
|
|
next_usec=$(jq -r '.next // 0' <<<"$entry")
|
|
|
|
enabled_state=$(systemctl is-enabled "$unit" 2>/dev/null || echo "unknown")
|
|
local enabled_bool="false"
|
|
[[ "$enabled_state" == "enabled" || "$enabled_state" == "static" ]] && enabled_bool="true"
|
|
|
|
schedule_expr=$(systemctl show "$unit" -p TimersCalendar --value 2>/dev/null | sed -n 's/.*OnCalendar=\([^;]*\);.*/\1/p' | sed 's/[[:space:]]*$//')
|
|
[[ -z "$schedule_expr" ]] && schedule_expr="(see: systemctl status $unit)"
|
|
|
|
next_run_at=""
|
|
if [[ "$next_usec" =~ ^[0-9]+$ && "$next_usec" -gt 0 ]]; then
|
|
next_run_at=$(date -u -d "@$((next_usec / 1000000))" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || echo "")
|
|
fi
|
|
|
|
if [[ -n "$next_run_at" ]]; then
|
|
add_task_with_next_run "systemd_timer" "$unit" "$activates" "$schedule_expr" "$unit" "$enabled_bool" "$next_run_at"
|
|
else
|
|
add_task "systemd_timer" "$unit" "$activates" "$schedule_expr" "$unit" "$enabled_bool"
|
|
fi
|
|
done < <(jq -c '.[]' <<<"$timers_json")
|
|
}
|
|
|
|
collect_cron
|
|
collect_systemd_timers
|
|
|
|
HOSTNAME_VALUE=$(hostname -f 2>/dev/null || hostname)
|
|
PAYLOAD=$(jq -n \
|
|
--arg hostname "$HOSTNAME_VALUE" \
|
|
--arg os_type "linux" \
|
|
--arg reported_at "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
|
|
--argjson tasks "$TASKS_JSON" \
|
|
'{hostname: $hostname, os_type: $os_type, reported_at: $reported_at, tasks: $tasks}')
|
|
|
|
if [[ "$DRY_RUN" == "1" ]]; then
|
|
echo "$PAYLOAD" | jq .
|
|
exit 0
|
|
fi
|
|
|
|
response=$(curl -sS -o /tmp/stm-agent-response.json -w "%{http_code}" \
|
|
-X POST "$API_URL/api/agent/report" \
|
|
-H "Authorization: Bearer $API_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$PAYLOAD")
|
|
|
|
if [[ "$response" -lt 200 || "$response" -ge 300 ]]; then
|
|
echo "Report failed with HTTP $response:" >&2
|
|
cat /tmp/stm-agent-response.json >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "Reported $(jq 'length' <<<"$TASKS_JSON") task(s) successfully."
|