-
-
Notifications
You must be signed in to change notification settings - Fork 14.7k
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
nixos/flood: init #269726
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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}"; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The technically correct one:
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, I now know that the correct one is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||
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 ]; | ||||||
}; | ||||||
} |
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() | ||
''; | ||
}) |
There was a problem hiding this comment.
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