OpenClaw Deploy

OpenClaw Deploy Role

Deploys the OpenClaw gateway on Ubuntu using Docker Compose and systemd. The gateway supports multiple messaging channels (Telegram, WhatsApp) with AI-powered replies via configured providers (Anthropic/OpenAI/OpenRouter/Ollama).

Key Difference

Unlike other service roles, OpenClaw has no prebuilt Docker image. This role clones the source repo and runs docker build on-host using the upstream Dockerfile. The build is skipped when the image+tag already exists and the source hasn’t changed.

Lifecycle Exception (Backup/Restore)

OpenClaw intentionally does not provide openclaw_backup or openclaw_restore roles.

  • Backup/restore is centralized through Echoport.

  • Do not use just backup openclaw or just restore openclaw.

  • Use the operator commands and restore-drill workflow in ops-control/docs/OPENCLAW_RUNBOOK.md (private ops-control repo).

Quick Start

- hosts: macmini
  become: true
  roles:
    - role: local.ops_library.openclaw_deploy
      vars:
        openclaw_version: "v2026.7.1"
        openclaw_data_dir: "/mnt/cryptdata/openclaw/data"
        openclaw_gateway_token: "{{ sops_secrets.gateway_token }}"
        openclaw_anthropic_api_key: "{{ sops_secrets.anthropic_api_key }}"
        openclaw_telegram_enabled: true
        openclaw_telegram_bot_token: "{{ sops_secrets.telegram_bot_token }}"
        openclaw_telegram_allow_from:
          - 304012876
        openclaw_traefik_enabled: true
        openclaw_traefik_host: "openclaw.macmini.tailde2ec.ts.net"

Architecture

  • OpenClaw runs as a Docker container managed by a systemd oneshot unit.

  • The source repo is cloned to a build directory and docker build creates the image using the upstream Dockerfile. The container uses the upstream CMD — no command override in compose.

  • The gateway handles all messaging channels internally (Telegram via grammY, etc.).

  • AI replies are handled by the gateway using configured provider APIs — no external scripts needed.

  • Optional model policy patching can enforce a default model and ordered fallback chain under agents.defaults.model.

  • Persistent state (sessions, config) is stored in a bind-mounted directory owned by container uid 1000.

  • The container binds to 127.0.0.1:18789 by default, with optional Traefik reverse proxy for external access. When Traefik is enabled, the bind host is validated to be loopback.

  • When Traefik is enabled, role-managed config sets gateway.bind: "lan" and gateway.controlUi.allowedOrigins to https://<openclaw_traefik_host> so the host-side reverse proxy can reach the gateway UI safely.

  • Role-managed gateway hardening sets explicit gateway.trustedProxies, disables gateway.allowRealIpFallback, and configures gateway.auth.rateLimit.

  • Role-managed gateway configuration sets gateway.mode: "local", which is required by current OpenClaw releases for a locally hosted gateway.

  • Gateway config (openclaw.json) is seeded once on first deploy and not overwritten on subsequent runs (use openclaw_force_config: true to overwrite). Existing configs are patched to ensure unused plugins (WhatsApp) are explicitly disabled.

  • Logs are written to a bind-mounted log directory with automatic logrotate.

  • Optional: an authenticated loopback HTTP endpoint (/.well-known/openclaw-health) can be enabled for Nyxmon json-metrics checks.

Role Variables

Required Variables

Variable

Description

openclaw_version

Git tag to checkout and build (e.g. v2026.7.1)

openclaw_gateway_token

Gateway authentication token

openclaw_anthropic_api_key

Anthropic API key for AI replies

Networking

Variable

Default

Description

openclaw_bind_host

127.0.0.1

Host IP to bind the container port

openclaw_host_port

18789

Host port for the gateway

openclaw_container_port

18789

Container port for the gateway

Gateway Hardening

Variable

Default

Description

openclaw_gateway_config_mode

0600

File mode used for openclaw.json

openclaw_gateway_trusted_proxies

[127.0.0.1, ::1]

Trusted proxy IP/CIDR list written to gateway.trustedProxies

openclaw_gateway_allow_real_ip_fallback

false

Controls gateway.allowRealIpFallback

openclaw_gateway_auth_rate_limit_enabled

true

Enable gateway.auth.rateLimit patching

openclaw_gateway_auth_rate_limit_max_attempts

10

Failed auth attempts per window

openclaw_gateway_auth_rate_limit_window_ms

60000

Rate-limit window in ms

openclaw_gateway_auth_rate_limit_lockout_ms

300000

Lockout duration in ms after threshold

openclaw_gateway_auth_rate_limit_exempt_loopback

true

Exempt loopback clients from auth rate limiting

Telegram Channel

Variable

Default

Description

openclaw_telegram_enabled

false

Enable Telegram channel

openclaw_telegram_bot_token

""

Telegram bot token (required when enabled)

openclaw_telegram_dm_policy

allowlist

DM policy: allowlist or open

openclaw_telegram_allow_from

[]

Numeric Telegram user IDs for allowlist

openclaw_telegram_streaming_mode

partial

Telegram preview streaming mode: off, partial, block, or progress

openclaw_telegram_streaming_preview_tool_progress

true

Show tool-progress lines in Telegram preview messages

openclaw_telegram_streaming_preview_command_text

raw

Tool command detail in previews: raw or status-only status

openclaw_session_dm_scope

main

DM session routing; use per-channel-peer for a bot shared by multiple Telegram users

AI Configuration

Variable

Default

Description

openclaw_openai_api_key

""

Optional OpenAI API key (OPENAI_API_KEY) for provider openai

openclaw_openrouter_api_key

""

Optional OpenRouter API key (OPENROUTER_API_KEY) for provider openrouter

openclaw_ollama_api_key

""

