From 4584a112e98c462ed0376a0901640d37bdca8e25 Mon Sep 17 00:00:00 2001 From: dawn Date: Thu, 9 Jul 2026 21:10:00 +0300 Subject: [PATCH] localinfra: add bobbin, hydrant, and web to the stack Signed-off-by: dawn --- .gitignore | 1 + bobbin/crates/resolver/src/legacy_upgrade.rs | 2 + docker-compose.yml | 110 +++++++++++++++++++ localinfra/Caddyfile | 39 +++++++ localinfra/bobbin.Dockerfile | 24 ++++ localinfra/readme.md | 16 +++ localinfra/web.Dockerfile | 18 +++ web/.gitignore | 1 + web/vite.config.ts | 6 +- 9 files changed, 214 insertions(+), 3 deletions(-) create mode 100644 localinfra/bobbin.Dockerfile create mode 100644 localinfra/web.Dockerfile diff --git a/.gitignore b/.gitignore index 3b80065c..bc920579 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,7 @@ blog/build/ build/ .wrangler/ localinfra/certs/* +localinfra/vendor/ id_rsa id_ecdsa id_dsa diff --git a/bobbin/crates/resolver/src/legacy_upgrade.rs b/bobbin/crates/resolver/src/legacy_upgrade.rs index d54f7ece..91bc4f93 100644 --- a/bobbin/crates/resolver/src/legacy_upgrade.rs +++ b/bobbin/crates/resolver/src/legacy_upgrade.rs @@ -448,6 +448,8 @@ fn upgrade_ref_update(l: LegacyRefUpdate) -> RefUpdate { push_options: None, r#ref: l.r#ref, repo: l.repo_did, + changed_files: None, + push_options: None, extra_data: l.extra_data, } } diff --git a/docker-compose.yml b/docker-compose.yml index 1b0642b1..fb5ea991 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -342,6 +342,12 @@ services: TANGLED_CODESEARCH_ZOEKT_URL: https://zoekt.tngl.boltless.dev ports: - "3000:3000" + healthcheck: + test: ["CMD", "wget", "-qO-", "http://localhost:3000/"] + interval: 5s + timeout: 2s + retries: 15 + start_period: 10s volumes: - .:/src:cached - go-cache:/go/cache @@ -370,12 +376,110 @@ services: condition: service_completed_successfully networks: [tngl] + hydrant: + build: + context: https://tangled.org/ptr.pet/hydrant.git#main + restart: unless-stopped + environment: + HYDRANT_API_BIND: 0.0.0.0:3000 + HYDRANT_DATABASE_PATH: /data/hydrant.db + HYDRANT_RELAY_HOSTS: pds::wss://pds.tngl.boltless.dev + HYDRANT_CRAWLER_URLS: list_repos::https://pds.tngl.boltless.dev + HYDRANT_PLC_URL: https://plc.tngl.boltless.dev + HYDRANT_FILTER_COLLECTIONS: sh.tangled.* + 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 + HYDRANT_BACKFILL_STRATEGY: sparse-filter + HYDRANT_VERIFY_SIGNATURES: none + RUST_LOG: info + volumes: + - hydrant-data:/data + - ./localinfra/certs/root.crt:/etc/ssl/certs/ca-certificates.crt:ro + healthcheck: + test: ["CMD", "bash", "-c", "echo > /dev/tcp/127.0.0.1/3000"] + interval: 5s + timeout: 2s + retries: 15 + start_period: 10s + depends_on: + plc: + condition: service_started + pds: + condition: service_healthy + caddy: + condition: service_started + init-accounts: + condition: service_completed_successfully + networks: [tngl] + bobbin: + build: + context: . + dockerfile: localinfra/bobbin.Dockerfile + restart: unless-stopped + environment: + BOBBIN_BIND: 0.0.0.0:8090 + BOBBIN_HYDRANT_URL: http://hydrant:3000 + BOBBIN_SLINGSHOT_URL: http://hydrant:3000 + BOBBIN_KNOT_ALLOW_PRIVATE: "true" + BOBBIN_KNOT_REQUIRE_HTTPS: "false" + BOBBIN_LOG: info + volumes: + - .:/src:cached + - bobbin-cargo:/cargo + - bobbin-target:/target + - ./localinfra/certs/root.crt:/etc/ssl/certs/ca-certificates.crt:ro + healthcheck: + test: ["CMD", "wget", "-qO-", "http://localhost:8090/xrpc/sh.tangled.bobbin.getCoverage"] + interval: 5s + timeout: 2s + retries: 15 + start_period: 10s + depends_on: + hydrant: + condition: service_started + caddy: + condition: service_started + networks: [tngl] + + web: + build: + context: . + dockerfile: localinfra/web.Dockerfile + restart: unless-stopped + environment: + BOBBIN_URL: https://bobbin.tngl.boltless.dev + 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" + NODE_EXTRA_CA_CERTS: /caddy-ca/root.crt + ports: + - "5174:5174" + volumes: + - ./web:/src:cached + # shadow generated dirs so the host tree stays untouched + - web-node-modules:/src/node_modules + - web-svelte-kit:/src/.svelte-kit + - ./localinfra/certs/root.crt:/caddy-ca/root.crt:ro + healthcheck: + test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:5174/').then(r=>process.exit(r.ok?0:1),()=>process.exit(1))"] + interval: 5s + timeout: 2s + retries: 30 + start_period: 20s + depends_on: + bobbin: + condition: service_started + caddy: + condition: service_started + networks: [tngl] + caddy: image: caddy:2-alpine restart: unless-stopped ports: - "80:80" - "443:443" + - "8090:8090" volumes: - ./localinfra/Caddyfile:/etc/caddy/Caddyfile - ./localinfra/certs:/etc/caddy/certs:ro @@ -394,6 +498,7 @@ services: - mirror.tngl.boltless.dev - zoekt.tngl.boltless.dev - pdsls.tngl.boltless.dev + - bobbin.tngl.boltless.dev volumes: caddy-data: @@ -411,6 +516,11 @@ volumes: go-cache: go-mod-cache: appview-data: + hydrant-data: + bobbin-cargo: + bobbin-target: + web-node-modules: + web-svelte-kit: networks: tngl: diff --git a/localinfra/Caddyfile b/localinfra/Caddyfile index ad09cb49..1f6053ff 100644 --- a/localinfra/Caddyfile +++ b/localinfra/Caddyfile @@ -78,3 +78,42 @@ pdsls.tngl.boltless.dev { tls internal reverse_proxy pdsls:80 } + +# 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 { + tls internal + @cors_preflight method OPTIONS + handle @cors_preflight { + header Access-Control-Allow-Origin "*" + header Access-Control-Allow-Methods "GET, POST, OPTIONS" + header Access-Control-Allow-Headers "Content-Type, authorization, atproto-proxy" + header Access-Control-Max-Age "86400" + respond 204 + } + handle { + header Access-Control-Allow-Origin "*" + header Access-Control-Allow-Methods "GET, POST, OPTIONS" + header Access-Control-Allow-Headers "Content-Type, authorization, atproto-proxy" + reverse_proxy bobbin:8090 + } +} + +# bobbin over plain http on :8090 with permissive CORS, for local web/ dev. +# bobbin serves no CORS headers itself (prod adds them at its reverse proxy). +:8090 { + @cors_preflight method OPTIONS + handle @cors_preflight { + header Access-Control-Allow-Origin "*" + header Access-Control-Allow-Methods "GET, POST, OPTIONS" + header Access-Control-Allow-Headers "Content-Type, authorization, atproto-proxy" + header Access-Control-Max-Age "86400" + respond 204 + } + handle { + header Access-Control-Allow-Origin "*" + header Access-Control-Allow-Methods "GET, POST, OPTIONS" + header Access-Control-Allow-Headers "Content-Type, authorization, atproto-proxy" + reverse_proxy bobbin:8090 + } +} diff --git a/localinfra/bobbin.Dockerfile b/localinfra/bobbin.Dockerfile new file mode 100644 index 00000000..3963ed14 --- /dev/null +++ b/localinfra/bobbin.Dockerfile @@ -0,0 +1,24 @@ +# Development only. Not for production use. +FROM rust:1.96-slim-trixie + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates pkg-config perl make cmake clang mold git curl wget \ + && rm -rf /var/lib/apt/lists/* + +ENV RUSTFLAGS="-C linker=clang -C link-arg=-fuse-ld=mold" +ENV CARGO_HOME=/cargo +ENV CARGO_TARGET_DIR=/target + +COPY <<'EOF' /usr/local/bin/bobbin-entrypoint.sh +#!/bin/sh +set -eu +cargo build --release --bin bobbin --package bobbin +exec /target/release/bobbin +EOF +RUN chmod +x /usr/local/bin/bobbin-entrypoint.sh + +WORKDIR /src + +EXPOSE 8090 + +ENTRYPOINT ["/usr/local/bin/bobbin-entrypoint.sh"] diff --git a/localinfra/readme.md b/localinfra/readme.md index 6ff808cb..c31958d1 100644 --- a/localinfra/readme.md +++ b/localinfra/readme.md @@ -19,6 +19,9 @@ To make that work: - appview () (live reloading) - [ncps](https://github.com/kalbasit/ncps) nix binary cache (internal, `http://ncps:8501`) - pdsls () +- bobbin (, host `:8090`) +- hydrant indexer + record/identity resolver feeding bobbin (internal) +- web/ sveltekit frontend (host `127.0.0.1:5174`, live reloading) - caddy reverse proxy ## Setup @@ -51,3 +54,16 @@ To make that work: This writes the image directory under `out/localinfra-spindle-images`. 5. `docker compose up` 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`. + +## bobbin stack + +The `web` service runs `vite dev` against the local bobbin on +`http://127.0.0.1:5174` (port 5174 so it doesn't clash with a host-side +`pnpm dev` on 5173). For host-side `web/` dev, point the frontend at the +local bobbin (e.g. in `web/.env`): + +```bash +BOBBIN_URL=http://127.0.0.1:8090 +VITE_HANDLE_RESOLVER_URL=https://pds.tngl.boltless.dev +VITE_PLC_DIRECTORY_URL=https://plc.tngl.boltless.dev +``` diff --git a/localinfra/web.Dockerfile b/localinfra/web.Dockerfile new file mode 100644 index 00000000..6eafb639 --- /dev/null +++ b/localinfra/web.Dockerfile @@ -0,0 +1,18 @@ +# Development only. Not for production use. +FROM node:24-slim + +RUN corepack enable && corepack install -g pnpm@11.10.0 + +COPY <<'EOF' /usr/local/bin/web-entrypoint.sh +#!/bin/sh +set -eu +corepack pnpm install --frozen-lockfile --store-dir /src/node_modules/.pnpm-store +exec corepack pnpm exec vite dev --host 0.0.0.0 +EOF +RUN chmod +x /usr/local/bin/web-entrypoint.sh + +WORKDIR /src + +EXPOSE 5174 + +ENTRYPOINT ["/usr/local/bin/web-entrypoint.sh"] diff --git a/web/.gitignore b/web/.gitignore index 43fc6b67..35b011a6 100644 --- a/web/.gitignore +++ b/web/.gitignore @@ -1,4 +1,5 @@ node_modules +.pnpm-store # Output .output diff --git a/web/vite.config.ts b/web/vite.config.ts index f5bf35a9..13681876 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -4,9 +4,9 @@ import Icons from 'unplugin-icons/vite'; import { defineConfig } from 'vitest/config'; import oauthMetadata from './static/oauth-client-metadata.json'; -const devHost = '127.0.0.1'; -const devPort = 5173; -const devRedirectUri = `http://${devHost}:${devPort}/oauth/callback`; +const devHost = process.env.VITE_DEV_HOST ?? '127.0.0.1'; +const devPort = Number(process.env.VITE_DEV_PORT ?? 5173); +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({ -- 2.51.2