diff --git a/nixos/doc/manual/release-notes/rl-2505.section.md b/nixos/doc/manual/release-notes/rl-2505.section.md index 10645d55e8389..e4ffb75742580 100644 --- a/nixos/doc/manual/release-notes/rl-2505.section.md +++ b/nixos/doc/manual/release-notes/rl-2505.section.md @@ -40,6 +40,10 @@ add `vimPlugins.notmuch-vim` to your (Neo)vim configuration if you want the vim plugin. +- 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. + ## Other Notable Changes {#sec-release-25.05-notable-changes} diff --git a/nixos/modules/services/misc/octoprint.nix b/nixos/modules/services/misc/octoprint.nix index d8e4c9c302b38..6ab48ee10e3c7 100644 --- a/nixos/modules/services/misc/octoprint.nix +++ b/nixos/modules/services/misc/octoprint.nix @@ -1,17 +1,27 @@ -{ config, lib, pkgs, ... }: +{ + config, + lib, + 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)); @@ -67,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"; }; }; @@ -86,6 +110,20 @@ in }; ##### implementation + imports = [ + (mkRenamedOptionModule + [ + "services" + "octoprint" + "extraConfig" + ] + [ + "services" + "octoprint" + "settings" + ] + ) + ]; config = lib.mkIf cfg.enable { @@ -100,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"; @@ -115,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 ''; @@ -127,12 +166,42 @@ in ExecStart = "${pluginsEnv}/bin/octoprint serve -b ${cfg.stateDir}"; User = cfg.user; Group = cfg.group; - SupplementaryGroups = [ - "dialout" + 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..dc60b10813311 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; }; @@ -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 ''; diff --git a/pkgs/by-name/oc/octoprint/ffmpeg-path.patch b/pkgs/by-name/oc/octoprint/ffmpeg-path.patch deleted file mode 100644 index 2e7c7dbe06428..0000000000000 --- a/pkgs/by-name/oc/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( diff --git a/pkgs/by-name/oc/octoprint/package.nix b/pkgs/by-name/oc/octoprint/package.nix index a4b437ac6d1c7..3042f7d6dd643 100644 --- a/pkgs/by-name/oc/octoprint/package.nix +++ b/pkgs/by-name/oc/octoprint/package.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 ]; }; };