Initial commit - realms platform
This commit is contained in:
parent
c590ab6d18
commit
c717c3751c
234 changed files with 74103 additions and 15231 deletions
232
terraform/modules/app_server/cloud-init.yaml.tpl
Normal file
232
terraform/modules/app_server/cloud-init.yaml.tpl
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
#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 3333/tcp comment 'WebRTC TCP'
|
||||
ufw allow in 3333/udp comment 'WebRTC UDP'
|
||||
ufw allow in 3334/tcp comment 'WebRTC TCP fallback'
|
||||
|
||||
# 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 && up -d |
|
||||
| |
|
||||
+---------------------------------------------------------------+
|
||||
|
||||
permissions: '0644'
|
||||
|
||||
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
|
||||
|
||||
# Enable unattended upgrades
|
||||
- systemctl enable unattended-upgrades
|
||||
- systemctl start unattended-upgrades
|
||||
|
||||
final_message: "Realms app server ready after $UPTIME seconds. Deploy via Forgejo CI/CD."
|
||||
196
terraform/modules/app_server/main.tf
Normal file
196
terraform/modules/app_server/main.tf
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
# =============================================================================
|
||||
# App Server Droplet
|
||||
# =============================================================================
|
||||
|
||||
resource "digitalocean_droplet" "app" {
|
||||
name = "${var.project_name}-app-${var.environment}"
|
||||
size = var.droplet_size
|
||||
image = var.droplet_image
|
||||
region = var.region
|
||||
vpc_uuid = var.vpc_uuid
|
||||
|
||||
ssh_keys = var.ssh_keys
|
||||
backups = var.enable_backups
|
||||
monitoring = true
|
||||
ipv6 = true
|
||||
|
||||
user_data = templatefile("${path.module}/cloud-init.yaml.tpl", {
|
||||
ssh_port = var.ssh_port
|
||||
vpc_ip_range = var.vpc_ip_range
|
||||
domain = var.domain
|
||||
forgejo_registry = var.forgejo_registry
|
||||
})
|
||||
|
||||
tags = var.tags
|
||||
|
||||
lifecycle {
|
||||
create_before_destroy = false
|
||||
ignore_changes = [user_data]
|
||||
}
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Firewall
|
||||
# =============================================================================
|
||||
|
||||
resource "digitalocean_firewall" "app" {
|
||||
name = "${var.project_name}-app-${var.environment}"
|
||||
|
||||
droplet_ids = [digitalocean_droplet.app.id]
|
||||
|
||||
# ==========================================================================
|
||||
# Inbound Rules
|
||||
# ==========================================================================
|
||||
|
||||
# HTTP (for redirect to HTTPS)
|
||||
inbound_rule {
|
||||
protocol = "tcp"
|
||||
port_range = "80"
|
||||
source_addresses = ["0.0.0.0/0", "::/0"]
|
||||
}
|
||||
|
||||
# HTTPS
|
||||
inbound_rule {
|
||||
protocol = "tcp"
|
||||
port_range = "443"
|
||||
source_addresses = ["0.0.0.0/0", "::/0"]
|
||||
}
|
||||
|
||||
# RTMP (streaming ingest)
|
||||
inbound_rule {
|
||||
protocol = "tcp"
|
||||
port_range = "1935"
|
||||
source_addresses = ["0.0.0.0/0", "::/0"]
|
||||
}
|
||||
|
||||
# SRT (streaming ingest)
|
||||
inbound_rule {
|
||||
protocol = "udp"
|
||||
port_range = "9999"
|
||||
source_addresses = ["0.0.0.0/0", "::/0"]
|
||||
}
|
||||
|
||||
# WebRTC (ICE candidates)
|
||||
inbound_rule {
|
||||
protocol = "tcp"
|
||||
port_range = "3333"
|
||||
source_addresses = ["0.0.0.0/0", "::/0"]
|
||||
}
|
||||
|
||||
inbound_rule {
|
||||
protocol = "udp"
|
||||
port_range = "3333"
|
||||
source_addresses = ["0.0.0.0/0", "::/0"]
|
||||
}
|
||||
|
||||
# WebRTC TCP (fallback)
|
||||
inbound_rule {
|
||||
protocol = "tcp"
|
||||
port_range = "3334"
|
||||
source_addresses = ["0.0.0.0/0", "::/0"]
|
||||
}
|
||||
|
||||
# OME API (internal, but opened for monitoring if needed)
|
||||
inbound_rule {
|
||||
protocol = "tcp"
|
||||
port_range = "8081"
|
||||
source_addresses = [var.vpc_ip_range]
|
||||
}
|
||||
|
||||
# VPC internal traffic (includes SSH on non-standard port)
|
||||
inbound_rule {
|
||||
protocol = "tcp"
|
||||
port_range = "1-65535"
|
||||
source_addresses = [var.vpc_ip_range]
|
||||
}
|
||||
|
||||
inbound_rule {
|
||||
protocol = "udp"
|
||||
port_range = "1-65535"
|
||||
source_addresses = [var.vpc_ip_range]
|
||||
}
|
||||
|
||||
inbound_rule {
|
||||
protocol = "icmp"
|
||||
source_addresses = [var.vpc_ip_range]
|
||||
}
|
||||
|
||||
# ==========================================================================
|
||||
# Outbound Rules
|
||||
# ==========================================================================
|
||||
|
||||
# DNS
|
||||
outbound_rule {
|
||||
protocol = "tcp"
|
||||
port_range = "53"
|
||||
destination_addresses = ["0.0.0.0/0", "::/0"]
|
||||
}
|
||||
|
||||
outbound_rule {
|
||||
protocol = "udp"
|
||||
port_range = "53"
|
||||
destination_addresses = ["0.0.0.0/0", "::/0"]
|
||||
}
|
||||
|
||||
# HTTP/HTTPS (for pulling images, updates)
|
||||
outbound_rule {
|
||||
protocol = "tcp"
|
||||
port_range = "80"
|
||||
destination_addresses = ["0.0.0.0/0", "::/0"]
|
||||
}
|
||||
|
||||
outbound_rule {
|
||||
protocol = "tcp"
|
||||
port_range = "443"
|
||||
destination_addresses = ["0.0.0.0/0", "::/0"]
|
||||
}
|
||||
|
||||
# NTP
|
||||
outbound_rule {
|
||||
protocol = "udp"
|
||||
port_range = "123"
|
||||
destination_addresses = ["0.0.0.0/0", "::/0"]
|
||||
}
|
||||
|
||||
# VPC internal traffic
|
||||
outbound_rule {
|
||||
protocol = "tcp"
|
||||
port_range = "1-65535"
|
||||
destination_addresses = [var.vpc_ip_range]
|
||||
}
|
||||
|
||||
outbound_rule {
|
||||
protocol = "udp"
|
||||
port_range = "1-65535"
|
||||
destination_addresses = [var.vpc_ip_range]
|
||||
}
|
||||
|
||||
outbound_rule {
|
||||
protocol = "icmp"
|
||||
destination_addresses = [var.vpc_ip_range]
|
||||
}
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# DNS Record (optional)
|
||||
# =============================================================================
|
||||
|
||||
resource "digitalocean_record" "app" {
|
||||
count = var.manage_dns ? 1 : 0
|
||||
|
||||
domain = var.dns_zone
|
||||
type = "A"
|
||||
name = "@"
|
||||
value = digitalocean_droplet.app.ipv4_address
|
||||
ttl = 600
|
||||
}
|
||||
|
||||
resource "digitalocean_record" "app_www" {
|
||||
count = var.manage_dns ? 1 : 0
|
||||
|
||||
domain = var.dns_zone
|
||||
type = "A"
|
||||
name = "www"
|
||||
value = digitalocean_droplet.app.ipv4_address
|
||||
ttl = 600
|
||||
}
|
||||
33
terraform/modules/app_server/outputs.tf
Normal file
33
terraform/modules/app_server/outputs.tf
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# =============================================================================
|
||||
# App Server Module Outputs
|
||||
# =============================================================================
|
||||
|
||||
output "droplet_id" {
|
||||
description = "ID of the app server droplet"
|
||||
value = digitalocean_droplet.app.id
|
||||
}
|
||||
|
||||
output "droplet_urn" {
|
||||
description = "URN of the app server droplet"
|
||||
value = digitalocean_droplet.app.urn
|
||||
}
|
||||
|
||||
output "public_ip" {
|
||||
description = "Public IPv4 address of the app server"
|
||||
value = digitalocean_droplet.app.ipv4_address
|
||||
}
|
||||
|
||||
output "private_ip" {
|
||||
description = "Private IPv4 address of the app server (VPC)"
|
||||
value = digitalocean_droplet.app.ipv4_address_private
|
||||
}
|
||||
|
||||
output "ipv6_address" {
|
||||
description = "IPv6 address of the app server"
|
||||
value = digitalocean_droplet.app.ipv6_address
|
||||
}
|
||||
|
||||
output "firewall_id" {
|
||||
description = "ID of the app server firewall"
|
||||
value = digitalocean_firewall.app.id
|
||||
}
|
||||
95
terraform/modules/app_server/variables.tf
Normal file
95
terraform/modules/app_server/variables.tf
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
# =============================================================================
|
||||
# App Server Module Variables
|
||||
# =============================================================================
|
||||
|
||||
variable "project_name" {
|
||||
description = "Project name"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "environment" {
|
||||
description = "Environment name"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "region" {
|
||||
description = "DigitalOcean region"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "vpc_uuid" {
|
||||
description = "UUID of the VPC"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "vpc_ip_range" {
|
||||
description = "IP range of the VPC (CIDR notation)"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "ssh_keys" {
|
||||
description = "List of SSH key IDs to add to the droplet"
|
||||
type = list(string)
|
||||
}
|
||||
|
||||
variable "droplet_size" {
|
||||
description = "Size slug for the droplet"
|
||||
type = string
|
||||
default = "s-2vcpu-4gb"
|
||||
}
|
||||
|
||||
variable "droplet_image" {
|
||||
description = "Image slug for the droplet"
|
||||
type = string
|
||||
default = "debian-12-x64"
|
||||
}
|
||||
|
||||
variable "ssh_port" {
|
||||
description = "SSH port (non-standard, VPC only)"
|
||||
type = number
|
||||
default = 51234
|
||||
}
|
||||
|
||||
variable "domain" {
|
||||
description = "Domain name for the app"
|
||||
type = string
|
||||
default = "realms.pub"
|
||||
}
|
||||
|
||||
variable "enable_backups" {
|
||||
description = "Enable automated backups"
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "tags" {
|
||||
description = "Tags to apply to resources"
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# DNS Configuration
|
||||
# =============================================================================
|
||||
|
||||
variable "manage_dns" {
|
||||
description = "Whether to manage DNS record via DigitalOcean"
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "dns_zone" {
|
||||
description = "DNS zone (base domain) managed by DigitalOcean"
|
||||
type = string
|
||||
default = "realms.pub"
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Forgejo Registry
|
||||
# =============================================================================
|
||||
|
||||
variable "forgejo_registry" {
|
||||
description = "Forgejo container registry URL"
|
||||
type = string
|
||||
default = "qbit.realms.pub"
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue