main
Includes: - gitea-backups/bin/backup.sh (per-push bundle + DB snapshot to local + S3) - gitea-backups/bin/install-hooks.sh (idempotent post-receive shim installer) - gitea-backups/bin/retention.sh (count-based retention: keep newest 7 dates) - gitea-mirror/bin/auto-mirror.sh (Gitea -> GitHub push mirror automation, hardened against Gitea outages) - crontab.txt (reference for the 3 cron entries) - README.md (architecture, layout, bootstrap)
gitea-ops
Source of truth for the Gitea install at /home/ubuntu/gitea/ on this server, and the automation around it.
Layout
gitea-ops/
├── README.md
├── crontab.txt # cron entries (install with: crontab crontab.txt)
├── gitea-backups/bin/
│ ├── backup.sh # called per push from each repo's post-receive hook
│ ├── install-hooks.sh # cron, every minute: ensures hook shim in every repo
│ └── retention.sh # cron, daily 03:00 UTC: keeps newest 7 date-folders
└── gitea-mirror/bin/
└── auto-mirror.sh # cron, every minute: ensures GitHub push-mirror per repo
The deployment target is fixed: scripts run from /home/ubuntu/gitea-backups/bin/ and /home/ubuntu/gitea-mirror/bin/.
Components
| Script | Trigger | Purpose |
|---|---|---|
backup.sh |
per git push (via post-receive hook) |
Bundle repo + snapshot SQLite, upload both to local + S3 |
install-hooks.sh |
cron, 1 min | Drop the zzz-backup shim into every repo's hooks/post-receive.d/ |
retention.sh |
cron, daily 03:00 UTC | Keep newest 7 calendar dates of backups (S3 + local), prune older |
auto-mirror.sh |
cron, 1 min | For every Gitea repo, ensure matching private GitHub repo + push mirror exist |
Backup destinations
- Local —
/home/ubuntu/gitea-backups/repos/<owner>/<repo>/*.bundleand/home/ubuntu/gitea-backups/db/*-gitea.db.gz - S3 —
s3://toqqer-gitea-backup/<YYYY-MM-DD>/repos/...and.../<YYYY-MM-DD>/db/...(regionap-south-1) - GitHub — live mirror at
github.com/prajwalpatil-toqqer/<repo>for every Gitea repo (real-time viasync_on_commit)
Retention semantics
Keep the most recent 7 calendar dates that have backups, regardless of how old they are. Quiet periods don't empty the store — the last 7 active dates always persist.
Required outside-of-repo state
Not committed (see .gitignore):
/home/ubuntu/gitea-mirror/gitea.token— Gitea PAT (scopes: repo, user)/home/ubuntu/gitea-mirror/github.token— GitHub classic PAT (scope: repo)/home/ubuntu/.aws/credentials— IAMgitea-backup-bot(scoped to one bucket)/home/ubuntu/.config/rclone/rclone.conf— same key, rclone format
Service unit
Gitea itself runs under user systemd:
- Unit:
~/.config/systemd/user/gitea.service - Persistence:
loginctl enable-linger ubuntu - Status:
systemctl --user status gitea - Logs:
journalctl --user -u gitea -f
Bootstrap a fresh server
# 1. Place the secret files (gitea.token, github.token, aws creds, rclone.conf) outside the repo
# 2. Mirror the directory layout:
mkdir -p /home/ubuntu/gitea-backups/{bin,db,repos,logs}
mkdir -p /home/ubuntu/gitea-mirror/{bin,logs}
cp gitea-backups/bin/* /home/ubuntu/gitea-backups/bin/
cp gitea-mirror/bin/* /home/ubuntu/gitea-mirror/bin/
chmod +x /home/ubuntu/gitea-backups/bin/*.sh /home/ubuntu/gitea-mirror/bin/*.sh
# 3. Install cron
crontab crontab.txt
Description
Languages
Shell
100%