file-share/lib/file-share-stack.ts
Adam Moussa cced82c0d7 Remove unused FB_HASH variable from user data
FileBrowser's users add command hashes internally.
2026-05-14 15:35:51 -04:00

211 lines
7.3 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 dlm from "aws-cdk-lib/aws-dlm";
import { Construct } from "constructs";
export class FileShareStack 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.fromSubnetAttributes(
this, "PrivateSubnet1", {
subnetId: "subnet-04e38c507e96f1926",
availabilityZone: "us-east-1a",
}
);
const sg = new ec2.SecurityGroup(this, "SecurityGroup", {
vpc,
securityGroupName: "file-share",
description: "File share - SMB and FileBrowser via VPN",
allowAllOutbound: true,
});
sg.addIngressRule(ec2.Peer.ipv4("10.10.0.0/16"), ec2.Port.tcp(445), "SMB from office VPN");
sg.addIngressRule(ec2.Peer.ipv4("10.20.0.0/16"), ec2.Port.tcp(445), "SMB from VPC");
sg.addIngressRule(ec2.Peer.ipv4("10.10.0.0/16"), ec2.Port.tcp(8080), "FileBrowser from office VPN");
sg.addIngressRule(ec2.Peer.ipv4("10.20.0.0/16"), ec2.Port.tcp(8080), "FileBrowser from VPC");
const role = new iam.Role(this, "InstanceRole", {
roleName: "file-share-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:file-share/*`,
],
}));
const userData = ec2.UserData.forLinux();
userData.addCommands(
"set -euxo pipefail",
"",
"# ── Data volume ──",
"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 file-share-data \"$DATA_DEVICE\"",
"fi",
"mkdir -p /data",
"echo \"LABEL=file-share-data /data ext4 defaults,nofail 0 2\" >> /etc/fstab",
"mount -a",
"mkdir -p /data/share",
"",
"# ── Samba ──",
"dnf install -y samba samba-common jq",
"",
"useradd --system --no-create-home --shell /sbin/nologin adam || true",
"chown adam:adam /data/share",
"",
"SMB_PASSWORD=$(aws secretsmanager get-secret-value --secret-id file-share/smb-password --query SecretString --output text --region us-east-1)",
"(echo \"$SMB_PASSWORD\"; echo \"$SMB_PASSWORD\") | smbpasswd -s -a adam",
"",
"cat > /etc/samba/smb.conf << 'SMBEOF'",
"[global]",
"workgroup = SEAHAVEN",
"server string = Sea Haven File Share",
"security = user",
"map to guest = never",
"log file = /var/log/samba/log.%m",
"max log size = 1000",
"server min protocol = SMB3",
"",
"# macOS Finder optimizations",
"vfs objects = catia fruit streams_xattr",
"fruit:metadata = stream",
"fruit:model = MacSamba",
"fruit:posix_rename = yes",
"fruit:veto_appledouble = no",
"fruit:nfs_aces = no",
"fruit:wipe_intentionally_left_blank_rfork = yes",
"fruit:delete_empty_adfiles = yes",
"",
"[files]",
"path = /data/share",
"browseable = yes",
"writable = yes",
"valid users = adam",
"create mask = 0644",
"directory mask = 0755",
"SMBEOF",
"",
"systemctl enable --now smb nmb",
"",
"# ── FileBrowser ──",
'FB_VERSION=$(curl -sf "https://api.github.com/repos/filebrowser/filebrowser/releases/latest" | jq -r .tag_name)',
'FB_URL="https://github.com/filebrowser/filebrowser/releases/download/${FB_VERSION}/linux-arm64-filebrowser.tar.gz"',
"curl -sfL \"$FB_URL\" | tar xz -C /usr/local/bin filebrowser",
"chmod +x /usr/local/bin/filebrowser",
"",
"mkdir -p /etc/filebrowser",
"FB_PASSWORD=$(aws secretsmanager get-secret-value --secret-id file-share/filebrowser-password --query SecretString --output text --region us-east-1)",
"",
"cat > /etc/filebrowser/config.json << FBEOF",
"{",
" \"address\": \"0.0.0.0\",",
" \"port\": 8080,",
" \"root\": \"/data/share\",",
" \"database\": \"/etc/filebrowser/filebrowser.db\",",
" \"log\": \"/var/log/filebrowser.log\"",
"}",
"FBEOF",
"",
"filebrowser config init --config /etc/filebrowser/config.json",
"filebrowser users add admin \"$FB_PASSWORD\" --config /etc/filebrowser/config.json --perm.admin",
"",
"cat > /etc/systemd/system/filebrowser.service << 'SVCEOF'",
"[Unit]",
"Description=FileBrowser",
"After=network.target",
"",
"[Service]",
"Type=simple",
"ExecStart=/usr/local/bin/filebrowser --config /etc/filebrowser/config.json",
"Restart=always",
"RestartSec=5",
"",
"[Install]",
"WantedBy=multi-user.target",
"SVCEOF",
"",
"systemctl daemon-reload",
"systemctl enable --now filebrowser",
);
const instance = new ec2.Instance(this, "Instance", {
instanceName: "file-share",
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(20, {
volumeType: ec2.EbsDeviceVolumeType.GP3,
encrypted: true,
}),
},
{
deviceName: "/dev/xvdf",
volume: ec2.BlockDeviceVolume.ebs(500, {
volumeType: ec2.EbsDeviceVolumeType.GP3,
encrypted: true,
}),
},
],
});
cdk.Tags.of(instance).add("file-share-backup", "true");
const dlmRole = new iam.Role(this, "DlmRole", {
roleName: "file-share-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 file share data volume",
state: "ENABLED",
executionRoleArn: dlmRole.roleArn,
policyDetails: {
resourceTypes: ["INSTANCE"],
targetTags: [{ key: "file-share-backup", value: "true" }],
schedules: [{
name: "file-share-nightly",
createRule: { interval: 24, intervalUnit: "HOURS", times: ["06:00"] },
retainRule: { count: 30 },
copyTags: true,
tagsToAdd: [{ key: "file-share-backup", value: "true" }],
}],
},
});
new cdk.CfnOutput(this, "InstanceId", {
value: instance.instanceId,
});
new cdk.CfnOutput(this, "PrivateIp", {
value: instance.instancePrivateIp,
description: "Use for SMB (smb://<ip>/files) and FileBrowser (http://<ip>:8080)",
});
}
}