Round five. 47 -> 34 -> 15 -> 7 -> 6 raw findings, and this round's sharpest was the loop's own blind spot: **the branch did not pass the gate.** `prettier --check` is one of its eight lanes and I had been running the other seven, so an unformatted `onError` from round one sat on the branch for four rounds — `siftlode publish` would have refused it, with no --skip flag to get past. **The restart hint could still strand the restored data.** Round four said the scripts no longer print a command they cannot resolve; they did. The COMPOSE_FILE fallback only fires when `docker-compose.yml` is ABSENT, so in a source checkout it never fires and the hint expanded to a bare, pasteable `docker compose up -d` — which reads the other file, whose volumes are `pgdata`/`downloads` rather than `siftlode_pgdata`/`siftlode_downloads`, and recreates the database on an empty one. When the file is unresolved the scripts now print `-f <the compose file you started with>`: not runnable, which is the point. **A comment naming the wrong owner.** `formats.py` still said a zero budget is "a refusal for enqueue to make" — the premise round one disproved, three files away from the two docstrings that now say the opposite. Anyone acting on it would delete the worker's refusal and re-open the original inversion, so it now says where the refusal actually lives. Also: the reset-flag rule was rewritten in three consecutive rounds and pinned by nothing — two tests now cover a fragment losing its token, both by a pasted verify link and by navigating away entirely; `backup.ps1` deletes a partially-copied dump before throwing, the guard round four gave the docs but not the script it mirrors; and one comment that read as if the asset were untouched by a line four below it. Gate green, all eight lanes: ruff, ruff format, 3x tsc, eslint, prettier, knip, vitest 687, pytest 950.
62 lines
3.3 KiB
PowerShell
62 lines
3.3 KiB
PowerShell
# Dumps the Siftlode Postgres database to backups\siftlode-<timestamp>.dump (custom format).
|
|
# Run from the project root: .\scripts\backup.ps1
|
|
#
|
|
# A dump is only HALF a backup. The other half is your .env — it holds TOKEN_ENCRYPTION_KEY, without
|
|
# which every encrypted value in the restored database (Google credentials, SMTP password, Plex
|
|
# token, stored refresh tokens) is unreadable. See docs/self-hosting.md § Backups.
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
# The dump is moved with `docker compose cp`, never through the PowerShell pipeline. Piping a binary
|
|
# stream corrupts it on Windows PowerShell 5.1 (text decoding), and on PowerShell 7 the old
|
|
# `-Encoding Byte` workaround does not even exist any more — it throws "'Byte' is not a supported
|
|
# encoding name", so this script simply failed there.
|
|
$remote = "/tmp/siftlode-backup.dump"
|
|
|
|
# Best-effort cleanup of the in-container dump. The try/catch is the load-bearing part: on Windows
|
|
# PowerShell 5.1 ANY redirected native stderr (`2>$null` and `2>&1` alike) becomes an ErrorRecord,
|
|
# and the $ErrorActionPreference above makes it TERMINATING — so one stray Compose warning would
|
|
# kill the script here, masking the real diagnostic below or reporting a finished backup as a
|
|
# failure. Do not remove the empty catch. A cleanup step must never decide the verdict.
|
|
function Remove-RemoteDump($path) {
|
|
try { docker compose exec -T db rm -f $path 2>&1 | Out-Null } catch { }
|
|
}
|
|
|
|
# `docker compose` finds docker-compose.yml on its own and honours $env:COMPOSE_FILE. The documented
|
|
# self-host layout (docs/self-hosting.md § Get the files) has NEITHER — just the selfhost file.
|
|
if (-not $env:COMPOSE_FILE -and -not (Test-Path "docker-compose.yml") -and (Test-Path "docker-compose.selfhost.yml")) {
|
|
$env:COMPOSE_FILE = "docker-compose.selfhost.yml"
|
|
}
|
|
|
|
$user = if ($env:POSTGRES_USER) { $env:POSTGRES_USER } else { "siftlode" }
|
|
$dbname = if ($env:POSTGRES_DB) { $env:POSTGRES_DB } else { "siftlode" }
|
|
New-Item -ItemType Directory -Force -Path "backups" | Out-Null
|
|
$ts = Get-Date -Format "yyyyMMdd-HHmmss"
|
|
$out = "backups\siftlode-$ts.dump"
|
|
|
|
docker compose exec -T db pg_dump -U $user -Fc $dbname -f $remote
|
|
# Capture BEFORE the cleanup call: any native command overwrites $LASTEXITCODE, so interpolating it
|
|
# after Remove-RemoteDump would report the cleanup's code (0) inside a branch that only runs when
|
|
# pg_dump failed — a message contradicting itself. Same reason $cpCode is captured below.
|
|
$dumpCode = $LASTEXITCODE
|
|
if ($dumpCode -ne 0) {
|
|
Remove-RemoteDump $remote
|
|
throw "BACKUP FAILED — pg_dump returned $dumpCode; no file was written."
|
|
}
|
|
|
|
docker compose cp "db:$remote" $out
|
|
$cpCode = $LASTEXITCODE
|
|
Remove-RemoteDump $remote
|
|
# Remove the partial file: `cp` streams, so a mid-stream failure leaves a truncated dump under
|
|
# the final name — non-zero length, so the size check below would not catch it either.
|
|
if ($cpCode -ne 0) {
|
|
Remove-Item $out -ErrorAction SilentlyContinue
|
|
throw "BACKUP FAILED — could not copy the dump out of the container (exit $cpCode)."
|
|
}
|
|
if (-not (Test-Path $out) -or (Get-Item $out).Length -eq 0) {
|
|
if (Test-Path $out) { Remove-Item $out }
|
|
throw "BACKUP FAILED — the dump came out empty; no backup was written."
|
|
}
|
|
|
|
Write-Output "Wrote $out"
|
|
Write-Output "Remember: back up your .env too (TOKEN_ENCRYPTION_KEY) — this dump alone cannot be fully restored."
|