feat: add Hetzner-hosted todo application stack

Implement the server-rendered Go todo application with PostgreSQL persistence, Tailwind styling, health checks, and test coverage.

Provision production and staging infrastructure with OpenTofu, including private networking, static IPs, Object Storage backups, and load balancing.

Configure Rocky Linux hosts with Ansible, rootless Podman, PostgreSQL, WAL-G backup automation, staging restores, and generated inventory.
This commit is contained in:
cole-maxwell1
2026-08-06 21:34:56 -05:00
commit 1382c42803
68 changed files with 2380 additions and 0 deletions
+140
View File
@@ -0,0 +1,140 @@
provider "hcloud" {
token = var.hcloud_token
}
provider "aws" {
access_key = var.s3_access_key
secret_key = var.s3_secret_key
region = var.s3_region
skip_credentials_validation = true
skip_metadata_api_check = true
skip_region_validation = true
skip_requesting_account_id = true
s3_use_path_style = true
endpoints {
s3 = var.s3_endpoint
}
}
resource "hcloud_network" "main" {
name = "${var.project_name}-network"
ip_range = "10.0.0.0/16"
}
resource "hcloud_network_subnet" "main" {
network_id = hcloud_network.main.id
type = "cloud"
network_zone = var.network_zone
ip_range = "10.0.1.0/24"
}
resource "hcloud_firewall" "nodes" {
name = "${var.project_name}-nodes"
rule {
direction = "in"
protocol = "tcp"
port = "22"
source_ips = var.ssh_source_cidrs
}
rule {
direction = "in"
protocol = "tcp"
port = "8080"
source_ips = [hcloud_network.main.ip_range]
}
rule {
direction = "in"
protocol = "icmp"
source_ips = [
"0.0.0.0/0",
"::/0",
]
}
}
module "prod" {
source = "./modules/compute"
name = "${var.project_name}-prod"
server_type = var.server_type
location = var.server_location
ssh_key_ids = var.ssh_key_ids
firewall_ids = [hcloud_firewall.nodes.id]
network_id = hcloud_network.main.id
private_ip = var.prod_private_ip
environment = "prod"
depends_on = [hcloud_network_subnet.main]
}
module "staging" {
source = "./modules/compute"
name = "${var.project_name}-staging"
server_type = var.server_type
location = var.server_location
ssh_key_ids = var.ssh_key_ids
firewall_ids = [hcloud_firewall.nodes.id]
network_id = hcloud_network.main.id
private_ip = var.staging_private_ip
environment = "staging"
depends_on = [hcloud_network_subnet.main]
}
module "storage" {
source = "./modules/storage"
bucket_name = var.bucket_name
}
resource "hcloud_load_balancer" "app" {
name = "${var.project_name}-lb"
load_balancer_type = "lb11"
location = var.server_location
algorithm {
type = "round_robin"
}
}
resource "hcloud_load_balancer_network" "app" {
load_balancer_id = hcloud_load_balancer.app.id
network_id = hcloud_network.main.id
depends_on = [hcloud_network_subnet.main]
}
resource "hcloud_load_balancer_target" "prod" {
type = "server"
load_balancer_id = hcloud_load_balancer.app.id
server_id = module.prod.server_id
use_private_ip = true
depends_on = [
hcloud_load_balancer_network.app,
module.prod,
]
}
resource "hcloud_load_balancer_service" "http" {
load_balancer_id = hcloud_load_balancer.app.id
protocol = "http"
listen_port = 80
destination_port = 8080
http {
sticky_sessions = false
}
health_check {
protocol = "http"
port = 8080
interval = 15
timeout = 10
retries = 3
http {
path = "/healthz"
status_codes = ["200"]
}
}
}