Initial commit - realms platform

This commit is contained in:
doomtube 2026-01-05 22:54:27 -05:00
parent c590ab6d18
commit c717c3751c
234 changed files with 74103 additions and 15231 deletions

48
terraform/main.tf Normal file
View file

@ -0,0 +1,48 @@
# =============================================================================
# Realms App Server Infrastructure
# Separate Terraform state from git infrastructure (jump host + Forgejo)
# =============================================================================
locals {
common_tags = concat([
var.project_name,
var.environment,
"terraform-managed",
"app-server"
], var.tags)
}
# =============================================================================
# SSH Keys
# =============================================================================
resource "digitalocean_ssh_key" "admin" {
for_each = var.admin_ssh_public_keys
name = "${var.project_name}-app-${var.environment}-${each.key}"
public_key = each.value
}
# =============================================================================
# App Server Module
# =============================================================================
module "app_server" {
source = "./modules/app_server"
project_name = var.project_name
environment = var.environment
region = var.region
vpc_uuid = var.vpc_uuid
vpc_ip_range = var.vpc_ip_range
ssh_keys = [for key in digitalocean_ssh_key.admin : key.id]
droplet_size = var.app_droplet_size
droplet_image = var.app_droplet_image
ssh_port = var.app_ssh_port
domain = var.app_domain
enable_backups = var.enable_droplet_backups
tags = local.common_tags
manage_dns = var.manage_dns
dns_zone = var.dns_zone
forgejo_registry = var.forgejo_registry
}

View 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."

View 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
}

View 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
}

View 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"
}

68
terraform/outputs.tf Normal file
View file

@ -0,0 +1,68 @@
# =============================================================================
# App Server Outputs
# =============================================================================
output "app_droplet_id" {
description = "ID of the app server droplet"
value = module.app_server.droplet_id
}
output "app_public_ip" {
description = "Public IPv4 address of the app server"
value = module.app_server.public_ip
}
output "app_private_ip" {
description = "Private IPv4 address of the app server (VPC)"
value = module.app_server.private_ip
}
output "app_ssh_port" {
description = "SSH port for the app server"
value = var.app_ssh_port
}
output "app_firewall_id" {
description = "ID of the app server firewall"
value = module.app_server.firewall_id
}
# =============================================================================
# Connection Info
# =============================================================================
output "ssh_config" {
description = "SSH config snippet for connecting to the app server"
value = <<-EOT
# Add to ~/.ssh/config
# Connect via jump host
Host realms-app
HostName ${module.app_server.private_ip}
Port ${var.app_ssh_port}
User root
ProxyJump realms-jump
IdentityFile ~/.ssh/id_ed25519
# Or direct connection (if on VPC)
Host realms-app-direct
HostName ${module.app_server.public_ip}
Port ${var.app_ssh_port}
User root
IdentityFile ~/.ssh/id_ed25519
EOT
}
output "deploy_info" {
description = "Deployment information for CI/CD"
value = <<-EOT
App Server Public IP: ${module.app_server.public_ip}
App Server Private IP: ${module.app_server.private_ip}
SSH Port: ${var.app_ssh_port}
Domain: ${var.app_domain}
Add these to Forgejo secrets:
DEPLOY_HOST: ${module.app_server.private_ip}
DEPLOY_PORT: ${var.app_ssh_port}
EOT
}

135
terraform/variables.tf Normal file
View file

@ -0,0 +1,135 @@
# =============================================================================
# Provider Configuration
# =============================================================================
variable "do_token" {
description = "DigitalOcean API token"
type = string
sensitive = true
}
# =============================================================================
# Project Configuration
# =============================================================================
variable "project_name" {
description = "Project name used for resource naming"
type = string
default = "realms"
}
variable "environment" {
description = "Environment name (production, staging, development)"
type = string
default = "production"
validation {
condition = contains(["production", "staging", "development"], var.environment)
error_message = "Environment must be one of: production, staging, development."
}
}
variable "region" {
description = "DigitalOcean region"
type = string
default = "nyc3"
}
# =============================================================================
# VPC Configuration (reference existing VPC)
# =============================================================================
variable "vpc_uuid" {
description = "UUID of the existing VPC (from terraform/ outputs)"
type = string
}
variable "vpc_ip_range" {
description = "IP range for the VPC (CIDR notation)"
type = string
default = "10.10.0.0/16"
}
# =============================================================================
# SSH Configuration
# =============================================================================
variable "admin_ssh_public_keys" {
description = "Map of admin SSH public keys (name => public_key)"
type = map(string)
default = {}
}
variable "app_ssh_port" {
description = "SSH port for the app server (VPC only, non-standard)"
type = number
default = 51234
}
# =============================================================================
# App Server Configuration
# =============================================================================
variable "app_droplet_size" {
description = "Size slug for the app server droplet"
type = string
default = "s-2vcpu-4gb"
}
variable "app_droplet_image" {
description = "Image slug for the app server droplet"
type = string
default = "debian-12-x64"
}
variable "app_domain" {
description = "Domain name for the app (e.g., realms.pub)"
type = string
default = "realms.pub"
}
# =============================================================================
# DNS Configuration
# =============================================================================
variable "manage_dns" {
description = "Whether to manage DNS records via DigitalOcean"
type = bool
default = true
}
variable "dns_zone" {
description = "DNS zone (base domain) managed by DigitalOcean"
type = string
default = "realms.pub"
}
# =============================================================================
# Backup Configuration
# =============================================================================
variable "enable_droplet_backups" {
description = "Enable automated droplet backups"
type = bool
default = false
}
# =============================================================================
# Tags
# =============================================================================
variable "tags" {
description = "Additional tags to apply to resources"
type = list(string)
default = []
}
# =============================================================================
# Forgejo Registry Configuration
# =============================================================================
variable "forgejo_registry" {
description = "Forgejo container registry URL"
type = string
default = "qbit.realms.pub"
}

26
terraform/versions.tf Normal file
View file

@ -0,0 +1,26 @@
# =============================================================================
# Terraform and Provider Version Constraints
# =============================================================================
terraform {
required_version = ">= 1.5.0"
required_providers {
digitalocean = {
source = "digitalocean/digitalocean"
version = "~> 2.34"
}
tls = {
source = "hashicorp/tls"
version = "~> 4.0"
}
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
}
}
provider "digitalocean" {
token = var.do_token
}