8000 feat(mesh): Lens as the CA of a Nebula mesh between its machines, with join tokens and a node registry by adeelahmad · Pull Request #546 · adeelahmad/lense · GitHub
[go: up one dir, main page]

Skip to content 8000

feat(mesh): Lens as the CA of a Nebula mesh between its machines, with join tokens and a node registry - #546

Merged
adeelahmad merged 3 commits into
mainfrom
claude/project-thread-6dqeie
Oct 18, 2026
Merged

adeelahmad merged 3 commits into
mainfrom
claude/project-thread-6dqeie

Conversation

@adeelahmad
Copy link
Copy Markdown
Owner

Description

Before: Lens ran on one machine, and a worker on another machine had no private, managed way to reach it.

After: Lens can be the certificate authority of a Nebula mesh (MIT) between the machines it runs on. An admin adds a node with a join token (POST /api/v1/mesh/tokens, shown once). The machine joins with POST /api/v1/mesh/join without ever sending the token: an HMAC keyed with its secret proves it, and the answer comes back sealed with a key derived from it. The machine gets an address, a certificate naming its role (worker or compute) and its namespaces as Nebula groups, and its whole Nebula config. The rendered firewall lets worker nodes reach the database port and every node the API port on the primary, and the primary a node's agent port, and nothing else. A node renews its certificate, reports every minute and refreshes its config with its own node token. Removing a node blocklists every certificate it was given on every machine, with undo while its certificate is good. /api/v1/mesh/nodes lists every machine with the job workers running on it.

How: domain/mesh.py drives nebula-cert (Nebula 1.11.2, pinned with checksums, fetched into <data_dir>/bin on first use). The CA key and token secrets are sealed like other setting secrets; node tokens are stored as hashes. Settings section mesh holds the network, port, hosts, cipher, certificate lifetime, relay, MTU and log level, off until a node is added. Workers record the node they run on (LENS_MESH_NODE).

This is the first of four pull requests (docs/mesh.md). Nebula running on the primary behind an optional Compose profile, the install line that joins a machine, Settings › Network and Nodes in the web app, and running sandboxes on compute nodes follow in their own.

Motivation and Context

Adeel asked for workers and every non-primary node to talk to Lens over its own private network, auto set up and managed, with Settings pages to configure, monitor and debug it, and for the assistant's Bash tools to run sandboxes on remote nodes over that network.

Steps to reproduce (if appropriate):

cd fastapi_backend && uv run pytest tests/api/test_mesh.py -q

The tests use a fake nebula-cert (tests/fake_nebula_cert.py) because CI runners don't have the binary. The same flows were run by hand against Nebula 1.11.2: two machines in separate network namespaces, with configs rendered by mesh.render, pinged each other and opened a TCP connection through the mesh with the firewall as rendered.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change; ! in the title)

Checklist:

  • The title is a Conventional Commit.
  • My code follows the code style of this project (make hooks checks it on commit).
  • I added or updated tests.
  • My change requires documentation updates, and I have updated docs/ accordingly.
  • Users need more than the title to understand it, so I wrote it under ## Unreleased in CHANGELOG.md.

Done questions (AGENTS.md)

  • Goal: this is step one of four; merged on green CI when it is. The rest follows in its own pull requests.
  • Agents: everything is in the REST API under /api/v1/mesh, in the regenerated OpenAPI client.
  • Zero setup: nothing is asked at install, no .env edit. Nebula is fetched on first use.
  • Fewest clicks: one call makes a token and turns the mesh on; joining is one call from the node.
  • Configurable: section mesh with defaults, editable through the settings API; the Settings › Network page comes in PR 3. Nothing in the setup wizard.
  • Light: off by default; single-machine installs never fetch or run Nebula.
  • Private and secure: certificates per machine, token never on the wire, sealed answer, throttled joins, hashed node tokens, blocklist on removal. Threat model: §1.6 mesh.enabled, §1.7 the join, node and admin calls, §1.11 P-MESH.
  • Cost: n/a, no model calls.
  • User decides: only an admin adds or removes a node; defaults unchanged (mesh.enabled off).
  • Upgrades: new tables and a new settings section with defaults only; nothing existing changes shape.
  • Honest: docs and CHANGELOG label it in progress and name what's still to come.
  • Tests, docs and generated files updated (make openapi).
  • Open standards: Nebula is MIT; no new Python dependency.
  • Result reported; "Refine later" in docs/mesh.md.
  • Reuse: Nebula for the mesh, Lens's sealed settings, audit log, throttle and worker registry.

Generated by Claude Code

…h join tokens and a node registry

Lens runs on one machine until an admin adds another. This is the core of the
private network those machines talk over: Lens makes the mesh's certificate
authority the first time a node is added (its key sealed in the database),
signs a certificate per machine naming its role (worker or compute) and the
namespaces it serves as Nebula groups, and renders each machine's Nebula
config, whose firewall lets worker nodes reach the database port and every
node the API port on the primary, and the primary a node's agent port, and
nothing else.

A machine joins with a token shown once to the admin, without ever sending
it: POST /api/v1/mesh/join carries the token's id, a nonce, the machine's
name and public key and an HMAC keyed with the token's secret; the answer
(certificate, config, node token) comes back sealed with a key derived from
that secret. Wrong proofs are throttled per address. After joining, a node
renews its certificate, reports every minute and fetches its config (whose
version changes with the blocklist) with its own node token. Removing a node
blocklists every certificate it was given on every machine and stops its
token; undo while its certificate is good.

Settings section `mesh` (network, port, hosts, cipher, cert_days, relay, mtu,
log level), off until a node is added; the network can't change once machines
have addresses in it. Nebula's release is pinned to one version with its
checksums kept here, fetched on first use. Workers register the node they run
on. Tests fake nebula-cert; the same flows were run against Nebula 1.11.2,
two machines in separate network namespaces reaching each other.

Nebula running on the primary, the install line that joins a machine and the
Settings pages follow in their own pull requests (docs/mesh.md).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTrHPwWHtEvFJFq3M1YpH4
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTrHPwWHtEvFJFq3M1YpH4
… in the server job too

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTrHPwWHtEvFJFq3M1YpH4
@adeelahmad
adeelahmad merged commit fce9d18 into main Oct 18, 2026
12 checks passed
@adeelahmad
adeelahmad deleted the claude/project-thread-6dqeie branch October 18, 2026 06:03
adeelahmad pushed a commit that referenced this pull request Oct 18, 2026
…ay and clipper sign-in as built

Since the last README refresh, #474, #484, #510, #511, #497, #495, #406, #516, #517,
#491, #400 and #506 merged. The README's built list now says the assistant answers
questions about Lens itself; a conversation exports as Markdown, PDF or JSON and its
answers, questions and code copy; a Tools chip picks an answer's tools, limit and
namespaces; sandbox runs open under the answer with downloads, and Settings starts the
gateway; deep research plans, runs and returns a cited report; ready-made MCP servers,
the MCP servers page and the optional containers start from the web app; and the web
clipper signs in or takes an API token. Each has its checks in docs/requirements.md.
In progress lists only open PRs; the Nebula mesh (#546, merged, partly built) moves to
Planned with what's next; the Planned proof item keeps only the Raspberry Pi timing;
the docs index links requirements and checks, and published feeds.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDx7PPUauyjMn1LZs8JAsS
adeelahmad added a commit that referenced this pull request Oct 18, 2026
…ay and clipper sign-in as built (#536)

Since the last README refresh, #474, #484, #510, #511, #497, #495, #406, #516, #517,
#491, #400 and #506 merged. The README's built list now says the assistant answers
questions about Lens itself; a conversation exports as Markdown, PDF or JSON and its
answers, questions and code copy; a Tools chip picks an answer's tools, limit and
namespaces; sandbox runs open under the answer with downloads, and Settings starts the
gateway; deep research plans, runs and returns a cited report; ready-made MCP servers,
the MCP servers page and the optional containers start from the web app; and the web
clipper signs in or takes an API token. Each has its checks in docs/requirements.md.
In progress lists only open PRs; the Nebula mesh (#546, merged, partly built) moves to
Planned with what's next; the Planned proof item keeps only the Raspberry Pi timing;
the docs index links requirements and checks, and published feeds.


Claude-Session: https://claude.ai/code/session_01HDx7PPUauyjMn1LZs8JAsS

Co-authored-by: Adeel Ahmad <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

0