#!/usr/bin/env bash # ShopDB backup WATCHER - the resident half of the in-app backup utility. # # WHY this exists: the api container cannot run docker, so when an operator # clicks "Back up now" the api only drops a sentinel file (.backup-requested) # on the shared backups volume. This watcher is the thing that actuates that # request: it polls for the sentinel and runs the one-shot backup.sh, which # ships a restic snapshot to the backup-store container and clears the sentinel # on success. That turns the UI button into a real, self-serve backup without # giving the api container any docker or host privileges. # # ASCII only, no emojis - matches the repo style gate. set -uo pipefail BACKUPS_ROOT="${SHOPDB_BACKUPS_ROOT:-/backups}" SENTINEL="${BACKUPS_ROOT}/.backup-requested" RESTORE_SENTINEL="${BACKUPS_ROOT}/.restore-requested" INTERVAL="${BACKUP_POLL_SECONDS:-10}" log() { echo "[backup-watch] $*"; } # shellcheck source=scripts/backup/status.sh . /opt/backup/status.sh mkdir -p "${BACKUPS_ROOT}" # The api container runs unprivileged and only drops sentinel files here; it # cannot write into a root-owned 0755 directory. Make the backups root # writable so "Back up now" and "Restore" can queue their sentinels. chmod 0777 "${BACKUPS_ROOT}" 2>/dev/null || true log "watcher up; polling ${SENTINEL} and ${RESTORE_SENTINEL} every ${INTERVAL}s" while true; do if [ -f "${SENTINEL}" ]; then log "backup requested - running one-shot" status_write backup queued "" "Backup queued" if /opt/backup/backup.sh; then log "backup completed" else # backup.sh only clears the sentinel on success, so a failed run # would otherwise loop forever. Clear it here and wait for the next # explicit request rather than hammering a broken backup. log "backup FAILED - clearing request, awaiting next" rm -f "${SENTINEL}" 2>/dev/null || true fi fi if [ -f "${RESTORE_SENTINEL}" ]; then # The api drops the target set name into the restore sentinel. Read it, # hand it to restore.sh, and clear the request whether it succeeds or # fails so a bad set cannot pin the watcher in a tight retry loop. setname="$(head -n1 "${RESTORE_SENTINEL}" 2>/dev/null | tr -d '\r\n')" log "restore requested - running restore for ${setname}" status_write restore queued "${setname}" "Restore queued" if /opt/backup/restore.sh "${setname}"; then log "restore completed" else log "restore FAILED - clearing request, awaiting next" fi rm -f "${RESTORE_SENTINEL}" 2>/dev/null || true fi sleep "${INTERVAL}" done