# DeepSeek AI Proxy (nginx auth-injecting reverse proxy)

ShopProQuote uses an auth-injecting nginx reverse proxy to forward `/deepseek/` requests to `api.deepseek.com`. The proxy validates the client's PocketBase token before injecting the DeepSeek API key, so the key never reaches the browser.

## Architecture (Roadmap 1.2 — fully deployed 2026-07-06)

```
Browser → /deepseek/v1/chat/completions
  └→ nginx [auth_request /_spq_validate]
      ├→ 127.0.0.1:8092 (spq-ai-validator, systemd)
      │    └→ POST http://127.0.0.1:8091/api/collections/users/auth-refresh
      │       with client's Bearer token → 200 or 401
      └→ on 200: strip client Authorization, inject DeepSeek key,
         rewrite path, proxy_pass https://api.deepseek.com (streaming)
```

## Components

### 1. Token validator — `/opt/spq/ai-proxy/validate.js`

Node HTTP service (no deps) on 127.0.0.1:8092. Validates the incoming Bearer token against PocketBase's `/api/collections/users/auth-refresh` endpoint. Returns 200 (valid) or 401 (invalid).

Key detail: uses `auth-refresh`, NOT `refresh` — the roadmap spec wrote `/api/collections/users/refresh` which is wrong for PB 0.39. The correct endpoint is `auth-refresh`.

### 2. systemd unit — `/etc/systemd/system/spq-ai-validator.service`

Enabled + auto-restart on failure. Environment: `PB_URL=http://127.0.0.1:8091`, `PORT=8092`.

If the unit file changes on disk, run `sudo systemctl daemon-reload` to clear the stale-config warning. No restart needed if the service is already running fine.

### 3. nginx snippet — `/etc/nginx/snippets/spq-deepseek.conf`

```nginx
location /deepseek/ {
    limit_req zone=spq_ai burst=5 nodelay;        # 20r/m rate limit
    auth_request /_spq_validate;                   # token validation gate
    auth_request_set $spq_auth_status $upstream_status;
    proxy_set_header Authorization "";              # strip client's token
    include /etc/nginx/snippets/deepseek-key.conf; # sets $deepseek_key
    proxy_set_header Authorization "Bearer $deepseek_key";
    rewrite ^/deepseek/(.*)$ /$1 break;
    proxy_pass https://api.deepseek.com;
    proxy_ssl_server_name on;
    proxy_set_header Host api.deepseek.com;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_buffering off;                            # streaming responses
}

location = /_spq_validate {
    internal;
    proxy_pass http://127.0.0.1:8092/;
    proxy_pass_request_body off;
    proxy_set_header Content-Length "";
    proxy_set_header Authorization $http_authorization;
}
```

The `limit_req_zone $binary_remote_addr zone=spq_ai:10m rate=20r/m;` directive is in `/etc/nginx/nginx.conf:15`, not in the snippet.

### 4. API key — `/etc/nginx/snippets/deepseek-key.conf`

Mode 0600, root-owned. Contains `set $deepseek_key "sk-...";`. Included by the snippet, never shipped to the client. Do NOT read this file unless explicitly needed. The key is NO LONGER in the site config directly (old location, now migrated to the separate 0600 snippet file).

### 5. Inclusion

Included in the `shopproquote` server block at `/etc/nginx/sites-enabled/shopproquote` via `include /etc/nginx/snippets/spq-deepseek.conf;`.

## Frontend — `src/lib/ai.ts`

Calls `/deepseek/v1/chat/completions` with the PocketBase token as Bearer. Handles 401 (invalid/missing token) and 429 (rate limited) gracefully by returning null.

## Model

`deepseek-v4-flash` with `thinking: { type: 'disabled' }`. The thinking parameter is critical — without it, DeepSeek-v4-flash puts all tokens into reasoning content and returns empty `content` field.

## Dev proxy (vite.config.ts) — intentionally stale

`vite.config.ts` still targets `https://127.0.0.1:3448` for `/deepseek` — a dead gateway. Ray confirmed (2026-07-07) he runs prod only (nginx/dist), never `npm run dev`. The dev proxy was intentionally left stale. If dev-mode AI testing is ever needed, point it at `http://127.0.0.1:80`.

## Verify the proxy is live

```bash
# Validator running?
systemctl status spq-ai-validator
ss -tlnp | grep 8092

# nginx config valid?
sudo nginx -t

# Snippet included in site?
grep spq-deepseek /etc/nginx/sites-enabled/shopproquote
```