forked from
tranquil.farm/tranquil-pds
Our Personal Data Server from scratch!
7.6 kB
263 lines
1{
2 lib,
3 pkgs,
4 config,
5 ...
6}:
7let
8 cfg = config.services.tranquil-pds;
9
10 inherit (lib) types mkOption;
11
12 settingsFormat = pkgs.formats.toml { };
13in
14{
15 _class = "nixos";
16
17 options.services.tranquil-pds = {
18 enable = lib.mkEnableOption "tranquil-pds AT Protocol personal data server";
19
20 package = mkOption {
21 type = types.package;
22 default = pkgs.callPackage ./default.nix { };
23 defaultText = lib.literalExpression "pkgs.tranquil-pds";
24 description = "The tranquil-pds package to use";
25 };
26
27 user = mkOption {
28 type = types.str;
29 default = "tranquil-pds";
30 description = "User under which tranquil-pds runs";
31 };
32
33 group = mkOption {
34 type = types.str;
35 default = "tranquil-pds";
36 description = "Group under which tranquil-pds runs";
37 };
38
39 dataDir = mkOption {
40 type = types.str;
41 default = "/var/lib/tranquil-pds";
42 description = "Working directory for tranquil-pds. Also expected to be used for data (blobs)";
43 };
44
45 environmentFiles = mkOption {
46 type = types.listOf types.path;
47 default = [ ];
48 description = ''
49 File to load environment variables from. Loaded variables override
50 values set in {option}`environment`.
51
52 Use it to set values of `JWT_SECRET`, `DPOP_SECRET` and `MASTER_KEY`.
53
54 Generate these with:
55 ```
56 openssl rand -base64 48
57 ```
58 '';
59 };
60
61 database.createLocally = mkOption {
62 type = types.bool;
63 default = false;
64 description = ''
65 Create the postgres database and user on the local host.
66 '';
67 };
68
69 settings = mkOption {
70 type = types.submodule {
71 freeformType = settingsFormat.type;
72
73 options = {
74 server = {
75 host = mkOption {
76 type = types.str;
77 default = "127.0.0.1";
78 description = "Host for tranquil-pds to listen on";
79 };
80
81 port = mkOption {
82 type = types.int;
83 default = 3000;
84 description = "Port for tranquil-pds to listen on";
85 };
86
87 hostname = mkOption {
88 type = types.str;
89 default = "";
90 example = "pds.example.com";
91 description = "The public-facing hostname of the PDS";
92 };
93
94 max_blob_size = mkOption {
95 type = types.int;
96 default = 10737418240; # 10 GiB
97 description = "Maximum allowed blob size in bytes.";
98 };
99 };
100
101 frontend = {
102 enabled =
103 lib.mkEnableOption "serving the frontend from the backend. Disable to serve the frontend manually"
104 // {
105 default = true;
106 };
107
108 dir = mkOption {
109 type = types.nullOr types.package;
110 default = pkgs.callPackage ./frontend.nix { };
111 defaultText = lib.literalExpression "pkgs.tranquil-frontend";
112 description = "Frontend package to be served by the backend";
113 };
114 };
115
116 storage = {
117 path = mkOption {
118 type = types.path;
119 default = "${cfg.dataDir}/blobs";
120 defaultText = "\${cfg.dataDir}/blobs";
121 description = "Directory for storing blobs";
122 };
123 };
124
125 tranquil_store = {
126 data_dir = mkOption {
127 type = types.path;
128 default = "${cfg.dataDir}/store";
129 defaultText = "\${cfg.dataDir}/store";
130 description = "Directory for tranquil-store files";
131 };
132 };
133 };
134 };
135
136 description = ''
137 Configuration options to set for the service. Secrets should be
138 specified using {option}`environmentFile`.
139
140 Refer to <https://tangled.org/tranquil.farm/tranquil-pds/blob/main/example.toml>
141 for available configuration options.
142 '';
143 };
144 };
145
146 config = lib.mkIf cfg.enable (
147 lib.mkMerge [
148 (lib.mkIf cfg.database.createLocally {
149 services.postgresql = {
150 enable = true;
151 ensureDatabases = [ cfg.user ];
152 ensureUsers = [
153 {
154 name = cfg.user;
155 ensureDBOwnership = true;
156 }
157 ];
158 };
159
160 services.tranquil-pds.settings.database.url =
161 lib.mkDefault "postgresql:///${cfg.user}?host=/run/postgresql";
162
163 systemd.services.tranquil-pds = {
164 requires = [ "postgresql.service" ];
165 after = [ "postgresql.service" ];
166 };
167 })
168
169 {
170 users.users.${cfg.user} = {
171 isSystemUser = true;
172 inherit (cfg) group;
173 home = cfg.dataDir;
174 };
175
176 users.groups.${cfg.group} = { };
177
178 # TODO: probably should split these out so only the directories that are actually used made and configured
179 systemd.tmpfiles.settings."tranquil-pds" =
180 lib.genAttrs
181 [
182 cfg.dataDir
183 cfg.settings.storage.path
184 cfg.settings.tranquil_store.data_dir
185 ]
186 (_: {
187 d = {
188 mode = "0750";
189 inherit (cfg) user group;
190 };
191 });
192
193 environment.etc = {
194 "tranquil-pds/config.toml".source =
195 let
196 conf = settingsFormat.generate "tranquil-pds.toml" cfg.settings;
197 in
198 pkgs.runCommandLocal "validated-tranquil-config" { nativeBuildInputs = [ cfg.package ]; } ''
199 tranquil-server --config ${conf} validate --ignore-secrets
200 ln -s ${conf} $out
201 '';
202 };
203
204 systemd.services.tranquil-pds = {
205 description = "Tranquil PDS - AT Protocol Personal Data Server";
206 after = [ "network-online.target" ];
207 wants = [ "network-online.target" ];
208 wantedBy = [ "multi-user.target" ];
209
210 serviceConfig = {
211 User = cfg.user;
212 Group = cfg.group;
213 UMask = "0077";
214 ExecStart = lib.getExe cfg.package;
215 Restart = "on-failure";
216 RestartSec = 5;
217
218 WorkingDirectory = cfg.dataDir;
219 StateDirectory = "tranquil-pds";
220 ReadWritePaths = [
221 cfg.settings.storage.path
222 ];
223
224 EnvironmentFile = cfg.environmentFiles;
225
226 CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
227 ProtectProc = "invisible";
228 ProcSubset = "pid";
229 NoNewPrivileges = true;
230 ProtectSystem = "strict";
231 ProtectHome = true;
232 PrivateTmp = true;
233 PrivateDevices = true;
234 PrivateUsers = true;
235 ProtectHostname = true;
236 ProtectClock = true;
237 ProtectKernelTunables = true;
238 ProtectKernelModules = true;
239 ProtectKernelLogs = true;
240 ProtectControlGroups = true;
241 RestrictAddressFamilies = [
242 "AF_INET"
243 "AF_INET6"
244 "AF_UNIX"
245 ];
246 RestrictNamespaces = true;
247 LockPersonality = true;
248 MemoryDenyWriteExecute = true;
249 RestrictRealtime = true;
250 RestrictSUIDSGID = true;
251 RemoveIPC = true;
252 PrivateMounts = true;
253 SystemCallFilter = [
254 "@system-service"
255 "~@privileged @resources"
256 ];
257 SystemCallArchitectures = "native";
258 };
259 };
260 }
261 ]
262 );
263}