at 25.11-pre 6.7 kB view raw
1{ 2 lib, 3 pkgs, 4 config, 5 ... 6}: 7let 8 cfg = config.services.pds; 9 10 inherit (lib) 11 getExe 12 mkEnableOption 13 mkIf 14 mkOption 15 mkPackageOption 16 escapeShellArgs 17 concatMapStringsSep 18 types 19 literalExpression 20 ; 21 22 pdsadminWrapper = 23 let 24 cfgSystemd = config.systemd.services.pds.serviceConfig; 25 in 26 pkgs.writeShellScriptBin "pdsadmin" '' 27 DUMMY_PDS_ENV_FILE="$(mktemp)" 28 trap 'rm -f "$DUMMY_PDS_ENV_FILE"' EXIT 29 env "PDS_ENV_FILE=$DUMMY_PDS_ENV_FILE" \ 30 ${escapeShellArgs cfgSystemd.Environment} \ 31 ${concatMapStringsSep " " (envFile: "$(cat ${envFile})") cfgSystemd.EnvironmentFile} \ 32 ${getExe pkgs.pdsadmin} "$@" 33 ''; 34in 35# All defaults are from https://github.com/bluesky-social/pds/blob/8b9fc24cec5f30066b0d0b86d2b0ba3d66c2b532/installer.sh 36{ 37 options.services.pds = { 38 enable = mkEnableOption "pds"; 39 40 package = mkPackageOption pkgs "pds" { }; 41 42 settings = mkOption { 43 type = types.submodule { 44 freeformType = types.attrsOf ( 45 types.oneOf [ 46 (types.nullOr types.str) 47 types.port 48 ] 49 ); 50 options = { 51 PDS_PORT = mkOption { 52 type = types.port; 53 default = 3000; 54 description = "Port to listen on"; 55 }; 56 57 PDS_HOSTNAME = mkOption { 58 type = types.str; 59 example = "pds.example.com"; 60 description = "Instance hostname (base domain name)"; 61 }; 62 63 PDS_BLOB_UPLOAD_LIMIT = mkOption { 64 type = types.str; 65 default = "52428800"; 66 description = "Size limit of uploaded blobs in bytes"; 67 }; 68 69 PDS_DID_PLC_URL = mkOption { 70 type = types.str; 71 default = "https://plc.directory"; 72 description = "URL of DID PLC directory"; 73 }; 74 75 PDS_BSKY_APP_VIEW_URL = mkOption { 76 type = types.str; 77 default = "https://api.bsky.app"; 78 description = "URL of bsky frontend"; 79 }; 80 81 PDS_BSKY_APP_VIEW_DID = mkOption { 82 type = types.str; 83 default = "did:web:api.bsky.app"; 84 description = "DID of bsky frontend"; 85 }; 86 87 PDS_REPORT_SERVICE_URL = mkOption { 88 type = types.str; 89 default = "https://mod.bsky.app"; 90 description = "URL of mod service"; 91 }; 92 93 PDS_REPORT_SERVICE_DID = mkOption { 94 type = types.str; 95 default = "did:plc:ar7c4by46qjdydhdevvrndac"; 96 description = "DID of mod service"; 97 }; 98 99 PDS_CRAWLERS = mkOption { 100 type = types.str; 101 default = "https://bsky.network"; 102 description = "URL of crawlers"; 103 }; 104 105 PDS_DATA_DIRECTORY = mkOption { 106 type = types.str; 107 default = "/var/lib/pds"; 108 description = "Directory to store state"; 109 }; 110 111 PDS_BLOBSTORE_DISK_LOCATION = mkOption { 112 type = types.nullOr types.str; 113 default = "/var/lib/pds/blocks"; 114 description = "Store blobs at this location, set to null to use e.g. S3"; 115 }; 116 117 LOG_ENABLED = mkOption { 118 type = types.nullOr types.str; 119 default = "true"; 120 description = "Enable logging"; 121 }; 122 }; 123 }; 124 125 description = '' 126 Environment variables to set for the service. Secrets should be 127 specified using {option}`environmentFile`. 128 129 Refer to <https://github.com/bluesky-social/atproto/blob/main/packages/pds/src/config/env.ts> for available environment variables. 130 ''; 131 }; 132 133 environmentFiles = mkOption { 134 type = types.listOf types.path; 135 default = [ ]; 136 description = '' 137 File to load environment variables from. Loaded variables override 138 values set in {option}`environment`. 139 140 Use it to set values of `PDS_JWT_SECRET`, `PDS_ADMIN_PASSWORD`, 141 and `PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX` secrets. 142 `PDS_JWT_SECRET` and `PDS_ADMIN_PASSWORD` can be generated with 143 ``` 144 openssl rand --hex 16 145 ``` 146 `PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX` can be generated with 147 ``` 148 openssl ecparam --name secp256k1 --genkey --noout --outform DER | tail --bytes=+8 | head --bytes=32 | xxd --plain --cols 32 149 ``` 150 ''; 151 }; 152 153 pdsadmin = { 154 enable = mkOption { 155 type = types.bool; 156 default = cfg.enable; 157 defaultText = literalExpression "config.services.pds.enable"; 158 description = "Add pdsadmin script to PATH"; 159 }; 160 }; 161 }; 162 163 config = mkIf cfg.enable { 164 environment = mkIf cfg.pdsadmin.enable { 165 systemPackages = [ pdsadminWrapper ]; 166 }; 167 168 systemd.services.pds = { 169 description = "pds"; 170 171 after = [ "network-online.target" ]; 172 wants = [ "network-online.target" ]; 173 wantedBy = [ "multi-user.target" ]; 174 175 serviceConfig = { 176 ExecStart = getExe cfg.package; 177 Environment = lib.mapAttrsToList (k: v: "${k}=${if builtins.isInt v then toString v else v}") ( 178 lib.filterAttrs (_: v: v != null) cfg.settings 179 ); 180 181 EnvironmentFile = cfg.environmentFiles; 182 User = "pds"; 183 Group = "pds"; 184 StateDirectory = "pds"; 185 StateDirectoryMode = "0755"; 186 Restart = "always"; 187 188 # Hardening 189 RemoveIPC = true; 190 CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ]; 191 NoNewPrivileges = true; 192 PrivateDevices = true; 193 ProtectClock = true; 194 ProtectKernelLogs = true; 195 ProtectControlGroups = true; 196 ProtectKernelModules = true; 197 PrivateMounts = true; 198 SystemCallArchitectures = [ "native" ]; 199 MemoryDenyWriteExecute = false; # required by V8 JIT 200 RestrictNamespaces = true; 201 RestrictSUIDSGID = true; 202 ProtectHostname = true; 203 LockPersonality = true; 204 ProtectKernelTunables = true; 205 RestrictAddressFamilies = [ 206 "AF_UNIX" 207 "AF_INET" 208 "AF_INET6" 209 ]; 210 RestrictRealtime = true; 211 DeviceAllow = [ "" ]; 212 ProtectSystem = "strict"; 213 ProtectProc = "invisible"; 214 ProcSubset = "pid"; 215 ProtectHome = true; 216 PrivateUsers = true; 217 PrivateTmp = true; 218 UMask = "0077"; 219 }; 220 }; 221 222 users = { 223 users.pds = { 224 group = "pds"; 225 isSystemUser = true; 226 }; 227 groups.pds = { }; 228 }; 229 230 }; 231 232 meta.maintainers = with lib.maintainers; [ t4ccer ]; 233}