Lots more updates

Also begin adding rust building capabilities
to be able to write rust binaries for some commands.
This commit is contained in:
Kaare Hoff Skovgaard 2025-07-06 22:37:16 +02:00
parent 624508dd14
commit dd1cfa79e7
Signed by: khs
GPG key ID: C7D890804F01E9F0
52 changed files with 2509 additions and 150 deletions

View file

@ -0,0 +1,7 @@
{
inputs,
pkgs,
lib,
...
}:
(lib.khscodes.mkRust pkgs "${inputs.self}/rust").checks.rust-audit

View file

@ -0,0 +1,7 @@
{
inputs,
pkgs,
lib,
...
}:
(lib.khscodes.mkRust pkgs "${inputs.self}/rust").checks.rust-clippy

View file

@ -0,0 +1,7 @@
{
inputs,
pkgs,
lib,
...
}:
(lib.khscodes.mkRust pkgs "${inputs.self}/rust").checks.rust-doc

View file

@ -0,0 +1,7 @@
{
inputs,
pkgs,
lib,
...
}:
(lib.khscodes.mkRust pkgs "${inputs.self}/rust").checks.rust-fmt

View file

@ -0,0 +1,7 @@
{
inputs,
pkgs,
lib,
...
}:
(lib.khscodes.mkRust pkgs "${inputs.self}/rust").checks.rust-hakari

14
nix/lib/rust/default.nix Normal file
View file

@ -0,0 +1,14 @@
{
inputs,
lib,
...
}:
{
mkRust =
pkgs: src:
import src {
inherit lib pkgs;
crane = inputs.crane;
advisory-db = inputs.advisory-db;
};
}

View file

@ -19,8 +19,8 @@ in
domain = if hostname == cfg then null else (lib.strings.removePrefix "${hostname}." cfg);
in
{
networking.hostName = hostname;
networking.domain = domain;
networking.hostName = lib.mkForce hostname;
networking.domain = lib.mkForce domain;
boot.kernel.sysctl = {
"kernel.hostname" = cfg;
};

View file

@ -6,12 +6,8 @@
...
}:
let
cfg = config.khscodes.terraform-hetzner;
cfg = config.khscodes.hetzner-instance;
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";
@ -41,7 +37,6 @@ let
};
firewallRules = firewallTcpRules ++ firewallUdpRules ++ firewallIcmpRules ++ cfg.extraFirewallRules;
firewallEnable = config.networking.firewall.enable;
mapRdns = cfg.mapRdns;
tldFromFqdn =
fqdn:
let
@ -53,8 +48,8 @@ let
lib.strings.removePrefix "${builtins.head split}." fqdn;
in
{
options.khscodes.terraform-hetzner = {
enable = lib.mkEnableOption "enables generating a terraform config";
options.khscodes.hetzner-instance = {
enable = lib.mkEnableOption "enables generating a opentofu config";
dnsNames = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "DNS names for the server";
@ -72,7 +67,7 @@ in
"bitwarden"
"vault"
];
description = "Whether to load terraform secrets from Bitwarden or Vault";
description = "Whether to load opentofu secrets from Bitwarden or Vault";
default = "vault";
};
datacenter = lib.mkOption {
@ -146,95 +141,102 @@ in
labels = {
app = fqdn;
};
config = inputs.terranix.lib.terranixConfiguration {
system = pkgs.hostPlatform.system;
modules = [
(
{ config, ... }:
{
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;
modules = [
(
{ config, ... }:
{
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;
};
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 = {
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;
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;
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;
};
};
}
)
];
in
{
assertions = [
{
assertion = config.khscodes.fqdn != null;
message = "Must set config.khscodes.fqdn when using terraform";
message = "Must set config.khscodes.fqdn when using opentofu";
}
];
khscodes.terraform-hetzner.output = config;
khscodes.provisioning.pre = {
modules = modules;
secretsSource = cfg.secretsSource;
variablesNeeded = [
"TF_VAR_cloudflare_token"
"TF_VAR_cloudflare_email"
"AWS_ACCESS_KEY_ID"
"AWS_SECRET_ACCESS_KEY"
"TF_VAR_hcloud_api_token"
];
};
}
);
}

View file

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

View file

@ -0,0 +1,63 @@
{
config,
lib,
inputs,
pkgs,
...
}:
let
cfg = config.khscodes.provisioning;
provisioning = {
modules = lib.mkOption {
type = lib.types.listOf lib.types.anything;
description = "Modules used to bring up the needed resources";
default = [ ];
};
secretsSource = lib.mkOption {
type = lib.types.enum [
"vault"
"bitwarden"
];
description = "Where to get the secrets for the provisioning from";
default = "vault";
};
variablesNeeded = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "Needed environment variables for the provisioning";
default = [ ];
};
};
in
{
options.khscodes.provisioning = {
pre = provisioning;
post = provisioning;
preConfig = lib.mkOption {
type = lib.types.nullOr lib.types.path;
description = "The generated config for the pre provisioning, if any was specified";
};
postConfig = lib.mkOption {
type = lib.types.nullOr lib.types.path;
description = "The generated config for the post provisioning, if any was specified";
};
};
config = {
khscodes.provisioning.preConfig =
if lib.lists.length cfg.pre.modules > 0 then
inputs.terranix.lib.terranixConfiguration {
system = pkgs.hostPlatform.system;
modules = cfg.pre.modules;
}
else
null;
khscodes.provisioning.postConfig =
if lib.lists.length cfg.post.modules > 0 then
inputs.terranix.lib.terranixConfiguration {
system = pkgs.hostPlatform.system;
modules = cfg.post.modules;
}
else
null;
};
}

View file

@ -1,4 +1,4 @@
{ inputs, khscodesLib }:
{ khscodesLib, ... }:
{ config, lib, ... }:
let
cfg = config.khscodes.hcloud;
@ -52,7 +52,7 @@ in
{
id = "\${ hcloud_server.${sanitizedName}.id }";
ipv4_address = "\${ hcloud_server.${sanitizedName}.ipv4_address }";
ipv6_address = "\${ hcloud_server.${sanitizedName}.ipv4_address }";
ipv6_address = "\${ hcloud_server.${sanitizedName}.ipv6_address }";
}
)
) cfg.server;

