at master 8.3 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.port; 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 server = { 157 host = cfg.address; 158 port = cfg.port; 159 klippy_uds_address = cfg.klipperSocket; 160 }; 161 machine = { 162 validate_service = false; 163 }; 164 } 165 // (lib.optionalAttrs (cfg.configDir != null) { 166 file_manager = { 167 config_path = cfg.configDir; 168 }; 169 }); 170 fullConfig = lib.recursiveUpdate cfg.settings forcedConfig; 171 in 172 format.generate "moonraker.cfg" fullConfig; 173 174 systemd.tmpfiles.rules = [ 175 "d '${cfg.stateDir}' - ${cfg.user} ${cfg.group} - -" 176 ] 177 ++ lib.optional (cfg.configDir != null) "d '${cfg.configDir}' - ${cfg.user} ${cfg.group} - -" 178 ++ lib.optionals cfg.analysis.enable [ 179 "d '${cfg.stateDir}/tools/klipper_estimator' - ${cfg.user} ${cfg.group} - -" 180 "L+ '${cfg.stateDir}/tools/klipper_estimator/klipper_estimator_linux' - - - - ${lib.getExe pkgs.klipper-estimator}" 181 ]; 182 183 systemd.services.moonraker = { 184 description = "Moonraker, an API web server for Klipper"; 185 wantedBy = [ "multi-user.target" ]; 186 after = [ "network.target" ] ++ lib.optional config.services.klipper.enable "klipper.service"; 187 188 # Moonraker really wants its own config to be writable... 189 script = '' 190 config_path=${ 191 # Deprecated separate config dir 192 if cfg.configDir != null then 193 "${cfg.configDir}/moonraker-temp.cfg" 194 # Config in unified data path 195 else 196 "${unifiedConfigDir}/moonraker-temp.cfg" 197 } 198 mkdir -p $(dirname "$config_path") 199 cp /etc/moonraker.cfg "$config_path" 200 chmod u+w "$config_path" 201 exec ${pkg}/bin/moonraker -d ${cfg.stateDir} -c "$config_path" 202 ''; 203 204 # Needs `ip` command 205 path = [ pkgs.iproute2 ]; 206 207 serviceConfig = { 208 WorkingDirectory = cfg.stateDir; 209 PrivateTmp = true; 210 Group = cfg.group; 211 User = cfg.user; 212 }; 213 }; 214 215 services.moonraker.settings = { 216 # set this to false, otherwise we'll get a warning indicating that `/etc/klipper.cfg` 217 # is not located in the moonraker config directory. 218 file_manager.check_klipper_config_path = lib.mkIf (!config.services.klipper.mutableConfig) false; 219 220 # enable analysis with our own klipper-estimator, disable updating it 221 analysis = lib.mkIf (cfg.analysis.enable) { 222 platform = "linux"; 223 enable_estimator_updates = false; 224 }; 225 # suppress PolicyKit warnings if system control is disabled 226 machine.provider = lib.mkIf (!cfg.allowSystemControl) (lib.mkDefault "none"); 227 }; 228 229 security.polkit.extraConfig = lib.optionalString cfg.allowSystemControl '' 230 // nixos/moonraker: Allow Moonraker to perform system-level operations 231 // 232 // This was enabled via services.moonraker.allowSystemControl. 233 polkit.addRule(function(action, subject) { 234 if ((action.id == "org.freedesktop.systemd1.manage-units" || 235 action.id == "org.freedesktop.login1.power-off" || 236 action.id == "org.freedesktop.login1.power-off-multiple-sessions" || 237 action.id == "org.freedesktop.login1.reboot" || 238 action.id == "org.freedesktop.login1.reboot-multiple-sessions" || 239 action.id.startsWith("org.freedesktop.packagekit.")) && 240 subject.user == "${cfg.user}") { 241 return polkit.Result.YES; 242 } 243 }); 244 ''; 245 }; 246 247 meta.maintainers = with lib.maintainers; [ 248 cab404 249 vtuan10 250 zhaofengli 251 ]; 252}