#!/bin/bash # Graceful Shutdown Handler cleanup() { echo "๐Ÿ›‘ Shutting down..." pkill -TERM -f chrome-linux64 2>/dev/null || true sleep 5 # Snapshot the profile (cookies/logins) to Postgres after Chrome has closed # its files, so the next boot restores a consistent login state. if [ -n "$DATABASE_URL" ]; then echo "๐Ÿ’พ Backing up Chrome profile to Postgres..." /opt/profilesync/profilesync backup || echo "โš ๏ธ Backup failed" fi exit 0 } trap cleanup SIGTERM SIGINT echo "๐Ÿš€ Starting Chrome Headless Boot Sequence on Hugging Face Spaces..." # 0. Restore Chrome profile from Postgres snapshot (survives HF restart/rebuild). # DATABASE_URL is a Space secret โ€” never hardcode the credential here. if [ -n "$DATABASE_URL" ]; then echo "๐Ÿ“ฅ Restoring Chrome profile from Postgres..." /opt/profilesync/profilesync restore || echo "โš ๏ธ Restore skipped/failed (first run?)" fi # 1. Structure Detection & Fix if [ -d "/home/chrome/data/data/Default" ]; then echo "โš ๏ธ Nested structure detected. Fixing..." mv /home/chrome/data/data/* /home/chrome/data/ 2>/dev/null || true rmdir /home/chrome/data/data 2>/dev/null || true fi # 2. Deep Junk Cleanup (80% bloat removal) echo "๐Ÿงน Performing Deep Junk Cleanup..." rm -rf /home/chrome/data/Default/Cache \ /home/chrome/data/Default/Code\ Cache \ /home/chrome/data/Default/GPUCache \ /home/chrome/data/Default/DawnGraphiteCache \ /home/chrome/data/Default/DawnWebGPUCache \ /home/chrome/data/Default/ShaderCache \ /home/chrome/data/Default/GrShaderCache \ /home/chrome/data/Default/Service\ Worker/CacheStorage \ /home/chrome/data/Default/History \ /home/chrome/data/Default/Favicons \ /home/chrome/data/Default/Top\ Sites \ /home/chrome/data/Default/Visited\ Links \ /home/chrome/data/Default/Network\ Action\ Predictor \ /home/chrome/data/Default/Network\ Persistent\ State 2>/dev/null || true rm -rf /home/chrome/data/component_crx_cache \ /home/chrome/data/optimization_guide_model_store \ /home/chrome/data/WasmTtsEngine \ /home/chrome/data/OnDeviceHeadSuggestModel \ /home/chrome/data/hyphen-data \ /home/chrome/data/ZxcvbnData \ /home/chrome/data/SafetyTips \ /home/chrome/data/Safe\ Browsing* \ /home/chrome/data/BrowserMetrics* \ /home/chrome/data/Crashpad \ /home/chrome/data/GrShaderCache \ /home/chrome/data/ShaderCache \ /home/chrome/data/GraphiteDawnCache 2>/dev/null || true # 3. Singleton/Lock removal echo "๐Ÿ”“ Removing lock files..." find /home/chrome -name "Singleton*" -exec rm -f {} + rm -f /home/chrome/data/Default/LOCK /home/chrome/data/Default/LOG.old 2>/dev/null || true # 3b. Session-restore wipe. The persisted profile saves the last set of open tabs # (Sessions/, Current/Last Tabs, Current/Last Session). On boot Chrome restores # them โ€” which resurrects stale tabs (e.g. the removed Flow tab) regardless of the # command-line URL. Deleting these makes Chrome open ONLY the launch URL; the # gpt/gemini extensions then open their own service tabs. Cookies/logins live in # separate files (Cookies, Login Data, Local Storage) and are untouched. echo "๐Ÿงน Wiping saved session/tab restore state..." rm -rf /home/chrome/data/Default/Sessions 2>/dev/null || true rm -f /home/chrome/data/Default/Current\ Tabs \ /home/chrome/data/Default/Last\ Tabs \ /home/chrome/data/Default/Current\ Session \ /home/chrome/data/Default/Last\ Session 2>/dev/null || true # 4. Preferences Injection (Developer Mode & Crash Fix) python3 -c " import json, os pref_path = '/home/chrome/data/Default/Preferences' os.makedirs(os.path.dirname(pref_path), exist_ok=True) data = {} if os.path.exists(pref_path): try: with open(pref_path, 'r') as f: data = json.load(f) except: pass # Enable Developer Mode if 'extensions' not in data: data['extensions'] = {} if 'ui' not in data['extensions']: data['extensions']['ui'] = {} data['extensions']['ui']['developer_mode'] = True # Prevent Restore Popup if 'profile' not in data: data['profile'] = {} data['profile']['exit_type'] = 'Normal' with open(pref_path, 'w') as f: json.dump(data, f) " 2>/dev/null || true # 5. Start Go Monitor Server echo "๐Ÿš€ Starting 3-tab Monitor Server..." touch /home/chrome/chrome.log /opt/monitor3/monitor > /home/chrome/monitor.log 2>&1 & # 6. Start CDP Proxy echo "๐Ÿ”Œ Starting CDP Proxy..." socat TCP-LISTEN:9223,fork,bind=0.0.0.0 TCP:127.0.0.1:9222 & # 6a. Start the backend API servers on localhost. The monitor doubles as a # gateway and fronts them on :3001 (/gpt, /gemini). Each logs separately # and runs in the background so a crash never blocks the boot. echo "๐Ÿงฉ Starting backend API servers (gpt / gemini)..." ( cd /opt/chatgpt && ./agent >> /home/chrome/chatgpt.log 2>&1 & ) ( cd /opt/gemini-srv && WS_PORT=9226 PORT=8000 ./free-gemini-api >> /home/chrome/gemini.log 2>&1 & ) # 6b. Periodic profile backup (safety net if HF kills the container without a clean # SIGTERM). Best-effort every 5 minutes; the shutdown trap does the final one. if [ -n "$DATABASE_URL" ]; then echo "๐Ÿ—„๏ธ Profile auto-backup every 5 min enabled." ( while sleep 300; do /opt/profilesync/profilesync backup >/dev/null 2>&1 || true; done ) & fi # 6c. Provide a valid per-user runtime dir. Chrome's mojo/IPC layer and each MV3 # extension service worker need this; without it Chrome logs # "XDG_RUNTIME_DIR is invalid or not set" and the extension service workers # intermittently fail to spawn (DidStartWorkerFail) โ†’ the gpt/gemini # extensions never connect. Purely additive: just creates a dir and exports it. export XDG_RUNTIME_DIR=/tmp/runtime-chrome mkdir -p "$XDG_RUNTIME_DIR" && chmod 700 "$XDG_RUNTIME_DIR" # 7. Launch Chrome Headless # NOTE: do NOT add --renderer-process-limit=1 / --process-per-site / --no-zygote here. # Each extension runs an MV3 service worker (its own renderer) and the chat # pipeline injects via chrome.scripting.executeScript into the service tab. Capping renderers # starves those workers โ€” they fail to start (DidStartWorkerFail) so the extension never # connects, and when one does start, executeScript hangs โ†’ the /gpt /gemini APIs # hang forever. HF cpu-basic has 16 GB (we use ~1โ€“2 GB), so the memory "savings" weren't # needed and broke the extensions. Keep Chrome multi-process. echo "โœจ Launching Chrome Headless..." /opt/chrome-linux64/chrome \ --headless=new \ --no-sandbox \ --disable-setuid-sandbox \ --disable-infobars \ --disable-blink-features=AutomationControlled \ --excludeSwitches=enable-automation \ --use-fake-ui-for-media-stream \ --use-fake-device-for-media-stream \ --use-mock-keychain \ --password-store=basic \ --window-size=980,1070 \ --user-agent='Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36' \ --disable-gpu \ --disable-software-rasterizer \ --disable-gpu-compositing \ --disable-breakpad \ --disable-crash-reporter \ --mute-audio \ --in-process-gpu \ --disable-dev-shm-usage \ --font-render-hinting=none \ --disable-font-subpixel-positioning \ --force-color-profile=srgb \ --remote-debugging-port=9222 \ --remote-debugging-address=127.0.0.1 \ --remote-allow-origins='*' \ --user-data-dir=/home/chrome/data \ --disable-background-networking \ --disable-sync \ --no-first-run \ --disable-default-apps \ --disable-background-timer-throttling \ --disable-renderer-backgrounding \ --disable-backgrounding-occluded-windows \ --load-extension=/opt/gpt-extension,/opt/gemini-extension \ --disable-extensions-except=/opt/gpt-extension,/opt/gemini-extension \ --disable-translate \ --memory-pressure-off \ --disable-client-side-phishing-detection \ --disable-component-update \ --disable-domain-reliability \ --disable-hang-monitor \ --disable-ipc-flooding-protection \ --disable-popup-blocking \ --disable-prompt-on-repost \ --disable-cookie-encryption \ --force-dark-mode \ --udp-force-randomized-port \ --disable-partial-raster \ --disable-features=TranslateUI,PaintHolding,IsolateOrigins,site-per-process \ --enable-features=NetworkService,NetworkServiceInProcess \ --disable-site-isolation-trials \ --js-flags="--max-old-space-size=512" \ --metrics-recording-only \ 'https://chatgpt.com/' >> /home/chrome/chrome.log 2>&1