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