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