Mount IMAP data in zfs volume, which should be easily backed
Some checks failed
/ check (push) Failing after 7m39s
/ dev-shell (push) Successful in 3m22s
/ rust-packages (push) Successful in 6m41s
/ systems (push) Successful in 42m54s
/ terraform-providers (push) Successful in 8m2s

up by TrueNAS.

Also enable full text search
This commit is contained in:
Kaare Hoff Skovgaard 2025-08-03 22:29:19 +02:00
parent 8f6c428305
commit fa8320b805
Signed by: khs
GPG key ID: C7D890804F01E9F0
10 changed files with 249 additions and 154 deletions

View file

@ -0,0 +1,10 @@
{ lib, ... }:
{
options.khscodes.infrastructure.nixos-install = {
preScript = lib.mkOption {
type = lib.types.anything;
default = "";
description = "Script to run before running nixos-anywhere.";
};
};
}

View file

@ -27,7 +27,8 @@ in
{ {
systemd.services."read-vault-auth-from-userdata" = { systemd.services."read-vault-auth-from-userdata" = {
enable = true; enable = true;
wantedBy = [ "multi-user.target" ]; wantedBy = [ "vault-agent-openbao.service" ];
before = [ "vault-agent-openbao.service" ];
wants = [ "network-online.target" ]; wants = [ "network-online.target" ];
after = [ "network-online.target" ]; after = [ "network-online.target" ];
environment = { environment = {

View file

@ -19,12 +19,12 @@ let
restartUnits = restartUnits =
svcs: svcs:
lib.strings.concatStringsSep "\n" ( lib.strings.concatStringsSep "\n" (
lib.lists.map (svc: "systemctl restart ${lib.escapeShellArg svc}") svcs lib.lists.map (svc: "systemctl restart ${lib.escapeShellArg svc} || true") svcs
); );
reloadOrRestartUnits = reloadOrRestartUnits =
svcs: svcs:
lib.strings.concatStringsSep "\n" ( lib.strings.concatStringsSep "\n" (
lib.lists.map (svc: "systemctl reload-or-restart ${lib.escapeShellArg svc}") svcs lib.lists.map (svc: "systemctl reload-or-restart ${lib.escapeShellArg svc} || true") svcs
); );
mapTemplate = mapTemplate =
template: template:

View file

@ -19,35 +19,6 @@ let
top = lib.lists.takeEnd 2 split; top = lib.lists.takeEnd 2 split;
in in
lib.strings.concatStringsSep "." top; lib.strings.concatStringsSep "." top;
serviceFromFqdn =
fqdn:
let
split = lib.strings.splitString "." fqdn;
in
assert
lib.strings.hasPrefix "_" (builtins.elemAt split 0)
&& lib.strings.hasPrefix "_" (builtins.elemAt split 1);
builtins.elemAt split 0;
protocolFromFqdn =
fqdn:
let
split = lib.strings.splitString "." fqdn;
in
assert
lib.strings.hasPrefix "_" (builtins.elemAt split 0)
&& lib.strings.hasPrefix "_" (builtins.elemAt split 1);
builtins.elemAt split 1;
nameFromFqdn =
fqdn:
let
split = lib.strings.splitString "." fqdn;
in
assert
lib.strings.hasPrefix "_" (builtins.elemAt split 0)
&& lib.strings.hasPrefix "_" (builtins.elemAt split 1);
lib.strings.concatStringsSep "." (
lib.lists.removePrefix [ (builtins.elemAt split 0) (builtins.elemAt split 1) ] split
);
dnsARecordModule = lib.khscodes.mkSubmodule { dnsARecordModule = lib.khscodes.mkSubmodule {
description = "Module for defining dns A/AAAA record"; description = "Module for defining dns A/AAAA record";
options = { options = {

View file

@ -28,4 +28,7 @@
"secrets.kaareskovgaard.net" = { "secrets.kaareskovgaard.net" = {
"VAULT_TOKEN" = "Initial root token"; "VAULT_TOKEN" = "Initial root token";
}; };
"mx.kaareskovgaard.net" = {
"MX_KAARESKOVGAARD_NET_ZROOT_ENCRYPTION_KEY" = "ZROOT_ENCRYPTION_KEY";
};
} }

View file

@ -18,14 +18,17 @@ pkgs.writeShellApplication {
# Build the configuration to ensure it doesn't fail when trying to install it on the host # Build the configuration to ensure it doesn't fail when trying to install it on the host
nix build --no-link '${inputs.self}#nixosConfigurations."'"$hostname"'".config.system.build.toplevel' nix build --no-link '${inputs.self}#nixosConfigurations."'"$hostname"'".config.system.build.toplevel'
fi fi
baseAttr='${inputs.self}#nixosConfigurations."'"$hostname"'".config.khscodes.infrastructure.provisioning' baseAttr='${inputs.self}#nixosConfigurations."'"$hostname"'".config.khscodes.infrastructure'
config="$(nix build --no-link --print-out-paths "''${baseAttr}.preConfig")" config="$(nix build --no-link --print-out-paths "''${baseAttr}.provisioning.preConfig")"
username="$(nix eval --raw "''${baseAttr}.preImageUsername")" preScript="$(nix eval --raw "''${baseAttr}.nixos-install.preScript")"
username="$(nix eval --raw "''${baseAttr}.provisioning.preImageUsername")"
if [[ "$config" == "null" ]]; then if [[ "$config" == "null" ]]; then
echo "No preprovisioning needed" echo "No preprovisioning needed"
exit 0 exit 0
fi fi
echo -n "tempkey" | ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "$username@$host" -- "cat >/tmp/tempkey"
nixos-anywhere --flake "${inputs.self}#$hostname" --target-host "$username@$host" INSTALL_ARGS=()
eval "$preScript"
nixos-anywhere --flake "${inputs.self}#$hostname" --target-host "$username@$host" "''${INSTALL_ARGS[@]}"
''; '';
} }

View file

@ -1,10 +1,21 @@
{ {
lib,
inputs, inputs,
... ...
}: }:
let
locationFromDatacenter =
datacenter:
let
split = lib.strings.splitString "-" datacenter;
in
assert (lib.lists.length split) == 2;
lib.lists.head split;
in
{ {
imports = [ imports = [
"${inputs.self}/nix/profiles/nixos/hetzner-server.nix" "${inputs.self}/nix/profiles/nixos/hetzner-server.nix"
./disko.nix
./mailserver ./mailserver
]; ];
khscodes = { khscodes = {
@ -15,6 +26,21 @@
server_type = "cax11"; server_type = "cax11";
}; };
provisioning.pre.modules = [ provisioning.pre.modules = [
(
{ config, ... }:
{
resource.hcloud_volume.zroot-disk1 = {
name = "mx.kaareskovgaard.net-zroot-disk1";
size = 100;
location = locationFromDatacenter config.khscodes.hcloud.server.compute.datacenter;
};
resource.hcloud_volume_attachment.zroot-disk1 = {
volume_id = "\${ resource.hcloud_volume.zroot-disk1.id }";
server_id = config.khscodes.hcloud.output.server.compute.id;
automount = false;
};
}
)
( (
{ ... }: { ... }:
{ {

View file

@ -1,4 +1,9 @@
{ pkgs, lib, ... }: {
config,
lib,
pkgs,
...
}:
let let
diskName = "nixos"; diskName = "nixos";
espSize = "500M"; espSize = "500M";
@ -7,66 +12,121 @@ let
volumeGroupName = "mainpool"; volumeGroupName = "mainpool";
rootLvName = "root"; rootLvName = "root";
zrootKey = "/run/secret/zroot.key"; zrootKey = "/run/secret/zroot.key";
zfsLoadKeyScript = pkgs.writeShellApplication { # Don't ask me why this changes when there's more than one volume attached.
name = "load-zfs-key"; nixosDisk = "/dev/sdb";
runtimeInputs = [ pkgs.zfs ]; zrootDisk1Disk = "/dev/sda";
vmailUser = config.mailserver.vmailUserName;
vmailGroup = config.mailserver.vmailGroupName;
downloadZrootKey = pkgs.writeShellApplication {
name = "zfs-download-zroot-key";
runtimeInputs = [
pkgs.openbao
pkgs.zfs
pkgs.uutils-coreutils-noprefix
pkgs.jq
];
text = '' text = ''
if ! zfs load-key -L ${zrootKey} zroot; then if [[ "$(zfs list -j -o keystatus zroot/mailserver | jq --raw-output '.datasets."zroot/mailserver".properties.keystatus.value')" == "available" ]]; then
echo -n "tempkey" /tmp/tempkey >&2 echo "Key already loaded, exiting"
zfs load-key -L /temp/tempkey zroot exit 0
zfs change-key -o keylocation=${zrootKey} zroot
fi fi
# The vault cli insists on needing a token helper, disable it
HOME="$(mktemp -d)"
export HOME
trap 'rm -rf $HOME' EXIT
echo 'token_helper = "/bin/true"' > "$HOME/.vault"
role_id="$(cat /var/lib/vault-agent/role-id)"
secret_id="$(cat /var/lib/vault-agent/secret-id)"
VAULT_TOKEN="$(bao write -field=token auth/approle/login "role_id=$role_id" "secret_id=$secret_id")"
export VAULT_TOKEN
encryption_key="$(bao kv get -mount=opentofu -field=MX_KAARESKOVGAARD_NET_ZROOT_ENCRYPTION_KEY mx.kaareskovgaard.net)"
rm -rf "$HOME"
echo "$encryption_key" | zfs load-key -L file:///dev/stdin zroot/mailserver
''; '';
}; };
in in
{ {
systemd.services = {
dovecot2 = {
unitConfig.RequiresMountsFor = [
"/var/mailserver/vmail"
"/var/mailserver/indices"
];
};
};
khscodes.infrastructure.nixos-install.preScript = ''
encryption_key="$(bao kv get -mount=opentofu -field=MX_KAARESKOVGAARD_NET_ZROOT_ENCRYPTION_KEY mx.kaareskovgaard.net)"
tmpfile="$(mktemp)"
touch "$tmpfile"
chmod 0600 "$tmpfile"
trap "rm -f $tmpfile" EXIT
echo "$encryption_key" > "$tmpfile"
INSTALL_ARGS+=("--disk-encryption-keys")
INSTALL_ARGS+=("/run/secret/zroot.key")
INSTALL_ARGS+=("$tmpfile")
'';
systemd.services.zfs-download-zroot-key = {
after = [
"network-online.target"
"zfs-import-zroot.service"
"read-vault-auth-from-userdata.service"
];
wants = [
"network-online.target"
"zfs-import-zroot.service"
"read-vault-auth-from-userdata.service"
];
wantedBy = [ "zfs-mount.target" ];
before = [ "zfs-mount.target" ];
environment = {
BAO_ADDR = config.khscodes.services.vault-agent.vault.address;
};
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStart = lib.getExe downloadZrootKey;
};
};
khscodes.services.vault-agent.templates = [ khscodes.services.vault-agent.templates = [
{ {
contents = '' contents = ''
{{- with pkiCert "mx.kaareskovgaard.net/data/zroot_encryption" -}} {{- with secret "opentofu/data/mx.kaareskovgaard.net" -}}
{{ .Data.data.key }} {{ .Data.data.MX_KAARESKOVGAARD_NET_ZROOT_ENCRYPTION_KEY }}
{{- end -}} {{- end -}}
''; '';
destination = zrootKey; destination = zrootKey;
owner = "root"; owner = "root";
group = "root"; group = "root";
perms = "0600"; perms = "0600";
exec = lib.getExe zfsLoadKeyScript; exec = ''
chown ${lib.escapeShellArg vmailUser}:${lib.escapeShellArg vmailGroup} /var/mailserver/vmail
chmod 2770 /var/mailserver/vmail
chown ${lib.escapeShellArg vmailUser}:${lib.escapeShellArg vmailGroup} /var/mailserver/indices
chmod 0700 /var/mailserver/indices
'';
restartUnits = [ restartUnits = [
"zfs-mount.service"
"postfix.service" "postfix.service"
"dovecot2.service" "dovecot2.service"
"rspamd.service" "rspamd.service"
]; ];
} }
]; ];
khscodes.infrastructure.provisioning.pre.modules = [ mailserver.mailDirectory = "/var/mailserver/vmail";
{ mailserver.indexDir = "/var/mailserver/indices";
resource.random_password.zroot_encryption_key = {
length = 48;
numeric = true;
lower = true;
upper = true;
special = false;
};
resource.vault_kv_secret_v2.test = {
mount = "mx.kaareskovgaard.net";
name = "zroot_encryption";
data_json = ''
{
"key": ''${ jsonencode(resource.random_password.zroot_encryption_key.result) }
}
'';
};
}
];
khscodes.infrastructure.vault-server-approle.policy = { khscodes.infrastructure.vault-server-approle.policy = {
"mx.kaareskovgaard.net/data/zroot_encryption" = { "opentofu/data/mx.kaareskovgaard.net" = {
capabilities = [ "read" ]; capabilities = [ "read" ];
}; };
}; };
disko.devices.disk = { networking.hostId = "9af535e4";
disko.devices = {
disk = {
"${diskName}" = { "${diskName}" = {
device = "/dev/sda"; device = nixosDisk;
type = "disk"; type = "disk";
content = { content = {
type = "gpt"; type = "gpt";
@ -91,8 +151,8 @@ in
}; };
}; };
}; };
zroot1 = { zroot-disk1 = {
device = "/dev/sdb"; device = zrootDisk1Disk;
type = "disk"; type = "disk";
content = { content = {
type = "gpt"; type = "gpt";
@ -108,7 +168,7 @@ in
}; };
}; };
}; };
devices.lvm_vg = { lvm_vg = {
"${volumeGroupName}" = { "${volumeGroupName}" = {
type = "lvm_vg"; type = "lvm_vg";
lvs = { lvs = {
@ -141,7 +201,7 @@ in
options = { options = {
encryption = "aes-256-gcm"; encryption = "aes-256-gcm";
keyformat = "passphrase"; keyformat = "passphrase";
keylocation = "file:///tmp/tempkey"; keylocation = "file:///run/secret/zroot.key";
}; };
}; };
"mailserver/vmail" = { "mailserver/vmail" = {
@ -158,11 +218,12 @@ in
type = "topology"; type = "topology";
vdev = [ vdev = [
{ {
members = [ "zroot1" ]; members = [ "zroot-disk1" ];
} }
]; ];
}; };
}; };
}; };
}; };
};
} }

View file

@ -95,6 +95,11 @@ in
fqdn = config.khscodes.networking.fqdn; fqdn = config.khscodes.networking.fqdn;
useUTF8FolderNames = true; useUTF8FolderNames = true;
certificateScheme = "acme"; certificateScheme = "acme";
fullTextSearch = {
enable = true;
autoIndex = true;
enforced = "body";
};
}; };
services.fail2ban.jails = { services.fail2ban.jails = {
postfix = { postfix = {

View file

@ -54,6 +54,8 @@ pub enum Endpoint {
Unifi, Unifi,
#[value(name = "vault")] #[value(name = "vault")]
Vault, Vault,
#[value(name = "mx.kaareskovgaard.net")]
MxKaareSkovgaardNet,
} }
impl Endpoint { impl Endpoint {
@ -83,6 +85,10 @@ impl Endpoint {
let data = VaultData::read_from_bao()?; let data = VaultData::read_from_bao()?;
Ok(data.into()) Ok(data.into())
} }
Self::MxKaareSkovgaardNet => {
let data = MxKaareSkovgaardNetData::read_from_bao()?;
Ok(data.into())
}
} }
} }
} }
@ -147,6 +153,13 @@ entry_definition!(
); );
entry_definition!(VaultDataConfig, VaultData, "vault", &["VAULT_TOKEN"]); entry_definition!(VaultDataConfig, VaultData, "vault", &["VAULT_TOKEN"]);
entry_definition!(
MxKaareSkovgaardNetDataConfig,
MxKaareSkovgaardNetData,
"mx.kaareskovgaard.net",
&["MX_KAARESKOVGAARD_NET_ZROOT_ENCRYPTION_KEY"]
);
fn transfer() -> anyhow::Result<()> { fn transfer() -> anyhow::Result<()> {
let openstack = OpenstackData::try_new_from_env()?; let openstack = OpenstackData::try_new_from_env()?;
let cloudflare = CloudflareData::try_new_from_env()?; let cloudflare = CloudflareData::try_new_from_env()?;
@ -154,6 +167,7 @@ fn transfer() -> anyhow::Result<()> {
let hcloud = HcloudData::try_new_from_env()?; let hcloud = HcloudData::try_new_from_env()?;
let unifi = UnifiData::try_new_from_env()?; let unifi = UnifiData::try_new_from_env()?;
let vault = VaultData::try_new_from_env()?; let vault = VaultData::try_new_from_env()?;
let mx_kaareskovgaard_net = MxKaareSkovgaardNetData::try_new_from_env()?;
write_kv_data(openstack)?; write_kv_data(openstack)?;
write_kv_data(cloudflare)?; write_kv_data(cloudflare)?;
@ -161,6 +175,7 @@ fn transfer() -> anyhow::Result<()> {
write_kv_data(hcloud)?; write_kv_data(hcloud)?;
write_kv_data(unifi)?; write_kv_data(unifi)?;
write_kv_data(vault)?; write_kv_data(vault)?;
write_kv_data(mx_kaareskovgaard_net)?;
Ok(()) Ok(())
} }