diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index f04fb63..cc9ed45 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -55,31 +55,25 @@ jobs: - name: Test Docker image run: | - CONTAINER_NAME="hermes-webtop-test" - - # Start container with shm-size (required by webtop) and all service ports mapped - docker run -d --name "$CONTAINER_NAME" \ - --shm-size=1gb \ - -p 3000:3000 \ - -p 8888:8888 \ - -p 7352:7352 \ - -p 20128:20128 \ - -p 9119:9119 \ - hermes-webtop:test - - # Wait for Hermes Dashboard to be ready (init scripts run in background) - echo "=== Waiting for Hermes Dashboard to become healthy ===" + CONTAINER_NAME="hermes-webtop" + + # Start container like how it is used - DevProd Parity + NO_LOGS=1 DOCKER_URI=hermes-webtop:test make start + + # Wait for init scripts to finish (port 9119 responding = start-hermes.sh done) + echo "=== Waiting for Hermes to become healthy ===" for i in $(seq 1 30); do if docker exec "$CONTAINER_NAME" curl -s -o /dev/null -w "%{http_code}" http://localhost:9119 2>/dev/null | grep -q "200\|302\|401"; then - echo "Dashboard ready after $((i * 5)) seconds." + echo "Hermes ready after $((i * 5)) seconds." break fi if [ "$i" -eq 30 ]; then - echo "WARNING: Dashboard did not respond within 150 seconds." + echo "WARNING: Hermes did not respond within 150 seconds." fi sleep 5 done + # Run self-check (it has its own 120s polling loop for all ports) echo "=== Running boot-time self-check validation ===" # Execute the self-check script inside the container. # We check the exit status. In headless CI, some warnings might be present, @@ -87,7 +81,7 @@ jobs: # Disable exit-on-error and capture the exit status of the self-check script set +e - docker exec "$CONTAINER_NAME" self-check + docker exec -u abc "$CONTAINER_NAME" self-check SELF_CHECK_STATUS=$? set -e @@ -97,6 +91,9 @@ jobs: if [ "${SELF_CHECK_STATUS}" -eq 2 ]; then echo "FAIL: Docker image smoke test failed; Collecting logs." mkdir -p /tmp/failure-logs + + # Capture docker-compose startup logs without color codes + docker compose logs --no-color > /tmp/failure-logs/docker-compose.log # Service logs (written to /tmp by start scripts) docker exec "$CONTAINER_NAME" sh -c 'cd /tmp && tar cf - *.log' | tar xf - -C /tmp/failure-logs/ diff --git a/Makefile b/Makefile index 07e9782..ee487d1 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,9 @@ start: PUID=$(shell id -u) \ PGID=$(shell id -g) \ docker compose up -d - docker compose logs -f + if [ -z "$$NO_LOGS" ]; then \ + docker compose logs -f; \ + fi stop: docker compose down diff --git a/docker/01-copy-web-ui-stamp.sh b/docker/01-copy-web-ui-stamp.sh deleted file mode 100755 index 0c27d01..0000000 --- a/docker/01-copy-web-ui-stamp.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -# Copy pre-built web UI stamp from image to runtime HERMES_HOME -# This must run synchronously before dashboard starts - -STAMP_SRC="/opt/hermes-prebuilt/web-ui-build-stamp.json" -STAMP_DEST="/config/.hermes/web-ui-build-stamp.json" - -if [ -f "$STAMP_SRC" ]; then - echo "[01-copy-web-ui-stamp] Copying pre-built web UI stamp to $STAMP_DEST" - mkdir -p "$(dirname "$STAMP_DEST")" - cp -f "$STAMP_SRC" "$STAMP_DEST" - chown abc:abc "$STAMP_DEST" -else - echo "[01-copy-web-ui-stamp] WARNING: No pre-built stamp found at $STAMP_SRC" -fi \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile index af92289..f6ee54d 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -2,7 +2,7 @@ ARG HERMES_VERSION="v2026.8.13" ARG OMNIROUTE_VERSION=3.8.49 ARG MODELRELAY_VERSION=1.18.0 ARG OLLAMA_VERSION=0.32.6 -ARG NODE_VERSION=24.18.0 +ARG NODE_VERSION=26.7.0 ARG CODE_SERVER_VERSION=4.131.0 ARG MNEMON_VERSION=0.1.17 ARG TARGETARCH @@ -53,22 +53,11 @@ ARG HERMES_VERSION ENV HERMES_HOME=/config/.hermes RUN curl -fsSL "https://raw.githubusercontent.com/NousResearch/hermes-agent/${HERMES_VERSION}/scripts/install.sh" | bash -s -- --skip-setup && \ rm -rf /var/lib/apt/lists/* && \ - mkdir -p /opt/hermes-prebuilt && \ cd /usr/local/lib/hermes-agent && \ CI=1 npm ci --include=dev --workspace web --silent 2>&1 || \ CI=1 npm install --no-save --include=dev --workspace web --silent 2>&1 && \ CI=1 npm run build -w web --silent 2>&1 && \ - npm cache clean --force && \ - HERMES_HOME=/opt/hermes-prebuilt python3 <<'PYEOF' -import sys -sys.path.insert(0, '/usr/local/lib/hermes-agent') -from hermes_cli.main import _write_web_ui_build_stamp, _workspace_root -from pathlib import Path -web_dir = Path('/usr/local/lib/hermes-agent/web') -project_root = _workspace_root(web_dir) -_write_web_ui_build_stamp(project_root, web_dir) -print('Build-time stamp written to /opt/hermes-prebuilt') -PYEOF + npm cache clean --force # Install ModelRelay and start automatically in the background ARG MODELRELAY_VERSION diff --git a/docker/self-check.sh b/docker/self-check.sh index 811a586..34dd3e5 100755 --- a/docker/self-check.sh +++ b/docker/self-check.sh @@ -319,6 +319,65 @@ else echo " (skipped)" fi +# ── 8. Ollama ─────────────────────────────────────────────────────────────── +section "Ollama" + +if ! should_skip "ollama"; then + # 1. Binary + if command -v ollama >/dev/null 2>&1; then + _ok "Binary" "$(ollama --version 2>/dev/null || echo 'installed')" + else + _fail "Binary" "ollama not in PATH" + fi + + # 2. API responds + OLLAMA_API=$(curl -s --max-time 5 http://localhost:11434/api/tags 2>/dev/null || echo "") + if [ -n "$OLLAMA_API" ]; then + _ok "API" "responding on :11434" + else + _fail "API" "no response from :11434" + fi + + # 3. Model listed — retry up to 15s (server may still be scanning models) + MODEL_LISTED=0 + for _attempt in 1 2 3 4 5; do + MODEL_LISTED=$(ollama list 2>/dev/null | grep -c "nomic-embed-text" || true) + [ -z "$MODEL_LISTED" ] && MODEL_LISTED=0 + [ "$MODEL_LISTED" -gt 0 ] && break + sleep 3 + done + if [ "$MODEL_LISTED" -gt 0 ]; then + _ok "Model" "nomic-embed-text available" + else + # Filesystem fallback: check if model files exist on disk (server may be slow to load) + MODEL_MANIFEST="$HOME/.ollama/models/manifests/registry.ollama.ai/library/nomic-embed-text/latest" + if [ -f "$MODEL_MANIFEST" ]; then + _warn "Model" "nomic-embed-text on disk but not listed by server (loading slow)" + else + _warn "Model" "nomic-embed-text not found on disk" + fi + fi + + # 4. Embedding generation — retry after model check (model may have loaded during retries above) + EMBED_RESULT="" + for _attempt in 1 2 3; do + EMBED_RESULT=$(curl -s --max-time 30 -X POST http://localhost:11434/api/embed \ + -d '{"model":"nomic-embed-text","input":"hello world"}' 2>/dev/null || echo "") + if echo "$EMBED_RESULT" | grep -q '"embeddings"'; then + break + fi + sleep 3 + done + if echo "$EMBED_RESULT" | grep -q '"embeddings"'; then + EMBED_DIM=$(echo "$EMBED_RESULT" | python3 -c "import json,sys; d=json.load(sys.stdin); print(len(d['embeddings'][0]))" 2>/dev/null || echo "?") + _ok "Embedding" "generated (dim=${EMBED_DIM})" + else + _warn "Embedding" "failed to generate embedding (non-critical)" + fi +else + echo " (skipped)" +fi + # ── Summary ────────────────────────────────────────────────────────────────── section "Summary" echo "" @@ -335,7 +394,8 @@ fi echo "" # ── Write JSON report ──────────────────────────────────────────────────────── -cat > "$REPORT_FILE" </dev/null || true +cat > "$REPORT_FILE" < ~/.hermes/logs/socat-9119.log 2>&1 & - nohup hermes dashboard --port 9009 --no-open > ~/.hermes/logs/dashboard.log 2>&1 & + nohup hermes dashboard --port 9009 --no-open --skip-build > ~/.hermes/logs/dashboard.log 2>&1 & # Remind Hermes on Mnemon setup if needed if [ ! -f "$HOME/.hermes/memories/USER.md" ]; then diff --git a/docker/start-omniroute.sh b/docker/start-omniroute.sh index 521ff26..ebc4ca9 100755 --- a/docker/start-omniroute.sh +++ b/docker/start-omniroute.sh @@ -85,7 +85,7 @@ if [ "$INIT_OMNIROUTE" -eq "1" ]; then curl -s -X PUT "http://localhost:20128/api/combos/$COMBO_ID" \ -H "Content-Type: application/json" \ -d '{ - "models": ["oc/deepseek-v4-flash-free","mimocode/mimo-auto","oc/big-pickle"], + "models": ["oc/deepseek-v4-flash-free","oc/big-pickle","opencode-zen/deepseek-v4-flash-free","opencode-zen/hy3-free","opencode-zen/mimo-v2.5-free","opencode-zen/north-mini-code-free","opencode-zen/nemotron-3-ultra-free","opencode-zen/big-pickle"], "strategy": "auto", "config": { "maxRetries": 2,