Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pds: init at 0.4.74, nixos/pds: init #350645

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions nixos/doc/manual/release-notes/rl-2505.section.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@

- [Actual Budget](https://actualbudget.org/), a local-first personal finance app. Available as [services.actual](#opt-services.actual.enable).

- [PDS](https://github.com/bluesky-social/pds), Personal Data Server for [bsky](https://bsky.social/). Available as [services.pds](option.html#opt-services.pds).

- [mqtt-exporter](https://github.com/kpetremann/mqtt-exporter/), a Prometheus exporter for exposing messages from MQTT. Available as [services.prometheus.exporters.mqtt](#opt-services.prometheus.exporters.mqtt.enable).

- [nvidia-gpu](https://github.com/utkuozdemir/nvidia_gpu_exporter), a Prometheus exporter that scrapes `nvidia-smi` for GPU metrics. Available as [services.prometheus.exporters.nvidia-gpu](#opt-services.prometheus.exporters.nvidia-gpu.enable).
Expand Down
1 change: 1 addition & 0 deletions nixos/modules/module-list.nix
Original file line number Diff line number Diff line change
Expand Up @@ -1529,6 +1529,7 @@
./services/web-apps/mobilizon.nix
./services/web-apps/openwebrx.nix
./services/web-apps/outline.nix
./services/web-apps/pds.nix
./services/web-apps/peering-manager.nix
./services/web-apps/peertube.nix
./services/web-apps/pgpkeyserver-lite.nix
Expand Down
233 changes: 233 additions & 0 deletions nixos/modules/services/web-apps/pds.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
{
lib,
pkgs,
config,
...
}:
let
cfg = config.services.pds;

inherit (lib)
getExe
mkEnableOption
mkIf
mkOption
mkPackageOption
escapeShellArgs
concatMapStringsSep
types
literalExpression
;

pdsadminWrapper =
let
cfgSystemd = config.systemd.services.pds.serviceConfig;
in
pkgs.writeShellScriptBin "pdsadmin" ''
DUMMY_PDS_ENV_FILE="$(mktemp)"
trap 'rm -f "$DUMMY_PDS_ENV_FILE"' EXIT
Comment on lines +27 to +28
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need a temporary file?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cause the script tries to source it and it almost always does not exist (the default is /pds/pds.env which looks docker-ish) but even if it does we still want to use our variables instead
https://github.com/bluesky-social/pds/blob/main/pdsadmin/account.sh#L6-L7

env "PDS_ENV_FILE=$DUMMY_PDS_ENV_FILE" \
${escapeShellArgs cfgSystemd.Environment} \
${concatMapStringsSep " " (envFile: "$(cat ${envFile})") cfgSystemd.EnvironmentFile} \
${getExe pkgs.pdsadmin} "$@"
'';
in
# All defaults are from https://github.com/bluesky-social/pds/blob/8b9fc24cec5f30066b0d0b86d2b0ba3d66c2b532/installer.sh
{
options.services.pds = {
enable = mkEnableOption "pds";

package = mkPackageOption pkgs "pds" { };

settings = mkOption {
type = types.submodule {
freeformType = types.attrsOf (
types.oneOf [
(types.nullOr types.str)
types.port
]
);
options = {
PDS_PORT = mkOption {
type = types.port;
default = 3000;
description = "Port to listen on";
};

PDS_HOSTNAME = mkOption {
type = types.str;
example = "pds.example.com";
description = "Instance hostname (base domain name)";
};

PDS_BLOB_UPLOAD_LIMIT = mkOption {
type = types.str;
default = "52428800";
description = "Size limit of uploaded blobs in bytes";
};

PDS_DID_PLC_URL = mkOption {
type = types.str;
default = "https://plc.directory";
description = "URL of DID PLC directory";
};

PDS_BSKY_APP_VIEW_URL = mkOption {
type = types.str;
default = "https://api.bsky.app";
description = "URL of bsky frontend";
};

PDS_BSKY_APP_VIEW_DID = mkOption {
type = types.str;
default = "did:web:api.bsky.app";
description = "DID of bsky frontend";
};

PDS_REPORT_SERVICE_URL = mkOption {
type = types.str;
default = "https://mod.bsky.app";
description = "URL of mod service";
};

PDS_REPORT_SERVICE_DID = mkOption {
type = types.str;
default = "did:plc:ar7c4by46qjdydhdevvrndac";
description = "DID of mod service";
};

PDS_CRAWLERS = mkOption {
type = types.str;
default = "https://bsky.network";
description = "URL of crawlers";
};

PDS_DATA_DIRECTORY = mkOption {
type = types.str;
default = "/var/lib/pds";
description = "Directory to store state";
};

PDS_BLOBSTORE_DISK_LOCATION = mkOption {
type = types.nullOr types.str;
default = "/var/lib/pds/blocks";
description = "Store blobs at this location, set to null to use e.g. S3";
};

LOG_ENABLED = mkOption {
type = types.nullOr types.str;
default = "true";
description = "Enable logging";
};
};
};

description = ''
Environment variables to set for the service. Secrets should be
specified using {option}`environmentFile`.

Refer to <https://github.com/bluesky-social/atproto/blob/main/packages/pds/src/config/env.ts> for available environment variables.
'';
};

environmentFiles = mkOption {
type = types.listOf types.path;
default = [ ];
description = ''
File to load environment variables from. Loaded variables override
values set in {option}`environment`.

Use it to set values of `PDS_JWT_SECRET`, `PDS_ADMIN_PASSWORD`,
and `PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX` secrets.
`PDS_JWT_SECRET` and `PDS_ADMIN_PASSWORD` can be generated with
```
openssl rand --hex 16
```
`PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX` can be generated with
```
openssl ecparam --name secp256k1 --genkey --noout --outform DER | tail --bytes=+8 | head --bytes=32 | xxd --plain --cols 32
```
'';
};

pdsadmin = {
enable = mkOption {
type = types.bool;
default = cfg.enable;
defaultText = literalExpression "config.services.pds.enable";
description = "Add pdsadmin script to PATH";
};
};
};

config = mkIf cfg.enable {
environment = mkIf cfg.pdsadmin.enable {
systemPackages = [ pdsadminWrapper ];
};

systemd.services.pds = {
description = "pds";

after = [ "network-online.target" ];
wants = [ "network-online.target" ];
t4ccer marked this conversation as resolved.
Show resolved Hide resolved
wantedBy = [ "multi-user.target" ];

serviceConfig = {
t4ccer marked this conversation as resolved.
Show resolved Hide resolved
ExecStart = getExe cfg.package;
Environment = lib.mapAttrsToList (k: v: "${k}=${if builtins.isInt v then toString v else v}") (
lib.filterAttrs (_: v: v != null) cfg.settings
);

EnvironmentFile = cfg.environmentFiles;
User = "pds";
Group = "pds";
StateDirectory = "pds";
t4ccer marked this conversation as resolved.
Show resolved Hide resolved
StateDirectoryMode = "0755";
Restart = "always";

# Hardening
RemoveIPC = true;
CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
NoNewPrivileges = true;
PrivateDevices = true;
ProtectClock = true;
ProtectKernelLogs = true;
ProtectControlGroups = true;
ProtectKernelModules = true;
PrivateMounts = true;
SystemCallArchitectures = [ "native" ];
MemoryDenyWriteExecute = false; # required by V8 JIT
RestrictNamespaces = true;
RestrictSUIDSGID = true;
ProtectHostname = true;
LockPersonality = true;
ProtectKernelTunables = true;
RestrictAddressFamilies = [
"AF_UNIX"
"AF_INET"
"AF_INET6"
];
RestrictRealtime = true;
DeviceAllow = [ "" ];
ProtectSystem = "strict";
ProtectProc = "invisible";
ProcSubset = "pid";
ProtectHome = true;
PrivateUsers = true;
PrivateTmp = true;
UMask = "0077";
};
};

users = {
users.pds = {
group = "pds";
isSystemUser = true;
};
groups.pds = { };
};

};

meta.maintainers = with lib.maintainers; [ t4ccer ];
}
1 change: 1 addition & 0 deletions nixos/tests/all-tests.nix
Original file line number Diff line number Diff line change
Expand Up @@ -799,6 +799,7 @@ in {
parsedmarc = handleTest ./parsedmarc {};
password-option-override-ordering = handleTest ./password-option-override-ordering.nix {};
pdns-recursor = handleTest ./pdns-recursor.nix {};
pds = handleTest ./pds.nix {};
peerflix = handleTest ./peerflix.nix {};
peering-manager = handleTest ./web-apps/peering-manager.nix {};
peertube = handleTestOn ["x86_64-linux"] ./web-apps/peertube.nix {};
Expand Down
29 changes: 29 additions & 0 deletions nixos/tests/pds.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import ./make-test-python.nix (
{ lib, ... }:
{
name = "PDS";

nodes.machine = {
services.pds = {
enable = true;
settings = {
PDS_PORT = 3000;
PDS_HOSTNAME = "example.com";

# Snake oil testing credentials
PDS_JWT_SECRET = "7b93fee53be046bf59c27a32a0fb2069";
PDS_ADMIN_PASSWORD = "3a4077bc0d5f04eca945ef0509f7e809";
PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX = "ae4f5028d04c833ba630f29debd5ff80b7700e43e9f4bf70f729a88cd6a6ce35";
};
};
};

testScript = ''
machine.wait_for_unit("pds.service")
machine.wait_for_open_port(3000)
machine.succeed("curl --fail http://localhost:3000")
'';

meta.maintainers = with lib.maintainers; [ t4ccer ];
}
)
97 changes: 97 additions & 0 deletions pkgs/by-name/pd/pds/package.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
{
stdenv,
t4ccer marked this conversation as resolved.
Show resolved Hide resolved
makeBinaryWrapper,
removeReferencesTo,
srcOnly,
python3,
pnpm,
fetchFromGitHub,
nodejs,
vips,
pkg-config,
nixosTests,
lib,
}:

let
nodeSources = srcOnly nodejs;
pythonEnv = python3.withPackages (p: [ p.setuptools ]);
in

stdenv.mkDerivation (finalAttrs: {
pname = "pds";
version = "0.4.74";

src = fetchFromGitHub {
owner = "bluesky-social";
repo = "pds";
rev = "v${finalAttrs.version}";
hash = "sha256-kNHsQ6funmo8bnkFBNWHQ0Fmd5nf/uh+x9buaRJMZnM=";
};

sourceRoot = "${finalAttrs.src.name}/service";

nativeBuildInputs = [
makeBinaryWrapper
nodejs
pythonEnv
pkg-config
pnpm.configHook
removeReferencesTo
];

# Required for `sharp` NPM dependency
buildInputs = [ vips ];

pnpmDeps = pnpm.fetchDeps {
inherit (finalAttrs)
pname
version
src
sourceRoot
;
hash = "sha256-oU4dwlBdsMmgAUv1ICaOqaqucmg/TjKOZxjnxpm0qL8=";
};

buildPhase = ''
runHook preBuild

pushd ./node_modules/.pnpm/better-sqlite3@*/node_modules/better-sqlite3
npm run build-release --offline --nodedir="${nodeSources}"
find build -type f -exec remove-references-to -t "${nodeSources}" {} \;
popd

makeWrapper "${lib.getExe nodejs}" "$out/bin/pds" \
--add-flags --enable-source-maps \
--add-flags "$out/lib/pds/index.js" \
--set-default NODE_ENV production

runHook postBuild
'';

installPhase = ''
runHook preInstall

mkdir -p $out/{bin,lib/pds}
mv node_modules $out/lib/pds
mv index.js $out/lib/pds

runHook postInstall
'';

passthru.tests = {
inherit (nixosTests) pds;
};

meta = {
description = "Bluesky Personal Data Server (PDS)";
homepage = "https://github.com/bluesky-social/pds";
license = with lib.licenses; [
mit
asl20
];
maintainers = with lib.maintainers; [ t4ccer ];
platforms = lib.platforms.unix;
mainProgram = "pds";
};
})
Loading
Loading