Release v1.0.0
Some checks failed
CI / Migrations, tests, build, and audit (push) Failing after 10m7s
CI / Production gate and container images (push) Successful in 20m35s

This commit is contained in:
2026-07-31 14:21:43 +08:00
commit 36f466ba11
187 changed files with 98907 additions and 0 deletions

98
scripts/verify-backup.sh Executable file
View File

@@ -0,0 +1,98 @@
#!/usr/bin/env bash
set -Eeuo pipefail
usage() {
echo "Usage: $0 <frameflow-backup-directory>" >&2
exit 2
}
fail() {
echo "Backup verification failed: $1" >&2
exit 1
}
[[ $# -eq 1 ]] || usage
[[ -d "$1" ]] || fail "directory not found: $1"
[[ ! -L "$1" ]] || fail "backup directory must not be a symbolic link"
backup_dir="$(cd "$1" && pwd -P)"
required_files=(postgres.dump redis-data.tar.gz minio-data.tar.gz manifest.txt SHA256SUMS)
for filename in "${required_files[@]}"; do
path="${backup_dir}/${filename}"
[[ -f "${path}" ]] || fail "missing ${filename}"
[[ ! -L "${path}" ]] || fail "${filename} must not be a symbolic link"
[[ -s "${path}" ]] || fail "${filename} is empty"
done
created_at="$(sed -n 's/^created_at=//p' "${backup_dir}/manifest.txt")"
compose_project="$(sed -n 's/^compose_project=//p' "${backup_dir}/manifest.txt")"
[[ "${created_at}" =~ ^[0-9]{8}T[0-9]{6}Z$ ]] || fail "manifest has an invalid created_at value"
[[ "${compose_project}" =~ ^[a-zA-Z0-9][a-zA-Z0-9_.-]*$ ]] || fail "manifest has an invalid compose_project value"
postgres_seen=0
redis_seen=0
minio_seen=0
checksum_rows=0
while read -r digest filename extra; do
[[ -z "${extra:-}" ]] || fail "SHA256SUMS contains an invalid row"
[[ "${digest}" =~ ^[[:xdigit:]]{64}$ ]] || fail "SHA256SUMS contains an invalid digest"
filename="${filename#\*}"
case "${filename}" in
postgres.dump) postgres_seen=$((postgres_seen + 1)) ;;
redis-data.tar.gz) redis_seen=$((redis_seen + 1)) ;;
minio-data.tar.gz) minio_seen=$((minio_seen + 1)) ;;
*) fail "SHA256SUMS references an unexpected file: ${filename}" ;;
esac
checksum_rows=$((checksum_rows + 1))
done < "${backup_dir}/SHA256SUMS"
[[ ${checksum_rows} -eq 3 && ${postgres_seen} -eq 1 && ${redis_seen} -eq 1 && ${minio_seen} -eq 1 ]] \
|| fail "SHA256SUMS must reference each data artifact exactly once"
if command -v sha256sum >/dev/null 2>&1; then
(cd "${backup_dir}" && sha256sum -c SHA256SUMS) >/dev/null \
|| fail "artifact checksum mismatch"
else
(cd "${backup_dir}" && shasum -a 256 -c SHA256SUMS) >/dev/null \
|| fail "artifact checksum mismatch"
fi
postgres_magic="$(LC_ALL=C head -c 5 "${backup_dir}/postgres.dump")"
[[ "${postgres_magic}" == "PGDMP" ]] || fail "PostgreSQL dump does not use the required custom format"
verify_archive_paths() {
local archive_name="$1"
local listing_file
local entry
local normalized
listing_file="$(mktemp "${TMPDIR:-/tmp}/frameflow-archive-list.XXXXXX")"
if ! tar -tzf "${backup_dir}/${archive_name}" > "${listing_file}"; then
rm -f "${listing_file}"
fail "${archive_name} is not a readable gzip tar archive"
fi
if [[ ! -s "${listing_file}" ]]; then
rm -f "${listing_file}"
fail "${archive_name} contains no entries"
fi
while IFS= read -r entry; do
[[ "${entry}" != /* ]] || {
rm -f "${listing_file}"
fail "${archive_name} contains an absolute path"
}
normalized="${entry#./}"
case "/${normalized}/" in
*"/../"*)
rm -f "${listing_file}"
fail "${archive_name} contains a parent-directory path"
;;
esac
done < "${listing_file}"
rm -f "${listing_file}"
}
verify_archive_paths redis-data.tar.gz
verify_archive_paths minio-data.tar.gz
echo "Backup verification passed: ${backup_dir}"
echo "Source Compose project: ${compose_project}; created at: ${created_at}"