From c751c1f9a963f6608c429e555e6b8e3d18a0d67d Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Sat, 23 Dec 2023 22:53:29 +0100 Subject: [PATCH 1/5] pyload-ng: add declarative user management patch --- .../pyload-ng/declarative-default-user.patch | 15 +++++++++++++++ .../applications/networking/pyload-ng/default.nix | 5 +++++ 2 files changed, 20 insertions(+) create mode 100644 pkgs/applications/networking/pyload-ng/declarative-default-user.patch diff --git a/pkgs/applications/networking/pyload-ng/declarative-default-user.patch b/pkgs/applications/networking/pyload-ng/declarative-default-user.patch new file mode 100644 index 000000000000..3c3e6f4ba724 --- /dev/null +++ b/pkgs/applications/networking/pyload-ng/declarative-default-user.patch @@ -0,0 +1,15 @@ +diff --git a/src/pyload/core/__init__.py b/src/pyload/core/__init__.py +index 4324fc700..f7fcd66ec 100644 +--- a/src/pyload/core/__init__.py ++++ b/src/pyload/core/__init__.py +@@ -46,8 +46,8 @@ class Exit(Exception): + # improve external scripts + class Core: + LOCALE_DOMAIN = APPID +- DEFAULT_USERNAME = APPID +- DEFAULT_PASSWORD = APPID ++ DEFAULT_USERNAME = os.getenv("PYLOAD_DEFAULT_USERNAME", APPID) ++ DEFAULT_PASSWORD = os.getenv("PYLOAD_DEFAULT_PASSWORD", APPID) + DEFAULT_DATADIR = os.path.join( + os.getenv("APPDATA") or USERHOMEDIR, "pyLoad" if os.name == "nt" else ".pyload" + ) diff --git a/pkgs/applications/networking/pyload-ng/default.nix b/pkgs/applications/networking/pyload-ng/default.nix index 4ead723f76e1..aa2a0bd592e4 100644 --- a/pkgs/applications/networking/pyload-ng/default.nix +++ b/pkgs/applications/networking/pyload-ng/default.nix @@ -10,6 +10,11 @@ python3.pkgs.buildPythonApplication rec { hash = "sha256-1lPIKkZESonDaVCnac0iUu/gCqXVDBhNZrk5S0eC6F0="; }; + patches = [ + # Makes it possible to change the default username/password in the module + ./declarative-default-user.patch + ]; + postPatch = '' # relax version bounds sed -i 's/\([A-z0-9]*\)~=.*$/\1/' setup.cfg From 0ccbca2746300e182ed5b8a72753535556ff9f14 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Sun, 24 Dec 2023 21:01:22 +0100 Subject: [PATCH 2/5] pyload-ng: add declarative configuration patch Unfortunately, their configuration file is a home-grown format, and not easy to parse through the usual UNIX tools. So introduce this patch to make it possible to modify various configuration options easily from the systemd service definition. --- .../pyload-ng/declarative-env-config.patch | 18 ++++++++++++++++++ .../networking/pyload-ng/default.nix | 3 +++ 2 files changed, 21 insertions(+) create mode 100644 pkgs/applications/networking/pyload-ng/declarative-env-config.patch diff --git a/pkgs/applications/networking/pyload-ng/declarative-env-config.patch b/pkgs/applications/networking/pyload-ng/declarative-env-config.patch new file mode 100644 index 000000000000..42f89ee485cb --- /dev/null +++ b/pkgs/applications/networking/pyload-ng/declarative-env-config.patch @@ -0,0 +1,18 @@ +diff --git a/src/pyload/core/__init__.py b/src/pyload/core/__init__.py +index 4324fc700..5d915a85e 100644 +--- a/src/pyload/core/__init__.py ++++ b/src/pyload/core/__init__.py +@@ -128,6 +128,13 @@ class Core: + else: + self._debug = max(0, int(debug)) + ++ # Allow setting any option declaratively, for the NixOS module ++ for env, value in os.environ.items(): ++ if not env.startswith("PYLOAD__"): ++ continue ++ section, opt = env.removeprefix("PYLOAD__").lower().split("__") ++ self.config.set(section, opt, value) ++ + # If no argument set, read storage dir from config file, + # otherwise save setting to config dir + if storagedir is None: diff --git a/pkgs/applications/networking/pyload-ng/default.nix b/pkgs/applications/networking/pyload-ng/default.nix index aa2a0bd592e4..eb0b6af94efe 100644 --- a/pkgs/applications/networking/pyload-ng/default.nix +++ b/pkgs/applications/networking/pyload-ng/default.nix @@ -13,6 +13,9 @@ python3.pkgs.buildPythonApplication rec { patches = [ # Makes it possible to change the default username/password in the module ./declarative-default-user.patch + # Makes it possible to change the configuration through environment variables + # in the NixOS module (aimed mostly at listen address/port) + ./declarative-env-config.patch ]; postPatch = '' From 70d0a6e54786994fb41f66796e20aff224028faa Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Sun, 24 Dec 2023 21:50:36 +0100 Subject: [PATCH 3/5] nixos/pyload: init --- nixos/modules/module-list.nix | 1 + nixos/modules/services/networking/pyload.nix | 147 +++++++++++++++++++ 2 files changed, 148 insertions(+) create mode 100644 nixos/modules/services/networking/pyload.nix diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index e6fffd4716de..13a06be5e2a5 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1056,6 +1056,7 @@ ./services/networking/openvpn.nix ./services/networking/ostinato.nix ./services/networking/owamp.nix + ./services/networking/pyload.nix ./services/networking/pdns-recursor.nix ./services/networking/pdnsd.nix ./services/networking/peroxide.nix diff --git a/nixos/modules/services/networking/pyload.nix b/nixos/modules/services/networking/pyload.nix new file mode 100644 index 000000000000..f2b85499d4dd --- /dev/null +++ b/nixos/modules/services/networking/pyload.nix @@ -0,0 +1,147 @@ +{ config, lib, pkgs, utils, ... }: +let + cfg = config.services.pyload; + + stateDir = "/var/lib/pyload"; +in +{ + meta.maintainers = with lib.maintainers; [ ambroisie ]; + + options = with lib; { + services.pyload = { + enable = mkEnableOption "pyLoad download manager"; + + package = mkPackageOption pkgs "pyLoad" { default = [ "pyload-ng" ]; }; + + listenAddress = mkOption { + type = types.str; + default = "localhost"; + example = "0.0.0.0"; + description = "Address to listen on for the web UI."; + }; + + port = mkOption { + type = types.port; + default = 8000; + example = 9876; + description = "Port to listen on for the web UI."; + }; + + downloadDirectory = mkOption { + type = types.path; + default = "${stateDir}/downloads"; + example = "/mnt/downloads"; + description = "Directory to store downloads."; + }; + + credentialsFile = mkOption { + type = with types; nullOr path; + default = null; + example = "/run/secrets/pyload-credentials.env"; + description = '' + File containing {env}`PYLOAD_DEFAULT_USERNAME` and + {env}`PYLOAD_DEFAULT_PASSWORD` in the format of an `EnvironmentFile=`, + as described by {manpage}`systemd.exec(5)`. + + If not given, they default to the username/password combo of + pyload/pyload. + ''; + }; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.tmpfiles.settings.pyload = { + ${cfg.downloadDirectory}.d = { }; + }; + + systemd.services.pyload = { + description = "pyLoad download manager"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + + # NOTE: unlike what the documentation says, it looks like `HOME` is not + # defined with this service definition... + # Since pyload tries to do the equivalent of `cd ~`, it needs to be able + # to resolve $HOME, which fails when `RootDirectory` is set. + # FIXME: check if `SetLoginEnvironment` fixes this issue in version 255 + environment = { + HOME = stateDir; + PYLOAD__WEBUI__HOST = cfg.listenAddress; + PYLOAD__WEBUI__PORT = builtins.toString cfg.port; + }; + + serviceConfig = { + ExecStart = utils.escapeSystemdExecArgs [ + (lib.getExe cfg.package) + "--userdir" + "${stateDir}/config" + "--storagedir" + cfg.downloadDirectory + ]; + + User = "pyload"; + Group = "pyload"; + DynamicUser = true; + + EnvironmentFile = lib.optional (cfg.credentialsFile != null) cfg.credentialsFile; + + StateDirectory = "pyload"; + WorkingDirectory = stateDir; + RuntimeDirectory = "pyload"; + RuntimeDirectoryMode = "0700"; + RootDirectory = "/run/pyload"; + BindReadOnlyPaths = [ + builtins.storeDir # Needed to run the python interpreter + ]; + BindPaths = [ + cfg.downloadDirectory + ]; + + # Hardening options + LockPersonality = true; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateMounts = true; + PrivateTmp = true; + PrivateUsers = true; + ProcSubset = "pid"; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProtectSystem = "strict"; + RemoveIPC = true; + RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX"; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ "@system-service" "~@resources" "~@privileged" ]; + UMask = "0002"; + CapabilityBoundingSet = [ + "~CAP_BLOCK_SUSPEND" + "~CAP_BPF" + "~CAP_CHOWN" + "~CAP_IPC_LOCK" + "~CAP_KILL" + "~CAP_LEASE" + "~CAP_LINUX_IMMUTABLE" + "~CAP_NET_ADMIN" + "~CAP_SYS_ADMIN" + "~CAP_SYS_BOOT" + "~CAP_SYS_CHROOT" + "~CAP_SYS_NICE" + "~CAP_SYS_PACCT" + "~CAP_SYS_PTRACE" + "~CAP_SYS_RESOURCE" + "~CAP_SYS_TTY_CONFIG" + ]; + }; + }; + }; +} From 60518d6a5245bbd48b0d043549d6e22209474873 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Sun, 24 Dec 2023 22:23:15 +0100 Subject: [PATCH 4/5] nixos/pyload: init test --- nixos/tests/all-tests.nix | 1 + nixos/tests/pyload.nix | 33 +++++++++++++++++++ .../networking/pyload-ng/default.nix | 24 +++++++++----- 3 files changed, 49 insertions(+), 9 deletions(-) create mode 100644 nixos/tests/pyload.nix diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 25ee587e8f7a..fc1f047d42cf 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -387,6 +387,7 @@ in { installed-tests = pkgs.recurseIntoAttrs (handleTest ./installed-tests {}); invidious = handleTest ./invidious.nix {}; livebook-service = handleTest ./livebook-service.nix {}; + pyload = handleTest ./pyload.nix {}; oci-containers = handleTestOn ["aarch64-linux" "x86_64-linux"] ./oci-containers.nix {}; odoo = handleTest ./odoo.nix {}; odoo15 = handleTest ./odoo.nix { package = pkgs.odoo15; }; diff --git a/nixos/tests/pyload.nix b/nixos/tests/pyload.nix new file mode 100644 index 000000000000..f913e822b2ff --- /dev/null +++ b/nixos/tests/pyload.nix @@ -0,0 +1,33 @@ +import ./make-test-python.nix ({ lib, ... }: { + name = "pyload"; + meta.maintainers = with lib.maintainers; [ ambroisie ]; + + nodes = { + machine = { ... }: { + services.pyload = { + enable = true; + + listenAddress = "0.0.0.0"; + port = 9876; + }; + + networking.firewall.allowedTCPPorts = [ 9876 ]; + }; + + client = { }; + }; + + testScript = '' + start_all() + + machine.wait_for_unit("pyload.service") + + with subtest("Web interface accessible locally"): + machine.wait_until_succeeds("curl -fs localhost:9876") + + client.wait_for_unit("network.target") + + with subtest("Web interface accessible from a different machine"): + client.wait_until_succeeds("curl -fs machine:9876") + ''; +}) diff --git a/pkgs/applications/networking/pyload-ng/default.nix b/pkgs/applications/networking/pyload-ng/default.nix index eb0b6af94efe..1f638d43daef 100644 --- a/pkgs/applications/networking/pyload-ng/default.nix +++ b/pkgs/applications/networking/pyload-ng/default.nix @@ -1,4 +1,4 @@ -{ lib, fetchPypi, python3 }: +{ lib, fetchPypi, nixosTests, python3 }: python3.pkgs.buildPythonApplication rec { version = "0.5.0b3.dev75"; @@ -43,14 +43,20 @@ python3.pkgs.buildPythonApplication rec { setuptools ]; - passthru.optional-dependencies = { - plugins = with python3.pkgs; [ - beautifulsoup4 # for some plugins - colorlog # colorful console logging - pillow # for some CAPTCHA plugin - send2trash # send some files to trash instead of deleting them - slixmpp # XMPP plugin - ]; + passthru = { + optional-dependencies = { + plugins = with python3.pkgs; [ + beautifulsoup4 # for some plugins + colorlog # colorful console logging + pillow # for some CAPTCHA plugin + send2trash # send some files to trash instead of deleting them + slixmpp # XMPP plugin + ]; + }; + + tests = { + inherit (nixosTests) pyload; + }; }; meta = with lib; { From e837f4623dae23f7fa03120ee52740fd45bee682 Mon Sep 17 00:00:00 2001 From: Bruno BELANYI Date: Sun, 24 Dec 2023 22:33:14 +0100 Subject: [PATCH 5/5] nixos/pyload: document new module --- nixos/doc/manual/release-notes/rl-2405.section.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nixos/doc/manual/release-notes/rl-2405.section.md b/nixos/doc/manual/release-notes/rl-2405.section.md index 9e8ef49783ca..61dc43e8f59c 100644 --- a/nixos/doc/manual/release-notes/rl-2405.section.md +++ b/nixos/doc/manual/release-notes/rl-2405.section.md @@ -24,6 +24,8 @@ In addition to numerous new and upgraded packages, this release has the followin - [Guix](https://guix.gnu.org), a functional package manager inspired by Nix. Available as [services.guix](#opt-services.guix.enable). +- [pyLoad](https://pyload.net/), a FOSS download manager written in Python. Available as [services.pyload](#opt-services.pyload.enable) + - [maubot](https://github.com/maubot/maubot), a plugin-based Matrix bot framework. Available as [services.maubot](#opt-services.maubot.enable). - systemd's gateway, upload, and remote services, which provides ways of sending journals across the network. Enable using [services.journald.gateway](#opt-services.journald.gateway.enable), [services.journald.upload](#opt-services.journald.upload.enable), and [services.journald.remote](#opt-services.journald.remote.enable).