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