Files
aibook-fun/deploy/terraform/main.tf
T

114 lines
3.3 KiB
Terraform

# aibook-fun - Main Infrastructure
# Managed by OpenTofu
resource "digitalocean_droplet" "web" {
name = "aibook-fun"
image = var.droplet_image
size = var.droplet_size
region = var.region
monitoring = true
backups = true
# CPU/RAM-only resize (provider default is true = also grow the disk).
resize_disk = false
ssh_keys = var.ssh_key_fingerprints
user_data = templatefile("${path.module}/templates/cloud-init.yaml", {
project_name = "aibook_fun"
})
tags = ["aibook-fun", "web", "weown-ai"]
lifecycle {
# A droplet destroy means losing every Docker volume + cert state on it.
# make any such plan a hard error instead of an accident.
prevent_destroy = true
# user_data/tags/ssh_keys are create-time-only on DO; ignore drift so
# unrelated changes don't arm `# forces replacement` on next apply.
ignore_changes = [user_data, tags, ssh_keys]
}
}
# Optional encrypted-at-rest data volume (default 0 = stateless: droplet local
# disk only). Enable by setting data_volume_size_gb > 0 if the site ever needs
# durable state; DO block storage is encrypted at rest, the droplet disk is not.
resource "digitalocean_volume" "data" {
count = var.data_volume_size_gb > 0 ? 1 : 0
region = var.region
name = "aibook-fun-data"
size = var.data_volume_size_gb
initial_filesystem_type = "ext4"
description = "aibook-fun encrypted-at-rest data (docker data-root)"
lifecycle {
prevent_destroy = true
}
}
resource "digitalocean_volume_attachment" "data" {
count = var.data_volume_size_gb > 0 ? 1 : 0
droplet_id = digitalocean_droplet.web.id
volume_id = digitalocean_volume.data[0].id
}
resource "digitalocean_reserved_ip" "web" {
region = var.region
}
resource "digitalocean_reserved_ip_assignment" "web" {
ip_address = digitalocean_reserved_ip.web.ip_address
droplet_id = digitalocean_droplet.web.id
}
#trivy:ignore:AVD-DIG-0001 # Public web server: HTTP/HTTPS inbound from internet is required by design
#trivy:ignore:AVD-DIG-0003 # Public web server: unrestricted outbound required for package installs, ACME, image pulls
resource "digitalocean_firewall" "web" {
name = "aibook-fun-fw"
droplet_ids = [digitalocean_droplet.web.id]
# SSH — restrict via var.ssh_source_cidrs. ⚠️ PRODUCTION: pin to admin IP/32 or VPN
inbound_rule {
protocol = "tcp"
port_range = "22"
source_addresses = var.ssh_source_cidrs
}
# HTTP (ACME challenge + redirect)
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"]
}
# HTTPS/QUIC (HTTP/3)
inbound_rule {
protocol = "udp"
port_range = "443"
source_addresses = ["0.0.0.0/0", "::/0"]
}
# All outbound TCP (apt, ACME, container image pulls)
outbound_rule {
protocol = "tcp"
port_range = "1-65535"
destination_addresses = ["0.0.0.0/0", "::/0"]
}
# All outbound UDP (DNS, NTP, QUIC)
outbound_rule {
protocol = "udp"
port_range = "1-65535"
destination_addresses = ["0.0.0.0/0", "::/0"]
}
tags = ["aibook-fun"]
}