From 2390a32f42ec29cb17c89a60ec78dec133207f40 Mon Sep 17 00:00:00 2001 From: dawn Date: Sat, 25 Jul 2026 02:54:51 +0300 Subject: [PATCH] localinfra: run camo and avatar Signed-off-by: dawn --- docker-compose.yml | 56 ++++++++++++++++++++++++++++++++++++ localinfra/Caddyfile | 11 +++++++ localinfra/avatar.Dockerfile | 27 +++++++++++++++++ localinfra/camo.Dockerfile | 26 +++++++++++++++++ localinfra/readme.md | 4 +++ web/vite.config.ts | 9 +++++- 6 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 localinfra/avatar.Dockerfile create mode 100644 localinfra/camo.Dockerfile diff --git a/docker-compose.yml b/docker-compose.yml index f02d280d..3f4820f0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -450,10 +450,16 @@ services: restart: unless-stopped environment: BOBBIN_URL: https://bobbin.tngl.boltless.dev + CAMO_URL: https://camo.tngl.boltless.dev + CAMO_SHARED_SECRET: localinfra-camo-secret + AVATAR_URL: https://avatar.tngl.boltless.dev + AVATAR_SHARED_SECRET: localinfra-avatar-secret VITE_HANDLE_RESOLVER_URL: https://pds.tngl.boltless.dev VITE_PLC_DIRECTORY_URL: https://plc.tngl.boltless.dev # host-side port; keeps the oauth loopback redirect consistent VITE_DEV_PORT: "5174" + # the bind mount below does not deliver inotify, see vite.config.ts + VITE_WATCH_POLL: "true" NODE_EXTRA_CA_CERTS: /caddy-ca/root.crt ports: - "5174:5174" @@ -476,6 +482,50 @@ services: condition: service_started networks: [tngl] + camo: + build: + context: . + dockerfile: localinfra/camo.Dockerfile + restart: unless-stopped + environment: + CAMO_SHARED_SECRET: localinfra-camo-secret + WRANGLER_SEND_METRICS: "false" + volumes: + - ./camo:/src:cached + # shadow generated dirs so the host tree stays untouched + - camo-node-modules:/src/node_modules + - camo-wrangler:/src/.wrangler + - ./localinfra/certs/root.crt:/caddy-ca/root.crt:ro + healthcheck: + test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:8787/').then(r=>process.exit(r.ok?0:1),()=>process.exit(1))"] + interval: 5s + timeout: 2s + retries: 30 + start_period: 30s + networks: [tngl] + + avatar: + build: + context: . + dockerfile: localinfra/avatar.Dockerfile + restart: unless-stopped + environment: + AVATAR_SHARED_SECRET: localinfra-avatar-secret + PLC_DIRECTORY_URL: https://plc.tngl.boltless.dev + WRANGLER_SEND_METRICS: "false" + volumes: + - ./avatar:/src:cached + - avatar-node-modules:/src/node_modules + - avatar-wrangler:/src/.wrangler + - ./localinfra/certs/root.crt:/caddy-ca/root.crt:ro + healthcheck: + test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:8787/').then(r=>process.exit(r.ok?0:1),()=>process.exit(1))"] + interval: 5s + timeout: 2s + retries: 30 + start_period: 30s + networks: [tngl] + caddy: image: caddy:2-alpine restart: unless-stopped @@ -504,6 +554,8 @@ services: - zoekt.tngl.boltless.dev - pdsls.tngl.boltless.dev - bobbin.tngl.boltless.dev + - camo.tngl.boltless.dev + - avatar.tngl.boltless.dev volumes: caddy-data: @@ -526,6 +578,10 @@ volumes: bobbin-target: web-node-modules: web-svelte-kit: + camo-node-modules: + camo-wrangler: + avatar-node-modules: + avatar-wrangler: networks: tngl: diff --git a/localinfra/Caddyfile b/localinfra/Caddyfile index 1f6053ff..7e14ce2a 100644 --- a/localinfra/Caddyfile +++ b/localinfra/Caddyfile @@ -79,6 +79,17 @@ pdsls.tngl.boltless.dev { reverse_proxy pdsls:80 } +# camo and avatar +camo.tngl.boltless.dev { + tls internal + reverse_proxy camo:8787 +} + +avatar.tngl.boltless.dev { + tls internal + reverse_proxy avatar:8787 +} + # bobbin (read appview / xrpc). permissive CORS so the web/ frontend in the # browser can hit it cross-origin (bobbin serves no CORS headers itself). bobbin.tngl.boltless.dev { diff --git a/localinfra/avatar.Dockerfile b/localinfra/avatar.Dockerfile new file mode 100644 index 00000000..a34c0838 --- /dev/null +++ b/localinfra/avatar.Dockerfile @@ -0,0 +1,27 @@ +# Development only. Not for production use. +FROM node:24-slim + +# slim carries no trust store and workerd reads the system one +RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +COPY <<'EOF' /usr/local/bin/avatar-entrypoint.sh +#!/bin/sh +set -eu +if [ -f /caddy-ca/root.crt ]; then + cp /caddy-ca/root.crt /usr/local/share/ca-certificates/caddy-dev.crt + update-ca-certificates >/dev/null +fi +npm ci --no-audit --no-fund +exec npx wrangler dev --ip 0.0.0.0 --port 8787 \ + --var "AVATAR_SHARED_SECRET:${AVATAR_SHARED_SECRET}" \ + --var "PLC_DIRECTORY_URL:${PLC_DIRECTORY_URL}" +EOF +RUN chmod +x /usr/local/bin/avatar-entrypoint.sh + +WORKDIR /src + +EXPOSE 8787 + +ENTRYPOINT ["/usr/local/bin/avatar-entrypoint.sh"] diff --git a/localinfra/camo.Dockerfile b/localinfra/camo.Dockerfile new file mode 100644 index 00000000..3784edeb --- /dev/null +++ b/localinfra/camo.Dockerfile @@ -0,0 +1,26 @@ +# Development only. Not for production use. +FROM node:24-slim + +# slim carries no trust store and workerd reads the system one +RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +COPY <<'EOF' /usr/local/bin/camo-entrypoint.sh +#!/bin/sh +set -eu +if [ -f /caddy-ca/root.crt ]; then + cp /caddy-ca/root.crt /usr/local/share/ca-certificates/caddy-dev.crt + update-ca-certificates >/dev/null +fi +npm ci --no-audit --no-fund +exec npx wrangler dev --ip 0.0.0.0 --port 8787 \ + --var "CAMO_SHARED_SECRET:${CAMO_SHARED_SECRET}" +EOF +RUN chmod +x /usr/local/bin/camo-entrypoint.sh + +WORKDIR /src + +EXPOSE 8787 + +ENTRYPOINT ["/usr/local/bin/camo-entrypoint.sh"] diff --git a/localinfra/readme.md b/localinfra/readme.md index d83eb57c..0fa4e19a 100644 --- a/localinfra/readme.md +++ b/localinfra/readme.md @@ -21,6 +21,10 @@ To make that work: - pdsls () - bobbin (, host `:8090`) - hydrant indexer + record/identity resolver feeding bobbin (internal) +- camo () and avatar + (), the workers from `camo/` and `avatar/` + under wrangler. avatar resolves against the local plc, so it serves real + avatars for accounts that have one and a generated placeholder otherwise - web/ sveltekit frontend (host `127.0.0.1:5174`, live reloading) - caddy reverse proxy diff --git a/web/vite.config.ts b/web/vite.config.ts index e293cd93..370a1a10 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -13,6 +13,12 @@ const dirname = // More info at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon const devHost = process.env.VITE_DEV_HOST ?? "127.0.0.1"; const devPort = Number(process.env.VITE_DEV_PORT ?? 5173); +// docker desktop's macos file sharing never forwards inotify to the container, +// so an edit on the host lands on disk but the watcher never fires and hmr goes +// stale. polling is the only way to see those writes, and only the container +// needs it +const watch = + process.env.VITE_WATCH_POLL === "true" ? { usePolling: true, interval: 300 } : undefined; const devRedirectUri = `http://127.0.0.1:${devPort}/oauth/callback`; const devClientId = `http://localhost?redirect_uri=${encodeURIComponent(devRedirectUri)}&scope=${encodeURIComponent(oauthMetadata.scope)}`; export default defineConfig({ @@ -42,7 +48,8 @@ export default defineConfig({ host: devHost, port: devPort, strictPort: true, - allowedHosts: [".ts.net"] + allowedHosts: [".ts.net"], + watch }, test: { expect: { -- 2.51.2