From 569d3ec526dcd4ca7c18baf46e48402744c3092e Mon Sep 17 00:00:00 2001 From: Edouard Vanbelle Date: Wed, 2 Sep 2026 20:03:41 +0200 Subject: [PATCH] chore: add 2 tools to backup and restore DB --- scripts/backup.sh | 5 +++ scripts/restore.sh | 88 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100755 scripts/backup.sh create mode 100755 scripts/restore.sh diff --git a/scripts/backup.sh b/scripts/backup.sh new file mode 100755 index 00000000..a82a9de0 --- /dev/null +++ b/scripts/backup.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +NOW="$(date '+%Y-%m-%d %H:%M:%S')" + +pg_dump postgres://postgres:postgres@localhost:5432/oxicloud -F c --disable-triggers > "backup.${NOW}.dump" diff --git a/scripts/restore.sh b/scripts/restore.sh new file mode 100755 index 00000000..6915c744 --- /dev/null +++ b/scripts/restore.sh @@ -0,0 +1,88 @@ +#!/bin/bash +# scripts/restore.sh — restore a pg_dump custom-format snapshot into a +# fresh oxicloud DB. +# +# Drops + recreates the whole DB before restoring so migrations added +# AFTER the dump was taken don't block --clean's DROPs. Symptom of +# that class (hit 2026-08-30): +# +# pg_restore: erreur : ... +# cannot drop constraint files_pkey on table storage.files because +# other objects depend on it +# DÉTAIL : constraint file_attached_blobs_file_id_fkey on table +# storage.file_attached_blobs depends on index storage.files_pkey +# +# The dump only knows about objects that existed at dump time; +# `pg_restore --clean` DROPs exactly those. Anything added since +# (`file_attached_blobs` in the example above) survives and blocks +# DROPs of things it depends on. Drop-and-recreate the whole DB +# sidesteps the problem entirely. +# +# Usage: +# ./scripts/restore.sh +# +# Companion of the pg_dump command in memory +# bug_pg_dump_folders_circular_fk.md: +# pg_dump postgres://... -F c --disable-triggers > backup.$NOW.dump +# +# NB: this restores the DB ONLY. Blob storage on disk +# (${OXICLOUD_STORAGE_PATH}) is NOT touched — snapshot + restore that +# separately with rsync if you need lockstep DB/disk state. + +set -euo pipefail + +DUMP="${1:?usage: $0 }" +[[ -f "$DUMP" ]] || { echo "[restore] ERROR: dump file not found: $DUMP" >&2; exit 1; } + +# Admin connection — connect to the `postgres` maintenance DB so we can +# drop `oxicloud` itself (can't drop the DB you're connected to). Both +# connection strings share credentials from the sandbox setup. +ADMIN="postgres://postgres:postgres@localhost:5432/postgres" +TARGET="postgres://postgres:postgres@localhost:5432/oxicloud" + +# 1. Terminate every connection to `oxicloud` so DROP DATABASE can +# proceed. OxiCloud running against 5432? rust-analyzer with +# sqlx-cli open? Any lingering psql session? All of them block +# `DROP DATABASE` with "database is being accessed by other users". +# pg_terminate_backend kicks them cleanly (they'll reconnect if +# they retry). +echo "[restore] Terminating connections to oxicloud..." +psql "$ADMIN" -c " + SELECT pg_terminate_backend(pid) + FROM pg_stat_activity + WHERE datname = 'oxicloud' + AND pid <> pg_backend_pid(); +" >/dev/null + +# 2. Drop + recreate. IF EXISTS on DROP so a fresh workstation without +# an existing `oxicloud` database doesn't error on the first-ever +# invocation. +echo "[restore] Dropping + recreating oxicloud database..." +psql "$ADMIN" <<'SQL' +DROP DATABASE IF EXISTS oxicloud; +CREATE DATABASE oxicloud OWNER postgres; +SQL + +# 3. Restore. `--clean --if-exists` no longer needed (fresh DB from +# step 2). The remaining flags: +# +# * --disable-triggers — turn triggers off during data load so the +# `storage.folders.parent_id` self-FK doesn't reject rows whose +# parent hasn't been inserted yet in the same COPY batch. See +# memory bug_pg_dump_folders_circular_fk for background. +# * --single-transaction — atomic restore (all-or-nothing) AND +# lets --disable-triggers work without superuser (superuser is +# required otherwise). +# * --no-owner --no-privileges — portable, ignores ownership / +# GRANTs from the source system so a dump taken from one machine +# restores cleanly on another with different user names. +echo "[restore] Restoring from $DUMP..." +pg_restore \ + --disable-triggers \ + --single-transaction \ + --no-owner \ + --no-privileges \ + -d "$TARGET" \ + "$DUMP" + +echo "[restore] Done. Database restored to snapshot state in $DUMP."