From f1bffd0211ae2a3691f60756e4fa8fa0dc2f56be Mon Sep 17 00:00:00 2001 From: Marketen Date: Sat, 6 Jun 2026 05:21:05 +0000 Subject: [PATCH 1/2] bump NousResearch/hermes-agent to v2026.6.5 --- dappnode_package.json | 4 ++-- docker-compose.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dappnode_package.json b/dappnode_package.json index 71670b1..37edfcb 100644 --- a/dappnode_package.json +++ b/dappnode_package.json @@ -1,7 +1,7 @@ { "name": "hermes-agent.dnp.dappnode.eth", - "version": "0.1.4", - "upstreamVersion": "v2026.5.29.2", + "version": "0.1.6", + "upstreamVersion": "v2026.6.5", "upstreamRepo": "NousResearch/hermes-agent", "upstreamArg": "UPSTREAM_VERSION", "shortDescription": "Self-improving AI agent with multi-LLM support and messaging gateway", diff --git a/docker-compose.yml b/docker-compose.yml index 80bcd5d..9ffd14b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,7 +5,7 @@ services: context: . dockerfile: Dockerfile args: - UPSTREAM_VERSION: v2026.5.29.2 + UPSTREAM_VERSION: v2026.6.5 image: hermes-agent.dnp.dappnode.eth:0.1.0 container_name: DAppNodePackage-hermes-agent.dnp.dappnode.eth restart: unless-stopped From 979afa3a05d46fd6f5972e2bc76d039dfd1fc291 Mon Sep 17 00:00:00 2001 From: hcastc00 Date: Mon, 8 Jun 2026 21:49:59 +0200 Subject: [PATCH 2/2] fix: Fix Nexus config for new version --- dappnode/dappnode/SKILL.md | 18 +++++++++++- setup-wizard/index.html | 56 ++++++++++++++++++++++++++++++-------- 2 files changed, 61 insertions(+), 13 deletions(-) diff --git a/dappnode/dappnode/SKILL.md b/dappnode/dappnode/SKILL.md index d955e43..401e8ae 100644 --- a/dappnode/dappnode/SKILL.md +++ b/dappnode/dappnode/SKILL.md @@ -68,7 +68,23 @@ DAppNode Nexus (`https://nexus.dappnode.com`) is DAppNode's own privacy-focused To configure, use the Setup Wizard at `http://hermes-agent.dappnode:8080` and select Nexus as the provider. Or manually: 1. Sign up at `https://nexus.dappnode.com` and create an API key -2. In the Setup Wizard or `config.yaml`, set the provider to Nexus with base URL `https://nexus-api.dappnode.com/v1` +2. Add the key to `/opt/data/.env` as `NEXUS_API_KEY=` +3. Register Nexus as a named provider in `/opt/data/config.yaml` (current Hermes no longer reads `OPENAI_BASE_URL`, and only sends `OPENAI_API_KEY` to `openai.com` hosts, so a named provider is required): + +```yaml +model: + default: "minimax/minimax-m2.7" # browse models at nexus.dappnode.com/models + provider: "nexus" +providers: + nexus: + name: "DAppNode Nexus" + base_url: "https://nexus-api.dappnode.com/v1" + key_env: "NEXUS_API_KEY" + default_model: "minimax/minimax-m2.7" + api_mode: "chat_completions" +``` + +Verify with `hermes doctor`; the resolved provider source should read `custom_provider:DAppNode Nexus`, not `no-key-required`. ## Troubleshooting diff --git a/setup-wizard/index.html b/setup-wizard/index.html index 7ce98a5..4a4b3f3 100644 --- a/setup-wizard/index.html +++ b/setup-wizard/index.html @@ -736,11 +736,14 @@

Configure ${p.name}

const env = {}; const p = selectedProvider; if (p.id === "nexus") { + // Nexus is registered as a named provider in config.yaml (see + // buildConfigYaml). Hermes resolves its key from NEXUS_API_KEY via + // the provider's key_env. We must NOT reuse OPENAI_API_KEY/ + // OPENAI_BASE_URL: recent Hermes no longer reads OPENAI_BASE_URL and + // host-gates OPENAI_API_KEY to openai.com, which silently drops the + // key for the nexus-api.dappnode.com host. const apiKey = (document.getElementById("api-key").value || "").trim(); if (apiKey) env.NEXUS_API_KEY = apiKey; - env.OPENAI_BASE_URL = "https://nexus-api.dappnode.com/v1"; - env.OPENAI_API_KEY = apiKey; - env.LLM_MODEL = getModelValue() || "minimax/minimax-m2.7"; } else if (p.id === "ollama") { const url = (document.getElementById("ollama-url").value || "").trim() || "http://ollama.dappnode:11434"; env.OPENAI_BASE_URL = url + "/v1"; @@ -752,10 +755,11 @@

Configure ${p.name}

const model = (document.getElementById("custom-model-name").value || "").trim(); if (spec === "anthropic") { if (apiKey) env.ANTHROPIC_API_KEY = apiKey; - if (model) env.LLM_MODEL = "anthropic/" + model; } else { - if (apiKey) env.OPENAI_API_KEY = apiKey; - if (model) env.LLM_MODEL = model; + // OpenAI-compatible custom endpoint: registered as a named provider + // in config.yaml with key_env CUSTOM_API_KEY. Don't use + // OPENAI_API_KEY — newer Hermes only sends it to openai.com hosts. + if (apiKey) env.CUSTOM_API_KEY = apiKey; } } else { const apiKey = (document.getElementById("api-key").value || "").trim(); @@ -786,10 +790,25 @@

Configure ${p.name}

"# Edit via this wizard, the Terminal tab, or /opt/data/config.yaml directly.", "", "model:" ]; + // Optional top-level `providers:` mapping for OpenAI-compatible endpoints + // that need an API key. Recent Hermes resolves the key from the env var + // named in `key_env` and no longer reads OPENAI_BASE_URL / non-openai.com + // OPENAI_API_KEY, so a named provider entry is required for hosts like + // Nexus or arbitrary custom endpoints. + const providersBlock = []; if (p.id === "nexus") { - lines.push(` default: "${getModelValue() || "minimax/minimax-m2.7"}"`); - lines.push(` provider: "custom"`); - lines.push(` base_url: "https://nexus-api.dappnode.com/v1"`); + const model = getModelValue() || "minimax/minimax-m2.7"; + lines.push(` default: "${model}"`); + lines.push(` provider: "nexus"`); + providersBlock.push( + "", "# Named provider: DAppNode Nexus (key resolved from NEXUS_API_KEY)", + "providers:", " nexus:", + ` name: "DAppNode Nexus"`, + ` base_url: "https://nexus-api.dappnode.com/v1"`, + ` key_env: "NEXUS_API_KEY"`, + ` default_model: "${model}"`, + ` api_mode: "chat_completions"` + ); } else if (p.id === "ollama") { const url = (document.getElementById("ollama-url").value || "").trim() || "http://ollama.dappnode:11434"; lines.push(` default: "${getModelValue() || "llama3"}"`); @@ -802,17 +821,30 @@

Configure ${p.name}

if (spec === "anthropic") { lines.push(` default: "anthropic/${model || "claude-3-opus-20240229"}"`); lines.push(` provider: "anthropic"`); + if (baseUrl) lines.push(` base_url: "${baseUrl}"`); } else { - lines.push(` default: "${model || "gpt-4"}"`); - lines.push(` provider: "custom"`); + // OpenAI-compatible custom endpoint → named provider so the API key + // (CUSTOM_API_KEY) is sent to the configured host. + const modelName = model || "gpt-4"; + lines.push(` default: "${modelName}"`); + lines.push(` provider: "custom-endpoint"`); + providersBlock.push( + "", "# Named provider: custom OpenAI-compatible endpoint", + "providers:", " custom-endpoint:", + ` name: "Custom Endpoint"`, + ` base_url: "${baseUrl}"`, + ` key_env: "CUSTOM_API_KEY"`, + ` default_model: "${modelName}"`, + ` api_mode: "${spec === "openai-responses" ? "codex_responses" : "chat_completions"}"` + ); } - if (baseUrl) lines.push(` base_url: "${baseUrl}"`); } else { const model = getModelValue(); if (model) lines.push(` default: "${p.id === "openrouter" ? model : p.id + "/" + model}"`); lines.push(` provider: "${p.provider}"`); if (p.id === "openrouter") lines.push(` base_url: "https://openrouter.ai/api/v1"`); } + lines.push(...providersBlock); lines.push("", "# Gateway settings (DAppNode)", "gateway:", " port: 3000", " bind: lan"); lines.push(" controlUi:", " dangerouslyAllowHostHeaderOriginFallback: true"); lines.push(" allowInsecureAuth: true", " dangerouslyDisableDeviceAuth: true");