#!/usr/bin/env bash # R-1105 Part 1/2: restic 0.14.0 vs 0.18.1 on throwaway LOCAL repos (scratchpad only). No real store is touched. # Usage: semantics.sh set -u S= OUT=${1:?out dir}; mkdir -p "$OUT" W=$S/work; rm -rf "$W"; mkdir -p "$W" export RESTIC_PASSWORD_FILE=$W/pw; head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n' > "$RESTIC_PASSWORD_FILE" export RESTIC_CACHE_DIR=$W/cache export TZ=UTC # data: a unit-like tree + one 300 MB file so --json emits status lines mid-run mkdir -p $W/src/unit/db-dumps $W/src/unit/volume-dumps $W/src/data echo "select 1;" > $W/src/unit/db-dumps/app.sql; echo '{"v":1}' > $W/src/unit/manifest.json head -c 300M /dev/urandom > $W/src/unit/volume-dumps/big.tar echo hello > $W/src/data/file.txt; echo secret > $W/src/data/noread.txt; chmod 000 $W/src/data/noread.txt for V in 0.14.0 0.18.1; do R="$S/bin/restic-$V"; REPO=$W/repo-$V; O=$OUT/$V; mkdir -p $O rr() { "$R" -r "$REPO" "$@"; } { echo "== $V"; "$R" version; } > $O/00-version.txt rr init > $O/01-init.txt 2>&1; echo "rc=$?" >> $O/01-init.txt rr init > $O/02-init-again.txt 2>&1; echo "rc=$?" >> $O/02-init-again.txt # T2 --json progress (full stream kept; the big file forces mid-run status lines) rr backup --tag felhom-offbox --tag app1 $W/src/unit --json > $O/10-backup-json.stdout 2> $O/10-backup-json.stderr; echo "rc=$?" > $O/10-backup-json.rc # T1 a missing source path beside an existing one; a missing path alone; an unreadable file rr backup --tag felhom-offbox --tag app1 $W/src/unit $W/src/nope > $O/11-missing-one.txt 2>&1; echo "rc=$?" >> $O/11-missing-one.txt rr backup --tag felhom-offbox --tag app2 $W/src/nope > $O/12-missing-all.txt 2>&1; echo "rc=$?" >> $O/12-missing-all.txt rr backup --tag felhom-offbox --tag app3 $W/src/data > $O/13-unreadable.txt 2>&1; echo "rc=$?" >> $O/13-unreadable.txt # nightly (non-json) output of a normal run, combined — what resticStep sees rr backup --tag felhom-offbox --tag app1 $W/src/unit > $O/14-backup-plain.txt 2>&1; echo "rc=$?" >> $O/14-backup-plain.txt # T4 snapshots --json, combined stdout+stderr EXACTLY as the runner reads it; latest --tag rr snapshots --json > $O/20-snapshots-json.combined 2>&1; echo "rc=$?" > $O/20.rc rr snapshots latest --tag app1 --json > $O/21-snapshots-latest-tag.combined 2>&1; echo "rc=$?" > $O/21.rc ID=$(rr snapshots latest --tag app1 --json 2>/dev/null | python3 -c 'import json,sys;print(json.load(sys.stdin)[0]["short_id"])') # T5 stats rr stats "$ID" --json > $O/22-stats-id.combined 2>&1; echo "rc=$?" > $O/22.rc rr stats --mode raw-data --json > $O/23-stats-raw.combined 2>&1; echo "rc=$?" > $O/23.rc # T6 restore --include --no-lock on a READ-ONLY repo (proves nothing is written); layout under target chmod -R a-w "$REPO" rr restore "$ID" --target $W/rs-$V --include $W/src/unit --no-lock > $O/30-restore-nolock-ro.txt 2>&1; echo "rc=$?" >> $O/30-restore-nolock-ro.txt chmod -R u+w "$REPO" (cd $W/rs-$V && find . -type f | sort; sha256sum .$W/src/unit/volume-dumps/big.tar $W/src/unit/volume-dumps/big.tar) > $O/31-restore-layout.txt 2>&1 rm -rf $W/rs-$V rr restore latest --tag app1 --target $W/rl-$V > $O/32-restore-latest-tag.txt 2>&1; echo "rc=$?" >> $O/32-restore-latest-tag.txt ls -la $REPO/locks >> $O/32-restore-latest-tag.txt; rm -rf $W/rl-$V # T7 error texts: wrong password, no repo, lock held by a live (stopped) exclusive op RESTIC_PASSWORD_FILE= RESTIC_PASSWORD=wrong rr cat config > $O/40-wrong-password.txt 2>&1 < /dev/null; echo "rc=$?" >> $O/40-wrong-password.txt "$R" -r $W/does-not-exist cat config > $O/41-no-repo.txt 2>&1; echo "rc=$?" >> $O/41-no-repo.txt rr prune > $O/42-prune-holder.txt 2>&1 & P=$!; for i in $(seq 1 50); do ls $REPO/locks 2>/dev/null | grep -q . && break; sleep 0.1; done; kill -STOP $P rr backup --tag felhom-offbox --tag app1 $W/src/unit > $O/43-locked.txt 2>&1; echo "rc=$?" >> $O/43-locked.txt rr unlock > $O/44-unlock-stale-only.txt 2>&1; echo "rc=$?" >> $O/44-unlock-stale-only.txt; ls $REPO/locks | wc -l >> $O/44-unlock-stale-only.txt kill -CONT $P; wait $P; echo "rc=$?" >> $O/42-prune-holder.txt # transport: sftp to a closed port; sftp with a refused key; rclone program over a dead ssh "$R" -r sftp:nobody@127.0.0.1:/x -o sftp.command="ssh nobody@127.0.0.1 -p 1 -oBatchMode=yes -oConnectTimeout=3 -s sftp" cat config > $O/45-sftp-refused-port.txt 2>&1; echo "rc=$?" >> $O/45-sftp-refused-port.txt ssh-keygen -q -t ed25519 -N '' -f $W/k-$V "$R" -r sftp:nobody@127.0.0.1:/x -o sftp.command="ssh nobody@127.0.0.1 -p 22 -oBatchMode=yes -oConnectTimeout=3 -oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null -oIdentitiesOnly=yes -i $W/k-$V -s sftp" cat config > $O/46-sftp-refused-key.txt 2>&1; echo "rc=$?" >> $O/46-sftp-refused-key.txt "$R" -r rclone:x -o rclone.program="ssh -p 1 -oBatchMode=yes -oConnectTimeout=3 nobody@127.0.0.1 rclone" cat config > $O/47-rclone-dead-ssh.txt 2>&1; echo "rc=$?" >> $O/47-rclone-dead-ssh.txt "$R" -r sftp:nobody@10.255.255.1:/x -o sftp.command="ssh nobody@10.255.255.1 -p 22 -oBatchMode=yes -oConnectTimeout=3 -s sftp" cat config > $O/48-sftp-unreachable.txt 2>&1; echo "rc=$?" >> $O/48-sftp-unreachable.txt # T8 check: healthy, then one damaged pack, --read-data rr check > $O/50-check-ok.txt 2>&1; echo "rc=$?" >> $O/50-check-ok.txt cp -a $REPO $W/dmg-$V; PK=$(find $W/dmg-$V/data -type f -size +1M | head -1); chmod u+w "$PK" printf 'XXXXXXXXXXXXXXXX' | dd of="$PK" bs=1 seek=4096 conv=notrunc 2>/dev/null "$R" -r $W/dmg-$V check --read-data > $O/51-check-damaged.txt 2>&1; echo "rc=$?" >> $O/51-check-damaged.txt chmod -R u+w $W/dmg-$V; rm -rf $W/dmg-$V done chmod -R u+w $W echo done