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