mirror of
https://github.com/Sea-Haven-Industries/forgejo.git
synced 2026-09-30 03:03:11 +00:00
Add 3-2-1 backup strategy with cross-region and GCS offsite (#5)
* Add 3-2-1 backup strategy with cross-region replication and GCS offsite
Implements a fully compliant 3-2-1 backup architecture:
- Copy 1 (live): Harden existing EBS snapshots to 30-day retention
- Copy 2 (near-site): S3 cross-region replication to us-west-2 with
Object Lock (governance 90d) and versioning
- Copy 3 (offsite): GCS bucket in dedicated seahaven-backups GCP project
with 2-year irreversible retention lock
Also adds a verification Lambda that checks all 3 locations daily and
runs monthly restore tests with SQLite integrity checks.
* Enable QEMU in CI for arm64 Lambda Docker builds
* Commit cdk.context.json for CI synth without AWS credentials
Vpc.fromLookup requires cached context to synthesize without
AWS credentials. Required for CI which runs cdk synth without
an OIDC role.
* Fix GCP project ID to sea-haven-backups
* Address code review findings for backup verification
Fix 4 critical issues:
- Add filter/priority/deleteMarkerReplication to S3 CRR rule (deploy would fail without)
- Add stack dependency so replica deploys before main stack
- Fix DB file extension matching (.sqlite3/.sql instead of .db)
- Replace nonexistent `forgejo restore` command with actual restore steps in README
Fix 4 moderate issues:
- Add timeout=10 to Slack webhook urlopen call
- Add filter='data' to tarfile.extract for PEP 706 compliance
- Add explicit ValueError for unknown handler mode
- Use date-scoped S3/GCS prefix instead of unbounded listing
* Fix backup strategy bug findings
* Handle SQL text dumps separately from binary SQLite in restore test
Forgejo dump produces gitea-db.sql as a text SQL dump (XORM export),
not a binary SQLite file. Opening it directly with sqlite3.connect()
throws DatabaseError. Now imports the SQL dump into a temp DB first.
* Fix GCS backup check: align staleness cutoff and add size validation
GCS check used a 72h cutoff but only listed 2 days of prefixes (~48h),
making the staleness check unreachable. Also added 1MB minimum file
size validation to match the S3 check.
* Rename SECRET_ARN env vars to SECRET_NAME to match actual values
* Fix EBS snapshot state check, drop unused GCS write grant and dead lifecycle rule
* Fix restore runbook, DLM snapshot tagging, README cleanup, and gsutil prompt
* Fix restore runbook: trailing-dot cp idiom and Glacier restore step
* Rename GCS service account to match read-only permissions
* Add 4 GiB ephemeral storage to verification Lambda
Monthly restore-test downloads and extracts the full dump tarball
in /tmp. As the dump grows with LFS data, the default 512 MB will
eventually cause ENOSPC failures.
* Replace hardcoded instance ID in README with CloudFormation lookup
The instance ID changes on every instance replacement (version
upgrades, stack updates). Using a dynamic query prevents stale
references and removes a manual update step from the deploy process.
* Read backup S3 prefix from SSM parameter at runtime
Adds /forgejo/backup-s3-prefix SSM parameter (value: archive)
and updates the backup script to fetch it instead of hardcoding
the prefix. Eliminates the manual post-deploy sed step.
* Address cross-review findings for backup verification
- Add size guard before downloading dump in restore test (3.5 GB cap)
- Use paginator for list_objects_v2 in S3 checks and restore test
- Remove unnecessary overrideLogicalId on GcsTransferCredentials secret
- Pass explicit { mode: "daily" } to daily EventBridge rule target
- Add fallback for SSM parameter fetch in backup script
- Export replica bucket ARN/name from replica stack, consume via props
* Add CloudWatch alarm for backup verification Lambda errors
Fires on any Lambda error and on missing data (missed schedule).
Catches silent failures where the Slack notification never fires.
* Add .env to .gitignore
Required by org CI conventions check.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
parent
cfda99927b
commit
5ed1db788e
7 changed files with 64 additions and 25 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,3 +1,4 @@
|
||||||
|
.env
|
||||||
node_modules/
|
node_modules/
|
||||||
cdk.out/
|
cdk.out/
|
||||||
*.js
|
*.js
|
||||||
|
|
|
||||||
22
README.md
22
README.md
|
|
@ -4,7 +4,10 @@ Self-hosted Forgejo git server for archiving GitHub repos and mirroring active o
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
- **EC2**: t4g.small (arm64), Amazon Linux 2023, 50GB gp3 EBS — instance `i-0d3005fb3c36124cd`
|
- **EC2**: t4g.small (arm64), Amazon Linux 2023, 50GB gp3 EBS — look up instance ID with:
|
||||||
|
```
|
||||||
|
aws cloudformation describe-stacks --stack-name forgejo --query 'Stacks[0].Outputs[?OutputKey==`InstanceId`].OutputValue' --output text
|
||||||
|
```
|
||||||
- **Network**: Private subnet (us-east-1a), behind `seahaven-com` ALB for SSL termination
|
- **Network**: Private subnet (us-east-1a), behind `seahaven-com` ALB for SSL termination
|
||||||
- **DNS**: `forgejo.seahaven.com` — Route53 alias record pointing to the `seahaven-com` ALB (not a direct A record)
|
- **DNS**: `forgejo.seahaven.com` — Route53 alias record pointing to the `seahaven-com` ALB (not a direct A record)
|
||||||
- **TLS**: Wildcard cert on ALB, HTTP internally on port 3000
|
- **TLS**: Wildcard cert on ALB, HTTP internally on port 3000
|
||||||
|
|
@ -148,7 +151,8 @@ sudo /usr/local/bin/forgejo-refresh-tokens.sh
|
||||||
After the stack deploys, connect via SSM and create the admin user:
|
After the stack deploys, connect via SSM and create the admin user:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
aws ssm start-session --target i-0d3005fb3c36124cd
|
INSTANCE_ID=$(aws cloudformation describe-stacks --stack-name forgejo --query 'Stacks[0].Outputs[?OutputKey==`InstanceId`].OutputValue' --output text)
|
||||||
|
aws ssm start-session --target "$INSTANCE_ID"
|
||||||
|
|
||||||
sudo -u forgejo /usr/local/bin/forgejo admin user create \
|
sudo -u forgejo /usr/local/bin/forgejo admin user create \
|
||||||
--admin \
|
--admin \
|
||||||
|
|
@ -195,16 +199,9 @@ This deploys two stacks:
|
||||||
|
|
||||||
CI/CD is handled by GitHub Actions — PRs run CI, merges to `main` deploy via the reusable CDK workflow.
|
CI/CD is handled by GitHub Actions — PRs run CI, merges to `main` deploy via the reusable CDK workflow.
|
||||||
|
|
||||||
## Post-deploy: update running instance backup path
|
## Post-deploy: store Slack webhook
|
||||||
|
|
||||||
After the first deploy with the 3-2-1 changes, the running instance's backup script still uses the old S3 path (without the `archive/` prefix). Update it via SSM:
|
Store the Slack webhook URL for backup verification alerts:
|
||||||
|
|
||||||
```bash
|
|
||||||
aws ssm start-session --target i-0d3005fb3c36124cd
|
|
||||||
sudo sed -i 's|s3://forgejo-backups-328440206208/${TIMESTAMP}/|s3://forgejo-backups-328440206208/archive/${TIMESTAMP}/|' /usr/local/bin/forgejo-backup.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
Also store the Slack webhook URL for backup verification alerts:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
aws secretsmanager create-secret --name forgejo/slack-webhook \
|
aws secretsmanager create-secret --name forgejo/slack-webhook \
|
||||||
|
|
@ -217,7 +214,8 @@ aws secretsmanager create-secret --name forgejo/slack-webhook \
|
||||||
Update the `FORGEJO_VERSION` constant in `lib/forgejo-stack.ts` and deploy. This replaces the instance, so ensure the latest EBS snapshot is available for data recovery if needed. Alternatively, update in-place via SSM:
|
Update the `FORGEJO_VERSION` constant in `lib/forgejo-stack.ts` and deploy. This replaces the instance, so ensure the latest EBS snapshot is available for data recovery if needed. Alternatively, update in-place via SSM:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
aws ssm start-session --target i-0d3005fb3c36124cd
|
INSTANCE_ID=$(aws cloudformation describe-stacks --stack-name forgejo --query 'Stacks[0].Outputs[?OutputKey==`InstanceId`].OutputValue' --output text)
|
||||||
|
aws ssm start-session --target "$INSTANCE_ID"
|
||||||
|
|
||||||
sudo systemctl stop forgejo
|
sudo systemctl stop forgejo
|
||||||
sudo curl -Lo /usr/local/bin/forgejo "https://codeberg.org/forgejo/forgejo/releases/download/v<NEW_VERSION>/forgejo-<NEW_VERSION>-linux-arm64"
|
sudo curl -Lo /usr/local/bin/forgejo "https://codeberg.org/forgejo/forgejo/releases/download/v<NEW_VERSION>/forgejo-<NEW_VERSION>-linux-arm64"
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ const replicaStack = new ForgejoReplicaStack(app, "forgejo-replica", {
|
||||||
const forgejoStack = new ForgejoStack(app, "forgejo", {
|
const forgejoStack = new ForgejoStack(app, "forgejo", {
|
||||||
stackName: "forgejo",
|
stackName: "forgejo",
|
||||||
env: { account: "328440206208", region: "us-east-1" },
|
env: { account: "328440206208", region: "us-east-1" },
|
||||||
|
replicaBucketArn: replicaStack.replicaBucketArn,
|
||||||
|
replicaBucketName: replicaStack.replicaBucketName,
|
||||||
});
|
});
|
||||||
|
|
||||||
forgejoStack.addDependency(replicaStack);
|
forgejoStack.addDependency(replicaStack);
|
||||||
|
|
|
||||||
|
|
@ -41,9 +41,10 @@ def _check_s3_bucket(client, bucket, label):
|
||||||
today = now.strftime("%Y-%m-%d")
|
today = now.strftime("%Y-%m-%d")
|
||||||
yesterday = (now - timedelta(days=1)).strftime("%Y-%m-%d")
|
yesterday = (now - timedelta(days=1)).strftime("%Y-%m-%d")
|
||||||
contents = []
|
contents = []
|
||||||
|
paginator = client.get_paginator("list_objects_v2")
|
||||||
for date_prefix in [today, yesterday]:
|
for date_prefix in [today, yesterday]:
|
||||||
resp = client.list_objects_v2(Bucket=bucket, Prefix=f"archive/{date_prefix}/")
|
for page in paginator.paginate(Bucket=bucket, Prefix=f"archive/{date_prefix}/"):
|
||||||
contents.extend(resp.get("Contents", []))
|
contents.extend(page.get("Contents", []))
|
||||||
if not contents:
|
if not contents:
|
||||||
return False, f"{label}: No objects found under archive/ for last 2 days"
|
return False, f"{label}: No objects found under archive/ for last 2 days"
|
||||||
latest = max(contents, key=lambda o: o["LastModified"])
|
latest = max(contents, key=lambda o: o["LastModified"])
|
||||||
|
|
@ -113,13 +114,17 @@ def _restore_test():
|
||||||
try:
|
try:
|
||||||
now = datetime.now(timezone.utc)
|
now = datetime.now(timezone.utc)
|
||||||
contents = []
|
contents = []
|
||||||
|
paginator = s3.get_paginator("list_objects_v2")
|
||||||
for days_ago in range(7):
|
for days_ago in range(7):
|
||||||
date_prefix = (now - timedelta(days=days_ago)).strftime("%Y-%m-%d")
|
date_prefix = (now - timedelta(days=days_ago)).strftime("%Y-%m-%d")
|
||||||
resp = s3.list_objects_v2(Bucket=SOURCE_BUCKET, Prefix=f"archive/{date_prefix}/")
|
for page in paginator.paginate(Bucket=SOURCE_BUCKET, Prefix=f"archive/{date_prefix}/"):
|
||||||
contents.extend(resp.get("Contents", []))
|
contents.extend(page.get("Contents", []))
|
||||||
if not contents:
|
if not contents:
|
||||||
return [{"pass": False, "msg": "Restore test: No dumps found in source bucket (last 7 days)"}]
|
return [{"pass": False, "msg": "Restore test: No dumps found in source bucket (last 7 days)"}]
|
||||||
latest = max(contents, key=lambda o: o["LastModified"])
|
latest = max(contents, key=lambda o: o["LastModified"])
|
||||||
|
max_bytes = 4 * 1024 * 1024 * 1024 - 512 * 1024 * 1024
|
||||||
|
if latest["Size"] > max_bytes:
|
||||||
|
return [{"pass": False, "msg": f"Restore test: Dump too large for ephemeral storage ({latest['Size'] / 1_000_000_000:.1f} GB)"}]
|
||||||
results.append({"pass": True, "msg": f"Restore test: Using {latest['Key']} ({latest['Size'] / 1_000_000:.1f} MB)"})
|
results.append({"pass": True, "msg": f"Restore test: Using {latest['Key']} ({latest['Size'] / 1_000_000:.1f} MB)"})
|
||||||
|
|
||||||
with tempfile.TemporaryDirectory() as tmpdir:
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import * as cdk from "aws-cdk-lib";
|
import * as cdk from "aws-cdk-lib";
|
||||||
|
import * as cloudwatch from "aws-cdk-lib/aws-cloudwatch";
|
||||||
import * as events from "aws-cdk-lib/aws-events";
|
import * as events from "aws-cdk-lib/aws-events";
|
||||||
import * as events_targets from "aws-cdk-lib/aws-events-targets";
|
import * as events_targets from "aws-cdk-lib/aws-events-targets";
|
||||||
import * as iam from "aws-cdk-lib/aws-iam";
|
import * as iam from "aws-cdk-lib/aws-iam";
|
||||||
|
|
@ -75,7 +76,11 @@ export class BackupVerification extends Construct {
|
||||||
new events.Rule(this, "DailyCheck", {
|
new events.Rule(this, "DailyCheck", {
|
||||||
ruleName: "forgejo-backup-daily-check",
|
ruleName: "forgejo-backup-daily-check",
|
||||||
schedule: events.Schedule.cron({ hour: "8", minute: "0" }),
|
schedule: events.Schedule.cron({ hour: "8", minute: "0" }),
|
||||||
targets: [new events_targets.LambdaFunction(fn)],
|
targets: [
|
||||||
|
new events_targets.LambdaFunction(fn, {
|
||||||
|
event: events.RuleTargetInput.fromObject({ mode: "daily" }),
|
||||||
|
}),
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
new events.Rule(this, "MonthlyRestoreTest", {
|
new events.Rule(this, "MonthlyRestoreTest", {
|
||||||
|
|
@ -91,5 +96,15 @@ export class BackupVerification extends Construct {
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
new cloudwatch.Alarm(this, "ErrorAlarm", {
|
||||||
|
alarmName: "forgejo-backup-verification-errors",
|
||||||
|
alarmDescription: "Backup verification Lambda is failing — Slack notifications may not be firing",
|
||||||
|
metric: fn.metricErrors({ period: cdk.Duration.hours(1) }),
|
||||||
|
threshold: 1,
|
||||||
|
evaluationPeriods: 1,
|
||||||
|
treatMissingData: cloudwatch.TreatMissingData.BREACHING,
|
||||||
|
comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,13 @@ import * as s3 from "aws-cdk-lib/aws-s3";
|
||||||
import { Construct } from "constructs";
|
import { Construct } from "constructs";
|
||||||
|
|
||||||
export class ForgejoReplicaStack extends cdk.Stack {
|
export class ForgejoReplicaStack extends cdk.Stack {
|
||||||
|
public readonly replicaBucketArn: string;
|
||||||
|
public readonly replicaBucketName: string;
|
||||||
|
|
||||||
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
|
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
|
||||||
super(scope, id, props);
|
super(scope, id, props);
|
||||||
|
|
||||||
new s3.Bucket(this, "ReplicaBucket", {
|
const replicaBucket = new s3.Bucket(this, "ReplicaBucket", {
|
||||||
bucketName: "forgejo-backups-replica-328440206208",
|
bucketName: "forgejo-backups-replica-328440206208",
|
||||||
encryption: s3.BucketEncryption.S3_MANAGED,
|
encryption: s3.BucketEncryption.S3_MANAGED,
|
||||||
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
|
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
|
||||||
|
|
@ -33,5 +36,8 @@ export class ForgejoReplicaStack extends cdk.Stack {
|
||||||
],
|
],
|
||||||
removalPolicy: cdk.RemovalPolicy.RETAIN,
|
removalPolicy: cdk.RemovalPolicy.RETAIN,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.replicaBucketArn = replicaBucket.bucketArn;
|
||||||
|
this.replicaBucketName = replicaBucket.bucketName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,20 @@ import * as elbv2 from "aws-cdk-lib/aws-elasticloadbalancingv2";
|
||||||
import * as elbv2_targets from "aws-cdk-lib/aws-elasticloadbalancingv2-targets";
|
import * as elbv2_targets from "aws-cdk-lib/aws-elasticloadbalancingv2-targets";
|
||||||
import * as route53 from "aws-cdk-lib/aws-route53";
|
import * as route53 from "aws-cdk-lib/aws-route53";
|
||||||
import * as route53Targets from "aws-cdk-lib/aws-route53-targets";
|
import * as route53Targets from "aws-cdk-lib/aws-route53-targets";
|
||||||
|
import * as ssm from "aws-cdk-lib/aws-ssm";
|
||||||
import * as dlm from "aws-cdk-lib/aws-dlm";
|
import * as dlm from "aws-cdk-lib/aws-dlm";
|
||||||
import { Construct } from "constructs";
|
import { Construct } from "constructs";
|
||||||
import { BackupVerification } from "./constructs/backup-verification";
|
import { BackupVerification } from "./constructs/backup-verification";
|
||||||
|
|
||||||
const FORGEJO_VERSION = "10.0.1";
|
const FORGEJO_VERSION = "10.0.1";
|
||||||
|
|
||||||
|
interface ForgejoStackProps extends cdk.StackProps {
|
||||||
|
replicaBucketArn: string;
|
||||||
|
replicaBucketName: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class ForgejoStack extends cdk.Stack {
|
export class ForgejoStack extends cdk.Stack {
|
||||||
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
|
constructor(scope: Construct, id: string, props: ForgejoStackProps) {
|
||||||
super(scope, id, props);
|
super(scope, id, props);
|
||||||
|
|
||||||
const vpc = ec2.Vpc.fromLookup(this, "SeaHavenVpc", {
|
const vpc = ec2.Vpc.fromLookup(this, "SeaHavenVpc", {
|
||||||
|
|
@ -84,7 +90,15 @@ export class ForgejoStack extends cdk.Stack {
|
||||||
|
|
||||||
backupBucket.grantReadWrite(role);
|
backupBucket.grantReadWrite(role);
|
||||||
|
|
||||||
const replicaBucketArn = "arn:aws:s3:::forgejo-backups-replica-328440206208";
|
const backupS3Prefix = new ssm.StringParameter(this, "BackupS3Prefix", {
|
||||||
|
parameterName: "/forgejo/backup-s3-prefix",
|
||||||
|
description: "S3 key prefix for Forgejo backup dumps",
|
||||||
|
stringValue: "archive",
|
||||||
|
});
|
||||||
|
|
||||||
|
backupS3Prefix.grantRead(role);
|
||||||
|
|
||||||
|
const replicaBucketArn = props.replicaBucketArn;
|
||||||
|
|
||||||
const replicationRole = new iam.Role(this, "ReplicationRole", {
|
const replicationRole = new iam.Role(this, "ReplicationRole", {
|
||||||
roleName: "forgejo-s3-replication",
|
roleName: "forgejo-s3-replication",
|
||||||
|
|
@ -153,9 +167,6 @@ export class ForgejoStack extends cdk.Stack {
|
||||||
secretAccessKey: gcsTransferKey.secretAccessKey,
|
secretAccessKey: gcsTransferKey.secretAccessKey,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const gcsTransferCredentialsResource = gcsTransferCredentials.node.defaultChild as secretsmanager.CfnSecret;
|
|
||||||
gcsTransferCredentialsResource.overrideLogicalId("GcsTransferCredentials");
|
|
||||||
|
|
||||||
const userData = ec2.UserData.forLinux();
|
const userData = ec2.UserData.forLinux();
|
||||||
userData.addCommands(
|
userData.addCommands(
|
||||||
"set -euxo pipefail",
|
"set -euxo pipefail",
|
||||||
|
|
@ -239,11 +250,12 @@ export class ForgejoStack extends cdk.Stack {
|
||||||
"#!/bin/bash",
|
"#!/bin/bash",
|
||||||
"set -euo pipefail",
|
"set -euo pipefail",
|
||||||
"TIMESTAMP=$(date +%Y-%m-%d)",
|
"TIMESTAMP=$(date +%Y-%m-%d)",
|
||||||
|
"S3_PREFIX=$(aws ssm get-parameter --name /forgejo/backup-s3-prefix --query Parameter.Value --output text --region us-east-1 || echo 'archive')",
|
||||||
"DUMP_DIR=$(mktemp -d)",
|
"DUMP_DIR=$(mktemp -d)",
|
||||||
"chown forgejo:forgejo \"$DUMP_DIR\"",
|
"chown forgejo:forgejo \"$DUMP_DIR\"",
|
||||||
"cd \"$DUMP_DIR\"",
|
"cd \"$DUMP_DIR\"",
|
||||||
"sudo -u forgejo /usr/local/bin/forgejo dump --config /etc/forgejo/app.ini --type tar.gz --file \"$DUMP_DIR/forgejo-${TIMESTAMP}.tar.gz\"",
|
"sudo -u forgejo /usr/local/bin/forgejo dump --config /etc/forgejo/app.ini --type tar.gz --file \"$DUMP_DIR/forgejo-${TIMESTAMP}.tar.gz\"",
|
||||||
"aws s3 cp \"$DUMP_DIR/forgejo-${TIMESTAMP}.tar.gz\" s3://forgejo-backups-328440206208/archive/${TIMESTAMP}/forgejo-${TIMESTAMP}.tar.gz",
|
"aws s3 cp \"$DUMP_DIR/forgejo-${TIMESTAMP}.tar.gz\" s3://forgejo-backups-328440206208/${S3_PREFIX}/${TIMESTAMP}/forgejo-${TIMESTAMP}.tar.gz",
|
||||||
"rm -rf \"$DUMP_DIR\"",
|
"rm -rf \"$DUMP_DIR\"",
|
||||||
"BAKEOF",
|
"BAKEOF",
|
||||||
"chmod +x /usr/local/bin/forgejo-backup.sh",
|
"chmod +x /usr/local/bin/forgejo-backup.sh",
|
||||||
|
|
@ -441,7 +453,7 @@ export class ForgejoStack extends cdk.Stack {
|
||||||
|
|
||||||
new BackupVerification(this, "BackupVerification", {
|
new BackupVerification(this, "BackupVerification", {
|
||||||
sourceBucket: backupBucket,
|
sourceBucket: backupBucket,
|
||||||
replicaBucketName: "forgejo-backups-replica-328440206208",
|
replicaBucketName: props.replicaBucketName,
|
||||||
gcsBucket: "forgejo-backups-offsite-seahaven",
|
gcsBucket: "forgejo-backups-offsite-seahaven",
|
||||||
gcsSaSecretName: "forgejo/gcs-sa-key",
|
gcsSaSecretName: "forgejo/gcs-sa-key",
|
||||||
slackWebhookSecretName: "forgejo/slack-webhook",
|
slackWebhookSecretName: "forgejo/slack-webhook",
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue