Skip to content

✨ Add profile option to switch NetBird profiles - #426

Open
lfarkas wants to merge 4 commits into
mainfrom
feat/profile-option
Open

✨ Add profile option to switch NetBird profiles#426
lfarkas wants to merge 4 commits into
mainfrom
feat/profile-option

Conversation

@lfarkas

@lfarkas lfarkas commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

Closes #423.

What

Adds a profile add-on option. A NetBird profile is simply its own config file — each one holds a separate peer identity, management URL and credentials — so pointing the client at a different config file switches the whole account/network it joins.

  • empty (default) → /config/config.json, exactly today's behaviour
  • work/config/profiles/work.json

Home Assistant switches networks by changing the option and restarting the add-on. DOCS.md gains a Switching profiles from Home Assistant section with the rest_commands and script to do that from an automation.

Why not netbird profile ...

The upstream profile commands (and netbird up --profile) all dial the daemon over gRPC — client/cmd/up.go calls switchOrCreateProfile before branching into foreground mode. This add-on runs netbird up --foreground-mode, which never starts that socket, so the native commands are unusable without reworking the add-on into daemon mode. Selecting the config file achieves the same result with 25 lines of shell.

Notes for review

  • Schema: match(^([a-zA-Z0-9_-]{1,64})?$)? — the grammar is wrapped in an optional group so the stored "" default still validates. This is the same shape hostname uses after fix: accept empty values for optional url/hostname options #411; a bare match(^[a-zA-Z0-9_-]+$)? would re-break start-at-boot on every install storing the default.
  • Double validation: the run script re-checks the name against the same grammar because that string becomes a filesystem path. This blocks traversal independently of the schema, mirroring what the env_vars loop already does.
  • Legacy migration: the /homeassistant/netbird/config.json/config/config.json move still targets the default path and runs before profile selection, so an upgrading user's old credentials cannot land in a profile file.
  • Docs caveat: Supervisor's options endpoint replaces persisted options, so the documented automation reads /info and merges before posting. A partial payload would silently reset setup_key and friends to their defaults.
  • No version bump: config.yaml version: tracks the NetBird release and must match the Dockerfile tag, so this rides the next Renovate bump.

Verification

shellcheck and bash -n clean; both YAMLs parse; every schema key has a translation.

Two test harnesses were run locally (not committed — this repo has no test suite):

  1. The real run script, with only its absolute paths redirected into a temp dir and bashio/netbird stubbed, asserting the resulting --config value. 10/10 pass: unset → default config, null → default config, named profile → profiles dir, and ../evil, a/b, /etc/shadow, a b, 65 chars all abort without invoking netbird. Includes a case asserting the legacy migration still lands in the default config when a profile is set.
  2. The schema entry checked with RE_SCHEMA_ELEMENT lifted verbatim from Supervisor's apps/options.py plus vol.Match semantics. 11/11 pass, confirming "" is accepted and ../evil, a/b, a.json, non-ASCII are rejected.

Not verified locally: yamllint and the add-on linter (no container registry access from my environment) — CI is the first real check. No live HAOS test, since that needs a built test image.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added support for separate NetBird profiles with independent peer identities and credentials.
    • Profile names support letters, numbers, hyphens, and underscores, up to 64 characters.
    • Profiles can be switched after restarting the add-on to connect to different networks.
    • Unregistered profiles support first-time setup with a setup key or login URL.
    • Existing installations continue using the default profile when none is selected.
  • Documentation

    • Added configuration guidance and Home Assistant automation instructions.
    • Documented temporary disconnection during switching and profile option reset behavior.

Closes #423.

A NetBird profile is simply its own config file: each one holds a separate
peer identity, management URL and credentials. The add-on hardcoded a single
config path, so it could only ever join one network.

Add a `profile` option that selects `/config/profiles/<name>.json`. Leaving
it empty keeps the existing `/config/config.json`, so current installs are
unaffected. Home Assistant can switch networks by changing the option and
restarting the add-on; DOCS.md shows the rest_command/script to do that.

The upstream `netbird profile` commands are not an option here: they dial the
daemon over gRPC, which `netbird up --foreground-mode` never starts.

