#!/usr/bin/env bash # # install-backup-cron.sh # ----------------------- # One-shot installer for the Africa Alert SQLite backup cron job. # # Idempotent: re-running is a no-op if the cron entry is already present # (detected by `crontab -l | grep -q africa-alert`). # # Default schedule: every 12 hours (00:00 and 12:00 UTC). # Override with CRON_SCHEDULE env var, e.g. # CRON_SCHEDULE="0 */6 * * *" sudo ./install-backup-cron.sh # # This script is meant to be run on the production Ubuntu 22.04 host. # It is NOT a Windows script. On a Windows dev box, only # `bash -n install-backup-cron.sh` (syntax check) is meaningful. set -eu SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" BACKUP_SCRIPT="${SCRIPT_DIR}/africa-alert-backup.sh" CRON_SCHEDULE="${CRON_SCHEDULE:-0 */12 * * *}" # every 12h, on the hour LOG_DIR="${LOG_DIR:-${PROJECT_ROOT}/logs}" INSTALL_LOG="${LOG_DIR}/backup-install.log" # Marker used by both the grep-based idempotency check and as a comment # inside the crontab so an operator reading `crontab -l` knows what the # line does. Keep the marker stable — other tooling relies on it. CRON_MARKER="africa-alert-backup" mkdir -p "${LOG_DIR}" touch "${INSTALL_LOG}" log() { local msg="[$(date -u +%Y-%m-%dT%H:%M:%SZ)] [install-cron] $*" echo "${msg}" | tee -a "${INSTALL_LOG}" >&2 } if [ ! -x "${BACKUP_SCRIPT}" ]; then log "ERROR: backup script not found or not executable: ${BACKUP_SCRIPT}" log "Hint: chmod +x ${BACKUP_SCRIPT}" exit 1 fi # Idempotency check: does the current crontab already contain our marker? if command -v crontab >/dev/null 2>&1; then EXISTING="$(crontab -l 2>/dev/null || true)" if echo "${EXISTING}" | grep -q "${CRON_MARKER}"; then log "cron entry already installed; nothing to do." log "current crontab:" echo "${EXISTING}" | grep "${CRON_MARKER}" | sed 's/^/ /' | tee -a "${INSTALL_LOG}" >&2 log "to reinstall, first run: crontab -l | grep -v '${CRON_MARKER}' | crontab -" exit 0 fi else log "WARN: crontab command not found; this is not a Linux host. Aborting." exit 1 fi # Build the cron line. The marker is a leading comment so it's greppable. CRON_LINE="${CRON_SCHEDULE} /bin/bash ${BACKUP_SCRIPT} >> ${LOG_DIR}/backup.log 2>&1 # ${CRON_MARKER}" # Install: append the new line to whatever crontab exists, or create one # if none. `crontab -` reads from stdin. { echo "${EXISTING:-}" echo "${CRON_LINE}" } | crontab - # Compute the next-run time. We use a small GNU-date trick: if now is # earlier than the next scheduled firing, that's the next run; otherwise # advance by 12h. This is a best-effort estimate — cron itself is the # source of truth. Format: YYYY-MM-DD HH:MM:SS UTC. compute_next_run() { local now_epoch now_epoch="$(date -u +%s)" # Step forward in 12h blocks until we land in the future. local candidate="${now_epoch}" while [ "${candidate}" -le "${now_epoch}" ]; do candidate=$((candidate + 12 * 3600)) done # Snap to the next "round" 12h boundary (00:00 or 12:00 UTC) by # subtracting (epoch mod 12h). This matches CRON_SCHEDULE default. local bucket=$((candidate % (12 * 3600))) candidate=$((candidate - bucket)) # If snapping pushed us into the past, add another 12h. if [ "${candidate}" -le "${now_epoch}" ]; then candidate=$((candidate + 12 * 3600)) fi date -u -d "@${candidate}" +"%Y-%m-%d %H:%M:%S UTC" } NEXT_RUN="$(compute_next_run)" log "installed cron entry:" log " ${CRON_LINE}" log "next scheduled run (estimate): ${NEXT_RUN}" log "verify with: crontab -l | grep ${CRON_MARKER}" log "uninstall with: crontab -l | grep -v '${CRON_MARKER}' | crontab -" exit 0