Skip to content

Commit

Permalink
nixos/flood: init
Browse files Browse the repository at this point in the history
  • Loading branch information
3JlOy-PYCCKUi committed Dec 5, 2023
1 parent c2273e1 commit aff5b3a
Show file tree
Hide file tree
Showing 6 changed files with 338 additions and 0 deletions.
2 changes: 2 additions & 0 deletions nixos/doc/manual/release-notes/rl-2405.section.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ In addition to numerous new and upgraded packages, this release has the followin

<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->

- [Flood](https://flood.js.org), a beautiful web UI for various torrent clients. Available as [services.flood](#opt-services.flood.enable).

- [Guix](https://guix.gnu.org), a functional package manager inspired by Nix. Available as [services.guix](#opt-services.guix.enable).

- [maubot](https://github.com/maubot/maubot), a plugin-based Matrix bot framework. Available as [services.maubot](#opt-services.maubot.enable).
Expand Down
1 change: 1 addition & 0 deletions nixos/modules/module-list.nix
Original file line number Diff line number Diff line change
Expand Up @@ -1220,6 +1220,7 @@
./services/system/zram-generator.nix
./services/torrent/deluge.nix
./services/torrent/flexget.nix
./services/torrent/flood.nix
./services/torrent/magnetico.nix
./services/torrent/opentracker.nix
./services/torrent/peerflix.nix
Expand Down
287 changes: 287 additions & 0 deletions nixos/modules/services/torrent/flood.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
{ config, lib, pkgs, ... }:

let
cfg = config.services.flood;
in
{
options = {
services = {
flood = {
enable = lib.mkEnableOption (lib.mdDoc "Flood daemon");

package = lib.mkPackageOptionMD pkgs "flood" { };

baseUrl = lib.mkOption {
type = lib.types.str;
default = "/";
description = lib.mdDoc ''
This URI will prefix all of Flood's HTTP requests.
'';
};

address = lib.mkOption {
type = lib.types.str;
default = "127.0.0.1";
description = lib.mdDoc ''
The host (address) that Flood should listen for web connections on.
'';
};

port = lib.mkOption {
type = lib.types.port;
default = 3000;
description = lib.mdDoc ''
The port that Flood should listen for web connections on.
'';
};

openFirewall = lib.mkOption {
default = false;
type = lib.types.bool;
description = lib.mdDoc ''
Whether to open the firewall for the port in
{option}`services.flood.port`.
'';
};

ssl = {
enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = lib.mdDoc ''
Enable SSL.
'';
};

key = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
description = lib.mdDoc ''
Absolute path to private key for SSL.
'';
};

cert = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
description = lib.mdDoc ''
Absolute path to fullchain cert for SSL.
'';
};
};

user = lib.mkOption {
type = lib.types.str;
default = "flood";
description = lib.mdDoc ''
User account under which flood runs.
'';
};

group = lib.mkOption {
type = lib.types.str;
default = "flood";
description = lib.mdDoc ''
Group under which flood runs.
'';
};

allowedPaths = lib.mkOption {
type = lib.types.listOf lib.types.path;
default = [ ];
description = lib.mdDoc ''
List of allowed paths for file operations.
'';
};

auth = {
deluge = {
host = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = lib.mdDoc ''
Host of Deluge RPC interface.
'';
};
port = lib.mkOption {
type = lib.types.nullOr lib.types.port;
default = null;
description = lib.mdDoc ''
Port of Deluge RPC interface.
'';
};
user = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = lib.mdDoc ''
Username of Deluge RPC interface.
'';
};
pass = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = lib.mdDoc ''
Password of Deluge RPC interface.
'';
};
};
rtorrent = {
host = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = lib.mdDoc ''
Host of rTorrent's SCGI interface.
'';
};
port = lib.mkOption {
type = lib.types.nullOr lib.types.port;
default = null;
description = lib.mdDoc ''
Port of rTorrent's SCGI interface.
'';
};
socket = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
description = lib.mdDoc ''
Path to rTorrent's SCGI unix socket.
'';
};
};
qbittorrent = {
url = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = lib.mdDoc ''
URL to qBittorrent Web API.
'';
};
user = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = lib.mdDoc ''
Username of qBittorrent Web API.
'';
};
pass = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = lib.mdDoc ''
Password of qBittorrent Web API.
'';
};
};
transmission = {
url = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = lib.mdDoc ''
URL to Transmission RPC interface.
'';
};
user = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = lib.mdDoc ''
Username of Transmission RPC interface.
'';
};
pass = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = lib.mdDoc ''
Password of Transmission RPC interface.
'';
};
};
};
};
};
};

