1{ config, lib, pkgs, ... }:
2with lib;
3let
4 pkg = pkgs.moonraker;
5 cfg = config.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 generators.mkValueStringDefault {} (head l)
10 else lib.concatMapStrings (s: "\n ${generators.mkValueStringDefault {} s}") l;
11 mkKeyValue = generators.mkKeyValueDefault {} ":";
12 };
13in {
14 options = {
15 services.moonraker = {
16 enable = mkEnableOption "Moonraker, an API web server for Klipper";
17
18 klipperSocket = mkOption {
19 type = types.path;
20 default = config.services.klipper.apiSocket;
21 description = "Path to Klipper's API socket.";
22 };
23
24 stateDir = mkOption {
25 type = types.path;
26 default = "/var/lib/moonraker";
27 description = "The directory containing the Moonraker databases.";
28 };
29
30 configDir = mkOption {
31 type = types.path;
32 default = cfg.stateDir + "/config";
33 description = ''
34 The directory containing client-writable configuration files.
35
36 Clients will be able to edit files in this directory via the API. This directory must be writable.
37 '';
38 };
39
40 user = mkOption {
41 type = types.str;
42 default = "moonraker";
43 description = "User account under which Moonraker runs.";
44 };
45
46 group = mkOption {
47 type = types.str;
48 default = "moonraker";
49 description = "Group account under which Moonraker runs.";
50 };
51
52 address = mkOption {
53 type = types.str;
54 default = "127.0.0.1";
55 example = "0.0.0.0";
56 description = "The IP or host to listen on.";
57 };
58
59 port = mkOption {
60 type = types.ints.unsigned;
61 default = 7125;
62 description = "The port to listen on.";
63 };
64
65 settings = mkOption {
66 type = format.type;
67 default = { };
68 example = {
69 authorization = {
70 trusted_clients = [ "10.0.0.0/24" ];
71 cors_domains = [ "https://app.fluidd.xyz" ];
72 };
73 };
74 description = ''
75 Configuration for Moonraker. See the <link xlink:href="https://moonraker.readthedocs.io/en/latest/configuration/">documentation</link>
76 for supported values.
77 '';
78 };
79 };
80 };
81
82 config = mkIf cfg.enable {
83 warnings = optional (cfg.settings ? update_manager)
84 ''Enabling update_manager is not supported on NixOS and will lead to non-removable warnings in some clients.'';
85
86 users.users = optionalAttrs (cfg.user == "moonraker") {
87 moonraker = {
88 group = cfg.group;
89 uid = config.ids.uids.moonraker;
90 };
91 };
92
93 users.groups = optionalAttrs (cfg.group == "moonraker") {
94 moonraker.gid = config.ids.gids.moonraker;
95 };
96
97 environment.etc."moonraker.cfg".source = let
98 forcedConfig = {
99 server = {
100 host = cfg.address;
101 port = cfg.port;
102 klippy_uds_address = cfg.klipperSocket;
103 config_path = cfg.configDir;
104 database_path = "${cfg.stateDir}/database";
105 };
106 };
107 fullConfig = recursiveUpdate cfg.settings forcedConfig;
108 in format.generate "moonraker.cfg" fullConfig;
109
110 systemd.tmpfiles.rules = [
111 "d '${cfg.stateDir}' - ${cfg.user} ${cfg.group} - -"
112 "d '${cfg.configDir}' - ${cfg.user} ${cfg.group} - -"
113 ];
114
115 systemd.services.moonraker = {
116 description = "Moonraker, an API web server for Klipper";
117 wantedBy = [ "multi-user.target" ];
118 after = [ "network.target" ]
119 ++ optional config.services.klipper.enable "klipper.service";
120
121 # Moonraker really wants its own config to be writable...
122 script = ''
123 cp /etc/moonraker.cfg ${cfg.configDir}/moonraker-temp.cfg
124 chmod u+w ${cfg.configDir}/moonraker-temp.cfg
125 exec ${pkg}/bin/moonraker -c ${cfg.configDir}/moonraker-temp.cfg
126 '';
127
128 serviceConfig = {
129 WorkingDirectory = cfg.stateDir;
130 Group = cfg.group;
131 User = cfg.user;
132 };
133 };
134 };
135}