@subshell-ai/plugin-netbird
v1.0.1
Published
NetBird network plugin for Subshell
Readme
@subshell-ai/plugin-netbird
The NetBird network plugin for Subshell. A type: "network" built-in: it
connects the control-plane host to a NetBird network and publishes Subshell on
it, so a device on that network can reach this server.
The plugin runs commands through host.run. Publishing returns the addresses
made reachable by joining; it doesn't run another command. The host manages
processes, files, credentials, and server configuration. Identity, platforms, exposure and the privileged
install steps live in package.json's subshell block: a page renders "not
available on this platform" and prints the root-requiring install commands without
importing any of this code.
What is unusual about NetBird specifically
- No
needs-privilegestate. Once the daemon is installed, the NetBird CLI authorises callers by kernel peer credentials (claimed for ≥ 0.76), so the server's own unprivileged user may runupandstatus --jsonwithout further grants. The ladder runsnot-installed → daemon-down → needs-login. - Publishing is not a command: joining IS the publish. A join already makes
the machine reachable at its WireGuard address, so the host records the
addresses and admits them to the trusted origins as part of the join itself;
the plugin's "Use this address" press survives only as the fallback for the
rare join whose address had not propagated yet. The plugin's own
unpublishstays a no-op (there is nothing on the machine for it to undo), but the host's half clears the record, removing the plugin contribution from the live trusted-origin registry. An origin independently trusted by another source remains allowed. - The management URL is the daemon's own config: the card has no field for
it. A self-hosted NetBird is set up on the machine: run
netbird setup/netbird upyourself first; this card then reflects and publishes what the daemon says. There used to be a "Management URL (self-hosted only)" settings field, and it was right about the mechanism (it fednetbird up --management-urlat join), but post-join the daemon owns its config, so the card's copy was a dead input that could disagree with what the machine already says. Hosted SaaS is what a barenetbird upuses; the setup key stays, because it is the join credential, not configuration (operator's ruling, 2026-09-16). - Peer names need a nameserver group. The FQDN address is offered alongside the IP, with a hint saying peer names only resolve when the NetBird account has a nameserver group configured (otherwise use the NetBird IP address), linked to the vendor's DNS page because that group is an account-console setting, not something on this machine.
Verified behavior and remaining assumptions
Measured against a live daemon on 2026-09-16 (NetBird 0.66.4). netbird
status --json answers with netbirdIp CIDR-suffixed (verbatim
"100.71.129.37/16"), the version under daemonVersion and cliVersion, and
the machine's name under fqdn with no trailing dot; there is no hostname key
at all. So the reader strips a trailing /prefixlen before accepting an IPv4
(a CIDR value is not a URL host, and rejecting it whole left the card listing no
IP under a hint telling the operator to use the IP), and the identity prefers the
daemon's version over the CLI's. The spellings the two specs guessed (peerIP,
ip, netbirdVersion, version) survive as fallbacks, and the guessed IP order
puts them ahead of the measured one. That read ran as the server's unprivileged
user and answered.
These still rest on claims not verified against a live NetBird (master spec § 10, item § 10.4). The code degrades honestly in each case rather than pretending the shape is settled:
- § 10.4: the peer-credential authorisation behind "no
needs-privilegestate" is unconfirmed as a mechanism (a working unprivilegedstatusis not proof of how it was authorised, andupwas never probed unprivileged). A socket error, a permission refusal, and an unparseable status body are all reported as a genericdaemon-downwith the same sentence; the plugin never guesses which one it saw. - The device-flow output shape is unconfirmed. The interactive join reads a login URL (and a device code if one appears) off the output stream, then aborts, falling back to a status re-read, mirroring the Tailscale interactive join. A run that yields no usable URL and no connection reports the CLI's own failure.
Publish state comes from the host's record
The plugin can only observe joined. The host tracks publication in its own
record, which the plugin can't read. Publishing doesn't change the NetBird
daemon, so subsequent plugin status checks still report joined. See the
implementation notes in src/status.ts.
The manifest's publishImplicit: true tells the host to use its publication
record. The host upgrades joined to published when, and only when, that record
exists. A plugin without the flag (Tailscale, whose serve state is readable)
is never upgraded, so a serve reset from a terminal still shows honestly as
joined. The mechanism is publishStateVisible in
apps/server/api/src/services/network/state.ts.
Licence
Apache-2.0, see LICENSE. (Everything outside apps/server/** is Apache-2.0;
this plugin ships both as a built-in inside the AGPL server binary and as a
permissively-licensed package third parties can build against.)
Icon
icon.png is NetBird's official brand mark, taken from the project's own
repository (netbirdio/netbird, docs/media/logo.png, BSD-3). Icon policy
(operator, 2026-09-16): the vendor's own asset or the letter-monogram
fallback, never a drawing of our own.