The schema wraps the name grammar in an optional group so the stored empty
default still validates (same shape as `hostname` after #411), and the run
script re-validates the name before it becomes a file path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 8, 2026 13:53
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d3340758-468f-413e-9d3a-12d5d32a0e3b

📥 Commits

Reviewing files that changed from the base of the PR and between a0e61ce and 0ad3940.

📒 Files selected for processing (3)
  • netbird/DOCS.md
  • netbird/rootfs/etc/s6-overlay/s6-rc.d/netbird/run
  • netbird/translations/en.yaml
🚧 Files skipped from review as they are similar to previous changes (3)
  • netbird/translations/en.yaml
  • netbird/rootfs/etc/s6-overlay/s6-rc.d/netbird/run
  • netbird/DOCS.md

📝 Walkthrough

Walkthrough

The add-on adds a validated profile option. Startup selects the default or a profile-specific NetBird configuration. Documentation describes profile behavior and Home Assistant commands for switching profiles.

Changes

Profile selection

Layer / File(s) Summary
Profile configuration contract
netbird/config.yaml, netbird/translations/en.yaml, netbird/DOCS.md
Adds the optional profile field, validates supported names, and describes profile-specific credentials and first-use authentication.
Profile runtime paths
netbird/rootfs/etc/s6-overlay/s6-rc.d/netbird/run
Migrates legacy configuration to the default path. Selects the default path for empty profiles or a validated path under /config/profiles for named profiles.
Home Assistant profile switching
netbird/DOCS.md
Describes profile behavior and provides Home Assistant commands that preserve existing options, validate responses, set a profile, and restart the add-on.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: mlsmaycon, pascal-fischer

Poem

I’m a rabbit with profiles to spare,
Each keeps its credentials with care.
Home Assistant picks one with a hop,
Then restarts NetBird at the top.
New paths bloom where old settings lay.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary change: adding a profile option to switch NetBird profiles.
Linked Issues check ✅ Passed The changes implement multiple NetBird profiles and Home Assistant-driven profile switching as requested in issue #423.
Out of Scope Changes check ✅ Passed The documentation, validation, migration, and runtime changes directly support the profile-switching feature in issue #423.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/profile-option

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a Home Assistant add-on option to select a NetBird “profile” by switching which config JSON file netbird up uses, enabling HA automations to swap identities/networks via an option change + add-on restart.

Changes:

  • Add profile option to the add-on schema and UI translation.
  • Update the s6 run script to resolve --config to either the default config or a per-profile config under /config/profiles/<name>.json.
  • Document profile usage and provide an example HA rest_command + script workflow for switching profiles safely.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
netbird/config.yaml Adds profile option + schema validation for profile names.
netbird/rootfs/etc/s6-overlay/s6-rc.d/netbird/run Selects the config path based on profile and passes it via --config.
netbird/DOCS.md Documents the new option and shows how to switch profiles from HA automations.
netbird/translations/en.yaml Adds UI strings for the new profile option.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread netbird/config.yaml Outdated
bashio prints "null" both for an option that is not set and for the literal
string, and cannot tell them apart — even bashio::config.exists conflates the
two. A profile named "null" would therefore have silently fallen back to the
default config and joined the wrong network with no error.

The script cannot resolve this, so exclude the name at the schema level, which
is the layer that can. Only the exact lowercase string is excluded: "nullx",
"null_1" and "NULL" stay valid, matching the script's case-sensitive compare.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (1)
netbird/rootfs/etc/s6-overlay/s6-rc.d/netbird/run (1)

45-47: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Use the supported NetBird config path for this image.

netbirdio/netbird:0.74.6 supports netbird up --config, but that flag is deprecated and emits a CLI warning. Use NB_CONFIG="${CONFIG_PATH}" for this netbird up invocation or configure netbird service run/reconfigure with the chosen profile path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@netbird/rootfs/etc/s6-overlay/s6-rc.d/netbird/run` around lines 45 - 47,
Update the netbird up invocation in the run script to use the supported
NB_CONFIG environment variable set to CONFIG_PATH, avoiding the deprecated
--config flag and its warning while preserving the selected profile
configuration.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@netbird/config.yaml`:
- Line 39: Update netbird/config.yaml at lines 39-39 to reject the literal
profile value null, preserving only the intended unnamed/default representation.
Update netbird/rootfs/etc/s6-overlay/s6-rc.d/netbird/run at lines 34-40 so only
that unnamed representation selects CONFIG_DEFAULT_PATH; allow the literal null
to resolve to /config/profiles/null.json.

In `@netbird/DOCS.md`:
- Around line 82-87: Update the profile-name documentation near the
allowed-character description to state that names may be at most 64 characters
long, matching the schema and runtime validation.
- Around line 85-87: Update the configuration-path documentation in DOCS.md and
any matching external NetBird documentation to use Home Assistant’s mapped
paths: `/addon_configs/netbird-netbird` on the host and `/config` inside the
add-on. Replace references to `/addon_config/*_netbird`, including the profile
directory, while preserving the existing profile-name and filename guidance.
- Around line 130-143: Update the Supervisor REST command definitions
netbird_set_options and netbird_restart (and the preceding related command) so
their authorization headers send the required Bearer-formatted token rather than
the raw SUPERVISOR_TOKEN value. Ensure the configured environment variable or
supported request mechanism produces “Authorization: Bearer <SUPERVISOR_TOKEN>”
for every command.
- Around line 153-157: Update the documented netbird_set_options usage in both
referenced sections so the REST payload does not include setup_key, using a
secret-safe update path; alternatively, explicitly document the Home Assistant
debug/secure-log risk before users share or enable this script.
- Around line 153-158: Update the automation sequence around netbird_info and
netbird_set_options to capture the options update response in a response
variable, then validate both the information lookup and options update succeeded
before invoking netbird_restart. Keep the restart skipped whenever either call
fails, preventing activation of an unchanged profile.

In `@netbird/rootfs/etc/s6-overlay/s6-rc.d/netbird/run`:
- Line 17: Update the legacy migration command in the run script so it moves
CONFIG_OLD_PATH to CONFIG_DEFAULT_PATH only when the destination does not
already exist, preserving the current profile. Handle and report migration
failures explicitly instead of silently ignoring mv errors.

---

Nitpick comments:
In `@netbird/rootfs/etc/s6-overlay/s6-rc.d/netbird/run`:
- Around line 45-47: Update the netbird up invocation in the run script to use
the supported NB_CONFIG environment variable set to CONFIG_PATH, avoiding the
deprecated --config flag and its warning while preserving the selected profile
configuration.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a05014fb-134f-4d09-ac7d-d540822c2cb9

📥 Commits

Reviewing files that changed from the base of the PR and between 59ed475 and 9961802.

📒 Files selected for processing (4)
  • netbird/DOCS.md
  • netbird/config.yaml
  • netbird/rootfs/etc/s6-overlay/s6-rc.d/netbird/run
  • netbird/translations/en.yaml

Comment thread netbird/config.yaml Outdated
Comment thread netbird/DOCS.md Outdated
Comment thread netbird/DOCS.md Outdated
Comment thread netbird/DOCS.md Outdated
Comment thread netbird/DOCS.md
Comment thread netbird/DOCS.md
Comment thread netbird/rootfs/etc/s6-overlay/s6-rc.d/netbird/run Outdated
lfarkas and others added 2 commits August 8, 2026 16:26
The pre-addon_config migration moved /homeassistant/netbird/config.json with an
unconditional mv, so if both files existed it overwrote the current default
config — discarding the live peer identity and credentials.

Skip the move when the destination exists (warning, legacy file left in place
for manual recovery) and fail loudly if the move itself fails, rather than
starting up as an unregistered peer.

Pre-existing behaviour, reported by CodeRabbit on #426 and fixed here because
this change already touches that line.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
From the #426 review:

- Use the X-Supervisor-Token header instead of Authorization. A raw token in
  Authorization does work (Supervisor takes the last space-separated field),
  but this is the purpose-built header and removes the ambiguity.
- Guard both calls on a 200 before restarting. A rest_command that receives an
  error response logs a warning and does not fail the script, so a rejected
  options update was still followed by a restart that came back on the old
  profile.
- Warn that the posted payload carries setup_key. Home Assistant logs it at
  warning level when a call fails, and logs headers too (including the token)
  at debug level.
- Document the 64-character limit and the reserved name null.
- Describe the add-on config directory correctly: /config inside the add-on,
  exposed as /addon_configs/<slug> (plural, one directory per slug).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@lfarkas

lfarkas commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@PizzaLovingNerd

Copy link
Copy Markdown

I tested this on Home Assistant, and I think the current profile UX is a little confusing and makes it too easy to configure the wrong profile accidentally.

When I change profile, the other fields (admin_url, management_url, setup_key, and hostname) keep the values from the previously selected profile. Those values are then passed to NetBird when the add-on starts with the new profile. There is no indication whether the selected profile already exists, and the form cannot show the settings stored in that profile’s JSON file.

For example, I can configure a work profile, change the name to personal, and accidentally create or update personal using the URLs, setup key, and hostname left over from work. The description makes those fields sound profile-specific, but Home Assistant actually stores them as one global set of add-on options.

Would it be safer to treat these fields as enrollment settings only? If the selected profile already exists, the add-on could ignore them and use the values in that profile’s configuration file. If the profile does not exist, it could require a specific create option before applying them. That would make switching profiles safe even though Home Assistant’s standard add-on form cannot dynamically load or clear fields when the profile name changes.

@TechHutTV

Copy link
Copy Markdown

@lfarkas any update here?

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.

Feature Request: change profiles exposed to HA

4 participants