at 25.11-pre 9.5 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7let 8 cfg = config.services.klipper; 9 format = pkgs.formats.ini { 10 # https://github.com/NixOS/nixpkgs/pull/121613#issuecomment-885241996 11 listToValue = 12 l: 13 if builtins.length l == 1 then 14 lib.generators.mkValueStringDefault { } (lib.head l) 15 else 16 lib.concatMapStrings (s: "\n ${lib.generators.mkValueStringDefault { } s}") l; 17 mkKeyValue = lib.generators.mkKeyValueDefault { } ":"; 18 }; 19in 20{ 21 imports = [ 22 (lib.mkRenamedOptionModule 23 [ "services" "klipper" "mutableConfigFolder" ] 24 [ "services" "klipper" "configDir" ] 25 ) 26 ]; 27 28 ##### interface 29 options = { 30 services.klipper = { 31 enable = lib.mkEnableOption "Klipper, the 3D printer firmware"; 32 33 package = lib.mkPackageOption pkgs "klipper" { }; 34 35 logFile = lib.mkOption { 36 type = lib.types.nullOr lib.types.path; 37 default = null; 38 example = "/var/lib/klipper/klipper.log"; 39 description = '' 40 Path of the file Klipper should log to. 41 If `null`, it logs to stdout, which is not recommended by upstream. 42 ''; 43 }; 44 45 inputTTY = lib.mkOption { 46 type = lib.types.path; 47 default = "/run/klipper/tty"; 48 description = "Path of the virtual printer symlink to create."; 49 }; 50 51 apiSocket = lib.mkOption { 52 type = lib.types.nullOr lib.types.path; 53 default = "/run/klipper/api"; 54 description = "Path of the API socket to create."; 55 }; 56 57 mutableConfig = lib.mkOption { 58 type = lib.types.bool; 59 default = false; 60 example = true; 61 description = '' 62 Whether to manage the config outside of NixOS. 63 64 It will still be initialized with the defined NixOS config if the file doesn't already exist. 65 ''; 66 }; 67 68 configDir = lib.mkOption { 69 type = lib.types.path; 70 default = "/var/lib/klipper"; 71 description = "Path to Klipper config file."; 72 }; 73 74 configFile = lib.mkOption { 75 type = lib.types.nullOr lib.types.path; 76 default = null; 77 description = "Path to default Klipper config."; 78 }; 79 80 octoprintIntegration = lib.mkOption { 81 type = lib.types.bool; 82 default = false; 83 description = "Allows Octoprint to control Klipper."; 84 }; 85 86 user = lib.mkOption { 87 type = lib.types.nullOr lib.types.str; 88 default = null; 89 description = '' 90 User account under which Klipper runs. 91 92 If null is specified (default), a temporary user will be created by systemd. 93 ''; 94 }; 95 96 group = lib.mkOption { 97 type = lib.types.nullOr lib.types.str; 98 default = null; 99 description = '' 100 Group account under which Klipper runs. 101 102 If null is specified (default), a temporary user will be created by systemd. 103 ''; 104 }; 105 106 settings = lib.mkOption { 107 type = lib.types.nullOr format.type; 108 default = null; 109 description = '' 110 Configuration for Klipper. See the [documentation](https://www.klipper3d.org/Overview.html#configuration-and-tuning-guides) 111 for supported values. 112 ''; 113 }; 114 115 firmwares = lib.mkOption { 116 description = "Firmwares klipper should manage"; 117 default = { }; 118 type = 119 with lib.types; 120 attrsOf (submodule { 121 options = { 122 enable = lib.mkEnableOption '' 123 building of firmware for manual flashing 124 ''; 125 enableKlipperFlash = lib.mkEnableOption '' 126 flashings scripts for firmware. This will add `klipper-flash-$mcu` scripts to your environment which can be called to flash the firmware. 127 Please check the configs at [klipper](https://github.com/Klipper3d/klipper/tree/master/config) whether your board supports flashing via `make flash` 128 ''; 129 serial = lib.mkOption { 130 type = lib.types.nullOr path; 131 default = null; 132 description = "Path to serial port this printer is connected to. Leave `null` to derive it from `service.klipper.settings`."; 133 }; 134 configFile = lib.mkOption { 135 type = path; 136 description = "Path to firmware config which is generated using `klipper-genconf`"; 137 }; 138 }; 139 }); 140 }; 141 }; 142 }; 143 144 ##### implementation 145 config = lib.mkIf cfg.enable { 146 assertions = [ 147 { 148 assertion = cfg.octoprintIntegration -> config.services.octoprint.enable; 149 message = "Option services.klipper.octoprintIntegration requires Octoprint to be enabled on this system. Please enable services.octoprint to use it."; 150 } 151 { 152 assertion = cfg.user != null -> cfg.group != null; 153 message = "Option services.klipper.group is not set when services.klipper.user is specified."; 154 } 155 { 156 assertion = 157 cfg.settings != null 158 -> lib.foldl (a: b: a && b) true ( 159 lib.mapAttrsToList ( 160 mcu: _: mcu != null -> (lib.hasAttrByPath [ "${mcu}" "serial" ] cfg.settings) 161 ) cfg.firmwares 162 ); 163 message = "Option services.klipper.settings.$mcu.serial must be set when settings.klipper.firmware.$mcu is specified"; 164 } 165 { 166 assertion = (cfg.configFile != null) != (cfg.settings != null); 167 message = "You need to either specify services.klipper.settings or services.klipper.configFile."; 168 } 169 ]; 170 171 services.klipper = lib.mkIf cfg.octoprintIntegration { 172 user = config.services.octoprint.user; 173 group = config.services.octoprint.group; 174 }; 175 176 systemd.services.klipper = 177 let 178 klippyArgs = 179 "--input-tty=${cfg.inputTTY}" 180 + lib.optionalString (cfg.apiSocket != null) " --api-server=${cfg.apiSocket}" 181 + lib.optionalString (cfg.logFile != null) " --logfile=${cfg.logFile}"; 182 printerConfig = 183 if cfg.settings != null then format.generate "klipper.cfg" cfg.settings else cfg.configFile; 184 in 185 { 186 description = "Klipper 3D Printer Firmware"; 187 wantedBy = [ "multi-user.target" ]; 188 after = [ "network.target" ]; 189 preStart = '' 190 mkdir -p ${cfg.configDir} 191 pushd ${cfg.configDir} 192 if [ -e printer.cfg ]; then 193 ${ 194 if cfg.mutableConfig then 195 ":" 196 else 197 '' 198 # Backup existing config using the same date format klipper uses for SAVE_CONFIG 199 old_config="printer-$(date +"%Y%m%d_%H%M%S").cfg" 200 mv printer.cfg "$old_config" 201 # Preserve SAVE_CONFIG section from the existing config 202 cat ${printerConfig} <(printf "\n") <(sed -n '/#*# <---------------------- SAVE_CONFIG ---------------------->/,$p' "$old_config") > printer.cfg 203 ${pkgs.diffutils}/bin/cmp printer.cfg "$old_config" && rm "$old_config" 204 '' 205 } 206 else 207 cat ${printerConfig} > printer.cfg 208 fi 209 popd 210 ''; 211 212 restartTriggers = lib.optional (!cfg.mutableConfig) [ printerConfig ]; 213 214 serviceConfig = 215 { 216 ExecStart = "${cfg.package}/bin/klippy ${klippyArgs} ${cfg.configDir}/printer.cfg"; 217 RuntimeDirectory = "klipper"; 218 StateDirectory = "klipper"; 219 SupplementaryGroups = [ "dialout" ]; 220 WorkingDirectory = "${cfg.package}/lib"; 221 OOMScoreAdjust = "-999"; 222 CPUSchedulingPolicy = "rr"; 223 CPUSchedulingPriority = 99; 224 IOSchedulingClass = "realtime"; 225 IOSchedulingPriority = 0; 226 UMask = "0002"; 227 } 228 // ( 229 if cfg.user != null then 230 { 231 Group = cfg.group; 232 User = cfg.user; 233 } 234 else 235 { 236 DynamicUser = true; 237 User = "klipper"; 238 } 239 ); 240 }; 241 242 environment.systemPackages = 243 let 244 default = a: b: if a != null then a else b; 245 genconf = pkgs.klipper-genconf.override { 246 klipper = cfg.package; 247 }; 248 firmwares = lib.filterAttrs (n: v: v != null) ( 249 lib.mapAttrs ( 250 mcu: 251 { 252 enable, 253 enableKlipperFlash, 254 configFile, 255 serial, 256 }: 257 if enable then 258 pkgs.klipper-firmware.override { 259 klipper = cfg.package; 260 mcu = lib.strings.sanitizeDerivationName mcu; 261 firmwareConfig = configFile; 262 } 263 else 264 null 265 ) cfg.firmwares 266 ); 267 firmwareFlasher = lib.mapAttrsToList ( 268 mcu: firmware: 269 pkgs.klipper-flash.override { 270 klipper = cfg.package; 271 klipper-firmware = firmware; 272 mcu = lib.strings.sanitizeDerivationName mcu; 273 flashDevice = default cfg.firmwares."${mcu}".serial cfg.settings."${mcu}".serial; 274 firmwareConfig = cfg.firmwares."${mcu}".configFile; 275 } 276 ) (lib.filterAttrs (mcu: firmware: cfg.firmwares."${mcu}".enableKlipperFlash) firmwares); 277 in 278 [ genconf ] ++ firmwareFlasher ++ lib.attrValues firmwares; 279 }; 280 meta.maintainers = [ 281 lib.maintainers.cab404 282 ]; 283}