Same proxy as netlify/functions/ai.mjs, rebuilt as a standalone Bun service on
the CVM host so AI calls stop spending Netlify compute/bandwidth credits and
the sats-paid Routstr key lives on our box.
- ai-proxy/server.ts: Bun.serve on 127.0.0.1:8787 — NIP-98 + Pro gate, model
allowlist, per-IP throttle, /api/ai + /api/ai/status. Own tsconfig
(typecheck:ai in npm test); eslint-ignored like cvm-server.
- ai-proxy/{package.json,.env.example,skatespots-ai.service,Caddyfile,DEPLOY.md}.
- scripts/deploy-ai-proxy.sh (npm run deploy:ai): scp + bun install + unit +
restart, mirroring deploy-cvm.sh.
- Client: one place decides the AI base, src/lib/sasquatchAi.ts (also the NIP-98
u tag). Defaults unchanged (/api/ai on Netlify); set VITE_SASQUATCH_AI_BASE to
move it. Fully reversible.
Deployed and verified on the host: /api/ai/status returns the live Routstr
balance. Public TLS (DNS + Caddy) is the remaining step before switching the app
over, so no client behaviour changes yet.
2.7 KiB
SkateSpots AI proxy — placement on the CVM host
Why: the Netlify Function (netlify/functions/ai.mjs) works, but every AI call
spends Netlify compute + response bandwidth credits. This service is the same
proxy running on the CVM host we already own, so AI traffic never touches Netlify
and the Routstr key lives on our box.
browser / APK ──HTTPS──▶ Caddy (TLS) ──http──▶ 127.0.0.1:8787 (Bun)
│
└──HTTPS──▶ Routstr node (sats)
What's needed (once)
- A hostname pointing at the CVM host, e.g.
ai.skate-spots.org→ the host's public IPv4. Add it whereverskate-spots.orgDNS is managed. - Ports 80 + 443 open to the internet (firewall at the provider;
ufwis inactive on the host). - Caddy for automatic TLS (see
Caddyfilefor the install snippet).
Deploy
npm run deploy:ai # uploads server.ts + package.json + unit, installs, restarts
bash scripts/deploy-ai-proxy.sh --logs
The first deploy needs /home/ubuntu/skatespots-ai/.env to exist (the unit
reads it). Create it from ai-proxy/.env.example with the real Routstr key — that
key is the one currently in Netlify env SASQUATCH_AI_KEY:
ssh <host> 'cat > ~/skatespots-ai/.env' <<'EOF'
SASQUATCH_AI_BASE=https://routstr.githappens.space/v1
SASQUATCH_AI_KEY=<the sk-… Routstr session key>
SASQUATCH_MODELS=
SASQUATCH_REQUIRE_PRO=1
AI_PROXY_HOST=127.0.0.1
AI_PROXY_PORT=8787
EOF
ssh <host> 'chmod 600 ~/skatespots-ai/.env'
Point the app at it
The client resolves its AI base in one place (src/lib/sasquatchAi.ts). Set the
build-time override and redeploy the web app:
VITE_SASQUATCH_AI_BASE=https://ai.skate-spots.org/api/ai
Leave it unset and the app keeps using /api/ai (the Netlify Function), so the
move is reversible and can be rolled out gradually. The proxy's CORS allowlist
already includes https://skate-spots.org and the local/Capacitor origins.
Verify
# from anywhere, once TLS is up:
curl -s -X POST https://ai.skate-spots.org/api/ai/status
# → {"configured":true,...} (owner balance; public on purpose)
Notes
SASQUATCH_REQUIRE_PRO=1keeps the Pro gate (owner pubkeys bypass). A client without a valid NIP-98 header gets 401; without an active Pro pass, 402.- The proxy is per-IP throttled at 20 req/min. It's a single long-lived process, so the throttle actually holds (unlike the serverless version).
netlify/functions/ai.mjsstays in the repo as the fallback path; nothing breaks if this service is down and the client is still pointed at/api/ai.