Our Personal Data Server from scratch!
0

Configure Feed

Select the types of activity you want to include in your feed.

tranquil-pds / module.nix
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}