View file

@ -0,0 +1,32 @@
{ khscodesLib, inputs }:
{ lib, config, ... }:
let
cfg = config.khscodes.openbao;
modules = [
./output.nix
./vault_mount.nix
];
in
{
options.khscodes.openbao = {
enable = lib.mkEnableOption "Enables the openbao provider";
};
imports = lib.lists.map (m: import m { inherit khscodesLib inputs; }) modules;
config = lib.mkIf cfg.enable {
provider.vault = {
address = "https://auth.kaareskovgaard.net";
};
terraform.required_providers.vault = {
source = "hashicorp/vault";
version = "5.0.0";
};
resource.vault_mount = lib.mapAttrs' (
name: value: {
name = khscodesLib.sanitize-terraform-name name;
value = value;
}
);
};
}

View file

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

View file

@ -0,0 +1,45 @@
{ khscodesLib, ... }:
{ lib, config, ... }:
let
cfg = config.khscodes.openbao;
in
{
options.khscodes.openbao = {
vault_ssh_secret_backend_ca = lib.mkOption {
type = lib.types.attrsOf (
khscodesLib.mkSubmodule {
options = {
backend = lib.mkOption {
type = lib.types.str;
description = "Path of the backend mount";
};
generate_signing_key = lib.mkOption {
type = lib.types.bool;
description = "Generate a signing key on the server";
};
key_type = lib.mkOption {
type = lib.types.str;
description = "The type of the signing key to use/generate";
};
};
description = "vault_ssh_secret_backend_ca";
}
);
};
};
config = lib.mkIf cfg.enable {
provider.vault = {
address = "https://auth.kaareskovgaard.net";
};
terraform.required_providers.vault = {
source = "hashicorp/vault";
version = "5.0.0";
};
resource.vault_ssh_secret_backend_ca = lib.mapAttrs' (
name: value: {
name = khscodesLib.sanitize-terraform-name name;
value = value;
}
);
};
}

View file

@ -0,0 +1,52 @@
{ khscodesLib, ... }:
{ lib, config, ... }:
let
cfg = config.khscodes.openbao;
in
{
options.khscodes.openbao = {
vault_mount = lib.mkOption {
type = lib.types.attrsOf (
khscodesLib.mkSubmodule {
options = {
type = lib.mkOption {
type = lib.types.str;
description = "Type of mount";
};
path = lib.mkOption {
type = lib.types.str;
description = "Path of the mount";
default = null;
};
default_lease_ttl_seconds = lib.mkOption {
type = lib.types.int;
description = "Default lease ttl in seconds";
default = null;
};
max_lease_ttl_seconds = lib.mkOption {
type = lib.types.int;
description = "Max lease ttl in seconds";
default = null;
};
};
description = "vault_mount";
}
);
};
};
config = lib.mkIf cfg.enable {
provider.vault = {
address = "https://auth.kaareskovgaard.net";
};
terraform.required_providers.vault = {
source = "hashicorp/vault";
version = "5.0.0";
};
resource.vault_mount = lib.mapAttrs' (
name: value: {
name = khscodesLib.sanitize-terraform-name name;
value = value;
}
);
};
}

View file

