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