migrate infra to terranix

This commit is contained in:
Romain Paquet 2026-01-19 18:47:16 +01:00
parent 32c4eeb2f8
commit de32fe0db0
16 changed files with 295 additions and 289 deletions

View file

@ -1,62 +1,16 @@
# This file is maintained automatically by "tofu init".
# Manual edits may be lost in future updates.
provider "registry.opentofu.org/hashicorp/assert" {
version = "0.16.0"
provider "registry.opentofu.org/hashicorp/external" {
version = "2.3.5"
hashes = [
"h1:2jeV46S9jN2rk0GXOa+HGNlVvyWzaB3wz0T65elbjOc=",
"zh:3c04d08d1bb4ae810b7972a219c8dd42a8ab901a9bc25197b250c38f3fa57033",
"zh:46119bcc47b545809c0ee873a72d44f4f875cca4d7228605f5c7a8956a5e7d55",
"zh:511949ee8a6ac8ff7296b4c9778deb2aec2783f5b85c4f27382a3b623fc50a4a",
"zh:b4ebb8b832bae26443880d2e17493f754495db2d6c3f02c6d0070cbf5ae21598",
"zh:bebed6c1873871eb824103f08e72055c077f01b10a40944760d19ffdd721d9ab",
"zh:e412855fd2fd81e0a847e45308bdbac99995315c503fdddf262ee59e1b7c5263",
"zh:ed47c4fe28c6f148f11fa4098516abea008c49fa670c3cedd2ff94596cac0831",
"zh:edee914b1d12ac6db241a1fecaa5186c47f361f4ceb2deb23ad45d67bf95c7b1",
"zh:eff5b2e1c2128217bdbc600eda4fe011831e5c655bf4acd84b6495fc20d128d3",
"zh:ff64424784171a3361b1ea95d8cef334ec1c4a395812edd0a77a1ed6b4119b0f",
"h1:en/2hMK/W/2hKtsEkbxGiiYwi/pSPS/UoGDILHIHjmw=",
]
}
provider "registry.opentofu.org/hetznercloud/hcloud" {
version = "1.57.0"
constraints = "~> 1.45"
version = "1.58.0"
hashes = [
"h1:Xk+Whn6wnhEJEeiO/mPII/mOL+buHLj05AKy4TbDz3U=",
"zh:016ecc39328f34f6c0ffa413598f354824f7878c89cd031f123edb4bc8a687a2",
"zh:10b362dc0847200c987214b129b5f85e2f7d8ad417261a1d2dd04ab74de15603",
"zh:194647d9a61dca4f411f44580316b88a11095d7a99679d445f9b0f2c1ba976c4",
"zh:1d8aafe2ce7890696385bb3a0c3286e7ee3020416d337f59935406e4c6f91de6",
"zh:594585616210fb232fad4ebda2387ecd3f483931e00eff988fca83add6ce7cfc",
"zh:65e50be33ffb85580546f119839e1293591cc6d4db729d809931d0408b6ae408",
"zh:7d4ed5bd8c477ec304142e2160203a76a0d09c93d224950bda253172b2571038",
"zh:90a70a70a266b78c8216903e711904e6969b3957d182602b5d788602ec9ef323",
"zh:abb8e28e96fb8de270995873de980896b7cb53cfc550f02c50eaa42884624ba9",
"zh:bbf34dca2de6e105ca7204222162a0402d8e9e9a28e1de5ffbaa2c0d6270a059",
"zh:c1a9edb693d632dcb5c3c9ee84c97138e08eadb9354e28592efd581f68ac0385",
"zh:dadbf1368fae314fe8dcb99ebefbc78409f3fc0e3808cd92ea573b8eee1cae98",
"zh:e713e00ca27348abd18da2eeff861905e84050e3e7e008f14a0c63c70ab2ff84",
]
}
provider "registry.opentofu.org/ovh/ovh" {
version = "2.10.0"
constraints = "> 2.5.0"
hashes = [
"h1:6CHM/tHZ7vAvQKtdqurs6ExO+46gpFooZ0zdaW74DKE=",
"zh:1582485c59b5e25fa407417de3040dfc31bfec3f9b884d51953f6625b930d2f6",
"zh:15b425716d5e05992cb1d68a49d58f0e9e0cbd7dbaa35ea9793404fa1ec45bed",
"zh:1c1547ff469c2f772d478f67d148d08b38468d43c9517b723b622a085625d949",
"zh:2491be291a8876da2dc1e71490428706cdca39002a1e89d10dd060474f59ce19",
"zh:2d9c7589764f838f04d38a87a0e6c9db6b560b6c5b510b69eabf2d67caa38d2b",
"zh:56c5b16a55dc4ac5f3eed69072e5ae74aafac2a4a8a84ba27fa06528320037cc",
"zh:629d2c7f709fc01adabba1c8b98ec7485dfebcc4b9f72f4bd4d36509166eb42c",
"zh:82f4b8b35a31a468d7a2a5aa4630f432ef64d9abfed8066afdaab0502886a72a",
"zh:84c5e65122efaee5e34c266cd750576969bd788c2bdbb804a7ffc08728ac3987",
"zh:85db08f3e1b27fda723b080bc5132069b6b7ba9699567cd44fb0a2207456a76c",
"zh:a84c043c96a01230e570163706f58c33ee59699fcc857d3db0f6e0b2a6b08bc6",
"zh:ad984516009930efc6ec465046287c6b293b6b219e3167aa4c0b900b903c6a50",
"zh:bd0114d45ec72134cf930a7619b70b0068e439759febba5717abb76219b85800",
"zh:f243a50dcf87687881972fcaba9226b4247588b6dc7368b0ef98168f643ee159",
"h1:6C2LNEvCyGPyWgALDAFTNbRp+5Iuikd4Ju1Xejh+aeg=",
]
}

24
infra/base.nix Normal file
View file

@ -0,0 +1,24 @@
{
config,
lib,
pkgs,
...
}:
{
terraform.required_providers.hcloud.source = "hetznercloud/hcloud";
data.external.hcloud-token = {
program = [
(lib.getExe (
pkgs.writeShellApplication {
name = "get-clan-secret";
text = ''
jq -n --arg secret "$(clan secrets get hcloud-token)" '{"secret":$secret}'
'';
}
))
];
};
provider.hcloud.token = config.data.external.hcloud-token "result.secret";
}

View file

@ -1,68 +0,0 @@
resource "hcloud_server" "crocus_server" {
name = "crocus"
server_type = "cx22"
datacenter = "nbg1-dc3"
image = "ubuntu-20.04"
firewall_ids = [hcloud_firewall.crocus_firewall.id]
public_net {
ipv4 = hcloud_primary_ip.crocus_ipv4.id
}
}
resource "hcloud_primary_ip" "crocus_ipv4" {
name = "crocus_ipv4"
type = "ipv4"
datacenter = "nbg1-dc3"
assignee_type = "server"
auto_delete = true
}
resource "hcloud_firewall" "crocus_firewall" {
name = "crocus-firewall"
rule {
direction = "in"
protocol = "icmp"
source_ips = ["0.0.0.0/0", "::/0"]
}
rule {
direction = "in"
protocol = "tcp"
port = "22"
source_ips = ["0.0.0.0/0", "::/0"]
}
rule {
direction = "in"
protocol = "tcp"
port = "22"
source_ips = ["0.0.0.0/0", "::/0"]
}
rule {
direction = "in"
protocol = "tcp"
port = "80"
source_ips = ["0.0.0.0/0", "::/0"]
}
rule {
direction = "in"
protocol = "tcp"
port = "443"
source_ips = ["0.0.0.0/0", "::/0"]
}
# radicle-node
rule {
direction = "in"
protocol = "tcp"
port = "8776"
source_ips = ["0.0.0.0/0", "::/0"]
}
}
output "crocus_ipv4" {
value = hcloud_primary_ip.crocus_ipv4.ip_address
}

20
infra/dns.nix Normal file
View file

@ -0,0 +1,20 @@
{ config, ... }:
{
resource.hcloud_zone.rpqt_fr = {
name = "rpqt.fr";
mode = "primary";
};
resource.hcloud_zone.turifer_dev = {
name = "turifer.dev";
mode = "primary";
};
output.rpqt_fr_zone_name = {
value = config.resource.hcloud_zone.rpqt_fr "name";
};
output.turifer_dev_zone_name = {
value = config.resource.hcloud_zone.turifer_dev "name";
};
}

View file

@ -1,44 +0,0 @@
data "ovh_vps" "verbena_vps" {
service_name = "vps-7e78bac2.vps.ovh.net"
}
data "ovh_domain_zone" "rpqt_fr" {
name = "rpqt.fr"
}
resource "ovh_domain_zone_import" "rpqt_fr_import" {
zone_name = "rpqt.fr"
zone_file = local.rpqt_fr_zone_file
}
data "ovh_domain_zone" "turifer_dev" {
name = "turifer.dev"
}
resource "ovh_domain_zone_import" "turifer_dev_import" {
zone_name = "turifer.dev"
zone_file = local.turifer_dev_zone_file
}
locals {
verbena_ipv4_addresses = [for ip in data.ovh_vps.verbena_vps.ips : ip if provider::assert::ipv4(ip)]
verbena_ipv6_addresses = [for ip in data.ovh_vps.verbena_vps.ips : ip if provider::assert::ipv6(ip)]
turifer_dev_zone_file = templatefile("./templates/turifer.dev.zone", {
crocus_ipv4_address = hcloud_server.crocus_server.ipv4_address
crocus_ipv6_address = hcloud_server.crocus_server.ipv6_address
verbena_ipv4_addresses = local.verbena_ipv4_addresses
verbena_ipv6_addresses = local.verbena_ipv6_addresses
})
rpqt_fr_zone_file = templatefile("./templates/rpqt.fr.zone", {
crocus_ipv4_address = hcloud_server.crocus_server.ipv4_address
crocus_ipv6_address = hcloud_server.crocus_server.ipv6_address
verbena_ipv4_addresses = local.verbena_ipv4_addresses
verbena_ipv6_addresses = local.verbena_ipv6_addresses
})
}

View file

@ -1,7 +1,28 @@
{ self, ... }:
{
perSystem =
{ pkgs, ... }:
{
terranix.terranixConfigurations.infra = {
terraformWrapper.package = pkgs.opentofu.withPlugins (p: [
p.hashicorp_external
p.hetznercloud_hcloud
]);
extraArgs = { inherit (self) infra; };
modules = [
./base.nix
./dns.nix
./mail.nix
./radicle.nix
./web.nix
];
};
};
flake.infra =
let
tf_outputs = builtins.fromJSON (builtins.readFile ../infra/outputs.json);
tf_outputs = builtins.fromJSON (builtins.readFile ./outputs.json);
in
{
machines = {
@ -12,6 +33,7 @@
};
crocus = {
ipv4 = tf_outputs.crocus_ipv4.value;
ipv6 = "2a01:4f8:1c1e:e415::1";
};
};
};

88
infra/lib.nix Normal file
View file

@ -0,0 +1,88 @@
{ lib, ... }:
let
mkMigaduDkim = zone: name: {
inherit zone;
name = "${name}._domainkey";
type = "CNAME";
records = [
{ value = "${name}.${zone}._domainkey.migadu.com."; }
];
};
in
{
mkMigadu_hcloud_zone_rrset = zone: hostedEmailVerify: {
dkim_1 = mkMigaduDkim zone "key1";
dkim_2 = mkMigaduDkim zone "key2";
dkim_3 = mkMigaduDkim zone "key3";
spf = {
inherit zone;
name = "@";
type = "TXT";
records = [
{
value = lib.tf.ref ''provider::hcloud::txt_record("v=spf1 include:spf.migadu.com -all")'';
}
{
value = lib.tf.ref ''provider::hcloud::txt_record("hosted-email-verify=pgeaq3bp")'';
}
];
};
dmarc = {
inherit zone;
name = "_dmarc";
type = "TXT";
records = [
{
value = lib.tf.ref ''provider::hcloud::txt_record("v=DMARC1; p=quarantine;")'';
}
];
};
mx = {
inherit zone;
name = "@";
type = "MX";
records = [
{ value = "10 aspmx1.migadu.com."; }
{ value = "20 aspmx2.migadu.com."; }
];
};
autoconfig = {
inherit zone;
name = "autoconfig";
type = "CNAME";
records = [ { value = "autoconfig.migadu.com."; } ];
};
autodiscover = {
inherit zone;
name = "_autodiscover._tcp";
type = "SRV";
records = [ { value = "0 1 443 autodiscover.migadu.com."; } ];
};
submissions = {
inherit zone;
name = "_submissions._tcp";
type = "SRV";
records = [ { value = "0 1 465 smtp.migadu.com."; } ];
};
imaps = {
inherit zone;
name = "_imaps._tcp";
type = "SRV";
records = [ { value = "0 1 993 imap.migadu.com."; } ];
};
pop3s = {
inherit zone;
name = "_pop3s._tcp";
type = "SRV";
records = [ { value = "0 1 995 pop.migadu.com."; } ];
};
};
}

15
infra/mail.nix Normal file
View file

@ -0,0 +1,15 @@
{ config, lib, ... }:
let
inherit (import ./lib.nix { inherit lib; })
mkMigadu_hcloud_zone_rrset
;
rpqt_fr = mkMigadu_hcloud_zone_rrset (config.resource.hcloud_zone.rpqt_fr "name") "pgeaq3bp";
# Prefix resource names with zone name to avoid collision
turifer_dev = lib.mapAttrs' (name: value: lib.nameValuePair "turifer_dev_${name}" value) (
mkMigadu_hcloud_zone_rrset (config.resource.hcloud_zone.turifer_dev "name") "k5z4lcfc"
);
in
{
resource.hcloud_zone_rrset = rpqt_fr // turifer_dev;
}

