Spaces:
Runtime error
Run all 3 backend servers in-container behind a single API-key gateway
Browse filesThe HF Space previously ran only Chrome + extensions + monitor; the ChatGPT/
Gemini/Flow API servers existed only in docker-compose (self-host). Because the
extensions dial their servers on hardcoded 127.0.0.1:9225/9226/9227, the servers
must share Chrome's localhost β a separate Space can't reach them. So:
- Dockerfile builds the chatgpt (Go) + gemini (Go) binaries and installs the flow
(Python/FastAPI) deps into the chrome image.
- start_hf.sh launches all three on localhost alongside Chrome.
- monitor/main.go doubles as a reverse-proxy gateway on :3001, routing /gpt /gemini
/flow to 127.0.0.1:9225/8000/8101, gated by an API_KEY bearer token (fail-closed).
- docker-compose.yml collapses to the single chrome service (servers are now baked in;
separate containers would collide on the same ports).
- Add cryptography to flow requirements (lazy-imported in cli/api.py).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Dockerfile +25 -2
- README.md +40 -5
- SETUP.md +22 -0
- docker-compose.yml +14 -39
- flow-agent/requirements.txt +1 -0
- monitor/main.go +66 -0
- start_hf.sh +8 -0
|
@@ -10,6 +10,18 @@ WORKDIR /src
|
|
| 10 |
COPY profilesync/ ./
|
| 11 |
RUN go mod tidy && CGO_ENABLED=0 go build -ldflags="-s -w" -o /profilesync .
|
| 12 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
FROM akashyadav758/chrome:latest
|
| 14 |
|
| 15 |
USER root
|
|
@@ -25,6 +37,17 @@ COPY --from=monitor-build /monitor /opt/monitor3/monitor
|
|
| 25 |
# Profile-sync binary (Chrome profile <-> Postgres snapshot)
|
| 26 |
COPY --from=profilesync-build /profilesync /opt/profilesync/profilesync
|
| 27 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
# Install Chrome for Testing (unbranded). Branded google-chrome-stable (>=128)
|
| 29 |
# silently ignores --load-extension, so unpacked extensions never load. CfT is the
|
| 30 |
# unbranded build of the same Chrome version where --load-extension still works.
|
|
@@ -38,8 +61,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends unzip && \
|
|
| 38 |
|
| 39 |
# Set correct permissions and ownership
|
| 40 |
RUN rm -rf /opt/flow-extension/_metadata && \
|
| 41 |
-
chown -R 1000:1000 /opt/gpt-extension /opt/gemini-extension /opt/flow-extension /opt/chrome-linux64 /opt/monitor3 /opt/profilesync && \
|
| 42 |
-
chmod -R 755 /opt/gpt-extension /opt/gemini-extension /opt/flow-extension /opt/chrome-linux64 /opt/monitor3 /opt/profilesync
|
| 43 |
|
| 44 |
# Replace the start script with the non-root version
|
| 45 |
COPY --chmod=755 start_hf.sh /start.sh
|
|
|
|
| 10 |
COPY profilesync/ ./
|
| 11 |
RUN go mod tidy && CGO_ENABLED=0 go build -ldflags="-s -w" -o /profilesync .
|
| 12 |
|
| 13 |
+
# Build the ChatGPT API server (talks to gpt-extension on 127.0.0.1:9225).
|
| 14 |
+
FROM golang:1.26-bookworm AS chatgpt-build
|
| 15 |
+
WORKDIR /src
|
| 16 |
+
COPY chatgpt-free-api/ ./
|
| 17 |
+
RUN go mod download && CGO_ENABLED=0 go build -ldflags="-s -w" -o /agent .
|
| 18 |
+
|
| 19 |
+
# Build the Gemini API server (talks to gemini-extension on 127.0.0.1:9226).
|
| 20 |
+
FROM golang:1.26-bookworm AS gemini-build
|
| 21 |
+
WORKDIR /src
|
| 22 |
+
COPY free-gemini-api/ ./
|
| 23 |
+
RUN go mod download && CGO_ENABLED=0 go build -ldflags="-s -w" -o /free-gemini-api .
|
| 24 |
+
|
| 25 |
FROM akashyadav758/chrome:latest
|
| 26 |
|
| 27 |
USER root
|
|
|
|
| 37 |
# Profile-sync binary (Chrome profile <-> Postgres snapshot)
|
| 38 |
COPY --from=profilesync-build /profilesync /opt/profilesync/profilesync
|
| 39 |
|
| 40 |
+
# Backend API servers (run on localhost; the monitor gateway fronts them).
|
| 41 |
+
COPY --from=chatgpt-build /agent /opt/chatgpt/agent
|
| 42 |
+
COPY chatgpt-free-api/config.json /opt/chatgpt/config.json
|
| 43 |
+
COPY --from=gemini-build /free-gemini-api /opt/gemini-srv/free-gemini-api
|
| 44 |
+
COPY flow-agent/ /opt/flow-srv/
|
| 45 |
+
|
| 46 |
+
# Flow server is Python (FastAPI/uvicorn). Base image has python3; add pip + deps.
|
| 47 |
+
RUN apt-get update && apt-get install -y --no-install-recommends python3-pip && \
|
| 48 |
+
pip3 install --no-cache-dir --break-system-packages -r /opt/flow-srv/requirements.txt && \
|
| 49 |
+
apt-get clean && rm -rf /var/lib/apt/lists/*
|
| 50 |
+
|
| 51 |
# Install Chrome for Testing (unbranded). Branded google-chrome-stable (>=128)
|
| 52 |
# silently ignores --load-extension, so unpacked extensions never load. CfT is the
|
| 53 |
# unbranded build of the same Chrome version where --load-extension still works.
|
|
|
|
| 61 |
|
| 62 |
# Set correct permissions and ownership
|
| 63 |
RUN rm -rf /opt/flow-extension/_metadata && \
|
| 64 |
+
chown -R 1000:1000 /opt/gpt-extension /opt/gemini-extension /opt/flow-extension /opt/chrome-linux64 /opt/monitor3 /opt/profilesync /opt/chatgpt /opt/gemini-srv /opt/flow-srv && \
|
| 65 |
+
chmod -R 755 /opt/gpt-extension /opt/gemini-extension /opt/flow-extension /opt/chrome-linux64 /opt/monitor3 /opt/profilesync /opt/chatgpt /opt/gemini-srv /opt/flow-srv
|
| 66 |
|
| 67 |
# Replace the start script with the non-root version
|
| 68 |
COPY --chmod=755 start_hf.sh /start.sh
|
|
@@ -46,10 +46,11 @@ run yourself β see each subproject's README for setup):
|
|
| 46 |
| [`free-gemini-api`](free-gemini-api/README.md) | Go reverse-proxy | local server | Gemini text / Imagen 3 images / Gemini video |
|
| 47 |
| [`flow-agent`](flow-agent/README.md) | Python FastAPI + CLI | `127.0.0.1:8100` | Google Flow T2V / V2V / I2V video + T2I / I2I images |
|
| 48 |
|
| 49 |
-
>
|
| 50 |
-
>
|
| 51 |
-
>
|
| 52 |
-
> `
|
|
|
|
| 53 |
|
| 54 |
---
|
| 55 |
|
|
@@ -171,13 +172,47 @@ exact UA β stays valid across the browser and the backend.
|
|
| 171 |
|
| 172 |
| URL | Description |
|
| 173 |
|-----|-------------|
|
| 174 |
-
| `https://akash1313-selfapi.hf.space/` | Live Chrome monitor UI |
|
| 175 |
| `https://akash1313-selfapi.hf.space/chrome.log` | Streaming Chrome log (debugging) |
|
|
|
|
|
|
|
|
|
|
| 176 |
| `127.0.0.1:9222` (in-container) | Chrome DevTools Protocol |
|
| 177 |
| `:9223` | CDP proxied for external tools (via socat) |
|
| 178 |
|
| 179 |
---
|
| 180 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 181 |
## Project layout
|
| 182 |
|
| 183 |
```
|
|
|
|
| 46 |
| [`free-gemini-api`](free-gemini-api/README.md) | Go reverse-proxy | local server | Gemini text / Imagen 3 images / Gemini video |
|
| 47 |
| [`flow-agent`](flow-agent/README.md) | Python FastAPI + CLI | `127.0.0.1:8100` | Google Flow T2V / V2V / I2V video + T2I / I2I images |
|
| 48 |
|
| 49 |
+
> β
The HF Space now runs **all three servers in the same container** as Chrome, and the
|
| 50 |
+
> monitor doubles as a **single API gateway** that fronts them on one public port β see
|
| 51 |
+
> [API gateway](#api-gateway). (The extensions dial their servers on hardcoded
|
| 52 |
+
> `127.0.0.1:9225/9226/9227`, so the servers *must* share Chrome's localhost β a separate
|
| 53 |
+
> Space can't reach them.) `docker-compose.yml` does the same wiring for self-hosting.
|
| 54 |
|
| 55 |
---
|
| 56 |
|
|
|
|
| 172 |
|
| 173 |
| URL | Description |
|
| 174 |
|-----|-------------|
|
| 175 |
+
| `https://akash1313-selfapi.hf.space/` | Live Chrome monitor UI (open, no key) |
|
| 176 |
| `https://akash1313-selfapi.hf.space/chrome.log` | Streaming Chrome log (debugging) |
|
| 177 |
+
| `https://akash1313-selfapi.hf.space/gpt/β¦` | ChatGPT API (gateway β `127.0.0.1:9225`) |
|
| 178 |
+
| `https://akash1313-selfapi.hf.space/gemini/β¦` | Gemini API (gateway β `127.0.0.1:8000`) |
|
| 179 |
+
| `https://akash1313-selfapi.hf.space/flow/β¦` | Flow API (gateway β `127.0.0.1:8101`) |
|
| 180 |
| `127.0.0.1:9222` (in-container) | Chrome DevTools Protocol |
|
| 181 |
| `:9223` | CDP proxied for external tools (via socat) |
|
| 182 |
|
| 183 |
---
|
| 184 |
|
| 185 |
+
## API gateway
|
| 186 |
+
|
| 187 |
+
The monitor (`monitor/main.go`) is also a reverse-proxy gateway: HF exposes only one public
|
| 188 |
+
port (`3001`), so all three backend APIs are reached under one base URL by path prefix. The
|
| 189 |
+
gateway strips the prefix and forwards to the server on localhost:
|
| 190 |
+
|
| 191 |
+
| Prefix | Backend | Example endpoints |
|
| 192 |
+
|--------|---------|-------------------|
|
| 193 |
+
| `/gpt/` | chatgpt-free-api `:9225` | `/gpt/api/chat`, `/gpt/v1/chat/completions`, `/gpt/health` |
|
| 194 |
+
| `/gemini/` | free-gemini-api `:8000` | `/gemini/chat`, `/gemini/status`, `/gemini/output/*` |
|
| 195 |
+
| `/flow/` | flow-agent `:8101` | `/flow/generate/video`, `/flow/generate/image`, `/flow/health` |
|
| 196 |
+
|
| 197 |
+
### Auth β required
|
| 198 |
+
|
| 199 |
+
The three prefixes are gated by an **`API_KEY`** Space secret. Every request must send it as a
|
| 200 |
+
Bearer token (or `?key=`). **Fail-closed:** if `API_KEY` is unset the gateway returns `503`, so
|
| 201 |
+
the logged-in accounts are never accidentally exposed. The monitor UI (`/`, `/chrome.log`) stays
|
| 202 |
+
open.
|
| 203 |
+
|
| 204 |
+
1. Set the Space secret **`API_KEY`** (Settings β Variables and secrets) to a strong random string.
|
| 205 |
+
2. Call it:
|
| 206 |
+
```bash
|
| 207 |
+
curl -X POST https://akash1313-selfapi.hf.space/gpt/api/chat \
|
| 208 |
+
-H "Authorization: Bearer $API_KEY" \
|
| 209 |
+
-H "Content-Type: application/json" \
|
| 210 |
+
-d '{"message":"hello"}'
|
| 211 |
+
```
|
| 212 |
+
No/!wrong key β `401`. Wrong path β forwarded to the backend (may `404` there).
|
| 213 |
+
|
| 214 |
+
---
|
| 215 |
+
|
| 216 |
## Project layout
|
| 217 |
|
| 218 |
```
|
|
@@ -52,3 +52,25 @@ HF free Spaces have **no permanent disk**, so there logins need a Postgres datab
|
|
| 52 |
4. The Space restarts β **log in once** β it auto-saves every 5 min and survives rebuilds.
|
| 53 |
|
| 54 |
> Deploy code to HF with `git push`. Never commit the connection string.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
4. The Space restarts β **log in once** β it auto-saves every 5 min and survives rebuilds.
|
| 53 |
|
| 54 |
> Deploy code to HF with `git push`. Never commit the connection string.
|
| 55 |
+
|
| 56 |
+
---
|
| 57 |
+
|
| 58 |
+
## Using the API
|
| 59 |
+
|
| 60 |
+
All three servers run inside the container; the monitor fronts them on one URL by path:
|
| 61 |
+
|
| 62 |
+
| Service | Base path | Example |
|
| 63 |
+
|---------|-----------|---------|
|
| 64 |
+
| ChatGPT | `/gpt/` | `POST /gpt/api/chat` |
|
| 65 |
+
| Gemini | `/gemini/` | `POST /gemini/chat` |
|
| 66 |
+
| Flow | `/flow/` | `POST /flow/generate/video` |
|
| 67 |
+
|
| 68 |
+
**Set an `API_KEY` secret** (HF: Settings β secret `API_KEY`; Docker: add to `.env`) β every API
|
| 69 |
+
call must send it:
|
| 70 |
+
```bash
|
| 71 |
+
curl -X POST https://YOUR-SPACE.hf.space/gpt/api/chat \
|
| 72 |
+
-H "Authorization: Bearer YOUR_API_KEY" \
|
| 73 |
+
-H "Content-Type: application/json" \
|
| 74 |
+
-d '{"message":"hello"}'
|
| 75 |
+
```
|
| 76 |
+
Without the key the API returns `401`/`503`. The monitor UI stays open (no key).
|
|
@@ -1,13 +1,16 @@
|
|
| 1 |
-
# Runs the headless Chrome + 3
|
|
|
|
| 2 |
#
|
| 3 |
-
# The
|
| 4 |
-
#
|
| 5 |
-
#
|
|
|
|
| 6 |
#
|
| 7 |
-
#
|
| 8 |
-
# chrome : 9222 CDP,
|
| 9 |
-
#
|
| 10 |
-
#
|
|
|
|
| 11 |
# flow : 8101 http API, 8100 ext-callback, 9227 cookie-WS
|
| 12 |
|
| 13 |
services:
|
|
@@ -15,41 +18,13 @@ services:
|
|
| 15 |
build: .
|
| 16 |
platform: linux/amd64
|
| 17 |
ports:
|
| 18 |
-
- "3001:3001" #
|
|
|
|
|
|
|
| 19 |
volumes:
|
| 20 |
- chrome-profile:/home/chrome/data # persists cookies/logins across restarts
|
| 21 |
restart: unless-stopped
|
| 22 |
|
| 23 |
-
chatgpt:
|
| 24 |
-
build: ./chatgpt-free-api
|
| 25 |
-
platform: linux/amd64
|
| 26 |
-
network_mode: "service:chrome"
|
| 27 |
-
depends_on:
|
| 28 |
-
- chrome
|
| 29 |
-
restart: unless-stopped
|
| 30 |
-
|
| 31 |
-
gemini:
|
| 32 |
-
build: ./free-gemini-api
|
| 33 |
-
platform: linux/amd64
|
| 34 |
-
environment:
|
| 35 |
-
WS_PORT: "9226"
|
| 36 |
-
PORT: "8000"
|
| 37 |
-
network_mode: "service:chrome"
|
| 38 |
-
depends_on:
|
| 39 |
-
- chrome
|
| 40 |
-
restart: unless-stopped
|
| 41 |
-
|
| 42 |
-
flow:
|
| 43 |
-
build: ./flow-agent
|
| 44 |
-
platform: linux/amd64
|
| 45 |
-
environment:
|
| 46 |
-
WS_PORT: "9227"
|
| 47 |
-
HTTP_PORT: "8100"
|
| 48 |
-
network_mode: "service:chrome"
|
| 49 |
-
depends_on:
|
| 50 |
-
- chrome
|
| 51 |
-
restart: unless-stopped
|
| 52 |
-
|
| 53 |
# Named volume keeps the Chrome profile (logins/cookies) across `docker compose down/up`
|
| 54 |
# and host reboots β the self-host equivalent of HF persistent storage, no Postgres needed.
|
| 55 |
volumes:
|
|
|
|
| 1 |
+
# Runs the whole stack in one container: headless Chrome + 3 extensions + the monitor
|
| 2 |
+
# (which also serves the API gateway) + the three backend servers.
|
| 3 |
#
|
| 4 |
+
# The chrome image (built from ./Dockerfile) now bakes in all three servers and start_hf.sh
|
| 5 |
+
# launches them on localhost alongside Chrome β exactly what the extensions expect (they dial
|
| 6 |
+
# 127.0.0.1:9225/9226/9227). So there are no separate server containers anymore; that would
|
| 7 |
+
# collide on the same ports. Only port 3001 is published.
|
| 8 |
#
|
| 9 |
+
# Internal port map (all on the container's 127.0.0.1):
|
| 10 |
+
# chrome : 9222 CDP, 9223 CDP-proxy
|
| 11 |
+
# monitor : 3001 UI + API gateway (/gpt /gemini /flow)
|
| 12 |
+
# chatgpt : 9225 (ws + http API)
|
| 13 |
+
# gemini : 8000 http API, 9226 cookie-WS
|
| 14 |
# flow : 8101 http API, 8100 ext-callback, 9227 cookie-WS
|
| 15 |
|
| 16 |
services:
|
|
|
|
| 18 |
build: .
|
| 19 |
platform: linux/amd64
|
| 20 |
ports:
|
| 21 |
+
- "3001:3001" # monitor UI + API gateway (the only published port)
|
| 22 |
+
environment:
|
| 23 |
+
API_KEY: ${API_KEY:-} # gates /gpt /gemini /flow; from .env. Unset = APIs disabled (503)
|
| 24 |
volumes:
|
| 25 |
- chrome-profile:/home/chrome/data # persists cookies/logins across restarts
|
| 26 |
restart: unless-stopped
|
| 27 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
# Named volume keeps the Chrome profile (logins/cookies) across `docker compose down/up`
|
| 29 |
# and host reboots β the self-host equivalent of HF persistent storage, no Postgres needed.
|
| 30 |
volumes:
|
|
@@ -2,3 +2,4 @@ websockets>=12.0
|
|
| 2 |
fastapi>=0.100.0
|
| 3 |
uvicorn>=0.22.0
|
| 4 |
python-multipart>=0.0.6
|
|
|
|
|
|
| 2 |
fastapi>=0.100.0
|
| 3 |
uvicorn>=0.22.0
|
| 4 |
python-multipart>=0.0.6
|
| 5 |
+
cryptography>=42.0 # lazy-imported in cli/api.py for self-signed cert generation
|
|
@@ -18,6 +18,8 @@ import (
|
|
| 18 |
"io"
|
| 19 |
"log"
|
| 20 |
"net/http"
|
|
|
|
|
|
|
| 21 |
"os"
|
| 22 |
"sort"
|
| 23 |
"strings"
|
|
@@ -496,6 +498,62 @@ func statsHandler(w http.ResponseWriter, r *http.Request) {
|
|
| 496 |
})
|
| 497 |
}
|
| 498 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 499 |
func main() {
|
| 500 |
// Pre-open the known AI services in the background so they're ready by first view.
|
| 501 |
go func() {
|
|
@@ -534,6 +592,14 @@ func main() {
|
|
| 534 |
http.HandleFunc("/api/stats", statsHandler)
|
| 535 |
http.HandleFunc("/chrome.log", chromeLogHandler)
|
| 536 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 537 |
addr := ":3001"
|
| 538 |
if p := os.Getenv("MONITOR_PORT"); p != "" {
|
| 539 |
addr = ":" + p
|
|
|
|
| 18 |
"io"
|
| 19 |
"log"
|
| 20 |
"net/http"
|
| 21 |
+
"net/http/httputil"
|
| 22 |
+
"net/url"
|
| 23 |
"os"
|
| 24 |
"sort"
|
| 25 |
"strings"
|
|
|
|
| 498 |
})
|
| 499 |
}
|
| 500 |
|
| 501 |
+
// ---- API gateway ----------------------------------------------------------
|
| 502 |
+
// The monitor doubles as the single public entry point for the three backend
|
| 503 |
+
// servers that run on localhost inside this same container. HF exposes only one
|
| 504 |
+
// port (3001), so /gpt, /gemini and /flow are reverse-proxied to the servers'
|
| 505 |
+
// loopback HTTP ports. All three are gated by an API key (fail-closed).
|
| 506 |
+
|
| 507 |
+
var apiKey = os.Getenv("API_KEY")
|
| 508 |
+
|
| 509 |
+
// authOK enforces the API key on gateway routes. If API_KEY is unset it refuses
|
| 510 |
+
// every call (503) so the logged-in accounts are never accidentally wide open.
|
| 511 |
+
func authOK(w http.ResponseWriter, r *http.Request) bool {
|
| 512 |
+
if apiKey == "" {
|
| 513 |
+
http.Error(w, "gateway disabled: API_KEY not set on the server", http.StatusServiceUnavailable)
|
| 514 |
+
return false
|
| 515 |
+
}
|
| 516 |
+
got := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")
|
| 517 |
+
if got == "" {
|
| 518 |
+
got = r.URL.Query().Get("key")
|
| 519 |
+
}
|
| 520 |
+
if got != apiKey {
|
| 521 |
+
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
| 522 |
+
return false
|
| 523 |
+
}
|
| 524 |
+
return true
|
| 525 |
+
}
|
| 526 |
+
|
| 527 |
+
// gatewayHandler builds an API-key-gated reverse proxy that strips prefix and
|
| 528 |
+
// forwards to target (e.g. /gpt/api/chat -> http://127.0.0.1:9225/api/chat).
|
| 529 |
+
func gatewayHandler(prefix, target string) http.HandlerFunc {
|
| 530 |
+
u, err := url.Parse(target)
|
| 531 |
+
if err != nil {
|
| 532 |
+
log.Fatalf("bad gateway target %q: %v", target, err)
|
| 533 |
+
}
|
| 534 |
+
proxy := &httputil.ReverseProxy{
|
| 535 |
+
Director: func(r *http.Request) {
|
| 536 |
+
r.URL.Scheme = u.Scheme
|
| 537 |
+
r.URL.Host = u.Host
|
| 538 |
+
r.Host = u.Host
|
| 539 |
+
p := strings.TrimPrefix(r.URL.Path, prefix)
|
| 540 |
+
if p == "" || p[0] != '/' {
|
| 541 |
+
p = "/" + p
|
| 542 |
+
}
|
| 543 |
+
r.URL.Path = p
|
| 544 |
+
},
|
| 545 |
+
ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) {
|
| 546 |
+
http.Error(w, "backend unavailable: "+err.Error(), http.StatusBadGateway)
|
| 547 |
+
},
|
| 548 |
+
}
|
| 549 |
+
return func(w http.ResponseWriter, r *http.Request) {
|
| 550 |
+
if !authOK(w, r) {
|
| 551 |
+
return
|
| 552 |
+
}
|
| 553 |
+
proxy.ServeHTTP(w, r)
|
| 554 |
+
}
|
| 555 |
+
}
|
| 556 |
+
|
| 557 |
func main() {
|
| 558 |
// Pre-open the known AI services in the background so they're ready by first view.
|
| 559 |
go func() {
|
|
|
|
| 592 |
http.HandleFunc("/api/stats", statsHandler)
|
| 593 |
http.HandleFunc("/chrome.log", chromeLogHandler)
|
| 594 |
|
| 595 |
+
// API gateway β the three backend servers on localhost (API-key gated).
|
| 596 |
+
http.HandleFunc("/gpt/", gatewayHandler("/gpt", "http://127.0.0.1:9225"))
|
| 597 |
+
http.HandleFunc("/gemini/", gatewayHandler("/gemini", "http://127.0.0.1:8000"))
|
| 598 |
+
http.HandleFunc("/flow/", gatewayHandler("/flow", "http://127.0.0.1:8101"))
|
| 599 |
+
if apiKey == "" {
|
| 600 |
+
log.Println("WARNING: API_KEY not set β /gpt /gemini /flow are disabled (503) until it is set")
|
| 601 |
+
}
|
| 602 |
+
|
| 603 |
addr := ":3001"
|
| 604 |
if p := os.Getenv("MONITOR_PORT"); p != "" {
|
| 605 |
addr = ":" + p
|
|
@@ -106,6 +106,14 @@ touch /home/chrome/chrome.log
|
|
| 106 |
echo "π Starting CDP Proxy..."
|
| 107 |
socat TCP-LISTEN:9223,fork,bind=0.0.0.0 TCP:127.0.0.1:9222 &
|
| 108 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
# 6b. Periodic profile backup (safety net if HF kills the container without a clean
|
| 110 |
# SIGTERM). Best-effort every 5 minutes; the shutdown trap does the final one.
|
| 111 |
if [ -n "$DATABASE_URL" ]; then
|
|
|
|
| 106 |
echo "π Starting CDP Proxy..."
|
| 107 |
socat TCP-LISTEN:9223,fork,bind=0.0.0.0 TCP:127.0.0.1:9222 &
|
| 108 |
|
| 109 |
+
# 6a. Start the three backend API servers on localhost. The monitor doubles as a
|
| 110 |
+
# gateway and fronts them on :3001 (/gpt, /gemini, /flow). Each logs separately
|
| 111 |
+
# and runs in the background so a crash never blocks the boot.
|
| 112 |
+
echo "π§© Starting backend API servers (gpt / gemini / flow)..."
|
| 113 |
+
( cd /opt/chatgpt && ./agent >> /home/chrome/chatgpt.log 2>&1 & )
|
| 114 |
+
( cd /opt/gemini-srv && WS_PORT=9226 PORT=8000 ./free-gemini-api >> /home/chrome/gemini.log 2>&1 & )
|
| 115 |
+
( cd /opt/flow-srv && WS_PORT=9227 HTTP_PORT=8100 python3 -m uvicorn cli.api:app --host 127.0.0.1 --port 8101 >> /home/chrome/flow.log 2>&1 & )
|
| 116 |
+
|
| 117 |
# 6b. Periodic profile backup (safety net if HF kills the container without a clean
|
| 118 |
# SIGTERM). Best-effort every 5 minutes; the shutdown trap does the final one.
|
| 119 |
if [ -n "$DATABASE_URL" ]; then
|