Test bringing up openstack instance
Some checks failed
/ check (push) Failing after 52s
/ dev-shell (push) Successful in 1m8s
/ rust-packages (push) Successful in 1m20s
/ terraform-providers (push) Successful in 57s

This commit is contained in:
Kaare Hoff Skovgaard 2025-07-08 12:22:24 +02:00
parent ea031511cf
commit 748e1763ad
Signed by: khs
GPG key ID: C7D890804F01E9F0
24 changed files with 932 additions and 99 deletions

View file

@ -1,4 +1,4 @@
{ ... }:
{
sanitize-terraform-name = name: builtins.replaceStrings [ "." ] [ "_" ] name;
sanitize-terraform-name = name: builtins.replaceStrings [ "." ":" "/" ] [ "_" "_" "_" ] name;
}

View file

@ -2,7 +2,6 @@
config,
lib,
inputs,
pkgs,
...
}:
let
@ -75,11 +74,6 @@ in
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";
@ -148,23 +142,13 @@ in
imports = [
inputs.self.terranixModules.cloudflare
inputs.self.terranixModules.hcloud
inputs.self.terranixModules.s3
];
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;
khscodes.s3 = {
enable = true;
bucket.key = cfg.bucket.key;
};
khscodes.hcloud.data.ssh_key.khs = {
name = "ca.kaareskovgaard.net";
};
@ -229,7 +213,7 @@ in
khscodes.provisioning.pre = {
modules = modules;
secretsSource = cfg.secretsSource;
endspoints = [
endpoints = [
"aws"
"cloudflare"
"hcloud"

View file

@ -0,0 +1,245 @@
{
config,
lib,
inputs,
...
}:
let
cfg = config.khscodes.khs-openstack-instance;
fqdn = config.khscodes.fqdn;
firewallTcpRules = lib.lists.flatten (
lib.lists.map (p: [
{
direction = "ingress";
ethertype = "IPv4";
protocol = "tcp";
port = p;
remote_subnet = "0.0.0.0/0";
}
{
direction = "ingress";
ethertype = "IPv6";
protocol = "tcp";
port = p;
remote_subnet = "::/0";
}
]) config.networking.firewall.allowedTCPPorts
);
firewallUdpRules = lib.lists.flatten (
lib.lists.map (p: [
{
direction = "ingress";
ethertype = "IPv4";
protocol = "udp";
port = p;
remote_subnet = "0.0.0.0/0";
}
{
direction = "ingress";
ethertype = "IPv6";
protocol = "udp";
port = p;
remote_subnet = "::/0";
}
]) config.networking.firewall.allowedUDPPorts
);
firewallIcmpRules = lib.lists.optionals config.networking.firewall.allowPing [
{
direction = "ingress";
ethertype = "IPv4";
protocol = "icmp";
remote_subnet = "0.0.0.0/0";
}
{
direction = "ingress";
ethertype = "IPv6";
protocol = "icmp";
remote_subnet = "::/0";
}
];
firewallRules = firewallTcpRules ++ firewallUdpRules ++ firewallIcmpRules ++ cfg.extraFirewallRules;
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.khs-openstack-instance = {
enable = lib.mkEnableOption "enables generating a opentofu config for khs openstack instance";
dnsNames = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "DNS names for the instance";
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 opentofu secrets from Bitwarden or Vault";
default = "vault";
};
flavor = lib.mkOption {
type = lib.types.nullOr lib.types.str;
description = "The server type to create";
default = null;
};
ssh_key = lib.mkOption {
type = lib.types.str;
description = "SSH key for the server (this only applies to the initial creation, deploying NixOS will render this key useless). Changing this will recreate the instance";
default = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCqY0FHnWFKfLG2yfgr4qka5sR9CK+EMAhzlHUkaQyWHTKD+G0/vC/fNPyL1VV3Dxc/ajxGuPzVE+mBMoyxazL3EtuCDOVvHJ5CR+MUSEckg/DDwcGHqy6rC8BvVVpTAVL04ByQdwFnpE1qNSBaQLkxaFVdtriGKkgMkc7+UNeYX/bv7yn+APqfP1a3xr6wdkSSdO8x4N2jsSygOIMx10hLyCV4Ueu7Kp8Ww4rGY8j5o7lKJhbgfItBfSOuQHdppHVF/GKYRhdnK6Y2fZVYbhq4KipUtclbZ6O/VYd8/sOO98+LMm7cOX+K35PQjUpYgcoNy5+Sw3CNS/NHn4JvOtTaUEYP7fK6c9LhMULOO3T7Cm6TMdiFjUKHkyG+s2Mu/LXJJoilw571zwuh6chkeitW8+Ht7k0aPV96kNEvTdoXwLhBifVEaChlAsLAzSUjUq+YYCiXVk0VIXCZQWKj8LoVNTmaqDksWwbcT64fw/FpVC0N18WHbKcFUEIW/O4spJMa30CQwf9FeqpoWoaF1oRClCSDPvX0AauCu0JcmRinz1/JmlXljnXWbSfm20/V+WyvktlI0wTD0cdpNuSasT9vS77YfJ8nutcWWZKSkCj4R4uHeCNpDTX5YXzapy7FxpM9ANCXLIvoGX7Yafba2Po+er7SSsUIY1AsnBBr8ZoDVw==";
};
extraFirewallRules = lib.mkOption {
type = lib.types.listOf lib.types.attrs;
description = "Extra firewall rules added to the instance";
default = [
{
direction = "egress";
ethertype = "IPv4";
protocol = "tcp";
port = 80;
remote_subnet = "0.0.0.0/0";
}
{
direction = "egress";
ethertype = "IPv6";
protocol = "tcp";
port = 80;
remote_subnet = "::/0";
}
{
direction = "egress";
ethertype = "IPv4";
protocol = "tcp";
port = 443;
remote_subnet = "0.0.0.0/0";
}
{
direction = "egress";
ethertype = "IPv6";
protocol = "tcp";
port = 443;
remote_subnet = "::/0";
}
{
direction = "egress";
ethertype = "IPv4";
protocol = "udp";
port = 443;
remote_subnet = "0.0.0.0/0";
}
{
direction = "egress";
ethertype = "IPv6";
protocol = "udp";
port = 443;
remote_subnet = "::/0";
}
{
direction = "egress";
ethertype = "IPv4";
protocol = "icmp";
remote_subnet = "0.0.0.0/0";
}
{
direction = "egress";
ethertype = "IPv6";
protocol = "icmp";
remote_subnet = "::/0";
}
];
};
};
config = lib.mkIf cfg.enable (
let
tags = [ fqdn ];
modules = [
(
{ config, ... }:
{
imports = [
inputs.self.terranixModules.cloudflare
inputs.self.terranixModules.openstack
inputs.self.terranixModules.unifi
inputs.self.terranixModules.s3
];
config = {
khscodes.s3 = {
enable = true;
bucket.key = cfg.bucket.key;
};
khscodes.openstack.enable = true;
khscodes.openstack.compute_instance.compute = {
inherit tags;
name = fqdn;
initial_image = "Ubuntu-22.04";
flavor = cfg.flavor;
ssh_public_key = cfg.ssh_key;
firewall_rules = firewallRules;
};
khscodes.cloudflare = {
enable = true;
dns = {
enable = true;
zone_name = tldFromFqdn fqdn;
aRecords = [
{
inherit fqdn;
content = config.khscodes.openstack.output.compute_instance.compute.ipv4_address;
}
];
aaaaRecords = [
{
inherit fqdn;
content = config.khscodes.openstack.output.compute_instance.compute.ipv6_address;
}
];
};
};
output.ipv4_address = {
value = config.khscodes.openstack.output.compute_instance.compute.ipv4_address;
sensitive = false;
};
output.ipv6_address = {
value = config.khscodes.openstack.output.compute_instance.compute.ipv6_address;
sensitive = false;
};
};
}
)
];
in
{
assertions = [
{
assertion = config.khscodes.fqdn != null;
message = "Must set config.khscodes.fqdn when using opentofu";
}
];
khscodes.provisioning.pre = {
modules = modules;
secretsSource = cfg.secretsSource;
endpoints = [
"aws"
"cloudflare"
"openstack"
"unifi"
];
};
}
);
}

View file

@ -0,0 +1 @@
{ ... }: { }

View file

@ -21,7 +21,7 @@ let
description = "Where to get the secrets for the provisioning from";
default = "vault";
};
endspoints = lib.mkOption {
endpoints = lib.mkOption {
type = lib.types.listOf (
lib.types.enum [
"openstack"

View file

@ -88,7 +88,7 @@ in
config = lib.mkIf cfg.enable {
hcloud.enable = true;
terraform.required_providers.hcloud.version = "~> 1.45.0";
terraform.required_providers.hcloud.version = "~> 1.51.0";
resource.hcloud_server = mapSanitizedAttrs (
{ name, value }:
{

View file

@ -0,0 +1,454 @@
{ khscodesLib, inputs }:
{ lib, config, ... }:
let
cfg = config.khscodes.openstack;
modules = [
./output.nix
];
firewallRuleModule = khscodesLib.mkSubmodule {
description = "Firewall rule";
options = {
direction = lib.mkOption {
type = lib.types.enum [
"ingress"
"egress"
];
description = "The direction of the firewall rule";
};
ethertype = lib.mkOption {
type = lib.types.enum [
"IPv4"
"IPv6"
];
description = "The IP version";
};
protocol = lib.mkOption {
type = lib.types.enum [
"tcp"
"udp"
"icmp"
];
description = "The protocol";
};
port = lib.mkOption {
type = lib.types.int;
description = "The port (for udp and tcp rules) to apply the rule to";
};
remote_subnet = lib.mkOption {
type = lib.types.str;
description = "The remote subnet to apply the rule to";
};
};
};
mapFirewallRule =
security_group_id: rule:
{
inherit (rule) direction ethertype;
inherit security_group_id;
protocol =
if rule.ethertype == "IPv6" && rule.protocol == "icmp" then "ipv6-icmp" else rule.protocol;
remote_ip_prefix = rule.remote_subnet;
}
// (lib.attrsets.optionalAttrs (rule.protocol != "icmp") {
port_range_min = rule.port;
port_range_max = rule.port;
});
openstackComputeInstance = khscodesLib.mkSubmodule {
description = "Openstack compute instance";
options = {
name = lib.mkOption {
type = lib.types.str;
description = "Name of the instance";
};
flavor = lib.mkOption {
type = lib.types.str;
description = "Flavor of the instance";
};
initial_image = lib.mkOption {
type = lib.types.str;
description = "Initial image of the server";
};
ssh_public_key = lib.mkOption {
type = lib.types.str;
description = "The ssh key added to the server";
};
ip4_cidr = lib.mkOption {
type = lib.types.str;
description = "IPv4 cidr of the private virtual network";
default = "172.24.0.0/24";
};
ip4_dns_nameservers = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "DNS (IPv4) nameservers to set (in DHCP) for the ipv4 subnet";
default = [
"1.1.1.1"
"1.0.0.1"
];
};
volume_size = lib.mkOption {
type = lib.types.int;
description = "Size of the root volume, in gigabytes";
default = 30;
};
volume_type = lib.mkOption {
type = lib.types.enum [
"Encrypted"
"__DEFAULT__"
];
description = "The type of volume to create";
default = "Encrypted";
};
ip6_dns_nameservers = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "DNS (IPv6) nameservers to set (in DHCP) for the ipv4 subnet";
default = [
"2606:4700:4700::1111"
"2606:4700:4700::1001"
];
};
tags = lib.mkOption {
type = lib.types.listOf lib.types.str;
};
firewall_rules = lib.mkOption {
type = lib.types.listOf firewallRuleModule;
description = "List of firewall rules to apply to the server";
default = [ ];
};
};
};
in
{
options.khscodes.openstack = {
enable = lib.mkEnableOption "Enables the openstack provider";
compute_instance = lib.mkOption {
type = lib.types.attrsOf openstackComputeInstance;
description = "Defines an openstack compute instance";
default = { };
};
};
imports = lib.lists.map (m: import m { inherit khscodesLib inputs; }) modules;
config = lib.mkIf cfg.enable {
terraform.required_providers.openstack = {
source = "terraform-provider-openstack/openstack";
version = "~> 3.2.0";
};
provider.openstack = {
user_name = "\${ var.openstack_username }";
tenant_name = "\${ var.openstack_tenant_name }";
password = "\${ var.openstack_password }";
auth_url = "\${ var.openstack_auth_url }";
region = "\${ var.openstack_region }";
endpoint_type = "\${ var.openstack_endpoint_type }";
};
variable = {
openstack_username = {
type = "string";
};
openstack_password = {
type = "string";
sensitive = true;
};
openstack_tenant_name = {
type = "string";
};
openstack_auth_url = {
type = "string";
};
openstack_endpoint_type = {
type = "string";
};
openstack_region = {
type = "string";
};
};
# DATA
# flavor
data.openstack_compute_flavor_v2 = lib.mapAttrs' (
name: value:
let
sanitizedName = khscodesLib.sanitize-terraform-name name;
in
{
name = sanitizedName;
value = {
name = value.flavor;
};
}
) cfg.compute_instance;
# image
data.openstack_images_image_v2 = lib.mapAttrs' (
name: value:
let
sanitizedName = khscodesLib.sanitize-terraform-name name;
in
{
name = sanitizedName;
value = {
name = value.initial_image;
};
}
) cfg.compute_instance;
# provider network
data.openstack_networking_network_v2.provider = {
name = "public";
};
# provider subnet pool (for ipv6)
data.openstack_networking_subnetpool_v2.provider = {
name = "provider-subnet-pool";
};
# RESOURCE
# keypair
resource.openstack_compute_keypair_v2 = lib.mapAttrs' (
name: value:
let
sanitizedName = khscodesLib.sanitize-terraform-name name;
in
{
name = sanitizedName;
value = {
name = khscodesLib.sanitize-terraform-name value.name;
public_key = value.ssh_public_key;
};
}
) cfg.compute_instance;
# router
resource.openstack_networking_router_v2 = lib.mapAttrs' (
name: value:
let
sanitizedName = khscodesLib.sanitize-terraform-name name;
in
{
name = sanitizedName;
value = {
name = value.name;
external_network_id = "\${ data.openstack_networking_network_v2.provider.id }";
tags = value.tags;
};
}
) cfg.compute_instance;
# network
resource.openstack_networking_network_v2 = lib.mapAttrs' (
name: value:
let
sanitizedName = khscodesLib.sanitize-terraform-name name;
in
{
name = sanitizedName;
value = {
name = value.name;
tags = value.tags;
};
}
) cfg.compute_instance;
# subnet
resource.openstack_networking_subnet_v2 =
(lib.mapAttrs' (
name: value:
let
sanitizedName = khscodesLib.sanitize-terraform-name name;
in
{
name = "${sanitizedName}_ip4";
value = {
name = "ip4: ${value.name}";
cidr = value.ip4_cidr;
ip_version = 4;
network_id = "\${ openstack_networking_network_v2.${sanitizedName}.id }";
dns_nameservers = value.ip4_dns_nameservers;
tags = value.tags;
};
}
) cfg.compute_instance)
// (lib.mapAttrs' (
name: value:
let
sanitizedName = khscodesLib.sanitize-terraform-name name;
in
{
name = "${sanitizedName}_ip6";
value = {
name = "ip6: ${value.name}";
ip_version = 6;
ipv6_address_mode = "dhcpv6-stateless";
ipv6_ra_mode = "dhcpv6-stateless";
subnetpool_id = "\${ data.openstack_networking_subnetpool_v2.provider.id }";
dns_nameservers = value.ip6_dns_nameservers;
network_id = "\${ openstack_networking_network_v2.${sanitizedName}.id }";
tags = value.tags;
};
}
) cfg.compute_instance);
# router interface
resource.openstack_networking_router_interface_v2 =
(lib.mapAttrs' (
name: value:
let
sanitizedName = khscodesLib.sanitize-terraform-name name;
in
{
name = "${sanitizedName}_ip4";
value = {
router_id = "\${ openstack_networking_router_v2.${sanitizedName}.id }";
subnet_id = "\${ openstack_networking_subnet_v2.${sanitizedName}_ip4.id }";
};
}
) cfg.compute_instance)
// (lib.mapAttrs' (
name: value:
let
sanitizedName = khscodesLib.sanitize-terraform-name name;
in
{
name = "${sanitizedName}_ip6";
value = {
router_id = "\${ openstack_networking_router_v2.${sanitizedName}.id }";
subnet_id = "\${ openstack_networking_subnet_v2.${sanitizedName}_ip6.id }";
};
}
) cfg.compute_instance);
# floating ip
resource.openstack_networking_floatingip_v2 = lib.mapAttrs' (
name: value:
let
sanitizedName = khscodesLib.sanitize-terraform-name name;
in
{
name = sanitizedName;
value = {
pool = "public";
tags = value.tags;
};
}
) cfg.compute_instance;
# volume
resource.openstack_blockstorage_volume_v3 = lib.mapAttrs' (
name: value:
let
sanitizedName = khscodesLib.sanitize-terraform-name name;
in
{
name = sanitizedName;
value = {
name = value.name;
size = value.volume_size;
image_id = "\${ data.openstack_images_image_v2.${sanitizedName}.id }";
volume_type = value.volume_type;
};
}
) cfg.compute_instance;
# security group
resource.openstack_networking_secgroup_v2 = lib.mapAttrs' (
name: value:
let
sanitizedName = khscodesLib.sanitize-terraform-name name;
in
{
name = sanitizedName;
value = {
name = value.name;
tags = value.tags;
};
}
) cfg.compute_instance;
# security group rules (firewall rules)
resource.openstack_networking_secgroup_rule_v2 = lib.attrsets.mergeAttrsList (
lib.lists.flatten (
lib.lists.map (
{ name, value }:
let
sanitizedName = khscodesLib.sanitize-terraform-name name;
in
lib.listToAttrs (
lib.lists.map (
rule:
let
protocol =
if rule.protocol == "icmp" then "icmp" else "${rule.protocol}_${builtins.toString rule.port}";
in
{
name = "${sanitizedName}_${rule.direction}_${rule.ethertype}_${protocol}_${khscodesLib.sanitize-terraform-name rule.remote_subnet}";
value = mapFirewallRule "\${ resource.openstack_networking_secgroup_v2.${sanitizedName}.id }" rule;
}
) value.firewall_rules
)
) (lib.attrsToList cfg.compute_instance)
)
);
# instance
data.openstack_networking_port_v2 = lib.mapAttrs' (
name: value:
let
sanitizedName = khscodesLib.sanitize-terraform-name name;
in
{
name = sanitizedName;
value = {
device_id = "\${ openstack_compute_instance_v2.${sanitizedName}.id }";
network_id = "\${ openstack_networking_network_v2.${sanitizedName}.id }";
};
}
) cfg.compute_instance;
resource.openstack_compute_instance_v2 = lib.mapAttrs' (
name: value:
let
sanitizedName = khscodesLib.sanitize-terraform-name name;
in
{
name = sanitizedName;
value = {
name = value.name;
tags = value.tags;
flavor_id = "\${ data.openstack_compute_flavor_v2.${sanitizedName}.id }";
key_pair = "\${ openstack_compute_keypair_v2.${sanitizedName}.name }";
block_device = [
{
uuid = "\${ openstack_blockstorage_volume_v3.${sanitizedName}.id }";
source_type = "volume";
boot_index = 0;
destination_type = "volume";
delete_on_termination = false;
}
];
security_groups = [ "\${ openstack_networking_secgroup_v2.${sanitizedName}.name }" ];
network = [
{
uuid = "\${ openstack_networking_network_v2.${sanitizedName}.id }";
}
];
};
}
) cfg.compute_instance;
# floating ip associate
resource.openstack_networking_floatingip_associate_v2 = lib.mapAttrs' (
name: value:
let
sanitizedName = khscodesLib.sanitize-terraform-name name;
in
{
name = sanitizedName;
value = {
floating_ip = "\${ openstack_networking_floatingip_v2.${sanitizedName}.address }";
port_id = "\${ data.openstack_networking_port_v2.${sanitizedName}.id }";
};
}
) cfg.compute_instance;
};
}

