Files
felhom.eu/documentation/audits/debian13-restic-2026-10-16/tools/window.sh
T

57 lines
3.6 KiB
Bash

#!/usr/bin/env bash
# R-1105: the forget plan (SP-2 policy, --group-by host,tags, 7/4/6) on ONE snapshot set, read by 0.14.0 and 0.18.1.
# The set: two apps, 230 days, nightly 02:30 UTC runs + some same-day manual runs + a DST-shifted run, made by 0.14.0.
# Then: 0.18.1 runs the real forget --prune; 0.14.0 must still list, check and restore; 0.18.1 restores a 0.14.0 snapshot.
set -u
S=<scratch>
OUT=${1:?out}; W=$S/winwork; rm -rf $W; mkdir -p $W $OUT
export RESTIC_PASSWORD_FILE=$W/pw RESTIC_CACHE_DIR=$W/cache
head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n' > $RESTIC_PASSWORD_FILE
OLD=$S/bin/restic-0.14.0; NEW=$S/bin/restic-0.18.1; REPO=$W/repo
mkdir -p $W/src/a $W/src/b
$OLD -r $REPO init >/dev/null
n=0
for d in $(seq 230 -1 0); do
day=$(date -u -d "2026-10-16 -$d days" +%F)
for app in a b; do
echo "$day $app" > $W/src/$app/f.txt
TZ=UTC $OLD -r $REPO backup --host box --tag felhom-offbox --tag $app --time "$day 02:30:00" $W/src/$app >/dev/null 2>&1 && n=$((n+1))
done
# a manual same-day run every 9th day (app a), and a run made in Budapest time every 17th day (app b)
if [ $((d % 9)) = 0 ]; then TZ=UTC $OLD -r $REPO backup --host box --tag felhom-offbox --tag a --time "$day 14:10:00" $W/src/a >/dev/null 2>&1 && n=$((n+1)); fi
if [ $((d % 17)) = 0 ]; then TZ=Europe/Budapest $OLD -r $REPO backup --host box --tag felhom-offbox --tag b --time "$day 00:40:00" $W/src/b >/dev/null 2>&1 && n=$((n+1)); fi
done
echo "snapshots made by 0.14.0: $n" > $OUT/summary.txt
POL="--group-by host,tags --keep-daily 7 --keep-weekly 4 --keep-monthly 6"
for V in 0.14.0 0.18.1; do
for Z in UTC Europe/Budapest; do
TZ=$Z $S/bin/restic-$V -r $REPO forget $POL --dry-run --json 2>&1 > $OUT/plan-$V-${Z//\//_}.json
done
done
python3 - $OUT >> $OUT/summary.txt <<'EOF'
import json,sys,glob,os
o=sys.argv[1]; plans={}
for f in sorted(glob.glob(o+"/plan-*.json")):
raw=open(f).read(); raw=raw[raw.index("["):]
g=json.loads(raw)
plans[os.path.basename(f)]=(sorted(s["id"] for x in g for s in x.get("remove") or []), sorted(s["id"] for x in g for s in x.get("keep") or []))
base=plans["plan-0.14.0-UTC.json"]
for k,(rm,kp) in plans.items():
print(f"{k}: remove {len(rm)} keep {len(kp)} — identical to 0.14.0/UTC: {(rm,kp)==base}")
EOF
# the real forget --prune by 0.18.1, then both directions
ID_OLD=$($OLD -r $REPO snapshots --json --tag a 2>/dev/null | python3 -c 'import json,sys;s=json.load(sys.stdin);print(s[-1]["short_id"])')
TZ=UTC $NEW -r $REPO forget $POL --prune > $OUT/forget-prune-0.18.1.txt 2>&1; echo "rc=$?" >> $OUT/forget-prune-0.18.1.txt
echo "f-new" > $W/src/a/f.txt; $NEW -r $REPO backup --host box --tag felhom-offbox --tag a $W/src/a > /dev/null 2>&1
ID_NEW=$($NEW -r $REPO snapshots --json --tag a 2>/dev/null | python3 -c 'import json,sys;s=json.load(sys.stdin);print(s[-1]["short_id"])')
{
echo "after 0.18.1 forget --prune + a 0.18.1 backup:"
echo "0.14.0 lists: $($OLD -r $REPO snapshots --json 2>&1 | python3 -c 'import json,sys;print(len(json.load(sys.stdin)))' 2>&1)"
echo "0.18.1 lists: $($NEW -r $REPO snapshots --json 2>&1 | python3 -c 'import json,sys;print(len(json.load(sys.stdin)))' 2>&1)"
$OLD -r $REPO check --read-data > $W/c.txt 2>&1; echo "0.14.0 check --read-data rc=$? ($(tail -1 $W/c.txt))"
$OLD -r $REPO restore $ID_NEW --target $W/r-old > /dev/null 2>&1; echo "0.14.0 restores the 0.18.1 snapshot: rc=$? content=$(cat $W/r-old$W/src/a/f.txt)"
$NEW -r $REPO restore $ID_OLD --target $W/r-new > /dev/null 2>&1; echo "0.18.1 restores a 0.14.0 snapshot ($ID_OLD): rc=$? content=$(cat $W/r-new$W/src/a/f.txt)"
} >> $OUT/summary.txt
sed -i "s#$W#<W>#g" $OUT/*.txt
cat $OUT/summary.txt