Skip to content

Commit 475f0af

Browse files
committed
Add supabase backup VM to Firebase storage and discord error hook
1 parent 206fa07 commit 475f0af

3 files changed

Lines changed: 269 additions & 0 deletions

File tree

‎backups/supabase/main.tf‎

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
locals {
2+
project = "compass-130ba"
3+
region = "us-west1"
4+
zone = "us-west1-b"
5+
service_name = "backup"
6+
machine_type = "e2-micro"
7+
}
8+
9+
variable "env" {
10+
description = "Environment (env or prod)"
11+
type = string
12+
default = "prod"
13+
}
14+
15+
provider "google" {
16+
project = local.project
17+
region = local.region
18+
zone = local.zone
19+
}
20+
21+
# Service account for the VM (needs Secret Manager + Storage access)
22+
resource "google_service_account" "backup_vm_sa" {
23+
account_id = "backup-vm-sa"
24+
display_name = "Backup VM Service Account"
25+
}
26+
27+
# IAM roles
28+
resource "google_project_iam_member" "backup_sa_secret_manager" {
29+
project = "compass-130ba"
30+
role = "roles/secretmanager.secretAccessor"
31+
member = "serviceAccount:${google_service_account.backup_vm_sa.email}"
32+
}
33+
34+
resource "google_project_iam_member" "backup_sa_storage_admin" {
35+
project = "compass-130ba"
36+
role = "roles/storage.objectAdmin"
37+
member = "serviceAccount:${google_service_account.backup_vm_sa.email}"
38+
}
39+
40+
# Minimal VM
41+
resource "google_compute_instance" "backup_vm" {
42+
name = "supabase-backup-vm"
43+
machine_type = local.machine_type
44+
zone = local.zone
45+
46+
boot_disk {
47+
initialize_params {
48+
image = "debian-11-bullseye-v20250915"
49+
size = 20
50+
}
51+
}
52+
53+
network_interface {
54+
network = "default"
55+
access_config {}
56+
}
57+
58+
service_account {
59+
email = google_service_account.backup_vm_sa.email
60+
scopes = ["https://www.googleapis.com/auth/cloud-platform"]
61+
}
62+
63+
metadata_startup_script = <<-EOT
64+
#!/bin/bash
65+
apt-get update
66+
apt-get install -y postgresql-client cron wget curl unzip
67+
68+
# Add PostgreSQL repo
69+
sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
70+
wget -qO - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
71+
72+
sudo apt-get update
73+
sudo apt-get install -y postgresql-client-17
74+
sudo apt-get install -y mailutils
75+
76+
# Create backup directory
77+
mkdir -p /home/martin/supabase_backups
78+
chown -R martin:martin /home/martin
79+
80+
# Example backup script
81+
cat <<'EOF' > /home/martin/backup.sh
82+
#!/bin/bash
83+
84+
# Backup Supabase database and upload to Google Cloud Storage daily, retaining backups for 30 days.
85+
86+
set -e
87+
88+
cd $(dirname "$0")
89+
90+
export ENV=prod
91+
92+
if [ "$ENV" = "prod" ]; then
93+
export PGHOST="aws-1-us-west-1.pooler.supabase.com"
94+
elif [ "$ENV" = "dev" ]; then
95+
export PGHOST="db.zbspxezubpzxmuxciurg.supabase.co"
96+
else
97+
echo "Error: ENV must be 'prod' or 'dev'" >&2
98+
exit 1
99+
fi
100+
101+
# Config
102+
PGPORT="5432"
103+
PGUSER="postgres.ltzepxnhhnrnvovqblfr"
104+
PGDATABASE="postgres"
105+
106+
# Retrieve password from Secret Manager
107+
PGPASSWORD=$(gcloud secrets versions access latest --secret="SUPABASE_DB_PASSWORD")
108+
109+
BUCKET_NAME="gs://compass-130ba.firebasestorage.app/backups/supabase"
110+
BACKUP_DIR="/tmp/supabase_backups"
111+
RETENTION_DAYS=30
112+
113+
mkdir -p "$BACKUP_DIR"
114+
115+
TIMESTAMP=$(date +"%F_%H-%M-%S")
116+
BACKUP_FILE="$BACKUP_DIR/$TIMESTAMP.sql"
117+
118+
export PGPASSWORD
119+
pg_dump -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d "$PGDATABASE" -F c -b -v -f "$BACKUP_FILE"
120+
121+
if [ $? -ne 0 ]; then
122+
echo "Backup failed!"
123+
exit 1
124+
fi
125+
126+
echo "Backup successful: $BACKUP_FILE"
127+
128+
# UPLOAD TO GCS
129+
echo "Uploading backup to GCS..."
130+
gsutil cp "$BACKUP_FILE" "$BUCKET_NAME/"
131+
132+
# LOCAL RETENTION
133+
LOCAL_RETENTION_DAYS=7
134+
echo "Removing local backups older than $LOCAL_RETENTION_DAYS days..."
135+
find "$BACKUP_DIR" -type f -mtime +$LOCAL_RETENTION_DAYS -delete
136+
137+
# GCS RETENTION
138+
echo "Cleaning old backups from GCS..."
139+
gsutil ls "$BUCKET_NAME/" | while read file; do
140+
filename=$(basename "$file")
141+
# Extract timestamp from filename
142+
file_date=$(echo "$filename" | sed -E 's/(.*)\.sql/\1/')
143+
# Convert to seconds since epoch
144+
file_date="2025-09-24_13-00-54"
145+
date_part=${file_date%_*} # "2025-09-24"
146+
time_part=${file_date#*_} # "13-00-54"
147+
time_part=${time_part//-/:} # "13:00:54"
148+
file_ts=$(date -d "$date_part $time_part" +%s)
149+
# echo "$file, $filename, $file_date, $file_ts"
150+
if [ -z "$file_ts" ]; then
151+
continue
152+
fi
153+
now=$(date +%s)
154+
diff_days=$(( (now - file_ts) / 86400 ))
155+
echo "File: $filename is $diff_days days old."
156+
if [ "$diff_days" -gt "$RETENTION_DAYS" ]; then
157+
echo "Deleting $file from GCS..."
158+
gsutil rm "$file"
159+
fi
160+
done
161+
162+
echo "Backup and retention process completed at $(date)."
163+
EOF
164+
165+
chmod +x /home/martin/backup.sh
166+
167+
# Add cron job: daily at 2AM
168+
( crontab -l 2>/dev/null; echo '0 2 * * * /home/martin/backup.sh >> /home/martin/backup.log 2>&1 || curl -H "Content-Type: application/json" -X POST -d "{\"content\": \"❌ Backup FAILED on $(hostname) at $(date)\"}" https://discord.com/api/webhooks/1420405275340574873/XgF5pgHABvvWT2fyWASBs3VhAF7Zy11rCH2BkI_RBxH1Xd5duWxGtukrc1cPy1ZucNwx' ) | crontab -
169+
# tail -f /home/martin/backup.log
170+
}

‎backups/supabase/ssh.sh‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/bin/bash
2+
3+
set -e
4+
5+
cd $(dirname "$0")
6+
7+
#gcloud compute firewall-rules create allow-iap-ssh \
8+
# --direction=INGRESS \
9+
# --action=ALLOW \
10+
# --rules=tcp:22 \
11+
# --source-ranges=35.235.240.0/20 \
12+
# --target-tags=iap-ssh
13+
# gcloud compute instances add-tags "supabase-backup-vm" --tags=iap-ssh --zone="us-west1-b"
14+
15+
16+
gcloud compute ssh --zone "us-west1-b" "supabase-backup-vm" --project "compass-130ba" --tunnel-through-iap
17+
18+
# sudo crontab -u backup -l
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#!/bin/bash
2+
3+
# Backup Supabase database and upload to Google Cloud Storage daily, retaining backups for 30 days.
4+
5+
set -e
6+
7+
cd $(dirname "$0")
8+
9+
export ENV=prod
10+
11+
if [ "$ENV" = "prod" ]; then
12+
export PGHOST="aws-1-us-west-1.pooler.supabase.com"
13+
elif [ "$ENV" = "dev" ]; then
14+
export PGHOST="db.zbspxezubpzxmuxciurg.supabase.co"
15+
else
16+
echo "Error: ENV must be 'prod' or 'dev'" >&2
17+
exit 1
18+
fi
19+
20+
# Config
21+
PGPORT="5432"
22+
PGUSER="postgres.ltzepxnhhnrnvovqblfr"
23+
PGDATABASE="postgres"
24+
25+
# Retrieve password from Secret Manager
26+
PGPASSWORD=$(gcloud secrets versions access latest --secret="SUPABASE_DB_PASSWORD")
27+
28+
BUCKET_NAME="gs://compass-130ba.firebasestorage.app/backups/supabase"
29+
BACKUP_DIR="/tmp/supabase_backups"
30+
RETENTION_DAYS=30
31+
32+
mkdir -p "$BACKUP_DIR"
33+
34+
TIMESTAMP=$(date +"%F_%H-%M-%S")
35+
BACKUP_FILE="$BACKUP_DIR/$TIMESTAMP.sql"
36+
37+
export PGPASSWORD
38+
pg_dump -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d "$PGDATABASE" -F c -b -v -f "$BACKUP_FILE"
39+
40+
if [ $? -ne 0 ]; then
41+
echo "Backup failed!"
42+
exit 1
43+
fi
44+
45+
echo "Backup successful: $BACKUP_FILE"
46+
47+
# UPLOAD TO GCS
48+
echo "Uploading backup to GCS..."
49+
gsutil cp "$BACKUP_FILE" "$BUCKET_NAME/"
50+
51+
# LOCAL RETENTION
52+
LOCAL_RETENTION_DAYS=7
53+
echo "Removing local backups older than $LOCAL_RETENTION_DAYS days..."
54+
find "$BACKUP_DIR" -type f -mtime +$LOCAL_RETENTION_DAYS -delete
55+
56+
# GCS RETENTION
57+
echo "Cleaning old backups from GCS..."
58+
gsutil ls "$BUCKET_NAME/" | while read file; do
59+
filename=$(basename "$file")
60+
# Extract timestamp from filename
61+
file_date=$(echo "$filename" | sed -E 's/(.*)\.sql/\1/')
62+
# Convert to seconds since epoch
63+
file_date="2025-09-24_13-00-54"
64+
date_part=${file_date%_*} # "2025-09-24"
65+
time_part=${file_date#*_} # "13-00-54"
66+
time_part=${time_part//-/:} # "13:00:54"
67+
file_ts=$(date -d "$date_part $time_part" +%s)
68+
# echo "$file, $filename, $file_date, $file_ts"
69+
if [ -z "$file_ts" ]; then
70+
continue
71+
fi
72+
now=$(date +%s)
73+
diff_days=$(( (now - file_ts) / 86400 ))
74+
echo "File: $filename is $diff_days days old."
75+
if [ "$diff_days" -gt "$RETENTION_DAYS" ]; then
76+
echo "Deleting $file from GCS..."
77+
gsutil rm "$file"
78+
fi
79+
done
80+
81+
echo "Backup and retention process completed at $(date)."

0 commit comments

Comments
 (0)