Repository navigation
feat(mesh): run Nebula on the primary in an optional mesh container - #553
Merged
Merged
Conversation
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
18 of 23 tasks
…sed, and #400 listed as built Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GTrHPwWHtEvFJFq3M1YpH4
…-6dqeie # Conflicts: # README.md
…-6dqeie # Conflicts: # CHANGELOG.md
… in changelog.d Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GTrHPwWHtEvFJFq3M1YpH4
This was referenced Oct 18, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
meshcontainer (profilemesh,mesh/sidecar.pyin 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 withnebula -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/meshshows how it runs underservice, and the primary's row in/api/v1/mesh/nodescarries its report.How:
mesh.Keeperruns whereLENS_MESH_DIRis set, one process at a time on a lease, and asks the upgrade agent once per turn ofmesh.enabled, so an admin who stops the container in Settings is not overruled. The container is the only one withNET_ADMINand/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):
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
meshcontainer:Turning the mesh off stopped Nebula and removed the
lens-meshinterface.Types of changes
!in the title)Checklist:
make hookschecks it on commit).docs/accordingly.## UnreleasedinCHANGELOG.md.Done questions (AGENTS.md)
GET /api/v1/mesh(service), and the services API and MCP tools list themeshprofile.meshsettings as before;LENS_MESH_PORTonly when 4242 is taken, as for the other published ports.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.make openapi).Generated by Claude Code