From 580e93a9a5b9b3a0952c1eed41a5f160cdef344a Mon Sep 17 00:00:00 2001 From: Mari Date: Mon, 8 Jun 2026 19:47:20 +0000 Subject: [PATCH 01/19] start iac with tf --- infra/README.md | 4 ++++ infra/main.tf | 51 +++++++++++++++++++++++++++++++++++++++++++++++++ infra/vars.tf | 44 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 infra/README.md create mode 100644 infra/main.tf create mode 100644 infra/vars.tf diff --git a/infra/README.md b/infra/README.md new file mode 100644 index 0000000..0716d56 --- /dev/null +++ b/infra/README.md @@ -0,0 +1,4 @@ +# `git.mari.zip` infrastructure + +This folder contains Terraform files and Ansible playbooks for managing the `git.mari.zip` GitArena instance and the required stack. +Use it as reference for deploying your GitArena on your own infrastructure. diff --git a/infra/main.tf b/infra/main.tf new file mode 100644 index 0000000..cb81e7c --- /dev/null +++ b/infra/main.tf @@ -0,0 +1,51 @@ +terraform { + required_version = ">= 1.11" + + required_providers { + tencentcloud = { + source = "tencentcloudstack/tencentcloud" + version = "1.82.74" + } + cloudflare = { + source = "cloudflare/cloudflare" + version = "5.19.0-beta.1" + } + aiven = { + source = "aiven/aiven" + version = "4.52.0" + } + vercel = { + source = "vercel/vercel" + version = "4.6.1" + } + newrelic = { + source = "newrelic/newrelic" + version = "3.81.0" + } + } +} + +provider "tencentcloud" { + secret_id = var.tencent_cloud_secret_id + secret_key = var.tencent_cloud_secret_key + region = "eu-frankfurt" +} + +provider "cloudflare" { + api_token = var.cloudflare_api_token +} + +provider "aiven" { + api_token = var.aiven_api_token +} + +provider "vercel" { + api_token = var.vercel_api_token + team = var.vercel_team +} + +provider "newrelic" { + account_id = var.newrelic_account_id + api_key = var.newrelic_api_token + region = "EU" +} diff --git a/infra/vars.tf b/infra/vars.tf new file mode 100644 index 0000000..738af83 --- /dev/null +++ b/infra/vars.tf @@ -0,0 +1,44 @@ +// Tencent Cloud +variable "tencent_cloud_secret_id" { + type = string + sensitive = true +} + +variable "tencent_cloud_secret_key" { + type = string + sensitive = true +} + +// Cloudflare +variable "cloudflare_api_token" { + type = string + sensitive = true +} + +// Aiven +variable "aiven_api_token" { + type = string + sensitive = true +} + +// Vercel +variable "vercel_api_token" { + type = string + sensitive = true +} + +variable "vercel_team" { + type = string + sensitive = true +} + +// NewRelic +variable "newrelic_account_id" { + type = string + sensitive = true +} + +variable "newrelic_api_token" { + type = string + sensitive = true +} From a7aa38d9033bdce6a35217c3fe59d2db9208c6c2 Mon Sep 17 00:00:00 2001 From: Mari Date: Mon, 8 Jun 2026 20:09:33 +0000 Subject: [PATCH 02/19] de-sensitize vercel team and newrelic account id --- infra/vars.tf | 2 -- 1 file changed, 2 deletions(-) diff --git a/infra/vars.tf b/infra/vars.tf index 738af83..6bca0d8 100644 --- a/infra/vars.tf +++ b/infra/vars.tf @@ -29,13 +29,11 @@ variable "vercel_api_token" { variable "vercel_team" { type = string - sensitive = true } // NewRelic variable "newrelic_account_id" { type = string - sensitive = true } variable "newrelic_api_token" { From d2df3130267b9a9502824c6889717686710313b6 Mon Sep 17 00:00:00 2001 From: Mari Date: Mon, 8 Jun 2026 20:15:33 +0000 Subject: [PATCH 03/19] add postgres --- infra/postgres.tf | 10 ++++++++++ infra/vars.tf | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 infra/postgres.tf diff --git a/infra/postgres.tf b/infra/postgres.tf new file mode 100644 index 0000000..4367b15 --- /dev/null +++ b/infra/postgres.tf @@ -0,0 +1,10 @@ +resource "aiven_pg" "main" { + project = "gitarena" + service_name = "gitarena" + cloud_name = "do-ams" + plan = "free-1-1gb" + + pg_user_config { + pg_version = "17" + } +} diff --git a/infra/vars.tf b/infra/vars.tf index 6bca0d8..8b02a18 100644 --- a/infra/vars.tf +++ b/infra/vars.tf @@ -28,12 +28,12 @@ variable "vercel_api_token" { } variable "vercel_team" { - type = string + type = string } // NewRelic variable "newrelic_account_id" { - type = string + type = string } variable "newrelic_api_token" { From 75268adac61e7d1a4ec1120460c01d025ae5fa4c Mon Sep 17 00:00:00 2001 From: Mari Date: Mon, 8 Jun 2026 20:30:32 +0000 Subject: [PATCH 04/19] tweak postgres for import --- infra/.gitignore | 32 +++++++++++ infra/.terraform.lock.hcl | 112 ++++++++++++++++++++++++++++++++++++++ infra/postgres.tf | 7 +++ infra/vars.tf | 7 +++ 4 files changed, 158 insertions(+) create mode 100644 infra/.gitignore create mode 100644 infra/.terraform.lock.hcl diff --git a/infra/.gitignore b/infra/.gitignore new file mode 100644 index 0000000..fd83ef5 --- /dev/null +++ b/infra/.gitignore @@ -0,0 +1,32 @@ +### Terraform ### +# Local .terraform directories +**/.terraform/* + +# .tfstate files +*.tfstate +*.tfstate.* + +# Crash log files +crash.log +crash.*.log + +# Exclude all .tfvars files, which are likely to contain sensitive data, such as +# password, private keys, and other secrets. These should not be part of version +# control as they are data points which are potentially sensitive and subject +# to change depending on the environment. +*.tfvars +*.tfvars.json + +# Ignore override files as they are usually used to override resources locally and so +# are not checked in +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan +*tfplan* + +# Ignore CLI configuration files +.terraformrc +terraform.rc diff --git a/infra/.terraform.lock.hcl b/infra/.terraform.lock.hcl new file mode 100644 index 0000000..aeeaadd --- /dev/null +++ b/infra/.terraform.lock.hcl @@ -0,0 +1,112 @@ +# This file is maintained automatically by "tofu init". +# Manual edits may be lost in future updates. + +provider "registry.opentofu.org/aiven/aiven" { + version = "4.52.0" + constraints = "4.52.0" + hashes = [ + "h1:dLC5KaPjLv+9JAv3iciQ/5QyWs2cLHma0/Nzql8MSRs=", + "zh:3861adbc2af6c20c3d9a009deb72665299c0b052345c899e02d70dd4d75e9836", + "zh:3bcf8018fe2d6aabee0d0ff4fa991aa830590ff4337445e49d12636bab5cc29e", + "zh:472c048c118b9311358d4ce14137351e24af2079cf9dfa5da81be6a09bf46de2", + "zh:5dfb9922b1b5c253accd9c7609614bff50224887fb6296a9bb0e57653bacb2e7", + "zh:65a27c83e65e2dfd0d64637355c4aeee0949a639c1cb8a9ce573fc06afa1ebbe", + "zh:7a5f98c363ae3d0a5af4be7e8770d4f721c3e81c5664c626b47ed623cac5bdc6", + "zh:88bde4f9f0a8ef0a2e92e0c737577595c47270792e93351f9bca1df16b5f4620", + "zh:8bf1277959019b614ad1f0f5f9d2e2512b27c5dcdf0440f93bce202900419b44", + "zh:8d2035e6e37fb7a1f7d400a4f13bde7c7c0301aafc322ea8d6d1c9029ab1bf7b", + "zh:995c7e3cf65098af2217c4e7ca41c5c7e9514a03ed2b5183748d01de8a97340a", + "zh:a637c8a87fcc579a12e6018a7138608a7d41945a4f1ea4918a97b60c952ab278", + "zh:a71e4728c2a804120b441c607282240a3849ca4048846478255eb6ea278ce103", + "zh:edc7d97576fb5a6a720303bcefaadb32050bae563b47b426ca00cfb1ed8845e4", + "zh:fdc29b6d096ea90e628b6674674247c94f263ffb70954341d67991e61533e0af", + ] +} + +provider "registry.opentofu.org/cloudflare/cloudflare" { + version = "5.19.0-beta.1" + constraints = "5.19.0-beta.1" + hashes = [ + "h1:ww+abkB/1fPvl2+U8MGPI2pmlu0FosxFFbIoT8RhKyI=", + "zh:35bb2678ae60b19fbeb2907f6716e79b30e56a0f84e3f3e48152d0f33b9615e6", + "zh:47e8a190296f81e3a752ad31fc3bac6d71fbace8ba5588ff51722552d997b5ea", + "zh:4ca15c9e280ba454133b9ed9b8ce5ec41fef48237b2b2030b09990e4dd226d1e", + "zh:7135772003b4f652436b6ef35f4f585c553627b73b9458a8f7745e4ce3e6e337", + "zh:84d59a18fd5c2712e890638101b83242463d80d63985b6c9bafa7a7253e3c4e0", + "zh:a0df0a27b752cab9e777852d1e42860890775ec65ae758af834893f8776d309e", + "zh:afd2638778185984b2b608510fcc136d1a3583d9f471c328c724800534c16e2b", + "zh:f809ab383cca0a5f83072981c64208cbd7fa67e986a86ee02dd2c82333221e32", + "zh:f94dadaff6f77352933d242c95708c05f91b8a06e1f4394a8a3725b2ad2a730b", + ] +} + +provider "registry.opentofu.org/newrelic/newrelic" { + version = "3.81.0" + constraints = "3.81.0" + hashes = [ + "h1:G2Ie0sSjDLNV4sxodzmH9USaeU1cN8VIlyGqdGfiFU8=", + "zh:0af973805868bcfe093375c5f3572cfdf1d237c28405a7bcde25a2749e392481", + "zh:144e3ac38e15115c1bb5c490d9ed88b5dcbc864d5cb4908e0211affff25fa791", + "zh:1617e14a29ae815aa0a502a23cc69463fcc15b2ae8538ecfa2dabcef2ce9c133", + "zh:445e5bed4f8ef59b602ec6471e6c6849903e4a2b63b376276fa4ca69ea18bee5", + "zh:61206498e9c22726251515f608e0e87f3d6f990b39c24915f4ac84ff78412bce", + "zh:6eaa4ca7b1b5d55f24d1dd8c4b998c17fe424b56a3cb0705d8e43e3046cb361c", + "zh:8a4975dbcbc200375d329e41a584c88d6ece58ab2db857dcfdabd86f8f9d4c19", + "zh:8f834afbb3bf30da27fc8b1b0580184c5dd832a89992d29e6b59565fa6cbcf2e", + "zh:903fbe54028f4f2c1c19dded286867b9d0c54da361215e0e944f6c7fb6e2728a", + "zh:9ed526701509a966fa1d47bf907ee8c297e94702703b05dee0d6cd1f637733e5", + "zh:d60fef0fa44c70abfba5064e789335316481ac3ca1be3a00d1434e951d4ab0fb", + "zh:d9e039c74162ab7e48444ea8a0357f411676aaa19905c55f804c84df296c2944", + "zh:dc11afea5db3ed4bb42406cc1c6725ff01f4d599b0093daa4456862aee5488f7", + "zh:e22d6ea423c7e784b20c6835d2eb57022716fbf0f25cb0cc0d2b61d5cc73ac86", + "zh:e639038f303d8de0d227d36c5f43cef27dd40bde944c218eef84c21cb3060505", + "zh:ef0f54494a0d27d55f06cf983160a743c7892d14d7d7b0539b90f4483f4369ed", + "zh:f999e4e43c9ecf75dc5300741ad473c9a27894b87606c93090736b9a7cd2874e", + "zh:fbd1fee2c9df3aa19cf8851ce134dea6e45ea01cb85695c1726670c285797e25", + ] +} + +provider "registry.opentofu.org/tencentcloudstack/tencentcloud" { + version = "1.82.74" + constraints = "1.82.74" + hashes = [ + "h1:LYmsbmzkBC1l8+uZt0J7oREtKX9cVPyCGUTmI7IyvEk=", + "zh:07a1b81f6e83d49d395dbbc3e33a116e720eb5a2b35ba6cf56173ea7729b8f74", + "zh:0bac52b6355512aa2bd8da1b066886382cb23b03b3fe98c4e516354f8a87ae18", + "zh:1822de94e4ca9d953c67ac433e78704d1a9ae43e7515ed4da642dcbefe37e0ff", + "zh:79def4f6c35f16b13b0f6614f21ecaf1ee12e6b05a00f0174ee1cebde63b68d6", + "zh:7ebdd88b2a6108a9676837f30e5eb04bc4956a9c8662f0ca67466a150f9f9dc0", + "zh:9552c9f317a3c8dc83a83607d168e14c3c1fea9ebddbc8bd900751e5b7de703d", + "zh:9cf055e94923b96a634c96a24dff63f9bfe2028b9bc04ddd1af56449add1fa90", + "zh:a76c19b19126a143afbe50c3a6585bfc291766d337289e6a73eabbb74de13ce0", + "zh:b84b1a1bdea4703f8247a921b14d1d9cf1a6a174165bb0dad895c8177bb123f7", + "zh:bf9be2d950de3836306889b30596bc6ae0a11a5a0c8c95573b9efa82f68e5fdf", + "zh:c5fc31bf9068aab9d48132cce35e29fb2b111bd289f18cdd504d0f3d04b8df8b", + "zh:c690a1d9eb68ebfab0f5440093f38bf14e2c238f238744e5e10448ebd4064a5c", + "zh:e465cd49359c8dc63f005f64b53be156611ce7a87bfa638668416698d298e26e", + "zh:ff876a14d9573dd66759192ac7f96c397109692db3775230c36e01811909740b", + ] +} + +provider "registry.opentofu.org/vercel/vercel" { + version = "4.6.1" + constraints = "4.6.1" + hashes = [ + "h1:xCN8oCbSFE0XC2Dqqz1Q0sPE7Pn+23b0JvaZIH0iLPU=", + "zh:240f12e66b5bfbc216b78b38761b4e62ebc5590fd8051d3f681ee2789c82437c", + "zh:34a6a8ecf0a389d9755ea3740fa48b4e12de57595209b26ac0a88aa65f0dd15b", + "zh:3feb6e58cebe1d441fd3caacfc8341e742905d22cceb7189fd0ea16cc8e66a66", + "zh:40826f814d6509a3e17f70425598214da3aead8ad8149821f4394b92fb8f1e83", + "zh:40ab52d0e84df0b98be68585e0be785de11c0e073264b3695eda314afdb62a66", + "zh:504f5e2d0d860ae9a8696a49a8855a3894badd928a5672521af09e13d0d68f10", + "zh:9317aafc789716433b4ad5b1f54ea807cba0faa7262752d298f24ee330503189", + "zh:9a4f4d4027182fea739a6d30e86c1f83f42f819e61344f9dbd84ade7d4fc3a27", + "zh:c2a196e526986546dee3825bb13c26aace145f0af26ac3ddbfe55499d1fd05c9", + "zh:cb69fc86f5ba56e5cd35b8044f56e26f8fe1e99e9c4571f368f834ca52a0c74f", + "zh:d2737c20efb5d5e08f153732a0dfe5685fb3989aa71db95805c921c0a3c77e55", + "zh:ee17a1817bc5220cb35f10feb737ec1bac6aedc31a7ba8c020965a1d1a610764", + "zh:f26e0763dbe6a6b2195c94b44696f2110f7f55433dc142839be16b9697fa5597", + "zh:f5798497eb2a7ec6015b5416987228019f9efe9ab05ca6332cf3f64f2cdef729", + "zh:fb984f9e18673acdd72ca4d29bf8bad34376bd9ff3252437e24c770862c85454", + ] +} diff --git a/infra/postgres.tf b/infra/postgres.tf index 4367b15..02772a5 100644 --- a/infra/postgres.tf +++ b/infra/postgres.tf @@ -4,7 +4,14 @@ resource "aiven_pg" "main" { cloud_name = "do-ams" plan = "free-1-1gb" + termination_protection = true + pg_user_config { pg_version = "17" + + ip_filter_string = [ + "162.62.58.144/32", // todo: change to tencent ip once its hooked up to tf + var.local_ip_block, + ] } } diff --git a/infra/vars.tf b/infra/vars.tf index 8b02a18..540fb2a 100644 --- a/infra/vars.tf +++ b/infra/vars.tf @@ -1,3 +1,10 @@ +// Local vars +variable "local_ip_block" { + type = string + sensitive = true +} + + // Tencent Cloud variable "tencent_cloud_secret_id" { type = string From af90d92913c64e2629cbea373c88be1c86902eea Mon Sep 17 00:00:00 2001 From: Mari Date: Mon, 8 Jun 2026 20:54:05 +0000 Subject: [PATCH 05/19] add vercel --- infra/vars.tf | 3 +-- infra/vercel.tf | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 infra/vercel.tf diff --git a/infra/vars.tf b/infra/vars.tf index 540fb2a..dd4a3aa 100644 --- a/infra/vars.tf +++ b/infra/vars.tf @@ -1,10 +1,9 @@ // Local vars variable "local_ip_block" { - type = string + type = string sensitive = true } - // Tencent Cloud variable "tencent_cloud_secret_id" { type = string diff --git a/infra/vercel.tf b/infra/vercel.tf new file mode 100644 index 0000000..e8f18f5 --- /dev/null +++ b/infra/vercel.tf @@ -0,0 +1,20 @@ +resource "vercel_project" "main" { + name = "gitarena" + framework = "nextjs" + root_directory = "gitarena-frontend" + + git_repository = { + type = "github" + repo = "mellowagain/gitarena" + } +} + +resource "vercel_project_environment_variable" "next_public_api_url" { + project_id = vercel_project.main.id + + key = "NEXT_PUBLIC_API_URL" + value = "https://api.git.mari.zip" + sensitive = false + + target = ["production", "preview", "development"] +} From 9771a60da7579b0c94ee479f92e4a9e9a7712c86 Mon Sep 17 00:00:00 2001 From: Mari Date: Mon, 8 Jun 2026 21:01:31 +0000 Subject: [PATCH 06/19] add domain vars --- infra/vars.tf | 10 ++++++++++ infra/vercel.tf | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/infra/vars.tf b/infra/vars.tf index dd4a3aa..201b88b 100644 --- a/infra/vars.tf +++ b/infra/vars.tf @@ -1,4 +1,14 @@ // Local vars +variable "frontend_domain" { + type = string + default = "https://git.mari.zip" +} + +variable "backend_domain" { + type = string + default = "https://api.git.mari.zip" +} + variable "local_ip_block" { type = string sensitive = true diff --git a/infra/vercel.tf b/infra/vercel.tf index e8f18f5..6793fa4 100644 --- a/infra/vercel.tf +++ b/infra/vercel.tf @@ -13,7 +13,7 @@ resource "vercel_project_environment_variable" "next_public_api_url" { project_id = vercel_project.main.id key = "NEXT_PUBLIC_API_URL" - value = "https://api.git.mari.zip" + value = var.backend_domain sensitive = false target = ["production", "preview", "development"] From fe063b050a8838332ff50ad2ea96e7d1a3a142e9 Mon Sep 17 00:00:00 2001 From: Mari Date: Mon, 8 Jun 2026 21:09:24 +0000 Subject: [PATCH 07/19] add tencent --- infra/tencent.tf | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 infra/tencent.tf diff --git a/infra/tencent.tf b/infra/tencent.tf new file mode 100644 index 0000000..794c181 --- /dev/null +++ b/infra/tencent.tf @@ -0,0 +1,7 @@ +resource "tencentcloud_lighthouse_instance" "main" { + instance_name = "Ubuntu-YUz3" + zone = "eu-frankfurt-1" + bundle_id = "bundle_starter_nmc_lin_med2_01" + blueprint_id = "lhbp-b46k6f98" + renew_flag = "NOTIFY_AND_MANUAL_RENEW" +} From 05e878601e2a29d35d69afe6de758e2bef7e0ffc Mon Sep 17 00:00:00 2001 From: Mari Date: Mon, 8 Jun 2026 21:13:47 +0000 Subject: [PATCH 08/19] hook up tencent lighthouse ip to postgres filter --- infra/postgres.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/postgres.tf b/infra/postgres.tf index 02772a5..46680b3 100644 --- a/infra/postgres.tf +++ b/infra/postgres.tf @@ -10,7 +10,7 @@ resource "aiven_pg" "main" { pg_version = "17" ip_filter_string = [ - "162.62.58.144/32", // todo: change to tencent ip once its hooked up to tf + "${tencentcloud_lighthouse_instance.main.public_addresses[0]}/32", var.local_ip_block, ] } From 8c1400cac0a9c57aaa6a46bab5b1c77329e0a760 Mon Sep 17 00:00:00 2001 From: Mari Date: Mon, 8 Jun 2026 22:03:11 +0000 Subject: [PATCH 09/19] add cloudflare dns --- infra/cloudflare.tf | 17 +++++++++++++++++ infra/vars.tf | 8 ++++++-- infra/vercel.tf | 12 +++++++++++- 3 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 infra/cloudflare.tf diff --git a/infra/cloudflare.tf b/infra/cloudflare.tf new file mode 100644 index 0000000..f108b79 --- /dev/null +++ b/infra/cloudflare.tf @@ -0,0 +1,17 @@ +resource "cloudflare_dns_record" "backend" { + zone_id = var.cloudflare_zone_id + name = var.backend_domain + type = "A" + content = tencentcloud_lighthouse_instance.main.public_addresses[0] + proxied = false + ttl = 1 +} + +resource "cloudflare_dns_record" "frontend" { + zone_id = var.cloudflare_zone_id + name = var.frontend_domain + type = "CNAME" + content = data.vercel_domain_config.frontend.recommended_cname + proxied = false + ttl = 600 +} diff --git a/infra/vars.tf b/infra/vars.tf index 201b88b..db2b261 100644 --- a/infra/vars.tf +++ b/infra/vars.tf @@ -1,12 +1,12 @@ // Local vars variable "frontend_domain" { type = string - default = "https://git.mari.zip" + default = "git.mari.zip" } variable "backend_domain" { type = string - default = "https://api.git.mari.zip" + default = "api.git.mari.zip" } variable "local_ip_block" { @@ -31,6 +31,10 @@ variable "cloudflare_api_token" { sensitive = true } +variable "cloudflare_zone_id" { + type = string +} + // Aiven variable "aiven_api_token" { type = string diff --git a/infra/vercel.tf b/infra/vercel.tf index 6793fa4..51e1bde 100644 --- a/infra/vercel.tf +++ b/infra/vercel.tf @@ -9,11 +9,21 @@ resource "vercel_project" "main" { } } +resource "vercel_project_domain" "frontend" { + project_id = vercel_project.main.id + domain = var.frontend_domain +} + +data "vercel_domain_config" "frontend" { + domain = var.frontend_domain + project_id_or_name = vercel_project.main.id +} + resource "vercel_project_environment_variable" "next_public_api_url" { project_id = vercel_project.main.id key = "NEXT_PUBLIC_API_URL" - value = var.backend_domain + value = "https://${var.backend_domain}" sensitive = false target = ["production", "preview", "development"] From 50510454044f39525ac85c028dd4c195d69e31eb Mon Sep 17 00:00:00 2001 From: Mari Date: Mon, 8 Jun 2026 22:26:57 +0000 Subject: [PATCH 10/19] add r2 --- infra/cloudflare.tf | 31 +++++++++++++++++++++++++++++++ infra/vars.tf | 4 ++++ 2 files changed, 35 insertions(+) diff --git a/infra/cloudflare.tf b/infra/cloudflare.tf index f108b79..aecab69 100644 --- a/infra/cloudflare.tf +++ b/infra/cloudflare.tf @@ -15,3 +15,34 @@ resource "cloudflare_dns_record" "frontend" { proxied = false ttl = 600 } + +resource "cloudflare_r2_bucket" "artifacts" { + account_id = var.cloudflare_account_id + name = "gitarena" + location = "WEUR" +} + +resource "cloudflare_r2_custom_domain" "artifacts" { + account_id = var.cloudflare_account_id + bucket_name = cloudflare_r2_bucket.artifacts.name + domain = "objects.${var.frontend_domain}" + zone_id = var.cloudflare_zone_id + enabled = true +} + +resource "cloudflare_r2_bucket_cors" "artifacts" { + account_id = var.cloudflare_account_id + bucket_name = cloudflare_r2_bucket.artifacts.name + + rules = [{ + allowed = { + origins = ["https://${var.frontend_domain}", "https://${var.backend_domain}"] + methods = ["GET", "PUT"] + headers = ["*"] + } + expose = { + headers = ["ETag"] + } + max_age_seconds = 3600 + }] +} diff --git a/infra/vars.tf b/infra/vars.tf index db2b261..78100eb 100644 --- a/infra/vars.tf +++ b/infra/vars.tf @@ -35,6 +35,10 @@ variable "cloudflare_zone_id" { type = string } +variable "cloudflare_account_id" { + type = string +} + // Aiven variable "aiven_api_token" { type = string From 4578594413f7a81ebf0418ec72b1f9d45a21fd61 Mon Sep 17 00:00:00 2001 From: Mari Date: Mon, 8 Jun 2026 22:58:34 +0000 Subject: [PATCH 11/19] add resend --- infra/.terraform.lock.hcl | 14 ++++++++++++ infra/cloudflare.tf | 18 ---------------- infra/dns.tf | 45 +++++++++++++++++++++++++++++++++++++++ infra/main.tf | 27 ++++------------------- infra/providers.tf | 28 ++++++++++++++++++++++++ infra/resend.tf | 4 ++++ infra/vars.tf | 6 ++++++ 7 files changed, 101 insertions(+), 41 deletions(-) create mode 100644 infra/dns.tf create mode 100644 infra/providers.tf create mode 100644 infra/resend.tf diff --git a/infra/.terraform.lock.hcl b/infra/.terraform.lock.hcl index aeeaadd..2417eba 100644 --- a/infra/.terraform.lock.hcl +++ b/infra/.terraform.lock.hcl @@ -110,3 +110,17 @@ provider "registry.opentofu.org/vercel/vercel" { "zh:fb984f9e18673acdd72ca4d29bf8bad34376bd9ff3252437e24c770862c85454", ] } + +provider "registry.terraform.io/jhoward321/resend" { + version = "0.1.3" + constraints = "0.1.3" + hashes = [ + "h1:EfoTJu8ZDYwSgyHJ0cILBwWhHdUxqiPAaSsIFRqxZ2w=", + "zh:1386c1a76e0b058535f35dd4718ebb4be2fbc6967aae7132bdb65b67b2c8af2a", + "zh:1d4e78bd3eebacf3ee3a1f6c5d635615a893a2d385956e82566775ea6d387c92", + "zh:3995967d8e4796ccdcff64d733589ef11bc4c30cad06ecb1eb45c0b33684a7e8", + "zh:447d7da914070a63aba10127b0eebbb1a507c1787d23873618b16fa702060502", + "zh:8135e0e17b8ee698f3dc76756f974a4b1b3e0a37a9f68cb04b7804bc701319f0", + "zh:9a37fddac3c1705e7916549406abf3a2dcee39d59aa97e76cc70b8f0a25b7a75", + ] +} diff --git a/infra/cloudflare.tf b/infra/cloudflare.tf index aecab69..bbe43f2 100644 --- a/infra/cloudflare.tf +++ b/infra/cloudflare.tf @@ -1,21 +1,3 @@ -resource "cloudflare_dns_record" "backend" { - zone_id = var.cloudflare_zone_id - name = var.backend_domain - type = "A" - content = tencentcloud_lighthouse_instance.main.public_addresses[0] - proxied = false - ttl = 1 -} - -resource "cloudflare_dns_record" "frontend" { - zone_id = var.cloudflare_zone_id - name = var.frontend_domain - type = "CNAME" - content = data.vercel_domain_config.frontend.recommended_cname - proxied = false - ttl = 600 -} - resource "cloudflare_r2_bucket" "artifacts" { account_id = var.cloudflare_account_id name = "gitarena" diff --git a/infra/dns.tf b/infra/dns.tf new file mode 100644 index 0000000..c37e4c5 --- /dev/null +++ b/infra/dns.tf @@ -0,0 +1,45 @@ +resource "cloudflare_dns_record" "backend" { + zone_id = var.cloudflare_zone_id + name = var.backend_domain + type = "A" + content = tencentcloud_lighthouse_instance.main.public_addresses[0] + proxied = false + ttl = 1 +} + +resource "cloudflare_dns_record" "frontend" { + zone_id = var.cloudflare_zone_id + name = var.frontend_domain + type = "CNAME" + content = data.vercel_domain_config.frontend.recommended_cname + proxied = false + ttl = 600 +} + +resource "cloudflare_dns_record" "mail" { + zone_id = var.cloudflare_zone_id + name = resend_domain.main.spf_mx_record.name + type = resend_domain.main.spf_mx_record.type + content = resend_domain.main.spf_mx_record.value + priority = resend_domain.main.spf_mx_record.priority + proxied = false + ttl = resend_domain.main.spf_mx_record.ttl +} + +resource "cloudflare_dns_record" "mail_txt" { + zone_id = var.cloudflare_zone_id + name = resend_domain.main.spf_txt_record.name + type = resend_domain.main.spf_txt_record.type + content = resend_domain.main.spf_txt_record.value + proxied = false + ttl = resend_domain.main.spf_txt_record.ttl +} + +resource "cloudflare_dns_record" "mail_dkim" { + zone_id = var.cloudflare_zone_id + name = resend_domain.main.dkim_records[0].name + type = resend_domain.main.dkim_records[0].type + content = resend_domain.main.dkim_records[0].value + proxied = false + ttl = resend_domain.main.dkim_records[0].ttl +} diff --git a/infra/main.tf b/infra/main.tf index cb81e7c..11ff629 100644 --- a/infra/main.tf +++ b/infra/main.tf @@ -18,6 +18,10 @@ terraform { source = "vercel/vercel" version = "4.6.1" } + resend = { + source = "registry.terraform.io/jhoward321/resend" + version = "0.1.3" + } newrelic = { source = "newrelic/newrelic" version = "3.81.0" @@ -25,27 +29,4 @@ terraform { } } -provider "tencentcloud" { - secret_id = var.tencent_cloud_secret_id - secret_key = var.tencent_cloud_secret_key - region = "eu-frankfurt" -} -provider "cloudflare" { - api_token = var.cloudflare_api_token -} - -provider "aiven" { - api_token = var.aiven_api_token -} - -provider "vercel" { - api_token = var.vercel_api_token - team = var.vercel_team -} - -provider "newrelic" { - account_id = var.newrelic_account_id - api_key = var.newrelic_api_token - region = "EU" -} diff --git a/infra/providers.tf b/infra/providers.tf new file mode 100644 index 0000000..b000880 --- /dev/null +++ b/infra/providers.tf @@ -0,0 +1,28 @@ +provider "tencentcloud" { + secret_id = var.tencent_cloud_secret_id + secret_key = var.tencent_cloud_secret_key + region = "eu-frankfurt" +} + +provider "cloudflare" { + api_token = var.cloudflare_api_token +} + +provider "aiven" { + api_token = var.aiven_api_token +} + +provider "vercel" { + api_token = var.vercel_api_token + team = var.vercel_team +} + +provider "resend" { + api_key = var.resend_api_key +} + +provider "newrelic" { + account_id = var.newrelic_account_id + api_key = var.newrelic_api_token + region = "EU" +} diff --git a/infra/resend.tf b/infra/resend.tf new file mode 100644 index 0000000..423fffa --- /dev/null +++ b/infra/resend.tf @@ -0,0 +1,4 @@ +resource "resend_domain" "main" { + name = "send.${var.frontend_domain}" + region = "eu-west-1" +} diff --git a/infra/vars.tf b/infra/vars.tf index 78100eb..83f4866 100644 --- a/infra/vars.tf +++ b/infra/vars.tf @@ -55,6 +55,12 @@ variable "vercel_team" { type = string } +// Resend +variable "resend_api_key" { + type = string + sensitive = true +} + // NewRelic variable "newrelic_account_id" { type = string From a97ce81d1094f7c09a64b7add6b5dc8577b6eb1c Mon Sep 17 00:00:00 2001 From: Mari Date: Mon, 8 Jun 2026 23:12:24 +0000 Subject: [PATCH 12/19] cast to number --- infra/dns.tf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/infra/dns.tf b/infra/dns.tf index c37e4c5..1979c5f 100644 --- a/infra/dns.tf +++ b/infra/dns.tf @@ -23,7 +23,7 @@ resource "cloudflare_dns_record" "mail" { content = resend_domain.main.spf_mx_record.value priority = resend_domain.main.spf_mx_record.priority proxied = false - ttl = resend_domain.main.spf_mx_record.ttl + ttl = try(tonumber(resend_domain.main.spf_mx_record.ttl), 3600) } resource "cloudflare_dns_record" "mail_txt" { @@ -32,7 +32,7 @@ resource "cloudflare_dns_record" "mail_txt" { type = resend_domain.main.spf_txt_record.type content = resend_domain.main.spf_txt_record.value proxied = false - ttl = resend_domain.main.spf_txt_record.ttl + ttl = try(tonumber(resend_domain.main.spf_txt_record.ttl), 3600) } resource "cloudflare_dns_record" "mail_dkim" { @@ -41,5 +41,5 @@ resource "cloudflare_dns_record" "mail_dkim" { type = resend_domain.main.dkim_records[0].type content = resend_domain.main.dkim_records[0].value proxied = false - ttl = resend_domain.main.dkim_records[0].ttl + ttl = try(tonumber(resend_domain.main.dkim_records[0].ttl), 3600) } From a8302badceed230c56830194c6cea7504607142b Mon Sep 17 00:00:00 2001 From: Mari Date: Mon, 8 Jun 2026 23:19:22 +0000 Subject: [PATCH 13/19] reference right domain --- infra/resend.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/resend.tf b/infra/resend.tf index 423fffa..a534dd1 100644 --- a/infra/resend.tf +++ b/infra/resend.tf @@ -1,4 +1,4 @@ resource "resend_domain" "main" { - name = "send.${var.frontend_domain}" + name = var.frontend_domain region = "eu-west-1" } From 35a43fd5456928aed0ff467c8b589054c3e0f3ad Mon Sep 17 00:00:00 2001 From: Mari Date: Mon, 8 Jun 2026 23:27:56 +0000 Subject: [PATCH 14/19] trim suffix to prevent endless re-plans --- infra/dns.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/dns.tf b/infra/dns.tf index 1979c5f..1a489b4 100644 --- a/infra/dns.tf +++ b/infra/dns.tf @@ -11,7 +11,7 @@ resource "cloudflare_dns_record" "frontend" { zone_id = var.cloudflare_zone_id name = var.frontend_domain type = "CNAME" - content = data.vercel_domain_config.frontend.recommended_cname + content = trimsuffix(data.vercel_domain_config.frontend.recommended_cname, ".") proxied = false ttl = 600 } From ae367b56e805440af8c8ac1f25a7f50257ef1c41 Mon Sep 17 00:00:00 2001 From: Mari Date: Mon, 8 Jun 2026 23:39:21 +0000 Subject: [PATCH 15/19] add firewall rules --- infra/tencent.tf | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/infra/tencent.tf b/infra/tencent.tf index 794c181..50fb710 100644 --- a/infra/tencent.tf +++ b/infra/tencent.tf @@ -1,7 +1,33 @@ resource "tencentcloud_lighthouse_instance" "main" { - instance_name = "Ubuntu-YUz3" - zone = "eu-frankfurt-1" - bundle_id = "bundle_starter_nmc_lin_med2_01" - blueprint_id = "lhbp-b46k6f98" - renew_flag = "NOTIFY_AND_MANUAL_RENEW" + instance_name = "Ubuntu-YUz3" + zone = "eu-frankfurt-1" + bundle_id = "bundle_starter_nmc_lin_med2_01" + blueprint_id = "lhbp-b46k6f98" + renew_flag = "NOTIFY_AND_MANUAL_RENEW" + firewall_template_id = tencentcloud_lighthouse_firewall_template.main_firewall.id +} + +resource "tencentcloud_lighthouse_firewall_template" "main_firewall" { + template_name = "gitarena" + + template_rules { + protocol = "TCP" + port = "80,443" + action = "ACCEPT" + firewall_rule_description = "caddy" + } + + template_rules { + protocol = "TCP" + port = "22,2222" + action = "ACCEPT" + firewall_rule_description = "ssh" + } + + template_rules { + protocol = "ICMP" + port = "ALL" + action = "ACCEPT" + firewall_rule_description = "ping" + } } From e7e7a2589d4de7b40d17ceca98bb50e24de3e44d Mon Sep 17 00:00:00 2001 From: Mari Date: Mon, 8 Jun 2026 23:43:08 +0000 Subject: [PATCH 16/19] add cidr block --- infra/tencent.tf | 3 +++ 1 file changed, 3 insertions(+) diff --git a/infra/tencent.tf b/infra/tencent.tf index 50fb710..acc1a98 100644 --- a/infra/tencent.tf +++ b/infra/tencent.tf @@ -13,6 +13,7 @@ resource "tencentcloud_lighthouse_firewall_template" "main_firewall" { template_rules { protocol = "TCP" port = "80,443" + cidr_block = "0.0.0.0/0" action = "ACCEPT" firewall_rule_description = "caddy" } @@ -20,6 +21,7 @@ resource "tencentcloud_lighthouse_firewall_template" "main_firewall" { template_rules { protocol = "TCP" port = "22,2222" + cidr_block = "0.0.0.0/0" action = "ACCEPT" firewall_rule_description = "ssh" } @@ -27,6 +29,7 @@ resource "tencentcloud_lighthouse_firewall_template" "main_firewall" { template_rules { protocol = "ICMP" port = "ALL" + cidr_block = "0.0.0.0/0" action = "ACCEPT" firewall_rule_description = "ping" } From c9a292a02bea2c7acbe70eb342b45b1dc94680d6 Mon Sep 17 00:00:00 2001 From: Mari Date: Mon, 8 Jun 2026 23:49:18 +0000 Subject: [PATCH 17/19] instance exists already cant apply template afterwards --- infra/tencent.tf | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/infra/tencent.tf b/infra/tencent.tf index acc1a98..7ea70f3 100644 --- a/infra/tencent.tf +++ b/infra/tencent.tf @@ -4,13 +4,17 @@ resource "tencentcloud_lighthouse_instance" "main" { bundle_id = "bundle_starter_nmc_lin_med2_01" blueprint_id = "lhbp-b46k6f98" renew_flag = "NOTIFY_AND_MANUAL_RENEW" - firewall_template_id = tencentcloud_lighthouse_firewall_template.main_firewall.id + firewall_template_id = tencentcloud_lighthouse_firewall_template.empty.id + + lifecycle { + ignore_changes = [firewall_template_id] + } } -resource "tencentcloud_lighthouse_firewall_template" "main_firewall" { - template_name = "gitarena" +resource "tencentcloud_lighthouse_firewall_rule" "main_firewall" { + instance_id = tencentcloud_lighthouse_instance.main.id - template_rules { + firewall_rules { protocol = "TCP" port = "80,443" cidr_block = "0.0.0.0/0" @@ -18,7 +22,7 @@ resource "tencentcloud_lighthouse_firewall_template" "main_firewall" { firewall_rule_description = "caddy" } - template_rules { + firewall_rules { protocol = "TCP" port = "22,2222" cidr_block = "0.0.0.0/0" @@ -26,7 +30,7 @@ resource "tencentcloud_lighthouse_firewall_template" "main_firewall" { firewall_rule_description = "ssh" } - template_rules { + firewall_rules { protocol = "ICMP" port = "ALL" cidr_block = "0.0.0.0/0" @@ -34,3 +38,7 @@ resource "tencentcloud_lighthouse_firewall_template" "main_firewall" { firewall_rule_description = "ping" } } + +resource "tencentcloud_lighthouse_firewall_template" "empty" { + template_name = "empty" +} From 163992e6fca9a0d9329b91610e43893b9e45b1fc Mon Sep 17 00:00:00 2001 From: Mari Date: Tue, 9 Jun 2026 00:09:34 +0000 Subject: [PATCH 18/19] add vercel reminder --- infra/README.md | 4 +++- infra/vercel.tf | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/infra/README.md b/infra/README.md index 0716d56..073344d 100644 --- a/infra/README.md +++ b/infra/README.md @@ -1,4 +1,6 @@ # `git.mari.zip` infrastructure -This folder contains Terraform files and Ansible playbooks for managing the `git.mari.zip` GitArena instance and the required stack. +This folder contains Terraform (OpenTofu) files for managing the `git.mari.zip` GitArena instance and the required stack. Use it as reference for deploying your GitArena on your own infrastructure. + +Later down the road, ansible playbooks will be added to auto setup GitArena on the terraform-provisioned cloud instance. diff --git a/infra/vercel.tf b/infra/vercel.tf index 51e1bde..c916e3a 100644 --- a/infra/vercel.tf +++ b/infra/vercel.tf @@ -28,3 +28,13 @@ resource "vercel_project_environment_variable" "next_public_api_url" { target = ["production", "preview", "development"] } + +resource "terraform_data" "vercel_redeploy_reminder" { + triggers_replace = { + env_vars = vercel_project_environment_variable.next_public_api_url.value + } + + provisioner "local-exec" { + command = "echo 'Vercel env vars changed, redeploy to apply: https://vercel.com/dashboard'" + } +} From 7f3fcdce06279b43f8be1f32fabf33b584dc5415 Mon Sep 17 00:00:00 2001 From: Mari Date: Tue, 9 Jun 2026 00:12:43 +0000 Subject: [PATCH 19/19] change port order to prevent drift --- infra/tencent.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infra/tencent.tf b/infra/tencent.tf index 7ea70f3..ac59a7f 100644 --- a/infra/tencent.tf +++ b/infra/tencent.tf @@ -16,7 +16,7 @@ resource "tencentcloud_lighthouse_firewall_rule" "main_firewall" { firewall_rules { protocol = "TCP" - port = "80,443" + port = "443,80" cidr_block = "0.0.0.0/0" action = "ACCEPT" firewall_rule_description = "caddy"