beeta/terraform/modules/app_server/cloud-init.yaml.tpl
doomtube c358db55aa
All checks were successful
Build and Push / build-all (push) Successful in 2m7s
fixes lol
2026-01-07 03:29:05 -05:00

302 lines
9.8 KiB
Smarty

#cloud-config
# =============================================================================
# Realms App Server Cloud-Init Configuration
# Production server for streaming platform - Docker-based deployment
# =============================================================================
write_files:
# SSH daemon configuration (non-standard port, VPC only access)
- path: /etc/ssh/sshd_config.d/99-hardening.conf
content: |
# Non-standard port (VPC access only via jump host)
Port ${ssh_port}
# Authentication hardening
PermitRootLogin prohibit-password
PasswordAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey
# Security settings
MaxAuthTries 3
LoginGraceTime 30
PermitEmptyPasswords no
X11Forwarding no
AllowTcpForwarding yes
AllowAgentForwarding no
PermitUserEnvironment no
MaxSessions 5
# Session settings
ClientAliveInterval 300
ClientAliveCountMax 2
# Logging
LogLevel VERBOSE
permissions: '0644'
# Fail2ban configuration for SSH
- path: /etc/fail2ban/jail.d/sshd.local
content: |
[sshd]
enabled = true
port = ${ssh_port}
filter = sshd
logpath = /var/log/auth.log
backend = systemd
bantime = 1h
findtime = 10m
maxretry = 3
permissions: '0644'
# Docker daemon configuration
- path: /etc/docker/daemon.json
content: |
{
"log-driver": "json-file",
"log-opts": {
"max-size": "50m",
"max-file": "5"
},
"storage-driver": "overlay2",
"live-restore": true,
"no-new-privileges": true
}
permissions: '0644'
# UFW configuration script
- path: /usr/local/bin/configure-firewall.sh
content: |
#!/bin/bash
set -e
# Reset UFW
ufw --force reset
# Default policies
ufw default deny incoming
ufw default deny outgoing
# Inbound: HTTP/HTTPS
ufw allow in 80/tcp comment 'HTTP'
ufw allow in 443/tcp comment 'HTTPS'
# Inbound: Streaming ports
ufw allow in 1935/tcp comment 'RTMP'
ufw allow in 9999/udp comment 'SRT'
ufw allow in 8088/tcp comment 'HLS/LLHLS streaming'
# WebRTC signaling now proxied through nginx (443), only need media ports
ufw allow in 3478/udp comment 'WebRTC STUN/TURN'
ufw allow in 10000:10009/udp comment 'WebRTC ICE candidates'
# Inbound: VPC traffic (includes system SSH)
ufw allow in from ${vpc_ip_range} comment 'VPC internal'
# Outbound: Necessary traffic
ufw allow out 53/tcp comment 'DNS'
ufw allow out 53/udp comment 'DNS'
ufw allow out 80/tcp comment 'HTTP'
ufw allow out 443/tcp comment 'HTTPS'
ufw allow out 123/udp comment 'NTP'
ufw allow out to ${vpc_ip_range} comment 'VPC'
# Enable logging
ufw logging medium
# Enable UFW
ufw --force enable
echo "Firewall configured successfully"
permissions: '0755'
# Swap configuration script (for 4GB RAM)
- path: /usr/local/bin/configure-swap.sh
content: |
#!/bin/bash
set -e
SWAP_FILE="/swapfile"
SWAP_SIZE="4G"
if [ ! -f "$SWAP_FILE" ]; then
echo "Creating $SWAP_SIZE swap file..."
fallocate -l $SWAP_SIZE $SWAP_FILE
chmod 600 $SWAP_FILE
mkswap $SWAP_FILE
swapon $SWAP_FILE
# Add to fstab
echo "$SWAP_FILE none swap sw 0 0" >> /etc/fstab
# Adjust swappiness
echo "vm.swappiness=10" >> /etc/sysctl.conf
sysctl vm.swappiness=10
echo "Swap configured successfully"
else
echo "Swap file already exists"
fi
permissions: '0755'
# Unattended upgrades configuration
- path: /etc/apt/apt.conf.d/50unattended-upgrades
content: |
Unattended-Upgrade::Allowed-Origins {
"$${distro_id}:$${distro_codename}";
"$${distro_id}:$${distro_codename}-security";
"$${distro_id}:$${distro_codename}-updates";
};
Unattended-Upgrade::AutoFixInterruptedDpkg "true";
Unattended-Upgrade::MinimalSteps "true";
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Automatic-Reboot "false";
permissions: '0644'
# Auto-upgrades configuration
- path: /etc/apt/apt.conf.d/20auto-upgrades
content: |
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
APT::Periodic::AutocleanInterval "7";
permissions: '0644'
# Message of the day
- path: /etc/motd
content: |
+---------------------------------------------------------------+
| REALMS APP SERVER |
| |
| Domain: ${domain}
| SSH Port: ${ssh_port} (VPC only)
| |
| App location: /opt/realms |
| Docker compose: /opt/realms/docker-compose.yml |
| |
| Commands: |
| cd /opt/realms && docker compose logs -f |
| cd /opt/realms && docker compose pull && docker compose up -d
| |
+---------------------------------------------------------------+
permissions: '0644'
# Environment file generator script (separate file to avoid YAML parsing issues)
- path: /usr/local/bin/generate-env.sh
content: |
#!/bin/bash
set -e
ENV_FILE="/opt/realms/.env"
if [ -f "$ENV_FILE" ]; then
echo ".env already exists, skipping generation"
exit 0
fi
echo "Generating .env with secure random secrets..."
DB_PASS=$(openssl rand -base64 32 | tr -d '/+=' | head -c 32)
JWT_SEC=$(openssl rand -base64 48 | tr -d '/+=' | head -c 48)
REDIS_PASS=$(openssl rand -base64 32 | tr -d '/+=' | head -c 32)
OME_TOKEN=$(openssl rand -hex 32)
NAKAMA_KEY=$(openssl rand -hex 16)
NAKAMA_PASS=$(openssl rand -base64 16 | tr -d '/+=' | head -c 16)
{
echo "DB_PASSWORD=$DB_PASS"
echo "JWT_SECRET=$JWT_SEC"
echo "REDIS_PASSWORD=$REDIS_PASS"
echo "OME_API_TOKEN=$OME_TOKEN"
echo "NAKAMA_SERVER_KEY=$NAKAMA_KEY"
echo "NAKAMA_CONSOLE_PASSWORD=$NAKAMA_PASS"
} > "$ENV_FILE"
chmod 600 "$ENV_FILE"
echo ".env generated with secure random secrets"
permissions: '0755'
runcmd:
# Ensure .ssh directory exists
- mkdir -p /root/.ssh && chmod 700 /root/.ssh
# Configure SSH
- grep -q 'Include /etc/ssh/sshd_config.d' /etc/ssh/sshd_config || sed -i '1i Include /etc/ssh/sshd_config.d/*.conf' /etc/ssh/sshd_config
- sed -i 's/^Port /#Port /' /etc/ssh/sshd_config
- systemctl restart sshd
# Wait for dpkg lock
- while fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1; do echo "Waiting for dpkg lock..."; sleep 5; done
# Install prerequisites
- apt-get update
- DEBIAN_FRONTEND=noninteractive apt-get -o DPkg::Lock::Timeout=60 install -y ca-certificates curl gnupg fail2ban ufw unattended-upgrades apt-listchanges vim git
# Add Docker's official GPG key and repository
- install -m 0755 -d /etc/apt/keyrings
- curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
- chmod a+r /etc/apt/keyrings/docker.gpg
- echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian $(. /etc/os-release && echo $VERSION_CODENAME) stable" > /etc/apt/sources.list.d/docker.list
# Install Docker
- apt-get update
- DEBIAN_FRONTEND=noninteractive apt-get -o DPkg::Lock::Timeout=60 install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# Enable and start Docker
- systemctl enable docker
- systemctl start docker
# Enable and start fail2ban
- systemctl enable fail2ban
- systemctl restart fail2ban
# Configure swap
- /usr/local/bin/configure-swap.sh
# Configure firewall
- /usr/local/bin/configure-firewall.sh
# Create app directory
- mkdir -p /opt/realms
- mkdir -p /opt/realms/uploads
# Generate .env with secure random secrets (script defined in write_files)
- /usr/local/bin/generate-env.sh
# Enable unattended upgrades
- systemctl enable unattended-upgrades
- systemctl start unattended-upgrades
# Install certbot for SSL certificates
- DEBIAN_FRONTEND=noninteractive apt-get -o DPkg::Lock::Timeout=60 install -y certbot
# Create directories for certbot webroot
- mkdir -p /opt/realms/certbot_webroot
# Obtain initial SSL certificate (standalone mode - no webserver running yet)
# This runs before Docker services start, so port 80 is free
# Retry with delays to wait for DigitalOcean firewall propagation
- |
MAX_ATTEMPTS=5
ATTEMPT=1
DELAY=30
while [ $ATTEMPT -le $MAX_ATTEMPTS ]; do
echo "Certbot attempt $ATTEMPT of $MAX_ATTEMPTS..."
if certbot certonly --standalone \
--non-interactive \
--agree-tos \
--email ${letsencrypt_email} \
-d ${domain}; then
echo "SSL certificate obtained successfully!"
break
else
echo "Certbot failed on attempt $ATTEMPT"
if [ $ATTEMPT -lt $MAX_ATTEMPTS ]; then
echo "Waiting $${DELAY}s for firewall propagation before retry..."
sleep $DELAY
DELAY=$((DELAY * 2)) # Exponential backoff
fi
fi
ATTEMPT=$((ATTEMPT + 1))
done
if [ $ATTEMPT -gt $MAX_ATTEMPTS ]; then
echo "Certbot failed after $MAX_ATTEMPTS attempts - obtain certificate manually"
fi
final_message: "Realms app server ready after $UPTIME seconds. Deploy via Forgejo CI/CD."