From fc83a7404df68dbe1365e6896e08ee806db636bc Mon Sep 17 00:00:00 2001 From: hanlehui Date: Sat, 2 May 2026 17:33:25 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=8D=95=E5=AE=B9?= =?UTF-8?q?=E5=99=A8docker=20build=E8=84=9A=E6=9C=AC=EF=BC=8C=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E6=96=87=E4=BB=B6=E6=9D=83=E9=99=90=EF=BC=8C=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0/opt/data/workspace=E5=B7=A5=E4=BD=9C=E5=8C=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.combined.example | 21 ++++++++++ Dockerfile.combined | 56 ++++++++++++++++++++++++++ docker-compose.combined.yml | 27 +++++++++++++ docker/start-combined.sh | 37 +++++++++++++++++ docs/docker-combined.md | 79 +++++++++++++++++++++++++++++++++++++ 5 files changed, 220 insertions(+) create mode 100644 .env.combined.example create mode 100644 Dockerfile.combined create mode 100644 docker-compose.combined.yml create mode 100644 docker/start-combined.sh create mode 100644 docs/docker-combined.md diff --git a/.env.combined.example b/.env.combined.example new file mode 100644 index 00000000..3cca773b --- /dev/null +++ b/.env.combined.example @@ -0,0 +1,21 @@ +# Combined Hermes Container Configuration +# Copy this file to .env and modify as needed + +# Docker image settings +HERMES_AGENT_IMAGE=nousresearch/hermes-agent:latest +COMBINED_IMAGE=hermes-combined:latest +CONTAINER_NAME=hermes + +# Data directory (host path) +HERMES_DATA_DIR=./hermes_data + +# Web UI settings +PORT=6060 +AUTH_DISABLED=false + +# Notes: +# - UPSTREAM is automatically set to http://127.0.0.1:8642 (localhost within container) +# - HERMES_BIN is set to /opt/hermes/.venv/bin/hermes +# - HERMES_HOME is set to /opt/data/.hermes (matches hermes user home) +# - Ports 8642-8670 are exposed for multi-profile gateway support +# - Container user is 'hermes' with home /opt/data diff --git a/Dockerfile.combined b/Dockerfile.combined new file mode 100644 index 00000000..1d367b7c --- /dev/null +++ b/Dockerfile.combined @@ -0,0 +1,56 @@ +ARG BASE_IMAGE=nousresearch/hermes-agent:latest +FROM ${BASE_IMAGE} + +USER root + +# Install build dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + curl \ + make \ + g++ \ + && rm -rf /var/lib/apt/lists/* + +# Install Node.js +RUN ARCH=$(dpkg --print-architecture) \ + && if [ "$ARCH" = "amd64" ]; then NODE_ARCH="x64"; else NODE_ARCH="$ARCH"; fi \ + && echo "Downloading Node.js v23.11.0 for ${NODE_ARCH}" \ + && curl -fsSL "https://nodejs.org/dist/v23.11.0/node-v23.11.0-linux-${NODE_ARCH}.tar.gz" \ + -o /tmp/node.tar.gz \ + && tar -xzf /tmp/node.tar.gz -C /usr/local --strip-components=1 \ + && rm -f /tmp/node.tar.gz \ + && node --version + +WORKDIR /app + +# Copy and build web-ui +COPY package*.json ./ +RUN npm install --ignore-scripts && npm rebuild node-pty + +COPY . . +RUN npm run build && npm prune --omit=dev + +# Ensure proper permissions for hermes user +RUN chown -R hermes:hermes /app + +# Copy startup script +COPY docker/start-combined.sh /start-combined.sh +RUN chmod +x /start-combined.sh + +# Environment variables +ENV NODE_ENV=production +ENV HOME=/opt/data +ENV HERMES_HOME=/opt/data/.hermes +ENV PORT=6060 +ENV UPSTREAM=http://127.0.0.1:8642 +ENV HERMES_BIN=/opt/hermes/.venv/bin/hermes +ENV PATH=/opt/hermes/.venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + +# Expose ports +# 8642-8670: Hermes Agent gateway ports +# 6060: Web UI port +EXPOSE 8642-8670 6060 + +# Use combined startup script +ENTRYPOINT ["/start-combined.sh"] +CMD [] diff --git a/docker-compose.combined.yml b/docker-compose.combined.yml new file mode 100644 index 00000000..26a2f9c1 --- /dev/null +++ b/docker-compose.combined.yml @@ -0,0 +1,27 @@ +services: + hermes: + build: + context: . + dockerfile: Dockerfile.combined + args: + BASE_IMAGE: ${HERMES_AGENT_IMAGE:-nousresearch/hermes-agent:latest} + image: ${COMBINED_IMAGE:-hermes-combined:latest} + container_name: ${CONTAINER_NAME:-hermes} + user: root + volumes: + - ${HERMES_DATA_DIR:-./hermes_data}:/opt/data/.hermes:z + - ${HERMES_DATA_DIR:-./hermes_data}/hermes-web-ui:/opt/data/.hermes-web-ui:z + - ${HERMES_DATA_DIR:-./hermes_data}/workspace:/opt/data/workspace:z + environment: + - PORT=${PORT:-6060} + - UPSTREAM=http://127.0.0.1:8642 + - HERMES_HOME=/opt/data/.hermes + - HERMES_BIN=/opt/hermes/.venv/bin/hermes + - AUTH_DISABLED=${AUTH_DISABLED:-false} + - PATH=/opt/hermes/.venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + ports: + - "${PORT:-6060}:${PORT:-6060}" + - "8642-8670:8642-8670" + stdin_open: true + tty: true + restart: unless-stopped diff --git a/docker/start-combined.sh b/docker/start-combined.sh new file mode 100644 index 00000000..d35d5936 --- /dev/null +++ b/docker/start-combined.sh @@ -0,0 +1,37 @@ +#!/bin/bash +set -e + +# Combined startup script for hermes-agent + hermes-web-ui +# Runs both services in a single container (as root) +# The web-ui's GatewayManager handles hermes-agent lifecycle + +# Graceful shutdown handler (before exec replaces this process) +cleanup() { + echo "[start-combined] Shutting down..." + exit 0 +} + +trap cleanup SIGTERM SIGINT SIGQUIT + +# Mark container environment so GatewayManager uses "gateway run" mode +# (Podman doesn't create /.dockerenv like Docker does) +touch /.dockerenv 2>/dev/null || true + +# Ensure .hermes directory exists +echo "[start-combined] Ensuring .hermes directory..." +mkdir -p /opt/data/.hermes/logs +mkdir -p /opt/data/.hermes-web-ui + +# Ensure workspace directory exists +mkdir -p /opt/data/workspace + +# Set hermes default working directory to /opt/data/workspace +echo "[start-combined] Setting hermes cwd to /opt/data/workspace..." +/opt/hermes/.venv/bin/hermes config set messaging.cwd /opt/data/workspace 2>/dev/null || true +/opt/hermes/.venv/bin/hermes config set terminal.cwd /opt/data/workspace 2>/dev/null || true + +# Start hermes-webui in foreground (as root, no su) +# The web-ui's GatewayManager will start hermes-agent automatically +echo "[start-combined] Starting hermes-webui on port ${PORT:-6060}..." +cd /app +exec node dist/server/index.js diff --git a/docs/docker-combined.md b/docs/docker-combined.md new file mode 100644 index 00000000..61454b1e --- /dev/null +++ b/docs/docker-combined.md @@ -0,0 +1,79 @@ +# Combined Hermes Container + +将webui和agent容器合二为一,对于容器文件权限做处理,兼容SELinux和podman + +## Quick Start + +### Build and Run + +```bash +# Copy environment configuration +cp .env.combined.example .env + +# Edit .env if needed (optional) +# vim .env + +# Build and start (using podman-compose) +podman-compose -f docker-compose.combined.yml up -d --build + +# View logs +podman-compose -f docker-compose.combined.yml logs -f + +# Stop +podman-compose -f docker-compose.combined.yml down +``` + +### Using Docker + +```bash +# Build and start (using docker compose) +docker compose -f docker-compose.combined.yml up -d --build + +# View logs +docker compose -f docker-compose.combined.yml logs -f + +# Stop +docker compose -f docker-compose.combined.yml down +``` + +## Access + +- **Web UI:** http://localhost:6060 +- **Auth Token:** Check logs or `./hermes_data/hermes-web-ui/.token` + +## Environment Variables + +| Variable | Default | Description | +|----------|---------|-------------| +| `HERMES_AGENT_IMAGE` | `nousresearch/hermes-agent:latest` | Base hermes-agent image | +| `COMBINED_IMAGE` | `hermes-combined:latest` | Combined image name | +| `CONTAINER_NAME` | `hermes` | Container name | +| `HERMES_DATA_DIR` | `./hermes_data` | Host path for data persistence | +| `PORT` | `6060` | Web UI port | +| `AUTH_DISABLED` | `false` | Set to `true` to disable authentication | + +### Restart Services + +```bash +podman-compose -f docker-compose.combined.yml restart +``` + +### Rebuild from Scratch + +```bash +podman-compose -f docker-compose.combined.yml down +podman-compose -f docker-compose.combined.yml up -d --build +``` + +### Check Container Health + +```bash +# Check if both services are running +podman-compose -f docker-compose.combined.yml ps + +# Check hermes-agent default gateway health +curl http://localhost:8642/health + +# Check hermes-webui health +curl http://localhost:6060/health +``` From 36db26221e1e88df2934a469019c051369969938 Mon Sep 17 00:00:00 2001 From: hanlehui Date: Sat, 2 May 2026 21:54:21 +0800 Subject: [PATCH 2/4] Update Dockerfile.combined --- Dockerfile.combined | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile.combined b/Dockerfile.combined index 1d367b7c..864d1c8d 100644 --- a/Dockerfile.combined +++ b/Dockerfile.combined @@ -25,6 +25,8 @@ WORKDIR /app # Copy and build web-ui COPY package*.json ./ +# Increase Node.js memory limit to prevent OOM during build +ENV NODE_OPTIONS=--max-old-space-size=4096 RUN npm install --ignore-scripts && npm rebuild node-pty COPY . . From 109479b1cbc2eae276c5f162e5c5b15e444440e8 Mon Sep 17 00:00:00 2001 From: hanlehui Date: Fri, 8 May 2026 20:44:02 +0800 Subject: [PATCH 3/4] =?UTF-8?q?fix:=20=E5=9C=A8docker=E4=B8=AD=E4=BB=A5roo?= =?UTF-8?q?t=E8=BF=90=E8=A1=8Chermes=E6=B2=A1=E6=9C=89=E9=97=AE=E9=A2=98?= =?UTF-8?q?=EF=BC=8C=E4=BF=AE=E5=A4=8Dgateway=E5=9B=A0=E4=B8=BA=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E4=B8=BAroot=E6=97=A0=E6=B3=95=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.combined.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.combined.yml b/docker-compose.combined.yml index 26a2f9c1..7b49a52e 100644 --- a/docker-compose.combined.yml +++ b/docker-compose.combined.yml @@ -18,6 +18,7 @@ services: - HERMES_HOME=/opt/data/.hermes - HERMES_BIN=/opt/hermes/.venv/bin/hermes - AUTH_DISABLED=${AUTH_DISABLED:-false} + - HERMES_ALLOW_ROOT_GATEWAY=1 - PATH=/opt/hermes/.venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin ports: - "${PORT:-6060}:${PORT:-6060}" From ffac07d25eee64c7ece74e9aa6ca138fb0be1f10 Mon Sep 17 00:00:00 2001 From: hanlehui Date: Sun, 10 May 2026 22:18:38 +0800 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=9B=A0PR#560?= =?UTF-8?q?=E7=9A=84=E6=94=B9=E5=8A=A8=E5=AF=BC=E8=87=B4=E8=9E=8D=E5=90=88?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E5=AE=B9=E5=99=A8=E5=90=AF=E5=8A=A8=E8=AE=BF?= =?UTF-8?q?=E9=97=AEgatewayhost=E7=9A=84DNS=E6=9C=AA=E6=89=BE=E5=88=B0?= =?UTF-8?q?=EF=BC=8C=E5=A2=9E=E5=8A=A0=E7=8E=AF=E5=A2=83=E5=8F=98=E9=87=8F?= =?UTF-8?q?=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.combined.yml | 2 +- packages/server/src/services/hermes/gateway-manager.ts | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docker-compose.combined.yml b/docker-compose.combined.yml index 7b49a52e..4ae2e431 100644 --- a/docker-compose.combined.yml +++ b/docker-compose.combined.yml @@ -14,11 +14,11 @@ services: - ${HERMES_DATA_DIR:-./hermes_data}/workspace:/opt/data/workspace:z environment: - PORT=${PORT:-6060} - - UPSTREAM=http://127.0.0.1:8642 - HERMES_HOME=/opt/data/.hermes - HERMES_BIN=/opt/hermes/.venv/bin/hermes - AUTH_DISABLED=${AUTH_DISABLED:-false} - HERMES_ALLOW_ROOT_GATEWAY=1 + - GATEWAY_DEFAULT_HOST=127.0.0.1 - PATH=/opt/hermes/.venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin ports: - "${PORT:-6060}:${PORT:-6060}" diff --git a/packages/server/src/services/hermes/gateway-manager.ts b/packages/server/src/services/hermes/gateway-manager.ts index 023656dc..b1e07b37 100644 --- a/packages/server/src/services/hermes/gateway-manager.ts +++ b/packages/server/src/services/hermes/gateway-manager.ts @@ -163,7 +163,9 @@ export class GatewayManager { */ private readProfilePort(name: string): { port: number; host: string } { const configPath = join(this.profileDir(name), 'config.yaml') - const defaultHost = initSystem === 'container' ? 'hermes-agent' : '127.0.0.1' + const defaultHost = initSystem === 'container' + ? (process.env.GATEWAY_DEFAULT_HOST || 'hermes-agent') + : '127.0.0.1' if (!existsSync(configPath)) return { port: 8642, host: defaultHost }