@ -1,51 +1,52 @@
{ pkgs, lib, ... }:
let
opentofu = pkgs.opentofu;
bw-opentofu = lib.khscodes.mkBwEnv {
inherit pkgs;
name = "bw-opentofu";
items = {
"KHS Openstack" = {
TF_VAR_openstack_username = "login.username";
TF_VAR_openstack_password = "login.password";
TF_VAR_openstack_tenant_name = "Project Name";
TF_VAR_openstack_auth_url = "Auth URL";
TF_VAR_openstack_endpoint_type = "Interface";
TF_VAR_openstack_region = "Region Name";
};
"Cloudflare" = {
TF_VAR_cloudflare_token = "DNS API Token";
TF_VAR_cloudflare_email = "login.username";
AWS_ACCESS_KEY_ID = "BW Terraform access key id";
AWS_SECRET_ACCESS_KEY = "BW Terraform secret access key";
};
"Hetzner Cloud" = {
TF_VAR_hcloud_api_token = "Terraform API Token";
};
# TODO: We should figure out a way of passing the secrets map at runtime instead of build time.
# 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 = {
"KHS Openstack" = {
TF_VAR_openstack_username = "login.username";
TF_VAR_openstack_password = "login.password";
TF_VAR_openstack_tenant_name = "Project Name";
TF_VAR_openstack_auth_url = "Auth URL";
TF_VAR_openstack_endpoint_type = "Interface";
TF_VAR_openstack_region = "Region Name";
};
exe = lib.getExe opentofu;
"Cloudflare" = {
TF_VAR_cloudflare_token = "DNS API Token";
TF_VAR_cloudflare_email = "login.username";
AWS_ACCESS_KEY_ID = "BW Terraform access key id";
AWS_SECRET_ACCESS_KEY = "BW Terraform secret access key";
};
"Hetzner Cloud" = {
TF_VAR_hcloud_api_token = "Terraform API Token";
};
};
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
'';
};
in
pkgs.writeShellApplication {
lib.khscodes.mkBwEnv {
inherit pkgs;
name = "bw-opentofu";
runtimeInputs = [
bw-opentofu
pkgs.uutils-coreutils-noprefix
pkgs.bitwarden-cli
];
text = ''
fqdn="$1"
config="$2"
lockHcl="$3"
dir="$(mktemp -d --tmpdir -t "terraform-hetzher-''${fqdn}.XXXXXXXXXX")"
cp "$lockHcl" "$dir/.terraform.lock.hcl"
cp "''${config}" "$dir/config.tf.json"
if [ "''${BW_SESSION:-}" == "" ]; then
BW_SESSION="$(bw unlock --raw)"
export BW_SESSION
trap "bw lock" EXIT
fi
bw-opentofu -chdir="$dir" init
bw-opentofu -chdir="$dir" apply
'';
items = secrets;
exe = lib.getExe wrappedScript;
}

View file

@ -0,0 +1,16 @@
{ pkgs, ... }:
pkgs.writeShellApplication {
name = "find-flake-root";
runtimeInputs = [ pkgs.uutils-coreutils-noprefix ];
text = ''
while [[ ! -f "$(pwd)/flake.nix" ]]; do
if [[ "$(pwd)" == "/" ]]; then
echo "Could not find flake root" 1>&2
exit 1
fi
cd ..
done
pwd
exit 0
'';
}

View file

@ -0,0 +1,6 @@
{
lib,
pkgs,
inputs,
}:
(lib.khscodes.mkRust pkgs "${inputs.self}/rust").buildRustPackage "hetzner-ipv6"

View file

@ -1,16 +0,0 @@
{
inputs,
pkgs,
}:
pkgs.writeShellApplication {
name = "opentofu-hetzner";
runtimeInputs = [
pkgs.nix
pkgs.khscodes.bw-opentofu
];
text = ''
hostname="$1"
config="$(nix build --no-link --print-out-paths '${inputs.self}#nixosConfigurations."'"$hostname"'".config.khscodes.terraform-hetzner.output')"
bw-opentofu "$hostname" "$config" "${./terraform.lock.hcl}"
'';
}

View file

@ -1,47 +0,0 @@
# This file is maintained automatically by "tofu init".
# Manual edits may be lost in future updates.
provider "registry.opentofu.org/cloudflare/cloudflare" {
version = "4.52.0"
constraints = "~> 4.0"
hashes = [
"h1:Pi5M+GeoMSN2eJ6QnIeXjBf19O+rby/74CfB2ocpv20=",
"zh:19be1a91c982b902c42aba47766860dfa5dc151eed1e95fd39ca642229381ef0",
"zh:1de451c4d1ecf7efbe67b6dace3426ba810711afdd644b0f1b870364c8ae91f8",
"zh:352b4a2120173298622e669258744554339d959ac3a95607b117a48ee4a83238",
"zh:3c6f1346d9154afbd2d558fabb4b0150fc8d559aa961254144fe1bc17fe6032f",
"zh:4c4c92d53fb535b1e0eff26f222bbd627b97d3b4c891ec9c321268676d06152f",
"zh:53276f68006c9ceb7cdb10a6ccf91a5c1eadd1407a28edb5741e84e88d7e29e8",
"zh:7925a97773948171a63d4f65bb81ee92fd6d07a447e36012977313293a5435c9",
"zh:7dfb0a4496cfe032437386d0a2cd9229a1956e9c30bd920923c141b0f0440060",
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
"zh:8d4aa79f0a414bb4163d771063c70cd991c8fac6c766e685bac2ee12903c5bd6",
"zh:a67540c13565616a7e7e51ee9366e88b0dc60046e1d75c72680e150bd02725bb",
"zh:a936383a4767f5393f38f622e92bf2d0c03fe04b69c284951f27345766c7b31b",
"zh:d4887d73c466ff036eecf50ad6404ba38fd82ea4855296b1846d244b0f13c380",
"zh:e9093c8bd5b6cd99c81666e315197791781b8f93afa14fc2e0f732d1bb2a44b7",
"zh:efd3b3f1ec59a37f635aa1d4efcf178734c2fcf8ddb0d56ea690bec342da8672",
]
}
provider "registry.opentofu.org/hetznercloud/hcloud" {
version = "1.45.0"
constraints = "~> 1.45.0"
hashes = [
"h1:dh2iL5GHfDui5DbZFD/kcWlwzmC6slgUirA0FbZBK7g=",
"zh:1c4b44a698cfaca215bdbadaf92669dd23533210c3cbf32895fbf4ff7acf6c24",
"zh:2915f8385559694e5097d8d0df16358200e9f0d9efb80559e9ea0bd072d792b9",
"zh:3a6b37b0bba50d263bd3dba26185bde13c825e59b6b301ab3f9f45686a21456b",
"zh:3e3910fa22a3a8d73d1aed38cc479c3e1958e9168b5f4a7d0da6cf03c2dfc155",
"zh:3f8d7d09e5c93162a1e9e6c89acac0799fb55765b44b7d1d020763c814263c57",
"zh:40bc5e94bff495440e1b4f797165d7f0dcee2282a86a61b158f47fe4bc57e9fb",
"zh:473f51d464b897d0e8e3d5ca2eb175b37e2f7ce03c8b26f47cc35885cf620946",
"zh:6fdd4bf71c19cfad78d7e1d2336be873eb8567a139d53e672e78ebcbc36a4d7d",
"zh:9e08638cbfc90d69f1c21ee34191db077d58d040cf7a9eed07a1dc335d463e97",
"zh:b1ed5ea81bc6d2c88efdefaeb244322874508d90d8217ac2e3541445254bdadc",
"zh:ced05776c27d550d15d4a71360243740ecb4ea1e65e67229fb2273a27353b00c",
"zh:da79b8a1a982a1d365ea206a2654e8b5003aeba9ccdc9c8751bb6ee3f40d8c49",
"zh:fabbad25bab09dd74f2b819992ab99b939c642374d6ca080b18d6e2a91d8d487",
"zh:fb0e083d2925f289999dc561ef1c2f84a9e0ab11388c40162ca8b470f50f71f5",
]
}

View file

@ -0,0 +1,27 @@
{
inputs,
pkgs,
}:
pkgs.writeShellApplication {
name = "pre-provisioning";
runtimeInputs = [
pkgs.nix
pkgs.khscodes.bw-opentofu
];
# TODO: Use secret source and required secrets to set up the correct env variables
text = ''
hostname="$1"
baseAttr='${inputs.self}#nixosConfigurations."'"$hostname"'".config.khscodes.provisioning'
config="$(nix eval --raw "''${baseAttr}.preConfig")"
secretsSource="$(nix eval --raw "''${baseAttr}.pre.secretsSource")"
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
fi
bw-opentofu "$hostname" "$config" "pre"
'';
}

View file

@ -0,0 +1,12 @@
{
lib,
pkgs,
inputs,
mkShell,
}:
mkShell {
packages = [
pkgs.nixd
pkgs.nixfmt-rfc-style
] ++ (lib.khscodes.mkRust pkgs "${inputs.self}/rust").devDeps;
}

View file

@ -4,7 +4,7 @@
}:
{
imports = [ "${inputs.self}/nix/profiles/hetzner-server.nix" ];
khscodes.terraform-hetzner = {
khscodes.hetzner-instance = {
enable = true;
mapRdns = true;
server_type = "cax11";