This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

localinfra: add bobbin, hydrant, and web to the stack

Signed-off-by: dawn <dawn@tangled.org>

author
dawn
date (Jul 31, 2026, 10:51 PM +0300) commit 192a083a parent 0b1987e2 change-id qzvkmqxz
+214 -3
+1
.gitignore
··· 32 32 build/ 33 33 .wrangler/ 34 34 localinfra/certs/* 35 + localinfra/vendor/ 35 36 id_rsa 36 37 id_ecdsa 37 38 id_dsa
+2
bobbin/crates/resolver/src/legacy_upgrade.rs
··· 450 450 push_options: None, 451 451 r#ref: l.r#ref, 452 452 repo: l.repo_did, 453 + changed_files: None, 454 + push_options: None, 453 455 extra_data: l.extra_data, 454 456 } 455 457 }
+110
docker-compose.yml
··· 346 346 ports: 347 347 - "3000:3000" 348 348 - "3333:3333" 349 + healthcheck: 350 + test: ["CMD", "wget", "-qO-", "http://localhost:3000/"] 351 + interval: 5s 352 + timeout: 2s 353 + retries: 15 354 + start_period: 10s 349 355 volumes: 350 356 - .:/src:cached 351 357 - go-cache:/go/cache ··· 374 380 condition: service_completed_successfully 375 381 networks: [tngl] 376 382 383 + hydrant: 384 + build: 385 + context: https://tangled.org/ptr.pet/hydrant.git#main 386 + restart: unless-stopped 387 + environment: 388 + HYDRANT_API_BIND: 0.0.0.0:3000 389 + HYDRANT_DATABASE_PATH: /data/hydrant.db 390 + HYDRANT_RELAY_HOSTS: pds::wss://pds.tngl.boltless.dev 391 + HYDRANT_CRAWLER_URLS: list_repos::https://pds.tngl.boltless.dev 392 + HYDRANT_PLC_URL: https://plc.tngl.boltless.dev 393 + HYDRANT_FILTER_COLLECTIONS: sh.tangled.* 394 + HYDRANT_FILTER_SIGNALS: sh.tangled.actor.profile,sh.tangled.feed.comment,sh.tangled.feed.reaction,sh.tangled.feed.star,sh.tangled.git.refUpdate,sh.tangled.graph.follow,sh.tangled.graph.vouch,sh.tangled.knot,sh.tangled.knot.member,sh.tangled.label.definition,sh.tangled.label.op,sh.tangled.pipeline,sh.tangled.pipeline.status,sh.tangled.publicKey,sh.tangled.repo,sh.tangled.repo.artifact,sh.tangled.repo.collaborator,sh.tangled.repo.issue,sh.tangled.repo.issue.comment,sh.tangled.repo.issue.state,sh.tangled.repo.pull,sh.tangled.repo.pull.comment,sh.tangled.repo.pull.status,sh.tangled.spindle,sh.tangled.spindle.member,sh.tangled.string 395 + HYDRANT_BACKFILL_STRATEGY: sparse-filter 396 + HYDRANT_VERIFY_SIGNATURES: none 397 + RUST_LOG: info 398 + volumes: 399 + - hydrant-data:/data 400 + - ./localinfra/certs/root.crt:/etc/ssl/certs/ca-certificates.crt:ro 401 + healthcheck: 402 + test: ["CMD", "bash", "-c", "echo > /dev/tcp/127.0.0.1/3000"] 403 + interval: 5s 404 + timeout: 2s 405 + retries: 15 406 + start_period: 10s 407 + depends_on: 408 + plc: 409 + condition: service_started 410 + pds: 411 + condition: service_healthy 412 + caddy: 413 + condition: service_started 414 + init-accounts: 415 + condition: service_completed_successfully 416 + networks: [tngl] 417 + bobbin: 418 + build: 419 + context: . 420 + dockerfile: localinfra/bobbin.Dockerfile 421 + restart: unless-stopped 422 + environment: 423 + BOBBIN_BIND: 0.0.0.0:8090 424 + BOBBIN_HYDRANT_URL: http://hydrant:3000 425 + BOBBIN_SLINGSHOT_URL: http://hydrant:3000 426 + BOBBIN_KNOT_ALLOW_PRIVATE: "true" 427 + BOBBIN_KNOT_REQUIRE_HTTPS: "false" 428 + BOBBIN_LOG: info 429 + volumes: 430 + - .:/src:cached 431 + - bobbin-cargo:/cargo 432 + - bobbin-target:/target 433 + - ./localinfra/certs/root.crt:/etc/ssl/certs/ca-certificates.crt:ro 434 + healthcheck: 435 + test: ["CMD", "wget", "-qO-", "http://localhost:8090/xrpc/sh.tangled.bobbin.getCoverage"] 436 + interval: 5s 437 + timeout: 2s 438 + retries: 15 439 + start_period: 10s 440 + depends_on: 441 + hydrant: 442 + condition: service_started 443 + caddy: 444 + condition: service_started 445 + networks: [tngl] 446 + 447 + web: 448 + build: 449 + context: . 450 + dockerfile: localinfra/web.Dockerfile 451 + restart: unless-stopped 452 + environment: 453 + BOBBIN_URL: https://bobbin.tngl.boltless.dev 454 + VITE_HANDLE_RESOLVER_URL: https://pds.tngl.boltless.dev 455 + VITE_PLC_DIRECTORY_URL: https://plc.tngl.boltless.dev 456 + # host-side port; keeps the oauth loopback redirect consistent 457 + VITE_DEV_PORT: "5174" 458 + NODE_EXTRA_CA_CERTS: /caddy-ca/root.crt 459 + ports: 460 + - "5174:5174" 461 + volumes: 462 + - ./web:/src:cached 463 + # shadow generated dirs so the host tree stays untouched 464 + - web-node-modules:/src/node_modules 465 + - web-svelte-kit:/src/.svelte-kit 466 + - ./localinfra/certs/root.crt:/caddy-ca/root.crt:ro 467 + healthcheck: 468 + test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:5174/').then(r=>process.exit(r.ok?0:1),()=>process.exit(1))"] 469 + interval: 5s 470 + timeout: 2s 471 + retries: 30 472 + start_period: 20s 473 + depends_on: 474 + bobbin: 475 + condition: service_started 476 + caddy: 477 + condition: service_started 478 + networks: [tngl] 479 + 377 480 caddy: 378 481 image: caddy:2-alpine 379 482 restart: unless-stopped 380 483 ports: 381 484 - "80:80" 382 485 - "443:443" 486 + - "8090:8090" 383 487 volumes: 384 488 - ./localinfra/Caddyfile:/etc/caddy/Caddyfile 385 489 - ./localinfra/certs:/etc/caddy/certs:ro ··· 400 504 - mirror.tngl.boltless.dev 401 505 - zoekt.tngl.boltless.dev 402 506 - pdsls.tngl.boltless.dev 507 + - bobbin.tngl.boltless.dev 403 508 404 509 volumes: 405 510 caddy-data: ··· 417 522 go-cache: 418 523 go-mod-cache: 419 524 appview-data: 525 + hydrant-data: 526 + bobbin-cargo: 527 + bobbin-target: 528 + web-node-modules: 529 + web-svelte-kit: 420 530 421 531 networks: 422 532 tngl:
+39
localinfra/Caddyfile
··· 78 78 tls internal 79 79 reverse_proxy pdsls:80 80 80 } 81 + 82 + # bobbin (read appview / xrpc). permissive CORS so the web/ frontend in the 83 + # browser can hit it cross-origin (bobbin serves no CORS headers itself). 84 + bobbin.tngl.boltless.dev { 85 + tls internal 86 + @cors_preflight method OPTIONS 87 + handle @cors_preflight { 88 + header Access-Control-Allow-Origin "*" 89 + header Access-Control-Allow-Methods "GET, POST, OPTIONS" 90 + header Access-Control-Allow-Headers "Content-Type, authorization, atproto-proxy" 91 + header Access-Control-Max-Age "86400" 92 + respond 204 93 + } 94 + handle { 95 + header Access-Control-Allow-Origin "*" 96 + header Access-Control-Allow-Methods "GET, POST, OPTIONS" 97 + header Access-Control-Allow-Headers "Content-Type, authorization, atproto-proxy" 98 + reverse_proxy bobbin:8090 99 + } 100 + } 101 + 102 + # bobbin over plain http on :8090 with permissive CORS, for local web/ dev. 103 + # bobbin serves no CORS headers itself (prod adds them at its reverse proxy). 104 + :8090 { 105 + @cors_preflight method OPTIONS 106 + handle @cors_preflight { 107 + header Access-Control-Allow-Origin "*" 108 + header Access-Control-Allow-Methods "GET, POST, OPTIONS" 109 + header Access-Control-Allow-Headers "Content-Type, authorization, atproto-proxy" 110 + header Access-Control-Max-Age "86400" 111 + respond 204 112 + } 113 + handle { 114 + header Access-Control-Allow-Origin "*" 115 + header Access-Control-Allow-Methods "GET, POST, OPTIONS" 116 + header Access-Control-Allow-Headers "Content-Type, authorization, atproto-proxy" 117 + reverse_proxy bobbin:8090 118 + } 119 + }
+24
localinfra/bobbin.Dockerfile
··· 1 + # Development only. Not for production use. 2 + FROM rust:1.96-slim-trixie 3 + 4 + RUN apt-get update && apt-get install -y --no-install-recommends \ 5 + ca-certificates pkg-config perl make cmake clang mold git curl wget \ 6 + && rm -rf /var/lib/apt/lists/* 7 + 8 + ENV RUSTFLAGS="-C linker=clang -C link-arg=-fuse-ld=mold" 9 + ENV CARGO_HOME=/cargo 10 + ENV CARGO_TARGET_DIR=/target 11 + 12 + COPY <<'EOF' /usr/local/bin/bobbin-entrypoint.sh 13 + #!/bin/sh 14 + set -eu 15 + cargo build --release --bin bobbin --package bobbin 16 + exec /target/release/bobbin 17 + EOF 18 + RUN chmod +x /usr/local/bin/bobbin-entrypoint.sh 19 + 20 + WORKDIR /src 21 + 22 + EXPOSE 8090 23 + 24 + ENTRYPOINT ["/usr/local/bin/bobbin-entrypoint.sh"]
+16
localinfra/readme.md
··· 19 19 - appview (<https://tngl.boltless.dev>) (live reloading) 20 20 - [ncps](https://github.com/kalbasit/ncps) nix binary cache (internal, `http://ncps:8501`) 21 21 - pdsls (<https://pdsls.tngl.boltless.dev>) 22 + - bobbin (<https://bobbin.tngl.boltless.dev>, host `:8090`) 23 + - hydrant indexer + record/identity resolver feeding bobbin (internal) 24 + - web/ sveltekit frontend (host `127.0.0.1:5174`, live reloading) 22 25 - caddy reverse proxy 23 26 24 27 ## Setup ··· 53 56 6. AppView will be running on `127.0.0.1:3000` with two test users: `alice.pds.tngl.boltless.dev` and `bob.pds.tngl.boltless.dev`. Both with password `password`. 54 57 55 58 `TANGLED_APPVIEW_HOST` must be a loopback IP with the mapped port (`127.0.0.1:3000`), not `localhost`: atproto's dev OAuth client requires a loopback IP for the redirect URI. If you remap the published appview port, update `TANGLED_APPVIEW_HOST` in `docker-compose.yml` to match. 59 + 60 + ## bobbin stack 61 + 62 + The `web` service runs `vite dev` against the local bobbin on 63 + `http://127.0.0.1:5174` (port 5174 so it doesn't clash with a host-side 64 + `pnpm dev` on 5173). For host-side `web/` dev, point the frontend at the 65 + local bobbin (e.g. in `web/.env`): 66 + 67 + ```bash 68 + BOBBIN_URL=http://127.0.0.1:8090 69 + VITE_HANDLE_RESOLVER_URL=https://pds.tngl.boltless.dev 70 + VITE_PLC_DIRECTORY_URL=https://plc.tngl.boltless.dev 71 + ```
+18
localinfra/web.Dockerfile
··· 1 + # Development only. Not for production use. 2 + FROM node:24-slim 3 + 4 + RUN corepack enable && corepack install -g pnpm@11.10.0 5 + 6 + COPY <<'EOF' /usr/local/bin/web-entrypoint.sh 7 + #!/bin/sh 8 + set -eu 9 + corepack pnpm install --frozen-lockfile --store-dir /src/node_modules/.pnpm-store 10 + exec corepack pnpm exec vite dev --host 0.0.0.0 11 + EOF 12 + RUN chmod +x /usr/local/bin/web-entrypoint.sh 13 + 14 + WORKDIR /src 15 + 16 + EXPOSE 5174 17 + 18 + ENTRYPOINT ["/usr/local/bin/web-entrypoint.sh"]
+1
web/.gitignore
··· 1 1 node_modules 2 + .pnpm-store 2 3 3 4 # Output 4 5 .output
+3 -3
web/vite.config.ts
··· 4 4 import { defineConfig } from 'vitest/config'; 5 5 import oauthMetadata from './static/oauth-client-metadata.json'; 6 6 7 - const devHost = '127.0.0.1'; 8 - const devPort = 5173; 9 - const devRedirectUri = `http://${devHost}:${devPort}/oauth/callback`; 7 + const devHost = process.env.VITE_DEV_HOST ?? '127.0.0.1'; 8 + const devPort = Number(process.env.VITE_DEV_PORT ?? 5173); 9 + const devRedirectUri = `http://127.0.0.1:${devPort}/oauth/callback`; 10 10 const devClientId = `http://localhost?redirect_uri=${encodeURIComponent(devRedirectUri)}&scope=${encodeURIComponent(oauthMetadata.scope)}`; 11 11 12 12 export default defineConfig({