Begin geting terraform working

This commit is contained in:
Kaare Hoff Skovgaard 2025-07-05 23:33:29 +02:00
parent eba2f6adf9
commit 2f725ca3ea
Signed by: khs
GPG key ID: C7D890804F01E9F0
15 changed files with 945 additions and 21 deletions

View file

@ -0,0 +1,29 @@
{
config,
lib,
...
}:
let
cfg = config.khscodes.fqdn;
in
{
options.khscodes.fqdn = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Sets the FQDN of the machine. This is a prerequisite for many modules to be used";
};
config = lib.mkIf (cfg != null) (
let
hostname = builtins.head (lib.strings.splitString "." cfg);
domain = if hostname == cfg then null else (lib.strings.removePrefix "${hostname}." cfg);
in
{
networking.hostName = hostname;
networking.domain = domain;
boot.kernel.sysctl = {
"kernel.hostname" = cfg;
};
}
);
}

View file

@ -1,4 +1,11 @@
{ config, lib, ... }:
let
cfg = config.khscodes.sshd;
in
{
options.khscodes.sshd.enable = lib.mkEnableOption "Enables sshd for the instance";
config = lib.mkIf cfg.enable {
services.sshd.enable = true;
};
}

View file

@ -0,0 +1,283 @@
{
config,
lib,
inputs,
pkgs,
...
}:
let
cfg = config.khscodes.terraform-hetzner;
fqdn = config.khscodes.fqdn;
hostPkgs = import inputs.nixpkgs {
system = pkgs.buildPlatform.system;
overlays = [ inputs.self.overlays.bitwarden-cli ];
};
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;
firewallEnable = config.networking.firewall.enable;
mapRdns = cfg.mapRdns;
tldFromFqdn =
fqdn:
let
split = lib.strings.splitString "." fqdn;
in
if lib.lists.length split < 3 then
fqdn
else
lib.strings.removePrefix "${builtins.head split}." fqdn;
in
{
options.khscodes.terraform-hetzner = {
enable = lib.mkEnableOption "enables generating a terraform config";
dnsNames = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "DNS names for the server";
default = [ fqdn ];
};
bucket = {
key = lib.mkOption {
type = lib.types.str;
description = "Key for use in the bucket";
default = "${fqdn}.tfstate";
};
};
secretsSource = lib.mkOption {
type = lib.types.enum [
"bitwarden"
"vault"
];
description = "Whether to load terraform secrets from Bitwarden or Vault";
default = "vault";
};
datacenter = lib.mkOption {
type = lib.types.str;
description = "The Hetzner datacenter to create a server in";
default = "hel1-dc2";
};
output = lib.mkOption {
type = lib.types.nullOr lib.types.package;
description = "The terranix package built from the configuration";
default = null;
};
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;
};
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;
};
config = inputs.terranix.lib.terranixConfiguration {
system = pkgs.hostPlatform.system;
modules = [
{
imports = [
inputs.self.terranixModules.cloudflare
inputs.terranix-hcloud.terranixModules.hcloud
];
hcloud.enable = true;
terraform.required_providers.hcloud.version = "~> 1.45.0";
terraform.backend.s3 = {
bucket = "bw-terraform";
key = cfg.bucket.key;
region = "auto";
endpoints = {
s3 = "https://477b394a6a545699445c40953e40f00b.r2.cloudflarestorage.com";
};
use_path_style = true;
skip_credentials_validation = true;
skip_region_validation = true;
skip_metadata_api_check = true;
skip_requesting_account_id = true;
skip_s3_checksum = true;
};
data.hcloud_ssh_key.khs = {
name = "ca.kaareskovgaard.net";
};
resource.hcloud_primary_ip.ipv4 = {
inherit labels;
name = "${fqdn} ipv4";
datacenter = cfg.datacenter;
type = "ipv4";
assignee_type = "server";
auto_delete = false;
};
resource.hcloud_primary_ip.ipv6 = {
inherit labels;
name = "${fqdn} ipv6";
datacenter = cfg.datacenter;
type = "ipv6";
assignee_type = "server";
auto_delete = false;
};
khscodes.cloudflare = {
enable = true;
dns = {
enable = true;
zone_name = tldFromFqdn fqdn;
aRecords = [
{
inherit fqdn;
content = "\${ hcloud_server.compute.ipv4_address }";
}
];
aaaaRecords = [
{
inherit fqdn;
content = "\${ hcloud_server.compute.ipv6_address }";
}
];
};
};
resource.hcloud_firewall.fw = lib.mkIf firewallEnable {
inherit labels;
name = fqdn;
apply_to = {
server = "\${ hcloud_server.compute.id }";
};
rule = firewallRules;
};
resource.hcloud_server.compute = {
inherit (cfg) server_type datacenter;
inherit labels;
name = fqdn;
image = "debian-12";
public_net = {
ipv4_enabled = true;
ipv4 = "\${ hcloud_primary_ip.ipv4.id }";
ipv6_enabled = true;
ipv6 = "\${ hcloud_primary_ip.ipv6.id }";
};
ssh_keys = [ "\${ data.hcloud_ssh_key.khs.id }" ];
lifecycle = {
ignore_changes = [
"ssh_keys"
"public_net"
"image"
];
};
};
output.ipv4_address = {
value = "\${ hcloud_server.compute.ipv4_address }";
sensitive = false;
};
output.ipv6_address = {
value = "\${ hcloud_server.compute.ipv6_address }";
sensitive = false;
};
}
(
{ lib, ... }:
{
config = lib.mkIf mapRdns {
resource.hcloud_rdns.ipv4 = {
primary_ip_id = "\${ hcloud_primary_ip.ipv4.id }";
ip_address = "\${ hcloud_server.compute.ipv4_address }";
dns_ptr = fqdn;
};
resource.hcloud_rdns.ipv6 = {
primary_ip_id = "\${ hcloud_primary_ip.ipv6.id }";
ip_address = "\${ hcloud_server.compute.ipv6_address }";
dns_ptr = fqdn;
};
};
}
)
];
};
in
{
assertions = [
{
assertion = config.khscodes.fqdn != null;
message = "Must set config.khscodes.fqdn when using terraform";
}
];
khscodes.terraform-hetzner.output = config;
}
);
}

