Polish: daily snapshot rotation #86
Executable
+74
@@ -0,0 +1,74 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# rotate-snapshots.sh — create today's daily snapshot + prune snapshots
|
||||||
|
# older than N days. Idempotent: safe to run repeatedly on the same day.
|
||||||
|
#
|
||||||
|
# Snapshots created by this script are named `daily-YYYY-MM-DD`. Only
|
||||||
|
# snapshots matching that prefix are candidates for pruning — hand-created
|
||||||
|
# snapshots (release-anchor, pre-migration, etc.) are never touched.
|
||||||
|
#
|
||||||
|
# Env / args:
|
||||||
|
# CLAWSTOR_BIN path to claw-store binary (default: ~/clawstor-deploy/claw-store)
|
||||||
|
# CLAWSTOR_CONFIG path to config.toml (default: ~/clawstor-deploy/config.toml)
|
||||||
|
# RETAIN_DAYS snapshots older than this many days get pruned (default: 30)
|
||||||
|
# DRY_RUN=1 print what would happen, no side effects
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
CLAWSTOR_BIN=${CLAWSTOR_BIN:-$HOME/clawstor-deploy/claw-store}
|
||||||
|
CLAWSTOR_CONFIG=${CLAWSTOR_CONFIG:-$HOME/clawstor-deploy/config.toml}
|
||||||
|
RETAIN_DAYS=${RETAIN_DAYS:-30}
|
||||||
|
DRY_RUN=${DRY_RUN:-0}
|
||||||
|
|
||||||
|
if [ ! -x "$CLAWSTOR_BIN" ]; then
|
||||||
|
echo "error: $CLAWSTOR_BIN not executable" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
CS="$CLAWSTOR_BIN --config $CLAWSTOR_CONFIG"
|
||||||
|
TODAY=$(date +%Y-%m-%d)
|
||||||
|
NAME="daily-$TODAY"
|
||||||
|
|
||||||
|
echo "── rotate-snapshots ────────────────────────────────"
|
||||||
|
echo "today: $NAME"
|
||||||
|
echo "retention: $RETAIN_DAYS days"
|
||||||
|
|
||||||
|
# Create today's snapshot. Idempotent: `cluster-snapshot-create` errors
|
||||||
|
# if the name already exists — we treat that as success.
|
||||||
|
if [ "$DRY_RUN" = "1" ]; then
|
||||||
|
echo "dry-run: would create snapshot $NAME"
|
||||||
|
else
|
||||||
|
if $CS cluster-snapshot-create --name "$NAME" 2>&1 | tail -6; then
|
||||||
|
echo "created: $NAME"
|
||||||
|
else
|
||||||
|
echo "already exists (idempotent): $NAME"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Compute cutoff — POSIX date arithmetic (no GNU date extensions needed
|
||||||
|
# because we do the math on the epoch integer).
|
||||||
|
NOW_EPOCH=$(date +%s)
|
||||||
|
CUTOFF_EPOCH=$((NOW_EPOCH - RETAIN_DAYS * 86400))
|
||||||
|
|
||||||
|
# Snapshot list format:
|
||||||
|
# CREATED_AT NAME BLOBS SIZE
|
||||||
|
# 1784046443 daily-2026-07-14 4 224
|
||||||
|
# So awk field 1 = epoch, field 2 = name.
|
||||||
|
PRUNE_LIST=$($CS cluster-snapshot-list 2>/dev/null | awk -v cutoff="$CUTOFF_EPOCH" '
|
||||||
|
NR > 4 && $2 ~ /^daily-/ && $1 < cutoff { print $2 }
|
||||||
|
')
|
||||||
|
|
||||||
|
echo
|
||||||
|
if [ -z "$PRUNE_LIST" ]; then
|
||||||
|
echo "nothing to prune"
|
||||||
|
else
|
||||||
|
echo "prune candidates (older than $RETAIN_DAYS days):"
|
||||||
|
echo "$PRUNE_LIST" | sed 's/^/ /'
|
||||||
|
if [ "$DRY_RUN" = "1" ]; then
|
||||||
|
echo "dry-run: no snapshots deleted"
|
||||||
|
else
|
||||||
|
echo "$PRUNE_LIST" | while read -r snap; do
|
||||||
|
$CS cluster-snapshot-delete --name "$snap" 2>&1 | tail -1
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo "────────────────────────────────────────────────────"
|
||||||
@@ -11,6 +11,7 @@ Systemd **user** units for clawstor.
|
|||||||
| `clawstor-scrub.service` + `clawstor-scrub.timer` | Weekly (Sun 04:00) BLAKE3 verify every chunk against its manifest. Non-zero exit = integrity failure — surfaced by systemd status. |
|
| `clawstor-scrub.service` + `clawstor-scrub.timer` | Weekly (Sun 04:00) BLAKE3 verify every chunk against its manifest. Non-zero exit = integrity failure — surfaced by systemd status. |
|
||||||
| `clawstor-gc.service` + `clawstor-gc.timer` | Nightly (03:30) orphan-chunk sweep. Override ExecStart via drop-in to add `--evict-to-gb N` for size-cap fleets. Sequenced before the Sunday scrub so scrub reads a fresh layout. |
|
| `clawstor-gc.service` + `clawstor-gc.timer` | Nightly (03:30) orphan-chunk sweep. Override ExecStart via drop-in to add `--evict-to-gb N` for size-cap fleets. Sequenced before the Sunday scrub so scrub reads a fresh layout. |
|
||||||
| `clawstor-ref-sweep.service` + `clawstor-ref-sweep.timer` | Nightly (03:15) Gitea live-refs poll + stale-fingerprint report. Set `GITEA_TOKEN` via a drop-in for private repos. Report-only (dry-run) — deletion is a follow-on. |
|
| `clawstor-ref-sweep.service` + `clawstor-ref-sweep.timer` | Nightly (03:15) Gitea live-refs poll + stale-fingerprint report. Set `GITEA_TOKEN` via a drop-in for private repos. Report-only (dry-run) — deletion is a follow-on. |
|
||||||
|
| `clawstor-snapshot-rotate.service` + `clawstor-snapshot-rotate.timer` | Daily (02:00) create `daily-YYYY-MM-DD` snapshot + prune snapshots older than `RETAIN_DAYS` (default 30). Only touches snapshots whose name matches `daily-*` — hand-created ones survive. |
|
||||||
|
|
||||||
## Install `clawstor-fuse.service`
|
## Install `clawstor-fuse.service`
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Clawstor daily snapshot rotation (create today + prune > N days)
|
||||||
|
Documentation=https://git.redclaw.dev/clawverse/clawstor
|
||||||
|
After=clawstor-cluster.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
Environment=RETAIN_DAYS=30
|
||||||
|
ExecStart=%h/clawstor-deploy/scripts/rotate-snapshots.sh
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Daily Clawstor snapshot rotation
|
||||||
|
Documentation=https://git.redclaw.dev/clawverse/clawstor
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
# 02:00 local — ahead of ref-sweep (03:15) + gc (03:30) so the fresh
|
||||||
|
# snapshot's pins protect its blobs from tonight's eviction.
|
||||||
|
OnCalendar=*-*-* 02:00:00
|
||||||
|
Persistent=true
|
||||||
|
Unit=clawstor-snapshot-rotate.service
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
Reference in New Issue
Block a user