Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nixos/flood: init #269726

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lib.mdDoc is now just an alias and can be safely removed everywhere.
see d36f950 and #237557


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;
Copy link
Contributor

@rasmus-kirk rasmus-kirk Jan 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Passing in secret values through options is bad practice, as they will leak out into the nix store. So this is not really ideal. Unfortunately, Flood does not seem to have any way of loading in these options declaratively without using the command line options. I am currently trying to ask on the Flood Discord if there is a current way or how a PR that addresses this would be structured.

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}";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The technically correct one:

Suggested change
ExecStart = "${cfg.package}/bin/flood ${lib.concatStringsSep " " args}";
ExecStart = "${cfg.package}/bin/flood ${lib.escapeShellArgs " " args}";

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I now know that the correct one is utils.escapeSystemdExecArgs.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

${lib.getExe cfg.package} would be nice.

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