Something went wrong. Try again.
Periodic backups of ATProto repos and blobs safekeeper.dummy.cafe
atproto backup
Something went wrong. Try again.
2.7 kB · 80 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081// Retention policy for repo CAR snapshots.//// Rule: keep every non-pruned snapshot taken in the last 7 days. For older// snapshots, bucket by ISO week and keep 2 per bucket (the earliest and latest// taken_at in that week). Pruning is *soft* — the CAR file is removed from// storage and the row's `pruned_at` is stamped; the row itself is preserved// so the dashboard can show a full history.
import { and, eq, isNull } from 'drizzle-orm';import db from '../database';import { repo_backup } from '../database/schema';import { storage } from '../storage';
const DAY = 24 * 60 * 60 * 1000;const WEEK = 7 * DAY;
// ISO week bucket key — YYYY-Www — computed in UTC. Same week = same bucket.function isoWeekKey(d: Date): string { const dt = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate())); // Shift to Thursday of the current week (ISO weeks are Monday-starting and // the week containing the year's first Thursday is week 1). const day = dt.getUTCDay() || 7; dt.setUTCDate(dt.getUTCDate() + 4 - day); const yearStart = new Date(Date.UTC(dt.getUTCFullYear(), 0, 1)); const weekNo = Math.ceil(((dt.getTime() - yearStart.getTime()) / DAY + 1) / 7); return `${dt.getUTCFullYear()}-W${String(weekNo).padStart(2, '0')}`;}
export async function pruneAccountBackups( did: string, now = new Date()): Promise<{ prunedCount: number; freedBytes: number }> { const live = await db .select() .from(repo_backup) .where(and(eq(repo_backup.account_did, did), isNull(repo_backup.pruned_at)));
const cutoff = now.getTime() - WEEK; const recent: typeof live = []; const old: typeof live = []; for (const row of live) { if (row.taken_at.getTime() >= cutoff) recent.push(row); else old.push(row); }
// Bucket old snapshots by ISO week, keep first + last by taken_at. const byWeek = new Map<string, typeof live>(); for (const row of old) { const key = isoWeekKey(row.taken_at); const bucket = byWeek.get(key); if (bucket) bucket.push(row); else byWeek.set(key, [row]); } const keepIds = new Set<number>(recent.map((r) => r.id)); for (const bucket of byWeek.values()) { bucket.sort((a, b) => a.taken_at.getTime() - b.taken_at.getTime()); keepIds.add(bucket[0]!.id); keepIds.add(bucket[bucket.length - 1]!.id); }
const s = storage(); let freedBytes = 0; let prunedCount = 0; for (const row of live) { if (keepIds.has(row.id)) continue; try { await s.delete(row.storage_path); } catch { // best-effort: if file is already gone, proceed. } await db .update(repo_backup) .set({ pruned_at: now }) .where(eq(repo_backup.id, row.id)); freedBytes += row.size_bytes; prunedCount += 1; }
return { prunedCount, freedBytes };}