1{
2 lib,
3 pkgs,
4 config,
5 ...
6}:
7
8let
9 cfg = config.services.prefect;
10 inherit (lib.types)
11 bool
12 str
13 enum
14 path
15 attrsOf
16 nullOr
17 submodule
18 port
19 ;
20
21in
22{
23 options.services.prefect = {
24 enable = lib.mkOption {
25 type = bool;
26 default = false;
27 description = "enable prefect server and worker services";
28 };
29
30 package = lib.mkPackageOption pkgs "prefect" { };
31
32 host = lib.mkOption {
33 type = str;
34 default = "127.0.0.1";
35 example = "0.0.0.0";
36 description = "Prefect server host";
37 };
38
39 port = lib.mkOption {
40 type = port;
41 default = 4200;
42 description = "Prefect server port";
43 };
44
45 dataDir = lib.mkOption {
46 type = path;
47 default = "/var/lib/prefect-server";
48 description = ''
49 Specify the directory for Prefect.
50 '';
51 };
52
53 database = lib.mkOption {
54 type = enum [
55 "sqlite"
56 "postgres"
57 ];
58 default = "sqlite";
59 description = "which database to use for prefect server: sqlite or postgres";
60 };
61
62 databaseHost = lib.mkOption {
63 type = str;
64 default = "localhost";
65 description = "database host for postgres only";
66 };
67
68 databasePort = lib.mkOption {
69 type = str;
70 default = "5432";
71 description = "database port for postgres only";
72 };
73
74 databaseName = lib.mkOption {
75 type = str;
76 default = "prefect";
77 description = "database name for postgres only";
78 };
79
80 databaseUser = lib.mkOption {
81 type = str;
82 default = "postgres";
83 description = "database user for postgres only";
84 };
85
86 databasePasswordFile = lib.mkOption {
87 type = nullOr str;
88 default = null;
89 description = ''
90 path to a file containing e.g.:
91 DBPASSWORD=supersecret
92
93 stored outside the nix store, read by systemd as EnvironmentFile.
94 '';
95 };
96
97 # now define workerPools as an attribute set of submodules,
98 # each key is the pool name, and the submodule has an installPolicy
99 workerPools = lib.mkOption {
100 type = attrsOf (submodule {
101 options = {
102 installPolicy = lib.mkOption {
103 type = enum [
104 "always"
105 "if-not-present"
106 "never"
107 "prompt"
108 ];
109 default = "always";
110 description = "install policy for the worker (always, if-not-present, never, prompt)";
111 };
112 };
113 });
114 default = { };
115 description = ''
116 define a set of worker pools with submodule config. example:
117 workerPools.my-pool = {
118 installPolicy = "never";
119 };
120 '';
121 };
122
123 baseUrl = lib.mkOption {
124 type = nullOr str;
125 default = null;
126 description = "external url when served by a reverse proxy, e.g. https://example.com/prefect";
127 };
128 };
129
130 config = lib.mkIf cfg.enable {
131 # define systemd.services as the server plus any worker definitions
132 systemd.services =
133 {
134 "prefect-server" = {
135 description = "prefect server";
136 wantedBy = [ "multi-user.target" ];
137 after = [ "network.target" ];
138
139 serviceConfig = {
140 DynamicUser = true;
141 StateDirectory = "prefect-server";
142 # TODO all my efforts to setup the database url
143 # have failed with some unable to open file
144 Environment = [
145 "PREFECT_HOME=%S/prefect-server"
146 "PREFECT_UI_STATIC_DIRECTORY=%S/prefect-server"
147 "PREFECT_SERVER_ANALYTICS_ENABLED=off"
148 "PREFECT_UI_API_URL=${cfg.baseUrl}/api"
149 "PREFECT_UI_URL=${cfg.baseUrl}"
150 ];
151 EnvironmentFile =
152 if cfg.database == "postgres" && cfg.databasePasswordFile != null then
153 [ cfg.databasePasswordFile ]
154 else
155 [ ];
156
157 # ReadWritePaths = [ cfg.dataDir ];
158 ProtectSystem = "strict";
159 ProtectHome = true;
160 PrivateTmp = true;
161 NoNewPrivileges = true;
162 MemoryDenyWriteExecute = true;
163 LockPersonality = true;
164 CapabilityBoundingSet = [ ];
165 AmbientCapabilities = [ ];
166 RestrictSUIDSGID = true;
167 RestrictAddressFamilies = [
168 "AF_INET"
169 "AF_INET6"
170 "AF_UNIX"
171 ];
172 ProtectKernelTunables = true;
173 ProtectKernelModules = true;
174 ProtectKernelLogs = true;
175 ProtectControlGroups = true;
176 MemoryAccounting = true;
177 CPUAccounting = true;
178
179 ExecStart = "${pkgs.prefect}/bin/prefect server start --host ${cfg.host} --port ${toString cfg.port}";
180 Restart = "always";
181 WorkingDirectory = cfg.dataDir;
182 };
183 };
184 }
185 // lib.concatMapAttrs (poolName: poolCfg: {
186 # return a partial attr set with one key: "prefect-worker-..."
187 "prefect-worker-${poolName}" = {
188 description = "prefect worker for pool '${poolName}'";
189 wantedBy = [ "multi-user.target" ];
190 after = [ "network.target" ];
191
192 environment.systemPackages = cfg.package;
193
194 serviceConfig = {
195 DynamicUser = true;
196 StateDirectory = "prefect-worker-${poolName}";
197 Environment = [
198 "PREFECT_HOME=%S/prefect-worker-${poolName}"
199 "PREFECT_API_URL=${cfg.baseUrl}/api"
200 ];
201 ProtectSystem = "strict";
202 ProtectHome = true;
203 PrivateTmp = true;
204 NoNewPrivileges = true;
205 MemoryDenyWriteExecute = true;
206 LockPersonality = true;
207 CapabilityBoundingSet = [ ];
208 AmbientCapabilities = [ ];
209 RestrictSUIDSGID = true;
210 RestrictAddressFamilies = [
211 "AF_INET"
212 "AF_INET6"
213 "AF_UNIX"
214 ];
215 ProtectKernelTunables = true;
216 ProtectKernelModules = true;
217 ProtectKernelLogs = true;
218 ProtectControlGroups = true;
219 MemoryAccounting = true;
220 CPUAccounting = true;
221 ExecStart = ''
222 ${pkgs.prefect}/bin/prefect worker start \
223 --pool ${poolName} \
224 --type process \
225 --install-policy ${poolCfg.installPolicy}
226 '';
227 Restart = "always";
228 };
229 };
230 }) cfg.workerPools;
231 };
232}