diff --git a/nixos/modules/services/misc/moonraker.nix b/nixos/modules/services/misc/moonraker.nix index ae57aaa6d47..b75227effa0 100644 --- a/nixos/modules/services/misc/moonraker.nix +++ b/nixos/modules/services/misc/moonraker.nix @@ -79,6 +79,19 @@ in { for supported values. ''; }; + + allowSystemControl = mkOption { + type = types.bool; + default = false; + description = '' + Whether to allow Moonraker to perform system-level operations. + + Moonraker exposes APIs to perform system-level operations, such as + reboot, shutdown, and management of systemd units. See the + documentation + for details on what clients are able to do. + ''; + }; }; }; @@ -86,6 +99,13 @@ in { warnings = optional (cfg.settings ? update_manager) ''Enabling update_manager is not supported on NixOS and will lead to non-removable warnings in some clients.''; + assertions = [ + { + assertion = cfg.allowSystemControl -> config.security.polkit.enable; + message = "services.moonraker.allowSystemControl requires polkit to be enabled (security.polkit.enable)."; + } + ]; + users.users = optionalAttrs (cfg.user == "moonraker") { moonraker = { group = cfg.group; @@ -128,11 +148,31 @@ in { exec ${pkg}/bin/moonraker -c ${cfg.configDir}/moonraker-temp.cfg ''; + # Needs `ip` command + path = [ pkgs.iproute2 ]; + serviceConfig = { WorkingDirectory = cfg.stateDir; Group = cfg.group; User = cfg.user; }; }; + + security.polkit.extraConfig = lib.optionalString cfg.allowSystemControl '' + // nixos/moonraker: Allow Moonraker to perform system-level operations + // + // This was enabled via services.moonraker.allowSystemControl. + polkit.addRule(function(action, subject) { + if ((action.id == "org.freedesktop.systemd1.manage-units" || + action.id == "org.freedesktop.login1.power-off" || + action.id == "org.freedesktop.login1.power-off-multiple-sessions" || + action.id == "org.freedesktop.login1.reboot" || + action.id == "org.freedesktop.login1.reboot-multiple-sessions" || + action.id.startsWith("org.freedesktop.packagekit.")) && + subject.user == "${cfg.user}") { + return polkit.Result.YES; + } + }); + ''; }; } diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index b6311f856fa..8d54f78a948 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -308,6 +308,7 @@ in molly-brown = handleTest ./molly-brown.nix {}; mongodb = handleTest ./mongodb.nix {}; moodle = handleTest ./moodle.nix {}; + moonraker = handleTest ./moonraker.nix {}; morty = handleTest ./morty.nix {}; mosquitto = handleTest ./mosquitto.nix {}; moosefs = handleTest ./moosefs.nix {}; diff --git a/nixos/tests/moonraker.nix b/nixos/tests/moonraker.nix new file mode 100644 index 00000000000..b0a93a4a608 --- /dev/null +++ b/nixos/tests/moonraker.nix @@ -0,0 +1,45 @@ +import ./make-test-python.nix ({ pkgs, ...} : { + name = "moonraker"; + meta = with pkgs.lib.maintainers; { + maintainers = [ zhaofengli ]; + }; + + nodes = { + printer = { config, pkgs, ... }: { + security.polkit.enable = true; + + services.moonraker = { + enable = true; + allowSystemControl = true; + + settings = { + authorization = { + trusted_clients = [ "127.0.0.0/8" "::1/128" ]; + }; + }; + }; + + services.klipper = { + enable = true; + + user = "moonraker"; + group = "moonraker"; + + # No mcu configured so won't even enter `ready` state + settings = {}; + }; + }; + }; + + testScript = '' + printer.start() + + printer.wait_for_unit("klipper.service") + printer.wait_for_unit("moonraker.service") + printer.wait_until_succeeds("curl http://localhost:7125/printer/info | grep -v 'Not Found' >&2", timeout=30) + + with subtest("Check that we can perform system-level operations"): + printer.succeed("curl -X POST http://localhost:7125/machine/services/stop?service=klipper | grep ok >&2") + printer.wait_until_succeeds("systemctl --no-pager show klipper.service | grep ActiveState=inactive", timeout=10) + ''; +}) diff --git a/pkgs/development/python-modules/preprocess-cancellation/default.nix b/pkgs/development/python-modules/preprocess-cancellation/default.nix new file mode 100644 index 00000000000..d671cb4d66e --- /dev/null +++ b/pkgs/development/python-modules/preprocess-cancellation/default.nix @@ -0,0 +1,31 @@ +{ lib, fetchFromGitHub, buildPythonPackage, pythonOlder, poetry-core +, pytestCheckHook, pytest-cov +, shapely }: + +buildPythonPackage rec { + pname = "preprocess-cancellation"; + version = "0.2.0"; + disabled = pythonOlder "3.6"; # >= 3.6 + format = "pyproject"; + + # No tests in PyPI + src = fetchFromGitHub { + owner = "kageurufu"; + repo = "cancelobject-preprocessor"; + rev = version; + hash = "sha256-mn3/etXA5dkL+IsyxwD4/XjU/t4/roYFVyqQxlLOoOI="; + }; + + nativeBuildInputs = [ poetry-core ]; + + propagatedBuildInputs = [ shapely ]; + + checkInputs = [ pytestCheckHook pytest-cov ]; + + meta = with lib; { + description = "Klipper GCode Preprocessor for Object Cancellation"; + homepage = "https://github.com/kageurufu/cancelobject-preprocessor"; + license = licenses.gpl3Only; + maintainers = with maintainers; [ zhaofengli ]; + }; +} diff --git a/pkgs/servers/klipper/default.nix b/pkgs/servers/klipper/default.nix index e2932c16a10..490bb9c9e09 100644 --- a/pkgs/servers/klipper/default.nix +++ b/pkgs/servers/klipper/default.nix @@ -36,6 +36,11 @@ stdenv.mkDerivation rec { mkdir -p $out/lib/klipper cp -r ./* $out/lib/klipper + # Moonraker expects `config_examples` and `docs` to be available + # under `klipper_path` + cp -r $src/docs $out/lib/docs + cp -r $src/config $out/lib/config + chmod 755 $out/lib/klipper/klippy.py runHook postInstall ''; diff --git a/pkgs/servers/moonraker/default.nix b/pkgs/servers/moonraker/default.nix index 31525464c0c..2350cd18042 100644 --- a/pkgs/servers/moonraker/default.nix +++ b/pkgs/servers/moonraker/default.nix @@ -1,9 +1,9 @@ -{ lib, stdenvNoCC, fetchFromGitHub, python3, makeWrapper, unstableGitUpdater }: +{ lib, stdenvNoCC, fetchFromGitHub, python3, makeWrapper, unstableGitUpdater, nixosTests }: let pythonEnv = python3.withPackages (packages: with packages; [ tornado - pyserial + pyserial-asyncio pillow lmdb streaming-form-data @@ -12,16 +12,21 @@ let libnacl paho-mqtt pycurl + zeroconf + preprocess-cancellation + jinja2 + dbus-next + apprise ]); in stdenvNoCC.mkDerivation rec { pname = "moonraker"; - version = "unstable-2021-12-05"; + version = "unstable-2022-03-10"; src = fetchFromGitHub { owner = "Arksine"; repo = "moonraker"; - rev = "ac73036857cc1ca83df072dd94bf28eb9d0ed8b0"; - sha256 = "Oqjt0z4grt+hdQ4t7KQSwkkCeRGoFFedJsTpMHwMm34="; + rev = "ee312ee9c6597c8d077d7c3208ccea4e696c97ca"; + sha256 = "l0VOQIfKgZ/Je4z+SKhWMgYzxye8WKs9W1GkNs7kABo="; }; nativeBuildInputs = [ makeWrapper ]; @@ -34,7 +39,10 @@ in stdenvNoCC.mkDerivation rec { --add-flags "$out/lib/moonraker/moonraker.py" ''; - passthru.updateScript = unstableGitUpdater { url = meta.homepage; }; + passthru = { + updateScript = unstableGitUpdater { url = meta.homepage; }; + tests.moonraker = nixosTests.moonraker; + }; meta = with lib; { description = "API web server for Klipper"; diff --git a/pkgs/top-level/python-packages.nix b/pkgs/top-level/python-packages.nix index 6a15088011d..b48a55412c4 100644 --- a/pkgs/top-level/python-packages.nix +++ b/pkgs/top-level/python-packages.nix @@ -6466,6 +6466,8 @@ in { premailer = callPackage ../development/python-modules/premailer { }; + preprocess-cancellation = callPackage ../development/python-modules/preprocess-cancellation { }; + preshed = callPackage ../development/python-modules/preshed { }; pretend = callPackage ../development/python-modules/pretend { };