- R2 백업 스크립트 (Raft 스냅샷 + fallback) - 경로 기반 백업 스크립트 - 환경변수 템플릿 - README 문서 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
176 lines
5.1 KiB
Bash
Executable File
176 lines
5.1 KiB
Bash
Executable File
#!/bin/bash
|
|
# Vault Snapshot Backup to Cloudflare R2
|
|
# Usage: ./vault-backup-r2.sh [snapshot-name]
|
|
|
|
set -euo pipefail
|
|
|
|
# ============================================
|
|
# Configuration
|
|
# ============================================
|
|
VAULT_ADDR="${VAULT_ADDR:-https://vault.anvil.it.com}"
|
|
VAULT_TOKEN="${VAULT_TOKEN:?VAULT_TOKEN is required}"
|
|
|
|
# R2 Configuration
|
|
R2_ACCOUNT_ID="${R2_ACCOUNT_ID:?R2_ACCOUNT_ID is required}"
|
|
R2_ACCESS_KEY="${R2_ACCESS_KEY:?R2_ACCESS_KEY is required}"
|
|
R2_SECRET_KEY="${R2_SECRET_KEY:?R2_SECRET_KEY is required}"
|
|
R2_BUCKET="${R2_BUCKET:-vault-backup}"
|
|
R2_ENDPOINT="https://${R2_ACCOUNT_ID}.r2.cloudflarestorage.com"
|
|
|
|
# Backup settings
|
|
BACKUP_DIR="${BACKUP_DIR:-/tmp/vault-backups}"
|
|
RETENTION_DAYS="${RETENTION_DAYS:-30}"
|
|
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
|
|
SNAPSHOT_NAME="${1:-vault-snapshot-${TIMESTAMP}}"
|
|
|
|
# ============================================
|
|
# Functions
|
|
# ============================================
|
|
|
|
log() {
|
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
|
|
}
|
|
|
|
error() {
|
|
log "ERROR: $*" >&2
|
|
exit 1
|
|
}
|
|
|
|
check_dependencies() {
|
|
for cmd in curl aws jq; do
|
|
if ! command -v "$cmd" &>/dev/null; then
|
|
error "$cmd is required but not installed"
|
|
fi
|
|
done
|
|
}
|
|
|
|
create_snapshot() {
|
|
log "Creating Vault snapshot..."
|
|
|
|
mkdir -p "$BACKUP_DIR"
|
|
local snapshot_file="${BACKUP_DIR}/${SNAPSHOT_NAME}.snap"
|
|
|
|
# Vault Raft snapshot (requires Raft storage backend)
|
|
curl -s -X POST \
|
|
-H "X-Vault-Token: ${VAULT_TOKEN}" \
|
|
"${VAULT_ADDR}/v1/sys/storage/raft/snapshot" \
|
|
-o "$snapshot_file" \
|
|
--fail || {
|
|
# Fallback: Export secrets as JSON (for non-Raft backends)
|
|
log "Raft snapshot failed, trying secrets export..."
|
|
export_secrets "$snapshot_file"
|
|
}
|
|
|
|
echo "$snapshot_file"
|
|
}
|
|
|
|
export_secrets() {
|
|
local output_file="$1"
|
|
local secrets_file="${output_file%.snap}.json"
|
|
|
|
log "Exporting secrets to JSON..."
|
|
|
|
# Get list of secret paths (requires list permission)
|
|
local paths=$(curl -s -X LIST \
|
|
-H "X-Vault-Token: ${VAULT_TOKEN}" \
|
|
"${VAULT_ADDR}/v1/secret/metadata" | jq -r '.data.keys[]?' 2>/dev/null || echo "")
|
|
|
|
if [[ -z "$paths" ]]; then
|
|
log "No secrets found or no list permission"
|
|
echo '{"secrets": [], "timestamp": "'$(date -Iseconds)'", "note": "empty or no permission"}' > "$secrets_file"
|
|
else
|
|
echo '{"secrets": [' > "$secrets_file"
|
|
local first=true
|
|
|
|
for path in $paths; do
|
|
local secret=$(curl -s \
|
|
-H "X-Vault-Token: ${VAULT_TOKEN}" \
|
|
"${VAULT_ADDR}/v1/secret/data/${path}" | jq '.data' 2>/dev/null)
|
|
|
|
if [[ -n "$secret" && "$secret" != "null" ]]; then
|
|
if [[ "$first" == "true" ]]; then
|
|
first=false
|
|
else
|
|
echo "," >> "$secrets_file"
|
|
fi
|
|
echo "{\"path\": \"${path}\", \"data\": ${secret}}" >> "$secrets_file"
|
|
fi
|
|
done
|
|
|
|
echo '], "timestamp": "'$(date -Iseconds)'"}' >> "$secrets_file"
|
|
fi
|
|
|
|
# Compress and encrypt (optional)
|
|
gzip -c "$secrets_file" > "$output_file"
|
|
rm -f "$secrets_file"
|
|
}
|
|
|
|
upload_to_r2() {
|
|
local file="$1"
|
|
local filename=$(basename "$file")
|
|
|
|
log "Uploading to R2: ${R2_BUCKET}/${filename}"
|
|
|
|
# Configure AWS CLI for R2
|
|
export AWS_ACCESS_KEY_ID="$R2_ACCESS_KEY"
|
|
export AWS_SECRET_ACCESS_KEY="$R2_SECRET_KEY"
|
|
|
|
aws s3 cp "$file" "s3://${R2_BUCKET}/${filename}" \
|
|
--endpoint-url "$R2_ENDPOINT" \
|
|
--quiet
|
|
|
|
log "Upload complete: s3://${R2_BUCKET}/${filename}"
|
|
}
|
|
|
|
cleanup_old_backups() {
|
|
log "Cleaning up backups older than ${RETENTION_DAYS} days..."
|
|
|
|
export AWS_ACCESS_KEY_ID="$R2_ACCESS_KEY"
|
|
export AWS_SECRET_ACCESS_KEY="$R2_SECRET_KEY"
|
|
|
|
local cutoff_date=$(date -v-${RETENTION_DAYS}d +%Y-%m-%d 2>/dev/null || date -d "${RETENTION_DAYS} days ago" +%Y-%m-%d)
|
|
|
|
aws s3 ls "s3://${R2_BUCKET}/" --endpoint-url "$R2_ENDPOINT" 2>/dev/null | \
|
|
while read -r line; do
|
|
local file_date=$(echo "$line" | awk '{print $1}')
|
|
local file_name=$(echo "$line" | awk '{print $4}')
|
|
|
|
if [[ "$file_date" < "$cutoff_date" && -n "$file_name" ]]; then
|
|
log "Deleting old backup: $file_name"
|
|
aws s3 rm "s3://${R2_BUCKET}/${file_name}" --endpoint-url "$R2_ENDPOINT" --quiet
|
|
fi
|
|
done
|
|
}
|
|
|
|
cleanup_local() {
|
|
log "Cleaning up local backup files..."
|
|
find "$BACKUP_DIR" -name "vault-snapshot-*.snap" -mtime +1 -delete 2>/dev/null || true
|
|
find "$BACKUP_DIR" -name "vault-snapshot-*.gz" -mtime +1 -delete 2>/dev/null || true
|
|
}
|
|
|
|
# ============================================
|
|
# Main
|
|
# ============================================
|
|
|
|
main() {
|
|
log "=== Vault Backup to R2 Started ==="
|
|
log "Vault: $VAULT_ADDR"
|
|
log "R2 Bucket: $R2_BUCKET"
|
|
|
|
check_dependencies
|
|
|
|
local snapshot_file
|
|
snapshot_file=$(create_snapshot)
|
|
|
|
if [[ -f "$snapshot_file" ]]; then
|
|
upload_to_r2 "$snapshot_file"
|
|
cleanup_old_backups
|
|
cleanup_local
|
|
log "=== Backup Complete ==="
|
|
else
|
|
error "Snapshot file not created"
|
|
fi
|
|
}
|
|
|
|
main "$@"
|