at 25.11-pre 8.4 kB view raw
1{ 2 config, 3 lib, 4 options, 5 pkgs, 6 ... 7}: 8let 9 cfg = config.services.moonraker; 10 pkg = cfg.package; 11 opt = options.services.moonraker; 12 format = pkgs.formats.ini { 13 # https://github.com/NixOS/nixpkgs/pull/121613#issuecomment-885241996 14 listToValue = 15 l: 16 if builtins.length l == 1 then 17 lib.generators.mkValueStringDefault { } (lib.head l) 18 else 19 lib.concatMapStrings (s: "\n ${lib.generators.mkValueStringDefault { } s}") l; 20 mkKeyValue = lib.generators.mkKeyValueDefault { } ":"; 21 }; 22 23 unifiedConfigDir = cfg.stateDir + "/config"; 24in 25{ 26 options = { 27 services.moonraker = { 28 enable = lib.mkEnableOption "Moonraker, an API web server for Klipper"; 29 30 package = lib.mkPackageOption pkgs "moonraker" { 31 nullable = true; 32 example = "moonraker.override { useGpiod = true; }"; 33 }; 34 35 klipperSocket = lib.mkOption { 36 type = lib.types.path; 37 default = config.services.klipper.apiSocket; 38 defaultText = lib.literalExpression "config.services.klipper.apiSocket"; 39 description = "Path to Klipper's API socket."; 40 }; 41 42 stateDir = lib.mkOption { 43 type = lib.types.path; 44 default = "/var/lib/moonraker"; 45 description = "The directory containing the Moonraker databases."; 46 }; 47 48 configDir = lib.mkOption { 49 type = lib.types.nullOr lib.types.path; 50 default = null; 51 description = '' 52 Deprecated directory containing client-writable configuration files. 53 54 Clients will be able to edit files in this directory via the API. This directory must be writable. 55 ''; 56 }; 57 58 user = lib.mkOption { 59 type = lib.types.str; 60 default = "moonraker"; 61 description = "User account under which Moonraker runs."; 62 }; 63 64 group = lib.mkOption { 65 type = lib.types.str; 66 default = "moonraker"; 67 description = "Group account under which Moonraker runs."; 68 }; 69 70 address = lib.mkOption { 71 type = lib.types.str; 72 default = "127.0.0.1"; 73 example = "0.0.0.0"; 74 description = "The IP or host to listen on."; 75 }; 76 77 port = lib.mkOption { 78 type = lib.types.ints.unsigned; 79 default = 7125; 80 description = "The port to listen on."; 81 }; 82 83 settings = lib.mkOption { 84 type = format.type; 85 default = { }; 86 example = { 87 authorization = { 88 trusted_clients = [ "10.0.0.0/24" ]; 89 cors_domains = [ 90 "https://app.fluidd.xyz" 91 "https://my.mainsail.xyz" 92 ]; 93 }; 94 }; 95 description = '' 96 Configuration for Moonraker. See the [documentation](https://moonraker.readthedocs.io/en/latest/configuration/) 97 for supported values. 98 ''; 99 }; 100 101 allowSystemControl = lib.mkOption { 102 type = lib.types.bool; 103 default = false; 104 description = '' 105 Whether to allow Moonraker to perform system-level operations. 106 107 Moonraker exposes APIs to perform system-level operations, such as 108 reboot, shutdown, and management of systemd units. See the 109 [documentation](https://moonraker.readthedocs.io/en/latest/web_api/#machine-commands) 110 for details on what clients are able to do. 111 ''; 112 }; 113 114 analysis.enable = lib.mkEnableOption "Runtime analysis with klipper-estimator"; 115 }; 116 }; 117 118 config = lib.mkIf cfg.enable { 119 warnings = 120 [ ] 121 ++ (lib.optional (lib.head (cfg.settings.update_manager.enable_system_updates or [ false ])) '' 122 Enabling system updates is not supported on NixOS and will lead to non-removable warnings in some clients. 123 '') 124 ++ (lib.optional (cfg.configDir != null) '' 125 services.moonraker.configDir has been deprecated upstream and will be removed. 126 127 Action: ${ 128 if cfg.configDir == unifiedConfigDir then 129 "Simply remove services.moonraker.configDir from your config." 130 else 131 "Move files from `${cfg.configDir}` to `${unifiedConfigDir}` then remove services.moonraker.configDir from your config." 132 } 133 ''); 134 135 assertions = [ 136 { 137 assertion = cfg.allowSystemControl -> config.security.polkit.enable; 138 message = "services.moonraker.allowSystemControl requires polkit to be enabled (security.polkit.enable)."; 139 } 140 ]; 141 142 users.users = lib.optionalAttrs (cfg.user == "moonraker") { 143 moonraker = { 144 group = cfg.group; 145 uid = config.ids.uids.moonraker; 146 }; 147 }; 148 149 users.groups = lib.optionalAttrs (cfg.group == "moonraker") { 150 moonraker.gid = config.ids.gids.moonraker; 151 }; 152 153 environment.etc."moonraker.cfg".source = 154 let 155 forcedConfig = 156 { 157 server = { 158 host = cfg.address; 159 port = cfg.port; 160 klippy_uds_address = cfg.klipperSocket; 161 }; 162 machine = { 163 validate_service = false; 164 }; 165 } 166 // (lib.optionalAttrs (cfg.configDir != null) { 167 file_manager = { 168 config_path = cfg.configDir; 169 }; 170 }); 171 fullConfig = lib.recursiveUpdate cfg.settings forcedConfig; 172 in 173 format.generate "moonraker.cfg" fullConfig; 174 175 systemd.tmpfiles.rules = 176 [ 177 "d '${cfg.stateDir}' - ${cfg.user} ${cfg.group} - -" 178 ] 179 ++ lib.optional (cfg.configDir != null) "d '${cfg.configDir}' - ${cfg.user} ${cfg.group} - -" 180 ++ lib.optionals cfg.analysis.enable [ 181 "d '${cfg.stateDir}/tools/klipper_estimator' - ${cfg.user} ${cfg.group} - -" 182 "L+ '${cfg.stateDir}/tools/klipper_estimator/klipper_estimator_linux' - - - - ${lib.getExe pkgs.klipper-estimator}" 183 ]; 184 185 systemd.services.moonraker = { 186 description = "Moonraker, an API web server for Klipper"; 187 wantedBy = [ "multi-user.target" ]; 188 after = [ "network.target" ] ++ lib.optional config.services.klipper.enable "klipper.service"; 189 190 # Moonraker really wants its own config to be writable... 191 script = '' 192 config_path=${ 193 # Deprecated separate config dir 194 if cfg.configDir != null then 195 "${cfg.configDir}/moonraker-temp.cfg" 196 # Config in unified data path 197 else 198 "${unifiedConfigDir}/moonraker-temp.cfg" 199 } 200 mkdir -p $(dirname "$config_path") 201 cp /etc/moonraker.cfg "$config_path" 202 chmod u+w "$config_path" 203 exec ${pkg}/bin/moonraker -d ${cfg.stateDir} -c "$config_path" 204 ''; 205 206 # Needs `ip` command 207 path = [ pkgs.iproute2 ]; 208 209 serviceConfig = { 210 WorkingDirectory = cfg.stateDir; 211 PrivateTmp = true; 212 Group = cfg.group; 213 User = cfg.user; 214 }; 215 }; 216 217 services.moonraker.settings = { 218 # set this to false, otherwise we'll get a warning indicating that `/etc/klipper.cfg` 219 # is not located in the moonraker config directory. 220 file_manager.check_klipper_config_path = lib.mkIf (!config.services.klipper.mutableConfig) false; 221 222 # enable analysis with our own klipper-estimator, disable updating it 223 analysis = lib.mkIf (cfg.analysis.enable) { 224 platform = "linux"; 225 enable_estimator_updates = false; 226 }; 227 # suppress PolicyKit warnings if system control is disabled 228 machine.provider = lib.mkIf (!cfg.allowSystemControl) (lib.mkDefault "none"); 229 }; 230 231 security.polkit.extraConfig = lib.optionalString cfg.allowSystemControl '' 232 // nixos/moonraker: Allow Moonraker to perform system-level operations 233 // 234 // This was enabled via services.moonraker.allowSystemControl. 235 polkit.addRule(function(action, subject) { 236 if ((action.id == "org.freedesktop.systemd1.manage-units" || 237 action.id == "org.freedesktop.login1.power-off" || 238 action.id == "org.freedesktop.login1.power-off-multiple-sessions" || 239 action.id == "org.freedesktop.login1.reboot" || 240 action.id == "org.freedesktop.login1.reboot-multiple-sessions" || 241 action.id.startsWith("org.freedesktop.packagekit.")) && 242 subject.user == "${cfg.user}") { 243 return polkit.Result.YES; 244 } 245 }); 246 ''; 247 }; 248 249 meta.maintainers = with lib.maintainers; [ 250 cab404 251 vtuan10 252 zhaofengli 253 ]; 254}