# 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"] }