View file

@ -0,0 +1,172 @@
{ inputs, khscodesLib }:
{ config, lib, ... }:
let
cfg = config.khscodes.cloudflare;
nameFromFQDNAndZone =
fqdn: zone:
let
stripped = lib.strings.removeSuffix ".${zone}" fqdn;
in
if stripped != fqdn then
stripped
else if fqdn == zone then
"@"
else
fqdn;
fqdnToTFname = fqdn: builtins.replaceStrings [ "." ] [ "_" ] fqdn;
dnsARecordModule = khscodesLib.mkSubmodule {
description = "Module for defining dns A/AAAA record";
options = {
fqdn = lib.mkOption {
type = lib.types.str;
description = "The FQDN of the A/AAAA record to create";
};
content = lib.mkOption {
type = lib.types.str;
description = "The content of the A/AAAA record (IPv4/IPv6 address)";
};
proxied = lib.mkOption {
type = lib.types.bool;
description = "Creates a proxied record in cloudflare";
default = false;
};
ttl = lib.mkOption {
type = lib.types.int;
description = "Time to Live for the A/AAAA record";
default = 600;
};
};
};
dnsTxtRecordModule = khscodesLib.mkSubmodule {
description = "Module for defining dns TXT record";
options = {
fqdn = lib.mkOption {
type = lib.types.str;
description = "The FQDN of the TXT record to create";
};
content = lib.mkOption {
type = lib.types.str;
description = "The content of the TXT record";
};
ttl = lib.mkOption {
type = lib.types.int;
description = "Time to Live for the TXT record";
default = 600;
};
};
};
dnsMxRecordModule = khscodesLib.mkSubmodule {
description = "Module for defining dns MX record";
options = {
fqdn = lib.mkOption {
type = lib.types.str;
description = "The FQDN of the MX record to create";
};
content = lib.mkOption {
type = lib.types.str;
description = "The content of the MX record";
};
priority = lib.mkOption {
type = lib.types.int;
description = "Priority for the MX record";
};
ttl = lib.mkOption {
type = lib.types.int;
description = "Time to Live for the MX record";
default = 600;
};
};
};
in
{
options.khscodes.cloudflare = {
enable = lib.mkEnableOption "Enables khscodes cloudflare terranix integration";
dns = {
enable = lib.mkEnableOption "Enables setting up DNS records";
zone_name = lib.mkOption {
type = lib.types.str;
description = "The dns zone name (TLD)";
};
aRecords = lib.mkOption {
type = lib.types.listOf dnsARecordModule;
default = [ ];
description = "A records to create in the zone";
};
aaaaRecords = lib.mkOption {
type = lib.types.listOf dnsARecordModule;
default = [ ];
description = "AAAA records to create in the zone";
};
txtRecords = lib.mkOption {
type = lib.types.listOf dnsTxtRecordModule;
default = [ ];
description = "TXT Records to create";
};
mxRecords = lib.mkOption {
type = lib.types.listOf dnsMxRecordModule;
default = [ ];
description = "MX records to create";
};
};
};
config = lib.mkIf cfg.enable {
provider.cloudflare.api_token = "\${ var.cloudflare_token }";
variable.cloudflare_token = {
type = "string";
sensitive = true;
};
terraform.required_providers.cloudflare = {
source = "cloudflare/cloudflare";
version = "~> 4.0";
};
data.cloudflare_zone.dns_zone = lib.attrsets.optionalAttrs cfg.dns.enable {
name = cfg.dns.zone_name;
};
resource.cloudflare_record = lib.attrsets.optionalAttrs cfg.dns.enable (
lib.listToAttrs (
(lib.lists.map (record: {
name = "${fqdnToTFname record.fqdn}_a";
value = {
inherit (record) content ttl proxied;
name = nameFromFQDNAndZone record.fqdn cfg.dns.zone_name;
type = "A";
zone_id = "\${ data.cloudflare_zone.dns_zone.id }";
comment = "app=${cfg.dns.zone_name}";
};
}) cfg.dns.aRecords)
++ (lib.lists.map (record: {
name = "${fqdnToTFname record.fqdn}_aaaa";
value = {
inherit (record) content ttl proxied;
name = nameFromFQDNAndZone record.fqdn cfg.dns.zone_name;
type = "AAAA";
zone_id = "\${ data.cloudflare_zone.dns_zone.id }";
comment = "app=${cfg.dns.zone_name}";
};
}) cfg.dns.aaaaRecords)
++ (lib.lists.map (record: {
name = "${fqdnToTFname record.fqdn}_txt";
value = {
inherit (record) content ttl;
name = nameFromFQDNAndZone record.fqdn cfg.dns.zone_name;
type = "TXT";
zone_id = "\${ data.cloudflare_zone.dns_zone.id }";
comment = "app=${cfg.dns.zone_name}";
};
}) cfg.dns.txtRecords)
++ (lib.lists.map (record: {
name = "${fqdnToTFname record.fqdn}_mx";
value = {
inherit (record) content priority;
name = nameFromFQDNAndZone record.fqdn cfg.dns.zone_name;
type = "MX";
zone_id = "\${ data.cloudflare_zone.dns_zone.id }";
comment = "app=${cfg.dns.zone_name}";
};
}) cfg.dns.mxRecords)
)
);
};
}