8000 feat(mesh): run Nebula on the primary in an optional mesh container by adeelahmad · Pull Request #553 · adeelahmad/lense · GitHub
[go: up one dir, main page]

Skip to content

feat(mesh): run Nebula on the primary in an optional mesh container - #553

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

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

Conversation

@adeelahmad
Copy link
Copy Markdown
Owner

Description

Before: Lens could be the mesh's certificate authority and render every machine's Nebula config (#546), but nothing ran Nebula on the primary, so no node could reach it.

After: when an admin adds a node, the machine's upgrade agent starts the optional mesh container (profile mesh, mesh/sidecar.py in the plain Python image). It makes this machine's Nebula key pair itself, so the private key stays in its own volume. It publishes the public key in the folder it shares with Lens's server; Lens's keeper signs it and writes the config back. The container checks each config with nebula -test, reloads Nebula on SIGHUP when it can (a removed node's certificate on the blocklist, the firewall) and restarts it when it must. It relays port 8000 on the mesh address to the database and 8080 to the API, for the machines Nebula's firewall lets in. Turning the mesh off stops it. GET /api/v1/mesh shows how it runs under service, and the primary's row in /api/v1/mesh/nodes carries its report.

How: mesh.Keeper runs where LENS_MESH_DIR is set, one process at a time on a lease, and asks the upgrade agent once per turn of mesh.enabled, so an admin who stops the container in Settings is not overruled. The container is the only one with NET_ADMIN and /dev/net/tun: everything else dropped, read-only, 256 MB. It fetches Nebula's release with the same pinned checksums as Lens (a test keeps the two lists equal). mesh/ is its own Nx project, so a change to the sidecar runs its tests.

Steps to reproduce (if appropriate):

cd fastapi_backend && uv run pytest tests/api/test_mesh_service.py -q
sh packaging/host/test-services.sh

CI uses a stand-in Nebula. The same flow ran here with Nebula 1.11.2 in Docker, with the real keeper writing the config into a real mesh container:

Machine Database (8000) API (8080)
worker node reached reached
compute node dropped by the firewall reached
removed node no handshake no handshake

Turning the mesh off stopped Nebula and removed the lens-mesh interface.

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: step two of the mesh. The install line that joins a machine, Settings › Network and Nodes, and sandboxes on compute nodes follow.
  • Agents: GET /api/v1/mesh (service), and the services API and MCP tools list the mesh profile.
  • Zero setup: adding a node starts the container through the upgrade agent; without one, Settings shows the line to run.
  • Fewest clicks: nothing to do beyond adding a node.
  • Configurable: mesh settings as before; LENS_MESH_PORT only when 4242 is taken, as for the other published ports.
  • Light: the profile is off by default; single-machine installs never start it or fetch Nebula.
  • Private and secure: only this container holds NET_ADMIN; its own key pair; checksum-checked Nebula; relays on the mesh address only. Threat model: §1.7 new entry point, P-MESH extended.
  • Cost: n/a, no model calls.
  • User decides: only adding a node starts it; defaults unchanged.
  • Upgrades: new volumes only; nothing stored changes shape.
  • Honest: docs and CHANGELOG still label the mesh in progress.
  • Tests, docs and generated files updated (make openapi).
  • Open standards: Nebula (MIT); the plain Python image; no new dependency.
  • Result reported with evidence; "Refine later" in docs/mesh.md.
  • Reuse: the services profiles and upgrade agent, the runner-with-lease pattern, the matterbridge shared-folder pattern.

Generated by Claude Code

The primary's half of the mesh: Lens's server can't create network devices,
so Nebula runs in a container of its own, `mesh` (mesh/sidecar.py in the
plain Python image), the only one holding NET_ADMIN and /dev/net/tun. It
makes this machine's key pair itself and keeps the private key in its own
volume; it puts the public key in the folder it shares with Lens's server,
and the keeper (mesh.Keeper, one process at a time on a lease) signs it and
writes the config back. The container checks each config with `nebula
-test`, reloads Nebula on SIGHUP when it can (the blocklist, the firewall)
and restarts it when it must, and relays port 8000 on the mesh address to
the database and 8080 to the API, for the machines Nebula's firewall lets
in. It fetches Nebula's release with the same pinned checksums as Lens.

Adding a node asks the upgrade agent to start the container (profile
`mesh`), and turning the mesh off stops it; GET /api/v1/mesh shows how it
runs under `service`, and the primary's row carries its report. Default
installs are unchanged: the profile is off and nothing listens.

Tested with a stand-in Nebula in CI, and with Nebula 1.11.2 in Docker: a
worker node reached the database and the API through the container, a
compute node the API only, and a removed node nothing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTrHPwWHtEvFJFq3M1YpH4
…uest

Co-Authored-By: Claude Opus 5.5 (1M context) <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
…, by pre-commit too

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTrHPwWHtEvFJFq3M1YpH4
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