feat(ops): Sicherungsskripte mit pg_dump fuer Windows und Unix
Die JSON-Sicherung im Adminbereich eignet sich zum Umziehen. Zum Wiederherstellen einer kaputten Instanz braucht es dagegen einen echten Dump - Schema, Daten, Sequenzen, Indizes. backup.ps1 und backup.sh fuehren pg_dump im Container aus, es muss also nichts auf dem Rechner installiert sein. Benutzer und Datenbankname kommen aus der .env, damit sie nicht doppelt gepflegt werden. Der Dump entsteht mit --clean --if-exists und ist damit fuer sich allein einspielbar. restore.* fragt vor dem Ueberschreiben nach und laesst sich ueber -Database beziehungsweise NEBULA_DB_NAME auf eine andere Datenbank umlenken. Damit laesst sich eine Sicherung pruefen, ohne die echte anzufassen - eine Sicherung, die nie zurueckgespielt wurde, ist keine. /backups liegt im .gitignore: die Dateien enthalten alle Daten der Instanz. Geprueft am echten Bestand: Dump 30,9 KB mit allen neun Tabellen, Rueckspielung in eine Pruefdatenbank ergab dieselben Zahlen, das Original blieb unveraendert. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -43,6 +43,9 @@ yarn-error.log*
|
||||
/data
|
||||
/postgres-data
|
||||
|
||||
# Sicherungen aus scripts/backup.* – enthalten alle Daten der Instanz
|
||||
/backups
|
||||
|
||||
# editors
|
||||
.idea/
|
||||
.vscode/*
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Byteweise Sicherung der Nebula-Datenbank mit pg_dump.
|
||||
|
||||
.DESCRIPTION
|
||||
Ergaenzt den JSON-Export im Adminbereich um eine vollstaendige
|
||||
Datenbanksicherung: Schema, Daten, Sequenzen, Indizes. Genau das, was
|
||||
man zum Wiederherstellen einer kaputten Instanz braucht.
|
||||
|
||||
Laeuft die Datenbank im mitgelieferten Container, wird pg_dump dort
|
||||
ausgefuehrt - auf dem Rechner selbst muss dann nichts installiert sein.
|
||||
|
||||
.PARAMETER OutDir
|
||||
Zielverzeichnis. Vorgabe: .\backups
|
||||
|
||||
.PARAMETER Container
|
||||
Name des Postgres-Containers. Vorgabe: nebula-db
|
||||
|
||||
.EXAMPLE
|
||||
.\scripts\backup.ps1
|
||||
.\scripts\backup.ps1 -OutDir D:\Sicherungen
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$OutDir = (Join-Path $PSScriptRoot "..\backups"),
|
||||
[string]$Container = "nebula-db"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# .env einlesen, damit Benutzer und Datenbankname nicht doppelt gepflegt werden.
|
||||
$envFile = Join-Path $PSScriptRoot "..\.env"
|
||||
$settings = @{ POSTGRES_USER = "nebula"; POSTGRES_DB = "nebula" }
|
||||
if (Test-Path $envFile) {
|
||||
foreach ($line in Get-Content $envFile) {
|
||||
if ($line -match '^\s*([A-Z_]+)\s*=\s*"?([^"#]*)"?\s*$') {
|
||||
$settings[$Matches[1]] = $Matches[2].Trim()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$user = $settings["POSTGRES_USER"]
|
||||
$database = $settings["POSTGRES_DB"]
|
||||
|
||||
if (-not (Test-Path $OutDir)) { New-Item -ItemType Directory -Path $OutDir | Out-Null }
|
||||
|
||||
$stamp = Get-Date -Format "yyyy-MM-dd-HHmmss"
|
||||
$target = Join-Path $OutDir "nebula-$stamp.sql"
|
||||
|
||||
$running = docker ps --filter "name=$Container" --format "{{.Names}}"
|
||||
if ($running -notcontains $Container) {
|
||||
Write-Error "Container '$Container' laeuft nicht. Zuerst 'docker compose up -d db' ausfuehren."
|
||||
}
|
||||
|
||||
Write-Host "Sichere Datenbank '$database' aus Container '$Container'..."
|
||||
|
||||
# --clean --if-exists macht die Datei fuer sich allein einspielbar.
|
||||
docker exec $Container pg_dump -U $user -d $database --clean --if-exists | Out-File -Encoding utf8 $target
|
||||
|
||||
if ($LASTEXITCODE -ne 0) { Write-Error "pg_dump ist fehlgeschlagen." }
|
||||
|
||||
$size = [math]::Round((Get-Item $target).Length / 1KB, 1)
|
||||
Write-Host "Fertig: $target ($size KB)" -ForegroundColor Green
|
||||
Write-Host "Wiederherstellen mit: .\scripts\restore.ps1 -File `"$target`""
|
||||
@@ -0,0 +1,43 @@
|
||||
#!/bin/sh
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Byteweise Sicherung der Nebula-Datenbank mit pg_dump.
|
||||
#
|
||||
# Ergaenzt den JSON-Export im Adminbereich um eine vollstaendige
|
||||
# Datenbanksicherung: Schema, Daten, Sequenzen, Indizes.
|
||||
#
|
||||
# Aufruf: ./scripts/backup.sh [Zielverzeichnis]
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
set -eu
|
||||
|
||||
root="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
out_dir="${1:-$root/backups}"
|
||||
container="${NEBULA_DB_CONTAINER:-nebula-db}"
|
||||
|
||||
user="nebula"
|
||||
database="nebula"
|
||||
if [ -f "$root/.env" ]; then
|
||||
# Nur die beiden benoetigten Werte lesen, ohne die Datei auszufuehren.
|
||||
value_of() {
|
||||
sed -n "s/^[[:space:]]*$1[[:space:]]*=[[:space:]]*\"\{0,1\}\([^\"#]*\)\"\{0,1\}[[:space:]]*$/\1/p" \
|
||||
"$root/.env" | tail -n 1
|
||||
}
|
||||
[ -n "$(value_of POSTGRES_USER)" ] && user="$(value_of POSTGRES_USER)"
|
||||
[ -n "$(value_of POSTGRES_DB)" ] && database="$(value_of POSTGRES_DB)"
|
||||
fi
|
||||
|
||||
if ! docker ps --filter "name=$container" --format '{{.Names}}' | grep -qx "$container"; then
|
||||
echo "Container '$container' laeuft nicht. Zuerst 'docker compose up -d db' ausfuehren." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$out_dir"
|
||||
stamp="$(date +%Y-%m-%d-%H%M%S)"
|
||||
target="$out_dir/nebula-$stamp.sql"
|
||||
|
||||
echo "Sichere Datenbank '$database' aus Container '$container'..."
|
||||
# --clean --if-exists macht die Datei fuer sich allein einspielbar.
|
||||
docker exec "$container" pg_dump -U "$user" -d "$database" --clean --if-exists > "$target"
|
||||
|
||||
size="$(du -h "$target" | cut -f1)"
|
||||
echo "Fertig: $target ($size)"
|
||||
echo "Wiederherstellen mit: ./scripts/restore.sh \"$target\""
|
||||
@@ -0,0 +1,62 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Spielt eine mit backup.ps1 erzeugte Sicherung zurueck.
|
||||
|
||||
.DESCRIPTION
|
||||
ACHTUNG: Der aktuelle Inhalt der Datenbank wird dabei ersetzt. Das Skript
|
||||
fragt vorher nach, es sei denn, -Force ist gesetzt.
|
||||
|
||||
.PARAMETER Database
|
||||
Zieldatenbank. Vorgabe: der Wert aus der .env.
|
||||
|
||||
Tipp: Eine Sicherung laesst sich pruefen, ohne die echte Datenbank
|
||||
anzufassen - in eine leere Testdatenbank einspielen und nachsehen:
|
||||
|
||||
docker exec nebula-db psql -U nebula -d postgres -c "CREATE DATABASE nebula_test"
|
||||
.\scripts\restore.ps1 -File .\backups\... -Database nebula_test -Force
|
||||
|
||||
.EXAMPLE
|
||||
.\scripts\restore.ps1 -File .\backups\nebula-2026-07-26-101500.sql
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$File,
|
||||
[string]$Database,
|
||||
[string]$Container = "nebula-db",
|
||||
[switch]$Force
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
if (-not (Test-Path $File)) { Write-Error "Datei nicht gefunden: $File" }
|
||||
|
||||
$envFile = Join-Path $PSScriptRoot "..\.env"
|
||||
$settings = @{ POSTGRES_USER = "nebula"; POSTGRES_DB = "nebula" }
|
||||
if (Test-Path $envFile) {
|
||||
foreach ($line in Get-Content $envFile) {
|
||||
if ($line -match '^\s*([A-Z_]+)\s*=\s*"?([^"#]*)"?\s*$') {
|
||||
$settings[$Matches[1]] = $Matches[2].Trim()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$user = $settings["POSTGRES_USER"]
|
||||
$database = if ($Database) { $Database } else { $settings["POSTGRES_DB"] }
|
||||
|
||||
if (-not $Force) {
|
||||
Write-Host "Der gesamte Inhalt der Datenbank '$database' wird ersetzt." -ForegroundColor Yellow
|
||||
$answer = Read-Host "Wirklich fortfahren? (ja/nein)"
|
||||
if ($answer -ne "ja") { Write-Host "Abgebrochen."; exit 0 }
|
||||
}
|
||||
|
||||
$running = docker ps --filter "name=$Container" --format "{{.Names}}"
|
||||
if ($running -notcontains $Container) {
|
||||
Write-Error "Container '$Container' laeuft nicht."
|
||||
}
|
||||
|
||||
Write-Host "Spiele '$File' ein..."
|
||||
Get-Content $File -Raw | docker exec -i $Container psql -U $user -d $database -v ON_ERROR_STOP=1
|
||||
|
||||
if ($LASTEXITCODE -ne 0) { Write-Error "Das Einspielen ist fehlgeschlagen." }
|
||||
|
||||
Write-Host "Fertig. Anwendung neu starten, damit sie eine frische Verbindung aufbaut." -ForegroundColor Green
|
||||
@@ -0,0 +1,57 @@
|
||||
#!/bin/sh
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Spielt eine mit backup.sh erzeugte Sicherung zurueck.
|
||||
#
|
||||
# ACHTUNG: Der aktuelle Inhalt der Datenbank wird ersetzt.
|
||||
#
|
||||
# Aufruf: ./scripts/restore.sh <datei.sql> [--force]
|
||||
#
|
||||
# Zieldatenbank ueberschreiben mit NEBULA_DB_NAME. Damit laesst sich eine
|
||||
# Sicherung pruefen, ohne die echte Datenbank anzufassen:
|
||||
#
|
||||
# docker exec nebula-db psql -U nebula -d postgres -c "CREATE DATABASE nebula_test"
|
||||
# NEBULA_DB_NAME=nebula_test ./scripts/restore.sh backups/... --force
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
set -eu
|
||||
|
||||
root="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
file="${1:-}"
|
||||
force="${2:-}"
|
||||
container="${NEBULA_DB_CONTAINER:-nebula-db}"
|
||||
|
||||
if [ -z "$file" ]; then
|
||||
echo "Aufruf: ./scripts/restore.sh <datei.sql> [--force]" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -f "$file" ]; then
|
||||
echo "Datei nicht gefunden: $file" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
user="nebula"
|
||||
database="nebula"
|
||||
if [ -f "$root/.env" ]; then
|
||||
value_of() {
|
||||
sed -n "s/^[[:space:]]*$1[[:space:]]*=[[:space:]]*\"\{0,1\}\([^\"#]*\)\"\{0,1\}[[:space:]]*$/\1/p" \
|
||||
"$root/.env" | tail -n 1
|
||||
}
|
||||
[ -n "$(value_of POSTGRES_USER)" ] && user="$(value_of POSTGRES_USER)"
|
||||
[ -n "$(value_of POSTGRES_DB)" ] && database="$(value_of POSTGRES_DB)"
|
||||
fi
|
||||
database="${NEBULA_DB_NAME:-$database}"
|
||||
|
||||
if [ "$force" != "--force" ]; then
|
||||
printf "Der gesamte Inhalt der Datenbank '%s' wird ersetzt.\nWirklich fortfahren? (ja/nein) " "$database"
|
||||
read -r answer
|
||||
[ "$answer" = "ja" ] || { echo "Abgebrochen."; exit 0; }
|
||||
fi
|
||||
|
||||
if ! docker ps --filter "name=$container" --format '{{.Names}}' | grep -qx "$container"; then
|
||||
echo "Container '$container' laeuft nicht." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Spiele '$file' ein..."
|
||||
docker exec -i "$container" psql -U "$user" -d "$database" -v ON_ERROR_STOP=1 < "$file"
|
||||
|
||||
echo "Fertig. Anwendung neu starten, damit sie eine frische Verbindung aufbaut."
|
||||
Reference in New Issue
Block a user