diff --git a/modules/build-types/server.nix b/modules/build-types/server.nix index b1fb6d1..fdce1af 100644 --- a/modules/build-types/server.nix +++ b/modules/build-types/server.nix @@ -1,12 +1,74 @@ -{ vars, lib, ... }: +{ vars, lib, pkgs, ... }: +let + poolName = lib.removePrefix "/" vars.storageRoot; + + # For each NFS share subpath, generate every ancestor path so ZFS datasets + # are created parent-first. e.g. "docker/config" → ["docker" "docker/config"] + ancestors = path: + let parts = lib.splitString "/" path; + in lib.imap1 (i: _: lib.concatStringsSep "/" (lib.take i parts)) parts; + + poolDatasets = lib.unique ( + lib.concatMap (share: ancestors share.subpath) (lib.attrValues vars.nfsShares) + ); +in { imports = [ ../beszel/enable-agent.nix ../services/zfs/enable-service.nix ]; - boot.zfs.extraPools = [ (lib.removePrefix "/" vars.storageRoot) ]; + boot.zfs.extraPools = [ poolName ]; + + # On a fresh image deploy the data disk (scsi1) starts blank — no pool + # exists yet, so zfs-import-tank.service would spin for 60 s and fail. + # This service runs first: if the pool is already present it exits instantly; + # otherwise it creates it (with all required datasets) so the standard + # import service finds it ready on the very first boot. + systemd.services."zfs-init-${poolName}" = { + description = "Initialize '${poolName}' ZFS pool on first boot if not present"; + wantedBy = [ "zfs-import-${poolName}.service" ]; + before = [ "zfs-import-${poolName}.service" ]; + after = [ "systemd-udev-settle.service" ]; + unitConfig.DefaultDependencies = false; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + path = [ pkgs.zfs_unstable ]; + script = '' + # Already imported — nothing to do. + if zpool list "${poolName}" >/dev/null 2>&1; then + exit 0 + fi + + # Pool exists on a device but not yet imported — let the standard + # zfs-import-${poolName}.service handle it normally. + if zpool import -d /dev/disk/by-id -N "${poolName}" 2>/dev/null; then + exit 0 + fi + + # No pool found at all. Create it on the Proxmox data disk (scsi1), + # which appears as /dev/disk/by-id/scsi-*drive-scsi1 inside the VM. + DATA_DISK="" + for candidate in /dev/disk/by-id/scsi-*drive-scsi1; do + [[ "$candidate" == *-part* ]] && continue + [ -b "$candidate" ] && DATA_DISK="$candidate" && break + done + + if [ -z "$DATA_DISK" ]; then + echo "zfs-init-${poolName}: no data disk found (expected /dev/disk/by-id/scsi-*drive-scsi1)" >&2 + exit 1 + fi + + echo "zfs-init-${poolName}: creating pool on $DATA_DISK" + zpool create -f "${poolName}" "$DATA_DISK" + ${lib.concatMapStrings (ds: '' + zfs create "${poolName}/${ds}" + '') poolDatasets} + ''; + }; systemd.services.nfs-server = { after = [ "zfs-mount.service" ];