Compare commits
No commits in common. "71b4792fdd1aae25cde021469cc2a48cdff68183" and "18651b63ed00ff5fce70c97f986f6a4f7e58822a" have entirely different histories.
71b4792fdd
...
18651b63ed
16 changed files with 309 additions and 997 deletions
|
@ -1,113 +0,0 @@
|
||||||
{
|
|
||||||
inputs,
|
|
||||||
lib,
|
|
||||||
pkgs,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
sharedModule = {
|
|
||||||
# Since it's common for CI not to have $DISPLAY available, explicitly disable graphics support
|
|
||||||
virtualisation.graphics = false;
|
|
||||||
};
|
|
||||||
diskMapping = {
|
|
||||||
disks = {
|
|
||||||
"disk1" = {
|
|
||||||
linuxDevice = "/dev/vdb";
|
|
||||||
size = 2048;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
template = "{id}";
|
|
||||||
};
|
|
||||||
diskMappingScript = pkgs.writeShellApplication {
|
|
||||||
name = "disk-mapping";
|
|
||||||
runtimeInputs = [ pkgs.util-linux ];
|
|
||||||
text = ''
|
|
||||||
df -h
|
|
||||||
lsblk
|
|
||||||
${lib.getExe' pkgs.uutils-coreutils-noprefix "mkdir"} -p /run/secret
|
|
||||||
echo ${lib.escapeShellArg (builtins.toJSON diskMapping)} > /run/secret/disk-mapping.json
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
diskMappingModule = {
|
|
||||||
systemd.services.disk-mapping = {
|
|
||||||
enable = true;
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
serviceConfig = {
|
|
||||||
Type = "oneshot";
|
|
||||||
RemainAfterExit = true;
|
|
||||||
ExecStart = lib.getExe diskMappingScript;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
systemd.services.khscodes-zpool-setup = {
|
|
||||||
after = [ "disk-mapping.service" ];
|
|
||||||
wants = [ "disk-mapping.service" ];
|
|
||||||
environment = {
|
|
||||||
LOGLEVEL = "trace";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in
|
|
||||||
pkgs.nixosTest {
|
|
||||||
name = "zfs";
|
|
||||||
nodes = {
|
|
||||||
machine =
|
|
||||||
{ ... }:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
inputs.self.nixosModules.systemd-boot
|
|
||||||
inputs.self.nixosModules.hetzner
|
|
||||||
inputs.self.nixosModules."fs/zfs"
|
|
||||||
inputs.self.nixosModules."networking/fqdn"
|
|
||||||
inputs.self.nixosModules."infrastructure/vault-server-approle"
|
|
||||||
inputs.self.nixosModules."infrastructure/provisioning"
|
|
||||||
inputs.self.nixosModules."infrastructure/openbao"
|
|
||||||
inputs.self.nixosModules."services/vault-agent"
|
|
||||||
inputs.self.nixosModules."services/read-vault-auth-from-userdata"
|
|
||||||
inputs.self.nixosModules."services/openssh"
|
|
||||||
inputs.self.nixosModules."virtualisation/qemu-guest"
|
|
||||||
inputs.disko.nixosModules.disko
|
|
||||||
sharedModule
|
|
||||||
diskMappingModule
|
|
||||||
{
|
|
||||||
virtualisation.emptyDiskImages = [ diskMapping.disks.disk1.size ];
|
|
||||||
khscodes.networking.fqdn = "machine";
|
|
||||||
networking.hostId = "deadbeef";
|
|
||||||
khscodes.fs.zfs = {
|
|
||||||
enable = true;
|
|
||||||
test = true;
|
|
||||||
zpools.zroot = {
|
|
||||||
vdevs = [
|
|
||||||
{
|
|
||||||
mode = "mirror";
|
|
||||||
members = [ "disk1" ];
|
|
||||||
}
|
|
||||||
];
|
|
||||||
datasets = {
|
|
||||||
"mailserver/vmail" = {
|
|
||||||
mountpoint = "/var/mailserver/vmail";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
];
|
|
||||||
system.stateVersion = "25.05";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
testScript = ''
|
|
||||||
machine.start()
|
|
||||||
machine.wait_for_unit("disk-mapping.service")
|
|
||||||
machine.wait_for_unit("khscodes-zpool-setup.service")
|
|
||||||
machine.succeed("zpool status zroot")
|
|
||||||
machine.succeed("df -h | grep /var/mailserver/vmail")
|
|
||||||
machine.succeed("echo 'test' > /var/mailserver/vmail/test")
|
|
||||||
# machine.succeed("systemctl restart khscodes-zpool-setup.service")
|
|
||||||
machine.shutdown()
|
|
||||||
machine.start()
|
|
||||||
machine.wait_for_unit("khscodes-zpool-setup.service")
|
|
||||||
machine.succeed("df -h | grep /var/mailserver/vmail")
|
|
||||||
machine.succeed('if [[ "$(cat /var/mailserver/vmail/test)" == "test" ]]; then exit 0; else exit 1; fi')
|
|
||||||
machine.succeed("systemctl restart khscodes-zpool-setup.service")
|
|
||||||
machine.reboot()
|
|
||||||
'';
|
|
||||||
}
|
|
|
@ -1,201 +0,0 @@
|
||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
pkgs,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
cfg = config.khscodes.fs.zfs;
|
|
||||||
isTest = cfg.test;
|
|
||||||
zpoolSetup = lib.getExe pkgs.khscodes.zpool-setup;
|
|
||||||
setupZpool =
|
|
||||||
{ name, value }:
|
|
||||||
let
|
|
||||||
enc = lib.strings.optionalString (!isTest) ''
|
|
||||||
|
|
||||||
--encryption-key-mount=${lib.escapeShellArg value.encryptionKeyOpenbao.mount} \
|
|
||||||
--encryption-key-name=${lib.escapeShellArg value.encryptionKeyOpenbao.name} \
|
|
||||||
--encryption-key-field=${lib.escapeShellArg value.encryptionKeyOpenbao.field} \
|
|
||||||
'';
|
|
||||||
in
|
|
||||||
''
|
|
||||||
${zpoolSetup} setup ${enc} \
|
|
||||||
--vdevs=${lib.escapeShellArg (builtins.toJSON value.vdevs)} \
|
|
||||||
--root-fs-options=${lib.escapeShellArg (builtins.toJSON value.rootFsOptions)} \
|
|
||||||
--zpool-options=${lib.escapeShellArg (builtins.toJSON value.zpoolOptions)} \
|
|
||||||
--datasets=${lib.escapeShellArg (builtins.toJSON value.datasets)} \
|
|
||||||
${lib.escapeShellArg name}
|
|
||||||
'';
|
|
||||||
setupZpools = lib.lists.map setupZpool (lib.attrsToList cfg.zpools);
|
|
||||||
vdevModule = lib.khscodes.mkSubmodule {
|
|
||||||
description = "vdev";
|
|
||||||
options = {
|
|
||||||
mode = lib.mkOption {
|
|
||||||
type = lib.types.enum [
|
|
||||||
"mirror"
|
|
||||||
"raidz"
|
|
||||||
"raidz1"
|
|
||||||
"raidz2"
|
|
||||||
"raidz3"
|
|
||||||
];
|
|
||||||
description = "Mode of the vdev";
|
|
||||||
default = "mirror";
|
|
||||||
};
|
|
||||||
members = lib.mkOption {
|
|
||||||
type = lib.types.listOf lib.types.str;
|
|
||||||
description = "Member disks of the vdev. Given as symbolic names, expected to be mapped to actual disks elsewhere.";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
datasetModule = lib.khscodes.mkSubmodule {
|
|
||||||
description = "dataset";
|
|
||||||
options = {
|
|
||||||
options = lib.mkOption {
|
|
||||||
description = "Options for the dataset";
|
|
||||||
type = lib.types.attrsOf lib.types.str;
|
|
||||||
default = { };
|
|
||||||
};
|
|
||||||
mountpoint = lib.mkOption {
|
|
||||||
type = lib.types.nullOr lib.types.str;
|
|
||||||
default = null;
|
|
||||||
description = "Path to mount the dataset to";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
zpoolModule = lib.khscodes.mkSubmodule {
|
|
||||||
description = "zpool";
|
|
||||||
options = {
|
|
||||||
vdevs = lib.mkOption {
|
|
||||||
type = lib.types.listOf vdevModule;
|
|
||||||
default = [ ];
|
|
||||||
};
|
|
||||||
encryptionKeyOpenbao = {
|
|
||||||
mount = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "opentofu";
|
|
||||||
description = "The mountpoint of the encryption key";
|
|
||||||
};
|
|
||||||
name = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
description = "The name of the encryption key in the mount";
|
|
||||||
default = config.khscodes.networking.fqdn;
|
|
||||||
};
|
|
||||||
field = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
description = "Field name of the encryption key";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
rootFsOptions = lib.mkOption {
|
|
||||||
type = lib.types.attrsOf lib.types.str;
|
|
||||||
default = { };
|
|
||||||
};
|
|
||||||
zpoolOptions = lib.mkOption {
|
|
||||||
type = lib.types.attrsOf lib.types.str;
|
|
||||||
default = { };
|
|
||||||
};
|
|
||||||
datasets = lib.mkOption {
|
|
||||||
type = lib.types.attrsOf datasetModule;
|
|
||||||
description = "Datasets for the zpool";
|
|
||||||
default = { };
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
options.khscodes.fs.zfs = {
|
|
||||||
enable = lib.mkEnableOption "Enables support for ZFS filesystem";
|
|
||||||
test = lib.mkOption {
|
|
||||||
type = lib.types.bool;
|
|
||||||
description = "Enables test mode. In test mode no encryption keys are needed and no additional setup is added for them";
|
|
||||||
default = false;
|
|
||||||
};
|
|
||||||
mainPoolName = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "zroot";
|
|
||||||
description = "The name of the main pool to create";
|
|
||||||
};
|
|
||||||
zpools = lib.mkOption {
|
|
||||||
type = lib.types.attrsOf zpoolModule;
|
|
||||||
description = "List of zpools and their layout";
|
|
||||||
default = {
|
|
||||||
"${cfg.mainPoolName}" = { };
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
config = lib.mkIf cfg.enable {
|
|
||||||
# TODO: Verify that each member disk is uniquely named, and exists somewhere?
|
|
||||||
assertions = lib.lists.map (
|
|
||||||
{ name, value }:
|
|
||||||
{
|
|
||||||
assertion = (lib.lists.length value.vdevs) > 0;
|
|
||||||
message = "Zpool ${name} contains no vdevs";
|
|
||||||
}
|
|
||||||
) (lib.attrsToList cfg.zpools);
|
|
||||||
boot.supportedFilesystems = {
|
|
||||||
zfs = true;
|
|
||||||
};
|
|
||||||
# On servers, we handle importing, creating and mounting of the pool manually.
|
|
||||||
boot.zfs = {
|
|
||||||
forceImportRoot = false;
|
|
||||||
requestEncryptionCredentials = false;
|
|
||||||
};
|
|
||||||
systemd.services.zfs-mount.enable = false;
|
|
||||||
systemd.services.zfs-import-zroot.enable = false;
|
|
||||||
systemd.services.khscodes-zpool-setup = {
|
|
||||||
after = [
|
|
||||||
"network-online.target"
|
|
||||||
];
|
|
||||||
wants = [
|
|
||||||
"network-online.target"
|
|
||||||
];
|
|
||||||
wantedBy = [
|
|
||||||
"multi-user.target"
|
|
||||||
];
|
|
||||||
environment = {
|
|
||||||
BAO_ADDR = config.khscodes.services.vault-agent.vault.address;
|
|
||||||
VAULT_ROLE_ID_FILE = "/var/lib/vault-agent/role-id";
|
|
||||||
VAULT_SECRET_ID_FILE = "/var/lib/vault-agent/secret-id";
|
|
||||||
DISK_MAPPING_FILE = "/run/secret/disk-mapping.json";
|
|
||||||
}
|
|
||||||
// (lib.attrsets.optionalAttrs isTest {
|
|
||||||
ZFS_TEST = "true";
|
|
||||||
});
|
|
||||||
unitConfig.ConditionPathExists = [
|
|
||||||
"/run/secret/disk-mapping.json"
|
|
||||||
]
|
|
||||||
++ lib.lists.optionals (!isTest) [
|
|
||||||
"/var/lib/vault-agent/role-id"
|
|
||||||
"/var/lib/vault-agent/secret-id"
|
|
||||||
];
|
|
||||||
serviceConfig = {
|
|
||||||
Type = "oneshot";
|
|
||||||
RemainAfterExit = true;
|
|
||||||
ExecStart = ''
|
|
||||||
${lib.strings.concatStringsSep "\n" setupZpools}
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
khscodes.infrastructure.vault-server-approle.policy = lib.mapAttrs' (name: value: {
|
|
||||||
name = "${value.encryptionKeyOpenbao.mount}/data/${value.encryptionKeyOpenbao.name}";
|
|
||||||
value = {
|
|
||||||
capabilities = [ "read" ];
|
|
||||||
};
|
|
||||||
}) cfg.zpools;
|
|
||||||
# Reading the disk setup through anopenbao secret allows
|
|
||||||
# the service to be restarted when adding new disks, or resizing existing disks.
|
|
||||||
khscodes.services.vault-agent.templates = [
|
|
||||||
{
|
|
||||||
contents = ''
|
|
||||||
{{- with secret "data-disks/data/${config.khscodes.networking.fqdn}" -}}
|
|
||||||
{{ .Data.data | toUnescapedJSON }}
|
|
||||||
{{- end -}}
|
|
||||||
'';
|
|
||||||
destination = "/run/secret/disk-mapping.json";
|
|
||||||
owner = "root";
|
|
||||||
group = "root";
|
|
||||||
perms = "0644";
|
|
||||||
restartUnits = [ "khscodes-zpool-setup.service" ];
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -6,18 +6,7 @@
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
cfg = config.khscodes.infrastructure.hetzner-instance;
|
cfg = config.khscodes.infrastructure.hetzner-instance;
|
||||||
mainConfig = config;
|
|
||||||
hasDisks = cfg.dataDisks != [ ];
|
hasDisks = cfg.dataDisks != [ ];
|
||||||
hasZfsDisk = lib.lists.foldl (acc: d: acc || d.zfs) false cfg.dataDisks;
|
|
||||||
diskZpools = lib.mkMerge (
|
|
||||||
lib.lists.map (d: {
|
|
||||||
"${d.zpoolName}".vdevs = [
|
|
||||||
{
|
|
||||||
members = [ d.name ];
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}) (lib.lists.filter (d: d.zfs) cfg.dataDisks)
|
|
||||||
);
|
|
||||||
fqdn = config.khscodes.networking.fqdn;
|
fqdn = config.khscodes.networking.fqdn;
|
||||||
provisioningUserData = config.khscodes.infrastructure.provisioning.instanceUserData;
|
provisioningUserData = config.khscodes.infrastructure.provisioning.instanceUserData;
|
||||||
locationFromDatacenter =
|
locationFromDatacenter =
|
||||||
|
@ -41,11 +30,6 @@ let
|
||||||
readOnly = true;
|
readOnly = true;
|
||||||
default = lib.khscodes.sanitize-terraform-name config.name;
|
default = lib.khscodes.sanitize-terraform-name config.name;
|
||||||
};
|
};
|
||||||
zfs = lib.mkEnableOption "Enables adding the disk to a zpool as its own vdev";
|
|
||||||
zpoolName = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = mainConfig.khscodes.fs.zfs.mainPoolName;
|
|
||||||
};
|
|
||||||
size = lib.mkOption {
|
size = lib.mkOption {
|
||||||
type = lib.types.int;
|
type = lib.types.int;
|
||||||
};
|
};
|
||||||
|
@ -400,8 +384,6 @@ in
|
||||||
capabilities = [ "read" ];
|
capabilities = [ "read" ];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
khscodes.fs.zfs.enable = lib.mkIf hasZfsDisk true;
|
|
||||||
khscodes.fs.zfs.zpools = diskZpools;
|
|
||||||
khscodes.infrastructure.provisioning = {
|
khscodes.infrastructure.provisioning = {
|
||||||
compute.modules = computeModules;
|
compute.modules = computeModules;
|
||||||
persistence.modules = persistenceModules;
|
persistence.modules = persistenceModules;
|
||||||
|
|
|
@ -4,9 +4,11 @@
|
||||||
inputs,
|
inputs,
|
||||||
}:
|
}:
|
||||||
(lib.khscodes.mkRust pkgs "${inputs.self}/rust").buildRustPackage {
|
(lib.khscodes.mkRust pkgs "${inputs.self}/rust").buildRustPackage {
|
||||||
crateName = "zpool-setup";
|
crateName = "disko-zpool-expand";
|
||||||
replacePath = true;
|
replacePath = true;
|
||||||
runtimeInputs = [
|
runtimeInputs = [
|
||||||
pkgs.zfs
|
pkgs.zfs
|
||||||
|
pkgs.cloud-utils
|
||||||
|
pkgs.uutils-coreutils-noprefix # Needed for readlink which growpart ends up calling
|
||||||
];
|
];
|
||||||
}
|
}
|
|
@ -6,7 +6,6 @@
|
||||||
(lib.khscodes.mkRust pkgs "${inputs.self}/rust").buildRustPackage {
|
(lib.khscodes.mkRust pkgs "${inputs.self}/rust").buildRustPackage {
|
||||||
crateName = "infrastructure";
|
crateName = "infrastructure";
|
||||||
runtimeInputs = [
|
runtimeInputs = [
|
||||||
pkgs.openssl # Needed when doing dkim keys
|
|
||||||
pkgs.openssh
|
pkgs.openssh
|
||||||
pkgs.openbao
|
pkgs.openbao
|
||||||
pkgs.khscodes.opentofu
|
pkgs.khscodes.opentofu
|
||||||
|
|
|
@ -14,11 +14,10 @@ in
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
"${inputs.self}/nix/profiles/nixos/hetzner-server.nix"
|
"${inputs.self}/nix/profiles/nixos/hetzner-server.nix"
|
||||||
./zfs.nix
|
./disko.nix
|
||||||
./mailserver
|
./mailserver
|
||||||
];
|
];
|
||||||
khscodes = {
|
khscodes = {
|
||||||
fs.zfs.zpools.zroot.encryptionKeyOpenbao.field = "MX_KAARESKOVGAARD_NET_ZROOT_ENCRYPTION_KEY";
|
|
||||||
infrastructure = {
|
infrastructure = {
|
||||||
hetzner-instance = {
|
hetzner-instance = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
@ -27,7 +26,6 @@ in
|
||||||
{
|
{
|
||||||
name = "mx.kaareskovgaard.net-zroot-disk1";
|
name = "mx.kaareskovgaard.net-zroot-disk1";
|
||||||
size = 10;
|
size = 10;
|
||||||
zfs = true;
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
server_type = "cax11";
|
server_type = "cax11";
|
||||||
|
|
139
nix/systems/aarch64-linux/mx.kaareskovgaard.net/disko.nix
Normal file
139
nix/systems/aarch64-linux/mx.kaareskovgaard.net/disko.nix
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
downloadZrootKey = pkgs.writeShellApplication {
|
||||||
|
name = "zfs-download-zroot-key";
|
||||||
|
runtimeInputs = [
|
||||||
|
pkgs.openbao
|
||||||
|
pkgs.zfs
|
||||||
|
pkgs.uutils-coreutils-noprefix
|
||||||
|
pkgs.jq
|
||||||
|
pkgs.gawk
|
||||||
|
];
|
||||||
|
text = ''
|
||||||
|
poolReady() {
|
||||||
|
pool="$1"
|
||||||
|
state="$(zpool import -d "/dev/disk/by-id" 2>/dev/null | awk "/pool: $pool/ { found = 1 }; /state:/ { if (found == 1) { print \$2; exit } }; END { if (found == 0) { print \"MISSING\" } }")"
|
||||||
|
if [[ "$state" = "ONLINE" ]]; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo "Pool $pool in state $state, waiting"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
poolImported() {
|
||||||
|
pool="$1"
|
||||||
|
zpool list "$pool" >/dev/null 2>/dev/null
|
||||||
|
}
|
||||||
|
poolImport() {
|
||||||
|
pool="$1"
|
||||||
|
zpool import -d "/dev/disk/by-id" -N "$pool"
|
||||||
|
}
|
||||||
|
if ! poolImported "zroot"; then
|
||||||
|
echo -n "importing ZFS pool \"zroot\"..."
|
||||||
|
# Loop across the import until it succeeds, because the devices needed may not be discovered yet.
|
||||||
|
for _ in $(seq 1 60); do
|
||||||
|
poolReady "zroot" && poolImport "zroot" && break
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
poolImported "zroot" || poolImport "zroot" # Try one last time, e.g. to import a degraded pool.
|
||||||
|
fi
|
||||||
|
if ! poolImported "zroot"; then
|
||||||
|
echo "Could not import zroot"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ "$(zfs list -j -o keystatus zroot/mailserver | jq --raw-output '.datasets."zroot/mailserver".properties.keystatus.value')" == "unavailable" ]]; then
|
||||||
|
# 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
|
||||||
|
fi
|
||||||
|
zfs mount -a
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
systemd.services = {
|
||||||
|
dovecot2 = {
|
||||||
|
after = [ "zfs-download-zroot-key.service" ];
|
||||||
|
wants = [ "zfs-download-zroot-key.service" ];
|
||||||
|
unitConfig.RequiresMountsFor = [
|
||||||
|
"/var/mailserver/vmail"
|
||||||
|
"/var/mailserver/indices"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
boot.supportedFilesystems = {
|
||||||
|
zfs = true;
|
||||||
|
};
|
||||||
|
boot.zfs = {
|
||||||
|
forceImportRoot = false;
|
||||||
|
requestEncryptionCredentials = false;
|
||||||
|
};
|
||||||
|
systemd.services.zfs-mount.enable = false;
|
||||||
|
systemd.services.zfs-import-zroot.enable = false;
|
||||||
|
fileSystems = {
|
||||||
|
"/var/mailserver/vmail" = {
|
||||||
|
enable = lib.mkForce false;
|
||||||
|
};
|
||||||
|
"/var/mailserver/indices" = {
|
||||||
|
enable = lib.mkForce false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
systemd.services.zfs-download-zroot-key = {
|
||||||
|
after = [
|
||||||
|
"network-online.target"
|
||||||
|
];
|
||||||
|
wants = [
|
||||||
|
"network-online.target"
|
||||||
|
];
|
||||||
|
wantedBy = [
|
||||||
|
"multi-user.target"
|
||||||
|
];
|
||||||
|
environment = {
|
||||||
|
BAO_ADDR = config.khscodes.services.vault-agent.vault.address;
|
||||||
|
};
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
RemainAfterExit = true;
|
||||||
|
ExecStart = lib.getExe downloadZrootKey;
|
||||||
|
};
|
||||||
|
unitConfig.ConditionPathExists = [
|
||||||
|
"/var/lib/vault-agent/role-id"
|
||||||
|
"/var/lib/vault-agent/secret-id"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
systemd.services.disko-zpool-expand-zroot = {
|
||||||
|
after = [ "zfs-download-zroot-key.service" ];
|
||||||
|
wants = [ "zfs-download-zroot-key.service" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
ExecStart = ''
|
||||||
|
${lib.getExe pkgs.khscodes.disko-zpool-expand} expand-zpool zroot
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
mailserver.mailDirectory = "/var/mailserver/vmail";
|
||||||
|
mailserver.indexDir = "/var/mailserver/indices";
|
||||||
|
khscodes.infrastructure.vault-server-approle.policy = {
|
||||||
|
"opentofu/data/mx.kaareskovgaard.net" = {
|
||||||
|
capabilities = [ "read" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
networking.hostId = "9af535e4";
|
||||||
|
}
|
|
@ -1,23 +0,0 @@
|
||||||
{
|
|
||||||
systemd.services = {
|
|
||||||
dovecot2 = {
|
|
||||||
after = [ "khscodes-zpool-setup.service" ];
|
|
||||||
wants = [ "khscodes-zpool-setup.service" ];
|
|
||||||
unitConfig.RequiresMountsFor = [
|
|
||||||
"/var/mailserver/vmail"
|
|
||||||
"/var/mailserver/indices"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
khscodes.fs.zfs.zpools.zroot.datasets = {
|
|
||||||
"mailserver/vmail" = {
|
|
||||||
mountpoint = "/var/mailserver/vmail";
|
|
||||||
};
|
|
||||||
"mailserver/indices" = {
|
|
||||||
mountpoint = "/var/mailserver/indices";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
mailserver.mailDirectory = "/var/mailserver/vmail";
|
|
||||||
mailserver.indexDir = "/var/mailserver/indices";
|
|
||||||
networking.hostId = "9af535e4";
|
|
||||||
}
|
|
172
rust/Cargo.lock
generated
172
rust/Cargo.lock
generated
|
@ -43,9 +43,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "0.6.20"
|
version = "0.6.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192"
|
checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"anstyle-parse",
|
"anstyle-parse",
|
||||||
|
@ -73,22 +73,22 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstyle-query"
|
name = "anstyle-query"
|
||||||
version = "1.1.4"
|
version = "1.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2"
|
checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.60.2",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstyle-wincon"
|
name = "anstyle-wincon"
|
||||||
version = "3.0.10"
|
version = "3.0.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a"
|
checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"once_cell_polyfill",
|
"once_cell_polyfill",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -126,9 +126,9 @@ checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.31"
|
version = "1.2.30"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2"
|
checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"shlex",
|
"shlex",
|
||||||
]
|
]
|
||||||
|
@ -147,9 +147,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.43"
|
version = "4.5.40"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "50fd97c9dc2399518aa331917ac6f274280ec5eb34e555dd291899745c48ec6f"
|
checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
|
@ -157,9 +157,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.5.43"
|
version = "4.5.40"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c35b5830294e1fa0462034af85cc95225a4cb07092c088c55bda3147cfcd8f65"
|
checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
|
@ -169,9 +169,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "4.5.41"
|
version = "4.5.40"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491"
|
checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
@ -270,6 +270,18 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "disko-zpool-expand"
|
||||||
|
version = "1.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"clap",
|
||||||
|
"common",
|
||||||
|
"hakari",
|
||||||
|
"log",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "displaydoc"
|
name = "displaydoc"
|
||||||
version = "0.2.5"
|
version = "0.2.5"
|
||||||
|
@ -344,7 +356,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
|
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -356,7 +368,7 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"libredox",
|
"libredox",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -625,9 +637,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libredox"
|
name = "libredox"
|
||||||
version = "0.1.9"
|
version = "0.1.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3"
|
checksum = "360e552c93fa0e8152ab463bc4c4837fce76a225df11dfaeea66c313de5e61f7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -847,7 +859,7 @@ dependencies = [
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -878,9 +890,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.142"
|
version = "1.0.140"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7"
|
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
@ -1066,28 +1078,13 @@ version = "0.9.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-link"
|
|
||||||
version = "0.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.59.0"
|
version = "0.59.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-targets 0.52.6",
|
"windows-targets",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-sys"
|
|
||||||
version = "0.60.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
|
||||||
dependencies = [
|
|
||||||
"windows-targets 0.53.3",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1096,31 +1093,14 @@ version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows_aarch64_gnullvm 0.52.6",
|
"windows_aarch64_gnullvm",
|
||||||
"windows_aarch64_msvc 0.52.6",
|
"windows_aarch64_msvc",
|
||||||
"windows_i686_gnu 0.52.6",
|
"windows_i686_gnu",
|
||||||
"windows_i686_gnullvm 0.52.6",
|
"windows_i686_gnullvm",
|
||||||
"windows_i686_msvc 0.52.6",
|
"windows_i686_msvc",
|
||||||
"windows_x86_64_gnu 0.52.6",
|
"windows_x86_64_gnu",
|
||||||
"windows_x86_64_gnullvm 0.52.6",
|
"windows_x86_64_gnullvm",
|
||||||
"windows_x86_64_msvc 0.52.6",
|
"windows_x86_64_msvc",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-targets"
|
|
||||||
version = "0.53.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
|
|
||||||
dependencies = [
|
|
||||||
"windows-link",
|
|
||||||
"windows_aarch64_gnullvm 0.53.0",
|
|
||||||
"windows_aarch64_msvc 0.53.0",
|
|
||||||
"windows_i686_gnu 0.53.0",
|
|
||||||
"windows_i686_gnullvm 0.53.0",
|
|
||||||
"windows_i686_msvc 0.53.0",
|
|
||||||
"windows_x86_64_gnu 0.53.0",
|
|
||||||
"windows_x86_64_gnullvm 0.53.0",
|
|
||||||
"windows_x86_64_msvc 0.53.0",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1129,96 +1109,48 @@ version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_aarch64_gnullvm"
|
|
||||||
version = "0.53.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_msvc"
|
name = "windows_aarch64_msvc"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_aarch64_msvc"
|
|
||||||
version = "0.53.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnu"
|
name = "windows_i686_gnu"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_gnu"
|
|
||||||
version = "0.53.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnullvm"
|
name = "windows_i686_gnullvm"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_gnullvm"
|
|
||||||
version = "0.53.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_msvc"
|
name = "windows_i686_msvc"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_msvc"
|
|
||||||
version = "0.53.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnu"
|
name = "windows_x86_64_gnu"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_gnu"
|
|
||||||
version = "0.53.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnullvm"
|
name = "windows_x86_64_gnullvm"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_gnullvm"
|
|
||||||
version = "0.53.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_msvc"
|
name = "windows_x86_64_msvc"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_msvc"
|
|
||||||
version = "0.53.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "writeable"
|
name = "writeable"
|
||||||
version = "0.6.1"
|
version = "0.6.1"
|
||||||
|
@ -1333,9 +1265,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerovec"
|
name = "zerovec"
|
||||||
version = "0.11.4"
|
version = "0.11.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b"
|
checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"yoke",
|
"yoke",
|
||||||
"zerofrom",
|
"zerofrom",
|
||||||
|
@ -1381,15 +1313,3 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"simd-adler32",
|
"simd-adler32",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zpool-setup"
|
|
||||||
version = "1.0.0"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"clap",
|
|
||||||
"common",
|
|
||||||
"hakari",
|
|
||||||
"log",
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ publish = false
|
||||||
|
|
||||||
### BEGIN HAKARI SECTION
|
### BEGIN HAKARI SECTION
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anstream = { version = "0.6.20" }
|
anstream = { version = "0.6.19" }
|
||||||
base64 = { version = "0.22.1" }
|
base64 = { version = "0.22.1" }
|
||||||
libc = { version = "0.2.174", features = ["extra_traits"] }
|
libc = { version = "0.2.174", features = ["extra_traits"] }
|
||||||
log = { version = "0.4.27", default-features = false, features = ["std"] }
|
log = { version = "0.4.27", default-features = false, features = ["std"] }
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
[package]
|
[package]
|
||||||
name = "zpool-setup"
|
name = "disko-zpool-expand"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
metadata.crane.name = "zpool-setup"
|
metadata.crane.name = "disko-zpool-expand"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
117
rust/program/disko-zpool-expand/src/main.rs
Normal file
117
rust/program/disko-zpool-expand/src/main.rs
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::{collections::BTreeMap, path::PathBuf};
|
||||||
|
|
||||||
|
use anyhow::Context as _;
|
||||||
|
use clap::{Parser, Subcommand};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
common::entrypoint(program);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(version, about, long_about = None)]
|
||||||
|
pub struct Args {
|
||||||
|
#[command(subcommand)]
|
||||||
|
pub command: Commands,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
pub enum Commands {
|
||||||
|
/// Expands the partitions based on a zpool and brings the pool up to the new size.
|
||||||
|
#[command(name = "expand-zpool")]
|
||||||
|
ExpandZpool(ExpandZpool),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, clap::Args)]
|
||||||
|
pub struct ExpandZpool {
|
||||||
|
/// Name of the pool to expand
|
||||||
|
pool_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn program() -> anyhow::Result<()> {
|
||||||
|
let args = Args::parse();
|
||||||
|
match args.command {
|
||||||
|
Commands::ExpandZpool(pool) => expand_zpool(pool),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct ZpoolStatus {
|
||||||
|
pools: BTreeMap<String, ZpoolStatusPool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct ZpoolStatusPool {
|
||||||
|
state: Option<ZpoolState>,
|
||||||
|
vdevs: BTreeMap<String, ZpoolStatusVdev>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Deserialize, PartialEq)]
|
||||||
|
enum ZpoolState {
|
||||||
|
#[serde(rename = "ONLINE")]
|
||||||
|
Online,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct ZpoolStatusVdev {
|
||||||
|
vdevs: BTreeMap<String, ZpoolStatusVdevVdev>,
|
||||||
|
}
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct ZpoolStatusVdevVdev {
|
||||||
|
path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expand_zpool(p: ExpandZpool) -> anyhow::Result<()> {
|
||||||
|
let mut proc = common::proc::Command::new("zpool");
|
||||||
|
proc.args(["status", "--json", &p.pool_name]);
|
||||||
|
let result: ZpoolStatus = proc
|
||||||
|
.try_spawn_to_json()
|
||||||
|
.context("Could not get zpool status")?;
|
||||||
|
|
||||||
|
let pool = result
|
||||||
|
.pools
|
||||||
|
.get(&p.pool_name)
|
||||||
|
.context("Could not find requested pool in status output")?;
|
||||||
|
|
||||||
|
if !pool
|
||||||
|
.state
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|st| *st == ZpoolState::Online)
|
||||||
|
{
|
||||||
|
return Err(anyhow::format_err!("Zpool {} is not online", p.pool_name));
|
||||||
|
}
|
||||||
|
|
||||||
|
for vdev in pool.vdevs.values() {
|
||||||
|
for vdev in vdev.vdevs.values() {
|
||||||
|
let partition_dev = vdev.path.display().to_string();
|
||||||
|
let Some(dev) = partition_dev.strip_suffix("-part1") else {
|
||||||
|
return Err(anyhow::format_err!(
|
||||||
|
"Expected vdev path {} to end with -part1",
|
||||||
|
vdev.path.display()
|
||||||
|
));
|
||||||
|
};
|
||||||
|
let mut proc = common::proc::Command::new("growpart");
|
||||||
|
proc.args([dev, "1"]);
|
||||||
|
let (stdout, _stderr, status) = proc.spawn_into_parts()?;
|
||||||
|
if !status.success() && !stdout.starts_with("NOCHANGE: ") {
|
||||||
|
return Err(anyhow::format_err!(
|
||||||
|
"Could not resize partitin for {}, err: {stdout}",
|
||||||
|
vdev.path.display()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
// let name = partition_dev
|
||||||
|
// .split("/")
|
||||||
|
// .last()
|
||||||
|
// .expect("Should always have at least one element");
|
||||||
|
let mut proc = common::proc::Command::new("zpool");
|
||||||
|
proc.args(["online", "-e", &p.pool_name, &partition_dev]);
|
||||||
|
proc.try_spawn_to_string().with_context(|| {
|
||||||
|
format!(
|
||||||
|
"Could not bring zpool {} online with expand flag",
|
||||||
|
p.pool_name
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -1,93 +0,0 @@
|
||||||
use std::{borrow::Cow, collections::BTreeMap, path::PathBuf, str::FromStr};
|
|
||||||
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
use crate::disk_mapping::DiskMapping;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
|
|
||||||
pub enum VdevMode {
|
|
||||||
#[serde(rename = "mirror")]
|
|
||||||
Mirror,
|
|
||||||
#[serde(rename = "raidz")]
|
|
||||||
Raidz,
|
|
||||||
#[serde(rename = "raidz1")]
|
|
||||||
Raidz1,
|
|
||||||
#[serde(rename = "raidz2")]
|
|
||||||
Raidz2,
|
|
||||||
#[serde(rename = "raidz3")]
|
|
||||||
Raidz3,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VdevMode {
|
|
||||||
fn str(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
Self::Mirror => "mirror",
|
|
||||||
Self::Raidz => "raidz",
|
|
||||||
Self::Raidz1 => "raidz1",
|
|
||||||
Self::Raidz2 => "raidz2",
|
|
||||||
Self::Raidz3 => "raidz3",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
pub struct Vdev {
|
|
||||||
pub mode: VdevMode,
|
|
||||||
pub members: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Vdev {
|
|
||||||
pub fn cli_args(&self, disk_mapper: &DiskMapping) -> anyhow::Result<Vec<Cow<'static, str>>> {
|
|
||||||
let mut args = Vec::with_capacity(self.members.len() + 1);
|
|
||||||
if self.members.len() > 1 || self.mode != VdevMode::Mirror {
|
|
||||||
args.push(Cow::Borrowed(self.mode.str()));
|
|
||||||
}
|
|
||||||
for member in self.members.iter() {
|
|
||||||
let resolved = disk_mapper.resolve(member)?;
|
|
||||||
args.push(resolved.into());
|
|
||||||
}
|
|
||||||
Ok(args)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(transparent)]
|
|
||||||
pub struct Vdevs(pub Vec<Vdev>);
|
|
||||||
|
|
||||||
impl FromStr for Vdevs {
|
|
||||||
type Err = anyhow::Error;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
common::json::from_str(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(transparent)]
|
|
||||||
pub struct Options(pub BTreeMap<String, String>);
|
|
||||||
|
|
||||||
impl FromStr for Options {
|
|
||||||
type Err = anyhow::Error;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
common::json::from_str(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
pub struct Dataset {
|
|
||||||
pub options: Options,
|
|
||||||
pub mountpoint: Option<PathBuf>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
#[serde(transparent)]
|
|
||||||
pub struct Datasets(pub BTreeMap<String, Dataset>);
|
|
||||||
|
|
||||||
impl FromStr for Datasets {
|
|
||||||
type Err = anyhow::Error;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
common::json::from_str(s)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
use std::collections::BTreeMap;
|
|
||||||
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[serde(transparent)]
|
|
||||||
pub struct DiskMapping(DiskmappingFile);
|
|
||||||
|
|
||||||
impl DiskMapping {
|
|
||||||
pub fn resolve(&self, name: &str) -> anyhow::Result<String> {
|
|
||||||
let resolved = self
|
|
||||||
.0
|
|
||||||
.disks
|
|
||||||
.get(name)
|
|
||||||
.ok_or_else(|| anyhow::format_err!("No mapping for disk named {}", name))?;
|
|
||||||
|
|
||||||
Ok(self.0.template.execute(resolved.linux_device.as_str()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
struct DiskmappingFile {
|
|
||||||
disks: BTreeMap<String, Disk>,
|
|
||||||
template: DeviceTemplate,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
struct Disk {
|
|
||||||
#[serde(rename = "linuxDevice")]
|
|
||||||
linux_device: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[serde(transparent)]
|
|
||||||
struct DeviceTemplate(String);
|
|
||||||
|
|
||||||
impl DeviceTemplate {
|
|
||||||
pub fn execute(&self, name: &str) -> String {
|
|
||||||
self.0.replace("{id}", name)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,200 +0,0 @@
|
||||||
use serde::Deserialize;
|
|
||||||
use std::{collections::BTreeMap, path::PathBuf};
|
|
||||||
|
|
||||||
use anyhow::Context as _;
|
|
||||||
use clap::{Parser, Subcommand};
|
|
||||||
|
|
||||||
mod cli;
|
|
||||||
mod disk_mapping;
|
|
||||||
mod zfs;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
common::entrypoint(program);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
|
||||||
#[command(version, about, long_about = None)]
|
|
||||||
pub struct Args {
|
|
||||||
#[command(subcommand)]
|
|
||||||
pub command: Commands,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Subcommand)]
|
|
||||||
pub enum Commands {
|
|
||||||
/// Expands the partitions based on a zpool and brings the pool up to the new size.
|
|
||||||
#[command(name = "setup")]
|
|
||||||
Setup(SetupZpool),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, clap::Args)]
|
|
||||||
pub struct SetupZpool {
|
|
||||||
/// Openbao mount of the encryption key for the pool. Can only omit during test.
|
|
||||||
#[arg(long = "encryption-key-mount")]
|
|
||||||
encryption_key_mount: Option<String>,
|
|
||||||
|
|
||||||
/// Openbao name of the encryption key for the pool. Can only omit during test.
|
|
||||||
#[arg(long = "encryption-key-name")]
|
|
||||||
encryption_key_name: Option<String>,
|
|
||||||
|
|
||||||
/// Openbao name of the encryption field for the pool. Can only omit during test.
|
|
||||||
#[arg(long = "encryption-key-field")]
|
|
||||||
encryption_key_field: Option<String>,
|
|
||||||
|
|
||||||
/// Vdevs of the pool
|
|
||||||
#[arg(long = "vdevs")]
|
|
||||||
vdevs: cli::Vdevs,
|
|
||||||
|
|
||||||
/// Options of the pool
|
|
||||||
#[arg(long = "zpool-options")]
|
|
||||||
zpool_options: cli::Options,
|
|
||||||
|
|
||||||
/// Options of the root file system
|
|
||||||
#[arg(long = "root-fs-options")]
|
|
||||||
root_fs_options: cli::Options,
|
|
||||||
|
|
||||||
/// Datasets the pool should have
|
|
||||||
#[arg(long = "datasets")]
|
|
||||||
datasets: cli::Datasets,
|
|
||||||
|
|
||||||
/// Name of the pool to expand
|
|
||||||
pool_name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TempDir {
|
|
||||||
path: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TempDir {
|
|
||||||
pub fn try_new(template: &str) -> anyhow::Result<Self> {
|
|
||||||
let mut proc = common::proc::Command::new("mktemp");
|
|
||||||
proc.args(["-dt", template]);
|
|
||||||
let path: PathBuf = proc.try_spawn_to_string()?.into();
|
|
||||||
common::fs::create_dir_recursive(&path)?;
|
|
||||||
Ok(Self { path })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for TempDir {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
common::fs::remove_dir_recursive(&self.path).expect("Could not clean up after temp dir");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SetupZpool {
|
|
||||||
fn encryption_key(&self) -> anyhow::Result<String> {
|
|
||||||
let is_test = common::env::read_env("ZFS_TEST").is_ok_and(|t| t == "true");
|
|
||||||
if is_test {
|
|
||||||
return Ok(String::from("testtest"));
|
|
||||||
}
|
|
||||||
let role_id_file = common::env::read_path_env("VAULT_ROLE_ID_FILE")?;
|
|
||||||
let role_id = common::fs::read_to_string(&role_id_file)?;
|
|
||||||
let secret_id_file = common::env::read_path_env("VAULT_SECRET_ID_FILE")?;
|
|
||||||
let secret_id = common::fs::read_to_string(&secret_id_file)?;
|
|
||||||
let tmpdir = TempDir::try_new("zpool-setup.XXXXXX")?;
|
|
||||||
common::fs::write_file_string(
|
|
||||||
&tmpdir.path.join(".vault"),
|
|
||||||
"token_helper = \"/bin/true\"",
|
|
||||||
common::fs::user_only_file_permissions(),
|
|
||||||
)?;
|
|
||||||
let mut login_proc = common::proc::Command::new("bao");
|
|
||||||
login_proc.env("HOME", tmpdir.path.display().to_string());
|
|
||||||
login_proc.args(["write", "-field=token", "auth/approle/login"]);
|
|
||||||
login_proc.args([
|
|
||||||
format!("role_id={role_id}"),
|
|
||||||
format!("secret_id={secret_id}"),
|
|
||||||
]);
|
|
||||||
let vault_token = login_proc.try_spawn_to_string()?;
|
|
||||||
let (field, name, mount) = match (
|
|
||||||
self.encryption_key_field.as_deref(),
|
|
||||||
self.encryption_key_name.as_deref(),
|
|
||||||
self.encryption_key_mount.as_deref(),
|
|
||||||
) {
|
|
||||||
(Some(field), Some(name), Some(mount)) => (field, name, mount),
|
|
||||||
_ => {
|
|
||||||
return Err(anyhow::format_err!(
|
|
||||||
"Missing one of --encryption-key-mount, --encryption-key-name, --encryption-key-field"
|
|
||||||
));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let mut proc = common::proc::Command::new("bao");
|
|
||||||
proc.env("HOME", tmpdir.path.display().to_string());
|
|
||||||
proc.env_sensitive("VAULT_TOKEN", vault_token);
|
|
||||||
proc.args(["kv", "get"]);
|
|
||||||
proc.arg(format!("-field={field}"));
|
|
||||||
proc.arg(format!("-mount={mount}"));
|
|
||||||
proc.arg(name);
|
|
||||||
|
|
||||||
proc.try_spawn_to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn program() -> anyhow::Result<()> {
|
|
||||||
let args = Args::parse();
|
|
||||||
match args.command {
|
|
||||||
Commands::Setup(setup) => setup_zpool(setup),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct ZpoolStatus {
|
|
||||||
pools: BTreeMap<String, ZpoolStatusPool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct ZpoolStatusPool {
|
|
||||||
state: Option<ZpoolState>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Deserialize, PartialEq)]
|
|
||||||
enum ZpoolState {
|
|
||||||
#[serde(rename = "ONLINE")]
|
|
||||||
Online,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setup_zpool(p: SetupZpool) -> anyhow::Result<()> {
|
|
||||||
let disk_mapping_file = common::env::read_path_env("DISK_MAPPING_FILE")?;
|
|
||||||
let disk_mapping = common::fs::read_to_string(&disk_mapping_file)?;
|
|
||||||
let disk_mapping = common::json::from_str(&disk_mapping)?;
|
|
||||||
if !zfs::import_pool(&p.pool_name)? {
|
|
||||||
let encryption_key = p.encryption_key()?;
|
|
||||||
zfs::create_pool(&p, &disk_mapping, &encryption_key)?;
|
|
||||||
for (name, dataset) in p.datasets.0.iter() {
|
|
||||||
zfs::create_dataset_recursive(&p.pool_name, name, dataset)?;
|
|
||||||
}
|
|
||||||
zfs::mount_all(&p.pool_name)?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
let mut proc: common::proc::Command = common::proc::Command::new("zpool");
|
|
||||||
proc.args(["status", "--json", &p.pool_name]);
|
|
||||||
let result: ZpoolStatus = proc
|
|
||||||
.try_spawn_to_json()
|
|
||||||
.context("Could not get zpool status")?;
|
|
||||||
|
|
||||||
let pool = result
|
|
||||||
.pools
|
|
||||||
.get(&p.pool_name)
|
|
||||||
.context("Could not find requested pool in status output")?;
|
|
||||||
|
|
||||||
if !pool
|
|
||||||
.state
|
|
||||||
.as_ref()
|
|
||||||
.is_some_and(|st| *st == ZpoolState::Online)
|
|
||||||
{
|
|
||||||
return Err(anyhow::format_err!("Zpool {} is not online", p.pool_name));
|
|
||||||
}
|
|
||||||
|
|
||||||
for vdev in p.vdevs.0.iter() {
|
|
||||||
for member in vdev.members.iter() {
|
|
||||||
let resolved = disk_mapping.resolve(member)?;
|
|
||||||
zfs::resize_disk(&p.pool_name, &resolved)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if zfs::encryption_key_needs_load(&p.pool_name)? {
|
|
||||||
let encryption_key = p.encryption_key()?;
|
|
||||||
zfs::load_key(&p.pool_name, &encryption_key)?;
|
|
||||||
}
|
|
||||||
// TODO: Update pool options, and all fs options, and create missing datasets.
|
|
||||||
// Maybe for extranous datasets, set mountpoint=none ?
|
|
||||||
zfs::mount_all(&p.pool_name)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -1,174 +0,0 @@
|
||||||
use std::collections::BTreeMap;
|
|
||||||
|
|
||||||
use anyhow::Context as _;
|
|
||||||
use common::proc::Command;
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
use crate::{SetupZpool, cli::Dataset, disk_mapping::DiskMapping};
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, PartialEq)]
|
|
||||||
enum ZpoolState {
|
|
||||||
#[serde(rename = "ONLINE")]
|
|
||||||
Online,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn import_pool(name: &str) -> anyhow::Result<bool> {
|
|
||||||
// Test if the pool exists and is already imported
|
|
||||||
let mut exists_proc = Command::new("zpool");
|
|
||||||
exists_proc.args(["status", name]);
|
|
||||||
if exists_proc.try_spawn_to_bytes().is_ok() {
|
|
||||||
return Ok(true);
|
|
||||||
}
|
|
||||||
// Try to import the pool if it exists
|
|
||||||
let mut proc = Command::new("zpool");
|
|
||||||
proc.args(["import", name]);
|
|
||||||
|
|
||||||
let (_stdout, stderr, exit_code) = proc.spawn_into_parts()?;
|
|
||||||
if exit_code.success() {
|
|
||||||
return Ok(true);
|
|
||||||
}
|
|
||||||
if stderr.contains("no such pool available") {
|
|
||||||
// The pool doesn't exist
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
Err(anyhow::format_err!(
|
|
||||||
"Could not import pool {name}, stderr: {stderr}"
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_pool(
|
|
||||||
zpool: &SetupZpool,
|
|
||||||
disk_mapping: &DiskMapping,
|
|
||||||
encryption_key: &str,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
let mut proc = Command::new("zpool");
|
|
||||||
proc.args([
|
|
||||||
"create",
|
|
||||||
zpool.pool_name.as_str(),
|
|
||||||
"-m",
|
|
||||||
"none",
|
|
||||||
"-o",
|
|
||||||
"feature@device_removal=enabled",
|
|
||||||
"-o",
|
|
||||||
"feature@draid=enabled",
|
|
||||||
"-o",
|
|
||||||
"feature@raidz_expansion=enabled",
|
|
||||||
"-o",
|
|
||||||
"feature@zilsaxattr=enabled",
|
|
||||||
"-o",
|
|
||||||
"feature@zstd_compress=enabled",
|
|
||||||
"-o",
|
|
||||||
"cachefile=none",
|
|
||||||
]);
|
|
||||||
|
|
||||||
for (key, value) in zpool.zpool_options.0.iter() {
|
|
||||||
proc.args(["-o", &format!("{key}={value}")]);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (key, value) in zpool.root_fs_options.0.iter() {
|
|
||||||
proc.args(["-O", &format!("{key}={value}")]);
|
|
||||||
}
|
|
||||||
|
|
||||||
proc.args([
|
|
||||||
"-O",
|
|
||||||
"encryption=aes-256-gcm",
|
|
||||||
"-O",
|
|
||||||
"keyformat=passphrase",
|
|
||||||
"-O",
|
|
||||||
"keylocation=prompt",
|
|
||||||
]);
|
|
||||||
|
|
||||||
for vdev in zpool.vdevs.0.iter() {
|
|
||||||
proc.args(vdev.cli_args(disk_mapping)?.into_iter());
|
|
||||||
}
|
|
||||||
|
|
||||||
proc.stdin_string(encryption_key);
|
|
||||||
proc.try_spawn_to_bytes()?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_dataset_recursive(
|
|
||||||
pool_name: &str,
|
|
||||||
dataset_name: &str,
|
|
||||||
dataset: &Dataset,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
let mut proc = Command::new("zfs");
|
|
||||||
let name = format!("{pool_name}/{dataset_name}");
|
|
||||||
proc.args(["create", "-p", "-u"]);
|
|
||||||
if let Some(mountpoint) = dataset.mountpoint.as_deref() {
|
|
||||||
proc.arg("-o");
|
|
||||||
proc.arg(format!("mountpoint={}", mountpoint.display()));
|
|
||||||
}
|
|
||||||
for (key, value) in dataset.options.0.iter() {
|
|
||||||
proc.arg("-o");
|
|
||||||
proc.arg(format!("{key}={value}"));
|
|
||||||
}
|
|
||||||
|
|
||||||
proc.arg(name);
|
|
||||||
|
|
||||||
let _ = proc.try_spawn_to_bytes()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn mount_all(pool: &str) -> anyhow::Result<()> {
|
|
||||||
let mut proc = Command::new("zfs");
|
|
||||||
proc.args(["mount", "-R", pool]);
|
|
||||||
proc.try_spawn_to_bytes()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resize_disk(pool_name: &str, device: &str) -> anyhow::Result<()> {
|
|
||||||
let mut proc = Command::new("zpool");
|
|
||||||
proc.args(["online", "-e", pool_name, device]);
|
|
||||||
let _ = proc.try_spawn_to_bytes().with_context(|| {
|
|
||||||
format!("Could not bring zpool {pool_name} online with expand flag for device {device}",)
|
|
||||||
})?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_key(pool_name: &str, encryption_key: &str) -> anyhow::Result<()> {
|
|
||||||
let mut proc = Command::new("zfs");
|
|
||||||
proc.args(["load-key", "-r", "-L", "prompt", pool_name]);
|
|
||||||
proc.stdin_bytes(encryption_key);
|
|
||||||
proc.try_spawn_to_string()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn encryption_key_needs_load(pool_name: &str) -> anyhow::Result<bool> {
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct PoolEncStatus {
|
|
||||||
datasets: BTreeMap<String, PoolEncStatusDataset>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct PoolEncStatusDataset {
|
|
||||||
properties: PoolEncStatusDatasetProperties,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct PoolEncStatusDatasetProperties {
|
|
||||||
keystatus: PoolEncStatusDatasetProperty,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct PoolEncStatusDatasetProperty {
|
|
||||||
value: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// "$(zfs list -j -o keystatus zroot/mailserver | jq --raw-output '.datasets."zroot/mailserver".properties.keystatus.value')" == "unavailable"
|
|
||||||
let mut proc = Command::new("zfs");
|
|
||||||
proc.args(["list", "-j", "-o", "keystatus", pool_name]);
|
|
||||||
let json: PoolEncStatus = proc.try_spawn_to_json()?;
|
|
||||||
let pool = json
|
|
||||||
.datasets
|
|
||||||
.get(pool_name)
|
|
||||||
.ok_or_else(|| anyhow::format_err!("Pool {pool_name} not found in status output"))?;
|
|
||||||
|
|
||||||
Ok(pool
|
|
||||||
.properties
|
|
||||||
.keystatus
|
|
||||||
.value
|
|
||||||
.as_deref()
|
|
||||||
.is_some_and(|v| v == "unavailable"))
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue