Prepare MariaDB restore tests and recover backup disk space safely
This commit is contained in:
1 parent
f0c761213d
commit
b0416461bc
23 files changed
+918
-129
No files matched your search
@@ -38,18 +38,71 @@ done
|
||||
OPERATION=restore
|
||||
load_config
|
||||
init_runtime
|
||||
check_space "$RESTORE_ROOT"
|
||||
# shellcheck source=lib/space.sh
|
||||
source "$LIB_DIR/space.sh"
|
||||
clean_backup_scratch
|
||||
ensure_space "$RESTORE_ROOT"
|
||||
STAGING_DIR=$(mktemp -d "$RESTORE_ROOT/${RUN_ID}.XXXXXX")
|
||||
if [[ $MODE == verify ]]; then printf '%s\n' "$RUN_ID" > "$STAGING_DIR/.verify-only"; fi
|
||||
INPUT="$STAGING_DIR/input"
|
||||
mkdir -m 700 "$INPUT"
|
||||
if [[ -n $REMOTE_RUN ]]; then
|
||||
[[ $REMOTE_RUN =~ ^[a-zA-Z0-9][a-zA-Z0-9_-]+$ && $REMOTE_RUN == "$ARCHIVE_PREFIX"_* ]] || die 'Unsafe remote run name'
|
||||
check_remote
|
||||
if [[ $LEGACY == false ]]; then
|
||||
remote="${RCLONE_REMOTE_BASE%/}/$REMOTE_RUN"
|
||||
rc cat "$remote/manifest.json" --count 1048577 > "$INPUT/manifest.json"
|
||||
rc cat "$remote/COMPLETE" --count 256 > "$INPUT/COMPLETE"
|
||||
helper metadata "$INPUT" >/dev/null
|
||||
python3 - "$INPUT" "$REMOTE_RUN" <<'PY' > "$STAGING_DIR/verify.tsv"
|
||||
import json,sys
|
||||
from pathlib import Path
|
||||
data=json.loads((Path(sys.argv[1])/'manifest.json').read_text())
|
||||
assert data['run_id'] == sys.argv[2], 'Remote run ID mismatch'
|
||||
for item in data['archives']:
|
||||
print('\t'.join(str(item[k]) for k in ('file','root','size','sha256')))
|
||||
PY
|
||||
rc lsf "$remote" --files-only --max-depth 1 > "$STAGING_DIR/remote-files.txt"
|
||||
python3 - "$INPUT/manifest.json" "$STAGING_DIR/remote-files.txt" <<'PY'
|
||||
import json,sys
|
||||
from pathlib import Path
|
||||
expected={x['file'] for x in json.loads(Path(sys.argv[1]).read_text())['archives']} | {'manifest.json','COMPLETE'}
|
||||
actual=set(Path(sys.argv[2]).read_text().splitlines())
|
||||
assert actual in (expected, expected | {'UPLOADED'}), 'Remote backup has missing or unexpected files'
|
||||
PY
|
||||
fi
|
||||
if [[ $MODE == verify ]]; then
|
||||
# Verify offsite archives sequentially, retaining at most one compressed file.
|
||||
while IFS=$'\t' read -r file root size hash; do
|
||||
ensure_space "$RESTORE_ROOT" "$size"
|
||||
rc cat "$remote/$file" --count "$((size + 1))" > "$INPUT/$file"
|
||||
[[ $(stat -c %s "$INPUT/$file") == "$size" && $(sha256sum "$INPUT/$file" | cut -d ' ' -f 1) == "$hash" ]] || die "Archive checksum/size mismatch: $file"
|
||||
helper tar "$INPUT/$file" "$root"
|
||||
rm -f -- "$INPUT/$file"
|
||||
done < "$STAGING_DIR/verify.tsv"
|
||||
echo 'Verification passed; remote archives checked sequentially; no application changes.'
|
||||
exit 0
|
||||
fi
|
||||
if [[ $LEGACY == false ]]; then
|
||||
size=$(python3 - "$INPUT/manifest.json" <<'PY'
|
||||
import json,sys
|
||||
print(sum(x['size'] for x in json.load(open(sys.argv[1]))['archives']))
|
||||
PY
|
||||
)
|
||||
ensure_space "$RESTORE_ROOT" "$size"
|
||||
fi
|
||||
echo "-- Downloading remote backup for restore verification: $REMOTE_RUN"
|
||||
rc copy "${RCLONE_REMOTE_BASE%/}/$REMOTE_RUN" "$INPUT"
|
||||
echo '-- Remote backup downloaded; validating manifest, hashes and archives'
|
||||
else
|
||||
[[ -d $LOCAL_RUN && ! -L $LOCAL_RUN ]] || die 'Local run must be an existing directory'
|
||||
if [[ $MODE == verify ]]; then
|
||||
helper verify "$LOCAL_RUN"
|
||||
echo 'Verification passed; local archives checked without copying; no application changes.'
|
||||
exit 0
|
||||
fi
|
||||
size=$(du -s -B1 -- "$LOCAL_RUN" | awk '{print $1}')
|
||||
ensure_space "$RESTORE_ROOT" "$size"
|
||||
# Resolve overlap separately: archive sources are allowed under WORKDIR, but
|
||||
# recursive copies into the freshly created restore workspace are forbidden.
|
||||
input_real=$(realpath -e "$LOCAL_RUN")
|
||||
@@ -85,6 +138,8 @@ helper verify "$INPUT"
|
||||
if [[ $MODE == verify ]]; then echo 'Verification passed; no application changes.'; exit 0; fi
|
||||
helper list "$INPUT" > "$STAGING_DIR/archive-list.tsv"
|
||||
if [[ $MODE == extract ]]; then
|
||||
size=$(helper expanded "$INPUT")
|
||||
ensure_space "$RESTORE_ROOT" "$size"
|
||||
mkdir "$STAGING_DIR/extracted"
|
||||
while IFS=$'\t' read -r label file _; do
|
||||
mkdir "$STAGING_DIR/extracted/$label"
|
||||
@@ -137,6 +192,9 @@ if [[ $MODE == plan ]]; then
|
||||
exit 0
|
||||
fi
|
||||
mkdir "$STAGING_DIR/extracted"
|
||||
# Budget all extracted components before productive writer shutdown/mutation.
|
||||
size=$(helper expanded "$INPUT")
|
||||
ensure_space "$RESTORE_ROOT" "$size"
|
||||
# Each component gets an independent extraction root, so links in one component
|
||||
# cannot redirect extraction of a later component into another tree.
|
||||
while IFS=$'\t' read -r label file _; do
|
||||
|
||||
Reference in new issue
Block a user