Compare commits

...

4 commits

Author SHA1 Message Date
dd23f08878
fix(terraform): restore the dump app.ini with the database
INTERNAL_TOKEN, JWT_SECRET, and LFS_JWT_SECRET live in that file. A restore that keeps the generated file cannot decrypt the dumped secrets.
2026-09-29 19:59:54 -04:00
494b63439f
docs(terraform): keep optional restore copies from aborting under set -e
if/fi matches user_data.sh. The file comment now says forgejo-services versions also go through the org-account role.
2026-09-29 19:53:28 -04:00
2541cd934c
docs(terraform): keep restore runbook on one bucket and fail closed
Glacier and the download use the prod bucket. GCS unpacks on the data volume. Neither path deletes live repos until the dump has a database.
2026-09-29 19:48:39 -04:00
2cc849024f
fix(terraform): accept dumps that already contain data/forgejo.db
Today's archive has no gitea-db.sqlite3 at the root. Copy that file only when it is present.
2026-09-29 19:37:48 -04:00
3 changed files with 93 additions and 20 deletions

View file

@ -107,37 +107,101 @@ sudo /usr/local/bin/forgejo-backup.sh
For backups older than 30 days (Glacier), restore the object first:
```bash
aws s3api restore-object --bucket forgejo-backups-328440206208 \
aws s3api restore-object --bucket forgejo-backups-011934824531 \
--key "archive/<date>/forgejo-<date>.tar.gz" \
--restore-request '{"Days":7,"GlacierJobParameters":{"Tier":"Standard"}}'
# Wait ~3-5 hours for restore to complete, then:
```
Download and restore:
Download and restore. The copy does not start until the dump contains a database, so a failed download leaves the live data alone.
```bash
aws s3 cp s3://forgejo-backups-328440206208/archive/<date>/forgejo-<date>.tar.gz /tmp/
set -euo pipefail
rm -rf /var/lib/forgejo/.restore && mkdir -p /var/lib/forgejo/.restore
aws s3 cp s3://forgejo-backups-011934824531/archive/<date>/forgejo-<date>.tar.gz - --no-progress \
| tar -xz -C /var/lib/forgejo/.restore
if [ ! -s /var/lib/forgejo/.restore/gitea-db.sqlite3 ] && [ ! -s /var/lib/forgejo/.restore/data/forgejo.db ]; then
echo "Dump has no database" >&2
exit 1
fi
systemctl stop forgejo
mkdir -p /tmp/forgejo-restore && tar -xzf /tmp/forgejo-<date>.tar.gz -C /tmp/forgejo-restore
cd /tmp/forgejo-restore
cp app.ini /etc/forgejo/app.ini
cp gitea-db.sqlite3 /var/lib/forgejo/data/forgejo.db
if [ -d /var/lib/forgejo/.restore/data ]; then
cp -a /var/lib/forgejo/.restore/data/. /var/lib/forgejo/data/
fi
rm -rf /var/lib/forgejo/data/repositories
cp -a repos /var/lib/forgejo/data/repositories
cp -a data/. /var/lib/forgejo/data/
[ -d lfs ] && cp -a lfs/. /var/lib/forgejo/data/lfs/
[ -d custom ] && cp -a custom/. /var/lib/forgejo/custom/
chown -R forgejo:forgejo /var/lib/forgejo /etc/forgejo/app.ini
mkdir -p /var/lib/forgejo/data/repositories
if [ -d /var/lib/forgejo/.restore/repos ]; then
cp -a /var/lib/forgejo/.restore/repos/. /var/lib/forgejo/data/repositories/
fi
if [ -f /var/lib/forgejo/.restore/gitea-db.sqlite3 ]; then
cp /var/lib/forgejo/.restore/gitea-db.sqlite3 /var/lib/forgejo/data/forgejo.db
fi
if [ -d /var/lib/forgejo/.restore/lfs ]; then
mkdir -p /var/lib/forgejo/data/lfs
cp -a /var/lib/forgejo/.restore/lfs/. /var/lib/forgejo/data/lfs/
fi
if [ -d /var/lib/forgejo/.restore/custom ]; then
cp -a /var/lib/forgejo/.restore/custom/. /var/lib/forgejo/custom/
fi
if [ ! -s /var/lib/forgejo/data/forgejo.db ]; then
echo "Restore did not produce /var/lib/forgejo/data/forgejo.db" >&2
exit 1
fi
if [ -f /var/lib/forgejo/.restore/app.ini ]; then
cp /var/lib/forgejo/.restore/app.ini /etc/forgejo/app.ini
chown root:forgejo /etc/forgejo/app.ini
chmod 660 /etc/forgejo/app.ini
fi
chown -R forgejo:forgejo /var/lib/forgejo
systemctl start forgejo
rm -rf /tmp/forgejo-restore /tmp/forgejo-<date>.tar.gz
rm -rf /var/lib/forgejo/.restore
```
### Restore from GCS (disaster recovery)
This path does not read S3. It unpacks the offsite object on the data volume and uses the same copy order as the S3 restore.
```bash
set -euo pipefail
gcloud config set project sea-haven-backups
gsutil cp gs://forgejo-backups-offsite-seahaven/archive/<date>/forgejo-<date>.tar.gz /tmp/
# Then follow the same restore steps as S3 above
rm -rf /var/lib/forgejo/.restore && mkdir -p /var/lib/forgejo/.restore
gsutil cp gs://forgejo-backups-offsite-seahaven/archive/<date>/forgejo-<date>.tar.gz - \
| tar -xz -C /var/lib/forgejo/.restore
if [ ! -s /var/lib/forgejo/.restore/gitea-db.sqlite3 ] && [ ! -s /var/lib/forgejo/.restore/data/forgejo.db ]; then
echo "Dump has no database" >&2
exit 1
fi
systemctl stop forgejo
if [ -d /var/lib/forgejo/.restore/data ]; then
cp -a /var/lib/forgejo/.restore/data/. /var/lib/forgejo/data/
fi
rm -rf /var/lib/forgejo/data/repositories
mkdir -p /var/lib/forgejo/data/repositories
if [ -d /var/lib/forgejo/.restore/repos ]; then
cp -a /var/lib/forgejo/.restore/repos/. /var/lib/forgejo/data/repositories/
fi
if [ -f /var/lib/forgejo/.restore/gitea-db.sqlite3 ]; then
cp /var/lib/forgejo/.restore/gitea-db.sqlite3 /var/lib/forgejo/data/forgejo.db
fi
if [ -d /var/lib/forgejo/.restore/lfs ]; then
mkdir -p /var/lib/forgejo/data/lfs
cp -a /var/lib/forgejo/.restore/lfs/. /var/lib/forgejo/data/lfs/
fi
if [ -d /var/lib/forgejo/.restore/custom ]; then
cp -a /var/lib/forgejo/.restore/custom/. /var/lib/forgejo/custom/
fi
if [ ! -s /var/lib/forgejo/data/forgejo.db ]; then
echo "Restore did not produce /var/lib/forgejo/data/forgejo.db" >&2
exit 1
fi
if [ -f /var/lib/forgejo/.restore/app.ini ]; then
cp /var/lib/forgejo/.restore/app.ini /etc/forgejo/app.ini
chown root:forgejo /etc/forgejo/app.ini
chmod 660 /etc/forgejo/app.ini
fi
chown -R forgejo:forgejo /var/lib/forgejo
systemctl start forgejo
rm -rf /var/lib/forgejo/.restore
```
## Autodiscovery

