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.
73 lines
2.7 KiB
Markdown
73 lines
2.7 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
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`:
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
# 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`.
|