diff --git a/.gitea/workflows/cron.yaml b/.gitea/workflows/cron.yaml new file mode 100644 index 0000000..3443beb --- /dev/null +++ b/.gitea/workflows/cron.yaml @@ -0,0 +1,52 @@ +name: Nightly Rebuild + +on: + schedule: + - cron: '0 0 * * *' + +jobs: + hadolint: + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + - uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5 # v3.3.0 + with: + dockerfile: Dockerfile + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + - uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4 + - run: docker build -t ci-image:${{ github.sha }} . + + build-push: + needs: [build] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + with: + fetch-depth: 0 + - uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4 + - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - id: get-latest-tag + run: | + TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") + echo "tag=$TAG" >> $GITHUB_OUTPUT + - id: meta + uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6 + with: + images: jcabillot/hermes-agent-webui + tags: | + type=raw,value=${{ steps.get-latest-tag.outputs.tag }}-latest,enable=${{ steps.get-latest-tag.outputs.tag != '' }} + - uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + pull: true diff --git a/.gitea/workflows/main.yaml b/.gitea/workflows/main.yaml new file mode 100644 index 0000000..fceb645 --- /dev/null +++ b/.gitea/workflows/main.yaml @@ -0,0 +1,39 @@ +name: Main Release + +on: + push: + branches: [main] + +jobs: + hadolint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + - uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5 # v3.3.0 + with: + dockerfile: Dockerfile + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + - uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4 + - run: docker build -t ci-image:${{ github.sha }} . + + tag: + needs: [build] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + with: + fetch-depth: 0 + - name: Configure git auth + run: | + git remote set-url origin "https://x-access-token:${{ secrets.SA_TOKEN_ACTION_PUSH_TAGS }}@scm.cabillot.eu/web/hermes-agent-webui.git" + - uses: anothrNick/github-tag-action@4ed44965e0db8dab2b466a16da04aec3cc312fd8 # v1.75.0 + env: + GITHUB_TOKEN: ${{ secrets.SA_TOKEN_ACTION_PUSH_TAGS }} + DEFAULT_BUMP: patch + RELEASE_BRANCHES: main + WITH_V: true + GIT_API_TAGGING: false diff --git a/.gitea/workflows/pr.yaml b/.gitea/workflows/pr.yaml new file mode 100644 index 0000000..e77c19f --- /dev/null +++ b/.gitea/workflows/pr.yaml @@ -0,0 +1,21 @@ +name: PR Checks + +on: + pull_request: + branches: [main] + +jobs: + hadolint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + - uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5 # v3.3.0 + with: + dockerfile: Dockerfile + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + - uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4 + - run: docker build -t ci-image:${{ github.sha }} . diff --git a/.gitea/workflows/tag.yaml b/.gitea/workflows/tag.yaml new file mode 100644 index 0000000..6bb4d56 --- /dev/null +++ b/.gitea/workflows/tag.yaml @@ -0,0 +1,47 @@ +name: Tag Release + +on: + push: + tags: ['*'] + +jobs: + hadolint: + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + - uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5 # v3.3.0 + with: + dockerfile: Dockerfile + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + - uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4 + - run: docker build -t ci-image:${{ github.sha }} . + + build-push: + needs: [build] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + - uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4 + - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - id: meta + uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6 + with: + images: jcabillot/hermes-agent-webui + tags: | + type=ref,event=tag + type=ref,event=tag,suffix=-latest + - uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + pull: true diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..298a7f9 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,39 @@ +# AGENTS.md + +## 1. Overview + +Container image for Hermes Agent WebUI with a bundled Hermes Agent runtime, distributed via Docker Hub as `jcabillot/hermes-agent-webui`. + +## 2. Folder Structure + +- `Dockerfile`: Multi-stage image build — clones `nousresearch/hermes-agent` and `nesquena/hermes-webui`, installs both in a shared venv, copies to a slim runtime stage. Exposes port `8787`. +- `.gitea/workflows/docker-build.yaml`: Gitea Actions pipeline — builds and pushes the image to Docker Hub on push to `main` and on a daily cron schedule. +- `renovate.json`: Regex-based custom manager tracking upstream `nousresearch/hermes-agent` and `nesquena/hermes-webui` Docker tag updates. +- `README.md`: Public-facing documentation. +- `AGENTS.md`: This file — contributor guide for AI agents. + +## 3. Core Behaviors & Patterns + +- **Build & Release**: The Dockerfile pins two upstream repos via `ARG HERMES_AGENT_VERSION` and `ARG HERMES_WEBUI_VERSION`. On push to `main`, the CI pipeline builds the image, bumps the git tag (`patch`), which triggers a tag release that pushes to `jcabillot/hermes-agent-webui`. A daily cron rebuild pushes `-latest`. +- **Dependency Tracking**: Renovate scans `Dockerfile` for both `ARG` version pins and opens PRs when either upstream publishes a new tag. +- **Multi-stage Build**: Build stage clones repos and creates a venv; runtime stage is a clean `python:3.12-slim` with only the venv and source trees copied over. +- **Health Check**: The container exposes port `8787` with a `/health` endpoint polled every 30s. + +## 4. Conventions + +- **Version Pinning**: Both upstream versions (`HERMES_AGENT_VERSION`, `HERMES_WEBUI_VERSION`) are pinned via `ARG` at the top of the builder stage, managed by renovate. +- **CI Secrets**: `DOCKERHUB_USERNAME`, `DOCKERHUB_TOKEN`, `SA_TOKEN_ACTION_PUSH_TAGS` required. Login skipped on PR events. +- **Dockerfile Style**: Multi-stage with `AS builder` / runtime split. `apt-get` in a single `RUN` with cleanup. `COPY --from=builder` for artifacts. Comments annotate stage boundaries. +- **Pin Hashing**: All GitHub Action versions pinned to commit SHA hashes with semantic version in trailing comment. + +## 5. Working Agreements + +- Respond in the user's preferred language (French or English); keep technical terms in English, never translate code blocks +- Create tests/lint only when explicitly requested +- Build context by reviewing related usages and patterns before editing +- Prefer simple solutions; avoid unnecessary abstraction +- Ask for clarification when requirements are ambiguous +- Make minimal changes; preserve public APIs and behavior +- Run type-check after code changes (no type-checker in this project — skip) +- New files: single-purpose, colocated with related concerns +- External dependencies: only when necessary, explain why diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e097a09 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,94 @@ +# Build stage: clone and install Hermes Agent + WebUI dependencies +FROM python:3.12-slim AS builder + +WORKDIR /build + +# hadolint ignore=DL3008 +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + curl \ + git \ + && rm -rf /var/lib/apt/lists/* + +# Install uv system-wide +# hadolint ignore=DL4006 +RUN curl -LsSf https://astral.sh/uv/install.sh | env UV_INSTALL_DIR=/usr/local/bin sh + +# Clone Hermes Agent at a pinned commit +ARG HERMES_AGENT_VERSION=v2026.6.5 +RUN git clone --depth 1 --branch ${HERMES_AGENT_VERSION} \ + https://github.com/NousResearch/hermes-agent.git /build/hermes-agent + +# Clone Hermes WebUI at a pinned commit +ARG HERMES_WEBUI_VERSION=v0.51.350 +RUN git clone --depth 1 --branch ${HERMES_WEBUI_VERSION} \ + https://github.com/nesquena/hermes-webui.git /build/hermes-webui + +# Create a shared venv and install both projects +# hadolint ignore=DL3059 +RUN uv venv /build/venv +ENV VIRTUAL_ENV=/build/venv +ENV PATH="/build/venv/bin:$PATH" + +# Install hermes-agent with all extras (includes ML/agent deps) +# hadolint ignore=DL3013,DL3059 +RUN uv pip install \ + "/build/hermes-agent[all]" + +# Install hermes-webui deps (pyyaml + cryptography) +# hadolint ignore=DL3059 +RUN uv pip install \ + -r /build/hermes-webui/requirements.txt + +# Install uv in the venv so the webui server can use it for profile/skill management +# hadolint ignore=DL3059 +RUN uv pip install \ + "uv>=0.6.0" + +# Runtime stage +FROM python:3.12-slim + +WORKDIR /app + +# hadolint ignore=DL3008 +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + curl \ + git \ + openssh-client \ + && rm -rf /var/lib/apt/lists/* + +# Copy the virtual environment and source trees from builder +COPY --from=builder /build/venv /opt/venv +COPY --from=builder /build/hermes-agent /opt/hermes-agent +COPY --from=builder /build/hermes-webui /app + +# Set environment +ENV PATH="/opt/venv/bin:$PATH" \ + VIRTUAL_ENV=/opt/venv \ + PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + PYTHONIOENCODING=utf-8 + +# Tell the WebUI where to find the agent +ENV HERMES_WEBUI_AGENT_DIR=/opt/hermes-agent \ + HERMES_WEBUI_HOST=0.0.0.0 \ + HERMES_WEBUI_PORT=8787 \ + HERMES_WEBUI_STATE_DIR=/home/hermes/.hermes/webui \ + HERMES_WEBUI_DEFAULT_WORKSPACE=/workspace \ + HERMES_HOME=/home/hermes/.hermes + +# Create non-root user +RUN useradd --create-home --shell /bin/bash hermes \ + && mkdir -p /workspace \ + && chown -R hermes:hermes /app /opt/venv /opt/hermes-agent /workspace /home/hermes + +USER hermes + +EXPOSE 8787 + +HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \ + CMD curl -f http://localhost:8787/health || exit 1 + +# Run the WebUI server (which runs Hermes Agent in-process) +CMD ["python", "/app/server.py"] diff --git a/README.md b/README.md index 5a51300..d740e32 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,29 @@ # hermes-agent-webui -Hermes Agent WebUI container image \ No newline at end of file +Container image for [Hermes WebUI](https://github.com/nesquena/hermes-webui) with a bundled [Hermes Agent](https://github.com/NousResearch/hermes-agent) runtime. + +## Image + +`jcabillot/hermes-agent-webui` — [Docker Hub](https://hub.docker.com/r/jcabillot/hermes-agent-webui) + +## Usage + +```bash +docker pull jcabillot/hermes-agent-webui +docker run -p 8787:8787 jcabillot/hermes-agent-webui +``` + +Open http://localhost:8787 + +## Build locally + +```bash +docker build -t jcabillot/hermes-agent-webui . +``` + +## Tags + +| Tag | Description | +|-----|-------------| +| `latest` | Latest stable build from `main` | +| `sha-` | Per-commit build | diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..c164b52 --- /dev/null +++ b/renovate.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "customManagers": [ + { + "customType": "regex", + "description": "Track nousresearch/hermes-agent Docker tag pinned in Dockerfile ARG", + "managerFilePatterns": ["/^Dockerfile$/"], + "matchStrings": ["ARG HERMES_AGENT_VERSION=v(?[^\\s]+)"], + "depNameTemplate": "nousresearch/hermes-agent", + "datasourceTemplate": "docker", + "versioningTemplate": "docker", + "extractVersionTemplate": "v^(?.*)$" + }, + { + "customType": "regex", + "description": "Track nesquena/hermes-webui Docker tag pinned in Dockerfile ARG", + "managerFilePatterns": ["/^Dockerfile$/"], + "matchStrings": ["ARG HERMES_WEBUI_VERSION=v(?[^\\s]+)"], + "depNameTemplate": "nesquena/hermes-webui", + "datasourceTemplate": "docker", + "versioningTemplate": "docker", + "extractVersionTemplate": "v^(?.*)$" + } + ] +}