View file

@ -0,0 +1,51 @@
{ khscodesLib, ... }:
{ config, lib, ... }:
let
cfg = config.khscodes.openstack;
openstackOutputInstanceModule = khscodesLib.mkSubmodule {
description = "Instance output";
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";
};
ipv6_external_gateway = lib.mkOption {
type = lib.types.str;
description = "The IPv6 external gateway for the network. This is useful to eg. create static routes in Unifi";
};
};
};
in
{
options.khscodes.openstack = {
output.compute_instance = lib.mkOption {
type = lib.types.attrsOf openstackOutputInstanceModule;
description = "Set by this module to be read by other modules when needing results of defining a compute instance";
default = { };
};
};
config = {
khscodes.openstack.output.compute_instance = lib.attrsets.mapAttrs (
name: value:
(
let
sanitizedName = khscodesLib.sanitize-terraform-name name;
in
{
id = "\${ openstack_compute_instance_v2.${sanitizedName}.id }";
ipv4_address = "\${ openstack_networking_floatingip_v2.${sanitizedName}.address }";
ipv6_address = "\${ data.openstack_networking_port_v2.${sanitizedName}.all_fixed_ips[1] }";
ipv6_external_gateway = "\${ [for ip in openstack_networking_router_v2.${sanitizedName}.external_fixed_ip : ip.ip_address if replace(ip.ip_address, \":\", \"\") != ip.ip_address][0] }";
}
)
) cfg.compute_instance;
};
}

