mirror of
https://github.com/Sea-Haven-Industries/forgejo.git
synced 2026-09-30 04:13:11 +00:00
Some checks failed
Deploy / deploy (push) Has been cancelled
* Initial plan * fix: remove archive/ prefix from S3 lifecycle rule to cover root-level objects The archive-to-glacier rule had prefix: "archive/" which caused pre-migration backup files written to the bucket root to be excluded from any transition policy. Removing the prefix makes the rule apply to all objects in the bucket, ensuring root-level dump files also transition to Glacier after 30 days. Closes #4 * fix: remove archive/ prefix from lifecycle rule in forgejo-replica-stack.ts --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
539 lines
21 KiB
TypeScript
539 lines
21 KiB
TypeScript
import * as cdk from "aws-cdk-lib";
|
|
import * as ec2 from "aws-cdk-lib/aws-ec2";
|
|
import * as iam from "aws-cdk-lib/aws-iam";
|
|
import * as s3 from "aws-cdk-lib/aws-s3";
|
|
import * as secretsmanager from "aws-cdk-lib/aws-secretsmanager";
|
|
import * as elbv2 from "aws-cdk-lib/aws-elasticloadbalancingv2";
|
|
import * as elbv2_targets from "aws-cdk-lib/aws-elasticloadbalancingv2-targets";
|
|
import * as route53 from "aws-cdk-lib/aws-route53";
|
|
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 { Construct } from "constructs";
|
|
import { BackupVerification } from "./constructs/backup-verification";
|
|
|
|
const FORGEJO_VERSION = "10.0.1";
|
|
|
|
interface ForgejoStackProps extends cdk.StackProps {
|
|
replicaBucketArn: string;
|
|
replicaBucketName: string;
|
|
}
|
|
|
|
export class ForgejoStack extends cdk.Stack {
|
|
constructor(scope: Construct, id: string, props: ForgejoStackProps) {
|
|
super(scope, id, props);
|
|
|
|
const vpc = ec2.Vpc.fromLookup(this, "SeaHavenVpc", {
|
|
vpcId: "vpc-0d3d4b67bd0cf8a68",
|
|
});
|
|
|
|
const privateSubnet1 = ec2.Subnet.fromSubnetAttributes(
|
|
this, "PrivateSubnet1", {
|
|
subnetId: "subnet-04e38c507e96f1926",
|
|
availabilityZone: "us-east-1a",
|
|
}
|
|
);
|
|
|
|
const sg = new ec2.SecurityGroup(this, "SecurityGroup", {
|
|
vpc,
|
|
securityGroupName: "forgejo",
|
|
description: "Forgejo git server - VPC and VPN access",
|
|
allowAllOutbound: true,
|
|
});
|
|
|
|
const albSg = ec2.SecurityGroup.fromSecurityGroupId(
|
|
this, "AlbSg", "sg-0b0301deed193258a",
|
|
{ allowAllOutbound: false }
|
|
);
|
|
albSg.addEgressRule(sg, ec2.Port.tcp(3000), "Forgejo HTTP");
|
|
sg.addIngressRule(albSg, ec2.Port.tcp(3000), "HTTP from ALB");
|
|
sg.addIngressRule(ec2.Peer.ipv4("10.20.0.0/16"), ec2.Port.tcp(3000), "HTTP from VPC");
|
|
sg.addIngressRule(ec2.Peer.ipv4("10.10.0.0/16"), ec2.Port.tcp(3000), "HTTP from office VPN");
|
|
sg.addIngressRule(ec2.Peer.ipv4("10.20.0.0/16"), ec2.Port.tcp(2222), "SSH git from VPC");
|
|
sg.addIngressRule(ec2.Peer.ipv4("10.10.0.0/16"), ec2.Port.tcp(2222), "SSH git from office VPN");
|
|
|
|
const role = new iam.Role(this, "InstanceRole", {
|
|
roleName: "forgejo-instance",
|
|
assumedBy: new iam.ServicePrincipal("ec2.amazonaws.com"),
|
|
managedPolicies: [
|
|
iam.ManagedPolicy.fromAwsManagedPolicyName("AmazonSSMManagedInstanceCore"),
|
|
],
|
|
});
|
|
|
|
role.addToPolicy(new iam.PolicyStatement({
|
|
actions: ["secretsmanager:GetSecretValue"],
|
|
resources: [
|
|
`arn:aws:secretsmanager:us-east-1:328440206208:secret:forgejo/*`,
|
|
],
|
|
}));
|
|
|
|
const backupBucket = new s3.Bucket(this, "BackupBucket", {
|
|
bucketName: "forgejo-backups-328440206208",
|
|
encryption: s3.BucketEncryption.S3_MANAGED,
|
|
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
|
|
versioned: true,
|
|
lifecycleRules: [
|
|
{
|
|
id: "archive-to-glacier",
|
|
transitions: [
|
|
{ storageClass: s3.StorageClass.GLACIER, transitionAfter: cdk.Duration.days(30) },
|
|
],
|
|
},
|
|
{
|
|
id: "cleanup-noncurrent-versions",
|
|
noncurrentVersionExpiration: cdk.Duration.days(90),
|
|
},
|
|
],
|
|
removalPolicy: cdk.RemovalPolicy.RETAIN,
|
|
});
|
|
|
|
backupBucket.grantReadWrite(role);
|
|
|
|
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", {
|
|
roleName: "forgejo-s3-replication",
|
|
assumedBy: new iam.ServicePrincipal("s3.amazonaws.com"),
|
|
});
|
|
|
|
replicationRole.addToPolicy(new iam.PolicyStatement({
|
|
actions: [
|
|
"s3:GetReplicationConfiguration",
|
|
"s3:ListBucket",
|
|
],
|
|
resources: [backupBucket.bucketArn],
|
|
}));
|
|
|
|
replicationRole.addToPolicy(new iam.PolicyStatement({
|
|
actions: [
|
|
"s3:GetObjectVersionForReplication",
|
|
"s3:GetObjectVersionAcl",
|
|
"s3:GetObjectVersionTagging",
|
|
],
|
|
resources: [`${backupBucket.bucketArn}/*`],
|
|
}));
|
|
|
|
replicationRole.addToPolicy(new iam.PolicyStatement({
|
|
actions: [
|
|
"s3:ReplicateObject",
|
|
"s3:ReplicateDelete",
|
|
"s3:ReplicateTags",
|
|
],
|
|
resources: [`${replicaBucketArn}/*`],
|
|
}));
|
|
|
|
const cfnBucket = backupBucket.node.defaultChild as s3.CfnBucket;
|
|
cfnBucket.replicationConfiguration = {
|
|
role: replicationRole.roleArn,
|
|
rules: [{
|
|
id: "replicate-to-west",
|
|
status: "Enabled",
|
|
priority: 1,
|
|
filter: { prefix: "" },
|
|
deleteMarkerReplication: { status: "Disabled" },
|
|
destination: {
|
|
bucket: replicaBucketArn,
|
|
storageClass: "STANDARD",
|
|
},
|
|
}],
|
|
};
|
|
|
|
const gcsTransferUser = new iam.User(this, "GcsTransferUser", {
|
|
userName: "forgejo-gcs-transfer",
|
|
});
|
|
|
|
gcsTransferUser.addToPolicy(new iam.PolicyStatement({
|
|
actions: ["s3:GetObject", "s3:ListBucket"],
|
|
resources: [backupBucket.bucketArn, `${backupBucket.bucketArn}/*`],
|
|
}));
|
|
|
|
const gcsTransferKey = new iam.AccessKey(this, "GcsTransferAccessKey", {
|
|
user: gcsTransferUser,
|
|
});
|
|
|
|
const gcsTransferCredentials = new secretsmanager.Secret(this, "GcsTransferCredentials", {
|
|
secretName: "forgejo/gcs-transfer-credentials",
|
|
secretObjectValue: {
|
|
accessKeyId: cdk.SecretValue.unsafePlainText(gcsTransferKey.accessKeyId),
|
|
secretAccessKey: gcsTransferKey.secretAccessKey,
|
|
},
|
|
});
|
|
// Preserve the logical ID to avoid CloudFormation replacement
|
|
const gcsTransferCredentialsResource = gcsTransferCredentials.node.defaultChild as secretsmanager.CfnSecret;
|
|
gcsTransferCredentialsResource.overrideLogicalId("GcsTransferCredentials");
|
|
|
|
const userData = ec2.UserData.forLinux();
|
|
userData.addCommands(
|
|
"set -euxo pipefail",
|
|
"",
|
|
`FORGEJO_VERSION="${FORGEJO_VERSION}"`,
|
|
"",
|
|
"dnf install -y git cronie",
|
|
"systemctl enable --now crond",
|
|
"",
|
|
"useradd --system --shell /bin/bash --home-dir /home/forgejo --create-home forgejo",
|
|
"",
|
|
"# ── Persistent data volume (wait for CfnVolumeAttachment) ──",
|
|
"until lsblk -dno NAME | grep -v $(lsblk -no PKNAME $(findmnt -n -o SOURCE /) 2>/dev/null || echo xvda) | grep -q .; do",
|
|
" echo 'Waiting for data volume...'",
|
|
" sleep 5",
|
|
"done",
|
|
"DATA_DEVICE=/dev/$(lsblk -dno NAME | grep -v $(lsblk -no PKNAME $(findmnt -n -o SOURCE /) 2>/dev/null || echo xvda) | head -1)",
|
|
"if ! blkid \"$DATA_DEVICE\"; then",
|
|
" mkfs.ext4 -L forgejo-data \"$DATA_DEVICE\"",
|
|
"fi",
|
|
"mkdir -p /var/lib/forgejo",
|
|
"echo \"LABEL=forgejo-data /var/lib/forgejo ext4 defaults,nofail 0 2\" >> /etc/fstab",
|
|
"mount -a",
|
|
"",
|
|
"mkdir -p /var/lib/forgejo/{data,log}",
|
|
"chown -R forgejo:forgejo /var/lib/forgejo",
|
|
"chmod 750 /var/lib/forgejo",
|
|
"",
|
|
'curl -Lo /usr/local/bin/forgejo "https://codeberg.org/forgejo/forgejo/releases/download/v${FORGEJO_VERSION}/forgejo-${FORGEJO_VERSION}-linux-arm64"',
|
|
"chmod +x /usr/local/bin/forgejo",
|
|
"",
|
|
"mkdir -p /etc/forgejo",
|
|
"chown root:forgejo /etc/forgejo",
|
|
"chmod 770 /etc/forgejo",
|
|
"",
|
|
"cat > /etc/forgejo/app.ini << 'INIEOF'",
|
|
"APP_NAME = Sea Haven Git",
|
|
"",
|
|
"[server]",
|
|
"DOMAIN = forgejo.seahaven.com",
|
|
"ROOT_URL = https://forgejo.seahaven.com/",
|
|
"HTTP_PORT = 3000",
|
|
"START_SSH_SERVER = true",
|
|
"SSH_PORT = 2222",
|
|
"SSH_LISTEN_PORT = 2222",
|
|
"LFS_START_SERVER = true",
|
|
"",
|
|
"[database]",
|
|
"DB_TYPE = sqlite3",
|
|
"PATH = /var/lib/forgejo/data/forgejo.db",
|
|
"",
|
|
"[repository]",
|
|
"ROOT = /var/lib/forgejo/data/repositories",
|
|
"",
|
|
"[log]",
|
|
"ROOT_PATH = /var/lib/forgejo/log",
|
|
"",
|
|
"[security]",
|
|
"INSTALL_LOCK = true",
|
|
"",
|
|
"[service]",
|
|
"DISABLE_REGISTRATION = true",
|
|
"",
|
|
"[mirror]",
|
|
"DEFAULT_INTERVAL = 1h",
|
|
"INIEOF",
|
|
"",
|
|
"chown root:forgejo /etc/forgejo/app.ini",
|
|
"chmod 660 /etc/forgejo/app.ini",
|
|
"",
|
|
"cat > /etc/systemd/system/forgejo.service << 'SVCEOF'",
|
|
"[Unit]",
|
|
"Description=Forgejo",
|
|
"After=network.target",
|
|
"",
|
|
"[Service]",
|
|
"Type=simple",
|
|
"User=forgejo",
|
|
"Group=forgejo",
|
|
"WorkingDirectory=/var/lib/forgejo",
|
|
"ExecStart=/usr/local/bin/forgejo web --config /etc/forgejo/app.ini",
|
|
"Restart=always",
|
|
"RestartSec=5",
|
|
"Environment=USER=forgejo HOME=/home/forgejo FORGEJO_WORK_DIR=/var/lib/forgejo",
|
|
"",
|
|
"[Install]",
|
|
"WantedBy=multi-user.target",
|
|
"SVCEOF",
|
|
"",
|
|
"systemctl daemon-reload",
|
|
"",
|
|
"# ── Restore from latest S3 dump if the data volume is empty (first boot or volume loss) ──",
|
|
"if [ ! -f /var/lib/forgejo/data/forgejo.db ]; then",
|
|
" echo 'No database on data volume - restoring latest backup from S3'",
|
|
" S3_PREFIX=$(aws ssm get-parameter --name /forgejo/backup-s3-prefix --query Parameter.Value --output text --region us-east-1 || echo 'archive')",
|
|
" LATEST=$(aws s3 ls s3://forgejo-backups-328440206208/${S3_PREFIX}/ --region us-east-1 | awk '{print $2}' | sort | tail -1 | tr -d '/')",
|
|
" if [ -n \"$LATEST\" ]; then",
|
|
" FILE=$(aws s3 ls s3://forgejo-backups-328440206208/${S3_PREFIX}/${LATEST}/ --region us-east-1 | awk '{print $4}' | tail -1)",
|
|
" RESTORE_DIR=$(mktemp -d)",
|
|
" aws s3 cp \"s3://forgejo-backups-328440206208/${S3_PREFIX}/${LATEST}/${FILE}\" \"$RESTORE_DIR/dump.tar.gz\" --region us-east-1",
|
|
" tar xzf \"$RESTORE_DIR/dump.tar.gz\" -C \"$RESTORE_DIR\"",
|
|
" cp -a \"$RESTORE_DIR\"/data/. /var/lib/forgejo/data/",
|
|
" mkdir -p /var/lib/forgejo/data/repositories",
|
|
" cp -a \"$RESTORE_DIR\"/repos/. /var/lib/forgejo/data/repositories/",
|
|
" chown -R forgejo:forgejo /var/lib/forgejo",
|
|
" rm -rf \"$RESTORE_DIR\"",
|
|
" else",
|
|
" echo 'No backup found in S3 - starting fresh'",
|
|
" fi",
|
|
"fi",
|
|
"",
|
|
"systemctl enable --now forgejo",
|
|
"",
|
|
"cat > /usr/local/bin/forgejo-backup.sh << 'BAKEOF'",
|
|
"#!/bin/bash",
|
|
"set -euo pipefail",
|
|
"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)",
|
|
"chown forgejo:forgejo \"$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\"",
|
|
"aws s3 cp \"$DUMP_DIR/forgejo-${TIMESTAMP}.tar.gz\" s3://forgejo-backups-328440206208/${S3_PREFIX}/${TIMESTAMP}/forgejo-${TIMESTAMP}.tar.gz",
|
|
"rm -rf \"$DUMP_DIR\"",
|
|
"BAKEOF",
|
|
"chmod +x /usr/local/bin/forgejo-backup.sh",
|
|
"",
|
|
"echo '0 5 * * * root /usr/local/bin/forgejo-backup.sh >> /var/log/forgejo-backup.log 2>&1' > /etc/cron.d/forgejo-backup",
|
|
"chmod 644 /etc/cron.d/forgejo-backup",
|
|
"",
|
|
"cat > /usr/local/bin/forgejo-autodiscover.sh << 'ADEOF'",
|
|
"#!/bin/bash",
|
|
"set -euo pipefail",
|
|
"GH_PAT=$(aws secretsmanager get-secret-value --secret-id forgejo/github-pat --query SecretString --output text --region us-east-1)",
|
|
"FORGEJO_TOKEN=$(aws secretsmanager get-secret-value --secret-id forgejo/api-token --query SecretString --output text --region us-east-1)",
|
|
"FORGEJO_URL=https://forgejo.seahaven.com/api/v1",
|
|
"GH_ORG=Sea-Haven-Industries",
|
|
"",
|
|
"gh_repos=$(curl -sf -H \"Authorization: token $GH_PAT\" \"https://api.github.com/orgs/$GH_ORG/repos?per_page=100&type=all\" | python3 -c \"",
|
|
"import sys, json",
|
|
"for r in json.load(sys.stdin):",
|
|
" print(f\\\"{r['name']}\\\\t{r['archived']}\\\")",
|
|
"\")",
|
|
"",
|
|
"forgejo_repos=$(curl -sf -H \"Authorization: token $FORGEJO_TOKEN\" \"$FORGEJO_URL/repos/search?limit=100\" | python3 -c \"",
|
|
"import sys, json",
|
|
"data = json.load(sys.stdin)",
|
|
"repos = data.get('data', data) if isinstance(data, dict) else data",
|
|
"for r in repos:",
|
|
" print(r['name'])",
|
|
"\")",
|
|
"",
|
|
"while IFS=$'\\t' read -r name archived; do",
|
|
" if ! echo \"$forgejo_repos\" | grep -qx \"$name\"; then",
|
|
" mirror=true",
|
|
" [ \"$archived\" = \"True\" ] && mirror=false",
|
|
" echo \"$(date -Is) Discovering: $name (mirror=$mirror)\"",
|
|
" curl -sf -X POST \"$FORGEJO_URL/repos/migrate\" \\",
|
|
" -H \"Authorization: token $FORGEJO_TOKEN\" \\",
|
|
" -H \"Content-Type: application/json\" \\",
|
|
" -d \"{",
|
|
" \\\"clone_addr\\\": \\\"https://github.com/$GH_ORG/${name}.git\\\",",
|
|
" \\\"auth_token\\\": \\\"${GH_PAT}\\\",",
|
|
" \\\"repo_name\\\": \\\"${name}\\\",",
|
|
" \\\"repo_owner\\\": \\\"adam\\\",",
|
|
" \\\"service\\\": \\\"github\\\",",
|
|
" \\\"mirror\\\": ${mirror},",
|
|
" \\\"issues\\\": true,",
|
|
" \\\"labels\\\": true,",
|
|
" \\\"milestones\\\": true,",
|
|
" \\\"pull_requests\\\": true,",
|
|
" \\\"releases\\\": true,",
|
|
" \\\"wiki\\\": true",
|
|
" }\" > /dev/null",
|
|
" fi",
|
|
"done <<< \"$gh_repos\"",
|
|
"ADEOF",
|
|
"chmod +x /usr/local/bin/forgejo-autodiscover.sh",
|
|
"",
|
|
"cat > /usr/local/bin/forgejo-refresh-tokens.sh << 'RTEOF'",
|
|
"#!/bin/bash",
|
|
"set -euo pipefail",
|
|
"GH_PAT=$(aws secretsmanager get-secret-value --secret-id forgejo/github-pat --query SecretString --output text --region us-east-1)",
|
|
"FORGEJO_TOKEN=$(aws secretsmanager get-secret-value --secret-id forgejo/api-token --query SecretString --output text --region us-east-1)",
|
|
"FORGEJO_URL=https://forgejo.seahaven.com/api/v1",
|
|
"REPO_ROOT=/var/lib/forgejo/data/repositories/adam",
|
|
"",
|
|
"export GIT_CONFIG_COUNT=1",
|
|
"export GIT_CONFIG_KEY_0=safe.directory",
|
|
"export GIT_CONFIG_VALUE_0='*'",
|
|
"",
|
|
"mirrors=$(curl -sf -H \"Authorization: token $FORGEJO_TOKEN\" \"$FORGEJO_URL/repos/search?limit=100\" | python3 -c \"",
|
|
"import sys, json",
|
|
"data = json.load(sys.stdin)",
|
|
"repos = data.get('data', data) if isinstance(data, dict) else data",
|
|
"for r in repos:",
|
|
" if r.get('mirror', False):",
|
|
" print(r['name'])",
|
|
"\")",
|
|
"",
|
|
"while read -r repo_name; do",
|
|
" [ -z \"$repo_name\" ] && continue",
|
|
" repo_dir=\"$REPO_ROOT/${repo_name}.git\"",
|
|
" if [ -d \"$repo_dir\" ]; then",
|
|
" new_url=\"https://${GH_PAT}@github.com/Sea-Haven-Industries/${repo_name}.git\"",
|
|
" git -C \"$repo_dir\" remote set-url origin \"$new_url\" 2>/dev/null && echo \"$(date -Is) Refreshed: $repo_name\"",
|
|
" fi",
|
|
"done <<< \"$mirrors\"",
|
|
"RTEOF",
|
|
"chmod +x /usr/local/bin/forgejo-refresh-tokens.sh",
|
|
"",
|
|
"printf '%s\\n' '0 * * * * root /usr/local/bin/forgejo-autodiscover.sh >> /var/log/forgejo-autodiscover.log 2>&1' > /etc/cron.d/forgejo-autodiscover",
|
|
"printf '%s\\n' '30 4 * * * root /usr/local/bin/forgejo-refresh-tokens.sh >> /var/log/forgejo-refresh-tokens.log 2>&1' > /etc/cron.d/forgejo-refresh-tokens",
|
|
"chmod 644 /etc/cron.d/forgejo-autodiscover /etc/cron.d/forgejo-refresh-tokens",
|
|
);
|
|
|
|
const instance = new ec2.Instance(this, "Instance", {
|
|
instanceName: "forgejo",
|
|
vpc,
|
|
vpcSubnets: { subnets: [privateSubnet1] },
|
|
instanceType: ec2.InstanceType.of(ec2.InstanceClass.T4G, ec2.InstanceSize.SMALL),
|
|
machineImage: ec2.MachineImage.latestAmazonLinux2023({
|
|
cpuType: ec2.AmazonLinuxCpuType.ARM_64,
|
|
// Cache the resolved AMI in cdk.context.json so deploys don't pick up
|
|
// new AL2023 releases implicitly (AMI change forces instance replacement).
|
|
// Refresh deliberately with: cdk context --reset <ami key> && cdk synth
|
|
cachedInContext: true,
|
|
}),
|
|
securityGroup: sg,
|
|
role,
|
|
userData,
|
|
blockDevices: [{
|
|
deviceName: "/dev/xvda",
|
|
volume: ec2.BlockDeviceVolume.ebs(20, {
|
|
volumeType: ec2.EbsDeviceVolumeType.GP3,
|
|
encrypted: true,
|
|
}),
|
|
}],
|
|
});
|
|
|
|
cdk.Tags.of(instance).add("forgejo-backup", "true");
|
|
|
|
// All Forgejo state (sqlite db, repositories, logs) lives on this volume,
|
|
// mounted at /var/lib/forgejo. RETAIN means it survives instance
|
|
// replacement and stack deletion; userdata mounts the existing filesystem
|
|
// (blkid guard prevents formatting) and restores from S3 only when empty.
|
|
const dataVolume = new ec2.Volume(this, "DataVolume", {
|
|
availabilityZone: "us-east-1a",
|
|
size: cdk.Size.gibibytes(50),
|
|
volumeType: ec2.EbsDeviceVolumeType.GP3,
|
|
encrypted: true,
|
|
removalPolicy: cdk.RemovalPolicy.RETAIN,
|
|
});
|
|
cdk.Tags.of(dataVolume).add("Name", "forgejo-data");
|
|
cdk.Tags.of(dataVolume).add("forgejo-backup", "true");
|
|
|
|
new ec2.CfnVolumeAttachment(this, "DataVolumeAttachment", {
|
|
instanceId: instance.instanceId,
|
|
volumeId: dataVolume.volumeId,
|
|
device: "/dev/xvdf",
|
|
});
|
|
|
|
const dlmRole = new iam.Role(this, "DlmRole", {
|
|
roleName: "forgejo-dlm",
|
|
assumedBy: new iam.ServicePrincipal("dlm.amazonaws.com"),
|
|
managedPolicies: [
|
|
iam.ManagedPolicy.fromAwsManagedPolicyName(
|
|
"service-role/AWSDataLifecycleManagerServiceRole"
|
|
),
|
|
],
|
|
});
|
|
|
|
new dlm.CfnLifecyclePolicy(this, "SnapshotPolicy", {
|
|
description: "Nightly EBS snapshots for Forgejo",
|
|
state: "ENABLED",
|
|
executionRoleArn: dlmRole.roleArn,
|
|
policyDetails: {
|
|
resourceTypes: ["INSTANCE"],
|
|
targetTags: [{ key: "forgejo-backup", value: "true" }],
|
|
schedules: [{
|
|
name: "forgejo-nightly",
|
|
createRule: { interval: 24, intervalUnit: "HOURS", times: ["06:00"] },
|
|
retainRule: { count: 30 },
|
|
copyTags: true,
|
|
tagsToAdd: [{ key: "forgejo-backup", value: "true" }],
|
|
}],
|
|
},
|
|
});
|
|
|
|
const alb = elbv2.ApplicationLoadBalancer.fromApplicationLoadBalancerAttributes(
|
|
this, "Alb", {
|
|
loadBalancerArn:
|
|
"arn:aws:elasticloadbalancing:us-east-1:328440206208:loadbalancer/app/seahaven-com/222c3257354ab559",
|
|
securityGroupId: "sg-0b0301deed193258a",
|
|
loadBalancerDnsName: "seahaven-com-1856441924.us-east-1.elb.amazonaws.com",
|
|
loadBalancerCanonicalHostedZoneId: "Z35SXDOTRQ7X7K",
|
|
}
|
|
);
|
|
|
|
const httpsListener = elbv2.ApplicationListener.fromApplicationListenerAttributes(
|
|
this, "HttpsListener", {
|
|
listenerArn:
|
|
"arn:aws:elasticloadbalancing:us-east-1:328440206208:listener/app/seahaven-com/222c3257354ab559/bab8bcf0da0e2927",
|
|
securityGroup: albSg,
|
|
}
|
|
);
|
|
|
|
const targetGroup = new elbv2.ApplicationTargetGroup(this, "TargetGroup", {
|
|
targetGroupName: "forgejo",
|
|
vpc,
|
|
port: 3000,
|
|
protocol: elbv2.ApplicationProtocol.HTTP,
|
|
targetType: elbv2.TargetType.INSTANCE,
|
|
healthCheck: {
|
|
path: "/",
|
|
healthyHttpCodes: "200,302",
|
|
},
|
|
targets: [new elbv2_targets.InstanceIdTarget(instance.instanceId, 3000)],
|
|
});
|
|
|
|
new elbv2.ApplicationListenerRule(this, "ListenerRule", {
|
|
listener: httpsListener,
|
|
priority: 4,
|
|
conditions: [elbv2.ListenerCondition.hostHeaders(["forgejo.seahaven.com"])],
|
|
targetGroups: [targetGroup],
|
|
});
|
|
|
|
const hostedZone = route53.HostedZone.fromHostedZoneAttributes(
|
|
this, "SeaHavenZone", {
|
|
hostedZoneId: "Z06652411XKH89KTZD3XA",
|
|
zoneName: "seahaven.com",
|
|
}
|
|
);
|
|
|
|
new route53.ARecord(this, "DnsRecord", {
|
|
zone: hostedZone,
|
|
recordName: "forgejo",
|
|
target: route53.RecordTarget.fromAlias(
|
|
new route53Targets.LoadBalancerTarget(alb)
|
|
),
|
|
});
|
|
|
|
const backupVerification = new BackupVerification(this, "BackupVerification", {
|
|
sourceBucket: backupBucket,
|
|
replicaBucketName: props.replicaBucketName,
|
|
gcsBucket: "forgejo-backups-offsite-seahaven",
|
|
gcsSaSecretName: "forgejo/gcs-sa-key",
|
|
slackWebhookSecretName: "forgejo/slack-webhook",
|
|
});
|
|
|
|
new cdk.CfnOutput(this, "BackupVerificationFunctionArn", {
|
|
value: backupVerification.functionArn,
|
|
});
|
|
|
|
new cdk.CfnOutput(this, "ForgejoUrl", {
|
|
value: "https://forgejo.seahaven.com",
|
|
});
|
|
|
|
new cdk.CfnOutput(this, "InstanceId", {
|
|
value: instance.instanceId,
|
|
});
|
|
|
|
new cdk.CfnOutput(this, "PrivateIp", {
|
|
value: instance.instancePrivateIp,
|
|
});
|
|
}
|
|
}
|