Restore this backup?
-
{{ restoreTarget.snapshotid }}
+
{{ formatTaken(restoreTarget.takenat || restoreTarget.snapshotid) }}
@@ -356,7 +356,7 @@ const STALE_HOURS = 48
const hoursSinceLastGood = computed(() => {
if (!lastGoodAt.value) return null
- const then = new Date(lastGoodAt.value.replace(/Z$/, 'Z')).getTime()
+ const then = new Date(normalizeStamp(lastGoodAt.value)).getTime()
return (Date.now() - then) / 36e5
})
@@ -383,11 +383,21 @@ const bannerSub = computed(() => {
// -- Formatting -------------------------------------------------------------
function formatTaken(value) {
if (!value) return 'unknown'
- const date = new Date(value.replace(/Z$/, 'Z'))
+ const date = new Date(normalizeStamp(value))
if (Number.isNaN(date.getTime())) return value
return date.toLocaleString()
}
+// Backup ids and manifest stamps use a filename-safe UTC form with no colons
+// (YYYY-MM-DDTHHMMSSZ, sometimes prefixed with a site name), which the Date
+// parser rejects. Reinsert the colons so it parses as real ISO 8601.
+function normalizeStamp(value) {
+ const match = String(value).match(/(\d{4}-\d{2}-\d{2})T(\d{2})(\d{2})(\d{2})Z$/)
+ if (!match) return value
+ const [, day, hour, minute, second] = match
+ return `${day}T${hour}:${minute}:${second}Z`
+}
+
function formatSize(bytes) {
if (!bytes) return '0 B'
const units = ['B', 'KB', 'MB', 'GB', 'TB']
diff --git a/scripts/backup/watch.sh b/scripts/backup/watch.sh
index 42c8b93e..18c53827 100644
--- a/scripts/backup/watch.sh
+++ b/scripts/backup/watch.sh
@@ -14,12 +14,17 @@ 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] $*"; }
mkdir -p "${BACKUPS_ROOT}"
-log "watcher up; polling ${SENTINEL} every ${INTERVAL}s"
+# 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
@@ -34,5 +39,18 @@ while true; do
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}"
+ 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