zfs: fix qbittorrent

This commit is contained in:
Simon Gardling 2025-11-20 16:17:09 -05:00
parent ae5189b6c6
commit 223910744a
Signed by: titaniumtown
GPG Key ID: 9AB28AC10ECE533D
3 changed files with 67 additions and 50 deletions

99
lib.nix
View File

@ -69,67 +69,68 @@ inputs.nixpkgs.lib.extend (
serviceConfig = { serviceConfig = {
Type = "oneshot"; Type = "oneshot";
RemainAfterExit = true; RemainAfterExit = true;
ExecStart = lib.getExe ( ExecStart = [
pkgs.writeShellApplication { (lib.getExe (
name = "ensure-zfs-mounts-with-pool-${serviceName}"; pkgs.writeShellApplication {
runtimeInputs = with pkgs; [ name = "ensure-zfs-mounts-with-pool-${serviceName}-${zpool}";
gawk runtimeInputs = with pkgs; [
coreutils gawk
config.boot.zfs.package coreutils
]; config.boot.zfs.package
];
text = '' text = ''
set -euo pipefail set -euo pipefail
echo "Ensuring ZFS mounts for service: ${serviceName}" echo "Ensuring ZFS mounts for service: ${serviceName} (pool: ${zpool})"
echo "Directories: ${lib.strings.concatStringsSep ", " dirs}" echo "Directories: ${lib.strings.concatStringsSep ", " dirs}"
# Validate mounts exist (ensureZfsMounts already has proper PATH) # Validate mounts exist (ensureZfsMounts already has proper PATH)
${lib.getExe pkgs.ensureZfsMounts} ${lib.strings.concatStringsSep " " dirs} ${lib.getExe pkgs.ensureZfsMounts} ${lib.strings.concatStringsSep " " dirs}
# Additional runtime check: verify paths are on correct zpool # Additional runtime check: verify paths are on correct zpool
${lib.optionalString (zpool != "") '' ${lib.optionalString (zpool != "") ''
echo "Verifying ZFS mountpoints are on pool '${zpool}'..." echo "Verifying ZFS mountpoints are on pool '${zpool}'..."
if ! zfs_list_output=$(zfs list -H -o name,mountpoint 2>&1); then if ! zfs_list_output=$(zfs list -H -o name,mountpoint 2>&1); then
echo "ERROR: Failed to query ZFS datasets: $zfs_list_output" >&2 echo "ERROR: Failed to query ZFS datasets: $zfs_list_output" >&2
exit 1
fi
# This loop handles variable number of directories, shellcheck false positive
# shellcheck disable=SC2043
for target in ${lib.strings.concatStringsSep " " dirs}; do
echo "Checking: $target"
# Find dataset that has this mountpoint
dataset=$(echo "$zfs_list_output" | awk -v target="$target" '$2 == target {print $1; exit}')
if [ -z "$dataset" ]; then
echo "ERROR: No ZFS dataset found for mountpoint: $target" >&2
exit 1 exit 1
fi fi
# Extract pool name from dataset (first part before /) # shellcheck disable=SC2043
actual_pool=$(echo "$dataset" | cut -d'/' -f1) for target in ${lib.strings.concatStringsSep " " dirs}; do
echo "Checking: $target"
if [ "$actual_pool" != "${zpool}" ]; then # Find dataset that has this mountpoint
echo "ERROR: ZFS pool mismatch for $target" >&2 dataset=$(echo "$zfs_list_output" | awk -v target="$target" '$2 == target {print $1; exit}')
echo " Expected pool: ${zpool}" >&2
echo " Actual pool: $actual_pool" >&2
echo " Dataset: $dataset" >&2
exit 1
fi
echo "$target is on $dataset (pool: $actual_pool)" if [ -z "$dataset" ]; then
done echo "ERROR: No ZFS dataset found for mountpoint: $target" >&2
exit 1
fi
echo "All paths verified successfully on pool '${zpool}'" # Extract pool name from dataset (first part before /)
''} actual_pool=$(echo "$dataset" | cut -d'/' -f1)
echo "Mount validation completed for ${serviceName}" if [ "$actual_pool" != "${zpool}" ]; then
''; echo "ERROR: ZFS pool mismatch for $target" >&2
} echo " Expected pool: ${zpool}" >&2
); echo " Actual pool: $actual_pool" >&2
echo " Dataset: $dataset" >&2
exit 1
fi
echo "$target is on $dataset (pool: $actual_pool)"
done
echo "All paths verified successfully on pool '${zpool}'"
''}
echo "Mount validation completed for ${serviceName} (pool: ${zpool})"
'';
}
))
];
}; };
}; };

View File

@ -11,6 +11,9 @@
(lib.serviceMountWithZpool "qbittorrent" service_configs.zpool_hdds [ (lib.serviceMountWithZpool "qbittorrent" service_configs.zpool_hdds [
service_configs.torrents_path service_configs.torrents_path
config.services.qbittorrent.serverConfig.Preferences.Downloads.TempPath config.services.qbittorrent.serverConfig.Preferences.Downloads.TempPath
])
(lib.serviceMountWithZpool "qbittorrent" service_configs.zpool_ssds [
"${config.services.qbittorrent.profileDir}/qBittorrent" "${config.services.qbittorrent.profileDir}/qBittorrent"
]) ])
(lib.vpnNamespaceOpenPort config.services.qbittorrent.webuiPort "qbittorrent") (lib.vpnNamespaceOpenPort config.services.qbittorrent.webuiPort "qbittorrent")

View File

@ -21,6 +21,10 @@ testPkgs.testers.runNixOSTest {
# Test service with paths outside zpool (should fail assertion) # Test service with paths outside zpool (should fail assertion)
(lib.serviceMountWithZpool "invalid-service" "rpool2" [ "/mnt/rpool_data" ]) (lib.serviceMountWithZpool "invalid-service" "rpool2" [ "/mnt/rpool_data" ])
# Test multi-command logic: service with multiple serviceMountWithZpool calls
(lib.serviceMountWithZpool "multi-service" "rpool" [ "/mnt/rpool_data" ])
(lib.serviceMountWithZpool "multi-service" "rpool2" [ "/mnt/rpool2_data" ])
]; ];
virtualisation = { virtualisation = {
@ -56,6 +60,14 @@ testPkgs.testers.runNixOSTest {
ExecStart = lib.getExe pkgs.bash; ExecStart = lib.getExe pkgs.bash;
}; };
}; };
systemd.services."multi-service" = {
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStart = lib.getExe pkgs.bash;
};
};
}; };
testScript = '' testScript = ''
@ -105,6 +117,7 @@ testPkgs.testers.runNixOSTest {
assert "Expected pool: rpool2" in journal_output assert "Expected pool: rpool2" in journal_output
assert "Actual pool: rpool" in journal_output assert "Actual pool: rpool" in journal_output
print("SUCCESS: Runtime validation correctly detected zpool mismatch!") machine.succeed("systemctl start multi-service")
machine.succeed("systemctl is-active multi-service-mounts.service")
''; '';
} }