Optional Ollama API key (OLLAMA_API_KEY) for provider ollama; when openclaw_ollama_base_url is set and this is empty, role injects ollama-local

openclaw_ollama_base_url

""

Optional Ollama endpoint (OLLAMA_BASE_URL); must be reachable from inside the OpenClaw container

openclaw_agent_workspace_dir

{{ openclaw_data_dir }}/workspace

Persistent agent workspace containing SOUL.md, USER.md, and memory files

openclaw_reply_system_prompt

""

Optional content managed in the agent workspace SOUL.md; empty preserves the existing file

openclaw_user_profile

""

Optional content managed in the agent workspace USER.md; empty preserves the existing file

openclaw_workspace_skills

[]

Managed workspace skills; each entry declares a safe skill name and controller-local files copied beneath workspace/skills/<name>

Workspace skills created interactively through Skill Workshop already persist across container replacement because the complete OpenClaw data directory is a bind mount. Use openclaw_workspace_skills as the recovery/source-control layer for skills that must also survive host rebuilds or volume loss:

openclaw_workspace_skills:
  - name: daily-notes
    files:
      - src: "{{ playbook_dir }}/../files/openclaw/skills/daily-notes/SKILL.md"
        dest: SKILL.md
        mode: "0640"
      - src: "{{ playbook_dir }}/../files/openclaw/skills/daily-notes/scripts/run.sh"
        dest: scripts/run.sh
        mode: "0750"

The role preserves unmanaged workspace skills. It refreshes cached OpenClaw session skill snapshots whenever the managed skill set or managed file content changes, so newly deployed instructions become visible to new session bindings.

Model Routing Policy

Variable

Default

Description

openclaw_agent_model_primary

""

Optional default model (provider/model) patched to agents.defaults.model.primary

openclaw_agent_model_fallbacks

[]

Optional ordered fallback list (provider/model entries) patched to agents.defaults.model.fallbacks

openclaw_openai_auth_order

[]

Optional ordered canonical OpenAI auth profiles patched to auth.order.openai; use an OAuth-only list such as ["openai:default"] to require ChatGPT/Codex subscription auth for agent turns

openclaw_codex_plugin_enabled

false

Install, enable, and allow the official @openclaw/codex app-server runtime plugin, pinned to the configured OpenClaw version

openclaw_codex_plugin_id

codex

Plugin registry ID used for inspection and managed config

openclaw_codex_plugin_package

@openclaw/codex

Official npm package installed through OpenClaw’s persistent plugin registry

openclaw_codex_plugin_version

OpenClaw version without leading v

Exact plugin version; override only when upstream does not publish the plugin in release lockstep

For subscription-backed OpenAI agent turns, enable the official Codex plugin, configure a canonical openai/<model> primary, and list the OAuth profile before any optional API-key backup:

openclaw_codex_plugin_enabled: true
openclaw_agent_model_primary: "openai/gpt-5.6-sol"
openclaw_openai_auth_order:
  - "openai:default"

The OAuth login itself remains persistent runtime state and is intentionally not stored in this public role. Run openclaw models auth login --provider openai --device-code on the deployed host, then verify with openclaw models auth list --provider openai.

Plugin package reconciliation and installed-version verification require a real deployment and are skipped in Ansible check mode. Check mode still reports the managed gateway-config changes that enable and allow the plugin. Package installation uses host networking so npm resolution follows the deployment host instead of Docker’s transient default-bridge DNS. This intentionally lets package install-time code reach host-local services; enable this path only for the official, exact-version-pinned package that the role verifies after installation.

When openclaw_gateway_config supplies a complete raw config override, it must include the codex entry in both plugins.entries (enabled) and plugins.allow; individual managed config patches are intentionally bypassed for raw overrides, while package reconciliation still runs when openclaw_codex_plugin_enabled is true.

Build Configuration

Variable

Default

Description

openclaw_git_depth

1

Shallow checkout depth for the tag-pinned upstream source

openclaw_git_single_branch

true

Limit source checkout to the requested tag/branch instead of all upstream refs

openclaw_image_name

openclaw

Docker image name

openclaw_image_tag

{{ openclaw_version }}

Docker image tag

openclaw_force_rebuild

false

Force Docker image rebuild even if image exists

Traefik Reverse Proxy

Variable

Default

Description

openclaw_traefik_enabled

false

Enable Traefik reverse proxy configuration

openclaw_traefik_host

""

Hostname for Traefik router (required when enabled)

openclaw_traefik_config_path

/etc/traefik/dynamic/openclaw.yml

Path for Traefik dynamic config

openclaw_traefik_entrypoints

[web-secure]

Traefik entrypoints to listen on

openclaw_traefik_cert_resolver

""

TLS cert resolver (empty = default TLS)

Nyxmon Metrics Endpoint

Variable

Default

Description

openclaw_metrics_endpoint_enabled

false

Enable OpenClaw JSON metrics endpoint for Nyxmon

openclaw_metrics_endpoint_bind

127.0.0.1

Bind address for endpoint service

openclaw_metrics_endpoint_port

9104

Endpoint port

openclaw_metrics_endpoint_path

/.well-known/openclaw-health

Endpoint path

openclaw_metrics_endpoint_auth_user

CHANGEME

Basic-auth username (required when enabled)

openclaw_metrics_endpoint_auth_password

CHANGEME

Basic-auth password (required when enabled)

openclaw_metrics_endpoint_timer_interval

120

Collector timer interval in seconds

openclaw_metrics_endpoint_container_name

{{ openclaw_container_name }}

OpenClaw container queried by collector

openclaw_metrics_endpoint_remove_data_on_disable

false

Remove openclaw_metrics_endpoint_data_dir when endpoint is disabled

openclaw_metrics_endpoint_synthetic_canary_enabled

false

Enable synthetic real agent-turn canary collection in metrics payload