View file

@ -1,15 +0,0 @@
terraform {
required_providers {
hcloud = {
source = "hetznercloud/hcloud"
version = "~> 1.45"
}
ovh = {
source = "ovh/ovh"
version = "> 2.5.0"
}
assert = {
source = "hashicorp/assert"
}
}
}

View file

@ -1,9 +0,0 @@
provider "hcloud" {
token = var.hcloud_token
}
provider "ovh" {
endpoint = "ovh-eu"
client_id = var.ovh_client_id
client_secret = var.ovh_client_secret
}

52
infra/radicle.nix Normal file
View file

@ -0,0 +1,52 @@
{
config,
infra,
lib,
...
}:
{
resource.hcloud_zone_rrset =
let
zone = config.resource.hcloud_zone.rpqt_fr "name";
in
{
radicle_a = {
inherit zone;
name = "radicle";
type = "A";
records = [ { value = infra.machines.crocus.ipv4; } ];
};
radicle_aaaa = {
inherit zone;
name = "radicle";
type = "AAAA";
records = [ { value = infra.machines.crocus.ipv6; } ];
};
radicles_srv = {
inherit zone;
name = "seed._radicle-node._tcp";
type = "SRV";
records = [ { value = "32767 32767 58776 radicle.rpqt.fr."; } ];
};
radicles_nid = {
inherit zone;
name = "seed._radicle-node._tcp";
type = "TXT";
records = [
{
value = lib.tf.ref ''provider::hcloud::txt_record("nid=z6MkuivFHDPg6Bd25v4bEWm7T7qLUYMWk1eVTE7exvum5Rvd")'';
}
];
};
radicle_ptr = {
inherit zone;
name = "_radicle-node._tcp";
type = "PTR";
records = [ { value = "seed._radicle-node._tcp.radicle.rpqt.fr."; } ];
};
};
}

