diff --git a/module.nix b/module.nix index 6b13439..2bb19b7 100644 --- a/module.nix +++ b/module.nix @@ -51,6 +51,17 @@ let 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 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; mkBazarrProviderSection = @@ -525,8 +543,9 @@ in "${inst.serviceName}.service" ] ++ (getSyncedAppDeps inst) + ++ (getDownloadClientDeps inst) ++ (lib.optional (inst.networkNamespacePath != null) "wg.service"); - requires = [ "${inst.serviceName}.service" ]; + requires = [ "${inst.serviceName}.service" ] ++ (getDownloadClientDeps inst); wantedBy = [ "multi-user.target" ]; serviceConfig = { Type = "oneshot"; diff --git a/tests/health-checks.nix b/tests/health-checks.nix index 795e1f1..f691527 100644 --- a/tests/health-checks.nix +++ b/tests/health-checks.nix @@ -88,10 +88,6 @@ pkgs.testers.runNixOSTest { { description = "Mock qBittorrent API"; wantedBy = [ "multi-user.target" ]; - before = [ - "sonarr-init.service" - "radarr-init.service" - ]; serviceConfig = { ExecStart = "${pkgs.python3}/bin/python3 ${mockQbitScript}"; Type = "simple"; @@ -131,6 +127,7 @@ pkgs.testers.runNixOSTest { implementation = "QBittorrent"; configContract = "QBittorrentSettings"; protocol = "torrent"; + serviceName = "mock-qbittorrent"; fields = { host = "127.0.0.1"; port = 6011; @@ -154,6 +151,7 @@ pkgs.testers.runNixOSTest { implementation = "QBittorrent"; configContract = "QBittorrentSettings"; protocol = "torrent"; + serviceName = "mock-qbittorrent"; fields = { host = "127.0.0.1"; port = 6011; @@ -272,27 +270,27 @@ pkgs.testers.runNixOSTest { assert "health check" in prowlarr_journal.lower() or "testing" in prowlarr_journal.lower(), \ "Expected health check log messages in prowlarr-init journal" - with subtest("Health check fails when download client is unreachable"): - # Stop mock qBittorrent to simulate failure + with subtest("Init service stops when download client service stops (Requires dependency)"): + # 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") - # Restart sonarr-init - it should FAIL because download client test will fail - machine.execute("systemctl restart sonarr-init.service") - - # Wait for the service to settle into failed state (it has Restart=on-failure) + # sonarr-init should be inactive (pulled down by dependency) machine.wait_until_succeeds( - "systemctl show sonarr-init.service --property=Result | grep -q 'exit-code'", - timeout=60, + "systemctl show sonarr-init.service --property=ActiveState | grep -q 'inactive'", + timeout=30, ) - # Check journal for health check failure message - journal = machine.succeed("journalctl -u sonarr-init.service --no-pager") - assert "health check failed" in journal.lower(), \ - "Expected health check failure message in sonarr-init journal, got: " + journal[-500:] + # radarr-init should also be inactive (same Requires dependency) + machine.wait_until_succeeds( + "systemctl show radarr-init.service --property=ActiveState | grep -q 'inactive'", + timeout=30, + ) with subtest("Health check fails when Prowlarr synced app is unreachable"): - # Sonarr is already stopped from previous subtest - # Also stop radarr to ensure both synced apps are unreachable + # Stop radarr to ensure synced apps are unreachable + # (sonarr is already stopped since sonarr-init was pulled down above) machine.succeed("systemctl stop radarr.service") # Restart prowlarr-init - it should FAIL because synced app connectivity test fails