From 91ae90836bec3555898072fe0d015d54f6519661 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 10 Sep 2026 00:47:16 +0200 Subject: [PATCH 1/2] docs(env): document the two optional off-host backup variables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BACKUP_S3_REGION and BACKUP_RETENTION_DAYS are both on the compose whitelist and both read by the backup job, but the example file listed neither — so an operator following it had no way to learn that the region defaults to auto, or that the retention number is what the bucket's own lifecycle rule has to be set to, since the worker never deletes an object itself. --- .env.production.example | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.env.production.example b/.env.production.example index ee3d19cf4..4a56a53b6 100644 --- a/.env.production.example +++ b/.env.production.example @@ -306,6 +306,20 @@ API_TOKEN_HMAC_KEY="" # BACKUP_S3_SECRET_KEY="" # BACKUP_ENCRYPTION_KEY="" +# The two optional members of the set, both with defaults, so leaving them +# unset does not disable backups. +# +# Region sent with the SigV4 signature. Default "auto", which is what +# Cloudflare R2 expects; AWS S3 and most MinIO deployments want their real +# region name here. +# BACKUP_S3_REGION="auto" +# +# How long backup objects are meant to live, in days. Default 30, minimum 1. +# The worker never deletes an object -- its grant is PutObject, GetObject and +# AbortMultipartUpload -- so this number is what you set the bucket's own +# lifecycle rule to. See docs/ops/backup-restore.md. +# BACKUP_RETENTION_DAYS="30" + # ----------------------------------------------------------------------------- # Audit-trail retention -- optional From 8ad5b90f881aededd65f3f9affdb1a67c4b7f83b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 10 Sep 2026 00:48:31 +0200 Subject: [PATCH 2/2] docs(ops): the off-host worker never prunes; the bucket lifecycle does The runbook said the worker prunes objects older than BACKUP_RETENTION_DAYS. It never calls DeleteObject; its grant is PutObject, GetObject and AbortMultipartUpload, on purpose, so a compromised worker cannot wipe the history. The sentence now says what expires objects and what the variable is for. --- docs/ops/backup-restore.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/ops/backup-restore.md b/docs/ops/backup-restore.md index 92991852b..0ac2d7ad5 100644 --- a/docs/ops/backup-restore.md +++ b/docs/ops/backup-restore.md @@ -54,8 +54,10 @@ Cloudflare R2 the **Object Read & Write** token already covers all three. ## Bucket lifecycle (recommended) -The worker prunes objects older than `BACKUP_RETENTION_DAYS`, but the -storage provider's lifecycle rule is the canonical safety net: +The worker never deletes a backup object: its grant covers PutObject, +GetObject and AbortMultipartUpload only, so a compromised worker cannot +wipe the history. Expiry is the storage provider's lifecycle rule, set to +match `BACKUP_RETENTION_DAYS`: ``` Filter: "" (all objects)