1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8
9 cfg = config.services.octoprint;
10
11 baseConfig = lib.recursiveUpdate {
12 plugins.curalegacy.cura_engine = "${pkgs.curaengine_stable}/bin/CuraEngine";
13 server.port = cfg.port;
14 webcam.ffmpeg = "${pkgs.ffmpeg.bin}/bin/ffmpeg";
15 } (lib.optionalAttrs (cfg.host != null) { server.host = cfg.host; });
16
17 fullConfig = lib.recursiveUpdate cfg.extraConfig baseConfig;
18
19 cfgUpdate = pkgs.writeText "octoprint-config.yaml" (builtins.toJSON fullConfig);
20
21 pluginsEnv = cfg.package.python.withPackages (ps: [ ps.octoprint ] ++ (cfg.plugins ps));
22
23in
24{
25 ##### interface
26
27 options = {
28
29 services.octoprint = {
30
31 package = lib.mkPackageOption pkgs "octoprint" { };
32
33 enable = lib.mkEnableOption "OctoPrint, web interface for 3D printers";
34
35 host = lib.mkOption {
36 type = lib.types.nullOr lib.types.str;
37 default = null;
38 description = ''
39 Host to bind OctoPrint to.
40 '';
41 };
42
43 port = lib.mkOption {
44 type = lib.types.port;
45 default = 5000;
46 description = ''
47 Port to bind OctoPrint to.
48 '';
49 };
50
51 openFirewall = lib.mkOption {
52 type = lib.types.bool;
53 default = false;
54 description = "Open ports in the firewall for OctoPrint.";
55 };
56
57 user = lib.mkOption {
58 type = lib.types.str;
59 default = "octoprint";
60 description = "User for the daemon.";
61 };
62
63 group = lib.mkOption {
64 type = lib.types.str;
65 default = "octoprint";
66 description = "Group for the daemon.";
67 };
68
69 stateDir = lib.mkOption {
70 type = lib.types.path;
71 default = "/var/lib/octoprint";
72 description = "State directory of the daemon.";
73 };
74
75 plugins = lib.mkOption {
76 type = lib.types.functionTo (lib.types.listOf lib.types.package);
77 default = plugins: [ ];
78 defaultText = lib.literalExpression "plugins: []";
79 example = lib.literalExpression "plugins: with plugins; [ themeify stlviewer ]";
80 description = "Additional plugins to be used. Available plugins are passed through the plugins input.";
81 };
82
83 extraConfig = lib.mkOption {
84 type = lib.types.attrs;
85 default = { };
86 description = "Extra options which are added to OctoPrint's YAML configuration file.";
87 };
88
89 };
90
91 };
92
93 ##### implementation
94
95 config = lib.mkIf cfg.enable {
96
97 users.users = lib.optionalAttrs (cfg.user == "octoprint") {
98 octoprint = {
99 group = cfg.group;
100 uid = config.ids.uids.octoprint;
101 };
102 };
103
104 users.groups = lib.optionalAttrs (cfg.group == "octoprint") {
105 octoprint.gid = config.ids.gids.octoprint;
106 };
107
108 systemd.tmpfiles.rules = [
109 "d '${cfg.stateDir}' - ${cfg.user} ${cfg.group} - -"
110 # this will allow octoprint access to raspberry specific hardware to check for throttling
111 # read-only will not work: "VCHI initialization failed" error
112 "a /dev/vchiq - - - - u:octoprint:rw"
113 ];
114
115 systemd.services.octoprint = {
116 description = "OctoPrint, web interface for 3D printers";
117 wantedBy = [ "multi-user.target" ];
118 after = [ "network.target" ];
119 path = [ pluginsEnv ];
120
121 preStart = ''
122 if [ -e "${cfg.stateDir}/config.yaml" ]; then
123 ${pkgs.yaml-merge}/bin/yaml-merge "${cfg.stateDir}/config.yaml" "${cfgUpdate}" > "${cfg.stateDir}/config.yaml.tmp"
124 mv "${cfg.stateDir}/config.yaml.tmp" "${cfg.stateDir}/config.yaml"
125 else
126 cp "${cfgUpdate}" "${cfg.stateDir}/config.yaml"
127 chmod 600 "${cfg.stateDir}/config.yaml"
128 fi
129 '';
130
131 serviceConfig = {
132 ExecStart = "${pluginsEnv}/bin/octoprint serve -b ${cfg.stateDir}";
133 User = cfg.user;
134 Group = cfg.group;
135 SupplementaryGroups = [
136 "dialout"
137 ];
138 };
139 };
140
141 networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ cfg.port ];
142 };
143}