openclaw_metrics_endpoint_synthetic_canary_agent

main

Optional agent id for canary command; leave empty to route by session_id only

openclaw_metrics_endpoint_synthetic_canary_session_id

probe-openclaw-canary

Session id prefix used for stateless canary attempts; each due run appends epoch/attempt suffixes to avoid context growth and lock contention

openclaw_metrics_endpoint_synthetic_canary_message

Reply exactly: OPENCLAW_CANARY_OK

Deterministic prompt for canary request

openclaw_metrics_endpoint_synthetic_canary_expected_text

OPENCLAW_CANARY_OK

Expected canary marker text (success when present as a standalone trimmed line)

openclaw_metrics_endpoint_synthetic_canary_interval

1800

Minimum seconds between canary executions (collector cycles in between use cached result)

openclaw_metrics_endpoint_synthetic_canary_timeout

45

Per-attempt timeout (seconds) for canary command

openclaw_metrics_endpoint_synthetic_canary_max_attempts

2

Max attempts per due canary run

openclaw_metrics_endpoint_synthetic_canary_retry_delay_seconds

5

Delay between failed canary attempts

openclaw_metrics_endpoint_synthetic_canary_state_path

{{ openclaw_metrics_endpoint_data_dir }}/openclaw-canary-state.json

Local state file for last canary result

openclaw_metrics_endpoint_synthetic_canary_sessions_dir

{{ openclaw_data_dir }}/agents/main/sessions

Host OpenClaw sessions directory used to clean generated canary session files

openclaw_metrics_endpoint_synthetic_canary_session_retention_seconds

86400

Seconds to retain generated canary session/trajectory files; set 0 to disable cleanup

openclaw_metrics_endpoint_synthetic_canary_output_max_chars

256

Max response chars persisted from canary output

Mail Skill (/mail): IMAP Read + SMTP Send

When openclaw_imap_enabled: true, the role deploys the /mail command skill and handler. IMAP command surface:

  • /mail unread [--account <name>]

  • /mail list [--account <name>] [--limit N]

  • /mail read <uid> [--account <name>]

  • /mail search <query> [--account <name>] [--limit N]

Optional phase-2 SMTP send command (requires openclaw_smtp_enabled: true):

  • /mail send --to <email[,email...]> --subject <text> --body <text> [--account <name>] [--cc <email[,email...]>] [--bcc <email[,email...]>]

Safety guarantees:

  • IMAP path remains read-only (SELECT ... readonly + SEARCH/FETCH only).

  • SMTP path supports plain-text send only (no attachments, no HTML composition).

  • Body formatting supports real newlines and escaped \n/\r\n sequences in /mail send --body.

  • Strict recipient/address validation with newline/header-injection rejection.

  • Bounded recipients/subject/body lengths with explicit limits.

  • SMTP body normalization handles common shell/LLM artifacts in --body, including:

    • shell-quoted wrappers ($'...' / $"..."),

    • escaped newline/tab tokens (\n, \r, \t),

    • mixed payloads with both escaped tokens and real newlines.

  • Sender identity is account-configured only: optional from_name and reply_to are static per account (not user-provided at command time).

  • SMTP success confirms handoff acceptance to the configured relay/MTA; final inbox placement depends on downstream recipient filtering.

  • Per-account credentials are split by purpose:

    • IMAP readers: imap_accounts.json

    • SMTP senders: smtp_accounts.json

IMAP Variables

Variable

Default

Description

openclaw_imap_enabled

false

Enable /mail skill deployment with IMAP read commands

openclaw_imap_host

""

IMAP host reachable from OpenClaw container

openclaw_imap_port

993

IMAP port

openclaw_imap_tls_mode

imaps

TLS mode: imaps or starttls

openclaw_imap_default_mailbox

INBOX

Mailbox opened in read-only mode

openclaw_imap_account_map

{}

Account map (logical_account -> {username, password, mailbox?})

openclaw_imap_default_account

""

Default logical account name

openclaw_imap_skill_name

mail-imap

Handler skill directory name

openclaw_imap_command_skill_name

mail

Slash command skill directory name

openclaw_imap_skills_dir

{{ openclaw_data_dir }}/skills

Host skill root for IMAP skill files

openclaw_imap_credentials_path

{{ openclaw_data_dir }}/credentials/imap_accounts.json

Rendered credential map (mode 0600)

openclaw_imap_container_credentials_path

/home/node/.openclaw/credentials/imap_accounts.json

Container path used by IMAP handler

openclaw_imap_connect_timeout_seconds

10

IMAP connection timeout

openclaw_imap_command_timeout_seconds

15

IMAP command timeout

openclaw_imap_default_limit

10

Default message listing limit

openclaw_imap_max_limit

25

Max allowed --limit

openclaw_imap_header_max_chars

240

Max chars for header fields in output

openclaw_imap_subject_max_chars

240

Max chars for subject in output

openclaw_imap_read_fetch_bytes

4096

Max bytes fetched for read snippet

openclaw_imap_read_snippet_chars

800

Max chars returned in /mail read snippet

openclaw_imap_search_query_max_chars

200

Max /mail search query length

SMTP Variables (Phase-2 Send)

Variable

Default

Description

openclaw_smtp_enabled

false

Enable /mail send SMTP delivery path

openclaw_smtp_host

""

SMTP host reachable from OpenClaw container

openclaw_smtp_port

587

SMTP submission port

openclaw_smtp_tls_mode

starttls

TLS mode: starttls or smtps

openclaw_smtp_account_map

{}

Account map (logical_account -> {username, password, from_address, from_name?, reply_to?})

openclaw_smtp_default_account

""

Default sender account for /mail send without --account

openclaw_smtp_credentials_path

{{ openclaw_data_dir }}/credentials/smtp_accounts.json

Rendered SMTP credential map (mode 0600)

openclaw_smtp_container_credentials_path

/home/node/.openclaw/credentials/smtp_accounts.json

Container path used by SMTP handler branch

openclaw_smtp_connect_timeout_seconds

10

SMTP connection timeout

openclaw_smtp_command_timeout_seconds

15

SMTP command timeout

openclaw_smtp_max_recipients

10

Max total recipients across To/Cc/Bcc

openclaw_smtp_address_max_chars

254

Max length for sender/recipient address fields

openclaw_smtp_from_name_max_chars

120

Max configured sender display-name length (from_name)

openclaw_smtp_subject_max_chars

240

Max /mail send --subject length

openclaw_smtp_body_max_chars

4000

Max /mail send --body length

Calendar Skill (/calendar): CalDAV Read + Guarded Create/Edit/Delete

When openclaw_calendar_enabled: true, the role deploys a /calendar command skill and handler using CalDAV calendars with explicit read/write allowlists per calendar id.

Command surface:

  • /calendar today

  • /calendar tomorrow

  • /calendar on <YYYY-MM-DD>

  • /calendar week [--start <YYYY-MM-DD>]

  • /calendar free --at <datetime> [--duration <minutes>]

  • /calendar create --calendar <id> --title <text> --start <datetime> [--duration <minutes>] [--repeat daily|weekly|monthly] [--count N | --until YYYY-MM-DD]

  • /calendar edit <event_id> [--title <text>] [--start <datetime>] [--duration <minutes>]

  • /calendar delete <event_id> --confirm

Safety guarantees:

  • Read and write access are configured per calendar entry in openclaw_calendar_map.

  • Write attempts to non-writable calendars are denied deterministically.

  • Event IDs are generated from calendar-local CalDAV resource paths and are required for edit/delete.

  • Delete always requires explicit --confirm.

  • Edit handles stale-write conflicts explicitly: CalDAV HTTP 412 returns a retry guidance message.

  • Recurrence is bounded to daily|weekly|monthly with optional count or until.

  • Recurring-event reads/free-time checks use CalDAV expansion for occurrence-correct results.

  • Event creation uses UTC timestamps (DTSTART/DTEND ...Z) to avoid timezone-component interoperability issues.

  • iCalendar lines are folded to RFC-friendly lengths for better CalDAV compatibility.

  • Read commands use bounded parallelism with explicit aggregate timeout handling.

  • Read commands degrade gracefully: if one calendar fails/times out, results from healthy calendars are still returned with warnings.

  • Aggregate timeout applies to fan-out coordination; effective wall time can exceed it by up to one per-calendar request timeout.

  • CalDAV 401/403 responses are tracked in a bounded health-state cache for operational alerting.

  • Bounded output and strict datetime/date validation.

  • Sanitized operational errors (no credential leakage).

Calendar Variables

Variable

Default

Description

openclaw_calendar_enabled

false

Enable /calendar skill deployment

openclaw_calendar_timezone

Europe/Berlin

Timezone used for parsing/rendering and event creation

openclaw_calendar_skill_name

calendar-caldav

Handler skill directory name

openclaw_calendar_command_skill_name

calendar

Slash command skill directory name

openclaw_calendar_skills_dir

{{ openclaw_data_dir }}/skills

Host skill root for calendar skill files

openclaw_calendar_container_skills_dir

/home/node/.openclaw/skills

Container path used by calendar command skill

openclaw_calendar_credentials_path

{{ openclaw_data_dir }}/credentials/calendar_accounts.json

Rendered runtime calendar config (0600)

openclaw_calendar_container_credentials_path

/home/node/.openclaw/credentials/calendar_accounts.json

Container runtime config path used by handler

openclaw_calendar_account_map

{}

Optional credential map (account_id -> {username, password}) reused by multiple calendars

openclaw_calendar_default_account

""

Optional default account id used when a calendar entry omits account

openclaw_calendar_map

{}

Calendar map (id -> {display_name?, url, account?, username?, password?, read?, write?})

openclaw_calendar_default_duration_minutes

60

Default duration for /calendar free and /calendar create

openclaw_calendar_request_timeout_seconds

10

CalDAV request timeout

openclaw_calendar_read_parallelism

4

Max concurrent per-calendar read requests for today/on/week/free

openclaw_calendar_read_aggregate_timeout_seconds

20

Aggregate timeout for multi-calendar read fan-out

openclaw_calendar_default_limit

10

Default list limit for today/on output

openclaw_calendar_max_limit

25

Maximum list limit for today/on output

openclaw_calendar_title_max_chars

180

Max rendered/stored title chars

openclaw_calendar_location_max_chars

140

Max rendered location chars

openclaw_calendar_health_state_path

{{ openclaw_data_dir }}/credentials/calendar_health_state.json

Host path for sanitized calendar auth-failure health cache (0600)

openclaw_calendar_container_health_state_path

/home/node/.openclaw/credentials/calendar_health_state.json

Container path for calendar auth-failure health cache

openclaw_calendar_auth_failure_window_seconds

3600

Rolling window used to count recent calendar auth failures

openclaw_calendar_auth_failure_threshold

3

Threshold for repeated calendar auth failures in the rolling window

Credential resolution order for each openclaw_calendar_map entry:

  1. account field (or openclaw_calendar_default_account) referencing openclaw_calendar_account_map.

  2. Inline username + password on the calendar entry (backward compatibility path).

Paperless Skill (/paperless): Read-Only Search + Telegram Delivery

When openclaw_paperless_enabled: true, the role deploys a /paperless command skill and handler using the Paperless-ngx REST API with read-only endpoint usage.

Command surface:

  • /paperless search <query> [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--limit N]

  • /paperless get <document_id> [--original|--archived] (defaults to original file)

  • /paperless latest [--query <text>] [--days N] [--limit N]

Safety guarantees:

  • Handler uses stdlib HTTP client (urllib.request) with explicit redirect deny.

  • Endpoint surface is read-only (GET /api/documents/, GET /api/documents/<id>/, GET /api/documents/<id>/download/).

  • Command skill execution assumes argv-style invocation by OpenClaw (no shell interpolation of raw user input).

  • Metadata-only search output format:

    • #{id} | {title} | {correspondent} | {created_date}

  • Bounded limits (default=5, max=10) and bounded field lengths.

  • Separate search timeout and download timeout.

  • Download guardrails enforce Telegram upload cap behavior:

    • if Content-Length is known and exceeds cap, skip body download and return deterministic URL fallback;

    • if Content-Length is absent, stream with hard cap and abort deterministically when exceeded.

  • Delivery target resolution is bounded and deterministic:

    • runtime invocation context/env (preferred),

    • recent OpenClaw session-state target when uniquely resolvable,

    • otherwise URL fallback with explicit reason.

  • HTTP 401/403 auth failures are tracked in rolling-window health state for metrics/Nyxmon alerting.

  • Sanitized errors only (no token/header leakage).

Paperless Variables

Variable

Default

Description

openclaw_paperless_enabled

false

Enable /paperless skill deployment

openclaw_paperless_base_url

""

Paperless base URL reachable from OpenClaw container

openclaw_paperless_token

""

Paperless API token (required when enabled)

openclaw_paperless_skill_name

paperless-read

Handler skill directory name

openclaw_paperless_command_skill_name

paperless

Slash command skill directory name

openclaw_paperless_skills_dir

{{ openclaw_data_dir }}/skills

Host skill root for Paperless skill files

openclaw_paperless_container_skills_dir

/home/node/.openclaw/skills

Container path used by Paperless command skill

openclaw_paperless_credentials_path

{{ openclaw_data_dir }}/credentials/paperless.json

Rendered Paperless runtime config (0600)

openclaw_paperless_container_credentials_path

/home/node/.openclaw/credentials/paperless.json

Container runtime config path used by handler

openclaw_paperless_health_state_path

{{ openclaw_data_dir }}/credentials/paperless_health_state.json

Host path for Paperless auth-failure rolling state (0600)

openclaw_paperless_container_health_state_path

/home/node/.openclaw/credentials/paperless_health_state.json

Container path for Paperless auth-failure rolling state

openclaw_paperless_search_timeout_seconds

10

Search/list request timeout

openclaw_paperless_download_timeout_seconds

60

Download request timeout

openclaw_paperless_delivery_timeout_seconds

30

Telegram media-send timeout

openclaw_paperless_default_limit

5

Default search/list limit

openclaw_paperless_max_limit

10

Maximum search/list limit

openclaw_paperless_latest_default_days

7

Default lookback window for /paperless latest

openclaw_paperless_latest_max_days

365

Maximum allowed --days for /paperless latest

openclaw_paperless_title_max_chars

120

Max rendered title chars

openclaw_paperless_correspondent_max_chars

80

Max rendered correspondent chars

openclaw_paperless_max_download_bytes

52428800

Max file size cap in bytes (50 MB hard limit)

openclaw_paperless_download_chunk_bytes

65536

Stream chunk size used for capped downloads

openclaw_paperless_downloads_dir

{{ openclaw_data_dir }}/workspace/.paperless-downloads

Host temp downloads directory for handler

openclaw_paperless_container_downloads_dir

/home/node/.openclaw/workspace/.paperless-downloads

Container temp downloads directory used by handler

openclaw_paperless_session_state_path

{{ openclaw_data_dir }}/agents/main/sessions/sessions.json

Host OpenClaw session-state JSON used for delivery-target fallback

openclaw_paperless_container_session_state_path

/home/node/.openclaw/agents/main/sessions/sessions.json

Container path to OpenClaw session-state JSON used by handler

openclaw_paperless_session_target_max_age_seconds

180

Max age for session-state target fallback candidates

openclaw_paperless_delivery_channel

telegram

Delivery channel (fixed to Telegram)

openclaw_paperless_auth_failure_window_seconds

3600

Rolling window used to count recent Paperless auth failures

openclaw_paperless_auth_failure_threshold

3

Threshold for repeated Paperless auth failures in rolling window

Home Assistant Skill (/homeassistant): Read + Guarded Write

When openclaw_homeassistant_enabled: true, the role deploys a /homeassistant command skill and handler using the Home Assistant REST API with a tightly restricted command surface.

Command surface:

  • /homeassistant state <entity_id>

  • /homeassistant list [--domain <domain>] [--limit N]

  • /homeassistant turn_on <entity_id>

  • /homeassistant turn_off <entity_id>

Safety guarantees:

  • Read endpoint usage is restricted to GET /api/states and GET /api/states/<entity_id>.

  • Write endpoint usage is restricted to:

    • POST /api/services/homeassistant/turn_on

    • POST /api/services/homeassistant/turn_off

  • Explicit read and write allowlists are enforced separately in-handler.

  • Write policy is entity-first: prefer openclaw_homeassistant_allow_write_entities and keep openclaw_homeassistant_allow_write_domains empty unless a reviewed exception is required.

  • Strict entity/domain input validation.

  • Bounded output (limit/field truncation/attribute cap) and request timeout.

  • Sanitized operational errors (no token/header leakage).

Operational note:

  • OpenClaw caches skill snapshots per session. The role now invalidates cached skillsSnapshot entries and rotates existing session bindings automatically when the managed slash-skill set changes, so pre-existing sessions pick up newly deployed commands in a fresh session context on the next message.

Home Assistant Variables

Variable

Default

Description

openclaw_homeassistant_enabled

false

Enable /homeassistant skill deployment

openclaw_homeassistant_base_url

""

Home Assistant base URL reachable from OpenClaw container

openclaw_homeassistant_token

""

Home Assistant long-lived API token (required when enabled)

openclaw_homeassistant_skill_name

homeassistant-read

Handler skill directory name

openclaw_homeassistant_command_skill_name

homeassistant

Slash command skill directory name

openclaw_homeassistant_skills_dir

{{ openclaw_data_dir }}/skills

Host skill root for Home Assistant skill files

openclaw_homeassistant_container_skills_dir

/home/node/.openclaw/skills

Container path used by Home Assistant command skill

openclaw_homeassistant_credentials_path

{{ openclaw_data_dir }}/credentials/homeassistant.json

Rendered Home Assistant runtime config (0600)

openclaw_homeassistant_container_credentials_path

/home/node/.openclaw/credentials/homeassistant.json

Container runtime config path used by handler

openclaw_homeassistant_allow_domains

[]

Allowed Home Assistant domains (for example sensor, climate)

openclaw_homeassistant_allow_entities

[]

Allowed full entity IDs (for example sun.sun)

openclaw_homeassistant_allow_write_domains

[]

Exception path: allowed write domains for `/homeassistant turn_on

openclaw_homeassistant_allow_write_entities

[]

Primary write allowlist: explicit entity IDs for `/homeassistant turn_on

openclaw_homeassistant_request_timeout_seconds

8

Home Assistant HTTP timeout per request

openclaw_homeassistant_default_limit

10

Default /homeassistant list row limit

openclaw_homeassistant_max_limit

25

Maximum /homeassistant list row limit

openclaw_homeassistant_state_max_chars

200

Max chars for rendered state values

openclaw_homeassistant_friendly_name_max_chars

120

Max chars for rendered friendly names

openclaw_homeassistant_attribute_max_items

8

Max rendered attribute key/value pairs in /homeassistant state

openclaw_homeassistant_attribute_value_max_chars

120

Max chars per rendered attribute value

OpsGate Skill (/opsgate): Explicit Investigator Ticket Submit

When openclaw_opsgate_enabled: true, the role deploys a /opsgate command skill, a deterministic local plugin tool, and the audited submit handler that creates a new OpsGate ticket through the existing submit-token API.

Command surface:

  • /opsgate create [--title <text>] [--task-ref <text>] <issue description>

Safety guarantees:

  • The handler submits exactly one fixed workflow shape:

    • one step

    • role investigator

    • agent codex

  • /opsgate ... is wired to a deterministic local plugin tool instead of relying on prompt-only inference.

  • The handler cannot approve, reject, cancel, archive, or mutate existing OpsGate tickets.

  • The skill does not submit arbitrary execution plans or reviewer/implementer workflows.

  • OpsGate approval is still required before any execution happens.

  • Approval links can use a different base URL from the internal submit endpoint, which matches the current split deploy model (submit via internal service URL, approve/view via operator HTTPS URL).

Operational note:

  • Both openclaw agent --message '/opsgate ...' --json and long-lived Telegram chats use the same deterministic local plugin path.

  • When the managed slash-skill set changes, the role automatically invalidates cached skill snapshots and rotates existing session bindings so pre-existing Telegram sessions pick up /opsgate and other managed slash skills on the next message.

OpsGate Variables

Variable

Default

Description

openclaw_opsgate_enabled

false

Enable /opsgate skill deployment

openclaw_opsgate_submit_base_url

""

OpsGate API base URL reachable from the OpenClaw container

openclaw_opsgate_approval_base_url

""

Optional operator-facing base URL used for returned ticket links (defaults to submit base URL)

openclaw_opsgate_submit_token

""

OpsGate submit token for source openclaw

openclaw_opsgate_extension_id

opsgate-submit

Local OpenClaw plugin id used for deterministic slash-command dispatch

openclaw_opsgate_tool_name

opsgate_submit

Tool name registered by the local OpsGate plugin

openclaw_opsgate_extensions_dir

{{ openclaw_data_dir }}/extensions

Host root for local OpenClaw extensions

openclaw_opsgate_extension_dir

{{ openclaw_opsgate_extensions_dir }}/{{ openclaw_opsgate_extension_id }}

Host path for the OpsGate local plugin

openclaw_opsgate_skill_name

opsgate-submit

Handler skill directory name

openclaw_opsgate_command_skill_name

opsgate

Slash command skill directory name

openclaw_opsgate_skills_dir

{{ openclaw_data_dir }}/skills

Host skill root for OpsGate skill files

openclaw_opsgate_container_skills_dir

/home/node/.openclaw/skills

Container path used by OpsGate command skill

openclaw_opsgate_credentials_path

{{ openclaw_data_dir }}/credentials/opsgate_submit.json

Rendered OpsGate runtime config (0600)

openclaw_opsgate_container_credentials_path

/home/node/.openclaw/credentials/opsgate_submit.json

Container runtime config path used by handler

openclaw_opsgate_submit_timeout_seconds

10

HTTP timeout for OpsGate ticket creation

openclaw_opsgate_ticket_expires_seconds

14400

Ticket expiry window in seconds (0 disables expiry)

openclaw_opsgate_title_max_chars

120

Max generated or user-supplied title length

openclaw_opsgate_summary_max_chars

240

Max generated summary length

openclaw_opsgate_request_max_chars

4000

Max captured issue description length

openclaw_opsgate_task_ref_max_chars

120

Max task_ref length for optional dedupe key

Handler unit tests for managed OpenClaw integration handlers live at:

  • tests/unit/test_openclaw_homeassistant_handler.py

  • tests/unit/test_openclaw_calendar_handler.py

  • tests/unit/test_openclaw_opsgate_handler.py

  • tests/unit/test_openclaw_paperless_handler.py

