Our Personal Data Server from scratch! tranquil.farm
oauth atproto pds rust postgresql objectstorage fun
at main 252 lines 7.3 kB view raw
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, backups)"; 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 backup = { 120 path = mkOption { 121 type = types.path; 122 default = "/var/lib/tranquil-pds/backups"; 123 description = "Directory for storing backups"; 124 }; 125 }; 126 127 email = { 128 sendmail_path = mkOption { 129 type = types.path; 130 default = lib.getExe pkgs.system-sendmail; 131 description = "Path to the sendmail executable to use for sending emails."; 132 }; 133 }; 134 135 signal = { 136 cli_path = mkOption { 137 type = types.path; 138 default = lib.getExe pkgs.signal-cli; 139 description = "Path to the signal-cli executable to use for sending Signal notifications."; 140 }; 141 }; 142 }; 143 }; 144 145 description = '' 146 Configuration options to set for the service. Secrets should be 147 specified using {option}`environmentFile`. 148 149 Refer to <https://tangled.org/tranquil.farm/tranquil-pds/blob/main/example.toml> 150 for available configuration options. 151 ''; 152 }; 153 }; 154 155 config = lib.mkIf cfg.enable ( 156 lib.mkMerge [ 157 (lib.mkIf cfg.database.createLocally { 158 services.postgresql = { 159 enable = true; 160 ensureDatabases = [ cfg.user ]; 161 ensureUsers = [ 162 { 163 name = cfg.user; 164 ensureDBOwnership = true; 165 } 166 ]; 167 }; 168 169 services.tranquil-pds.settings.database.url = 170 lib.mkDefault "postgresql:///${cfg.user}?host=/run/postgresql"; 171 172 systemd.services.tranquil-pds = { 173 requires = [ "postgresql.service" ]; 174 after = [ "postgresql.service" ]; 175 }; 176 }) 177 178 { 179 users.users.${cfg.user} = { 180 isSystemUser = true; 181 inherit (cfg) group; 182 home = cfg.dataDir; 183 }; 184 185 users.groups.${cfg.group} = { }; 186 187 systemd.tmpfiles.settings."tranquil-pds" = 188 lib.genAttrs 189 [ 190 cfg.dataDir 191 cfg.settings.storage.path 192 cfg.settings.backup.path 193 ] 194 (_: { 195 d = { 196 mode = "0750"; 197 inherit (cfg) user group; 198 }; 199 }); 200 201 environment.etc = { 202 "tranquil-pds/config.toml".source = settingsFormat.generate "tranquil-pds.toml" cfg.settings; 203 }; 204 205 systemd.services.tranquil-pds = { 206 description = "Tranquil PDS - AT Protocol Personal Data Server"; 207 after = [ "network-online.target" ]; 208 wants = [ "network-online.target" ]; 209 wantedBy = [ "multi-user.target" ]; 210 211 serviceConfig = { 212 User = cfg.user; 213 Group = cfg.group; 214 ExecStart = lib.getExe cfg.package; 215 Restart = "on-failure"; 216 RestartSec = 5; 217 218 WorkingDirectory = cfg.dataDir; 219 StateDirectory = "tranquil-pds"; 220 221 EnvironmentFile = cfg.environmentFiles; 222 223 NoNewPrivileges = true; 224 ProtectSystem = "strict"; 225 ProtectHome = true; 226 PrivateTmp = true; 227 PrivateDevices = true; 228 ProtectKernelTunables = true; 229 ProtectKernelModules = true; 230 ProtectControlGroups = true; 231 RestrictAddressFamilies = [ 232 "AF_INET" 233 "AF_INET6" 234 "AF_UNIX" 235 ]; 236 RestrictNamespaces = true; 237 LockPersonality = true; 238 MemoryDenyWriteExecute = true; 239 RestrictRealtime = true; 240 RestrictSUIDSGID = true; 241 RemoveIPC = true; 242 243 ReadWritePaths = [ 244 cfg.settings.storage.path 245 cfg.settings.backup.path 246 ]; 247 }; 248 }; 249 } 250 ] 251 ); 252}