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.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}