ops(backup): missed-run detection + Forgejo code/DB backup to S3 #3025
No reviewers
Labels
No labels
Fable 5 ревью
GG-форсайт
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
feedback/max
generative
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
ИРД
вторичка
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#3025
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/2203-backup-staleness-and-forgejo"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Two narrow deliverables for #2203, refs #2989.
1. Missed-run detection for backups.
ops/backup.shandtradein-mvp/deploy/backup-tradein-db.shnow write a sentinel file(
.last_success, UTC timestamp) on every run that passes all existingguards -- nothing about their own hardening (min-size floor,
gzip -t+trailer integrity check, globals dump, retention) is touched. A new,
separate
ops/check-backup-staleness.sh(own cron entry, suggested hourly)checks a sentinel's age against a threshold (default 26h for a daily cron)
and alerts if it is stale or missing. Shared sentinel/notify/state logic
lives in
ops/lib-backup.shso it is not copy-pasted a third time.Alert channel: reuses the existing Telegram bot from
ops/uptime-healthcheck.sh-- sameTELEGRAM_BOT_TOKEN/TELEGRAM_CHAT_IDvariable names, read from
/etc/default/gendesign-backup(deliberately adifferent env file than uptime's
/etc/default/gendesign-uptime, so backupalerting does not depend on the uptime watchdog's config existing -- point
both at the same bot/chat if you want one destination). Alerts only on a
state transition (fresh->stale, stale->fresh), same discipline as
uptime-healthcheck.sh'sprev_status/set_status, so an hourly crondoes not spam. No channel configured -> logs only, no new alerting system
invented.
notify()inops/lib-backup.shis the single extension pointif a different channel is wanted later.
2. Forgejo backup -- did not exist at all. New
ops/backup-forgejo.sh:pg_dump -U forgejo forgejoviadocker execagainst theshared
gendesign-postgres-1container (Forgejo has no dedicated DBcontainer -- separate user+db in the main app's Postgres, per
infra/Forgejo_Migration_BotServer_To_Beget_2026-05-16.mdin the vault).tar -czfof the bare-repo tree underFORGEJO_REPOS_DIR(default/home/gendesign/forgejo/data/forgejo/git/repositories, matching thedocumented Forgejo layout). Bare repos already ARE just refs+objects --
tar-ing the whole tree in one shot is a superset of what a per-repo
git bundleloop would capture, and needs zero changes when repos areadded/removed. LFS objects/avatars/attachments elsewhere under
FORGEJO_DATA_DIRare out of scope for this pass -- flagging as a knowngap, not silently dropped.
ops/backup.sh: non-empty + min-size floor, integritycheck (
gzip -t+ trailer for the DB dump,tar -tzffor the bundle),sentinel on success, non-zero exit on any failure.
s3://gendsgn-backups/forgejo/under a separate, narrower S3service user -- NOT the existing
gendsgn-backup-writer(which hasroot-of-bucket write access and is used by the other two backups). Off-box
upload is mandatory here (unlike
ops/backup.sh, where it is optional) --the whole point of this script is getting Forgejo's code off the VM it
runs on.
FORGEJO_S3_ENDPOINT/FORGEJO_S3_BUCKET/FORGEJO_S3_ACCESS_KEY/FORGEJO_S3_SECRET_KEYset (via
/etc/default/gendesign-backup-forgejo, see the newops/gendesign-backup-forgejo.default.example), the script logs a loud"NOT CONFIGURED" error and exits 1 -- it does not silently skip the
upload or fall back to local-only.
Bucket policy JSON for the new service user
For a human to create in the Selectel panel (bucket
gendsgn-backups,mirrors the existing
gendsgn-backup-writerpolicy pattern documented invault
meta/00_credentials.mdsection "Selectel S3 -- бэкап-бакетgendsgn-backups" -- same Sid names/shape, scoped to the
forgejo/prefixonly). Replace
<FORGEJO_WRITER_USER_ID>with the new service user's IDonce created, same way the existing writer/reader policy references their
own user IDs:
Notes on the shape (matching the existing writer's documented rationale):
PutObjectscoped strictly to.../forgejo/*-- this key cannot writeanywhere else in the bucket (so it cannot clobber the main/tradein
backups living at bucket root).
ListBucketis explicitly denied even though it seems harmless -- acompromised prod-adjacent host should not be able to enumerate what other
backups exist in the bucket either.
guard against a future overly-broad
Allowbeing added by mistake -- samereasoning as the existing writer/reader policy.
s3.bucket.user-role-shaped, per the existing writer/readerpattern (that role fails closed with no policy;
s3.userwould failopen) -- create the new service user with that same role.
Effect on running stacks
None. Nothing here touches
docker-compose.prod.yml,.forgejo/workflows/*,or any running container config -- purely new/extended cron scripts. New
ops/*.shfiles already match the deploy trigger glob ("ops/*.sh"in.forgejo/workflows/deploy.yml) and itschmod +x ops/*.shstep, so noworkflow changes were needed;
tradein-mvp/deploy/backup-tradein-db.shisalready covered by the existing
tradein-mvp/deploy/**trigger.What a human needs to do after merge (not done here -- no prod SSH, no new creds created)
/etc/default/gendesign-backup(chmod 600,already exists):
TELEGRAM_BOT_TOKEN=.../TELEGRAM_CHAT_ID=...(reuse the uptime bot's values, or a separate bot -- either works).
ops/check-backup-staleness.sh(see its header forthe exact 3 lines -- one per sentinel: main, tradein, forgejo).
gendsgn-backup-forgejo-writers3.bucket.userservice user, apply the policy JSON above (swap in thereal user ID), then create
/etc/default/gendesign-backup-forgejo(chmod 600) from
ops/gendesign-backup-forgejo.default.examplewith thereal access/secret key.
ops/backup-forgejo.sh(see its header for thesuggested line) once step 3 is done -- until then it will run (if
cron'd early) and correctly fail loudly every time, which is expected.
Tests
Behavioral bash checks run locally (not wired into CI --
.forgejo/workflows/*is out of scope per the issue, and there is no
ops/tests/convention inthis repo yet to hook into): stale sentinel -> nonzero + alert attempt;
fresh sentinel -> silent, exit 0; missing sentinel -> nonzero;
fresh->stale->stale->fresh transition sequence -> alerts only on the two
transitions, not the repeat;
backup-forgejo.shwith no S3 vars -> loud"NOT CONFIGURED" + nonzero, no docker/tar touched;
backup-forgejo.shwithS3 vars but a missing repos dir -> loud nonzero. All 11 assertions passed.
bash -nclean on all changed/new scripts (matches the existing CI syntaxgate in
.forgejo/workflows/ci.yml, which globsops/*.sh/ops/**/*.sh--untouched, no workflow changes needed).