forgejo/lib/forgejo-stack.ts
Adam Moussa a500d69716 Add Forgejo CDK stack
EC2 (t4g.small, arm64) in private subnet with VPN-only access,
DLM nightly snapshots, and Route53 DNS at forgejo.seahaven.com.
2026-05-11 17:52:37 -04:00

195 lines
6 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 route53 from "aws-cdk-lib/aws-route53";
import * as dlm from "aws-cdk-lib/aws-dlm";
import { Construct } from "constructs";
const FORGEJO_VERSION = "10.0.1";
export class ForgejoStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const vpc = ec2.Vpc.fromLookup(this, "SeaHavenVpc", {
vpcId: "vpc-0d3d4b67bd0cf8a68",
});
const privateSubnet1 = ec2.Subnet.fromSubnetId(
this, "PrivateSubnet1", "subnet-04e38c507e96f1926"
);
const sg = new ec2.SecurityGroup(this, "SecurityGroup", {
vpc,
securityGroupName: "forgejo",
description: "Forgejo git server - VPC and VPN access",
allowAllOutbound: true,
});
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"),
],
});
const userData = ec2.UserData.forLinux();
userData.addCommands(
"set -euxo pipefail",
"",
`FORGEJO_VERSION="${FORGEJO_VERSION}"`,
"",
"dnf install -y git",
"",
"useradd --system --shell /bin/bash --home-dir /home/forgejo --create-home forgejo",
"",
"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 = http://forgejo.seahaven.com:3000/",
"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",
"systemctl enable --now forgejo",
);
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,
}),
securityGroup: sg,
role,
userData,
blockDevices: [{
deviceName: "/dev/xvda",
volume: ec2.BlockDeviceVolume.ebs(50, {
volumeType: ec2.EbsDeviceVolumeType.GP3,
encrypted: true,
}),
}],
});
cdk.Tags.of(instance).add("forgejo-backup", "true");
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: 7 },
copyTags: true,
}],
},
});
const hostedZone = route53.HostedZone.fromHostedZoneAttributes(
this, "SeaHavenZone", {
hostedZoneId: "Z06652411XKH89KTZD3XA",
zoneName: "seahaven.com",
}
);
new route53.ARecord(this, "DnsRecord", {
zone: hostedZone,
recordName: "forgejo",
target: route53.RecordTarget.fromIpAddresses(instance.instancePrivateIp),
ttl: cdk.Duration.minutes(5),
});
new cdk.CfnOutput(this, "ForgejoUrl", {
value: "http://forgejo.seahaven.com:3000",
});
new cdk.CfnOutput(this, "InstanceId", {
value: instance.instanceId,
});
new cdk.CfnOutput(this, "PrivateIp", {
value: instance.instancePrivateIp,
});
}
}