gateway/entrypoint.sh is the container’s entrypoint. It runs on every container start and is fully idempotent — steps that already completed on a prior boot are skipped.
On shutdown (SIGTERM/SIGINT), the entrypoint runs gateway_backup.py backup to save gateway state to Supabase Storage before forwarding the signal to child processes. This ensures auth tokens, agent configs, and secrets survive container recreation.
Boot sequence
Step 0 — Resolve Supabase credentials
Three credential paths are tried in order: Path A: environment variables —SUPABASE_URL + SUPABASE_SERVICE_ROLE_KEY already set in env. Used immediately, no waiting.
Path B: gateway token exchange — For remote gateways installed via the gateway installer (install.yourhq.ai/gateway). If GATEWAY_TOKEN is set and $OPENCLAW_HOME/.token-consumed does not exist, the entrypoint calls consume_gateway_token() to exchange the one-time token for a gateway slug and UUID.
- Requires:
GATEWAY_TOKEN,SUPABASE_URL,SUPABASE_ANON_KEY - On success: writes the assigned slug to
.gateway-slugand marks the token consumed with.token-consumed - One-shot — the marker file prevents re-exchange on subsequent boots
- If it fails, the container exits (restart with a fresh token)
ui-config volume. The entrypoint polls /config/workspaces.json + /config/secrets.json every 5 seconds until creds appear. This lets the gateway start before the user has completed browser onboarding — it will wait indefinitely, logging a status line every 30 seconds.
On every subsequent boot, the pinned slug from .gateway-slug (if present) is restored into GATEWAY_ID so the gateway always registers as the same row in Supabase.
Step 0b — Restore from backup (conditional)
If the$OPENCLAW_HOME/agents directory does not exist (indicating a fresh boot or recreated container) and Supabase credentials are available:
- Locates
gateway_backup.py(from/opt/yourhq/daemons/or the entrypoint’s siblingdaemons/directory) - Runs
gateway_backup.py restoreto check Supabase Storage for previous backups - Downloads and extracts the newest available backup (falls back to older backups if the newest is corrupted)
- Restores auth tokens, agent configs, secrets, and Telegram pairing data
- If a VNC password is found in the restored backup (
$OPENCLAW_HOME/.vnc-password), exports it asVNC_PASSWORDso the VNC setup step reuses it instead of generating a random one
agents directory already exists (returning container with existing state), this step is skipped entirely.
Retention: 3 backups per gateway, 7-day max age. The backup excludes sessions and codex-home directories.
Step 1 — Seed git repo (first boot only)
If$OPENCLAW_HOME/repo.git does not exist:
- Creates a bare git repo with
defaultas the HEAD branch - Seeds it with agent template branches:
- From
TEMPLATES_SOURCE(if set, formatgit+<url>) - Otherwise from
/opt/templatesbundled in the image
- From
- Each template directory becomes a git branch:
defaultortemplate/<name>
Step 2 — Attach git remote (conditional)
IfGIT_REMOTE_URL is set (or synthesized from GITHUB_TOKEN + GITHUB_REPO_OWNER + GITHUB_REPO_NAME):
- Adds/updates the
originremote on the bare repo - Installs a
post-commithook that async-pushes every commit to origin - Fetches from origin to fast-forward any branches that moved while the gateway was down
Step 3 — OpenClaw onboard (first boot only)
If$OPENCLAW_HOME/openclaw.json does not exist, runs:
openclaw.json with baseline config. If it exits non-zero, the entrypoint logs a warning and continues — the next boot will retry.
Step 4 — Patch openclaw.json
Every boot (idempotent via //= defaults in jq):
- Sets
tools.profile = "full" - Sets Chrome executable and profile paths
- Enables Telegram channel with
pairingDM policy - Enables the
hq-bootstrapplugin and adds its path toplugins.load.paths
Step 5 — Install hq-bootstrap plugin
Copies*.json and *.ts files from /opt/openclaw-plugins/hq-bootstrap into $OPENCLAW_HOME/plugins/hq-bootstrap/. Runs every boot — safe to repeat.
Step 6 — VNC password
If$HOME/.vnc/passwd does not exist, generates a VNC password:
- Uses
VNC_PASSWORDenv if set (this is automatically populated from a backup restore in Step 0b, so restored gateways keep their original VNC password) - Otherwise generates a random 12-character password
- Saves the vncpasswd-encoded hash to
$HOME/.vnc/passwd - Saves plaintext to
$OPENCLAW_HOME/.vnc-password(readable inside thegateway-statevolume)
Step 7 — Start Xtigervnc + XFCE
Starts the virtual display and desktop:- Removes stale X lock files (
/tmp/.X1-lock,/tmp/.X11-unix/X1) from a crashed prior run - Starts Xtigervnc on
:1— combined X server + VNC server (RFB on port 5901, localhost-only) - Starts
autocutseltwice (CLIPBOARD → PRIMARY sync for noVNC clipboard passthrough) - Starts
dbus-daemonsession bus with a deterministic socket path - Starts
startxfce4
dbus-launch) because dbus-launch double-forks in a way that leaves XFCE components unable to find the bus address in container environments.
Step 8 — Start websockify (noVNC)
IfNOVNC_BIND != "off", starts:
0.0.0.0:6901. Port 6901 is not mapped to the host — the UI proxies noVNC through its own /api/novnc route over Docker’s internal network (gateway:6901).
Set NOVNC_BIND=off to skip noVNC entirely (e.g. on a headless host where you never need remote desktop).
Step 9 — Register in Supabase
If Supabase creds are available, upserts a row in thegateways table with:
slug=GATEWAY_IDlabel=GATEWAY_LABELstatus=readylast_seen_at= nowmeta.reachable_urls= URLs built fromHOST_REACHABLE_URL+ port configmeta.networking_mode=NETWORKING_MODEtenant_id=TENANT_ID
(tenant_id, slug), so re-runs update the existing row.
Also resolves GATEWAY_DB_ID (the row’s UUID) and exports it so the hq-bootstrap plugin can tag agent_usage rows with the gateway that ran them.
Step 10 — Start files-API (conditional)
IfGATEWAY_AUTH_TOKEN is available (from env or /config/gateway-auth-token) AND FILES_API_BIND != "off":
Step 11 — Clear Chrome Singleton locks
Sweeps$HOME/.openclaw/browser/ for Singleton* files left behind by a crashed browser session. Without this, Chrome aborts launch on next boot reporting it’s already running.
Step 12 — Source gateway secrets
secrets_sync daemon (running in the runner container) has written gateway-level secrets to disk, the entrypoint sources them into the environment before starting OpenClaw. This makes gateway-scoped credentials (e.g. shared API keys) available to the main process and all agents.
The secrets .env files are generated by secrets_sync.py, which runs inside the runner container on the shared gateway-state volume. The file at ~/.openclaw/secrets/gateway.env contains gateway-scoped keys; per-agent files at ~/.openclaw/secrets/agents/<slug>.env contain merged (gateway + agent-specific) keys. All files are chmod 0600.
Step 13 — Exec OpenClaw gateway
State directory layout
$OPENCLAW_HOME defaults to ~/.openclaw and is persisted in the gateway-state Docker volume.
$HOME/.vnc/passwd is the vncpasswd-encoded password hash read by Xtigervnc.
