Monorepo for Tangled

nix: add PLC, PDS, and Jetstream packages

Add Nix package definitions for the three AT Protocol services needed
for fully offline local development:
- did-plc-server: DID PLC directory server (Node.js)
- pds: AT Protocol Personal Data Server (Node.js)
- jetstream: AT Protocol event stream service (Go binary)

AI-assisted: GitLab Duo Agentic Chat (Claude Opus 4.6)
Signed-off-by: Alessio Caiazza <code.git@caiazza.info>

+220
+95
nix/pkgs/did-plc-server.nix
··· 1 + # did-plc-server: DID PLC directory server for local development. 2 + # 3 + # Builds @did-plc/server from the did-method-plc monorepo source. 4 + # The server stores and resolves did:plc identity operations. 5 + # 6 + # When DATABASE_URL is set, uses PostgreSQL for persistent storage. 7 + # When DATABASE_URL is unset, uses an in-memory mock database. 8 + { 9 + lib, 10 + stdenv, 11 + fetchFromGitHub, 12 + fetchYarnDeps, 13 + nodejs, 14 + yarn, 15 + fixup-yarn-lock, 16 + cacert, 17 + makeBinaryWrapper, 18 + }: let 19 + src = fetchFromGitHub { 20 + owner = "did-method-plc"; 21 + repo = "did-method-plc"; 22 + rev = "1e301e5e29dd419c4dfc7d3100a2a614f9286ebe"; 23 + hash = "sha256-9GEMvxSOf6aR6YJhKre47x8sNUVASmjhzC9zq+ZS9ig="; 24 + }; 25 + in 26 + stdenv.mkDerivation { 27 + pname = "did-plc-server"; 28 + version = "0-unstable-2026-02-03"; 29 + 30 + inherit src; 31 + 32 + offlineCache = fetchYarnDeps { 33 + yarnLock = "${src}/yarn.lock"; 34 + hash = "sha256-XLJKRg/1nCzYM4uodCEBKXOal2sq8O6lx5MPPCZCXGw="; 35 + }; 36 + 37 + nativeBuildInputs = [ 38 + nodejs 39 + yarn 40 + fixup-yarn-lock 41 + makeBinaryWrapper 42 + ]; 43 + 44 + configurePhase = '' 45 + runHook preConfigure 46 + 47 + export HOME=$TMPDIR 48 + fixup-yarn-lock yarn.lock 49 + yarn config --offline set yarn-offline-mirror $offlineCache 50 + yarn install --frozen-lockfile --offline --no-progress --non-interactive --ignore-scripts 51 + 52 + runHook postConfigure 53 + ''; 54 + 55 + buildPhase = '' 56 + runHook preBuild 57 + 58 + # Build the lib package first (server depends on it) 59 + pushd packages/lib 60 + node ../../node_modules/.bin/esbuild src/index.ts --bundle --platform=node --outdir=dist --format=cjs || \ 61 + yarn run build 62 + popd 63 + 64 + # Build the server package 65 + pushd packages/server 66 + node ../../node_modules/.bin/esbuild src/bin.ts --bundle --platform=node --outdir=dist --format=cjs --external:pg --external:pg-native || \ 67 + yarn run build 68 + popd 69 + 70 + runHook postBuild 71 + ''; 72 + 73 + installPhase = '' 74 + runHook preInstall 75 + 76 + mkdir -p $out/lib/plc-server 77 + cp -r node_modules $out/lib/plc-server/ 78 + cp -r packages $out/lib/plc-server/ 79 + 80 + makeWrapper "${nodejs}/bin/node" "$out/bin/plc-server" \ 81 + --add-flags "--enable-source-maps" \ 82 + --add-flags "$out/lib/plc-server/packages/server/dist/bin.js" \ 83 + --set-default NODE_ENV production 84 + 85 + runHook postInstall 86 + ''; 87 + 88 + meta = { 89 + description = "DID PLC Directory Service for local development"; 90 + homepage = "https://github.com/did-method-plc/did-method-plc"; 91 + license = lib.licenses.mit; 92 + platforms = lib.platforms.unix; 93 + mainProgram = "plc-server"; 94 + }; 95 + }
+61
nix/pkgs/jetstream.nix
··· 1 + # jetstream: AT Protocol event firehose proxy for local development. 2 + # 3 + # Subscribes to a PDS's com.atproto.sync.subscribeRepos and re-broadcasts 4 + # events over WebSocket in a simplified JSON format. 5 + # 6 + # Key env vars: 7 + # JETSTREAM_WS_URL — upstream ATProto firehose URL 8 + # JETSTREAM_LISTEN_ADDR — WebSocket listen address (default :6008) 9 + # JETSTREAM_DATA_DIR — PebbleDB cursor storage directory 10 + { 11 + lib, 12 + buildGoModule, 13 + fetchFromGitHub, 14 + }: 15 + buildGoModule { 16 + pname = "jetstream"; 17 + version = "0-unstable-2024-06-18"; 18 + 19 + src = fetchFromGitHub { 20 + owner = "bluesky-social"; 21 + repo = "jetstream"; 22 + rev = "a003f34cdb510c37d7245f0965814a6168243812"; 23 + hash = "sha256-VMSOHTTLr+wFI0NQthGJvlj1oQkTlFSjWkfbRRLqM0A="; 24 + }; 25 + 26 + vendorHash = "sha256-UvJQ27BZ9Pri8zhY+T40qvH+g5ZInENQqSby+Z+lReg="; 27 + 28 + proxyVendor = true; 29 + 30 + subPackages = ["cmd/jetstream"]; 31 + 32 + # CGO is needed for PebbleDB's zstd compression (DataDog/czstd) 33 + env.CGO_ENABLED = 1; 34 + 35 + # Disable the 15-second liveness checker that kills Jetstream when idle. 36 + # It's designed for Docker (restart on crash) but we run natively. 37 + # In local dev there may be long periods with no events. 38 + postPatch = '' 39 + substituteInPlace cmd/jetstream/main.go \ 40 + --replace-fail 'close(livenessKill)' '// disabled for local-dev: close(livenessKill)' 41 + ''; 42 + 43 + # The go.sum needs updating for newer Go toolchains 44 + overrideModAttrs = _: { 45 + preBuild = '' 46 + go mod tidy 47 + ''; 48 + }; 49 + 50 + preBuild = '' 51 + go mod tidy 52 + ''; 53 + 54 + meta = { 55 + description = "AT Protocol event firehose proxy"; 56 + homepage = "https://github.com/bluesky-social/jetstream"; 57 + license = lib.licenses.mit; 58 + platforms = lib.platforms.unix; 59 + mainProgram = "jetstream"; 60 + }; 61 + }
+64
nix/pkgs/pds-local-dev.nix
··· 1 + # pds-local-dev: Patched bluesky-pds for local HTTP development. 2 + # 3 + # The upstream PDS assumes HTTPS for its OAuth UI. Four things break on 4 + # plain http://localhost: 5 + # 6 + # 1. CSP `upgrade-insecure-requests` tells browsers to load all sub-resources 7 + # over https://, so the OAuth React UI never loads. 8 + # 2. Device/session cookies are set with `Secure`, so browsers never send 9 + # them back over HTTP → CSRF mismatch on the login form. 10 + # 3. The CSRF cookie itself is hardcoded `secure: true`. 11 + # 4. The OAuth authorize endpoint validates `sec-fetch-site` and only accepts 12 + # `cross-site` or `none`. When both the appview and PDS run on localhost 13 + # (different ports), browsers send `same-site`, which gets rejected. 14 + # 15 + # This derivation copies the stock bluesky-pds and patches all four. 16 + { 17 + lib, 18 + bluesky-pds, 19 + makeBinaryWrapper, 20 + nodejs, 21 + runCommand, 22 + }: 23 + runCommand "pds-local-dev-${bluesky-pds.version}" { 24 + nativeBuildInputs = [makeBinaryWrapper]; 25 + inherit (bluesky-pds) meta; 26 + } '' 27 + mkdir -p $out/{bin,lib} 28 + 29 + # Copy the lib directory (node_modules + index.js) and make writable 30 + cp -r ${bluesky-pds}/lib/pds $out/lib/pds 31 + chmod -R u+w $out/lib/pds 32 + 33 + # 1. Remove upgrade-insecure-requests from the default CSP. 34 + find $out/lib/pds -name 'send-web-page.js' -path '*/oauth-provider/dist/*' | while read -r f; do 35 + substituteInPlace "$f" \ 36 + --replace-fail "'upgrade-insecure-requests': true," "'upgrade-insecure-requests': false," 37 + done 38 + 39 + # 2. Allow device/session cookies over plain HTTP. 40 + find $out/lib/pds -name 'device-manager.js' -path '*/oauth-provider/dist/*' | while read -r f; do 41 + substituteInPlace "$f" \ 42 + --replace-fail "secure: zod_1.z.boolean().default(true)," "secure: zod_1.z.boolean().default(false)," 43 + done 44 + 45 + # 3. Allow CSRF cookie over plain HTTP. 46 + find $out/lib/pds -name 'csrf.js' -path '*/oauth-provider/dist/*' | while read -r f; do 47 + substituteInPlace "$f" \ 48 + --replace-fail "secure: true," "secure: false," 49 + done 50 + 51 + # 4. Accept same-site sec-fetch-site header on the authorize endpoint. 52 + # When appview (localhost:3000) redirects to PDS (localhost:2583), 53 + # browsers send sec-fetch-site: same-site instead of cross-site. 54 + find $out/lib/pds -name 'create-authorization-page-middleware.js' -path '*/oauth-provider/dist/*' | while read -r f; do 55 + substituteInPlace "$f" \ 56 + --replace-fail "['cross-site', 'none']" "['cross-site', 'same-site', 'none']" 57 + done 58 + 59 + # Create a new wrapper binary pointing to our patched lib 60 + makeBinaryWrapper ${nodejs}/bin/node $out/bin/pds \ 61 + --add-flags --enable-source-maps \ 62 + --add-flags "$out/lib/pds/index.js" \ 63 + --set-default NODE_ENV production 64 + ''