diff --git a/flake.nix b/flake.nix index a033781..5e9eaea 100644 --- a/flake.nix +++ b/flake.nix @@ -66,5 +66,9 @@ inherit inputs; khscodesLib = inputs.self.lib; }; + terranixModules.hcloud = import ./nix/modules/terranix/hcloud { + inherit inputs; + khscodesLib = inputs.self.lib; + }; }; } diff --git a/nix/lib/sanitize-terraform-name/default.nix b/nix/lib/sanitize-terraform-name/default.nix new file mode 100644 index 0000000..9c0cf9a --- /dev/null +++ b/nix/lib/sanitize-terraform-name/default.nix @@ -0,0 +1,4 @@ +{ ... }: +{ + sanitize-terraform-name = name: builtins.replaceStrings [ "." ] [ "_" ] name; +} diff --git a/nix/modules/nixos/terraform-hetzner/default.nix b/nix/modules/nixos/terraform-hetzner/default.nix index 059af8d..4af48d3 100644 --- a/nix/modules/nixos/terraform-hetzner/default.nix +++ b/nix/modules/nixos/terraform-hetzner/default.nix @@ -149,119 +149,76 @@ in 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, ... }: { - 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; + imports = [ + inputs.self.terranixModules.cloudflare + inputs.self.terranixModules.hcloud + ]; + config = { + 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; }; - resource.hcloud_rdns.ipv6 = { - primary_ip_id = "\${ hcloud_primary_ip.ipv6.id }"; - ip_address = "\${ hcloud_server.compute.ipv6_address }"; - dns_ptr = fqdn; + + 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 = fqdn; + ssh_keys = [ config.khscodes.hcloud.output.data.ssh_key.khs.id ]; + }; + khscodes.cloudflare = { + enable = true; + dns = { + enable = true; + zone_name = tldFromFqdn fqdn; + aRecords = [ + { + inherit fqdn; + content = config.khscodes.hcloud.output.server.compute.ipv4_address; + } + ]; + aaaaRecords = [ + { + inherit fqdn; + content = config.khscodes.hcloud.output.server.compute.ipv6_address; + } + ]; + }; + }; + 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; }; }; } diff --git a/nix/modules/terranix/cloudflare/default.nix b/nix/modules/terranix/cloudflare/default.nix index b598f30..8835e4d 100644 --- a/nix/modules/terranix/cloudflare/default.nix +++ b/nix/modules/terranix/cloudflare/default.nix @@ -13,7 +13,6 @@ let "@" else fqdn; - fqdnToTFname = fqdn: builtins.replaceStrings [ "." ] [ "_" ] fqdn; dnsARecordModule = khscodesLib.mkSubmodule { description = "Module for defining dns A/AAAA record"; options = { @@ -127,7 +126,7 @@ in resource.cloudflare_record = lib.attrsets.optionalAttrs cfg.dns.enable ( lib.listToAttrs ( (lib.lists.map (record: { - name = "${fqdnToTFname record.fqdn}_a"; + name = "${khscodesLib.sanitize-terraform-name record.fqdn}_a"; value = { inherit (record) content ttl proxied; name = nameFromFQDNAndZone record.fqdn cfg.dns.zone_name; @@ -137,7 +136,7 @@ in }; }) cfg.dns.aRecords) ++ (lib.lists.map (record: { - name = "${fqdnToTFname record.fqdn}_aaaa"; + name = "${khscodesLib.sanitize-terraform-name record.fqdn}_aaaa"; value = { inherit (record) content ttl proxied; name = nameFromFQDNAndZone record.fqdn cfg.dns.zone_name; @@ -147,7 +146,7 @@ in }; }) cfg.dns.aaaaRecords) ++ (lib.lists.map (record: { - name = "${fqdnToTFname record.fqdn}_txt"; + name = "${khscodesLib.sanitize-terraform-name record.fqdn}_txt"; value = { inherit (record) content ttl; name = nameFromFQDNAndZone record.fqdn cfg.dns.zone_name; @@ -157,7 +156,7 @@ in }; }) cfg.dns.txtRecords) ++ (lib.lists.map (record: { - name = "${fqdnToTFname record.fqdn}_mx"; + name = "${khscodesLib.sanitize-terraform-name record.fqdn}_mx"; value = { inherit (record) content priority; name = nameFromFQDNAndZone record.fqdn cfg.dns.zone_name; diff --git a/nix/modules/terranix/hcloud/default.nix b/nix/modules/terranix/hcloud/default.nix new file mode 100644 index 0000000..49761fd --- /dev/null +++ b/nix/modules/terranix/hcloud/default.nix @@ -0,0 +1,189 @@ +{ inputs, khscodesLib }: +{ config, lib, ... }: +let + cfg = config.khscodes.hcloud; + serversWithRdns = lib.filterAttrs (_: value: value.rdns != null) cfg.server; + mapSanitizedAttrs = + f: list: + lib.listToAttrs ( + lib.map ( + { name, value }: + let + sanitizedName = khscodesLib.sanitize-terraform-name name; + in + { + name = sanitizedName; + value = f { + inherit value; + name = sanitizedName; + }; + } + ) (lib.attrsToList list) + ); + hcloudServerModule = khscodesLib.mkSubmodule { + description = "Module for defining hcloud server"; + options = { + name = lib.mkOption { + type = lib.types.str; + description = "Name of the server"; + }; + server_type = lib.mkOption { + type = lib.types.str; + description = "Server type for the instance"; + }; + datacenter = lib.mkOption { + type = lib.types.str; + description = "Data center for the instance"; + }; + initial_image = lib.mkOption { + type = lib.types.str; + description = "Initial image of the server"; + }; + ssh_keys = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "The ssh keys added to the server"; + }; + labels = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + }; + rdns = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "FQDN to map rDNS to"; + }; + }; + }; + hcloudDataSshKeys = khscodesLib.mkSubmodule { + description = "SSH Keys"; + options = { + name = lib.mkOption { + type = lib.types.str; + description = "The name of the ssh keys"; + }; + id = lib.mkOption { + type = lib.types.str; + description = "A terraform expression for the ssh keys"; + }; + }; + }; +in +{ + options.khscodes.hcloud = { + enable = lib.mkEnableOption "Enables khscodes hcloud terranix integration"; + server = lib.mkOption { + type = lib.types.attrsOf hcloudServerModule; + description = "Defines an hcloud server"; + default = { }; + }; + data.ssh_key = lib.mkOption { + type = lib.types.attrsOf hcloudDataSshKeys; + description = "Reads ssh keys as data source from hcloud"; + default = { }; + }; + }; + imports = [ + inputs.terranix-hcloud.terranixModules.hcloud + (import ./output.nix { inherit inputs khscodesLib; }) + ]; + config = lib.mkIf cfg.enable { + + hcloud.enable = true; + terraform.required_providers.hcloud.version = "~> 1.45.0"; + resource.hcloud_server = mapSanitizedAttrs ( + { name, value }: + { + inherit (value) + server_type + datacenter + labels + ssh_keys + name + ; + image = value.initial_image; + public_net = { + ipv4_enabled = true; + ipv4 = "\${ hcloud_primary_ip.${name}_ipv4.id }"; + ipv6_enabled = true; + ipv6 = "\${ hcloud_primary_ip.${name}_ipv6.id }"; + }; + lifecycle = { + ignore_changes = [ + "ssh_keys" + "public_net" + "image" + ]; + }; + } + ) cfg.server; + resource.hcloud_primary_ip = + (lib.mapAttrs' ( + name: value: + let + sanitizedName = khscodesLib.sanitize-terraform-name name; + in + { + name = "${sanitizedName}_ipv4"; + value = { + inherit (value) labels; + name = "${value.name} ipv4"; + datacenter = value.datacenter; + type = "ipv4"; + assignee_type = "server"; + auto_delete = false; + }; + } + ) cfg.server) + // (lib.mapAttrs' ( + name: value: + let + sanitizedName = khscodesLib.sanitize-terraform-name name; + in + { + name = "${sanitizedName}_ipv6"; + value = { + inherit (value) labels; + name = "${value.name} ipv6"; + datacenter = value.datacenter; + type = "ipv6"; + assignee_type = "server"; + auto_delete = false; + }; + } + ) cfg.server); + resource.hcloud_rdns = + (lib.mapAttrs' ( + name: value: + let + sanitizedName = khscodesLib.sanitize-terraform-name name; + in + { + name = "${sanitizedName}_ipv4"; + value = { + primary_ip_id = "\${ hcloud_primary_ip.${sanitizedName}_ipv4.id }"; + ip_address = "\${ hcloud_server.${sanitizedName}.ipv4_address }"; + dns_ptr = value.rdns; + }; + } + ) serversWithRdns) + // (lib.mapAttrs' ( + name: value: + let + sanitizedName = khscodesLib.sanitize-terraform-name name; + in + { + name = "${sanitizedName}_ipv6"; + value = { + primary_ip_id = "\${ hcloud_primary_ip.${sanitizedName}_ipv6.id }"; + ip_address = "\${ hcloud_server.${sanitizedName}.ipv6_address }"; + dns_ptr = value.rdns; + }; + } + ) serversWithRdns); + data.hcloud_ssh_key = mapSanitizedAttrs ( + { value, ... }: + { + name = value.name; + } + ) cfg.data.ssh_key; + }; +} diff --git a/nix/modules/terranix/hcloud/output.nix b/nix/modules/terranix/hcloud/output.nix new file mode 100644 index 0000000..1aacf80 --- /dev/null +++ b/nix/modules/terranix/hcloud/output.nix @@ -0,0 +1,69 @@ +{ inputs, khscodesLib }: +{ config, lib, ... }: +let + cfg = config.khscodes.hcloud; + hcloudOutputServerModule = khscodesLib.mkSubmodule { + description = "Module defined when a corresponding server has been defined"; + options = { + id = lib.mkOption { + type = lib.types.str; + description = "ID of the instance, as a terraform string expression"; + }; + ipv4_address = lib.mkOption { + type = lib.types.str; + description = "IPv4 address of the instance, as a terraform string expression"; + }; + ipv6_address = lib.mkOption { + type = lib.types.str; + description = "IPv6 address of the instance, as a terraform string expression"; + }; + }; + }; + hcloudDataOutputSshKeyModule = khscodesLib.mkSubmodule { + description = "Module defined when a corresponding ssh key has ben retrieved"; + options = { + id = lib.mkOption { + type = lib.types.str; + description = "ID of the ssh key, as a terraform string expression"; + }; + }; + }; +in +{ + options.khscodes.hcloud = { + output.server = lib.mkOption { + type = lib.types.attrsOf hcloudOutputServerModule; + description = "Set by this module to be read by other modules when needing results of defining a server"; + default = { }; + }; + output.data.ssh_key = lib.mkOption { + type = lib.types.attrsOf hcloudDataOutputSshKeyModule; + description = "Set by this module to be read by other modules when needing the result of the ssh key"; + default = { }; + }; + }; + config = { + khscodes.hcloud.output.server = lib.attrsets.mapAttrs ( + name: value: + ( + let + sanitizedName = khscodesLib.sanitize-terraform-name name; + in + { + id = "\${ hcloud_server.${sanitizedName}.id }"; + ipv4_address = "\${ hcloud_server.${sanitizedName}.ipv4_address }"; + ipv6_address = "\${ hcloud_server.${sanitizedName}.ipv4_address }"; + } + ) + ) cfg.server; + khscodes.hcloud.output.data.ssh_key = lib.attrsets.mapAttrs ( + name: _: + let + sanitizedName = khscodesLib.sanitize-terraform-name name; + in + { + id = "\${ data.hcloud_ssh_key.${sanitizedName}.id }"; + } + ) cfg.data.ssh_key; + }; +}