Compare commits
2 Commits
8ed67464d0
...
f87e395225
| Author | SHA1 | Date | |
|---|---|---|---|
|
f87e395225
|
|||
|
9770e6d667
|
@@ -1,44 +1,10 @@
|
|||||||
{
|
{
|
||||||
lib,
|
lib,
|
||||||
pkgs,
|
pkgs,
|
||||||
|
inputs,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
# Mock qBittorrent - simple enough to keep mocked
|
|
||||||
mockQBittorrent = pkgs.writers.writePython3Bin "mock-qbt" { flakeIgnore = [ "E501" ]; } ''
|
|
||||||
import http.server
|
|
||||||
import socketserver
|
|
||||||
|
|
||||||
|
|
||||||
class Handler(http.server.BaseHTTPRequestHandler):
|
|
||||||
def log_message(self, fmt, *args):
|
|
||||||
print(f"qbt: {fmt % args}")
|
|
||||||
|
|
||||||
def do_GET(self):
|
|
||||||
if self.path == "/api/v2/transfer/speedLimitsMode":
|
|
||||||
self.send_response(200)
|
|
||||||
self.send_header("Content-type", "text/plain")
|
|
||||||
self.end_headers()
|
|
||||||
self.wfile.write(("1" if getattr(self.server, "alt_speed", False) else "0").encode())
|
|
||||||
else:
|
|
||||||
self.send_response(404)
|
|
||||||
self.end_headers()
|
|
||||||
|
|
||||||
def do_POST(self):
|
|
||||||
if self.path == "/api/v2/transfer/toggleSpeedLimitsMode":
|
|
||||||
self.server.alt_speed = not getattr(self.server, "alt_speed", False)
|
|
||||||
self.send_response(200)
|
|
||||||
self.end_headers()
|
|
||||||
else:
|
|
||||||
self.send_response(404)
|
|
||||||
self.end_headers()
|
|
||||||
|
|
||||||
|
|
||||||
with socketserver.TCPServer(("0.0.0.0", 8080), Handler) as s:
|
|
||||||
print("Mock qBittorrent on port 8080")
|
|
||||||
s.serve_forever()
|
|
||||||
'';
|
|
||||||
|
|
||||||
payloads = {
|
payloads = {
|
||||||
auth = pkgs.writeText "auth.json" (builtins.toJSON { Username = "jellyfin"; });
|
auth = pkgs.writeText "auth.json" (builtins.toJSON { Username = "jellyfin"; });
|
||||||
empty = pkgs.writeText "empty.json" (builtins.toJSON { });
|
empty = pkgs.writeText "empty.json" (builtins.toJSON { });
|
||||||
@@ -48,8 +14,48 @@ pkgs.testers.runNixOSTest {
|
|||||||
name = "jellyfin-qbittorrent-monitor";
|
name = "jellyfin-qbittorrent-monitor";
|
||||||
|
|
||||||
nodes = {
|
nodes = {
|
||||||
server = {
|
server =
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
inputs.vpn-confinement.nixosModules.default
|
||||||
|
];
|
||||||
|
|
||||||
services.jellyfin.enable = true;
|
services.jellyfin.enable = true;
|
||||||
|
|
||||||
|
# Real qBittorrent service
|
||||||
|
services.qbittorrent = {
|
||||||
|
enable = true;
|
||||||
|
webuiPort = 8080;
|
||||||
|
openFirewall = true;
|
||||||
|
|
||||||
|
serverConfig.LegalNotice.Accepted = true;
|
||||||
|
|
||||||
|
serverConfig.Preferences = {
|
||||||
|
WebUI = {
|
||||||
|
# Disable authentication for testing
|
||||||
|
AuthSubnetWhitelist = "0.0.0.0/0,::/0";
|
||||||
|
AuthSubnetWhitelistEnabled = true;
|
||||||
|
LocalHostAuth = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
Downloads = {
|
||||||
|
SavePath = "/var/lib/qbittorrent/downloads";
|
||||||
|
TempPath = "/var/lib/qbittorrent/incomplete";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
serverConfig.BitTorrent.Session = {
|
||||||
|
# Normal speed - unlimited
|
||||||
|
GlobalUPSpeedLimit = 0;
|
||||||
|
GlobalDLSpeedLimit = 0;
|
||||||
|
|
||||||
|
# Alternate speed limits for when Jellyfin is streaming
|
||||||
|
AlternativeGlobalUPSpeedLimit = 100;
|
||||||
|
AlternativeGlobalDLSpeedLimit = 100;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
curl
|
curl
|
||||||
ffmpeg
|
ffmpeg
|
||||||
@@ -71,10 +77,12 @@ pkgs.testers.runNixOSTest {
|
|||||||
prefixLength = 24;
|
prefixLength = 24;
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
systemd.services.mock-qbittorrent = {
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
# Create directories for qBittorrent
|
||||||
serviceConfig.ExecStart = lib.getExe mockQBittorrent;
|
systemd.tmpfiles.rules = [
|
||||||
};
|
"d /var/lib/qbittorrent/downloads 0755 qbittorrent qbittorrent"
|
||||||
|
"d /var/lib/qbittorrent/incomplete 0755 qbittorrent qbittorrent"
|
||||||
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
# Public test IP (RFC 5737 TEST-NET-3) so Jellyfin sees it as external
|
# Public test IP (RFC 5737 TEST-NET-3) so Jellyfin sees it as external
|
||||||
@@ -122,9 +130,12 @@ pkgs.testers.runNixOSTest {
|
|||||||
server.wait_for_unit("jellyfin.service")
|
server.wait_for_unit("jellyfin.service")
|
||||||
server.wait_for_open_port(8096)
|
server.wait_for_open_port(8096)
|
||||||
server.wait_until_succeeds("curl -sf http://localhost:8096/health | grep -q Healthy", timeout=60)
|
server.wait_until_succeeds("curl -sf http://localhost:8096/health | grep -q Healthy", timeout=60)
|
||||||
server.wait_for_unit("mock-qbittorrent.service")
|
server.wait_for_unit("qbittorrent.service")
|
||||||
server.wait_for_open_port(8080)
|
server.wait_for_open_port(8080)
|
||||||
|
|
||||||
|
# Wait for qBittorrent WebUI to be responsive
|
||||||
|
server.wait_until_succeeds("curl -sf http://localhost:8080/api/v2/app/version", timeout=30)
|
||||||
|
|
||||||
with subtest("Complete Jellyfin setup wizard"):
|
with subtest("Complete Jellyfin setup wizard"):
|
||||||
server.wait_until_succeeds(api_get("/Startup/Configuration"))
|
server.wait_until_succeeds(api_get("/Startup/Configuration"))
|
||||||
server.succeed(api_get("/Startup/FirstUser"))
|
server.succeed(api_get("/Startup/FirstUser"))
|
||||||
@@ -168,13 +179,13 @@ pkgs.testers.runNixOSTest {
|
|||||||
python = "${pkgs.python3.withPackages (ps: [ ps.requests ])}/bin/python"
|
python = "${pkgs.python3.withPackages (ps: [ ps.requests ])}/bin/python"
|
||||||
monitor = "${../services/jellyfin-qbittorrent-monitor.py}"
|
monitor = "${../services/jellyfin-qbittorrent-monitor.py}"
|
||||||
server.succeed(f"""
|
server.succeed(f"""
|
||||||
systemd-run --unit=monitor-test \\
|
systemd-run --unit=monitor-test \
|
||||||
--setenv=JELLYFIN_URL=http://localhost:8096 \\
|
--setenv=JELLYFIN_URL=http://localhost:8096 \
|
||||||
--setenv=JELLYFIN_API_KEY={token} \\
|
--setenv=JELLYFIN_API_KEY={token} \
|
||||||
--setenv=QBITTORRENT_URL=http://localhost:8080 \\
|
--setenv=QBITTORRENT_URL=http://localhost:8080 \
|
||||||
--setenv=CHECK_INTERVAL=1 \\
|
--setenv=CHECK_INTERVAL=1 \
|
||||||
--setenv=STREAMING_START_DELAY=1 \\
|
--setenv=STREAMING_START_DELAY=1 \
|
||||||
--setenv=STREAMING_STOP_DELAY=1 \\
|
--setenv=STREAMING_STOP_DELAY=1 \
|
||||||
{python} {monitor}
|
{python} {monitor}
|
||||||
""")
|
""")
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
@@ -274,12 +285,13 @@ pkgs.testers.runNixOSTest {
|
|||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
assert is_throttled(), "Should be throttled before qBittorrent restart"
|
assert is_throttled(), "Should be throttled before qBittorrent restart"
|
||||||
|
|
||||||
# Restart mock-qbittorrent (this resets alt_speed to False)
|
# Restart qBittorrent (this resets alt_speed to its config default - disabled)
|
||||||
server.succeed("systemctl restart mock-qbittorrent.service")
|
server.succeed("systemctl restart qbittorrent.service")
|
||||||
server.wait_for_unit("mock-qbittorrent.service")
|
server.wait_for_unit("qbittorrent.service")
|
||||||
server.wait_for_open_port(8080)
|
server.wait_for_open_port(8080)
|
||||||
|
server.wait_until_succeeds("curl -sf http://localhost:8080/api/v2/app/version", timeout=30)
|
||||||
|
|
||||||
# qBittorrent restarted - alt_speed is now False (default)
|
# qBittorrent restarted - alt_speed is now False (default on startup)
|
||||||
# The monitor should detect this and re-apply throttling
|
# The monitor should detect this and re-apply throttling
|
||||||
time.sleep(3) # Give monitor time to detect and re-apply
|
time.sleep(3) # Give monitor time to detect and re-apply
|
||||||
assert is_throttled(), "Monitor should re-apply throttling after qBittorrent restart"
|
assert is_throttled(), "Monitor should re-apply throttling after qBittorrent restart"
|
||||||
@@ -299,10 +311,11 @@ pkgs.testers.runNixOSTest {
|
|||||||
# Verify we're unthrottled (no active streams)
|
# Verify we're unthrottled (no active streams)
|
||||||
assert not is_throttled(), "Should be unthrottled before test"
|
assert not is_throttled(), "Should be unthrottled before test"
|
||||||
|
|
||||||
# Restart mock-qbittorrent
|
# Restart qBittorrent
|
||||||
server.succeed("systemctl restart mock-qbittorrent.service")
|
server.succeed("systemctl restart qbittorrent.service")
|
||||||
server.wait_for_unit("mock-qbittorrent.service")
|
server.wait_for_unit("qbittorrent.service")
|
||||||
server.wait_for_open_port(8080)
|
server.wait_for_open_port(8080)
|
||||||
|
server.wait_until_succeeds("curl -sf http://localhost:8080/api/v2/app/version", timeout=30)
|
||||||
|
|
||||||
# Give monitor time to check state
|
# Give monitor time to check state
|
||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
|
|||||||
Reference in New Issue
Block a user