supersam/scripts/backup.py

126 lines
4.0 KiB
Python

#!/usr/bin/env python3
"""SuperSam daily backup — export public tables to CSV, tar.gz, upload to S3."""
import csv
import io
import os
import sys
import subprocess
import tarfile
import tempfile
from datetime import datetime
import boto3
from botocore.config import Config
# S3 config
S3_ENDPOINT = "https://s3.ru1.storage.beget.cloud"
S3_KEY = "YG4MQNKAPNL65200MBUY"
S3_SECRET = "8mXkFM2VRQ3pN1Nx4mhmJ2jrZoB5YTPUa4CaZh43"
S3_BUCKET = "02f162ff4a18-supersam-s3"
# DB config
DB_HOST = "supabase-db"
DB_USER = "supabase_admin"
DB_NAME = "postgres"
def get_tables():
"""Get list of public tables with data."""
result = subprocess.run(
["docker", "exec", "-i", DB_HOST, "psql", "-U", DB_USER, "-d", DB_NAME,
"-t", "-A", "-c",
"SELECT tablename FROM pg_tables WHERE schemaname='public' ORDER BY tablename;"],
capture_output=True, text=True
)
if result.returncode != 0:
print(f"ERROR getting tables: {result.stderr}", file=sys.stderr)
sys.exit(1)
tables = [t.strip() for t in result.stdout.strip().split("\n") if t.strip()]
return tables
def export_table_csv(table_name, out_dir):
"""Export a single table to CSV via psql \\copy."""
csv_path = os.path.join(out_dir, f"{table_name}.csv")
# Use psql \copy (client-side) to avoid needing superuser for COPY
cmd = [
"docker", "exec", "-i", DB_HOST,
"psql", "-U", DB_USER, "-d", DB_NAME,
"-c", f"\\copy (SELECT * FROM public.{table_name}) TO '/tmp/{table_name}.csv' WITH CSV HEADER;"
]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
print(f"WARN: {table_name} export failed: {result.stderr}", file=sys.stderr)
return False
# Copy from container to host
cmd2 = ["docker", "cp", f"{DB_HOST}:/tmp/{table_name}.csv", csv_path]
result2 = subprocess.run(cmd2, capture_output=True, text=True)
if result2.returncode != 0:
print(f"WARN: {table_name} docker cp failed: {result2.stderr}", file=sys.stderr)
return False
# Clean up temp file in container
subprocess.run(["docker", "exec", "-i", DB_HOST, "rm", "-f", f"/tmp/{table_name}.csv"],
capture_output=True, text=True)
# Check if file has data (more than just header)
with open(csv_path, "r") as f:
lines = f.readlines()
if len(lines) <= 1:
print(f" {table_name}: empty (skipping)")
return False
print(f" {table_name}: {len(lines)-1} rows")
return True
def upload_to_s3(file_path, key):
"""Upload file to S3."""
s3 = boto3.client(
"s3",
endpoint_url=S3_ENDPOINT,
aws_access_key_id=S3_KEY,
aws_secret_access_key=S3_SECRET,
config=Config(signature_version="s3v4"),
region_name="ru-1"
)
s3.upload_file(file_path, S3_BUCKET, key)
print(f" Uploaded: s3://{S3_BUCKET}/{key}")
def main():
date_str = datetime.now().strftime("%Y-%m-%d")
archive_name = f"supersam-backup-{date_str}.tar.gz"
s3_key = f"backups/{archive_name}"
print(f"=== SuperSam Backup {date_str} ===")
with tempfile.TemporaryDirectory() as tmpdir:
tables = get_tables()
print(f"Found {len(tables)} tables")
exported = []
for table in tables:
if export_table_csv(table, tmpdir):
exported.append(table)
if not exported:
print("No tables with data — nothing to backup")
sys.exit(0)
# Create tar.gz
archive_path = os.path.join(tmpdir, archive_name)
with tarfile.open(archive_path, "w:gz") as tar:
for table in exported:
csv_path = os.path.join(tmpdir, f"{table}.csv")
tar.add(csv_path, arcname=f"{table}.csv")
# Get size
size_mb = os.path.getsize(archive_path) / (1024 * 1024)
print(f"Archive: {archive_name} ({size_mb:.2f} MB)")
# Upload
upload_to_s3(archive_path, s3_key)
print(f"=== Backup complete ===")
if __name__ == "__main__":
main()