Advanced

Variable

Default

Description

openclaw_gateway_config

{}

Full config override — rendered directly as openclaw.json, ignoring individual channel vars

openclaw_force_config

false

Overwrite openclaw.json even if it already exists

openclaw_session_state_path

{{ openclaw_data_dir }}/agents/main/sessions/sessions.json

Host session-state JSON patched when managed slash-skill snapshots must be invalidated

openclaw_managed_skills_manifest_path

{{ openclaw_data_dir }}/managed-skills-manifest.json

Host manifest file used to detect managed slash-skill set changes across deploys

openclaw_extra_env

{}

Extra environment variables merged into the container env

openclaw_data_dir

/home/openclaw/data

Persistent data directory (bind-mounted into container)

openclaw_log_dir

/home/openclaw/logs

Log directory (bind-mounted to /tmp/openclaw in container)

openclaw_mem_limit

2g

Docker container memory limit

openclaw_cpus

1.0

Docker container CPU limit

openclaw_pids_limit

256

Docker container PID limit

openclaw_node_max_old_space_size

1536

Node.js memory limit (MB)

openclaw_logrotate_days

7

Days to retain rotated log files

Feature Flags

Variable

Default

Description

openclaw_install_docker

true

Install Docker via docker_install role

openclaw_manage_user

true

Create system user/group

openclaw_healthcheck_enabled

true

Run post-deploy health check (TCP + container)

For a complete list, see defaults/main.yml.

Gateway Configuration

The role seeds openclaw.json into the data directory on first deploy. By default, it builds the config from individual openclaw_telegram_* variables with explicit plugin control (unused channels like WhatsApp are disabled), managed Telegram preview-streaming policy, direct-message session scope, and the required local gateway mode. Once seeded, the config is not overwritten on subsequent deploys — set openclaw_force_config: true to re-render. Existing configs are still patched for the local gateway mode, managed Telegram streaming policy and DM session scope, role-managed plugin entries, and legacy Telegram streaming aliases required by newer OpenClaw schemas.

OpenClaw’s upstream main DM scope shares one conversation across all direct-message senders. Set openclaw_session_dm_scope: per-channel-peer for shared Telegram bots so each sender receives an isolated conversation history while the configured agent workspace remains shared.

openclaw_reply_system_prompt is written to <workspace>/SOUL.md, not the OpenClaw state-directory root. Set openclaw_user_profile when the deployment should also manage <workspace>/USER.md; leaving it empty preserves an onboarding- or operator-maintained profile.

To provide a full custom config, set openclaw_gateway_config:

openclaw_gateway_config:
  channels:
    telegram:
      enabled: true
      dmPolicy: allowlist
      allowFrom: [304012876]
  plugins:
    load:
      paths:
        - /home/openclaw/data/extensions/opsgate-submit
    entries:
      telegram:
        enabled: true
      opsgate-submit:
        enabled: true
      whatsapp:
        enabled: false

Health Check

The role performs a two-stage health check after deploy:

  1. TCP port checkwait_for on the bound host/port (TCP connect)

  2. Container check — verifies the Docker container is in running state

Note: The deploy health check intentionally uses TCP + container state. OpenClaw also serves a control UI over HTTP, but role health checks avoid endpoint/auth coupling.

Nyxmon Monitoring Integration

When openclaw_metrics_endpoint_enabled: true:

  • a root collector timer (openclaw-metrics-collector.timer) runs docker exec <container> node dist/index.js ... --json and writes JSON to:

    • {{ openclaw_metrics_endpoint_json_path }}

  • an unprivileged HTTP server ({{ openclaw_metrics_endpoint_service_name }}) serves the JSON with basic auth at:

    • http://{{ openclaw_metrics_endpoint_bind }}:{{ openclaw_metrics_endpoint_port }}{{ openclaw_metrics_endpoint_path }}

  • response includes meta.age_seconds to detect stale collector output.

  • the collector timer schedules from timer activation and from collector completion, so post-reboot timer activation cannot leave the unit in an elapsed state with no next run.

  • collector runs that execute the synthetic canary can take longer than one timer tick (openclaw_metrics_endpoint_timer_interval); systemd oneshot does not overlap runs.

  • $.openclaw.collector_ok means the collector executed and parsed required metrics inputs. A nonzero health --json result with parseable JSON is still collected so warning-class health sub-signals, such as Telegram Bot API probe failures, do not masquerade as collector failures.

  • optional synthetic canary mode executes a real agent --json liveness turn at a conservative cadence and stores:

    • uses a dedicated canary session id prefix (probe-openclaw-canary by default)

    • appends epoch/attempt suffixes to each due canary attempt so stale canary history cannot grow the prompt or leave retry attempts contending on an abandoned session lock

    • removes generated canary session and trajectory files after openclaw_metrics_endpoint_synthetic_canary_session_retention_seconds

    • treats files beginning with the configured canary prefix as generated canary artifacts; do not create long-lived manual sessions under that prefix

    • does not edit OpenClaw’s lightweight sessions.json index; old canary index entries may remain after backing session files are removed

    • passes --timeout to the OpenClaw CLI and keeps a short outer subprocess grace window

    • $.openclaw.synthetic.canary.last_run.ok

    • $.openclaw.synthetic.canary.last_run.expected_match

    • $.openclaw.synthetic.canary.last_run.session_id

    • $.openclaw.synthetic.canary.last_success_epoch

    • $.openclaw.synthetic.canary.last_success_age_seconds

    • last_success_age_seconds is always numeric (2147483647 sentinel when there is no successful run yet)

