op-and-chloe ("openclaw-ey") is a two-instance OpenClaw stack for any VPS.
- π Op: admin instance with SSH access β fix Chloe, restarts, large architectural changes
- π― Chloe: day-to-day instance β create all agents here; has Bitwarden, email, M365, webtop
- π₯οΈ Webtop Chromium + CDP for shared browser (you + Chloe)
- π Passwordless: Bitwarden in Chloe; login/unlock interactive only, no secrets in files
- β€οΈ Healthcheck + watchdog
π Easiest setup ever: just run
sudo ./setup.shand youβre on your way! π
Why do you need this?
Why not just use OpenClaw as it is?
You can - and you should. OpenClaw is awesome.
Setting it up from start to finish can be very tedious, though. How do you securely share credentials? How do you give OpenClaw browser access - especially on a headless server? Should you use Browserless or run headless Chromium? If you can't see the browser, how do you log in? How do you troubleshoot or fix things from your phone? And how do you help someone set up OpenClaw if they're not comfortable with SSH or the command line?
I created op-and-chloe to make this process easier.
It's not a framework, not a lock-in, it's a simple wizard to pre-configure your stack to get you started. Once up and running, you can change it completely.
It looks like this:
What you get out of the box:
-
βοΈ A working stack on any VPS. I use Hetzner, it's ~$4.70/month and runs the full stack really well - but you can use any VPS provider. See HETZNER.md.
-
π± Two Telegram chats. One for Op (admin, fixing Chloe, restarts); one for Chloe (day-to-day β create all agents here).
-
π Private access via Tailscale. Guard, worker, and Webtop are on your Tailscale network with optional HTTPS - no public ports. Use them from your phone or laptop.
-
π Static site publishing from Chloe. Publish multiple sites from
workspace/sites/using a single validated registry picked up by the proxy. -
β€οΈ Health Check. Scripts to configure, verify and keep your stack healthy.
-
π Passwordless credentials Bitwarden runs in Chloeβs container. Login and unlock are interactive only; no secrets stored in files.
After purchasing your VPS from a provider, SSH into your server and follow the setup wizard step-by-step. No advanced technical skills required - just run each guided step in order. The wizard makes it easy: after each action, youβll return to the menu so you can check your progress before moving on.
git clone https://github.com/mere/op-and-chloe.git
cd op-and-chloe
sudo ./setup.shThat's it!
It takes about 20 minutes to follow the steps and your `AI personal assistant` is ready! β¨To update your op-and-chloe stack: run git pull, then run the setup again. The wizard will show you if anything needs updating.
cd op-and-chloe # or wherever you cloned the repo
git pull
sudo ./setup.shThe stack consists of:
- Three Docker containers:
- π Op: admin instance with SSH access. For fixing Chloe, restarts, and large architectural changes.
- π― Chloe: day-to-day instance. Create all agents here. Has Bitwarden, email (Himalaya, M365), and webtop.
- π₯οΈ Webtop: shared browser for you and Chloe (co-working, automation).
Chloe (claw-y) is your first OpenClaw instance.
(You can name her/him/they/it anything; it will ask for a name once it's up and running. π)
This is your day-to-day instance. Create all agents here. You get Bitwarden, email (Himalaya, M365), and webtop β standard OpenClaw; add skills and agents as you like.
Chloe can also publish static sites from her workspace. Put the built outputs under sites/, register them in sites/sites.json, and the proxy will pick them up automatically.
When things break or you need restarts or big changes, talk to Op (admin with SSH access). Talk to Chloe for daily work.
Bun (optional): Chloeβs worker image ships with Bun. The stack does not require it for a minimal setup. bun and bunx are symlinked into /usr/local/bin so OpenClawβs agent shell (short allowlist PATH with /usr/local/bin, not Bunβs install dir) can run them.
QMD (OpenClaw memory): The worker image installs @tobilu/qmd with Bun and symlinks qmd β /usr/local/bin/qmd, so the gateway finds the same binary as agent shells. The worker entrypoint creates ~/.openclaw/agents/<agent>/qmd/xdg-cache/qmd (including main) on the mounted state volume so SQLite can open the index. After rebuilding the worker image, run openclaw memory index --force once if a previous run left a bad or missing index.
Op is the admin instance with SSH access. Opβs job is to:
- fix Chloe when she breaks,
- do restarts, Docker, repo, and host changes,
- handle large architectural changes (whatever youβd otherwise SSH in to do).
You talk to Chloe for day-to-day work (create all agents there). You talk to Op when you need admin.
On a Mac mini, you can easily give OpenClaw access to a browser. On a headless VPS that's harder: headless browsers or services like Browserless can be detected by some sites, and you can't easily see what the agent is doing. Ideally you want to co-work: you log in to LinkedIn, ask the agent to check messages and draft replies; the agent fills a form, you review and submit.
op-and-chloe gives you that: a small Docker image with a browser that both you and Chloe share. You log in once; Chloe uses the same session.
All secrets, tokens, and passwords are stored securely in Bitwarden β never on the server itself. During setup, you'll connect your Bitwarden vault to Chloe (worker) and unlock it interactively when needed; only the vault URL and session key are stored in worker state. Chloe uses the bw script to read from the vault for email setup, O365 config, and other tools.
Run the setup wizard:
sudo ./setup.shIs your AI about to take over the world? Stop the containers with:
sudo ./stop.shFalse alarm, it was just ordering cat food? Start it with:
Note: This also rebuilds the containers, so it's a good way to reset if things go wrong!
sudo ./start.shRun full health check on the stack:
sudo ./healthcheck.shRun a manual backup now:
sudo ./scripts/host/daily-backup.sh --forceTo run any openclaw command, use:
./openclaw-guard some command
# or
./openclaw-worker some commandUse setup step 16. daily backups to toggle scheduled backups, run a one-off backup immediately, and choose where the compressed archives are stored.
- Default: disabled
- Schedule: once per day at
03:17server time via/etc/cron.d/openclaw-daily-backup - Default backup directory: a sibling
backups/folder next to the stack checkout, for example/opt/backups/op-and-chloe - Default retention: keep the last
30backups - Retention
0means keep all backups with no pruning - Archive contents:
/etc/openclaw, Chloe'sstate, and Chloe'sworkspace - Manual run:
sudo ./scripts/host/daily-backup.sh --force
Use setup step 17. sites publishing to enable or disable public site publishing without editing env files by hand.
- Default: disabled
- When enabled, the setup wizard asks for
SITES_BASE_DOMAINsuch assites.example.com - The wizard creates
workspace/sites/sites.jsonautomatically if it is missing - Chloe publishes by adding entries to
workspace/sites/sites.json - Existing published site files are kept when you disable the feature
flowchart LR
U[User]
BW[(Bitwarden)]
subgraph VPS["VPS"]
subgraph Chloe["OpenClaw Docker"]
A[Agents π€ π€ π€]
end
subgraph OPD["OpenClaw Docker"]
Op["π Op (Admin)"]
end
subgraph Docker[Webtop Docker]
B["π₯οΈ Webtop"]
end
D[Docker / Host / Repo access]
end
U --> Chloe
Op --> D
A --> B
A --> BW
A --> E[π Email<br />π Calendar]
U --> Op
B --> S[π· Linkedin<br /> π Social Media]
See the System diagram and Components section above for topology and roles.
Chloe has Bitwarden in her container. She uses bw (e.g. bw list items, bw get item <id>) to read from the vault; session lives in worker state. One-time setup: scripts/worker/email-setup.py, scripts/worker/fetch-o365-config.py.
# In Chloe: Bitwarden runs locally
bw list items
bw get item <id>
# Email and M365 (after one-time setup)
himalaya envelope list -a icloud -s 20 -o json
m365 mail list --top 20- OpenClaw Web (Control UI, bind modes, Tailscale): https://docs.openclaw.ai/web
Published sites live in Chloe's workspace under sites/, with one registry file at sites/sites.json.
Use setup step 17. sites publishing to turn the proxy on or off and set the base domain.
Example:
sites/
sites.json
hello/
dist/
index.html
docs/
build/
index.html
sites/sites.json:
{
"sites": [
{
"name": "hello",
"subdomain": "hello",
"root": "hello/dist"
},
{
"name": "docs",
"subdomain": "docs",
"root": "docs/build"
}
]
}-
Clean URLs: The proxy resolves
{path}, then{path}.html, then{path}/index.html, then{path}/, then/index.html(covers static exports like Next.jsout/, folder indexes, and SPA fallback). Nospa/html_pathsflags. -
Set
SITES_BASE_DOMAINin/etc/openclaw/stack.env, for examplesites.example.com. -
Point wildcard DNS such as
*.sites.example.comat the VPS. -
Restart the stack after changing env or proxy settings:
sudo ./start.sh -
All
rootpaths are relative toworkspace/sites/. -
Each
rootmust point to a subdirectory insideworkspace/sites/, notworkspace/sites/itself. -
Published trees must not contain symlinks.
-
Chloe should only write the registry and site files under
workspace/sites/; she should not write raw proxy config.
Published URLs are world-readable by default: anyone who knows https://<subdomain>.<your-base-domain> can load the static files. That is appropriate for marketing pages, demos, and shareable tools.
To limit who can view a site:
- Leave sites publishing disabled in setup until you are ready to go public, or remove the entry from
sites/sites.jsonto take a site down without disabling the whole feature. - App-level auth β For SPAs and mini-apps, add your own sign-in (OAuth, magic links, etc.) in the front end or a small backend you deploy separately. The stack only serves static files from
sites/. - HTTP Basic Auth (per site) β Optional
basicauthon each entry insites/sites.json: setuserandbcrypt(a bcrypt modular-crypt string). Chloe can generate it in the worker withscripts/sites/hash_site_password.py(see repo;python3-bcryptis installed in the worker image). Example:printf '%s' 'your-password' | python3 /opt/op-and-chloe/scripts/sites/hash_site_password.py. On the VPS,caddy hash-passwordinsite-proxystill works if you prefer. Only the hash goes in the registry β never a plaintext password.
./openclaw-guard devices list (or worker) shows "device token mismatch":
- The container was started with an old gateway token. Recreate so they pick up the current env file:
sudo ./stop.sh && sudo ./start.sh(wait ~90s for gateways to be ready, then try again).
Dashboard URLs (Guard/Worker) return HTTP 502 after stop.sh / start.sh:
- The gateways can take 60β90 seconds to start listening.
start.shnow waits for them before applying Tailscale serve. If you still see 502, wait a minute and refresh, or re-run:sudo ./scripts/host/apply-tailscale-serve.sh
Chloe's browser tool shows wrong URL or cdpReady: false:
- The worker state must point at the browser container's CDP endpoint. On every
start.shwe refresh it automatically. To fix immediately:sudo ./scripts/host/update-webtop-cdp-url.sh(then use the worker dashboard or reconnect so Chloe picks up the new config).
Webtop URL (https://hostname:445/) not working:
- Ensure the browser container is running:
docker ps | grep browser - Ensure Tailscale serve is configured:
tailscale serve status- you should see port 445 β 127.0.0.1:6080 - Re-apply serve config:
sudo ./scripts/host/apply-tailscale-serve.sh - For HTTPS to work, enable HTTPS certificates in the admin console and run
sudo tailscale certon the VPS
Published site not appearing on its subdomain:
- Ensure
SITES_BASE_DOMAINis set in/etc/openclaw/stack.env - Ensure wildcard DNS for
*.your-base-domainpoints at the VPS - Ensure
workspace/sites/sites.jsonexists and contains the site entry - Ensure
rootpoints to an existing directory insideworkspace/sites/ - Ensure the published tree does not contain symlinks
- Check the
site-proxyandsites-reconcilercontainers withdocker compose ps
Published site returns 404 for extensionless URLs (/blog, /blog/) but blog.html works, right after changing reconcile_sites.py:
The sites-reconciler container must pick up the new script. Recreate it (or run start.sh after git pull):
docker compose --env-file /etc/openclaw/stack.env -f compose.yml --profile sites up -d --force-recreate sites-reconciler
The stack uses a shell loop so each reconcile runs a fresh Python process and sees the latest mounted repo code.
site-proxy fails to start: bind ... 0.0.0.0:443: address already in use (or nothing listens on public :80/:443):
- Tailscale often binds HTTPS on the tailnet interface (
tailscaledon100.x.x.x:443). That blocks Docker from publishing0.0.0.0:443for Caddy even though your public IP looks free. - Set
SITES_HTTP_HOSTandSITES_HTTPS_HOSTin/etc/openclaw/stack.envto your VPSβs public IPv4 (the address wildcard DNS should use), then recreate the proxy:
docker compose --env-file /etc/openclaw/stack.env -f compose.yml --profile sites up -d site-proxy
(from your stack directory, e.g. where you cloned this repo).
site-proxy logs Import file is empty, or HTTPS only works after restarting site-proxy:
If sites.generated.caddy on the host has content but Caddy inside the container sees an empty file, you likely have an old single-file bind mount. The reconciler replaces that file atomically; Docker can leave the container on a stale inode. Use the current compose.yml (directory mount at /etc/caddy/sites-registry/) and recreate:
docker compose --env-file /etc/openclaw/stack.env -f compose.yml --profile sites up -d --force-recreate site-proxy
- No master password on disk: In setup step 6 you log in and unlock once; only
BW_SERVERand the session key are saved (worker state:state/secrets/bitwarden.env,state/secrets/bw-session). Chloe uses that session viabw; re-run step 6 if the vault is locked. - Bitwarden runs in Chloeβs container; she has
bwin PATH.
This project is licensed under the MIT License.
Contributions are welcome. See CONTRIBUTING.md for how to get started.