View file

@ -1,31 +0,0 @@
$TTL 3600
@ IN SOA dns100.ovh.net. tech.ovh.net. (2026010123 86400 3600 3600000 60)
IN NS dns100.ovh.net.
IN NS ns100.ovh.net.
rpqt.fr. 3000 IN TXT "hosted-email-verify=pgeaq3bp"
rpqt.fr. 3000 IN MX 10 aspmx1.migadu.com.
rpqt.fr. 3000 IN MX 20 aspmx2.migadu.com.
rpqt.fr. 3000 IN TXT "v=spf1 include:spf.migadu.com -all"
key1._domainkey.rpqt.fr. 3000 IN CNAME key1.rpqt.fr._domainkey.migadu.com.
key2._domainkey.rpqt.fr. 3000 IN CNAME key2.rpqt.fr._domainkey.migadu.com.
key3._domainkey.rpqt.fr. 3000 IN CNAME key3.rpqt.fr._domainkey.migadu.com.
_dmarc.rpqt.fr. 3000 IN TXT "v=DMARC1; p=quarantine;"
autoconfig.rpqt.fr. 3000 IN CNAME autoconfig.migadu.com.
_autodiscover._tcp.rpqt.fr. 3000 IN SRV 0 1 443 autodiscover.migadu.com.
_submissions._tcp.rpqt.fr. 3000 IN SRV 0 1 465 smtp.migadu.com.
_imaps._tcp.rpqt.fr. 3000 IN SRV 0 1 993 imap.migadu.com.
_pop3s._tcp.rpqt.fr. 3000 IN SRV 0 1 995 pop.migadu.com.
@ 10800 IN A 46.23.81.157
@ 10800 IN AAAA 2a03:6000:1813:1337::157
%{ for addr in verbena_ipv4_addresses ~}
cloud 10800 IN A ${addr}
%{ endfor ~}
%{ for addr in verbena_ipv6_addresses ~}
cloud 10800 IN AAAA ${addr}
%{ endfor ~}
radicle 10800 IN A ${crocus_ipv4_address}
radicle 10800 IN AAAA ${crocus_ipv6_address}

