From 8d4c3d14a4172cd3f34f0f745972b2affd6c07f3 Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Mon, 5 Oct 2026 11:22:18 +0700 Subject: [PATCH] feat: add openclaw and hermes agent templates --- README.md | 2 + hermes/.env.example | 15 ++++++++ hermes/README.md | 52 +++++++++++++++++++++++++ hermes/compose.yml | 49 ++++++++++++++++++++++++ openclaw/.env.example | 69 +++++++++++++++++++++++++++++++++ openclaw/README.md | 65 +++++++++++++++++++++++++++++++ openclaw/compose.yml | 89 +++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 341 insertions(+) create mode 100644 hermes/.env.example create mode 100644 hermes/README.md create mode 100644 hermes/compose.yml create mode 100644 openclaw/.env.example create mode 100644 openclaw/README.md create mode 100644 openclaw/compose.yml diff --git a/README.md b/README.md index f4f9c92..6170e55 100644 --- a/README.md +++ b/README.md @@ -82,8 +82,10 @@ Each links to its own README for variables, ports, and storage. | [diun](diun/README.md) | Image-update notifier, reading the Docker API through a read-only proxy | | [gitea-mirror](gitea-mirror/README.md) | Gitea + PostgreSQL + gitea-mirror, mirroring GitHub repos | | [goclaw](goclaw/README.md) | Multi-tenant AI agent gateway, with pgvector PostgreSQL | +| [hermes](hermes/README.md) | Hermes Agent with its web chat UI | | [litellm](litellm/README.md) | LiteLLM proxy in front of many LLM providers, with PostgreSQL and Redis | | [open-webui](open-webui/README.md) | Open WebUI chat interface for OpenAI-compatible and Ollama providers | +| [openclaw](openclaw/README.md) | OpenClaw AI agent gateway, with a CDP browser | | [opencode](opencode/README.md) | opencode coding agent, served as a browser UI | | [owncloud](owncloud/README.md) | ownCloud file sync and share, with MariaDB and Redis | | [paseo](paseo/README.md) | Paseo coding-agent daemon and web UI | diff --git a/hermes/.env.example b/hermes/.env.example new file mode 100644 index 0000000..713152b --- /dev/null +++ b/hermes/.env.example @@ -0,0 +1,15 @@ +# Copy to .env and fill in. Never commit .env. +# +# cp .env.example .env + +# Web UI login password. +# Generate one with: openssl rand -base64 24 +HERMES_WEBUI_PASSWORD= + +# Model provider for the agent. +OPENROUTER_API_KEY= + +# Other providers. Uncomment here and in compose.yml to enable one. +# ANTHROPIC_API_KEY= +# OPENAI_API_KEY= +# GOOGLE_API_KEY= diff --git a/hermes/README.md b/hermes/README.md new file mode 100644 index 0000000..8bbafa7 --- /dev/null +++ b/hermes/README.md @@ -0,0 +1,52 @@ +# hermes + +[Hermes Agent](https://github.com/NousResearch/hermes-agent), an autonomous AI +agent with persistent memory and scheduling, behind +[Hermes WebUI](https://github.com/nesquena/hermes-webui), a self-hosted web +chat for it. + +Two containers: `hermes-agent` runs the agent gateway, and `hermes-webui` +serves the chat on port `8787`. + +## Setup + +1. Set `HERMES_WEBUI_PASSWORD` and `OPENROUTER_API_KEY`. +2. Map the domain to the `hermes-webui` container on port `8787` and deploy. +3. Open the domain and log in with `HERMES_WEBUI_PASSWORD`. + +Health check: `GET /health` on the web UI, also its compose healthcheck. + +## Environment + +| Variable | Default | Purpose | +| --- | --- | --- | +| `HERMES_WEBUI_PASSWORD` | — | Web UI login | +| `OPENROUTER_API_KEY` | empty | Model provider | +| `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GOOGLE_API_KEY` | optional | Other model providers | + +`HERMES_WEBUI_PASSWORD` is required: without it the chat, and the agent +behind it, are open to anyone who reaches the domain. + +The uid/gid pairs are pinned to `1000` in `compose.yml`. Both containers write +the shared `hermes-home` volume, so both must run as the same user. + +## Storage + +| Volume | Mount | Holds | +| --- | --- | --- | +| `hermes-home` | `/home/hermes/.hermes` (agent), `/home/hermeswebui/.hermes` (web UI) | Agent config, memory, skills and sessions; web UI state in `webui/` | +| `hermes-agent-src` | `/opt/hermes` (agent), `.hermes/hermes-agent`, read-only (web UI) | The agent's source code, which the web UI imports | +| `hermes-workspace` | `/workspace` (web UI) | Files the agent works on | + +### Updating the agent + +`hermes-agent-src` is filled from the agent image only when the volume is +first created. A newer `latest` image therefore keeps running the old source +until the volume is removed, which upstream documents as the upgrade step: +stop the app, delete the `hermes-agent-src` volume, and deploy again. Nothing +else lives in that volume. + +## Images + +Both images use `latest`. Upstream recommends moving the two together, since +the web UI imports the agent's source and is built against matching versions. diff --git a/hermes/compose.yml b/hermes/compose.yml new file mode 100644 index 0000000..61b6b62 --- /dev/null +++ b/hermes/compose.yml @@ -0,0 +1,49 @@ +services: + hermes-agent: + image: nousresearch/hermes-agent:latest + restart: unless-stopped + command: gateway run + environment: + - HERMES_HOME=/home/hermes/.hermes + - HERMES_UID=1000 + - HERMES_GID=1000 + - OPENROUTER_API_KEY=${OPENROUTER_API_KEY:-} + # - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-} + # - OPENAI_API_KEY=${OPENAI_API_KEY:-} + # - GOOGLE_API_KEY=${GOOGLE_API_KEY:-} + volumes: + - hermes-home:/home/hermes/.hermes + - hermes-agent-src:/opt/hermes + healthcheck: + test: ["CMD-SHELL", "test -d /home/hermes/.hermes || exit 1"] + interval: 10s + timeout: 5s + retries: 5 + + # Web chat UI, sharing the agent's home and source. + hermes-webui: + image: ghcr.io/nesquena/hermes-webui:latest + restart: unless-stopped + depends_on: + - hermes-agent + environment: + - HERMES_WEBUI_PASSWORD=${HERMES_WEBUI_PASSWORD:?required} + - WANTED_UID=1000 + - WANTED_GID=1000 + - HERMES_WEBUI_HOST=0.0.0.0 + - HERMES_WEBUI_PORT=8787 + - HERMES_WEBUI_STATE_DIR=/home/hermeswebui/.hermes/webui + volumes: + - hermes-home:/home/hermeswebui/.hermes + - hermes-agent-src:/home/hermeswebui/.hermes/hermes-agent:ro + - hermes-workspace:/workspace + healthcheck: + test: ["CMD", "curl", "-f", "http://127.0.0.1:8787/health"] + interval: 30s + timeout: 5s + retries: 3 + +volumes: + hermes-home: + hermes-agent-src: + hermes-workspace: diff --git a/openclaw/.env.example b/openclaw/.env.example new file mode 100644 index 0000000..c94cc99 --- /dev/null +++ b/openclaw/.env.example @@ -0,0 +1,69 @@ +# Copy to .env and fill in. Never commit .env. +# +# cp .env.example .env + +# Web UI login. +# Generate a password with: openssl rand -base64 24 +AUTH_USERNAME=admin +AUTH_PASSWORD= + +# Token for the agent gateway. Generate one with: openssl rand -hex 32 +OPENCLAW_GATEWAY_TOKEN= + +# At least one provider key is required; OpenRouter is the active one. +OPENROUTER_API_KEY= + +# Default model, e.g. openrouter/anthropic/claude-sonnet-4.5. +# OPENCLAW_PRIMARY_MODEL= + +# Other providers. Uncomment here and in compose.yml to enable one. +# ANTHROPIC_API_KEY= +# OPENAI_API_KEY= +# GEMINI_API_KEY= +# XAI_API_KEY= +# GROQ_API_KEY= +# MISTRAL_API_KEY= +# CEREBRAS_API_KEY= +# VENICE_API_KEY= +# MOONSHOT_API_KEY= +# MOONSHOT_BASE_URL=https://api.moonshot.ai/v1 +# KIMI_API_KEY= +# KIMI_BASE_URL=https://api.moonshot.ai/anthropic +# MINIMAX_API_KEY= +# ZAI_API_KEY= +# AI_GATEWAY_API_KEY= +# OPENCODE_API_KEY= +# SYNTHETIC_API_KEY= +# COPILOT_GITHUB_TOKEN= +# XIAOMI_API_KEY= +# Speech to text. +# DEEPGRAM_API_KEY= +# AWS Bedrock. +# AWS_ACCESS_KEY_ID= +# AWS_SECRET_ACCESS_KEY= +# AWS_SESSION_TOKEN= +# AWS_REGION=us-east-1 +# BEDROCK_PROVIDER_FILTER=anthropic +# OLLAMA_BASE_URL= + +# Chat channels. +# TELEGRAM_BOT_TOKEN= +# DISCORD_BOT_TOKEN= +# SLACK_BOT_TOKEN= +# SLACK_APP_TOKEN= +# WHATSAPP_ENABLED= + +# Gateway, browser and hook tuning. +# Origins allowed to open the control UI, comma separated, e.g. https://openclaw.example.com. +# OPENCLAW_ALLOWED_ORIGINS= +# OPENCLAW_GATEWAY_BIND=loopback +# BROWSER_DEFAULT_PROFILE=openclaw +# BROWSER_EVALUATE_ENABLED=true +# BROWSER_SNAPSHOT_MODE=efficient +# BROWSER_REMOTE_TIMEOUT_MS=1500 +# BROWSER_REMOTE_HANDSHAKE_TIMEOUT_MS=3000 +# HOOKS_ENABLED=false +# HOOKS_PATH=/hooks + +# Extra apt packages installed into the container at start, space separated. +# OPENCLAW_DOCKER_APT_PACKAGES= diff --git a/openclaw/README.md b/openclaw/README.md new file mode 100644 index 0000000..9e62bab --- /dev/null +++ b/openclaw/README.md @@ -0,0 +1,65 @@ +# openclaw + +[OpenClaw](https://docs.openclaw.ai): a personal AI agent gateway with a web +UI, chat-channel bots and browser automation. Runs Coolify's +`coollabsio/openclaw` image, which wraps OpenClaw with nginx basic auth and +env-driven configuration. + +Two containers: `openclaw`, and `browser`, a Chromium the agent drives over +the Chrome DevTools Protocol. + +## Setup + +1. Set `AUTH_PASSWORD`, `OPENCLAW_GATEWAY_TOKEN` and `OPENROUTER_API_KEY`. +2. Map the domain to port `8080` and deploy. +3. Open the domain and log in with `AUTH_USERNAME` / `AUTH_PASSWORD`. + +Health check: `GET /healthz`, also the compose healthcheck. + +## Environment + +| Variable | Default | Purpose | +| --- | --- | --- | +| `AUTH_USERNAME` / `AUTH_PASSWORD` | `admin` / — | Basic-auth login in front of the web UI | +| `OPENCLAW_GATEWAY_TOKEN` | — | Token the web UI and clients use to reach the gateway | +| `OPENROUTER_API_KEY` | empty | Model provider | +| `OPENCLAW_PRIMARY_MODEL` | optional | Default model | +| Other provider keys, `AWS_*`, `OLLAMA_BASE_URL` | optional | Additional model providers | +| `TELEGRAM_BOT_TOKEN`, `DISCORD_BOT_TOKEN`, `SLACK_*`, `WHATSAPP_ENABLED` | optional | Chat channels | +| `OPENCLAW_ALLOWED_ORIGINS` | optional | Origins allowed to open the control UI | +| `BROWSER_*`, `HOOKS_*`, `OPENCLAW_GATEWAY_BIND` | optional | Tuning, defaults shown in `compose.yml` | +| `OPENCLAW_DOCKER_APT_PACKAGES` | optional | Extra apt packages installed at start | + +`AUTH_PASSWORD` is required: without it nginx serves the UI with no login. +The entrypoint itself refuses to start without `OPENCLAW_GATEWAY_TOKEN` or +without at least one provider key. + +OpenRouter stays active as the one provider every deployment needs; any +other provider is enabled by uncommenting its line. Each provider key is read +from the environment on every start, never stored in the config. + +`PORT`, the gateway port, the state and workspace directories and +`BROWSER_CDP_URL` are fixed in `compose.yml`, as properties of this layout. + +`OPENCLAW_CONFIG_JSON` sets `gateway.trustedProxies` to `127.0.0.1`. The +image's own nginx sits in front of the gateway on loopback, and current +OpenClaw (tested on 2026.9.8) rejects every proxied request with 403 +`proxy_attribution_required` unless that proxy is trusted. The wrapper merges +this JSON into `openclaw.json` on every start. + +The wrapper adds the deployment's domain to the control UI's allowed origins +from the `COOLIFY_URL` and `COOLIFY_FQDN` Coolify injects. +`OPENCLAW_ALLOWED_ORIGINS` is for any other origin, and for a deployment +Coolify does not inject that into. + +## Storage + +| Volume | Mount | Holds | +| --- | --- | --- | +| `openclaw-data` | `/data` | Config and sessions in `.openclaw`, the agent workspace in `workspace` | +| `browser-data` | `/config` | Chromium profile: cookies and logins of sites the agent uses | + +## Images + +Both images use `latest`. `coollabsio/openclaw` publishes no major tag, only +dated releases, so `latest` is the moving one. diff --git a/openclaw/compose.yml b/openclaw/compose.yml new file mode 100644 index 0000000..585856f --- /dev/null +++ b/openclaw/compose.yml @@ -0,0 +1,89 @@ +services: + openclaw: + image: coollabsio/openclaw:latest + restart: unless-stopped + environment: + - AUTH_USERNAME=${AUTH_USERNAME:-admin} + - AUTH_PASSWORD=${AUTH_PASSWORD:?required} + - OPENCLAW_GATEWAY_TOKEN=${OPENCLAW_GATEWAY_TOKEN:?required} + - OPENROUTER_API_KEY=${OPENROUTER_API_KEY:-} + - PORT=8080 + - OPENCLAW_GATEWAY_PORT=18789 + - OPENCLAW_STATE_DIR=/data/.openclaw + - OPENCLAW_WORKSPACE_DIR=/data/workspace + - BROWSER_CDP_URL=http://browser:9223 + - 'OPENCLAW_CONFIG_JSON={"gateway":{"trustedProxies":["127.0.0.1"]}}' + # - OPENCLAW_PRIMARY_MODEL=${OPENCLAW_PRIMARY_MODEL:-} + # - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-} + # - OPENAI_API_KEY=${OPENAI_API_KEY:-} + # - GEMINI_API_KEY=${GEMINI_API_KEY:-} + # - XAI_API_KEY=${XAI_API_KEY:-} + # - GROQ_API_KEY=${GROQ_API_KEY:-} + # - MISTRAL_API_KEY=${MISTRAL_API_KEY:-} + # - CEREBRAS_API_KEY=${CEREBRAS_API_KEY:-} + # - VENICE_API_KEY=${VENICE_API_KEY:-} + # - MOONSHOT_API_KEY=${MOONSHOT_API_KEY:-} + # - MOONSHOT_BASE_URL=${MOONSHOT_BASE_URL:-https://api.moonshot.ai/v1} + # - KIMI_API_KEY=${KIMI_API_KEY:-} + # - KIMI_BASE_URL=${KIMI_BASE_URL:-https://api.moonshot.ai/anthropic} + # - MINIMAX_API_KEY=${MINIMAX_API_KEY:-} + # - ZAI_API_KEY=${ZAI_API_KEY:-} + # - AI_GATEWAY_API_KEY=${AI_GATEWAY_API_KEY:-} + # - OPENCODE_API_KEY=${OPENCODE_API_KEY:-} + # - SYNTHETIC_API_KEY=${SYNTHETIC_API_KEY:-} + # - COPILOT_GITHUB_TOKEN=${COPILOT_GITHUB_TOKEN:-} + # - XIAOMI_API_KEY=${XIAOMI_API_KEY:-} + # - DEEPGRAM_API_KEY=${DEEPGRAM_API_KEY:-} + # - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-} + # - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-} + # - AWS_SESSION_TOKEN=${AWS_SESSION_TOKEN:-} + # - AWS_REGION=${AWS_REGION:-us-east-1} + # - BEDROCK_PROVIDER_FILTER=${BEDROCK_PROVIDER_FILTER:-anthropic} + # - OLLAMA_BASE_URL=${OLLAMA_BASE_URL:-} + # - TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN:-} + # - DISCORD_BOT_TOKEN=${DISCORD_BOT_TOKEN:-} + # - SLACK_BOT_TOKEN=${SLACK_BOT_TOKEN:-} + # - SLACK_APP_TOKEN=${SLACK_APP_TOKEN:-} + # - WHATSAPP_ENABLED=${WHATSAPP_ENABLED:-} + # - OPENCLAW_ALLOWED_ORIGINS=${OPENCLAW_ALLOWED_ORIGINS:-} + # - OPENCLAW_GATEWAY_BIND=${OPENCLAW_GATEWAY_BIND:-loopback} + # - BROWSER_DEFAULT_PROFILE=${BROWSER_DEFAULT_PROFILE:-openclaw} + # - BROWSER_EVALUATE_ENABLED=${BROWSER_EVALUATE_ENABLED:-true} + # - BROWSER_SNAPSHOT_MODE=${BROWSER_SNAPSHOT_MODE:-efficient} + # - BROWSER_REMOTE_TIMEOUT_MS=${BROWSER_REMOTE_TIMEOUT_MS:-1500} + # - BROWSER_REMOTE_HANDSHAKE_TIMEOUT_MS=${BROWSER_REMOTE_HANDSHAKE_TIMEOUT_MS:-3000} + # - HOOKS_ENABLED=${HOOKS_ENABLED:-false} + # - HOOKS_PATH=${HOOKS_PATH:-/hooks} + # - OPENCLAW_DOCKER_APT_PACKAGES=${OPENCLAW_DOCKER_APT_PACKAGES:-} + volumes: + - openclaw-data:/data + depends_on: + browser: + condition: service_healthy + healthcheck: + test: ["CMD", "curl", "-sf", "http://127.0.0.1:8080/healthz"] + interval: 10s + timeout: 10s + retries: 5 + + # Chromium the agent drives over CDP. + browser: + image: coollabsio/openclaw-browser:latest + restart: unless-stopped + shm_size: 2g + environment: + - PUID=1000 + - PGID=1000 + - CHROME_CLI=--remote-debugging-port=9222 + - TZ=Etc/UTC + volumes: + - browser-data:/config + healthcheck: + test: ["CMD-SHELL", "bash -c ':> /dev/tcp/127.0.0.1/9222' || exit 1"] + interval: 5s + timeout: 5s + retries: 10 + +volumes: + openclaw-data: + browser-data: