diff --git a/ansible/playbooks/deploy.yml b/ansible/playbooks/deploy.yml index fc1e4ed..b5ca037 100644 --- a/ansible/playbooks/deploy.yml +++ b/ansible/playbooks/deploy.yml @@ -66,6 +66,10 @@ - role: mailgun - role: authentik - role: nextcloud + - role: diun + tags: diun + - role: kuma + tags: kuma post_tasks: - name: Display deployment summary diff --git a/tofu/dns.tf b/tofu/dns.tf index 548a441..b191b09 100644 --- a/tofu/dns.tf +++ b/tofu/dns.tf @@ -6,7 +6,8 @@ data "hcloud_zone" "main" { name = var.base_domain } -# A Records for client servers (e.g., test.vrije.cloud -> 78.47.191.38) +# A Records for client servers with public IPs (e.g., test.vrije.cloud -> 78.47.191.38) +# Clients without public IPs (behind edge proxy) point to edge server instead resource "hcloud_zone_rrset" "client_a" { for_each = var.clients @@ -16,8 +17,8 @@ resource "hcloud_zone_rrset" "client_a" { ttl = 300 records = [ { - value = hcloud_server.client[each.key].ipv4_address - comment = "Client ${each.key} server" + value = lookup(each.value, "public_ip_enabled", true) ? hcloud_server.client[each.key].ipv4_address : hcloud_server.edge.ipv4_address + comment = lookup(each.value, "public_ip_enabled", true) ? "Client ${each.key} server" : "Client ${each.key} via edge proxy" } ] } @@ -32,15 +33,18 @@ resource "hcloud_zone_rrset" "client_wildcard" { ttl = 300 records = [ { - value = hcloud_server.client[each.key].ipv4_address - comment = "Wildcard for ${each.key} subdomains (Zitadel, Nextcloud, etc)" + value = lookup(each.value, "public_ip_enabled", true) ? hcloud_server.client[each.key].ipv4_address : hcloud_server.edge.ipv4_address + comment = lookup(each.value, "public_ip_enabled", true) ? "Wildcard for ${each.key} subdomains" : "Wildcard for ${each.key} via edge proxy" } ] } -# AAAA Records for IPv6 (e.g., test.vrije.cloud IPv6) +# AAAA Records for IPv6 (only for servers with public IPs) resource "hcloud_zone_rrset" "client_aaaa" { - for_each = var.clients + for_each = { + for k, v in var.clients : k => v + if lookup(v, "public_ip_enabled", true) + } zone = data.hcloud_zone.main.name name = each.value.subdomain diff --git a/tofu/main.tf b/tofu/main.tf index 2b4ef9f..726e7f2 100644 --- a/tofu/main.tf +++ b/tofu/main.tf @@ -70,18 +70,25 @@ resource "hcloud_server" "client" { # Enable backups if requested backups = var.enable_snapshots + # Public network configuration (can be disabled for private-only servers) + public_net { + ipv4_enabled = lookup(each.value, "public_ip_enabled", true) + ipv6_enabled = lookup(each.value, "public_ip_enabled", true) + } + + # Private network (required for servers without public IP) + dynamic "network" { + for_each = lookup(each.value, "private_ip", null) != null ? [1] : [] + content { + network_id = hcloud_network.private.id + ip = each.value.private_ip + } + } + # User data for initial setup - user_data = <<-EOF - #cloud-config - package_update: true - package_upgrade: true - packages: - - curl - - wget - - git - - python3 - - python3-pip - runcmd: - - hostnamectl set-hostname ${each.key} - EOF + user_data = lookup(each.value, "public_ip_enabled", true) == false ? templatefile("${path.module}/user-data-private.yml", { + hostname = each.key + }) : templatefile("${path.module}/user-data-public.yml", { + hostname = each.key + }) } diff --git a/tofu/network.tf b/tofu/network.tf new file mode 100644 index 0000000..104b865 --- /dev/null +++ b/tofu/network.tf @@ -0,0 +1,86 @@ +# Private Network Configuration +# Enables client servers to communicate without public IPs + +# Private Network +resource "hcloud_network" "private" { + name = "client-private-network" + ip_range = "10.0.0.0/16" + + labels = { + managed = "terraform" + purpose = "client-internal" + } +} + +# Subnet for client servers +resource "hcloud_network_subnet" "clients" { + network_id = hcloud_network.private.id + type = "cloud" + network_zone = "eu-central" + ip_range = "10.0.0.0/24" +} + +# Note: Client servers attach to private network via main.tf dynamic block + +# Edge Server Configuration +# Single public-facing reverse proxy for all clients + +# SSH key for edge server +resource "hcloud_ssh_key" "edge" { + name = "edge-server-deploy-key" + public_key = file("${path.module}/../keys/ssh/edge.pub") +} + +# Edge server (public IP + private network) +resource "hcloud_server" "edge" { + name = "edge" + server_type = var.edge_server_type + image = "ubuntu-24.04" + location = var.edge_location + ssh_keys = [hcloud_ssh_key.edge.id] + firewall_ids = [hcloud_firewall.client_firewall.id] + + labels = { + role = "edge-proxy" + managed = "terraform" + } + + # Enable backups + backups = var.enable_snapshots + + # User data for initial setup + user_data = <<-EOF + #cloud-config + package_update: true + package_upgrade: true + packages: + - curl + - wget + - git + - python3 + - python3-pip + runcmd: + - hostnamectl set-hostname edge + EOF + + # Ensure public network is enabled + public_net { + ipv4_enabled = true + ipv6_enabled = true + } +} + +# Attach edge server to private network +resource "hcloud_server_network" "edge" { + server_id = hcloud_server.edge.id + network_id = hcloud_network.private.id + ip = "10.0.0.2" # Fixed IP for edge server (10.0.0.1 is gateway) +} + +# NAT Gateway Route +# Routes all internet-bound traffic from private network through edge server +resource "hcloud_network_route" "nat_gateway" { + network_id = hcloud_network.private.id + destination = "0.0.0.0/0" + gateway = "10.0.0.2" # Edge server acts as NAT gateway +} diff --git a/tofu/user-data-private.yml b/tofu/user-data-private.yml new file mode 100644 index 0000000..d13590b --- /dev/null +++ b/tofu/user-data-private.yml @@ -0,0 +1,25 @@ +#cloud-config +package_update: true +package_upgrade: true +packages: + - curl + - wget + - git + - python3 + - python3-pip +runcmd: + - hostnamectl set-hostname ${hostname} + - | + # Configure default route for private-only server + # Hetzner network route forwards traffic to edge gateway (10.0.0.2) + cat > /etc/netplan/60-private-network.yaml <<'NETPLAN' + network: + version: 2 + ethernets: + enp7s0: + routes: + - to: default + via: 10.0.0.1 + NETPLAN + chmod 600 /etc/netplan/60-private-network.yaml + netplan apply diff --git a/tofu/user-data-public.yml b/tofu/user-data-public.yml new file mode 100644 index 0000000..a40a720 --- /dev/null +++ b/tofu/user-data-public.yml @@ -0,0 +1,11 @@ +#cloud-config +package_update: true +package_upgrade: true +packages: + - curl + - wget + - git + - python3 + - python3-pip +runcmd: + - hostnamectl set-hostname ${hostname} diff --git a/tofu/variables.tf b/tofu/variables.tf index 6c4d85c..cf2b1da 100644 --- a/tofu/variables.tf +++ b/tofu/variables.tf @@ -31,10 +31,25 @@ variable "clients" { subdomain = string # e.g., "alpha" for alpha.platform.nl apps = list(string) # e.g., ["zitadel", "nextcloud"] nextcloud_volume_size = number # Size in GB for Nextcloud data volume (min 10, max 10000) + private_ip = optional(string) # Private IP in 10.0.0.0/24 range (e.g., "10.0.0.10") + public_ip_enabled = optional(bool, true) # Whether to enable public IP (default: true for backward compatibility) })) default = {} } +# Edge Server Configuration +variable "edge_server_type" { + description = "Server type for edge proxy server" + type = string + default = "cpx22" # 3 vCPU, 4 GB RAM - CPX11/21 unavailable in fsn1 +} + +variable "edge_location" { + description = "Location for edge proxy server" + type = string + default = "fsn1" # Falkenstein, Germany +} + # Enable automated snapshots variable "enable_snapshots" { description = "Enable automated daily snapshots (20% of server cost)"