test #26

Merged
brian merged 2 commits from test into main 2026-09-10 17:54:38 +00:00
Owner
No description provided.
feat: run the full-system backup with the app stopped, in its own container
Some checks failed
Build and Deploy / test (push) Failing after 26s
Build and Deploy / build (push) Has been skipped
Build and Deploy / deploy-main (push) Has been skipped
Build and Deploy / deploy (push) Has been skipped
71f365b0fe
A backup used to archive the upload volume while the application was still
writing to it, and a restore used to swap directories and pg_restore out from
under a live process — which is why run_restore had to dispose the engine and
re-run the startup schema sync to leave anything working.

Both now happen in a separate image. journal-borg stops journal-web, does the
work against a volume nothing is touching, and starts it again. db deliberately
stays up: pg_dump and pg_restore need a live server, and stopping it would mean
archiving the data directory byte-for-byte instead, which ties every archive to
one Postgres major and makes the existing ones unreadable. With web stopped
Postgres has no writers anyway, so the archive layout is unchanged and every
archive already in the repository still restores.

The UI still drives it. Web enqueues a row in system_jobs and the agent polls
for it — a table rather than a socket or a broker, because for the length of a
run there is no web process to hold a connection or own a lock, and Postgres is
up throughout. Progress is appended to the row as it happens rather than
reported at the end: while web is down the browser can reach nothing, so the
whole account of the run has to be waiting in the database when it comes back.
The panel treats an unreachable server as the expected state and renders the log
on reconnect; the service worker is what keeps the page itself alive meanwhile.

Which containers to stop is discovered, never named. The agent inspects itself,
reads its own com.docker.compose.project, and stops that project's containers
labelled journal.role=web. Test and prod run on the same host with names one
suffix apart, so a name-based rule would eventually take production down; with
no project label it refuses rather than matching across every stack. The blank
override in Settings is for deployments that carry no Compose labels.

It reaches Docker through a docker-socket-proxy restricted to CONTAINERS=1 and
POST=1 — enough to list, inspect, start and stop, not enough to create a
container, exec into one or mount anything. The agent holds the repository's SSH
key and passphrase, so it is the last process that should have the real socket.

system_backup.py keeps the half both images share plus the read-only operations
(test, init, list), which stop nothing and stay synchronous in web. The
PG_MAJOR pin moved to Dockerfile.agent with pg_dump and pg_restore; the app
image no longer carries the Postgres client. The two images import the same
backend/, so deploy.yml builds, pushes and pulls both from one commit.

Tests pin the two failures that are silent: that web is stopped before the dump
(invert it and no archive looks any different — you are simply copying a live
volume again), and that the containers come back even when a run fails, because
a failed backup that leaves the site down is a different and much worse event.
Also that a restore survives pg_restore --clean dropping system_jobs, which is
the table the running job itself lives in.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DBaLzSUjPjsgKHhxqMngxt
fix: don't name a method for a builtin its own class annotates with
All checks were successful
Build and Deploy / test (push) Successful in 25s
Build and Deploy / build (push) Successful in 1m54s
Build and Deploy / deploy-main (push) Has been skipped
Build and Deploy / deploy (push) Successful in 31s
3544517cb1
`Docker.list` shadowed the builtin for every annotation evaluated later in the
class body, so `managed(...) -> list[str]` a few methods down resolved `list` to
the method and raised `'function' object is not subscriptable` at import.

It ran locally because Python 3.14 defers annotation evaluation under PEP 649 and
never looks at them. The images and CI run 3.13, which evaluates at class
definition time. Renamed to list_containers.

The reason this reached CI as a unit-test error rather than a smoke-test one is
that the smoke step only imported app.main. Nothing in the web process imports
backend/agent, so the agent's entrypoint had no import check of its own — which
means a name error in it would otherwise first surface when the container ran,
during a backup. The smoke step now imports both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DBaLzSUjPjsgKHhxqMngxt
brian merged commit 5bdcae1a23 into main 2026-09-10 17:54:38 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
brian/Journal!26
No description provided.