1 { config, lib, options, pkgs, ... }:
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
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 {} ":";
16 services.moonraker = {
17 enable = mkEnableOption (lib.mdDoc "Moonraker, an API web server for Klipper");
19 klipperSocket = mkOption {
21 default = config.services.klipper.apiSocket;
22 defaultText = literalExpression "config.services.klipper.apiSocket";
23 description = lib.mdDoc "Path to Klipper's API socket.";
28 default = "/var/lib/moonraker";
29 description = lib.mdDoc "The directory containing the Moonraker databases.";
32 configDir = mkOption {
34 default = cfg.stateDir + "/config";
35 defaultText = literalExpression ''config.${opt.stateDir} + "/config"'';
36 description = lib.mdDoc ''
37 The directory containing client-writable configuration files.
39 Clients will be able to edit files in this directory via the API. This directory must be writable.
45 default = "moonraker";
46 description = lib.mdDoc "User account under which Moonraker runs.";
51 default = "moonraker";
52 description = lib.mdDoc "Group account under which Moonraker runs.";
57 default = "127.0.0.1";
59 description = lib.mdDoc "The IP or host to listen on.";
63 type = types.ints.unsigned;
65 description = lib.mdDoc "The port to listen on.";
73 trusted_clients = [ "10.0.0.0/24" ];
74 cors_domains = [ "https://app.fluidd.xyz" ];
77 description = lib.mdDoc ''
78 Configuration for Moonraker. See the [documentation](https://moonraker.readthedocs.io/en/latest/configuration/)
83 allowSystemControl = mkOption {
86 description = lib.mdDoc ''
87 Whether to allow Moonraker to perform system-level operations.
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.
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.'';
104 assertion = cfg.allowSystemControl -> config.security.polkit.enable;
105 message = "services.moonraker.allowSystemControl requires polkit to be enabled (security.polkit.enable).";
109 users.users = optionalAttrs (cfg.user == "moonraker") {
112 uid = config.ids.uids.moonraker;
116 users.groups = optionalAttrs (cfg.group == "moonraker") {
117 moonraker.gid = config.ids.gids.moonraker;
120 environment.etc."moonraker.cfg".source = let
125 klippy_uds_address = cfg.klipperSocket;
128 config_path = cfg.configDir;
131 database_path = "${cfg.stateDir}/database";
134 fullConfig = recursiveUpdate cfg.settings forcedConfig;
135 in format.generate "moonraker.cfg" fullConfig;
137 systemd.tmpfiles.rules = [
138 "d '${cfg.stateDir}' - ${cfg.user} ${cfg.group} - -"
139 "d '${cfg.configDir}' - ${cfg.user} ${cfg.group} - -"
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";
148 # Moonraker really wants its own config to be writable...
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
156 path = [ pkgs.iproute2 ];
159 WorkingDirectory = cfg.stateDir;
166 security.polkit.extraConfig = lib.optionalString cfg.allowSystemControl ''
167 // nixos/moonraker: Allow Moonraker to perform system-level operations
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;
184 meta.maintainers = with maintainers; [