fix: route Claude API through backend instead of nginx envsubst
- Add Claude API proxy in mock-backend.js (reads ANTHROPIC_API_KEY from env) - Supports SSE streaming via pipe - Move ANTHROPIC_API_KEY to backend service in docker-compose.demo.yml - Remove envsubst from entrypoint (no longer needed) - nginx-demo.conf proxies /aiui/api/claude/ to backend This fixes the 401 error when Portainer doesn't pass env vars to nginx correctly — the Node.js backend reads process.env directly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
08eb3b61e0
commit
f8e5e947ec
@ -13,6 +13,7 @@ services:
|
|||||||
container_name: archy-demo-backend
|
container_name: archy-demo-backend
|
||||||
environment:
|
environment:
|
||||||
VITE_DEV_MODE: "existing" # Skip setup/onboarding, go straight to login
|
VITE_DEV_MODE: "existing" # Skip setup/onboarding, go straight to login
|
||||||
|
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
|
||||||
expose:
|
expose:
|
||||||
- "5959"
|
- "5959"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@ -22,8 +23,6 @@ services:
|
|||||||
context: .
|
context: .
|
||||||
dockerfile: neode-ui/Dockerfile.web
|
dockerfile: neode-ui/Dockerfile.web
|
||||||
container_name: archy-demo-web
|
container_name: archy-demo-web
|
||||||
environment:
|
|
||||||
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
|
|
||||||
ports:
|
ports:
|
||||||
- "4848:80"
|
- "4848:80"
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# Substitute only ANTHROPIC_API_KEY in nginx config, leave nginx variables untouched
|
# Copy nginx config (no envsubst needed — API key is handled by backend)
|
||||||
envsubst '${ANTHROPIC_API_KEY}' < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf
|
cp /etc/nginx/nginx.conf.template /etc/nginx/nginx.conf
|
||||||
exec nginx -g 'daemon off;'
|
exec nginx -g 'daemon off;'
|
||||||
|
|||||||
@ -78,22 +78,15 @@ http {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Proxy Claude API requests from AIUI to Anthropic
|
# Proxy Claude API requests to backend (which handles API key + streaming)
|
||||||
location /aiui/api/claude/ {
|
location /aiui/api/claude/ {
|
||||||
rewrite ^/aiui/api/claude/(.*)$ /$1 break;
|
proxy_pass http://neode-backend:5959;
|
||||||
|
proxy_http_version 1.1;
|
||||||
proxy_pass https://api.anthropic.com;
|
proxy_set_header Host $host;
|
||||||
proxy_ssl_server_name on;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header Host api.anthropic.com;
|
|
||||||
proxy_set_header x-api-key "${ANTHROPIC_API_KEY}";
|
|
||||||
proxy_set_header anthropic-version "2023-06-01";
|
|
||||||
proxy_set_header Content-Type "application/json";
|
|
||||||
|
|
||||||
# SSE streaming support
|
|
||||||
proxy_buffering off;
|
proxy_buffering off;
|
||||||
proxy_cache off;
|
proxy_cache off;
|
||||||
proxy_read_timeout 300s;
|
proxy_read_timeout 300s;
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Connection "";
|
proxy_set_header Connection "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1016,6 +1016,55 @@ app.get('/app/filebrowser/api/raw/*', (req, res) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Claude API Proxy (reads ANTHROPIC_API_KEY from environment)
|
||||||
|
// =============================================================================
|
||||||
|
import https from 'https'
|
||||||
|
|
||||||
|
app.post('/aiui/api/claude/*', (req, res) => {
|
||||||
|
const apiKey = process.env.ANTHROPIC_API_KEY
|
||||||
|
if (!apiKey) {
|
||||||
|
return res.status(500).json({
|
||||||
|
type: 'error',
|
||||||
|
error: { type: 'configuration_error', message: 'ANTHROPIC_API_KEY not configured on server' }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiPath = '/' + req.params[0]
|
||||||
|
const bodyStr = JSON.stringify(req.body)
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
hostname: 'api.anthropic.com',
|
||||||
|
port: 443,
|
||||||
|
path: apiPath,
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'x-api-key': apiKey,
|
||||||
|
'anthropic-version': '2023-06-01',
|
||||||
|
'Content-Length': Buffer.byteLength(bodyStr),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const proxyReq = https.request(options, (proxyRes) => {
|
||||||
|
res.writeHead(proxyRes.statusCode, proxyRes.headers)
|
||||||
|
proxyRes.pipe(res)
|
||||||
|
})
|
||||||
|
|
||||||
|
proxyReq.on('error', (err) => {
|
||||||
|
console.error('[Claude Proxy] Error:', err.message)
|
||||||
|
if (!res.headersSent) {
|
||||||
|
res.status(502).json({
|
||||||
|
type: 'error',
|
||||||
|
error: { type: 'proxy_error', message: err.message }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
proxyReq.write(bodyStr)
|
||||||
|
proxyReq.end()
|
||||||
|
})
|
||||||
|
|
||||||
// Health check
|
// Health check
|
||||||
app.get('/health', (req, res) => {
|
app.get('/health', (req, res) => {
|
||||||
res.status(200).send('healthy')
|
res.status(200).send('healthy')
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user