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>

dawn (Jul 13, 2026, 3:58 PM +0300) 4584a112 c04bcc18

+214 -3
+1
.gitignore
··· 29 29 build/ 30 30 .wrangler/ 31 31 localinfra/certs/* 32 + localinfra/vendor/ 32 33 id_rsa 33 34 id_ecdsa 34 35 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
··· 342 342 TANGLED_CODESEARCH_ZOEKT_URL: https://zoekt.tngl.boltless.dev 343 343 ports: 344 344 - "3000:3000" 345 + healthcheck: 346 + test: ["CMD", "wget", "-qO-", "http://localhost:3000/"] 347 + interval: 5s 348 + timeout: 2s 349 + retries: 15 350 + start_period: 10s 345 351 volumes: 346 352 - .:/src:cached 347 353 - go-cache:/go/cache ··· 370 376 condition: service_completed_successfully 371 377 networks: [tngl] 372 378 379 + hydrant: 380 + build: 381 + context: https://tangled.org/ptr.pet/hydrant.git#main 382 + restart: unless-stopped 383 + environment: 384 + HYDRANT_API_BIND: 0.0.0.0:3000 385 + HYDRANT_DATABASE_PATH: /data/hydrant.db 386 + HYDRANT_RELAY_HOSTS: pds::wss://pds.tngl.boltless.dev 387 + HYDRANT_CRAWLER_URLS: list_repos::https://pds.tngl.boltless.dev 388 + HYDRANT_PLC_URL: https://plc.tngl.boltless.dev 389 + HYDRANT_FILTER_COLLECTIONS: sh.tangled.* 390 + 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 391 + HYDRANT_BACKFILL_STRATEGY: sparse-filter 392 + HYDRANT_VERIFY_SIGNATURES: none 393 + RUST_LOG: info 394 + volumes: 395 + - hydrant-data:/data 396 + - ./localinfra/certs/root.crt:/etc/ssl/certs/ca-certificates.crt:ro 397 + healthcheck: 398 + test: ["CMD", "bash", "-c", "echo > /dev/tcp/127.0.0.1/3000"] 399 + interval: 5s 400 + timeout: 2s 401 + retries: 15 402 + start_period: 10s 403 + depends_on: 404 + plc: 405 + condition: service_started 406 + pds: 407 + condition: service_healthy 408 + caddy: 409 + condition: service_started 410 + init-accounts: 411 + condition: service_completed_successfully 412 + networks: [tngl] 413 + bobbin: 414 + build: 415 + context: . 416 + dockerfile: localinfra/bobbin.Dockerfile 417 + restart: unless-stopped 418 + environment: 419 + BOBBIN_BIND: 0.0.0.0:8090 420 + BOBBIN_HYDRANT_URL: http://hydrant:3000 421 + BOBBIN_SLINGSHOT_URL: http://hydrant:3000 422 + BOBBIN_KNOT_ALLOW_PRIVATE: "true" 423 + BOBBIN_KNOT_REQUIRE_HTTPS: "false" 424 + BOBBIN_LOG: info 425 + volumes: 426 + - .:/src:cached 427 + - bobbin-cargo:/cargo 428 + - bobbin-target:/target 429 + - ./localinfra/certs/root.crt:/etc/ssl/certs/ca-certificates.crt:ro 430 + healthcheck: 431 + test: ["CMD", "wget", "-qO-", "http://localhost:8090/xrpc/sh.tangled.bobbin.getCoverage"] 432 + interval: 5s 433 + timeout: 2s 434 + retries: 15 435 + start_period: 10s 436 + depends_on: 437 + hydrant: 438 + condition: service_started 439 + caddy: 440 + condition: service_started 441 + networks: [tngl] 442 + 443 + web: 444 + build: 445 + context: . 446 + dockerfile: localinfra/web.Dockerfile 447 + restart: unless-stopped 448 + environment: 449 + BOBBIN_URL: https://bobbin.tngl.boltless.dev 450 + VITE_HANDLE_RESOLVER_URL: https://pds.tngl.boltless.dev 451 + VITE_PLC_DIRECTORY_URL: https://plc.tngl.boltless.dev 452 + # host-side port; keeps the oauth loopback redirect consistent 453 + VITE_DEV_PORT: "5174" 454 + NODE_EXTRA_CA_CERTS: /caddy-ca/root.crt 455 + ports: 456 + - "5174:5174" 457 + volumes: 458 + - ./web:/src:cached 459 + # shadow generated dirs so the host tree stays untouched 460 + - web-node-modules:/src/node_modules 461 + - web-svelte-kit:/src/.svelte-kit 462 + - ./localinfra/certs/root.crt:/caddy-ca/root.crt:ro 463 + healthcheck: 464 + test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:5174/').then(r=>process.exit(r.ok?0:1),()=>process.exit(1))"] 465 + interval: 5s 466 + timeout: 2s 467 + retries: 30 468 + start_period: 20s 469 + depends_on: 470 + bobbin: 471 + condition: service_started 472 + caddy: 473 + condition: service_started 474 + networks: [tngl] 475 + 373 476 caddy: 374 477 image: caddy:2-alpine 375 478 restart: unless-stopped 376 479 ports: 377 480 - "80:80" 378 481 - "443:443" 482 + - "8090:8090" 379 483 volumes: 380 484 - ./localinfra/Caddyfile:/etc/caddy/Caddyfile 381 485 - ./localinfra/certs:/etc/caddy/certs:ro ··· 394 498 - mirror.tngl.boltless.dev 395 499 - zoekt.tngl.boltless.dev 396 500 - pdsls.tngl.boltless.dev 501 + - bobbin.tngl.boltless.dev 397 502 398 503 volumes: 399 504 caddy-data: ··· 411 516 go-cache: 412 517 go-mod-cache: 413 518 appview-data: 519 + hydrant-data: 520 + bobbin-cargo: 521 + bobbin-target: 522 + web-node-modules: 523 + web-svelte-kit: 414 524 415 525 networks: 416 526 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({