Spaces:
Sleeping
title: Selfapi-v2
emoji: π
colorFrom: gray
colorTo: blue
sdk: docker
app_port: 3001
pinned: false
Selfapi-v2 β Headless Chrome + Unpacked Extensions
A Hugging Face Space that runs a persistent, headless Chrome for Testing browser inside Docker with three custom unpacked extensions auto-loaded, plus a live web monitor to watch the browser in real time.
The browser stays logged in across restarts β on Hugging Face the profile is snapshotted to a Postgres database (see Persistence below); on a self-hosted Docker setup it lives in a named volume. The bundled extensions sync cookies / automate workflows for ChatGPT, Google Gemini, and Google Flow.
Bundled extensions
| Extension | Source dir | Loaded at | What it does |
|---|---|---|---|
| Gpt Agent Api | free-Chatgpt-api/gpt-extension |
/opt/gpt-extension |
Auto-syncs ChatGPT cookies to a local ChatGPT Free API server |
| Gemini Api Agent | free-gemini-api/gemini-extension |
/opt/gemini-extension |
Auto-syncs Google Gemini cookies to a local Free Gemini API server |
Both are Manifest V3 extensions with background service workers. Each subfolder also contains the matching backend server.
Backend servers
Each extension is the browser half of a standalone project. The extension uses your authenticated browser session and forwards requests to a local API server (which you run yourself β see each subproject's README for setup):
| Project | Server | Talks to | Purpose |
|---|---|---|---|
free-Chatgpt-api |
Go | 127.0.0.1:9225 |
ChatGPT backend API from your logged-in session |
free-gemini-api |
Go reverse-proxy | local server | Gemini text / Imagen 3 images / Gemini video |
flow-agent |
Python FastAPI + CLI | 127.0.0.1:8100 |
Google Flow T2V / V2V / I2V video + T2I / I2I images |
β The HF Space now runs all three servers in the same container as Chrome, and the monitor doubles as a single API gateway that fronts them on one public port β see API gateway. (The extensions dial their servers on hardcoded
127.0.0.1:9225/9226/9227, so the servers must share Chrome's localhost β a separate Space can't reach them.)docker-compose.ymldoes the same wiring for self-hosting.
How it works
HF Space (port 3001)
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Docker container (Debian 12) β
β β
β profilesync βββΊ restores profile from Postgres on boot β
β (Go) and backs it up every 5 min / on exit β
β β β
β monitor (3-tab) ββ€ serves the live UI + /chrome.log β
β (Go, :3001) β β
β βΌ β
β Chrome for Testing 145 (--headless=new) β
β --load-extension=/opt/gpt,/opt/gemini,/opt/flow β
β --remote-debugging-port=9222 (CDP) β
β --user-data-dir=/home/chrome/data (persistent) β
β β² β
β socat :9223 ββββββ forwards external CDP β 127.0.0.1 β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
start_hf.sh is the boot sequence:
- Restore profile β if
DATABASE_URLis set,profilesync restorepulls the last Chrome profile snapshot from Postgres so logins survive an HF restart/rebuild. - Profile repair & cleanup β fixes a nested
data/datalayout and removes cache/junk to keep the persistent profile small. - Lock removal β clears
Singleton*/LOCKfiles left by an unclean shutdown. - Preferences injection β enables
extensions.ui.developer_modeand sets a cleanexit_typeso the "restore pages?" popup never appears. - Monitor server β starts the Go 3-tab monitor on
:3001and symlinkschrome.log. - CDP proxy β
socatexposes the DevTools port (9223 β 127.0.0.1:9222). - Auto-backup β if
DATABASE_URLis set,profilesync backupruns every 5 min (plus a final backup onSIGTERM/SIGINTshutdown). - Launch Chrome β Chrome for Testing in
--headless=newwith all three extensions.
Persistence (staying logged in)
Hugging Face Spaces on the free cpu-basic tier have no persistent disk β the container
filesystem (including /home/chrome/data) is wiped on every restart, rebuild, or factory
reboot. To keep logins, the profile is snapshotted to an external Postgres database.
How it works (the profilesync Go tool, built into the image):
- On boot: restores the profile from Postgres (
profilesync restore). - Every 5 min + on shutdown: backs the profile up (
profilesync backup). - Stores it in one row:
chrome_profile(id, updated_at, data bytea)β table auto-created. - All of this is gated on the
DATABASE_URLenv var. If it is not set, restore/backup are silently skipped and logins are lost on restart.
Setup β one-time
- Create a free Postgres at neon.tech β new project β copy the
connection string. It looks like:
postgresql://USER:PASSWORD@HOST.neon.tech/DBNAME?sslmode=require - Add it as a Space secret: HF Space β Settings β Variables and secrets β
New secret
- Name:
DATABASE_URL - Value: the connection string from step 1
- Name:
- Saving the secret auto-restarts the Space. Because nothing was backed up yet, the first boot has nothing to restore β so log in once after the secret is set. Within 5 minutes that login is snapshotted to Postgres and will survive all future restarts.
π‘ Verify a backup landed: connect to the DB and run
SELECT id, updated_at, octet_length(data) FROM chrome_profile;β a row with a non-zero size means the profile is persisted.
π The connection string is a credential β keep it only in the HF secret. A local copy in
neon-dbis gitignored so it never reaches this public repo.
Why Chrome for Testing (not regular Chrome)
Branded Google Chrome β₯ 128 silently ignores --load-extension (removed by the
March 2025 Chrome RFC to curb
abuse). On a branded build the extensions never load and chrome://extensions/ shows an
empty list with no error in the log.
The fix is to run Chrome for Testing
β the unbranded build of the same Chrome version where --load-extension still works. The
Dockerfile downloads CfT 145.0.7632.117 to /opt/chrome-linux64 and start_hf.sh launches
that binary. Everything else (monitor, CDP proxy, flags) is unchanged.
Alternatives if you ever upgrade: use the CDP
Extensions.loadUnpackedmethod (requires--remote-debugging-pipe+--enable-unsafe-extension-debugging), or plain Chromium. Both also keep--load-extension-style loading working.
User-Agent must match the OS
The Space runs on Linux, so Chrome (and the backend HTTP clients) advertise a Linux User-Agent:
Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36
A macOS UA on a Linux box is an inconsistency that Cloudflare Turnstile flags, causing
the ChatGPT "verify you are human" check to loop forever (the tick never clears). The browser
UA lives in start_hf.sh; the ChatGPT backend (free-Chatgpt-api/cookies.go) uses the same
UA and Chrome version (145) so the cf_clearance cookie β which Cloudflare binds to the
exact UA β stays valid across the browser and the backend.
Endpoints
| URL | Description |
|---|---|
https://akash1313-selfapi-v2.hf.space/ |
Live Chrome monitor UI β key required (open /?key=YOUR_API_KEY once; it sets a cookie) |
https://akash1313-selfapi-v2.hf.space/chrome.log |
Streaming Chrome log β key required |
https://akash1313-selfapi-v2.hf.space/gpt/β¦ |
ChatGPT API (gateway β 127.0.0.1:9225) |
https://akash1313-selfapi-v2.hf.space/gemini/β¦ |
Gemini API (gateway β 127.0.0.1:8000) |
https://akash1313-selfapi-v2.hf.space/flow/β¦ |
Flow API (gateway β 127.0.0.1:8101) |
127.0.0.1:9222 (in-container) |
Chrome DevTools Protocol |
:9223 |
CDP proxied for external tools (via socat) |
API gateway
The monitor (monitor/main.go) is also a reverse-proxy gateway: HF exposes only one public
port (3001), so all three backend APIs are reached under one base URL by path prefix. The
gateway strips the prefix and forwards to the server on localhost:
| Prefix | Backend | Example endpoints |
|---|---|---|
/gpt/ |
free-Chatgpt-api :9225 |
/gpt/api/chat, /gpt/v1/chat/completions, /gpt/health |
/gemini/ |
free-gemini-api :8000 |
/gemini/chat, /gemini/status, /gemini/output/* |
/flow/ |
flow-agent :8101 |
/flow/generate/video, /flow/generate/image, /flow/health |
Auth β required
Every route β the API prefixes, the monitor UI (/), /api/*, /chrome.log, /logs/* β is
gated by an API_KEY Space secret. Send it as a Bearer token, ?key=, or (for the browser UI)
open /?key=YOUR_API_KEY once to set an apikey cookie. Fail-closed: if API_KEY is unset
every route returns 503, so the logged-in accounts are never exposed. This Space is also
private on Hugging Face, so the URL itself requires HF auth β defense in depth.
- Set the Space secret
API_KEY(Settings β Variables and secrets) to a strong random string. - Call it:
No/!wrong key βcurl -X POST https://akash1313-selfapi-v2.hf.space/gpt/api/chat \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"message":"hello"}'401. Wrong path β forwarded to the backend (may404there).
Project layout
chrome-space/
βββ Dockerfile # builds on akashyadav758/chrome, installs CfT, copies extensions
βββ docker-compose.yml # full self-host stack: browser + 3 API servers on one loopback
βββ start_hf.sh # container entrypoint / Chrome boot sequence
βββ profilesync/ # Go tool: Chrome profile <-> Postgres snapshot
βββ monitor/ # Go 3-tab live monitor (GPT / Gemini / Flow)
βββ free-Chatgpt-api/ # ChatGPT Free API server + gpt-extension
βββ free-gemini-api/ # Free Gemini API server + gemini-extension
βββ flow-agent/ # Flow automation CLI + Flow-extension
Deploy
This repo is the HF Space β pushing to main triggers an automatic rebuild:
git push origin main
First-time / persistence checklist:
- Push the code (above).
- Set the
DATABASE_URLsecret β see Persistence. - Log in once in the monitor UI; the profile auto-backs up within 5 min.
hf-tokenandneon-dbare gitignored β keep your HF token and DB credentials out of commits.
Self-hosting (full stack)
To run the browser and all three backend API servers locally, use docker-compose.yml.
The server containers join the chrome container's network namespace
(network_mode: "service:chrome"), so they all share one 127.0.0.1 β exactly what the
extensions expect. The Chrome profile persists in a named Docker volume (no Postgres needed).
docker compose up --build -d # browser + chatgpt + gemini + flow
docker compose down # profile survives in the chrome-profile volume
Or just the browser image:
# Build
docker build --platform linux/amd64 -t chrome-space .
# Run
docker run -d --name chrome-space -p 3001:3001 --platform linux/amd64 chrome-space
# Verify the 3 extensions loaded (look for service_worker targets)
docker exec chrome-space curl -s http://127.0.0.1:9222/json \
| python3 -c 'import json,sys;[print(t["type"],t["url"]) for t in json.load(sys.stdin)]'
Open http://localhost:3001 to watch the browser.
Troubleshooting
| Symptom | Cause / fix |
|---|---|
| Login lost after restart/rebuild | DATABASE_URL not set (or DB unreachable) β set the Postgres secret, see Persistence |
| ChatGPT Cloudflare "verify you are human" loops, tick never passes | UA/OS mismatch (macOS UA on Linux) β ensure the Linux UA in start_hf.sh. If it still loops, it's the datacenter IP (HF = AWS); route through a residential proxy via --proxy-server=http://user:pass@host:port |
chrome://extensions/ empty, no error in log |
Branded Chrome ignoring --load-extension β use Chrome for Testing (see above) |
COPY failed: ... not found at build |
Extension source dir name in the Dockerfile doesn't match disk |
| Extension listed but service worker idle | MV3 headless quirk β open the extension's toggle or hit Update on the extensions page |
Failed to connect to the bus spam in log |
Harmless D-Bus warnings in a container with no system bus |
| "Restore pages?" popup | Handled by the exit_type=Normal Preferences injection in start_hf.sh |