SkateSpots/ai-proxy/DEPLOY.md
SkateSpots ec06a63b01 feat(ai-proxy): run the AI proxy on the CVM host, ready to switch over
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.
2026-09-28 13:32:05 +07:00

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)

  1. A hostname pointing at the CVM host, e.g. ai.skate-spots.org → the host's public IPv4. Add it wherever skate-spots.org DNS is managed.
  2. Ports 80 + 443 open to the internet (firewall at the provider; ufw is inactive on the host).
  3. Caddy for automatic TLS (see Caddyfile for 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=1 keeps 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.mjs stays in the repo as the fallback path; nothing breaks if this service is down and the client is still pointed at /api/ai.