Designed for Nyxmon json-metrics checks, for example:

  • $.openclaw.collector_ok == true

  • $.openclaw.health.telegram_probe_ok == true (warning)

  • $.openclaw.channels.telegram.running == true

  • $.openclaw.calendar.auth.has_repeated_failures == false

  • $.openclaw.calendar.auth.has_recent_failures == false

  • $.openclaw.paperless.auth.has_repeated_failures == false

  • $.openclaw.paperless.auth.has_recent_failures == false

  • $.meta.age_seconds < 600

  • $.openclaw.synthetic.canary.last_run.ok == true (synthetic check)

Weeknotes/Todo Automation

When openclaw_weeknotes_enabled: true, the role deploys a weeknotes handler plus command skills (/todo, /note, /snooze) that provide chat-driven todo and journal management. Weeknotes data is stored under a dedicated directory with pre-edit snapshots and an append-only operation log.

Commands: /todo add, /todo list, /note add, /note find, /snooze. A daily cron reminder summarizes open todos on weekdays (noise-suppressed when empty; can be snoozed per-day with /snooze).

Weeknotes Variables

Variable

Default

Description

openclaw_weeknotes_enabled

false

Enable weeknotes/todo skill

openclaw_weeknotes_tz

Europe/Berlin

Timezone for the container (TZ env var)

openclaw_weeknotes_skill_name

weeknotes-md

Handler directory name under openclaw_weeknotes_skills_dir

openclaw_weeknotes_command_skill_names

[todo, note, snooze]

Slash command skill names deployed as SKILL.md

openclaw_weeknotes_skills_dir

{{ openclaw_data_dir }}/skills

Host path for custom skills

openclaw_weeknotes_dir

{{ openclaw_data_dir }}/weeknotes

Host path for weeknotes data

openclaw_weeknotes_container_dir

/home/node/.openclaw/weeknotes

Container mount for weeknotes

openclaw_weeknotes_container_skills_dir

/home/node/.openclaw/skills

Container mount for skills

openclaw_weeknotes_backend

local

Storage backend: local or s3

openclaw_weeknotes_snapshot_max_count

200

Max snapshots retained per file

openclaw_weeknotes_snapshot_max_days

14

Max snapshot age in days

openclaw_weeknotes_lock_timeout

5

File lock timeout in seconds

openclaw_weeknotes_max_payload_length

4000

Max character length for todo and journal write operations; sized for long voice-note transcriptions while retaining a bounded payload

openclaw_weeknotes_rate_limit_writes

10

Max writes per rate-limit window

openclaw_weeknotes_rate_limit_window_secs

600

Rate-limit window in seconds

openclaw_weeknotes_reminder_cron

0 9 * * 1-5

Cron schedule for daily reminder

openclaw_weeknotes_reminder_command

/todo reminder

System-event text sent by reminder cron job

Weeknotes S3 Backend Variables

Used when openclaw_weeknotes_backend: s3.

Variable

Default

Description

openclaw_weeknotes_s3_endpoint

""

S3/MinIO endpoint URL

openclaw_weeknotes_s3_region

us-east-1

S3 region string

openclaw_weeknotes_s3_bucket

""

Bucket name

openclaw_weeknotes_s3_prefix

""

Optional object key prefix

openclaw_weeknotes_s3_access_key

""

Access key for weeknotes service account

openclaw_weeknotes_s3_secret_key

""

Secret key for weeknotes service account

openclaw_weeknotes_s3_note_key_format

%Y-%m-%d.md

date format string for note object key

openclaw_weeknotes_s3_todo_list_mode

current_note

Todo-list source mode: current_note or daily_notes

openclaw_weeknotes_s3_todo_list_key_regex

^[0-9]{4}-[0-9]{2}-[0-9]{2}\\.md$

Regex used in daily_notes mode to select note object keys

openclaw_weeknotes_s3_todo_cache_refresh_secs

120

Cache refresh interval for daily_notes mode

openclaw_weeknotes_s3_mc_alias

weeknotes

MinIO client alias name used by handler

openclaw_weeknotes_s3_mc_host_path

/usr/local/bin/mc

Host path to MinIO client binary (bind-mounted read-only)

openclaw_weeknotes_s3_mc_container_path

/usr/local/bin/mc

Container path for MinIO client binary

Storage Model

  • local backend: weekly files YYYY-Www.md (e.g., 2026-W09.md)

  • s3 backend: direct object-store reads/writes using openclaw_weeknotes_s3_note_key_format (default daily keys like 2026-02-25.md)

  • Pre-edit snapshots: .snapshots/<file-or-key>.<timestamp>.bak

  • Operation log: .ops-log.jsonl (append-only)

Docker Compose Services

  • openclaw-gateway: Main gateway service, starts with docker compose up

  • openclaw-cli: CLI utility, available via docker compose run --rm openclaw-cli <subcommand> (uses profiles: [cli], does not auto-start, and shares the gateway network namespace so CLI commands can reach ws://127.0.0.1:18789)

Post-Deploy: Telegram

If using allowlist dm_policy with allowFrom, messages from listed users should work immediately. If pairing is needed:

docker compose -f /home/openclaw/site/docker-compose.yml \
  run --rm openclaw-cli pairing list telegram
docker compose -f /home/openclaw/site/docker-compose.yml \
  run --rm openclaw-cli pairing approve telegram <CODE>

Handlers

  • reload systemd — Daemon reload after unit file changes

  • restart openclaw — Restart the systemd service

  • reload traefik — Restart Traefik after config changes

Testing

# Check container status
systemctl status openclaw
docker compose -f /home/openclaw/site/docker-compose.yml ps

# View logs
docker logs openclaw-gateway --tail 50
docker compose -f /home/openclaw/site/docker-compose.yml logs -f

# CLI access
docker compose -f /home/openclaw/site/docker-compose.yml run --rm openclaw-cli <subcommand>

# Verify Traefik routing (from Tailscale device)
curl -k https://openclaw.macmini.tailde2ec.ts.net/

License

MIT