Add a Private mode toggle that routes Nexus through the attested local proxy - #43
Add a Private mode toggle that routes Nexus through the attested local proxy#43Marketen wants to merge 3 commits into
Conversation
|
The streaming issue listed under Known issue, not from this PR is now fixed in dappnode-nexus-sdk#4. Root cause was Verified against the live Gateway with a client that closes on Release chain for Private mode is now: dappnode-nexus-sdk#3 + #4 → nexus-local-proxy 0.1.1 → this PR. |
|
Dappnode bot has built and pinned the built packages to an IPFS node, for commit: cca08b6 This is a development version and should only be installed for testing purposes.
Hash: (by dappnodebot/build-action) |
❌ Package harness failed
Run overview
Baseline vs candidate
Hard-check reasons
Container comparison
Container inventoryBaseline
Candidate No container snapshot was available. Runtime analysis advisory⚪ Inconclusive — Log analysis was unavailable
✅ No new runtime findings were reported. Analyzer diagnostics
Cleanup and diagnostics
Deterministic checks decide the verdict. Runtime log analysis is advisory. TropiBot publishes bounded summaries and never posts raw logs. |
d9d2b4c to
0aa3e19
Compare
…l proxy The wizard hardcoded the direct Nexus endpoint, which is the path where TLS terminates at Cloudflare and prompts are readable there. A checkbox on the Nexus configuration screen now switches model.base_url to the nexus-local-proxy package on the same DAppNode, which verifies the Gateway's AWS Nitro attestation against a pinned policy and encrypts bodies with EHBP past that point. It is a toggle in this package rather than a separate package: a fork would double maintenance and split users. The base URL is written in two places -- env.OPENAI_BASE_URL and model.base_url in the generated config.yaml -- and both now go through one nexusBaseUrl() helper so they cannot drift apart. config.yaml is the load-bearing one; the env vars get cleared by config migration on boot. Default is off. The proxy fails closed, so opting in should be a deliberate choice made after reading what it changes rather than something a user discovers when inference stops working. The toggle copy states that trade-off rather than presenting it as free -- a verification failure surfaces as connection errors with no silent fallback to the unprotected path -- and links to the proxy's verification page. No manifest dependency is declared. nexus-local-proxy has never been published to the DAppNode registry: resolving it by name returns NOREPO and it does not appear in a registry search, because only its IPFS artifacts exist and it has been installed by hash. A dependencies entry would therefore fail to resolve at install time and break installing Hermes at all. The toggle copy carries the requirement instead -- it names the package, says this one will not install it, and tells the user to leave the toggle off until they have. Two supporting fixes in patch-config.py: - The context_size lookup was gated on the literal string nexus-api.dappnode.com in base_url. With Private mode on that gate is false, so model.context_length would never be set and every model would silently use Hermes' 256K fallback -- wrong for all of them (Deepseek V4 Flash is 1048576, MiniMax M2.7 is 204800). It now recognises either Nexus route, and falls back to the public catalog when the configured endpoint has no /models, which is the case on nexus-local-proxy releases before 0.1.1. - That lookup has in fact never worked. Cloudflare fronts nexus-api.dappnode.com and 403s the default Python-urllib User-Agent, so the fetch always failed and every Nexus user has been running on the 256K fallback. Upstream Hermes already guards against the same WAF behaviour in providers/base.py. Sending a real User-Agent fixes it; verified live, returning 1048576 and 204800 for the two models above, both directly and through a running proxy. Verified: wizard JS syntax-checked, and buildEnv()/buildConfigYaml() exercised against a DOM stub to confirm both write sites flip together with the toggle while the API key and context_length still land in config.yaml. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
0aa3e19 to
cca08b6
Compare
🛠️ Package harness hit an infrastructure error
Run overview
Baseline vs candidate
Hard-check reasons
Container comparison
Container inventoryBaseline
Candidate No container snapshot was available. Runtime analysis advisory⚪ Inconclusive — Log analysis was unavailable
✅ No new runtime findings were reported. Analyzer diagnostics
Cleanup and diagnostics
Deterministic checks decide the verdict. Runtime log analysis is advisory. TropiBot publishes bounded summaries and never posts raw logs. |
The dependency came out earlier because nexus-local-proxy.dnp.dappnode.eth had no registry entry -- resolving it by name returned NOREPO, so declaring it would have broken installing Hermes at all. It is now shipped as a core package via DNP_CORE, so it is guaranteed present and the dependency resolves. That makes the toggle copy accurate again: it no longer tells the user to go install the proxy themselves, only what routing through it changes and that it fails closed. Requires nexus-local-proxy 0.2.0 and the DNP_CORE release that carries it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This reverts commit 5467e02.
|
Updated. Two commits added on top:
Net effect on this PR is unchanged from before: no manifest dependency, and the toggle copy tells the user to install Related, now open:
|
Rebased onto current
main(8a15e02). The branch previously carried four unrelated local commits —bypass login,seamless no auth ui, and two restart-loop fixes — because it was cut from a localmasterthat had unpushed work. Those are gone; the PR is now a single commit touching 4 files.What
The wizard hardcoded the direct Nexus endpoint, which is the path where TLS terminates at Cloudflare and prompts are readable there. A checkbox on the Nexus configuration screen switches
model.base_urlto the nexus-local-proxy package on the same DAppNode, which verifies the Gateway's AWS Nitro attestation against a pinned policy and encrypts bodies with EHBP past that point.A toggle in this package rather than a separate one: a fork would double maintenance and split users.
The base URL is written in two places —
env.OPENAI_BASE_URLandmodel.base_urlin the generated config.yaml — and both now go through onenexusBaseUrl()helper so they cannot drift apart. config.yaml is the load-bearing one; the env vars get cleared by config migration on boot.Default is off. The proxy fails closed, so opting in should be a deliberate choice made after reading what it changes. The toggle copy states that trade-off rather than presenting it as free, and links to the proxy's verification page.
The new Login-with-Nexus panel from #34 sits above it untouched — that flow only mints an API key and never writes a base URL, so these two write sites remain the only ones.
No manifest dependency is declared.
nexus-local-proxyhas never been published to the DAppNode registry: resolving it by name returnsNOREPO, and a registry search for "nexus" returns nothing — only its IPFS artifacts exist, and it has been installed by hash. Adependenciesentry would fail to resolve at install time and break installing Hermes at all. The toggle copy carries the requirement instead: it names the package, says this one will not install it, and tells the user to leave the toggle off until they have.Supporting fixes in patch-config.py
The
context_sizelookup was gated on the literal stringnexus-api.dappnode.cominbase_url. With Private mode on that gate is false, somodel.context_lengthwould never be set and every model would silently use Hermes' 256K fallback. It now recognises either Nexus route, and falls back to the public catalog when the configured endpoint has no/models.That lookup has never worked. Cloudflare fronts
nexus-api.dappnode.comand 403s the defaultPython-urllib/<ver>User-Agent, so the fetch always failed and every Nexus user has been running on the 256K fallback. Confirmed on a live node: the running agent's config.yaml has nocontext_lengthat all. Upstream Hermes guards against the same WAF behaviour inproviders/base.py. A real User-Agent fixes it — verified returning 1048576 and 204800 for Deepseek V4 Flash and MiniMax M2.7.Verified on a real DAppNode
dnprivate_network, sonexus-local-proxy.dappnode.privateresolves and is reachable from Hermes.buildEnv()/buildConfigYaml()exercised against a DOM stub — default is direct, toggle flips both write sites together, API key andcontext_lengthstill land in config.yaml;patch-config.pyhelpers re-verified live.env.OPENAI_BASE_URL = url) is preserved by the merge.Dependency chain — now unblocked
The streaming bug this PR previously listed as a blocker is fixed in dappnode/dappnode-nexus-sdk#4 (merged). Remaining order:
dappnode-nexus-sdk#3, bump nexus #4— mergednexus-local-proxy#10— merged, but the released v0.1.1 does not contain it. Its published manifest pins5a77ef4; the release workflow started from the bump NousResearch/hermes-agent to v2026.5.7 #9 merge and entrypoint improvements #10 landed eight minutes into that run, while the follow-up run was skipped by the version gate. dappnode/DAppNodePackage-nexus-local-proxy#11 bumps to 0.1.2 from the correctly-pinnedmain.One thing for a maintainer to decide
main'sv2026.7.20+0.1.7against myv2026.7.1+0.1.8. I kept main's upstream bump and set0.1.8. But a 0.1.8 dev build is already installed on at least one node, andreleases.jsonrecords 0.1.0/0.1.1/0.2.0 — the numbering in this repo needs a call.Upstream bump.Re-checked against thev2026.7.20image now thatmainhas moved:CustomProfile.fetch_models()still defers to{base_url}/models,providers/base.pystill resolves the URL the same way, and there is still nocustomentry in the static fallback catalog. The picker rationale holds unchanged, so only the version question below is open.🤖 Generated with Claude Code