config =
let
addAuthOpt = prefix: opt:
lib.mapAttrsToList
(k: v:
"--${prefix}${toString k} ${toString v}")
(lib.filterAttrs (k: v: !isNull v) opt);
delugeAuth = addAuthOpt "de" cfg.auth.deluge;
rtorrentAuth = addAuthOpt "rt" cfg.auth.rtorrent;
qbittorrentAuth = addAuthOpt "qb" cfg.auth.qbittorrent;
transmissionAuth = addAuthOpt "tr" cfg.auth.transmission;
authOpts = lib.lists.findSingle (x: x != [ ]) [ ] [ null ] [ delugeAuth rtorrentAuth qbittorrentAuth transmissionAuth ];
args = [
"--rundir %S/flood"
"--baseuri ${cfg.baseUrl}"
"--host ${cfg.address}"
"--port ${toString cfg.port}"
]
++ lib.optionals cfg.ssl.enable [ "--ssl" ]
++ lib.optionals (cfg.ssl.enable && (!isNull cfg.ssl.key)) [ "--sslkey ${cfg.ssl.key}" ]
++ lib.optionals (cfg.ssl.enable && (!isNull cfg.ssl.cert)) [ "--sslcert ${cfg.ssl.cert}" ]
++ lib.optionals (authOpts != [ ]) [ "--auth none" ]
++ authOpts
++ map (x: "--allowedpath ${toString x}") cfg.allowedPaths;
in
lib.mkIf cfg.enable {
assertions = [
{
assertion = authOpts != [ null ];
message = "Only one client authentication must be configured";
}
];
systemd.services.flood =
{
after = [ "network.target" ];
description = "Flood Daemon";
wantedBy = [ "multi-user.target" ];
path = [ pkgs.mediainfo ];
serviceConfig = {
ExecStart = "${cfg.package}/bin/flood ${lib.concatStringsSep " " args}";
Restart = "on-failure";
UMask = "077";
DynamicUser = true;
User = cfg.user;
Group = cfg.group;
StateDirectory = "flood";
ReadWritePaths = cfg.allowedPaths ++
lib.optionals (!isNull cfg.auth.rtorrent.socket) [ "-${cfg.auth.rtorrent.socket}" ];

AmbientCapabilities = [ "" ];
CapabilityBoundingSet = [ "" ];
DevicePolicy = "closed";
ProtectSystem = "full";
LockPersonality = true;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateTmp = true;
PrivateUsers = true;
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProcSubset = "pid";
ProtectProc = "invisible";
RemoveIPC = true;
RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = [
"~@cpu-emulation"
"~@debug"
"~@mount"
"~@obsolete"
"~@privileged"
"~@resources"
];
};
};
networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ cfg.port ];
};
}
1 change: 1 addition & 0 deletions nixos/tests/all-tests.nix
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ in {
firewall-nftables = handleTest ./firewall.nix { nftables = true; };
fish = handleTest ./fish.nix {};
flannel = handleTestOn ["x86_64-linux"] ./flannel.nix {};
flood = handleTest ./flood.nix {};
floorp = handleTest ./firefox.nix { firefoxPackage = pkgs.floorp; };
fluentd = handleTest ./fluentd.nix {};
fluidd = handleTest ./fluidd.nix {};
Expand Down
44 changes: 44 additions & 0 deletions nixos/tests/flood.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import ./make-test-python.nix ({ pkgs, lib, ... }:
let
address = "127.0.0.127";
port = 3030;
url = "${address}:${toString port}";
in
{
name = "flood";
meta = with lib.maintainers; {
maintainers = [ _3JlOy-PYCCKUi ];
};

nodes.machine = { pkgs, ... }: {
services.flood = {
enable = true;
inherit address port;
auth.rtorrent = {
host = "127.0.0.1";
port = 9999;
};
};
environment.systemPackages = [ pkgs.jq ];
};

testScript = ''
start_all()
machine.wait_for_unit("flood.service")
machine.wait_for_open_port(${toString port}, "${address}")
machine.succeed("ss -tlpn 'src = ${address}' | grep LISTEN | grep node")
machine.succeed("curl -sf '${url}' | grep Flood")
# https://github.com/jesec/flood/blob/5a0d2bec844fe5f2163588b308158179d23b6d87/server/routes/api/auth.ts#L212
machine.succeed("curl -sf -c /tmp/cookies 'http://${url}/api/auth/verify' | jq -e .username >&2")
# https://github.com/jesec/flood/blob/5a0d2bec844fe5f2163588b308158179d23b6d87/server/routes/api/client.ts#L19
status = machine.fail("curl -s --fail-with-body -b /tmp/cookies 'http://${url}/api/client/connection-test'")
machine.succeed(f"echo '{status}' | jq .isConnected | grep -P '^false$'")
machine.shutdown()
'';
})
3 changes: 3 additions & 0 deletions pkgs/applications/networking/p2p/flood/default.nix
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{ lib
, buildNpmPackage
, fetchFromGitHub
, nixosTests
}:

buildNpmPackage rec {
Expand All @@ -16,6 +17,8 @@ buildNpmPackage rec {

npmDepsHash = "sha256-XmDnvq+ni5TOf3UQFc4JvGI3LiGpjbrLAocRvrW8qgk=";

passthru.tests.flood = nixosTests.flood;

meta = with lib; {
description = "Modern web UI for various torrent clients with a Node.js backend and React frontend";
homepage = "https://flood.js.org";
Expand Down

0 comments on commit aff5b3a

Please sign in to comment.