With an off-site destination set up, the library's files are sent to the
bucket's media/ folder as media/ab/cd/<sha256><ext>, beside the database
dumps under database/. Each file goes once: a new backup_blob table records
what each destination holds (migration 0121), and a run's work list is every
image and attachment sha with no row yet, so runs are incremental and a new
bucket starts its copy from the beginning.
- The bucket checks each file against the sha recorded at import; a file
over 1 GiB goes in parts and is checked before the upload is completed.
A file missing or changed on disk is reported, not stored.
- A run is chunked: each 45-minute chunk commits after every file and queues
the next, so other long-maintenance jobs get turns. One run at a time.
The stall sweep judges a media run by its progress heartbeat
(backup_run.last_progress_at), not its start.
- A file that can't be sent is listed on the run (status ok, with the error
line) and tried again by the next run. A refused put for an object
already in an append-only bucket counts as sent.
- Nightly, with a destination set, runs the dump and then the media.
- Weekly reconcile lists media/ and re-sends what the bucket lost, and
adopts what it already holds (a restored database sends nothing twice).
- Uploads use the bandwidth limit; read transactions end before transfers.
- Backups card: with a destination, an "Images and attachments" section
shows coverage (files and bytes in the bucket), "Send new files now", and
the media runs. Without one, the local tarball section is unchanged.
- Media run history keeps the latest 60 rows.
Task #6879, milestone 586.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>