View file

@ -1,39 +0,0 @@
$TTL 3600
@ IN SOA dns100.ovh.net. tech.ovh.net. (2025071505 86400 3600 3600000 60)
IN NS dns100.ovh.net.
IN NS ns100.ovh.net.
turifer.dev. 3000 IN TXT "hosted-email-verify=k5z4lcfc"
turifer.dev. 3000 IN MX 10 aspmx1.migadu.com.
turifer.dev. 3000 IN MX 20 aspmx2.migadu.com.
turifer.dev. 3000 IN TXT "v=spf1 include:spf.migadu.com -all"
key1._domainkey.turifer.dev. 3000 IN CNAME key1.turifer.dev._domainkey.migadu.com.
key2._domainkey.turifer.dev. 3000 IN CNAME key2.turifer.dev._domainkey.migadu.com.
key3._domainkey.turifer.dev. 3000 IN CNAME key3.turifer.dev._domainkey.migadu.com.
_dmarc.turifer.dev. 3000 IN TXT "v=DMARC1; p=quarantine;"
autoconfig.turifer.dev. 3000 IN CNAME autoconfig.migadu.com.
_autodiscover._tcp.turifer.dev. 3000 IN SRV 0 1 443 autodiscover.migadu.com.
_submissions._tcp.turifer.dev. 3000 IN SRV 0 1 465 smtp.migadu.com.
_imaps._tcp.turifer.dev. 3000 IN SRV 0 1 993 imap.migadu.com.
_pop3s._tcp.turifer.dev. 3000 IN SRV 0 1 995 pop.migadu.com.
%{ for addr in verbena_ipv4_addresses ~}
git.turifer.dev. 10800 IN A ${addr}
%{ endfor ~}
%{ for addr in verbena_ipv6_addresses ~}
git.turifer.dev. 10800 IN AAAA ${addr}
%{ endfor ~}
%{ for addr in verbena_ipv4_addresses ~}
buildbot.turifer.dev. 10800 IN A ${addr}
%{ endfor ~}
%{ for addr in verbena_ipv6_addresses ~}
buildbot.turifer.dev. 10800 IN AAAA ${addr}
%{ endfor ~}
%{ for addr in verbena_ipv4_addresses ~}
wg1.turifer.dev. 10800 IN A ${addr}
%{ endfor ~}
%{ for addr in verbena_ipv6_addresses ~}
wg1.turifer.dev. 10800 IN AAAA ${addr}
%{ endfor ~}

