250 lines
8.2 KiB
Nix
250 lines
8.2 KiB
Nix
|
{ depot, config, pkgs, lib, ... }:
|
||
|
|
||
|
let
|
||
|
cfg = config.my.services.bsky-pds;
|
||
|
|
||
|
marshalValue = key: value: if builtins.isString value then value
|
||
|
else if builtins.isInt value then toString value
|
||
|
else if builtins.isBool value then (if value then "true" else "false")
|
||
|
else throw "unknown value type for key ${key}";
|
||
|
cfgFile = pkgs.writeTextFile {
|
||
|
name = "bsky-pds.env";
|
||
|
text = lib.concatStringsSep "\n" (lib.mapAttrsToList (attrName: attrVal: lib.optionalString (attrVal != null) ''
|
||
|
${lib.toUpper attrName}=${marshalValue attrName attrVal}
|
||
|
'') cfg.settings);
|
||
|
};
|
||
|
|
||
|
inSecretsDir = v: builtins.isString v && (builtins.match "/var/lib/bsky-pds/secrets/.*" (toString v)) != null;
|
||
|
|
||
|
testAndGenerate = how: path: ''
|
||
|
test -f "${path}" || {
|
||
|
${how} >> "${path}"
|
||
|
}
|
||
|
'';
|
||
|
testAndGenerateHex = testAndGenerate "${pkgs.openssl}/bin/openssl rand --hex 16";
|
||
|
testAndGenerateK256 = testAndGenerate "${pkgs.openssl}/bin/openssl ecparam --name secp256k1 --genkey --noout --outform DER";
|
||
|
in
|
||
|
{
|
||
|
options.my.services.bsky-pds = {
|
||
|
enable = lib.mkEnableOption "Bluesky Personal Data Server";
|
||
|
|
||
|
package = lib.mkOption {
|
||
|
type = lib.types.package;
|
||
|
default = depot.nix.pkgs.bsky-pds;
|
||
|
description = ''
|
||
|
Package containing the PDS code.
|
||
|
'';
|
||
|
};
|
||
|
|
||
|
configureCaddy = (lib.mkEnableOption "configure Caddy to serve the PDS") // {
|
||
|
default = true;
|
||
|
};
|
||
|
|
||
|
settings = lib.mkOption {
|
||
|
type = with lib.types; submodule {
|
||
|
freeformType = attrsOf (nullOr (oneOf [
|
||
|
str
|
||
|
bool
|
||
|
int
|
||
|
]));
|
||
|
|
||
|
options.log_enabled = lib.mkOption {
|
||
|
type = lib.types.bool;
|
||
|
default = true;
|
||
|
description = ''
|
||
|
Whether or not to output any log messages.
|
||
|
'';
|
||
|
};
|
||
|
options.port = lib.mkOption {
|
||
|
type = lib.types.port;
|
||
|
default = 3000;
|
||
|
description = ''
|
||
|
Port number that the PDS will listen on.
|
||
|
'';
|
||
|
};
|
||
|
options.pds_hostname = lib.mkOption {
|
||
|
type = lib.types.str;
|
||
|
description = ''
|
||
|
Hostname under which the PDS will run.
|
||
|
'';
|
||
|
};
|
||
|
options.pds_admin_email = lib.mkOption {
|
||
|
type = lib.types.str;
|
||
|
description = ''
|
||
|
Email address of the PDS operator.
|
||
|
'';
|
||
|
};
|
||
|
options.pds_data_directory = lib.mkOption {
|
||
|
type = lib.types.path;
|
||
|
default = "/var/lib/bsky-pds/data";
|
||
|
description = ''
|
||
|
Path to data directory.
|
||
|
'';
|
||
|
};
|
||
|
options.pds_blobstore_disk_location = lib.mkOption {
|
||
|
type = lib.types.nullOr lib.types.path;
|
||
|
default = "/var/lib/bsky-pds/blobs";
|
||
|
description = ''
|
||
|
Path to location for blobstore storage, if using on-disk storage.
|
||
|
'';
|
||
|
};
|
||
|
options.pds_blob_upload_limit = lib.mkOption {
|
||
|
type = lib.types.int;
|
||
|
default = 52428800;
|
||
|
description = ''
|
||
|
Maximum allowable blob size for upload.
|
||
|
'';
|
||
|
};
|
||
|
options.pds_did_plc_url = lib.mkOption {
|
||
|
type = lib.types.str;
|
||
|
default = "https://plc.directory";
|
||
|
description = ''
|
||
|
URL of the PLC directory.
|
||
|
'';
|
||
|
};
|
||
|
|
||
|
options.pds_bsky_app_view_url = lib.mkOption {
|
||
|
type = lib.types.str;
|
||
|
default = "https://api.bsky.app";
|
||
|
description = ''
|
||
|
URL of the Bluesky AppView.
|
||
|
'';
|
||
|
};
|
||
|
options.pds_bsky_app_view_did = lib.mkOption {
|
||
|
type = lib.types.str;
|
||
|
default = "did:web:api.bsky.app";
|
||
|
description = ''
|
||
|
DID of the Bluesky AppView.
|
||
|
'';
|
||
|
};
|
||
|
options.pds_report_service_url = lib.mkOption {
|
||
|
type = lib.types.str;
|
||
|
default = "https://mod.bsky.app";
|
||
|
description = ''
|
||
|
URL of the Bluesky moderation service.
|
||
|
'';
|
||
|
};
|
||
|
options.pds_report_service_did = lib.mkOption {
|
||
|
type = lib.types.str;
|
||
|
default = "did:plc:ar7c4by46qjdydhdevvrndac";
|
||
|
description = ''
|
||
|
DID of the Bluesky moderation service.
|
||
|
'';
|
||
|
};
|
||
|
options.crawlers = lib.mkOption {
|
||
|
type = lib.types.commas;
|
||
|
default = "https://bsky.network";
|
||
|
description = ''
|
||
|
URLs of hosts to notify of new data.
|
||
|
'';
|
||
|
};
|
||
|
};
|
||
|
default = {};
|
||
|
description = ''
|
||
|
Configuration for Bluesky PDS.
|
||
|
'';
|
||
|
};
|
||
|
|
||
|
generateSecrets = lib.mkEnableOption "automatically generate required PDS secrets";
|
||
|
|
||
|
secrets = lib.mkOption {
|
||
|
type = with lib.types; submodule {
|
||
|
freeformType = attrsOf (nullOr path);
|
||
|
|
||
|
options.pds_jwt_secret = lib.mkOption {
|
||
|
type = nullOr path;
|
||
|
default = null;
|
||
|
description = ''
|
||
|
Path to a file containing a 16 character hex secret used for JWT secrets.
|
||
|
'';
|
||
|
};
|
||
|
options.pds_admin_password = lib.mkOption {
|
||
|
type = nullOr path;
|
||
|
default = null;
|
||
|
description = ''
|
||
|
Path to a file containing the PDS admin password.
|
||
|
'';
|
||
|
};
|
||
|
options.pds_plc_rotation_key_k256_private_key = lib.mkOption {
|
||
|
type = nullOr path;
|
||
|
default = null;
|
||
|
description = ''
|
||
|
Path to a file containing a secp256k1 private key in DER format.
|
||
|
'';
|
||
|
};
|
||
|
};
|
||
|
default = {};
|
||
|
description = ''
|
||
|
Path to files containing secrets for Bluesky PDS.
|
||
|
'';
|
||
|
};
|
||
|
};
|
||
|
|
||
|
config = lib.mkMerge [(lib.mkIf cfg.generateSecrets {
|
||
|
my.services.bsky-pds.secrets = {
|
||
|
pds_jwt_secret = lib.mkDefault "/var/lib/bsky-pds/secrets/pds_jwt_secret";
|
||
|
pds_admin_password = lib.mkDefault "/var/lib/bsky-pds/secrets/pds_admin_password";
|
||
|
pds_plc_rotation_key_k256_private_key = lib.mkDefault "/var/lib/bsky-pds/secrets/pds_plc_rotation_key_k256_private_key";
|
||
|
};
|
||
|
}) (lib.mkIf cfg.enable {
|
||
|
systemd.services.bsky-pds = {
|
||
|
wantedBy = [ "multi-user.target" ];
|
||
|
unitConfig = {
|
||
|
Description = "Bluesky PDS Service";
|
||
|
Documentation = "https://github.com/bluesky-social/pds";
|
||
|
};
|
||
|
script = ''
|
||
|
mkdir -p /var/lib/bsky-pds "$PDS_DATA_DIRECTORY"
|
||
|
if [[ ! -z "$PDS_BLOBSTORE_DISK_LOCATION" ]]; then
|
||
|
mkdir -p "$PDS_BLOBSTORE_DISK_LOCATION"
|
||
|
fi
|
||
|
|
||
|
old_umask=$(umask)
|
||
|
umask 077
|
||
|
mkdir -p /var/lib/bsky-pds/secrets
|
||
|
${lib.optionalString (inSecretsDir cfg.secrets.pds_jwt_secret) (testAndGenerateHex cfg.secrets.pds_jwt_secret)}
|
||
|
${lib.optionalString (inSecretsDir cfg.secrets.pds_admin_password) (testAndGenerateHex cfg.secrets.pds_admin_password)}
|
||
|
${lib.optionalString (inSecretsDir cfg.secrets.pds_plc_rotation_key_k256_private_key) (testAndGenerateK256 cfg.secrets.pds_plc_rotation_key_k256_private_key)}
|
||
|
umask "$old_umask"
|
||
|
|
||
|
${lib.concatStringsSep "\n" (lib.mapAttrsToList (k: v: lib.optionalString (v != null) ''
|
||
|
export ${lib.toUpper k}="$(cat "${v}")"
|
||
|
'') cfg.secrets)}
|
||
|
|
||
|
${lib.optionalString (cfg.secrets.pds_plc_rotation_key_k256_private_key != null) ''
|
||
|
if [[ -z "$PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX ]]; then
|
||
|
export PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX="$(cat "${cfg.secrets.pds_plc_rotation_key_k256_private_key}" | ${pkgs.coreutils}/bin/tail --bytes=+8 | ${pkgs.coreutils}/bin/head --bytes=32 | ${pkgs.xxd}/bin/xxd --plain --cols 32)"
|
||
|
fi
|
||
|
''}
|
||
|
|
||
|
exec ${cfg.package}/bin/bsky-pds
|
||
|
'';
|
||
|
serviceConfig = {
|
||
|
User = "bsky-pds";
|
||
|
DynamicUser = true;
|
||
|
StateDirectory = "bsky-pds";
|
||
|
EnvironmentFile = cfgFile;
|
||
|
};
|
||
|
};
|
||
|
}) (lib.mkIf (cfg.enable && cfg.configureCaddy) {
|
||
|
services.caddy = {
|
||
|
enable = lib.mkDefault true;
|
||
|
globalConfig = ''
|
||
|
email ${cfg.settings.pds_admin_email}
|
||
|
on_demand_tls {
|
||
|
ask http://localhost:${toString cfg.settings.port}/tls-check
|
||
|
}
|
||
|
'';
|
||
|
virtualHosts."${cfg.settings.pds_hostname}" = {
|
||
|
serverAliases = [ "*.${cfg.settings.pds_hostname}" ];
|
||
|
extraConfig = ''
|
||
|
tls {
|
||
|
on_demand
|
||
|
}
|
||
|
reverse_proxy http://localhost:${toString cfg.settings.port}
|
||
|
'';
|
||
|
};
|
||
|
};
|
||
|
})];
|
||
|
}
|