From 0cb5232d5713b396a6fb87247cd46c21532dada9 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 19 Aug 2024 14:02:20 +0200 Subject: [PATCH 1/4] octoprint: remove ffmpeg patch ffmpeg is only used for timelapses which will just be disabled if the options isn't set. Additionally this allows people to ship their own ffmpeg. --- pkgs/applications/misc/octoprint/default.nix | 8 +------ .../misc/octoprint/ffmpeg-path.patch | 22 ------------------- 2 files changed, 1 insertion(+), 29 deletions(-) delete mode 100644 pkgs/applications/misc/octoprint/ffmpeg-path.patch diff --git a/pkgs/applications/misc/octoprint/default.nix b/pkgs/applications/misc/octoprint/default.nix index 5052f0aef02f3..1f58cb65bdfb3 100644 --- a/pkgs/applications/misc/octoprint/default.nix +++ b/pkgs/applications/misc/octoprint/default.nix @@ -1,5 +1,4 @@ { - pkgs, stdenv, callPackage, lib, @@ -197,12 +196,6 @@ let src = ./pip-path.patch; pip = "${self.pip}/bin/pip"; }) - - # hardcore path to ffmpeg and hide related settings - (substituteAll { - src = ./ffmpeg-path.patch; - ffmpeg = "${pkgs.ffmpeg}/bin/ffmpeg"; - }) ]; postPatch = @@ -266,6 +259,7 @@ let gebner WhittlesJr gador + patrickdag ]; }; }; diff --git a/pkgs/applications/misc/octoprint/ffmpeg-path.patch b/pkgs/applications/misc/octoprint/ffmpeg-path.patch deleted file mode 100644 index 2e7c7dbe06428..0000000000000 --- a/pkgs/applications/misc/octoprint/ffmpeg-path.patch +++ /dev/null @@ -1,22 +0,0 @@ -diff --git a/src/octoprint/server/api/settings.py b/src/octoprint/server/api/settings.py -index c3e6cea10..ced2f8fa0 100644 ---- a/src/octoprint/server/api/settings.py -+++ b/src/octoprint/server/api/settings.py -@@ -130,7 +130,7 @@ data["webcam"] = { - "webcamEnabled": s.getBoolean(["webcam", "webcamEnabled"]), - "snapshotTimeout": s.getInt(["webcam", "snapshotTimeout"]), - "timelapseEnabled": s.getBoolean(["webcam", "timelapseEnabled"]), -- "ffmpegPath": s.get(["webcam", "ffmpeg"]), -+ "ffmpegPath": "@ffmpeg@", - "ffmpegCommandline": s.get(["webcam", "ffmpegCommandline"]), - "bitrate": s.get(["webcam", "bitrate"]), - "ffmpegThreads": s.get(["webcam", "ffmpegThreads"]), -@@ -548,8 +548,6 @@ def _saveSettings(data): - ["webcam", "snapshotSslValidation"], - data["webcam"]["snapshotSslValidation"], - ) -- if "ffmpegPath" in data["webcam"]: -- s.set(["webcam", "ffmpeg"], data["webcam"]["ffmpegPath"]) - if "ffmpegCommandline" in data["webcam"]: - commandline = data["webcam"]["ffmpegCommandline"] - if not all( From ee4c257f7c36afee8383620fd45f6a35cf36f9a7 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 19 Aug 2024 14:16:39 +0200 Subject: [PATCH 2/4] nixos/octoprint: format --- nixos/modules/services/misc/octoprint.nix | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/nixos/modules/services/misc/octoprint.nix b/nixos/modules/services/misc/octoprint.nix index d8e4c9c302b38..a53bdd2646fa4 100644 --- a/nixos/modules/services/misc/octoprint.nix +++ b/nixos/modules/services/misc/octoprint.nix @@ -1,4 +1,10 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + pkgs, + ... +}: + let cfg = config.services.octoprint; @@ -127,9 +133,7 @@ in ExecStart = "${pluginsEnv}/bin/octoprint serve -b ${cfg.stateDir}"; User = cfg.user; Group = cfg.group; - SupplementaryGroups = [ - "dialout" - ]; + SupplementaryGroups = [ "dialout" ]; }; }; From 933914d726a379d4800b81081e555aa9405d1de0 Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 19 Aug 2024 15:34:42 +0200 Subject: [PATCH 3/4] nixos/octoprint: RFC 42 compliance, systemd hardening --- .../manual/release-notes/rl-2411.section.md | 4 + nixos/modules/services/misc/octoprint.nix | 115 ++++++++++++++---- nixos/tests/octoprint.nix | 2 +- 3 files changed, 95 insertions(+), 26 deletions(-) diff --git a/nixos/doc/manual/release-notes/rl-2411.section.md b/nixos/doc/manual/release-notes/rl-2411.section.md index 5c4bd436d7bb6..5921725216d4e 100644 --- a/nixos/doc/manual/release-notes/rl-2411.section.md +++ b/nixos/doc/manual/release-notes/rl-2411.section.md @@ -456,6 +456,10 @@ - the `ankisyncd` package and its `services.ankisyncd` have been removed, use [`services.anki-sync-server`](#opt-services.anki-sync-server.enable) instead. +- The `octoprint` service has gained an `enableRaspberryPi` option, which will + be disabled for state versions following 25.05. Users running on Raspberry Pi + should enable the option to restore full functionality. + - `nodePackages.vscode-css-languageserver-bin`, `nodePackages.vscode-html-languageserver-bin`, and `nodePackages.vscode-json-languageserver-bin` were dropped due to an unmaintained upstream. The `vscode-langservers-extracted` package is a maintained drop-in replacement. diff --git a/nixos/modules/services/misc/octoprint.nix b/nixos/modules/services/misc/octoprint.nix index a53bdd2646fa4..6ab48ee10e3c7 100644 --- a/nixos/modules/services/misc/octoprint.nix +++ b/nixos/modules/services/misc/octoprint.nix @@ -4,20 +4,24 @@ pkgs, ... }: - let - cfg = config.services.octoprint; + inherit (lib) + literalExpression + mkDefault + mkEnableOption + mkOption + mkRenamedOptionModule + optional + types + versionOlder + ; - baseConfig = { - plugins.curalegacy.cura_engine = "${pkgs.curaengine_stable}/bin/CuraEngine"; - server.port = cfg.port; - webcam.ffmpeg = "${pkgs.ffmpeg.bin}/bin/ffmpeg"; - } // lib.optionalAttrs (cfg.host != null) {server.host = cfg.host;}; + cfg = config.services.octoprint; - fullConfig = lib.recursiveUpdate cfg.extraConfig baseConfig; + formatType = pkgs.formats.json { }; - cfgUpdate = pkgs.writeText "octoprint-config.yaml" (builtins.toJSON fullConfig); + configFile = formatType.generate "octoprint-config.yaml" cfg.settings; pluginsEnv = package.python.withPackages (ps: [ ps.octoprint ] ++ (cfg.plugins ps)); @@ -73,18 +77,32 @@ in description = "State directory of the daemon."; }; - plugins = lib.mkOption { - type = lib.types.functionTo (lib.types.listOf lib.types.package); - default = plugins: [ ]; - defaultText = lib.literalExpression "plugins: []"; - example = lib.literalExpression "plugins: with plugins; [ themeify stlviewer ]"; + plugins = mkOption { + type = types.functionTo (types.listOf types.package); + default = _plugins: [ ]; + defaultText = literalExpression "plugins: []"; + example = literalExpression "plugins: with plugins; [ themeify stlviewer ]"; description = "Additional plugins to be used. Available plugins are passed through the plugins input."; }; - extraConfig = lib.mkOption { - type = lib.types.attrs; + settings = mkOption { default = { }; - description = "Extra options which are added to OctoPrint's YAML configuration file."; + description = '' + The octoprint settings, for definitions see the upstream [documentation](https://docs.octoprint.org). + Will override any existing settings. + ''; + type = types.submodule { + freeformType = formatType.type; + config = { + plugins.curalegacy.cura_engine = mkDefault "${pkgs.curaengine_stable}/bin/CuraEngine"; + server.host = cfg.host; + server.port = cfg.port; + webcam.ffmpeg = mkDefault "${pkgs.ffmpeg.bin}/bin/ffmpeg"; + }; + }; + }; + enableRaspberryPi = mkEnableOption "RaspberryPi specific hardware access rules" // { + default = versionOlder config.system.stateVersion "25.05"; }; }; @@ -92,6 +110,20 @@ in }; ##### implementation + imports = [ + (mkRenamedOptionModule + [ + "services" + "octoprint" + "extraConfig" + ] + [ + "services" + "octoprint" + "settings" + ] + ) + ]; config = lib.mkIf cfg.enable { @@ -106,12 +138,13 @@ in octoprint.gid = config.ids.gids.octoprint; }; - systemd.tmpfiles.rules = [ - "d '${cfg.stateDir}' - ${cfg.user} ${cfg.group} - -" - # this will allow octoprint access to raspberry specific hardware to check for throttling - # read-only will not work: "VCHI initialization failed" error - "a /dev/vchiq - - - - u:octoprint:rw" - ]; + systemd.tmpfiles.rules = + [ "d '${cfg.stateDir}' - ${cfg.user} ${cfg.group} - -" ] + ++ optional cfg.enableRaspberryPi + # this will allow octoprint access to raspberry specific hardware to check for throttling + # read-only will not work: "VCHI initialization failed" error + # FIXME: this should probably be a udev rule + "a /dev/vchiq - - - - u:octoprint:rw"; systemd.services.octoprint = { description = "OctoPrint, web interface for 3D printers"; @@ -121,10 +154,10 @@ in preStart = '' if [ -e "${cfg.stateDir}/config.yaml" ]; then - ${pkgs.yaml-merge}/bin/yaml-merge "${cfg.stateDir}/config.yaml" "${cfgUpdate}" > "${cfg.stateDir}/config.yaml.tmp" + ${pkgs.yaml-merge}/bin/yaml-merge "${cfg.stateDir}/config.yaml" "${configFile}" > "${cfg.stateDir}/config.yaml.tmp" mv "${cfg.stateDir}/config.yaml.tmp" "${cfg.stateDir}/config.yaml" else - cp "${cfgUpdate}" "${cfg.stateDir}/config.yaml" + cp "${configFile}" "${cfg.stateDir}/config.yaml" chmod 600 "${cfg.stateDir}/config.yaml" fi ''; @@ -134,9 +167,41 @@ in User = cfg.user; Group = cfg.group; SupplementaryGroups = [ "dialout" ]; + + # Hardening + CapabilityBoundingSet = ""; + LockPersonality = true; + MemoryDenyWriteExecute = true; + PrivateUsers = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProcSubset = "pid"; + ProtectSystem = "strict"; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_NETLINK" + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service" + "@pkey" + ]; + ReadWritePaths = [ cfg.stateDir ]; + UMask = "0077"; + }; }; networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ cfg.port ]; }; + meta.maintainers = with lib.maintainers; [ patrickdag ]; } diff --git a/nixos/tests/octoprint.nix b/nixos/tests/octoprint.nix index 15a2d677d4cf8..968fef08144ea 100644 --- a/nixos/tests/octoprint.nix +++ b/nixos/tests/octoprint.nix @@ -11,7 +11,7 @@ in environment.systemPackages = with pkgs; [ jq ]; services.octoprint = { enable = true; - extraConfig = { + settings = { server = { firstRun = false; }; From 2599d2effdda740f40050d47fed0e3586397ef1e Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 3 Nov 2024 22:21:42 +0100 Subject: [PATCH 4/4] nixos/octoprint: add tests for reachability over IPv6 --- nixos/tests/octoprint.nix | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/nixos/tests/octoprint.nix b/nixos/tests/octoprint.nix index 968fef08144ea..dc60b10813311 100644 --- a/nixos/tests/octoprint.nix +++ b/nixos/tests/octoprint.nix @@ -50,11 +50,18 @@ in # used to fail early, in case octoprint first starts and then crashes with octoprint_running: # type: ignore[union-attr] with subtest("Check for web interface"): - machine.wait_until_succeeds("curl -s localhost:5000") + machine.wait_until_succeeds("curl -s -4 localhost:5000") + machine.wait_until_succeeds("curl -s -6 localhost:5000") - with subtest("Check API"): - version = json.loads(machine.succeed(curl_cmd + "localhost:5000/api/version")) - server = json.loads(machine.succeed(curl_cmd + "localhost:5000/api/server")) + with subtest("Check API IPv4"): + version = json.loads(machine.succeed(curl_cmd + "-4 localhost:5000/api/version")) + server = json.loads(machine.succeed(curl_cmd + "-4 localhost:5000/api/server")) + assert version["server"] == str("${pkgs.octoprint.version}") + assert server["safemode"] == None + + with subtest("Check API IPv6"): + version = json.loads(machine.succeed(curl_cmd + "-6 localhost:5000/api/version")) + server = json.loads(machine.succeed(curl_cmd + "-6 localhost:5000/api/server")) assert version["server"] == str("${pkgs.octoprint.version}") assert server["safemode"] == None '';