Files
story/deploy/server/sync-backup-offsite.sh
T

129 lines
4.8 KiB
Bash
Executable File

#!/usr/bin/env bash
set -Eeuo pipefail
backup_root=${BACKUP_ROOT:-/var/backups/data-terminal-cloud}
state_dir=${OFFSITE_STATE_DIR:-/var/lib/data-terminal-cloud-offsite}
offsite_config_file=${OFFSITE_CONFIG_FILE:-/etc/data-terminal-cloud/offsite-backup.env}
ossutil_bin=${OSSUTIL_BIN:-/usr/local/bin/ossutil}
mkdir -p "$state_dir"
chmod 0750 "$state_dir"
umask 077
status_file="$state_dir/status.json"
status_tmp=$(mktemp "$state_dir/.status-XXXXXX")
transfer_dir=$(mktemp -d "$state_dir/.transfer-XXXXXX")
cleanup() {
rm -f -- "$status_tmp"
rm -rf -- "$transfer_dir"
}
trap cleanup EXIT INT TERM
write_status() {
local status=$1
local reason=$2
local source_archive=${3:-}
local object_ref=${4:-}
local sha256=${5:-}
local size_bytes=${6:-0}
local verified_epoch=0
if [[ "$status" == verified ]]; then
verified_epoch=$(date +%s)
fi
python3 - "$status_tmp" "$status" "$reason" "$source_archive" "$object_ref" "$sha256" "$size_bytes" "$verified_epoch" <<'PY'
import datetime
import json
import sys
payload = {
"schema": "platform-offsite-backup-v1",
"status": sys.argv[2],
"reason": sys.argv[3],
"source_archive": sys.argv[4],
"object_ref": sys.argv[5],
"sha256": sys.argv[6],
"size_bytes": int(sys.argv[7]),
"verified_epoch": int(sys.argv[8]),
"observed_at": datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z"),
}
with open(sys.argv[1], "w", encoding="utf-8") as target:
json.dump(payload, target, ensure_ascii=False, separators=(",", ":"))
target.write("\n")
PY
chmod 0644 "$status_tmp"
mv -f "$status_tmp" "$status_file"
}
fail_sync() {
local reason=$1
local code=${2:-1}
write_status failed "$reason" "${source_name:-}" "${remote_archive:-}" "${source_sha:-}" "${source_size:-0}"
echo "offsite_backup status=failed reason=$reason" >&2
exit "$code"
}
if [[ ! -f "$offsite_config_file" ]]; then
write_status disabled config_missing
echo "offsite_backup status=disabled reason=config_missing"
exit 0
fi
# The installer creates this file as root:root 0600.
# shellcheck disable=SC1090
source "$offsite_config_file"
offsite_enabled=${OFFSITE_BACKUP_ENABLED:-false}
bucket=${OFFSITE_OSS_BUCKET:-}
prefix=${OFFSITE_OSS_PREFIX:-data-terminal-cloud}
oss_config=${OFFSITE_OSS_CONFIG_FILE:-/etc/data-terminal-cloud/ossutilconfig}
if [[ "$offsite_enabled" != true ]]; then
write_status disabled not_enabled
echo "offsite_backup status=disabled reason=not_enabled"
exit 0
fi
[[ "$bucket" =~ ^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$ ]] || fail_sync invalid_bucket 2
[[ "$prefix" =~ ^[A-Za-z0-9._/-]+$ && "$prefix" != /* && "$prefix" != *..* ]] || fail_sync invalid_prefix 2
[[ -x "$ossutil_bin" ]] || fail_sync ossutil_missing 3
[[ -r "$oss_config" ]] || fail_sync oss_config_missing 4
latest_backup=$(find "$backup_root" -maxdepth 1 -type f -name 'platform-*.tar.gz.enc' -printf '%T@ %p\n' 2>/dev/null | sort -nr | head -n1 | cut -d' ' -f2- || true)
[[ -n "$latest_backup" && -f "$latest_backup" ]] || fail_sync local_backup_missing 5
[[ -f "$latest_backup.sha256" ]] || fail_sync local_checksum_missing 5
if ! (cd "$backup_root" && sha256sum -c "$(basename "$latest_backup").sha256" >/dev/null 2>&1); then
fail_sync local_checksum_failed 5
fi
source_name=$(basename "$latest_backup")
source_sha=$(sha256sum "$latest_backup" | awk '{print $1}')
source_size=$(stat -c %s "$latest_backup")
prefix=${prefix%/}
remote_archive="oss://$bucket/$prefix/$source_name"
remote_checksum="$remote_archive.sha256"
checkpoint_dir="$state_dir/checkpoints"
mkdir -p "$checkpoint_dir"
common=(-f -c "$oss_config" --job 1 --parallel 1 --checkpoint-dir "$checkpoint_dir" --output-format json)
if ! "$ossutil_bin" cp "$latest_backup" "$remote_archive" "${common[@]}" --metadata "sha256=$source_sha" >/dev/null; then
fail_sync archive_upload_failed 6
fi
if ! "$ossutil_bin" cp "$latest_backup.sha256" "$remote_checksum" "${common[@]}" >/dev/null; then
fail_sync checksum_upload_failed 6
fi
downloaded="$transfer_dir/$source_name"
downloaded_checksum="$downloaded.sha256"
if ! "$ossutil_bin" cp "$remote_archive" "$downloaded" "${common[@]}" >/dev/null; then
fail_sync archive_download_verification_failed 7
fi
if ! "$ossutil_bin" cp "$remote_checksum" "$downloaded_checksum" "${common[@]}" >/dev/null; then
fail_sync checksum_download_verification_failed 7
fi
downloaded_sha=$(sha256sum "$downloaded" | awk '{print $1}')
checksum_sha=$(awk 'NF >= 1 {print $1; exit}' "$downloaded_checksum")
if [[ "$downloaded_sha" != "$source_sha" || "$checksum_sha" != "$source_sha" ]]; then
fail_sync remote_checksum_mismatch 8
fi
write_status verified upload_and_download_verified "$source_name" "$remote_archive" "$source_sha" "$source_size"
printf 'offsite_backup status=verified source=%s object=%s size_bytes=%s\n' "$source_name" "$remote_archive" "$source_size"