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