diff --git a/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml index 690cd0bd1bb..f241bff0760 100644 --- a/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml +++ b/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml @@ -149,6 +149,13 @@ services.meshcentral.enable + + + moonraker, + an API web server for Klipper. Available as + moonraker. + +
diff --git a/nixos/doc/manual/release-notes/rl-2111.section.md b/nixos/doc/manual/release-notes/rl-2111.section.md index 898f2d64f15..13cb08e2c9a 100644 --- a/nixos/doc/manual/release-notes/rl-2111.section.md +++ b/nixos/doc/manual/release-notes/rl-2111.section.md @@ -45,6 +45,9 @@ pt-services.clipcat.enable). - [MeshCentral](https://www.meshcommander.com/meshcentral2/overview), a remote administration service ("TeamViewer but self-hosted and with more features") is now available with a package and a module: [services.meshcentral.enable](#opt-services.meshcentral.enable) +- [moonraker](https://github.com/Arksine/moonraker), an API web server for Klipper. + Available as [moonraker](#opt-services.moonraker.enable). + ## Backward Incompatibilities {#sec-release-21.11-incompatibilities} - The `staticjinja` package has been upgraded from 1.0.4 to 3.0.1 diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix index a6839e02c4f..c2e588bf00d 100644 --- a/nixos/modules/misc/ids.nix +++ b/nixos/modules/misc/ids.nix @@ -349,6 +349,7 @@ in zigbee2mqtt = 317; # shadow = 318; # unused hqplayer = 319; + moonraker = 320; # When adding a uid, make sure it doesn't match an existing gid. And don't use uids above 399! @@ -652,6 +653,7 @@ in zigbee2mqtt = 317; shadow = 318; hqplayer = 319; + moonraker = 320; # When adding a gid, make sure it doesn't match an existing # uid. Users and groups with the same name should have equal diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index a5314fdd83c..4276a625ed6 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -533,6 +533,7 @@ ./services/misc/mbpfan.nix ./services/misc/mediatomb.nix ./services/misc/metabase.nix + ./services/misc/moonraker.nix ./services/misc/mwlib.nix ./services/misc/mx-puppet-discord.nix ./services/misc/n8n.nix diff --git a/nixos/modules/services/misc/klipper.nix b/nixos/modules/services/misc/klipper.nix index 909408225e0..e6b9dd234a9 100644 --- a/nixos/modules/services/misc/klipper.nix +++ b/nixos/modules/services/misc/klipper.nix @@ -30,8 +30,7 @@ in apiSocket = mkOption { type = types.nullOr types.path; - default = null; - example = "/run/klipper/api"; + default = "/run/klipper/api"; description = "Path of the API socket to create."; }; diff --git a/nixos/modules/services/misc/moonraker.nix b/nixos/modules/services/misc/moonraker.nix new file mode 100644 index 00000000000..de8668a0c06 --- /dev/null +++ b/nixos/modules/services/misc/moonraker.nix @@ -0,0 +1,135 @@ +{ config, lib, pkgs, ... }: +with lib; +let + pkg = pkgs.moonraker; + cfg = config.services.moonraker; + format = pkgs.formats.ini { + # https://github.com/NixOS/nixpkgs/pull/121613#issuecomment-885241996 + listToValue = l: + if builtins.length l == 1 then generators.mkValueStringDefault {} (head l) + else lib.concatMapStrings (s: "\n ${generators.mkValueStringDefault {} s}") l; + mkKeyValue = generators.mkKeyValueDefault {} ":"; + }; +in { + options = { + services.moonraker = { + enable = mkEnableOption "Moonraker, an API web server for Klipper"; + + klipperSocket = mkOption { + type = types.path; + default = config.services.klipper.apiSocket; + description = "Path to Klipper's API socket."; + }; + + stateDir = mkOption { + type = types.path; + default = "/var/lib/moonraker"; + description = "The directory containing the Moonraker databases."; + }; + + configDir = mkOption { + type = types.path; + default = cfg.stateDir + "/config"; + description = '' + The directory containing client-writable configuration files. + + Clients will be able to edit files in this directory via the API. This directory must be writable. + ''; + }; + + user = mkOption { + type = types.str; + default = "moonraker"; + description = "User account under which Moonraker runs."; + }; + + group = mkOption { + type = types.str; + default = "moonraker"; + description = "Group account under which Moonraker runs."; + }; + + address = mkOption { + type = types.str; + default = "127.0.0.1"; + example = "0.0.0.0"; + description = "The IP or host to listen on."; + }; + + port = mkOption { + type = types.ints.unsigned; + default = 7125; + description = "The port to listen on."; + }; + + settings = mkOption { + type = format.type; + default = { }; + example = { + authorization = { + trusted_clients = [ "10.0.0.0/24" ]; + cors_domains = [ "https://app.fluidd.xyz" ]; + }; + }; + description = '' + Configuration for Moonraker. See the documentation + for supported values. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + warnings = optional (cfg.settings ? update_manager) + ''Enabling update_manager is not supported on NixOS and will lead to non-removable warnings in some clients.''; + + users.users = optionalAttrs (cfg.user == "moonraker") { + moonraker = { + group = cfg.group; + uid = config.ids.uids.moonraker; + }; + }; + + users.groups = optionalAttrs (cfg.group == "moonraker") { + moonraker.gid = config.ids.gids.moonraker; + }; + + environment.etc."moonraker.cfg".source = let + forcedConfig = { + server = { + host = cfg.address; + port = cfg.port; + klippy_uds_address = cfg.klipperSocket; + config_path = cfg.configDir; + database_path = "${cfg.stateDir}/database"; + }; + }; + fullConfig = recursiveUpdate cfg.settings forcedConfig; + in format.generate "moonraker.cfg" fullConfig; + + systemd.tmpfiles.rules = [ + "d '${cfg.stateDir}' - ${cfg.user} ${cfg.group} - -" + "d '${cfg.configDir}' - ${cfg.user} ${cfg.group} - -" + ]; + + systemd.services.moonraker = { + description = "Moonraker, an API web server for Klipper"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ] + ++ optional config.services.klipper.enable "klipper.service"; + + # Moonraker really wants its own config to be writable... + script = '' + cp /etc/moonraker.cfg ${cfg.configDir}/moonraker-temp.cfg + chmod u+w ${cfg.configDir}/moonraker-temp.cfg + exec ${pkg}/bin/moonraker -c ${cfg.configDir}/moonraker-temp.cfg + ''; + + serviceConfig = { + WorkingDirectory = cfg.stateDir; + Group = cfg.group; + User = cfg.user; + }; + }; + }; +} diff --git a/pkgs/applications/misc/fluidd/default.nix b/pkgs/applications/misc/fluidd/default.nix new file mode 100644 index 00000000000..eee466650e3 --- /dev/null +++ b/pkgs/applications/misc/fluidd/default.nix @@ -0,0 +1,34 @@ +{ lib, stdenvNoCC, fetchurl, unzip }: + +stdenvNoCC.mkDerivation rec { + pname = "fluidd"; + version = "1.16.2"; + + src = fetchurl { + name = "fluidd-v${version}.zip"; + url = "https://github.com/cadriel/fluidd/releases/download/v${version}/fluidd.zip"; + sha256 = "1qwj25xvvxvm1fxx216nn2gp7js4d682mm3l4s7ns90fc5ygvc8i"; + }; + + nativeBuildInputs = [ unzip ]; + + dontConfigure = true; + dontBuild = true; + + unpackPhase = '' + mkdir fluidd + unzip $src -d fluidd + ''; + + installPhase = '' + mkdir -p $out/share/fluidd + cp -r fluidd $out/share/fluidd/htdocs + ''; + + meta = with lib; { + description = "Klipper web interface"; + homepage = "https://docs.fluidd.xyz"; + license = licenses.gpl3Only; + maintainers = with maintainers; [ zhaofengli ]; + }; +} diff --git a/pkgs/development/python-modules/streaming-form-data/default.nix b/pkgs/development/python-modules/streaming-form-data/default.nix new file mode 100644 index 00000000000..2f52d4aeb30 --- /dev/null +++ b/pkgs/development/python-modules/streaming-form-data/default.nix @@ -0,0 +1,34 @@ +{ lib, fetchFromGitHub, buildPythonPackage, pythonOlder, +cython, numpy, pytest, requests-toolbelt }: + +buildPythonPackage rec { + pname = "streaming-form-data"; + version = "1.8.1"; + disabled = pythonOlder "3.6"; + + src = fetchFromGitHub { + owner = "siddhantgoel"; + repo = "streaming-form-data"; + rev = "v${version}"; + sha256 = "1wnak8gwkc42ihgf0g9r7r858hxbqav2xdgqa8azid8v2ff6iq4d"; + }; + + nativeBuildInputs = [ cython ]; + + propagatedBuildInputs = [ requests-toolbelt ]; + + checkInputs = [ numpy pytest ]; + + checkPhase = '' + make test + ''; + + pythonImportsCheck = [ "streaming_form_data" ]; + + meta = with lib; { + description = "Streaming parser for multipart/form-data"; + homepage = "https://github.com/siddhantgoel/streaming-form-data"; + license = licenses.mit; + maintainers = with maintainers; [ zhaofengli ]; + }; +} diff --git a/pkgs/servers/moonraker/default.nix b/pkgs/servers/moonraker/default.nix new file mode 100644 index 00000000000..2ca35beeff1 --- /dev/null +++ b/pkgs/servers/moonraker/default.nix @@ -0,0 +1,44 @@ +{ lib, stdenvNoCC, fetchFromGitHub, python3, makeWrapper, unstableGitUpdater }: + +let + pythonEnv = python3.withPackages (packages: with packages; [ + tornado + pyserial + pillow + lmdb + streaming-form-data + distro + inotify-simple + libnacl + paho-mqtt + ]); +in stdenvNoCC.mkDerivation rec { + pname = "moonraker"; + version = "unstable-2021-07-18"; + + src = fetchFromGitHub { + owner = "Arksine"; + repo = "moonraker"; + rev = "42f61ceafa90fcfea8bffbe968e26a6fd8b61af6"; + sha256 = "1w6l9pgs4n4nnk3h40y346bf6j3v4j4h1qnhj5dwlbwdxiqpd9gs"; + }; + + nativeBuildInputs = [ makeWrapper ]; + + installPhase = '' + mkdir -p $out $out/bin $out/lib + cp -r moonraker $out/lib + + makeWrapper ${pythonEnv}/bin/python $out/bin/moonraker \ + --add-flags "$out/lib/moonraker/moonraker.py" + ''; + + passthru.updateScript = unstableGitUpdater { url = meta.homepage; }; + + meta = with lib; { + description = "API web server for Klipper"; + homepage = "https://github.com/Arksine/moonraker"; + license = licenses.gpl3Only; + maintainers = with maintainers; [ zhaofengli ]; + }; +} diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index c7766eb1f26..8db6277f18b 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -3100,6 +3100,8 @@ in moodle-dl = callPackage ../tools/networking/moodle-dl { }; + moonraker = callPackage ../servers/moonraker { }; + mousetweaks = callPackage ../applications/accessibility/mousetweaks { inherit (pkgs.xorg) libX11 libXtst libXfixes; }; @@ -10893,6 +10895,8 @@ in flyctl = callPackage ../development/web/flyctl { }; + fluidd = callPackage ../applications/misc/fluidd { }; + flutterPackages = recurseIntoAttrs (callPackage ../development/compilers/flutter { }); flutter = flutterPackages.stable; diff --git a/pkgs/top-level/python-packages.nix b/pkgs/top-level/python-packages.nix index ff292debd20..ebbf5b5e76e 100644 --- a/pkgs/top-level/python-packages.nix +++ b/pkgs/top-level/python-packages.nix @@ -8402,6 +8402,8 @@ in { stravalib = callPackage ../development/python-modules/stravalib { }; + streaming-form-data = callPackage ../development/python-modules/streaming-form-data { }; + streamz = callPackage ../development/python-modules/streamz { }; strict-rfc3339 = callPackage ../development/python-modules/strict-rfc3339 { };