Monorepo for Tangled tangled.org
4

Configure Feed

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

localinfra: run camo and avatar

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

author
dawn
date (Jul 25, 2026, 9:13 PM +0300) commit 1a94c966 parent 050a6282 change-id vptrnsky
+132 -1
+56
docker-compose.yml
··· 450 450 restart: unless-stopped 451 451 environment: 452 452 BOBBIN_URL: https://bobbin.tngl.boltless.dev 453 + CAMO_URL: https://camo.tngl.boltless.dev 454 + CAMO_SHARED_SECRET: localinfra-camo-secret 455 + AVATAR_URL: https://avatar.tngl.boltless.dev 456 + AVATAR_SHARED_SECRET: localinfra-avatar-secret 453 457 VITE_HANDLE_RESOLVER_URL: https://pds.tngl.boltless.dev 454 458 VITE_PLC_DIRECTORY_URL: https://plc.tngl.boltless.dev 455 459 # host-side port; keeps the oauth loopback redirect consistent 456 460 VITE_DEV_PORT: "5174" 461 + # the bind mount below does not deliver inotify, see vite.config.ts 462 + VITE_WATCH_POLL: "true" 457 463 NODE_EXTRA_CA_CERTS: /caddy-ca/root.crt 458 464 ports: 459 465 - "5174:5174" ··· 476 482 condition: service_started 477 483 networks: [tngl] 478 484 485 + camo: 486 + build: 487 + context: . 488 + dockerfile: localinfra/camo.Dockerfile 489 + restart: unless-stopped 490 + environment: 491 + CAMO_SHARED_SECRET: localinfra-camo-secret 492 + WRANGLER_SEND_METRICS: "false" 493 + volumes: 494 + - ./camo:/src:cached 495 + # shadow generated dirs so the host tree stays untouched 496 + - camo-node-modules:/src/node_modules 497 + - camo-wrangler:/src/.wrangler 498 + - ./localinfra/certs/root.crt:/caddy-ca/root.crt:ro 499 + healthcheck: 500 + test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:8787/').then(r=>process.exit(r.ok?0:1),()=>process.exit(1))"] 501 + interval: 5s 502 + timeout: 2s 503 + retries: 30 504 + start_period: 30s 505 + networks: [tngl] 506 + 507 + avatar: 508 + build: 509 + context: . 510 + dockerfile: localinfra/avatar.Dockerfile 511 + restart: unless-stopped 512 + environment: 513 + AVATAR_SHARED_SECRET: localinfra-avatar-secret 514 + PLC_DIRECTORY_URL: https://plc.tngl.boltless.dev 515 + WRANGLER_SEND_METRICS: "false" 516 + volumes: 517 + - ./avatar:/src:cached 518 + - avatar-node-modules:/src/node_modules 519 + - avatar-wrangler:/src/.wrangler 520 + - ./localinfra/certs/root.crt:/caddy-ca/root.crt:ro 521 + healthcheck: 522 + test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:8787/').then(r=>process.exit(r.ok?0:1),()=>process.exit(1))"] 523 + interval: 5s 524 + timeout: 2s 525 + retries: 30 526 + start_period: 30s 527 + networks: [tngl] 528 + 479 529 caddy: 480 530 image: caddy:2-alpine 481 531 restart: unless-stopped ··· 504 554 - zoekt.tngl.boltless.dev 505 555 - pdsls.tngl.boltless.dev 506 556 - bobbin.tngl.boltless.dev 557 + - camo.tngl.boltless.dev 558 + - avatar.tngl.boltless.dev 507 559 508 560 volumes: 509 561 caddy-data: ··· 526 578 bobbin-target: 527 579 web-node-modules: 528 580 web-svelte-kit: 581 + camo-node-modules: 582 + camo-wrangler: 583 + avatar-node-modules: 584 + avatar-wrangler: 529 585 530 586 networks: 531 587 tngl:
+11
localinfra/Caddyfile
··· 79 79 reverse_proxy pdsls:80 80 80 } 81 81 82 + # camo and avatar 83 + camo.tngl.boltless.dev { 84 + tls internal 85 + reverse_proxy camo:8787 86 + } 87 + 88 + avatar.tngl.boltless.dev { 89 + tls internal 90 + reverse_proxy avatar:8787 91 + } 92 + 82 93 # bobbin (read appview / xrpc). permissive CORS so the web/ frontend in the 83 94 # browser can hit it cross-origin (bobbin serves no CORS headers itself). 84 95 bobbin.tngl.boltless.dev {
+27
localinfra/avatar.Dockerfile
··· 1 + # Development only. Not for production use. 2 + FROM node:24-slim 3 + 4 + # slim carries no trust store and workerd reads the system one 5 + RUN apt-get update \ 6 + && apt-get install -y --no-install-recommends ca-certificates \ 7 + && rm -rf /var/lib/apt/lists/* 8 + 9 + COPY <<'EOF' /usr/local/bin/avatar-entrypoint.sh 10 + #!/bin/sh 11 + set -eu 12 + if [ -f /caddy-ca/root.crt ]; then 13 + cp /caddy-ca/root.crt /usr/local/share/ca-certificates/caddy-dev.crt 14 + update-ca-certificates >/dev/null 15 + fi 16 + npm ci --no-audit --no-fund 17 + exec npx wrangler dev --ip 0.0.0.0 --port 8787 \ 18 + --var "AVATAR_SHARED_SECRET:${AVATAR_SHARED_SECRET}" \ 19 + --var "PLC_DIRECTORY_URL:${PLC_DIRECTORY_URL}" 20 + EOF 21 + RUN chmod +x /usr/local/bin/avatar-entrypoint.sh 22 + 23 + WORKDIR /src 24 + 25 + EXPOSE 8787 26 + 27 + ENTRYPOINT ["/usr/local/bin/avatar-entrypoint.sh"]
+26
localinfra/camo.Dockerfile
··· 1 + # Development only. Not for production use. 2 + FROM node:24-slim 3 + 4 + # slim carries no trust store and workerd reads the system one 5 + RUN apt-get update \ 6 + && apt-get install -y --no-install-recommends ca-certificates \ 7 + && rm -rf /var/lib/apt/lists/* 8 + 9 + COPY <<'EOF' /usr/local/bin/camo-entrypoint.sh 10 + #!/bin/sh 11 + set -eu 12 + if [ -f /caddy-ca/root.crt ]; then 13 + cp /caddy-ca/root.crt /usr/local/share/ca-certificates/caddy-dev.crt 14 + update-ca-certificates >/dev/null 15 + fi 16 + npm ci --no-audit --no-fund 17 + exec npx wrangler dev --ip 0.0.0.0 --port 8787 \ 18 + --var "CAMO_SHARED_SECRET:${CAMO_SHARED_SECRET}" 19 + EOF 20 + RUN chmod +x /usr/local/bin/camo-entrypoint.sh 21 + 22 + WORKDIR /src 23 + 24 + EXPOSE 8787 25 + 26 + ENTRYPOINT ["/usr/local/bin/camo-entrypoint.sh"]
+4
localinfra/readme.md
··· 21 21 - pdsls (<https://pdsls.tngl.boltless.dev>) 22 22 - bobbin (<https://bobbin.tngl.boltless.dev>, host `:8090`) 23 23 - hydrant indexer + record/identity resolver feeding bobbin (internal) 24 + - camo (<https://camo.tngl.boltless.dev>) and avatar 25 + (<https://avatar.tngl.boltless.dev>), the workers from `camo/` and `avatar/` 26 + under wrangler. avatar resolves against the local plc, so it serves real 27 + avatars for accounts that have one and a generated placeholder otherwise 24 28 - web/ sveltekit frontend (host `127.0.0.1:5174`, live reloading) 25 29 - caddy reverse proxy 26 30
+8 -1
web/vite.config.ts
··· 13 13 // More info at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon 14 14 const devHost = process.env.VITE_DEV_HOST ?? "127.0.0.1"; 15 15 const devPort = Number(process.env.VITE_DEV_PORT ?? 5173); 16 + // docker desktop's macos file sharing never forwards inotify to the container, 17 + // so an edit on the host lands on disk but the watcher never fires and hmr goes 18 + // stale. polling is the only way to see those writes, and only the container 19 + // needs it 20 + const watch = 21 + process.env.VITE_WATCH_POLL === "true" ? { usePolling: true, interval: 300 } : undefined; 16 22 const devRedirectUri = `http://127.0.0.1:${devPort}/oauth/callback`; 17 23 const devClientId = `http://localhost?redirect_uri=${encodeURIComponent(devRedirectUri)}&scope=${encodeURIComponent(oauthMetadata.scope)}`; 18 24 export default defineConfig({ ··· 42 48 host: devHost, 43 49 port: devPort, 44 50 strictPort: true, 45 - allowedHosts: [".ts.net"] 51 + allowedHosts: [".ts.net"], 52 + watch 46 53 }, 47 54 test: { 48 55 expect: {