wait on qbt service before init

This commit is contained in:
2026-03-15 13:45:07 -04:00
parent 7c0a617640
commit ef0da7582c
2 changed files with 36 additions and 19 deletions

View File

@@ -51,6 +51,17 @@ let
tvCategory = "tvshows"; tvCategory = "tvshows";
}; };
}; };
serviceName = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = ''
Name of the systemd service for this download client.
When set, the init service will depend on (After + Requires) this service,
ensuring the download client is running before health checks execute.
'';
example = "qbittorrent";
};
}; };
}; };
@@ -424,6 +435,13 @@ let
# Get list of service names that syncedApps depend on # Get list of service names that syncedApps depend on
getSyncedAppDeps = inst: map (app: "${app.serviceName}.service") inst.syncedApps; getSyncedAppDeps = inst: map (app: "${app.serviceName}.service") inst.syncedApps;
# Get list of service names that download clients depend on
getDownloadClientDeps =
inst:
lib.concatMap (
dc: lib.optional (dc.serviceName != null) "${dc.serviceName}.service"
) inst.downloadClients;
enabledInstances = lib.filterAttrs (_: inst: inst.enable) cfg; enabledInstances = lib.filterAttrs (_: inst: inst.enable) cfg;
mkBazarrProviderSection = mkBazarrProviderSection =
@@ -525,8 +543,9 @@ in
"${inst.serviceName}.service" "${inst.serviceName}.service"
] ]
++ (getSyncedAppDeps inst) ++ (getSyncedAppDeps inst)
++ (getDownloadClientDeps inst)
++ (lib.optional (inst.networkNamespacePath != null) "wg.service"); ++ (lib.optional (inst.networkNamespacePath != null) "wg.service");
requires = [ "${inst.serviceName}.service" ]; requires = [ "${inst.serviceName}.service" ] ++ (getDownloadClientDeps inst);
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
serviceConfig = { serviceConfig = {
Type = "oneshot"; Type = "oneshot";

View File

@@ -88,10 +88,6 @@ pkgs.testers.runNixOSTest {
{ {
description = "Mock qBittorrent API"; description = "Mock qBittorrent API";
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
before = [
"sonarr-init.service"
"radarr-init.service"
];
serviceConfig = { serviceConfig = {
ExecStart = "${pkgs.python3}/bin/python3 ${mockQbitScript}"; ExecStart = "${pkgs.python3}/bin/python3 ${mockQbitScript}";
Type = "simple"; Type = "simple";
@@ -131,6 +127,7 @@ pkgs.testers.runNixOSTest {
implementation = "QBittorrent"; implementation = "QBittorrent";
configContract = "QBittorrentSettings"; configContract = "QBittorrentSettings";
protocol = "torrent"; protocol = "torrent";
serviceName = "mock-qbittorrent";
fields = { fields = {
host = "127.0.0.1"; host = "127.0.0.1";
port = 6011; port = 6011;
@@ -154,6 +151,7 @@ pkgs.testers.runNixOSTest {
implementation = "QBittorrent"; implementation = "QBittorrent";
configContract = "QBittorrentSettings"; configContract = "QBittorrentSettings";
protocol = "torrent"; protocol = "torrent";
serviceName = "mock-qbittorrent";
fields = { fields = {
host = "127.0.0.1"; host = "127.0.0.1";
port = 6011; port = 6011;
@@ -272,27 +270,27 @@ pkgs.testers.runNixOSTest {
assert "health check" in prowlarr_journal.lower() or "testing" in prowlarr_journal.lower(), \ assert "health check" in prowlarr_journal.lower() or "testing" in prowlarr_journal.lower(), \
"Expected health check log messages in prowlarr-init journal" "Expected health check log messages in prowlarr-init journal"
with subtest("Health check fails when download client is unreachable"): with subtest("Init service stops when download client service stops (Requires dependency)"):
# Stop mock qBittorrent to simulate failure # With serviceName set, sonarr-init has Requires=mock-qbittorrent.service.
# When mock-qbittorrent stops, sonarr-init should also be pulled down
# no spurious restart loops or alert spam.
machine.succeed("systemctl stop mock-qbittorrent.service") machine.succeed("systemctl stop mock-qbittorrent.service")
# Restart sonarr-init - it should FAIL because download client test will fail # sonarr-init should be inactive (pulled down by dependency)
machine.execute("systemctl restart sonarr-init.service")
# Wait for the service to settle into failed state (it has Restart=on-failure)
machine.wait_until_succeeds( machine.wait_until_succeeds(
"systemctl show sonarr-init.service --property=Result | grep -q 'exit-code'", "systemctl show sonarr-init.service --property=ActiveState | grep -q 'inactive'",
timeout=60, timeout=30,
) )
# Check journal for health check failure message # radarr-init should also be inactive (same Requires dependency)
journal = machine.succeed("journalctl -u sonarr-init.service --no-pager") machine.wait_until_succeeds(
assert "health check failed" in journal.lower(), \ "systemctl show radarr-init.service --property=ActiveState | grep -q 'inactive'",
"Expected health check failure message in sonarr-init journal, got: " + journal[-500:] timeout=30,
)
with subtest("Health check fails when Prowlarr synced app is unreachable"): with subtest("Health check fails when Prowlarr synced app is unreachable"):
# Sonarr is already stopped from previous subtest # Stop radarr to ensure synced apps are unreachable
# Also stop radarr to ensure both synced apps are unreachable # (sonarr is already stopped since sonarr-init was pulled down above)
machine.succeed("systemctl stop radarr.service") machine.succeed("systemctl stop radarr.service")
# Restart prowlarr-init - it should FAIL because synced app connectivity test fails # Restart prowlarr-init - it should FAIL because synced app connectivity test fails