View file

@ -0,0 +1,33 @@
{ ... }:
{ lib, config, ... }:
let
cfg = config.khscodes.s3;
in
{
options.khscodes.s3 = {
enable = lib.mkEnableOption "Enables the s3 backend";
bucket = {
key = lib.mkOption {
type = lib.types.str;
description = "key for the bucket to use";
};
};
};
config = lib.mkIf cfg.enable {
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;
};
};
}

View file

@ -0,0 +1,23 @@
{ khscodesLib, inputs }:
{ lib, config, ... }:
let
cfg = config.khscodes.unifi;
modules = [
./output.nix
];
in
{
options.khscodes.unifi = {
enable = lib.mkEnableOption "Enables the unifi provider";
bucket = {
key = lib.mkOption {
type = lib.types.str;
description = "key for the bucket to use";
};
};
};
imports = lib.lists.map (m: import m { inherit khscodesLib inputs; }) modules;
config = lib.mkIf cfg.enable { };
}

View file

@ -0,0 +1,12 @@
{ khscodesLib, ... }:
{ config, lib, ... }:
let
cfg = config.khscodes.unifi;
in
{
options.khscodes.unifi = {
};
config = {
};
}

View file

@ -5,26 +5,7 @@ let
# for now this map just needs to include every secret we could need, which also makes the reading of secrets take way longer than
# needed.
secrets = import ./secrets-map.nix;
wrappedScript = pkgs.writeShellApplication {
name = "bw-opentofu-wrapped";
runtimeInputs = [
pkgs.uutils-coreutils-noprefix
pkgs.bitwarden-cli
pkgs.khscodes.find-flake-root
opentofu
];
text = ''
fqdn="$1"
config="$2"
phase="$3"
flakeRoot="$(find-flake-root)"
dir="$flakeRoot/.terraform-cache/$fqdn/$phase"
mkdir -p "$dir"
cat "''${config}" > "$dir/config.tf.json"
tofu -chdir="$dir" init
tofu -chdir="$dir" apply
'';
};
wrappedScript = pkgs.khscodes.instance-opentofu;
in
lib.khscodes.mkBwEnv {
inherit pkgs;

View file

@ -0,0 +1,22 @@
{ pkgs, ... }:
let
opentofu = pkgs.khscodes.opentofu;
in
pkgs.writeShellApplication {
name = "instance-opentofu";
runtimeInputs = [
pkgs.uutils-coreutils-noprefix
opentofu
];
text = ''
fqdn="$1"
config="$2"
cmd="''${3:-apply}"
dir="$(mktemp -dt "$fqdn-pre-provisioning.XXXXXX")"
mkdir -p "$dir"
cat "''${config}" > "$dir/config.tf.json"
tofu -chdir="$dir" init > /dev/null
tofu -chdir="$dir" "$cmd"
'';
}

View file

@ -1 +1,7 @@
{ pkgs }: pkgs.opentofu.withPlugins (p: [ pkgs.khscodes.terraform-provider-unifi ])
{ pkgs }:
pkgs.opentofu.withPlugins (p: [
pkgs.khscodes.terraform-provider-unifi
pkgs.khscodes.terraform-provider-cloudflare
pkgs.khscodes.terraform-provider-hcloud
pkgs.khscodes.terraform-provider-openstack
])

View file

@ -7,21 +7,27 @@ pkgs.writeShellApplication {
runtimeInputs = [
pkgs.nix
pkgs.khscodes.bw-opentofu
pkgs.khscodes.instance-opentofu
pkgs.khscodes.openbao-helper
pkgs.jq
];
# TODO: Use secret source and required secrets to set up the correct env variables
text = ''
hostname="$1"
cmd="''${2:-apply}"
baseAttr='${inputs.self}#nixosConfigurations."'"$hostname"'".config.khscodes.provisioning'
config="$(nix eval --raw "''${baseAttr}.preConfig")"
config="$(nix build --no-link --print-out-paths "''${baseAttr}.preConfig")"
secretsSource="$(nix eval --raw "''${baseAttr}.pre.secretsSource")"
endpoints="$(nix eval --json "''${baseAttr}.pre.endpoints")"
if [[ "$config" == "null" ]]; then
echo "No preprovisioning needed"
exit 0
fi
if [[ "$secretsSource" == "vault" ]]; then
>&2 echo "Provisioning using vault is not yet implemented"
exit 1
readarray -t endpoints_args < <(echo "$endpoints" | jq -cr 'map(["-e", .])[][]')
openbao-helper wrap-program "''${endpoints_args[@]}" -- instance-opentofu "$hostname" "$config" "$cmd"
exit 0
fi
bw-opentofu "$hostname" "$config" "pre"
bw-opentofu "$hostname" "$config" "$cmd"
'';
}

View file

@ -0,0 +1,10 @@
{ pkgs }:
pkgs.terraform-providers.mkProvider {
hash = "sha256-rgXsROzfjtUw994JH8x+j/UNMyl7E9cZ+77Fczc3uB8=";
homepage = "https://registry.terraform.io/providers/cloudflare/cloudflare";
owner = "cloudflare";
repo = "terraform-provider-cloudflare";
rev = "v4.52.0";
spdx = "MPL-2.0";
vendorHash = "sha256-RULgejA/RTDHhRJRiqlgckK4Ut3GLvIE081/i6gQTjI=";
}

View file

@ -0,0 +1,10 @@
{ pkgs }:
pkgs.terraform-providers.mkProvider {
hash = "sha256-/BcK9K/jNEU4r7mFc4rrhNnPlyH98UrDUCgIluY52fA=";
homepage = "https://registry.terraform.io/providers/hetznercloud/hcloud";
owner = "hetznercloud";
repo = "terraform-provider-hcloud";
rev = "v1.51.0";
spdx = "MPL-2.0";
vendorHash = "sha256-jbNkhNSSO9jT20J6dVhBEbN9cwtNrvx5EUcyOZcMd4Y=";
}

View file

@ -0,0 +1,10 @@
{ pkgs }:
pkgs.terraform-providers.mkProvider {
hash = "sha256-pGNHWhg/1LM1IJYEVLppCJWVzow+j3WPW+H8yWQXMyM=";
homepage = "https://registry.terraform.io/providers/terraform-provider-openstack/openstack";
owner = "terraform-provider-openstack";
repo = "terraform-provider-openstack";
rev = "v3.2.0";
spdx = "MPL-2.0";
vendorHash = "sha256-mTWLix4A0GRe7ayHTwU3Jt+DfDKMIKJlt1I6JuL3wXU=";
}

View file

@ -1,5 +1,6 @@
{ ... }:
{ modulesPath, ... }:
{
imports = [ "${modulesPath}/profiles/qemu-guest.nix" ];
config.khscodes = {
hetzner.enable = true;
sshd.enable = true;

View file

@ -0,0 +1,7 @@
{ modulesPath, ... }:
{
imports = [ "${modulesPath}/profiles/qemu-guest.nix" ];
config.khscodes = {
sshd.enable = true;
};
}

View file

@ -0,0 +1,14 @@
{
inputs,
...
}:
{
imports = [ "${inputs.self}/nix/profiles/khs-openstack-server.nix" ];
khscodes.khs-openstack-instance = {
enable = true;
flavor = "m.medium";
secretsSource = "vault";
};
khscodes.fqdn = "test.kaareskovgaard.net";
system.stateVersion = "25.05";
}