silx: 2.1.1 -> 2.1.2 (#361612)
[NixPkgs.git] / nixos / modules / services / misc / klipper.nix
blobd9eef161eb727c9f2dcddba93b02c011b7e83c1c
1 { config, lib, pkgs, ... }:
2 let
3   cfg = config.services.klipper;
4   format = pkgs.formats.ini {
5     # https://github.com/NixOS/nixpkgs/pull/121613#issuecomment-885241996
6     listToValue = l:
7       if builtins.length l == 1 then lib.generators.mkValueStringDefault { } (lib.head l)
8       else lib.concatMapStrings (s: "\n  ${lib.generators.mkValueStringDefault {} s}") l;
9     mkKeyValue = lib.generators.mkKeyValueDefault { } ":";
10   };
13   ##### interface
14   options = {
15     services.klipper = {
16       enable = lib.mkEnableOption "Klipper, the 3D printer firmware";
18       package = lib.mkPackageOption pkgs "klipper" { };
20       logFile = lib.mkOption {
21         type = lib.types.nullOr lib.types.path;
22         default = null;
23         example = "/var/lib/klipper/klipper.log";
24         description = ''
25           Path of the file Klipper should log to.
26           If `null`, it logs to stdout, which is not recommended by upstream.
27         '';
28       };
30       inputTTY = lib.mkOption {
31         type = lib.types.path;
32         default = "/run/klipper/tty";
33         description = "Path of the virtual printer symlink to create.";
34       };
36       apiSocket = lib.mkOption {
37         type = lib.types.nullOr lib.types.path;
38         default = "/run/klipper/api";
39         description = "Path of the API socket to create.";
40       };
42       mutableConfig = lib.mkOption {
43         type = lib.types.bool;
44         default = false;
45         example = true;
46         description = ''
47           Whether to copy the config to a mutable directory instead of using the one directly from the nix store.
48           This will only copy the config if the file at `services.klipper.mutableConfigPath` doesn't exist.
49         '';
50       };
52       mutableConfigFolder = lib.mkOption {
53         type = lib.types.path;
54         default = "/var/lib/klipper";
55         description = "Path to mutable Klipper config file.";
56       };
58       configFile = lib.mkOption {
59         type = lib.types.nullOr lib.types.path;
60         default = null;
61         description = ''
62           Path to default Klipper config.
63         '';
64       };
66       octoprintIntegration = lib.mkOption {
67         type = lib.types.bool;
68         default = false;
69         description = "Allows Octoprint to control Klipper.";
70       };
72       user = lib.mkOption {
73         type = lib.types.nullOr lib.types.str;
74         default = null;
75         description = ''
76           User account under which Klipper runs.
78           If null is specified (default), a temporary user will be created by systemd.
79         '';
80       };
82       group = lib.mkOption {
83         type = lib.types.nullOr lib.types.str;
84         default = null;
85         description = ''
86           Group account under which Klipper runs.
88           If null is specified (default), a temporary user will be created by systemd.
89         '';
90       };
92       settings = lib.mkOption {
93         type = lib.types.nullOr format.type;
94         default = null;
95         description = ''
96           Configuration for Klipper. See the [documentation](https://www.klipper3d.org/Overview.html#configuration-and-tuning-guides)
97           for supported values.
98         '';
99       };
101       firmwares = lib.mkOption {
102         description = "Firmwares klipper should manage";
103         default = { };
104         type = with lib.types; attrsOf
105           (submodule {
106             options = {
107               enable = lib.mkEnableOption ''
108                 building of firmware for manual flashing
109               '';
110               enableKlipperFlash = lib.mkEnableOption ''
111                 flashings scripts for firmware. This will add `klipper-flash-$mcu` scripts to your environment which can be called to flash the firmware.
112                 Please check the configs at [klipper](https://github.com/Klipper3d/klipper/tree/master/config) whether your board supports flashing via `make flash`
113               '';
114               serial = lib.mkOption {
115                 type = lib.types.nullOr path;
116                 default = null;
117                 description = "Path to serial port this printer is connected to. Leave `null` to derive it from `service.klipper.settings`.";
118               };
119               configFile = lib.mkOption {
120                 type = path;
121                 description = "Path to firmware config which is generated using `klipper-genconf`";
122               };
123             };
124           });
125       };
126     };
127   };
129   ##### implementation
130   config = lib.mkIf cfg.enable {
131     assertions = [
132       {
133         assertion = cfg.octoprintIntegration -> config.services.octoprint.enable;
134         message = "Option services.klipper.octoprintIntegration requires Octoprint to be enabled on this system. Please enable services.octoprint to use it.";
135       }
136       {
137         assertion = cfg.user != null -> cfg.group != null;
138         message = "Option services.klipper.group is not set when services.klipper.user is specified.";
139       }
140       {
141         assertion = cfg.settings != null -> lib.foldl (a: b: a && b) true (lib.mapAttrsToList (mcu: _: mcu != null -> (lib.hasAttrByPath [ "${mcu}" "serial" ] cfg.settings)) cfg.firmwares);
142         message = "Option services.klipper.settings.$mcu.serial must be set when settings.klipper.firmware.$mcu is specified";
143       }
144       {
145         assertion = (cfg.configFile != null) != (cfg.settings != null);
146         message = "You need to either specify services.klipper.settings or services.klipper.configFile.";
147       }
148     ];
150     environment.etc = lib.mkIf (!cfg.mutableConfig) {
151       "klipper.cfg".source = if cfg.settings != null then format.generate "klipper.cfg" cfg.settings else cfg.configFile;
152     };
154     services.klipper = lib.mkIf cfg.octoprintIntegration {
155       user = config.services.octoprint.user;
156       group = config.services.octoprint.group;
157     };
159     systemd.services.klipper =
160       let
161         klippyArgs = "--input-tty=${cfg.inputTTY}"
162           + lib.optionalString (cfg.apiSocket != null) " --api-server=${cfg.apiSocket}"
163           + lib.optionalString (cfg.logFile != null) " --logfile=${cfg.logFile}"
164         ;
165         printerConfigPath =
166           if cfg.mutableConfig
167           then cfg.mutableConfigFolder + "/printer.cfg"
168           else "/etc/klipper.cfg";
169         printerConfigFile =
170           if cfg.settings != null
171           then format.generate "klipper.cfg" cfg.settings
172           else cfg.configFile;
173       in
174       {
175         description = "Klipper 3D Printer Firmware";
176         wantedBy = [ "multi-user.target" ];
177         after = [ "network.target" ];
178         preStart = ''
179           mkdir -p ${cfg.mutableConfigFolder}
180           ${lib.optionalString (cfg.mutableConfig) ''
181             [ -e ${printerConfigPath} ] || {
182               cp ${printerConfigFile} ${printerConfigPath}
183               chmod +w ${printerConfigPath}
184             }
185           ''}
186           mkdir -p ${cfg.mutableConfigFolder}/gcodes
187         '';
189         serviceConfig = {
190           ExecStart = "${cfg.package}/bin/klippy ${klippyArgs} ${printerConfigPath}";
191           RuntimeDirectory = "klipper";
192           StateDirectory = "klipper";
193           SupplementaryGroups = [ "dialout" ];
194           WorkingDirectory = "${cfg.package}/lib";
195           OOMScoreAdjust = "-999";
196           CPUSchedulingPolicy = "rr";
197           CPUSchedulingPriority = 99;
198           IOSchedulingClass = "realtime";
199           IOSchedulingPriority = 0;
200           UMask = "0002";
201         } // (if cfg.user != null then {
202           Group = cfg.group;
203           User = cfg.user;
204         } else {
205           DynamicUser = true;
206           User = "klipper";
207         });
208       };
210     environment.systemPackages =
211       with pkgs;
212       let
213         default = a: b: if a != null then a else b;
214         firmwares = lib.filterAttrs (n: v: v != null) (lib.mapAttrs
215           (mcu: { enable, enableKlipperFlash, configFile, serial }:
216             if enable then
217               pkgs.klipper-firmware.override
218                 {
219                   mcu = lib.strings.sanitizeDerivationName mcu;
220                   firmwareConfig = configFile;
221                 } else null)
222           cfg.firmwares);
223         firmwareFlasher = lib.mapAttrsToList
224           (mcu: firmware: pkgs.klipper-flash.override {
225             mcu = lib.strings.sanitizeDerivationName mcu;
226             klipper-firmware = firmware;
227             flashDevice = default cfg.firmwares."${mcu}".serial cfg.settings."${mcu}".serial;
228             firmwareConfig = cfg.firmwares."${mcu}".configFile;
229           })
230           (lib.filterAttrs (mcu: firmware: cfg.firmwares."${mcu}".enableKlipperFlash) firmwares);
231       in
232       [ klipper-genconf ] ++ firmwareFlasher ++ lib.attrValues firmwares;
233   };
234   meta.maintainers = [
235     lib.maintainers.cab404
236   ];