This repository has been archived on 2026-04-18. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
server-config/services/bitmagnet.nix
Simon Gardling 19ea2dc02b
All checks were successful
Build and Deploy / deploy (push) Successful in 1m12s
prowlarr: handle bitmagnet restart
2026-04-12 21:30:08 -04:00

114 lines
3.8 KiB
Nix

{
pkgs,
service_configs,
config,
lib,
...
}:
let
prowlarrPort = toString service_configs.ports.private.prowlarr.port;
sonarrPort = toString service_configs.ports.private.sonarr.port;
radarrPort = toString service_configs.ports.private.radarr.port;
bitmagnetPort = toString service_configs.ports.private.bitmagnet.port;
bridgeAddr = config.vpnNamespaces.wg.bridgeAddress;
prowlarrConfigXml = "${service_configs.prowlarr.dataDir}/config.xml";
sonarrConfigXml = "${service_configs.sonarr.dataDir}/config.xml";
radarrConfigXml = "${service_configs.radarr.dataDir}/config.xml";
curl = "${pkgs.curl}/bin/curl";
jq = "${pkgs.jq}/bin/jq";
# Clears the escalating failure backoff for the Bitmagnet indexer across
# Prowlarr, Sonarr, and Radarr so searches resume immediately after
# Bitmagnet restarts instead of waiting hours for disable timers to expire.
recoveryScript = pkgs.writeShellScript "prowlarr-bitmagnet-recovery" ''
set -euo pipefail
wait_for() {
for _ in $(seq 1 "$2"); do
${curl} -sf --max-time 5 "$1" > /dev/null && return 0
sleep 5
done
echo "$1 not reachable, aborting" >&2; exit 1
}
# Test a Bitmagnet-named indexer to clear its failure status.
# A successful test triggers RecordSuccess() which resets the backoff.
clear_status() {
local key indexer
key=$(${lib.extractArrApiKey ''"$3"''}) || return 0
indexer=$(${curl} -sf --max-time 10 \
-H "X-Api-Key: $key" "$2/api/$1/indexer" | \
${jq} 'first(.[] | select(.name | test("Bitmagnet"; "i")))') || return 0
[ -n "$indexer" ] && [ "$indexer" != "null" ] || return 0
${curl} -sf --max-time 30 \
-H "X-Api-Key: $key" -H "Content-Type: application/json" \
-X POST "$2/api/$1/indexer/test" -d "$indexer" > /dev/null
}
wait_for "http://localhost:${bitmagnetPort}" 12
wait_for "http://localhost:${prowlarrPort}/ping" 6
# Prowlarr first downstream apps route searches through it.
clear_status v1 "http://localhost:${prowlarrPort}" "${prowlarrConfigXml}" || true
clear_status v3 "http://${bridgeAddr}:${sonarrPort}" "${sonarrConfigXml}" || true
clear_status v3 "http://${bridgeAddr}:${radarrPort}" "${radarrConfigXml}" || true
'';
in
{
imports = [
(lib.vpnNamespaceOpenPort service_configs.ports.private.bitmagnet.port "bitmagnet")
(lib.mkCaddyReverseProxy {
subdomain = "bitmagnet";
port = service_configs.ports.private.bitmagnet.port;
auth = true;
vpn = true;
})
];
services.bitmagnet = {
enable = true;
settings = {
postgres = {
host = service_configs.postgres.socket;
};
http_server = {
# TODO! make issue about this being a string and not a `port` type
port = ":" + (toString service_configs.ports.private.bitmagnet.port);
};
};
};
# The upstream default (Restart=on-failure) leaves Bitmagnet dead after
# clean exits (e.g. systemd stop during deploy). Always restart it.
systemd.services.bitmagnet.serviceConfig = {
Restart = lib.mkForce "always";
RestartSec = 10;
};
# After Bitmagnet restarts, clear the escalating failure backoff across
# Prowlarr, Sonarr, and Radarr so searches resume immediately instead of
# waiting hours for the disable timers to expire.
systemd.services.prowlarr-bitmagnet-recovery = {
description = "Clear Prowlarr/Sonarr/Radarr failure status for Bitmagnet indexer";
after = [
"bitmagnet.service"
"prowlarr.service"
"sonarr.service"
"radarr.service"
];
bindsTo = [ "bitmagnet.service" ];
wantedBy = [ "bitmagnet.service" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStart = recoveryScript;
# Same VPN namespace as Bitmagnet and Prowlarr.
NetworkNamespacePath = "/run/netns/wg";
};
};
}