machines/nix/modules/nixos/infrastructure/hetzner-instance/default.nix
Kaare Hoff Skovgaard e6a152e95c
Some checks failed
/ rust-packages (push) Successful in 13m31s
/ dev-shell (push) Successful in 4m18s
/ check (push) Failing after 4m18s
/ terraform-providers (push) Successful in 13m19s
/ systems (push) Successful in 50m43s
Begin working on porting much of the opentofu related code
into rust. Mainly this should give proper argument parsing and
error handling, and also remove some of all the scattered shell
scripts.
2025-08-05 01:42:57 +02:00

394 lines
11 KiB
Nix

{
config,
lib,
inputs,
...
}:
let
cfg = config.khscodes.infrastructure.hetzner-instance;
hasDisks = cfg.dataDisks != [ ];
fqdn = config.khscodes.networking.fqdn;
provisioningUserData = config.khscodes.infrastructure.provisioning.instanceUserData;
locationFromDatacenter =
datacenter:
let
split = lib.strings.splitString "-" datacenter;
in
assert (lib.lists.length split) == 2;
lib.lists.head split;
diskModule = lib.khscodes.mkSubmodule' (
{ config }:
{
description = "Persistent disk";
options = {
name = lib.mkOption {
type = lib.types.str;
};
nameSanitized = lib.mkOption {
type = lib.types.str;
readOnly = true;
default = lib.khscodes.sanitize-terraform-name config.name;
};
size = lib.mkOption {
type = lib.types.int;
};
};
}
);
firewallTcpRules = lib.lists.map (p: {
direction = "in";
protocol = "tcp";
port = p;
source_ips = [
"0.0.0.0/0"
"::/0"
];
}) config.networking.firewall.allowedTCPPorts;
firewallUdpRules = lib.lists.map (p: {
direction = "in";
protocol = "udp";
port = p;
source_ips = [
"0.0.0.0/0"
"::/0"
];
}) config.networking.firewall.allowedUDPPorts;
firewallIcmpRules = lib.lists.optional config.networking.firewall.allowPing {
direction = "in";
protocol = "icmp";
source_ips = [
"0.0.0.0/0"
"::/0"
];
description = "ping";
};
firewallRules =
firewallTcpRules
++ firewallUdpRules
++ firewallIcmpRules
++ cfg.extraFirewallRules
++ [
{
direction = "out";
protocol = "tcp";
port = 80;
destination_ips = [
"0.0.0.0/0"
"::/0"
];
description = "http";
}
{
direction = "out";
protocol = "tcp";
port = 443;
destination_ips = [
"0.0.0.0/0"
"::/0"
];
description = "http";
}
{
direction = "out";
protocol = "udp";
port = 443;
destination_ips = [
"0.0.0.0/0"
"::/0"
];
description = "quic";
}
{
direction = "out";
protocol = "udp";
port = 53;
destination_ips = [
"0.0.0.0/0"
"::/0"
];
description = "dns";
}
{
direction = "out";
protocol = "udp";
port = 123;
destination_ips = [
"0.0.0.0/0"
"::/0"
];
description = "ntp";
}
{
direction = "out";
protocol = "tcp";
port = 53;
destination_ips = [
"0.0.0.0/0"
"::/0"
];
description = "dns";
}
];
firewallEnable = config.networking.firewall.enable;
in
{
options.khscodes.infrastructure.hetzner-instance = {
enable = lib.mkEnableOption "enables generating a opentofu config";
dnsName = lib.mkOption {
type = lib.types.str;
default = fqdn;
};
dnsAliases = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = lib.lists.unique (
lib.lists.filter (alias: alias != cfg.dnsName) config.khscodes.networking.aliases
);
};
bucket = {
key = lib.mkOption {
type = lib.types.str;
description = "Key for use in the bucket";
default = "${fqdn}.tfstate";
};
};
datacenter = lib.mkOption {
type = lib.types.str;
description = "The Hetzner datacenter to create a server in";
default = "hel1-dc2";
};
mapRdns = lib.mkOption {
type = lib.types.bool;
description = "Sets up RDNS for the server";
default = false;
};
server_type = lib.mkOption {
type = lib.types.nullOr lib.types.str;
description = "The server type to create";
default = null;
};
dataDisks = lib.mkOption {
type = lib.types.listOf diskModule;
description = "Extra data disks to add to the instance, these will be added in the persistence phase";
default = [ ];
};
extraFirewallRules = lib.mkOption {
type = lib.types.listOf lib.types.attrs;
description = "Extra firewall rules added to the instance";
default = [
{
direction = "out";
protocol = "tcp";
port = 80;
destination_ips = [
"0.0.0.0/0"
"::/0"
];
description = "http";
}
{
direction = "out";
protocol = "tcp";
port = 443;
destination_ips = [
"0.0.0.0/0"
"::/0"
];
description = "https";
}
{
direction = "out";
protocol = "udp";
port = 443;
destination_ips = [
"0.0.0.0/0"
"::/0"
];
description = "quic";
}
{
direction = "out";
protocol = "icmp";
destination_ips = [
"0.0.0.0/0"
"::/0"
];
description = "Ping";
}
];
};
};
config = lib.mkIf cfg.enable (
let
labels = {
app = fqdn;
};
persistenceModules = lib.lists.optional hasDisks (
{ ... }:
{
imports = [
inputs.self.terranixModules.hcloud
inputs.self.terranixModules.s3
];
config = {
khscodes.s3 = {
enable = true;
bucket.key = "persistence-" + cfg.bucket.key;
};
khscodes.hcloud.enable = true;
resource.hcloud_volume = lib.listToAttrs (
lib.lists.map (disk: {
name = disk.nameSanitized;
value = {
inherit (disk) name size;
location = locationFromDatacenter cfg.datacenter;
};
}) cfg.dataDisks
);
};
}
);
persistenceAttachModules = lib.lists.optional hasDisks (
{ config, ... }:
{
config = {
data.hcloud_volume = lib.listToAttrs (
lib.lists.map (disk: {
name = disk.nameSanitized;
value = {
name = disk.name;
};
}) cfg.dataDisks
);
resource.hcloud_volume_attachment = lib.listToAttrs (
lib.lists.map (disk: {
name = disk.nameSanitized;
value = {
volume_id = "\${ data.hcloud_volume.${disk.nameSanitized}.id }";
server_id = config.khscodes.hcloud.output.server.compute.id;
};
}) cfg.dataDisks
);
resource.vault_kv_secret_v2.data_disks = {
mount = "data-disks";
name = fqdn;
data_json = ''
{
"template": "{id}",
"disks": ''${ jsonencode({ ${
lib.strings.concatStringsSep ", " (
lib.lists.map (disk: ''
${builtins.toJSON disk.name} = {
"linuxDevice" = data.hcloud_volume.${disk.nameSanitized}.linux_device,
"size" = ${builtins.toString disk.size}
}
'') cfg.dataDisks
)
} }) }
}
'';
};
};
}
);
computeModules = [
(
{ config, ... }:
{
imports = [
inputs.self.terranixModules.cloudflare
inputs.self.terranixModules.hcloud
inputs.self.terranixModules.s3
];
config = {
khscodes.s3 = {
enable = true;
bucket.key = cfg.bucket.key;
};
khscodes.hcloud.data.ssh_key.khs = {
name = "ca.kaareskovgaard.net";
};
khscodes.hcloud.enable = true;
khscodes.hcloud.server.compute = {
inherit (cfg) server_type datacenter;
inherit labels;
name = fqdn;
initial_image = "debian-12";
rdns = lib.mkIf cfg.mapRdns fqdn;
ssh_keys = [ config.khscodes.hcloud.output.data.ssh_key.khs.id ];
user_data = builtins.toJSON provisioningUserData;
};
khscodes.cloudflare = {
enable = true;
dns = {
enable = true;
aRecords = [
{
fqdn = cfg.dnsName;
content = config.khscodes.hcloud.output.server.compute.ipv4_address;
}
];
aaaaRecords = [
{
fqdn = cfg.dnsName;
content = config.khscodes.hcloud.output.server.compute.ipv6_address;
}
];
cnameRecords = lib.lists.map (domain: {
fqdn = domain;
content = cfg.dnsName;
}) cfg.dnsAliases;
};
};
resource.hcloud_firewall.fw = lib.mkIf firewallEnable {
inherit labels;
name = fqdn;
apply_to = {
server = config.khscodes.hcloud.output.server.compute.id;
};
rule = firewallRules;
};
output.ipv4_address = {
value = config.khscodes.hcloud.output.server.compute.ipv4_address;
sensitive = false;
};
output.ipv6_address = {
value = config.khscodes.hcloud.output.server.compute.ipv6_address;
sensitive = false;
};
};
}
)
];
in
{
assertions = [
{
assertion = fqdn != null;
message = "Must set config.khscodes.networking.fqdn when using opentofu";
}
];
khscodes.services.openssh = {
enable = true;
hostCertificate = {
enable = true;
};
};
khscodes.services.read-vault-auth-from-userdata = {
url = "http://169.254.169.254/latest/user-data";
doubleDecodeJsonData = true;
};
khscodes.infrastructure.vault-server-approle.policy = lib.mkIf hasDisks {
"data-disks/data/${fqdn}" = {
capabilities = [ "read" ];
};
};
khscodes.infrastructure.provisioning = {
compute.modules = computeModules;
persistence.modules = persistenceModules;
persistenceAttach.modules = persistenceAttachModules;
};
}
);
}