Recently Written · git

subplz-web

git clone https://github.com/equwal/subplz-web

Log | Files | Refs


infra/burst/state/main.tf (4313 bytes)

1 # The shared state of burst mode: Postgres and Redis on the web server, the
2 # file bucket (Cloudflare R2), and the key of the SSH tunnel that each burst
3 # worker uses to reach Postgres and Redis. Workers can come from any cloud;
4 # this module does not depend on one.
5 #
6 # Run it ON THE WEB SERVER, as root: the setup step changes this machine
7 # (see setup-web-server.sh). See ../README.md for when and how.
8 
9 terraform {
10   required_version = ">= 1.9"
11   required_providers {
12     cloudflare = {
13       source  = "cloudflare/cloudflare"
14       version = "~> 5.25"
15     }
16     random = {
17       source  = "hashicorp/random"
18       version = "~> 3.6"
19     }
20     tls = {
21       source  = "hashicorp/tls"
22       version = "~> 4.0"
23     }
24   }
25   # The state holds passwords and keys: /var/lib/subplz-burst, root only.
26   backend "local" {}
27 }
28 
29 # Reads CLOUDFLARE_API_TOKEN.
30 provider "cloudflare" {}
31 
32 resource "random_password" "postgres" {
33   length  = 32
34   special = false
35 }
36 
37 resource "random_password" "redis" {
38   length  = 32
39   special = false
40 }
41 
42 # One key for every worker: it can open the two tunnels and nothing else.
43 resource "tls_private_key" "tunnel" {
44   algorithm = "ED25519"
45 }
46 
47 resource "cloudflare_r2_bucket" "files" {
48   account_id = var.cloudflare_account_id
49   name       = var.bucket_name
50   location   = var.bucket_location
51 }
52 
53 # The permission groups "read objects" and "write objects" of R2, found by name
54 # unless var.r2_permission_group_ids gives them.
55 data "cloudflare_api_token_permission_groups_list" "r2_read" {
56   count = var.r2_permission_group_ids == null ? 1 : 0
57   name  = "Workers%20R2%20Storage%20Bucket%20Item%20Read"
58 }
59 
60 data "cloudflare_api_token_permission_groups_list" "r2_write" {
61   count = var.r2_permission_group_ids == null ? 1 : 0
62   name  = "Workers%20R2%20Storage%20Bucket%20Item%20Write"
63 }
64 
65 # The app and the workers get a key for this bucket only. R2 takes the token
66 # id as the S3 key id, and the SHA-256 of the token value as the S3 secret.
67 resource "cloudflare_api_token" "files" {
68   name = "${var.bucket_name}-app"
69   policies = [{
70     effect            = "allow"
71     permission_groups = [for id in local.r2_permission_groups : { id = id }]
72     resources = jsonencode({
73       "com.cloudflare.edge.r2.bucket.${var.cloudflare_account_id}_default_${cloudflare_r2_bucket.files.name}" = "*"
74     })
75   }]
76 }
77 
78 # Postgres and Redis on 127.0.0.1, and the tunnel user. The script is
79 # idempotent: a second run changes nothing.
80 resource "terraform_data" "web_server" {
81   triggers_replace = [
82     filesha256("${path.module}/setup-web-server.sh"),
83     sha256(random_password.postgres.result),
84     sha256(random_password.redis.result),
85     tls_private_key.tunnel.public_key_openssh,
86   ]
87 
88   provisioner "local-exec" {
89     command = "bash ${path.module}/setup-web-server.sh"
90     environment = {
91       POSTGRES_PASSWORD = random_password.postgres.result
92       REDIS_PASSWORD    = random_password.redis.result
93       TUNNEL_PUBLIC_KEY = tls_private_key.tunnel.public_key_openssh
94     }
95   }
96 }
97 
98 locals {
99   r2_permission_groups = var.r2_permission_group_ids != null ? var.r2_permission_group_ids : concat(
100     [for d in data.cloudflare_api_token_permission_groups_list.r2_read : d.result[0].id],
101     [for d in data.cloudflare_api_token_permission_groups_list.r2_write : d.result[0].id],
102   )
103   s3_endpoint = "https://${var.cloudflare_account_id}.r2.cloudflarestorage.com"
104   s3_key_id   = cloudflare_api_token.files.id
105   s3_secret   = sha256(cloudflare_api_token.files.value)
106   # Through the tunnel, a worker sees Postgres and Redis on its own 127.0.0.1,
107   # as the web server does.
108   database_url = "postgresql+psycopg://subplz:${random_password.postgres.result}@127.0.0.1:5432/subplz"
109   redis_url    = "redis://:${random_password.redis.result}@127.0.0.1:6379/0"
110   known_hosts = (
111     var.ssh_port == 22
112     ? "${var.web_server_ip} ${trimspace(file(var.host_key_file))}"
113     : "[${var.web_server_ip}]:${var.ssh_port} ${trimspace(file(var.host_key_file))}"
114   )
115   # boto3 1.36 and later send checksums that some S3 services refuse.
116   storage_env = <<-EOT
117     AWS_ACCESS_KEY_ID=${local.s3_key_id}
118     AWS_SECRET_ACCESS_KEY=${local.s3_secret}
119     AWS_ENDPOINT_URL_S3=${local.s3_endpoint}
120     AWS_DEFAULT_REGION=auto
121     AWS_REQUEST_CHECKSUM_CALCULATION=when_required
122     AWS_RESPONSE_CHECKSUM_VALIDATION=when_required
123   EOT
124 }