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