{"id":50577865,"url":"https://github.com/eratchev/openclaw-deploy","last_synced_at":"2026-06-21T19:00:24.992Z","repository":{"id":341740566,"uuid":"1171193335","full_name":"eratchev/openclaw-deploy","owner":"eratchev","description":"Hardened single-VPS deployment of OpenClaw AI assistant — Docker Compose, Caddy TLS, Redis, Python execution guardrail, automated S3 backups.","archived":false,"fork":false,"pushed_at":"2026-05-07T15:42:19.000Z","size":788,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-07T17:35:45.614Z","etag":null,"topics":["ai-assistant","docker-compose","llm","openclaw","self-hosted","telegram-bot","whatsapp"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/eratchev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"docs/security-checklist.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-03T01:00:57.000Z","updated_at":"2026-05-07T15:54:58.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/eratchev/openclaw-deploy","commit_stats":null,"previous_names":["eratchev/openclaw-deploy"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/eratchev/openclaw-deploy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eratchev%2Fopenclaw-deploy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eratchev%2Fopenclaw-deploy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eratchev%2Fopenclaw-deploy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eratchev%2Fopenclaw-deploy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/eratchev","download_url":"https://codeload.github.com/eratchev/openclaw-deploy/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/eratchev%2Fopenclaw-deploy/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34622271,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-21T02:00:05.568Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-assistant","docker-compose","llm","openclaw","self-hosted","telegram-bot","whatsapp"],"created_at":"2026-06-05T00:00:28.685Z","updated_at":"2026-06-21T19:00:24.968Z","avatar_url":"https://github.com/eratchev.png","language":"Python","funding_links":[],"categories":["Open Source Projects"],"sub_categories":["Hosting Platforms"],"readme":"# openclaw-deploy\n\n\u003e Hardened single-VPS deployment of [OpenClaw](https://github.com/openclaw/openclaw) with execution guardrails. Personal assistant + publishable open-source template.\n\u003e\n\u003e **Repo:** https://github.com/eratchev/openclaw-deploy\n\n## What This Is\n\nOne VPS. One Docker Compose. Hardened container, log-driven execution guardrail, Redis session store, TLS via Caddy. Telegram, WhatsApp, Google Calendar, and Brave Search are all optional integrations — the base stack runs without any of them.\n\nOut of the box you get:\n\n- TLS termination via Caddy with automatic Let's Encrypt certificates\n- OpenClaw Gateway running as a non-root user with all Linux capabilities dropped, read-only filesystem, and resource limits enforced\n- Redis session store isolated to an internal Docker network — unreachable from the internet\n- A Python execution guardrail that kills runaway LLM sessions before they burn tokens or abuse tools\n- VPS hardening via `scripts/provision.sh` (UFW, SSH key-only auth, Fail2ban, unattended security upgrades)\n- Automated daily backups of the `/data` volume to Hetzner Object Storage with configurable retention\n- A `Makefile` with commands for bring-up, teardown, logs, backup, and upgrade\n\n## What This Is NOT\n\n- Not multi-tenant\n- Not Kubernetes\n- Not a managed SaaS\n- Not hardened for enterprise (see threat model)\n\n## Prerequisites\n\n- A VPS (Hetzner CX22 or equivalent, ~$5-7/month)\n- Ubuntu 24.04 LTS\n- A domain name pointing to the VPS\n- OpenClaw already set up locally (you need to onboard channels before deploying)\n- Docker + Docker Compose (installed by provision.sh)\n- **SSH public key loaded on the VPS** — `scripts/provision.sh` disables password authentication. Run `ssh-copy-id user@\u003cyour-vps\u003e` before provisioning or you will be locked out.\n\n## Quickstart\n\n**Prerequisites:**\n- A VPS running Ubuntu 24.04 (Hetzner CX22 ~$5/mo works well)\n- A domain pointing at the VPS IP\n- SSH key access: `ssh-copy-id user@\u003cyour-vps\u003e`\n- A Telegram bot token from [@BotFather](https://t.me/BotFather)\n- An [Anthropic API key](https://console.anthropic.com)\n\n**Deploy:**\n\n```bash\ngit clone https://github.com/eratchev/openclaw-deploy.git\ncd openclaw-deploy\nmake deploy HOST=user@\u003cyour-vps\u003e\n```\n\nThe wizard provisions the VPS, configures everything interactively, and starts the stack. When it finishes, send a message to your bot.\n\n**Add WhatsApp (optional):**\n\n```bash\nmake pair-whatsapp\n```\n\nRenders a QR code in your terminal. Scan with WhatsApp on your phone.\n\n**Check health:**\n\n```bash\nmake doctor\n```\n\n**Deploy code changes** (after every `git push`):\n\n```bash\nmake push\n```\n\nPulls latest code on the VPS, rebuilds running service containers, updates CLI binaries, and deploys workspace files. Non-interactive — safe to run at any time.\n\n## Security Model\n\nThis deployment shifts OpenClaw's execution risk to containment. OpenClaw can execute arbitrary code via skills and tools — the hardening around it prevents that from compromising the host.\n\nSee [docs/threat-model.md](docs/threat-model.md) for the full threat model including known gaps. Phase 1 ships with outbound egress unrestricted — read it before deploying.\n\n## Execution Guardrails\n\nA Python watchdog runs inside the container and kills OpenClaw if sessions exceed configurable limits (tool calls, LLM calls, session time, idle timeout). Because OpenClaw has no per-session abort API, a violation kills all sessions — the container restarts automatically.\n\nSee [docs/execution-guardrails.md](docs/execution-guardrails.md) for limits and tuning.\n\n## Backups\n\nThe `/data` volume (OpenClaw config, credentials, session history) is backed up daily to Hetzner Object Storage. Backups older than `BACKUP_RETAIN_DAYS` (default: 7) are pruned automatically.\n\n**Setup (one-time):**\n\n1. Create a bucket in [Hetzner Object Storage](https://console.hetzner.com) and generate S3 credentials\n2. Add the `BACKUP_S3_*` vars to `.env` (see `.env.example`)\n3. Install the cron job: `sudo bash scripts/install-backup-cron.sh`\n\n**Manual backup:** `make backup-remote`\n\nBackups run daily at 03:00 UTC. Logs go to `/var/log/openclaw-backup.log`.\n\n## Google Calendar Integration *(optional)*\n\nOpenClaw can read and write your Google Calendar via an MCP proxy that runs on the internal Docker network. All writes go through a policy engine (conflict detection, business hours, rate limits) before touching the Google API.\n\n**One-time setup (local machine):**\n\n```bash\nmake setup-gcal CLIENT_SECRET=path/to/client_secret.json\n```\n\nThis generates a Fernet encryption key, runs the Google OAuth browser flow, encrypts the token, copies it to the VPS, updates `.env`, and restarts the calendar-proxy. Requires `client_secret.json` from Google Cloud Console (see below).\n\n**Multiple accounts:** Add a second account with `ACCOUNT=\u003clabel\u003e`:\n\n```bash\nmake setup-gcal CLIENT_SECRET=path/to/client_secret.json ACCOUNT=jobs\n```\n\nThe agent selects an account via `gcal --account jobs list`. Omitting `--account` uses the default (first entry in `GCAL_ACCOUNTS`).\n\n**Additional `.env` vars (add via `make deploy` or manually):**\n\n```bash\nGCAL_USER_TIMEZONE=America/Los_Angeles  # your local timezone\nGCAL_ALLOWED_CALENDARS=primary          # comma-separated calendar IDs\nGCAL_WORK_CALENDAR_ID=                  # optional — requires confirmation for any write\n```\n\nThen `make up-calendar` to start the base stack **plus** the calendar-proxy container. (Plain `make up` skips `calendar-proxy` — it only starts when explicitly requested via the `calendar` profile.)\n\n**One-time exec approvals setup** (run after first deploy):\n\n```bash\nmake setup-approvals\n```\n\nThis configures the `gcal` and `date` binaries on the exec allowlist so the agent can call them without interactive approval.\n\nSee [docs/calendar-proxy.md](docs/calendar-proxy.md) for tuning, health checks, and troubleshooting.\n\n## Gmail Integration *(optional)*\n\nOpenClaw can read, search, and reply to Gmail, and proactively notifies you via Telegram when important emails arrive (scored by Claude AI).\n\n**One-time setup (local machine):**\n\n```bash\nmake setup-gmail CLIENT_SECRET=path/to/client_secret.json\n```\n\nThis generates a Fernet encryption key, runs the Google OAuth browser flow (requesting `gmail.readonly`, `gmail.send`, `gmail.modify`), encrypts the token, copies it to the VPS, updates `.env`, registers the `gmail` CLI on the exec approvals allowlist, and starts the service.\n\nRequires `client_secret.json` from Google Cloud Console (same project as Calendar if using both — see below).\n\n**Multiple accounts:** Add a second account with `ACCOUNT=\u003clabel\u003e`:\n\n```bash\nmake setup-gmail CLIENT_SECRET=path/to/client_secret.json ACCOUNT=jobs\n```\n\nThe agent selects an account via `gmail --account jobs list`. Omitting `--account` uses the default (first entry in `GMAIL_ACCOUNTS`).\n\n**Start:**\n\n```bash\nmake up-mail\n```\n\n**Available agent commands:**\n\n| Command | Description |\n|---|---|\n| `gmail list` | Show unread inbox (up to 10) |\n| `gmail get --thread-id ID` | Fetch full thread |\n| `gmail search --query \"...\"` | Gmail query syntax |\n| `gmail reply --thread-id ID --message-id ID --body \"...\"` | Reply to thread |\n| `gmail send --to EMAIL --subject \"...\" --body \"...\" --confirmed` | Send new email |\n| `gmail mark-read --message-id ID` | Mark as read |\n| `gmail --account jobs list` | Use a non-default account |\n\n**Proactive notifications:**\n\nWhen new emails arrive, the agent scores them for importance using Claude and sends a Telegram summary for anything scoring ≥ 7 (configurable via `GMAIL_IMPORTANCE_THRESHOLD`). Requires `ALERT_TELEGRAM_CHAT_ID` in `.env`.\n\n**Re-auth (if token expires):**\n\n```bash\nmake setup-gmail CLIENT_SECRET=path/to/client_secret.json\n```\n\nSafe to re-run — generates a fresh key and token.\n\nSee [docs/superpowers/specs/2026-03-13-gmail-integration-design.md](docs/superpowers/specs/2026-03-13-gmail-integration-design.md) for architecture details.\n\n### Getting `client_secret.json`\n\nBoth Calendar and Gmail integrations use the same Google Cloud OAuth flow:\n\n1. Go to [console.cloud.google.com](https://console.cloud.google.com) and create a project (or reuse one).\n2. Enable the API(s) you need: **APIs \u0026 Services → Library**\n   - For Calendar: enable **Google Calendar API**\n   - For Gmail: enable **Gmail API**\n3. Create credentials: **APIs \u0026 Services → Credentials → Create Credentials → OAuth client ID**\n   - Application type: **Desktop app**\n   - Name: anything (e.g. `openclaw`)\n4. Download the JSON — that is your `client_secret.json`.\n5. Add your Google account as a test user: **OAuth consent screen → Test users → Add**.\n\nYou can reuse the same project and the same `client_secret.json` for both Calendar and Gmail.\n\n### Voice Transcription *(optional)*\n\nAutomatically transcribes Telegram voice notes via OpenAI Whisper so you can speak to OpenClaw hands-free.\n\n**Setup:**\n\n1. Add to `.env`:\n   ```bash\n   OPENAI_API_KEY=sk-...\n   TELEGRAM_TOKEN=\u003cyour bot token\u003e   # same token as channels.telegram.botToken in openclaw.json\n   ```\n\n2. Configure OpenClaw for webhook mode (required — voice-proxy intercepts incoming webhook POSTs; long-polling cannot be intercepted):\n   ```bash\n   # Set secret before URL or validation fails\n   docker compose exec openclaw openclaw config set channels.telegram.webhookSecret \u003crandom-32-char-hex\u003e\n   docker compose exec openclaw openclaw config set channels.telegram.webhookUrl https://\u003cyour-domain\u003e/telegram-webhook\n   docker compose exec openclaw openclaw config set channels.telegram.webhookHost 0.0.0.0\n   ```\n\n3. `make up-voice`\n\n4. Send a voice note to your bot — it should reply as if you typed the text.\n\n**Cost:** ~$0.006/min (OpenAI Whisper). Negligible for personal use.\n**Rate limit:** 10 voice messages/minute per chat (configurable via `VOICE_RATE_LIMIT_PER_MIN`).\n\n**Troubleshooting:** Check `docker compose logs voice-proxy`. Each voice request logs `status=ok|error|no_api_key|rate_limited|size_exceeded`.\n\n## Brave Search *(optional)*\n\nThe agent can search the web using the Brave Search API. Get a free API key at [brave.com/search/api](https://brave.com/search/api), then configure it in the running container:\n\n```bash\ndocker compose exec openclaw openclaw config set tools.web.search.apiKey \u003cYOUR_KEY\u003e\ndocker compose exec openclaw openclaw config set tools.web.search.provider brave\ndocker compose exec openclaw openclaw config set tools.web.search.maxResults 5\ndocker compose restart openclaw\n```\n\n## OpenClaw Skills *(optional)*\n\nOpenClaw ships with bundled skills that unlock additional capabilities. Some require external CLI binaries to be installed in the container. Install them in one step:\n\n```bash\nmake setup-skills                                    # all supported skills\nmake setup-skills SKILLS=\"github session-logs\"       # specific skills only\n```\n\nSupported skills and their binaries:\n\n| Skill | Binary | Works out of the box? |\n|---|---|---|\n| `session-logs` | `jq`, `rg` | ✅ Yes |\n| `github` | `gh` | Needs `gh auth login` |\n| `spotify-player` | `spogo` | Needs cookie auth (see below) |\n| `summarize` | `summarize` | ❌ Not available on Linux |\n\n---\n\n### GitHub skill\n\nAfter installing, authenticate `gh` inside the container:\n\n```bash\nmake ssh   # or: ssh user@your-vps\nsudo docker compose exec -it openclaw gh auth login\n```\n\nFollow the prompts. Once authenticated, ask the bot things like \"do I have any open PRs?\" or \"what's the status of my latest CI run?\"\n\n---\n\n### Spotify skill\n\nRequires a Spotify Premium account. Uses [spogo](https://github.com/steipete/spogo) — a statically compiled CLI that controls Spotify via the Web API using a browser session cookie. Works with any sign-in method (Google, Apple, Yahoo, email).\n\n**Install:**\n\n```bash\nmake setup-skills SKILLS=spotify-player\n```\n\n**Authenticate:**\n\n1. Open [open.spotify.com](https://open.spotify.com) in Chrome/Firefox while logged in to Spotify\n2. Open DevTools → Application → Cookies → `open.spotify.com` → copy the values of **`sp_dc`** and **`sp_t`**\n3. Run this on your local machine (substituting the cookie values):\n\n```bash\nSP_DC=\"your-sp_dc-value\"\nSP_T=\"your-sp_t-value\"\n\npython3 -c \"\nimport json\ncookies = [\n    {'name':'sp_dc','value':'$SP_DC','domain':'.spotify.com','path':'/','expires':'2027-12-31T23:59:59Z','secure':True,'http_only':True},\n    {'name':'sp_t','value':'$SP_T','domain':'.spotify.com','path':'/','expires':'2027-12-31T23:59:59Z','secure':True,'http_only':False}\n]\nprint(json.dumps(cookies, indent=2))\n\" \u003e /tmp/spogo_cookies.json\n\nscp /tmp/spogo_cookies.json user@your-vps:/tmp/spogo_cookies.json\nssh user@your-vps \"\n  sudo docker compose -f ~/openclaw-deploy/docker-compose.yml cp \\\n    /tmp/spogo_cookies.json openclaw:/home/node/.openclaw/spogo/cookies/default.json\n  rm -f /tmp/spogo_cookies.json\n\"\nrm -f /tmp/spogo_cookies.json\n```\n\nVerify: `make doctor` or:\n\n```bash\nssh user@your-vps \\\n  \"sudo docker compose -f ~/openclaw-deploy/docker-compose.yml exec -T openclaw \\\n  /home/node/.openclaw/bin/spogo auth status\"\n```\n\nCredentials are stored in the persistent volume. Re-run the auth step if the cookies expire (typically annually).\n\nAfter that, ask the bot: \"play some jazz\", \"skip this song\", \"what's playing?\"\n\n---\n\n## Agent Workspace *(optional)*\n\nThe `workspace/` directory contains the agent's instruction files. These are copied into the container at runtime and control agent behaviour:\n\n- `AGENTS.md` — injected into every system prompt (always active, all sessions)\n- `SOUL.md` — agent identity and personality\n- `POLICY.md` — safety rules, authority model, guardrails\n- `OPERATIONS.md` — execution model and tool usage\n- `USER.md` — who the agent is helping (preferences, context)\n- `COMMANDS.md` — global commands available in all sessions including groups\n- `MEMORY_GUIDE.md` — memory instructions and tool quick-references (operator-owned, redeployed on every `make deploy`)\n- `MEMORY.md` — agent-owned long-term memory, loaded in direct/DM sessions only; never overwritten after first deploy\n\nEdit the files locally, then deploy:\n\n```bash\nmake deploy-workspace\n```\n\n\u003e **Telegram groups:** The bot only responds when @mentioned (e.g. `@YourBotName ai update`). This is controlled by `channels.telegram.groupPolicy: open` — change it to `disabled` to block group messages entirely, or configure per-group `requireMention: false` to allow unprefixed commands.\n\n## Upgrading\n\n`make backup-remote \u0026\u0026 make update`\n\nSee [docs/upgrade-path.md](docs/upgrade-path.md).\n\n## Pre-launch Checklist\n\nSee [docs/security-checklist.md](docs/security-checklist.md). Run through it before going live.\n\n## Troubleshooting\n\n### Guardrail exits immediately / `pairing required`\n\n**Symptom:** `[entrypoint] guardrail exited (code 0), restarting in 5s...` loops indefinitely. Running `docker compose exec openclaw openclaw logs --json` returns `gateway closed (1008): pairing required`.\n\n**Cause:** OpenClaw config was created on macOS. The gateway pins the CLI device to `darwin` and rejects connections from the Linux container.\n\n**Fix:**\n```bash\ndocker compose exec openclaw node -e \"\nconst fs = require('fs');\nconst p = '/home/node/.openclaw/devices/paired.json';\nconst d = JSON.parse(fs.readFileSync(p, 'utf8'));\nfor (const id of Object.keys(d)) {\n  if (d[id].clientId === 'cli') d[id].platform = 'linux';\n}\nfs.writeFileSync(p, JSON.stringify(d, null, 2));\nfs.writeFileSync('/home/node/.openclaw/devices/pending.json', '{}');\nconsole.log('done');\n\"\ndocker compose restart openclaw\n```\n\n### Caddy fails with `unrecognized global option: reverse_proxy`\n\n**Cause:** The `DOMAIN` variable is not visible to Caddy, so `{$DOMAIN}` expands to an empty string and Caddy interprets the site block as the global options block.\n\n**Fix:** Ensure `env_file: - .env` is present under the `caddy` service in `docker-compose.yml` (already included in this repo).\n\n### OpenClaw reports `Missing config`\n\n**Cause:** The `/data` volume is empty. On a fresh deploy this is handled automatically — the entrypoint bootstraps `openclaw.json` from `.env` on first start.\n\n**Fix:** If bootstrap did not run (e.g. the container started before `.env` was written), ensure `.env` has `TELEGRAM_TOKEN` and `DOMAIN` set, then restart:\n```bash\nmake doctor  # confirms .env vars\nsudo docker compose restart openclaw\n```\n\n### Bootstrap fails with `config set` error on first start\n\n**Symptom:** `[entrypoint] ERROR: TELEGRAM_TOKEN is not set`\n\n**Cause:** `.env` is missing a required variable. The bootstrap runs before the gateway and fails fast.\n\n**Fix:** Verify `.env` has `TELEGRAM_TOKEN`, `DOMAIN`, and `ANTHROPIC_API_KEY` set, then restart:\n```bash\nmake doctor  # shows which vars are missing\nsudo docker compose restart openclaw\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feratchev%2Fopenclaw-deploy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feratchev%2Fopenclaw-deploy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feratchev%2Fopenclaw-deploy/lists"}