Monorepo for Tangled
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 23, 2026, 6:48 PM +0300) commit d7b7c504 parent 8e13fd34 change-id uozwvppt
+214 -3
+1
.gitignore
··· 30 30 build/ 31 31 .wrangler/ 32 32 localinfra/certs/* 33 + localinfra/vendor/ 33 34 id_rsa 34 35 id_ecdsa 35 36 id_dsa
+2
bobbin/crates/resolver/src/legacy_upgrade.rs
··· 448 448 push_options: None, 449 449 r#ref: l.r#ref, 450 450 repo: l.repo_did, 451 + changed_files: None, 452 + push_options: None, 451 453 extra_data: l.extra_data, 452 454 } 453 455 }
+110
docker-compose.yml
··· 345 345 ports: 346 346 - "3000:3000" 347 347 - "3333:3333" 348 + healthcheck: 349 + test: ["CMD", "wget", "-qO-", "http://localhost:3000/"] 350 + interval: 5s 351 + timeout: 2s 352 + retries: 15 353 + start_period: 10s 348 354 volumes: 349 355 - .:/src:cached 350 356 - go-cache:/go/cache ··· 373 379 condition: service_completed_successfully 374 380 networks: [tngl] 375 381 382 + hydrant: 383 + build: 384 + context: https://tangled.org/ptr.pet/hydrant.git#main 385 + restart: unless-stopped 386 + environment: 387 + HYDRANT_API_BIND: 0.0.0.0:3000 388 + HYDRANT_DATABASE_PATH: /data/hydrant.db 389 + HYDRANT_RELAY_HOSTS: pds::wss://pds.tngl.boltless.dev 390 + HYDRANT_CRAWLER_URLS: list_repos::https://pds.tngl.boltless.dev 391 + HYDRANT_PLC_URL: https://plc.tngl.boltless.dev 392 + HYDRANT_FILTER_COLLECTIONS: sh.tangled.* 393 + 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 394 + HYDRANT_BACKFILL_STRATEGY: sparse-filter 395 + HYDRANT_VERIFY_SIGNATURES: none 396 + RUST_LOG: info 397 + volumes: 398 + - hydrant-data:/data 399 + - ./localinfra/certs/root.crt:/etc/ssl/certs/ca-certificates.crt:ro 400 + healthcheck: 401 + test: ["CMD", "bash", "-c", "echo > /dev/tcp/127.0.0.1/3000"] 402 + interval: 5s 403 + timeout: 2s 404 + retries: 15 405 + start_period: 10s 406 + depends_on: 407 + plc: 408 + condition: service_started 409 + pds: 410 + condition: service_healthy 411 + caddy: 412 + condition: service_started 413 + init-accounts: 414 + condition: service_completed_successfully 415 + networks: [tngl] 416 + bobbin: 417 + build: 418 + context: . 419 + dockerfile: localinfra/bobbin.Dockerfile 420 + restart: unless-stopped 421 + environment: 422 + BOBBIN_BIND: 0.0.0.0:8090 423 + BOBBIN_HYDRANT_URL: http://hydrant:3000 424 + BOBBIN_SLINGSHOT_URL: http://hydrant:3000 425 + BOBBIN_KNOT_ALLOW_PRIVATE: "true" 426 + BOBBIN_KNOT_REQUIRE_HTTPS: "false" 427 + BOBBIN_LOG: info 428 + volumes: 429 + - .:/src:cached 430 + - bobbin-cargo:/cargo 431 + - bobbin-target:/target 432 + - ./localinfra/certs/root.crt:/etc/ssl/certs/ca-certificates.crt:ro 433 + healthcheck: 434 + test: ["CMD", "wget", "-qO-", "http://localhost:8090/xrpc/sh.tangled.bobbin.getCoverage"] 435 + interval: 5s 436 + timeout: 2s 437 + retries: 15 438 + start_period: 10s 439 + depends_on: 440 + hydrant: 441 + condition: service_started 442 + caddy: 443 + condition: service_started 444 + networks: [tngl] 445 + 446 + web: 447 + build: 448 + context: . 449 + dockerfile: localinfra/web.Dockerfile 450 + restart: unless-stopped 451 + environment: 452 + BOBBIN_URL: https://bobbin.tngl.boltless.dev 453 + VITE_HANDLE_RESOLVER_URL: https://pds.tngl.boltless.dev 454 + VITE_PLC_DIRECTORY_URL: https://plc.tngl.boltless.dev 455 + # host-side port; keeps the oauth loopback redirect consistent 456 + VITE_DEV_PORT: "5174" 457 + NODE_EXTRA_CA_CERTS: /caddy-ca/root.crt 458 + ports: 459 + - "5174:5174" 460 + volumes: 461 + - ./web:/src:cached 462 + # shadow generated dirs so the host tree stays untouched 463 + - web-node-modules:/src/node_modules 464 + - web-svelte-kit:/src/.svelte-kit 465 + - ./localinfra/certs/root.crt:/caddy-ca/root.crt:ro 466 + healthcheck: 467 + test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:5174/').then(r=>process.exit(r.ok?0:1),()=>process.exit(1))"] 468 + interval: 5s 469 + timeout: 2s 470 + retries: 30 471 + start_period: 20s 472 + depends_on: 473 + bobbin: 474 + condition: service_started 475 + caddy: 476 + condition: service_started 477 + networks: [tngl] 478 + 376 479 caddy: 377 480 image: caddy:2-alpine 378 481 restart: unless-stopped 379 482 ports: 380 483 - "80:80" 381 484 - "443:443" 485 + - "8090:8090" 382 486 volumes: 383 487 - ./localinfra/Caddyfile:/etc/caddy/Caddyfile 384 488 - ./localinfra/certs:/etc/caddy/certs:ro ··· 399 503 - mirror.tngl.boltless.dev 400 504 - zoekt.tngl.boltless.dev 401 505 - pdsls.tngl.boltless.dev 506 + - bobbin.tngl.boltless.dev 402 507 403 508 volumes: 404 509 caddy-data: ··· 416 521 go-cache: 417 522 go-mod-cache: 418 523 appview-data: 524 + hydrant-data: 525 + bobbin-cargo: 526 + bobbin-target: 527 + web-node-modules: 528 + web-svelte-kit: 419 529 420 530 networks: 421 531 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 ··· 51 54 This writes the image directory under `out/localinfra-spindle-images`. 52 55 5. `docker compose up` 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`. 57 + 58 + ## bobbin stack 59 + 60 + The `web` service runs `vite dev` against the local bobbin on 61 + `http://127.0.0.1:5174` (port 5174 so it doesn't clash with a host-side 62 + `pnpm dev` on 5173). For host-side `web/` dev, point the frontend at the 63 + local bobbin (e.g. in `web/.env`): 64 + 65 + ```bash 66 + BOBBIN_URL=http://127.0.0.1:8090 67 + VITE_HANDLE_RESOLVER_URL=https://pds.tngl.boltless.dev 68 + VITE_PLC_DIRECTORY_URL=https://plc.tngl.boltless.dev 69 + ```
+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({