- CLI binary: bin/gniza -> bin/gniza4cp - Install path: /usr/local/gniza4cp/ - Config path: /etc/gniza4cp/ - Log path: /var/log/gniza4cp/ - WHM plugin: gniza4cp-whm/ - cPanel plugin: cpanel/gniza4cp/ - AdminBin: Gniza4cp::Restore - Perl modules: Gniza4cpWHM::*, Gniza4cpCPanel::* - DaisyUI theme: gniza4cp - All internal references, branding, paths updated - Git remote updated to gniza4cp repo
308 lines
9.1 KiB
Bash
308 lines
9.1 KiB
Bash
#!/usr/bin/env bash
|
|
# gniza4cp/lib/schedule.sh — Cron management for decoupled schedules
|
|
#
|
|
# Schedules are defined in /etc/gniza4cp/schedules.d/<name>.conf:
|
|
# SCHEDULE="hourly|daily|weekly|monthly|custom"
|
|
# SCHEDULE_TIME="HH:MM"
|
|
# SCHEDULE_DAY="" # dow (0-6) for weekly, dom (1-28) for monthly
|
|
# SCHEDULE_CRON="" # full 5-field cron expr for custom
|
|
# REMOTES="" # comma-separated remote names (empty = all)
|
|
#
|
|
# Cron lines are tagged with "# gniza4cp:<name>" for clean install/remove.
|
|
|
|
readonly GNIZA4CP_CRON_TAG="# gniza4cp:"
|
|
readonly SCHEDULES_DIR="/etc/gniza4cp/schedules.d"
|
|
|
|
# ── Discovery ─────────────────────────────────────────────────
|
|
|
|
# List schedule names (filenames without .conf) sorted alphabetically.
|
|
list_schedules() {
|
|
if [[ ! -d "$SCHEDULES_DIR" ]]; then
|
|
return 0
|
|
fi
|
|
local f
|
|
for f in "$SCHEDULES_DIR"/*.conf; do
|
|
[[ -f "$f" ]] || continue
|
|
basename "$f" .conf
|
|
done
|
|
}
|
|
|
|
# Return 0 if at least one schedule config exists.
|
|
has_schedules() {
|
|
local schedules
|
|
schedules=$(list_schedules)
|
|
[[ -n "$schedules" ]]
|
|
}
|
|
|
|
# ── Loading ───────────────────────────────────────────────────
|
|
|
|
# Source a schedule config and set SCHEDULE/REMOTES globals.
|
|
# Usage: load_schedule <name>
|
|
load_schedule() {
|
|
local name="$1"
|
|
local conf="$SCHEDULES_DIR/${name}.conf"
|
|
|
|
if [[ ! -f "$conf" ]]; then
|
|
log_error "Schedule config not found: $conf"
|
|
return 1
|
|
fi
|
|
|
|
# Reset schedule globals before sourcing
|
|
SCHEDULE=""
|
|
SCHEDULE_TIME=""
|
|
SCHEDULE_DAY=""
|
|
SCHEDULE_CRON=""
|
|
SCHEDULE_REMOTES=""
|
|
SCHEDULE_SYSBACKUP=""
|
|
SCHEDULE_SKIP_SUSPENDED=""
|
|
|
|
_safe_source_config "$conf" || {
|
|
log_error "Failed to parse schedule config: $conf"
|
|
return 1
|
|
}
|
|
|
|
# Map REMOTES to SCHEDULE_REMOTES to avoid conflicts
|
|
SCHEDULE_REMOTES="${REMOTES:-}"
|
|
SCHEDULE_SYSBACKUP="${SYSBACKUP:-}"
|
|
SCHEDULE_SKIP_SUSPENDED="${SKIP_SUSPENDED:-}"
|
|
|
|
log_debug "Loaded schedule '$name': ${SCHEDULE} at ${SCHEDULE_TIME:-02:00}, remotes=${SCHEDULE_REMOTES:-all}"
|
|
}
|
|
|
|
# ── Cron Generation ───────────────────────────────────────────
|
|
|
|
# Convert schedule vars to a 5-field cron expression.
|
|
# Must be called after load_schedule() sets SCHEDULE/SCHEDULE_TIME/etc.
|
|
schedule_to_cron() {
|
|
local name="$1"
|
|
local schedule="${SCHEDULE:-}"
|
|
local stime="${SCHEDULE_TIME:-02:00}"
|
|
local sday="${SCHEDULE_DAY:-}"
|
|
local scron="${SCHEDULE_CRON:-}"
|
|
|
|
if [[ -z "$schedule" ]]; then
|
|
return 1 # no schedule configured
|
|
fi
|
|
|
|
local hour minute
|
|
hour="${stime%%:*}"
|
|
minute="${stime##*:}"
|
|
# Strip leading zeros for cron
|
|
hour=$((10#$hour))
|
|
minute=$((10#$minute))
|
|
|
|
case "$schedule" in
|
|
hourly)
|
|
if [[ -n "$sday" && "$sday" -gt 1 ]] 2>/dev/null; then
|
|
echo "$minute */$sday * * *"
|
|
else
|
|
echo "$minute * * * *"
|
|
fi
|
|
;;
|
|
daily)
|
|
echo "$minute $hour * * *"
|
|
;;
|
|
weekly)
|
|
if [[ -z "$sday" ]]; then
|
|
log_error "Schedule '$name': SCHEDULE_DAY required for weekly schedule"
|
|
return 1
|
|
fi
|
|
echo "$minute $hour * * $sday"
|
|
;;
|
|
monthly)
|
|
if [[ -z "$sday" ]]; then
|
|
log_error "Schedule '$name': SCHEDULE_DAY required for monthly schedule"
|
|
return 1
|
|
fi
|
|
echo "$minute $hour $sday * *"
|
|
;;
|
|
custom)
|
|
if [[ -z "$scron" ]]; then
|
|
log_error "Schedule '$name': SCHEDULE_CRON required for custom schedule"
|
|
return 1
|
|
fi
|
|
echo "$scron"
|
|
;;
|
|
*)
|
|
log_error "Schedule '$name': unknown SCHEDULE value: $schedule"
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Build the full cron line for a schedule.
|
|
# Uses SCHEDULE_REMOTES if set, otherwise targets all remotes.
|
|
build_cron_line() {
|
|
local name="$1"
|
|
local cron_expr
|
|
cron_expr=$(schedule_to_cron "$name") || return 1
|
|
|
|
local extra_flags=""
|
|
if [[ -n "$SCHEDULE_REMOTES" ]]; then
|
|
extra_flags+=" --remote=$SCHEDULE_REMOTES"
|
|
fi
|
|
if [[ "${SCHEDULE_SYSBACKUP:-}" == "yes" ]]; then
|
|
extra_flags+=" --sysbackup"
|
|
fi
|
|
if [[ "${SCHEDULE_SKIP_SUSPENDED:-}" == "yes" ]]; then
|
|
extra_flags+=" --skip-suspended"
|
|
fi
|
|
|
|
echo "$cron_expr /usr/local/bin/gniza4cp backup${extra_flags} >/dev/null 2>&1"
|
|
}
|
|
|
|
# ── Crontab Management ────────────────────────────────────────
|
|
|
|
# Install cron entries for all schedules in schedules.d/.
|
|
# Strips any existing gniza4cp entries first, then appends new ones.
|
|
install_schedules() {
|
|
if ! has_schedules; then
|
|
log_error "No schedules configured in $SCHEDULES_DIR"
|
|
return 1
|
|
fi
|
|
|
|
# Collect new cron lines
|
|
local new_lines=""
|
|
local count=0
|
|
local schedules; schedules=$(list_schedules)
|
|
|
|
while IFS= read -r sname; do
|
|
[[ -z "$sname" ]] && continue
|
|
load_schedule "$sname" || { log_error "Skipping schedule '$sname': failed to load"; continue; }
|
|
|
|
if [[ -z "${SCHEDULE:-}" ]]; then
|
|
log_debug "Schedule '$sname' has no SCHEDULE type, skipping"
|
|
continue
|
|
fi
|
|
|
|
local cron_line
|
|
cron_line=$(build_cron_line "$sname") || { log_error "Skipping schedule '$sname': invalid schedule"; continue; }
|
|
|
|
new_lines+="${GNIZA4CP_CRON_TAG}${sname}"$'\n'
|
|
new_lines+="${cron_line}"$'\n'
|
|
((count++)) || true
|
|
done <<< "$schedules"
|
|
|
|
if (( count == 0 )); then
|
|
log_warn "No valid schedules found"
|
|
return 1
|
|
fi
|
|
|
|
# Get current crontab, strip old gniza4cp lines
|
|
local current_crontab=""
|
|
current_crontab=$(crontab -l 2>/dev/null) || true
|
|
|
|
local filtered=""
|
|
local skip_next=false
|
|
while IFS= read -r line; do
|
|
if [[ "$line" == "${GNIZA4CP_CRON_TAG}"* ]]; then
|
|
skip_next=true
|
|
continue
|
|
fi
|
|
if [[ "$skip_next" == "true" ]]; then
|
|
skip_next=false
|
|
continue
|
|
fi
|
|
filtered+="$line"$'\n'
|
|
done <<< "$current_crontab"
|
|
|
|
# Append daily stats collection (runs at 05:00 UTC)
|
|
new_lines+="${GNIZA4CP_CRON_TAG}_stats"$'\n'
|
|
new_lines+="0 5 * * * /usr/local/bin/gniza4cp stats >> /var/log/gniza4cp/cron-stats.log 2>&1"$'\n'
|
|
|
|
# Append new lines
|
|
local final="${filtered}${new_lines}"
|
|
|
|
# Install
|
|
echo "$final" | crontab - || {
|
|
log_error "Failed to install crontab"
|
|
return 1
|
|
}
|
|
|
|
echo "Installed $count schedule(s):"
|
|
echo ""
|
|
|
|
# Show what was installed
|
|
while IFS= read -r sname; do
|
|
[[ -z "$sname" ]] && continue
|
|
load_schedule "$sname" 2>/dev/null || continue
|
|
[[ -z "${SCHEDULE:-}" ]] && continue
|
|
local cron_line; cron_line=$(build_cron_line "$sname" 2>/dev/null) || continue
|
|
echo " [$sname] $cron_line"
|
|
done <<< "$schedules"
|
|
}
|
|
|
|
# Display current gniza4cp cron entries.
|
|
show_schedules() {
|
|
local current_crontab=""
|
|
current_crontab=$(crontab -l 2>/dev/null) || true
|
|
|
|
if [[ -z "$current_crontab" ]]; then
|
|
echo "No crontab entries found."
|
|
return 0
|
|
fi
|
|
|
|
local found=false
|
|
local next_is_command=false
|
|
local current_tag=""
|
|
while IFS= read -r line; do
|
|
if [[ "$line" == "${GNIZA4CP_CRON_TAG}"* ]]; then
|
|
current_tag="${line#"$GNIZA4CP_CRON_TAG"}"
|
|
next_is_command=true
|
|
continue
|
|
fi
|
|
if [[ "$next_is_command" == "true" ]]; then
|
|
next_is_command=false
|
|
if [[ "$found" == "false" ]]; then
|
|
echo "Current gniza4cp schedules:"
|
|
echo ""
|
|
found=true
|
|
fi
|
|
echo " [$current_tag] $line"
|
|
fi
|
|
done <<< "$current_crontab"
|
|
|
|
if [[ "$found" == "false" ]]; then
|
|
echo "No gniza4cp schedule entries in crontab."
|
|
fi
|
|
}
|
|
|
|
# Remove all gniza4cp cron entries.
|
|
remove_schedules() {
|
|
local current_crontab=""
|
|
current_crontab=$(crontab -l 2>/dev/null) || true
|
|
|
|
if [[ -z "$current_crontab" ]]; then
|
|
echo "No crontab entries to remove."
|
|
return 0
|
|
fi
|
|
|
|
local filtered=""
|
|
local skip_next=false
|
|
local removed=0
|
|
while IFS= read -r line; do
|
|
if [[ "$line" == "${GNIZA4CP_CRON_TAG}"* ]]; then
|
|
skip_next=true
|
|
((removed++)) || true
|
|
continue
|
|
fi
|
|
if [[ "$skip_next" == "true" ]]; then
|
|
skip_next=false
|
|
continue
|
|
fi
|
|
filtered+="$line"$'\n'
|
|
done <<< "$current_crontab"
|
|
|
|
if (( removed == 0 )); then
|
|
echo "No gniza4cp schedule entries found in crontab."
|
|
return 0
|
|
fi
|
|
|
|
echo "$filtered" | crontab - || {
|
|
log_error "Failed to update crontab"
|
|
return 1
|
|
}
|
|
|
|
echo "Removed $removed gniza4cp schedule(s) from crontab."
|
|
}
|