-
Notifications
You must be signed in to change notification settings - Fork 0
139 lines (121 loc) · 5.18 KB
/
Copy pathbackup-redis-snapshot.yml
File metadata and controls
139 lines (121 loc) · 5.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
name: Backup Redis Snapshot
on:
# Production Redis lives on HOSTUP. Automated backups must run from HOSTUP
# where the internal Redis/R2 credentials exist; GitHub has repeatedly failed
# this before reaching the backup step. Keep manual dispatch only.
workflow_dispatch:
jobs:
backup-redis:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Validate required backup configuration
env:
REDIS_URL: ${{ secrets.REDIS_URL }}
R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
R2_BUCKET: ${{ secrets.R2_BUCKET }}
run: |
set -euo pipefail
test -n "${REDIS_URL:-}" || { echo 'Missing secret: REDIS_URL'; exit 1; }
test -n "${R2_ACCOUNT_ID:-}" || { echo 'Missing secret: R2_ACCOUNT_ID'; exit 1; }
test -n "${R2_ACCESS_KEY_ID:-}" || { echo 'Missing secret: R2_ACCESS_KEY_ID'; exit 1; }
test -n "${R2_SECRET_ACCESS_KEY:-}" || { echo 'Missing secret: R2_SECRET_ACCESS_KEY'; exit 1; }
test -n "${R2_BUCKET:-}" || { echo 'Missing secret: R2_BUCKET'; exit 1; }
- name: Install backup dependencies
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install -y redis-tools
- name: Export Redis snapshot (BGSAVE if allowed, fallback to RDB export)
env:
REDIS_URL: ${{ secrets.REDIS_URL }}
run: |
set -euo pipefail
TS="$(date -u +%Y%m%dT%H%M%SZ)"
FILE="redis-${TS}.rdb"
echo "BACKUP_FILE=$FILE" >> "$GITHUB_ENV"
if redis-cli -u "$REDIS_URL" BGSAVE; then
echo "BGSAVE accepted by Redis server."
else
echo "BGSAVE not allowed; falling back to direct redis-cli --rdb export."
fi
redis-cli -u "$REDIS_URL" --rdb "$FILE"
test -s "$FILE" || { echo 'RDB export produced empty file'; exit 1; }
sha256sum "$FILE" | tee "${FILE}.sha256"
ls -lh "$FILE"
- name: Upload snapshot to Cloudflare R2
env:
AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: auto
R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
R2_BUCKET: ${{ secrets.R2_BUCKET }}
R2_PREFIX: ${{ secrets.R2_PREFIX }}
run: |
set -euo pipefail
PREFIX="${R2_PREFIX:-redis-daily}"
PREFIX="${PREFIX#/}"
PREFIX="${PREFIX%/}"
ENDPOINT_URL="https://${R2_ACCOUNT_ID}.r2.cloudflarestorage.com"
DEST="s3://${R2_BUCKET}/${PREFIX}/${BACKUP_FILE}"
DEST_SHA="${DEST}.sha256"
aws --endpoint-url "$ENDPOINT_URL" s3 cp "$BACKUP_FILE" "$DEST" --only-show-errors
aws --endpoint-url "$ENDPOINT_URL" s3 cp "${BACKUP_FILE}.sha256" "$DEST_SHA" --only-show-errors
echo "Uploaded: $DEST"
- name: Enforce 30-day retention
env:
AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: auto
R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
R2_BUCKET: ${{ secrets.R2_BUCKET }}
R2_PREFIX: ${{ secrets.R2_PREFIX }}
run: |
set -euo pipefail
PREFIX="${R2_PREFIX:-redis-daily}"
PREFIX="${PREFIX#/}"
PREFIX="${PREFIX%/}"
ENDPOINT_URL="https://${R2_ACCOUNT_ID}.r2.cloudflarestorage.com"
export ENDPOINT_URL
aws --endpoint-url "$ENDPOINT_URL" s3api list-objects-v2 \
--bucket "$R2_BUCKET" \
--prefix "${PREFIX}/" \
> objects.json
python - <<'PY'
import datetime
import json
import os
import subprocess
bucket = os.environ["R2_BUCKET"]
cutoff = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(days=30)
with open("objects.json", "r", encoding="utf-8") as f:
payload = json.load(f)
endpoint = os.environ.get("ENDPOINT_URL", "").strip()
for obj in payload.get("Contents", []):
key = obj["Key"]
last_modified = datetime.datetime.fromisoformat(obj["LastModified"].replace("Z", "+00:00"))
if last_modified >= cutoff:
continue
cmd = ["aws"]
if endpoint:
cmd.extend(["--endpoint-url", endpoint])
cmd.extend([
"s3api", "delete-object",
"--bucket", bucket,
"--key", key,
])
subprocess.run(cmd, check=True)
print(f"Deleted expired backup: {key}")
PY
- name: Upload run artifact metadata
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: redis-backup-metadata-${{ github.run_id }}
path: |
*.sha256
objects.json
if-no-files-found: ignore