View file

@ -13,9 +13,10 @@
# 4. Point TFC_AWS_* at hcptf-forgejo / hcptf-forgejo-plan.
# 5. Re-run the script without --allow-workspace.
# 6. Second Manual apply creates the instance, buckets, ALB, and Lambda.
# Later edits to these hcptf-* inline policies need the same window.
# DenySelfMutation blocks PutRolePolicy on hcptf-* from the scoped role.
# Do not add StringLike on bootstrap trust. CreatePolicy stays on hcptf-bootstrap.
# Later edits to hcptf-* inline policies and to policy/tf-managed/forgejo-services
# need the org-account role. The scoped role cannot PutRolePolicy or
# CreatePolicyVersion. Do not add StringLike on bootstrap trust.
# CreatePolicy stays on hcptf-bootstrap.
data "aws_iam_policy_document" "hcptf_apply_trust" {
statement {

View file

@ -125,8 +125,10 @@ if [ ! -f /var/lib/forgejo/data/forgejo.db ]; then
if [ -d "$RESTORE_DIR/repos" ]; then
cp -a "$RESTORE_DIR"/repos/. /var/lib/forgejo/data/repositories/
fi
# Dump sqlite lives at the archive root, not under data/. Copy it last.
# Older dumps keep sqlite at the archive root. Current dumps already have data/forgejo.db.
if [ -f "$RESTORE_DIR/gitea-db.sqlite3" ]; then
cp "$RESTORE_DIR/gitea-db.sqlite3" /var/lib/forgejo/data/forgejo.db
fi
if [ -d "$RESTORE_DIR/lfs" ]; then
mkdir -p /var/lib/forgejo/data/lfs
cp -a "$RESTORE_DIR"/lfs/. /var/lib/forgejo/data/lfs/
@ -134,6 +136,12 @@ if [ ! -f /var/lib/forgejo/data/forgejo.db ]; then
if [ -d "$RESTORE_DIR/custom" ]; then
cp -a "$RESTORE_DIR"/custom/. /var/lib/forgejo/custom/
fi
# The dump's app.ini carries INTERNAL_TOKEN, JWT_SECRET, and LFS_JWT_SECRET.
if [ -f "$RESTORE_DIR/app.ini" ]; then
cp "$RESTORE_DIR/app.ini" /etc/forgejo/app.ini
chown root:forgejo /etc/forgejo/app.ini
chmod 660 /etc/forgejo/app.ini
fi
chown -R forgejo:forgejo /var/lib/forgejo
rm -rf "$RESTORE_DIR"
if [ ! -s /var/lib/forgejo/data/forgejo.db ]; then