View file

@ -1,11 +0,0 @@
variable "hcloud_token" {
sensitive = true
}
variable "ovh_client_id" {
sensitive = true
}
variable "ovh_client_secret" {
sensitive = true
}

View file

@ -1,20 +0,0 @@
output "verbena_ipv4" {
value = local.verbena_ipv4_addresses[0]
}
output "verbena_ipv6" {
value = local.verbena_ipv6_addresses[0]
}
output "verbena_gateway6" {
value = local.gateway6
}
locals {
hextets = 4
parts = split(":", local.verbena_ipv6_addresses[0])
prefix_parts = slice(local.parts, 0, local.hextets)
prefix_str = join(":", local.prefix_parts)
gateway6 = "${local.prefix_str}::1"
}

68
infra/web.nix Normal file
View file

@ -0,0 +1,68 @@
{ config, infra, ... }:
{
resource.hcloud_zone_rrset =
let
sourcehut_pages = {
ipv4 = "46.23.81.157";
ipv6 = "2a03:6000:1813:1337::157";
};
zone = config.resource.hcloud_zone.rpqt_fr "name";
in
{
a = {
inherit zone;
name = "@";
type = "A";
records = [ { value = sourcehut_pages.ipv4; } ];
};
aaaa = {
inherit zone;
name = "@";
type = "AAAA";
records = [ { value = sourcehut_pages.ipv6; } ];
};
cloud_a = {
inherit zone;
name = "cloud";
type = "A";
records = [ { value = infra.machines.verbena.ipv4; } ];
};
cloud_aaaa = {
inherit zone;
name = "cloud";
type = "AAAA";
records = [ { value = infra.machines.verbena.ipv6; } ];
};
git_turifer_dev_a = {
zone = config.resource.hcloud_zone.turifer_dev "name";
name = "git";
type = "A";
records = [ { value = infra.machines.verbena.ipv4; } ];
};
git_turifer_dev_aaaa = {
zone = config.resource.hcloud_zone.turifer_dev "name";
name = "git";
type = "AAAA";
records = [ { value = infra.machines.verbena.ipv6; } ];
};
buildbot_turifer_dev_a = {
zone = config.resource.hcloud_zone.turifer_dev "name";
name = "buildbot";
type = "A";
records = [ { value = infra.machines.verbena.ipv4; } ];
};
buildbot_turifer_dev_aaaa = {
zone = config.resource.hcloud_zone.turifer_dev "name";
name = "buildbot";
type = "AAAA";
records = [ { value = infra.machines.verbena.ipv6; } ];
};
};
}