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