diff --git a/netbird/DOCS.md b/netbird/DOCS.md index 53247d9..512ab11 100644 --- a/netbird/DOCS.md +++ b/netbird/DOCS.md @@ -28,7 +28,12 @@ comparison to installing any other Home Assistant add-on. ## Configuration -You'll see the config file at `/addon_config/*_netbird/config.json` after first boot. +After the first boot you'll see the NetBird configuration in this add-on's own +configuration directory: `/config` as seen from inside the add-on, which Home +Assistant exposes to you as `/addon_configs/xxxxxxxx_netbird`, where +`xxxxxxxx_netbird` is the slug of your installation (visible in the address bar +when the add-on page is open). It holds `config.json`, plus a `profiles` +directory if you use the [`profile`](#option-profile) option. ### Option: `log_level` @@ -75,6 +80,28 @@ Hostname in the NetBird network (used to during registration) This hostname will be used in the Peers to identify your machine. +### Option: `profile` + +Name of the profile to connect with (default: the single, unnamed profile) + +A profile is a separate NetBird configuration: each one keeps its own peer +identity and credentials, so switching the profile makes this client join a +different NetBird network or account. Leave the option empty to keep using the +single default configuration at `config.json`; set it to a name and the client +uses `profiles/.json` instead. Both paths are relative to the add-on +configuration directory described under [Configuration](#configuration). + +A name may be up to 64 characters of letters, digits, `-` and `_`. The name +`null` is not allowed, because the add-on cannot tell it apart from an unset +option. + +A profile that has not been registered yet needs a `setup_key`, or the login URL +printed in the log, the first time it starts. Once registered, a profile keeps +working when you switch away and back again. + +See [Switching profiles from Home Assistant](#switching-profiles-from-home-assistant) +for changing profiles from an automation. + ### Option: `rosenpass` Rosenpass can be enabled by setting a flag on client start-up. @@ -97,6 +124,82 @@ Extra environment variables to pass to the NetBird client. This is a list of environment variables that will be passed to the NetBird client. You can use this to configure the client further. +## Switching profiles from Home Assistant + +Home Assistant can switch profiles by changing the `profile` option and +restarting the add-on. Add the following to `configuration.yaml`, replacing +`xxxxxxxx_netbird` with the slug of your installation (visible in the address bar +when the add-on page is open): + +```yaml +rest_command: + netbird_info: + url: http://supervisor/addons/xxxxxxxx_netbird/info + method: get + headers: + x-supervisor-token: !env_var SUPERVISOR_TOKEN + netbird_set_options: + url: http://supervisor/addons/xxxxxxxx_netbird/options + method: post + content_type: application/json + headers: + x-supervisor-token: !env_var SUPERVISOR_TOKEN + payload: '{{ {"options": options} | to_json }}' + netbird_restart: + url: http://supervisor/addons/xxxxxxxx_netbird/restart + method: post + headers: + x-supervisor-token: !env_var SUPERVISOR_TOKEN + +script: + netbird_switch_profile: + alias: Switch NetBird profile + fields: + profile: + description: Profile to connect with, or empty for the default profile. + example: work + sequence: + - action: rest_command.netbird_info + response_variable: info + - condition: template + value_template: "{{ info.status == 200 }}" + - action: rest_command.netbird_set_options + response_variable: updated + data: + options: "{{ info.content.data.options | combine({'profile': profile}) }}" + - condition: template + value_template: "{{ updated.status == 200 }}" + - action: rest_command.netbird_restart +``` + +Then call `script.netbird_switch_profile` with the profile you want: + +```yaml +action: script.netbird_switch_profile +data: + profile: work +``` + +Keep the `netbird_info` step. The Supervisor **replaces** the stored options with +whatever is posted, so sending only the profile would reset every other option +(including your `setup_key`) to its default. Reading the current options first +and merging the new profile into them avoids that. + +Keep the two `condition` steps as well. A `rest_command` that gets an error +response logs a warning but does not fail the script, so without them a rejected +options update would still be followed by a restart, quietly bringing the add-on +back up on the *old* profile. With them, the script stops instead and the add-on +keeps running untouched. + +Because the whole options map has to be posted back, the payload contains your +`setup_key`. Home Assistant logs that payload at warning level whenever one of +these calls fails, and logs the payload and headers (including the Supervisor +token) if you set `homeassistant.components.rest_command` to `debug`. Treat +those logs as secrets, and scrub them before sharing them. + +Switching restarts the add-on, so the connection drops for a few seconds and all +peers see the machine go offline and come back under its new identity. + ## Changelog & Releases This repository keeps a change log using [GitHub's releases][releases] diff --git a/netbird/config.yaml b/netbird/config.yaml index 09e3543..d2513f7 100644 --- a/netbird/config.yaml +++ b/netbird/config.yaml @@ -27,6 +27,7 @@ options: management_url: "" setup_key: "" hostname: "" + profile: "" rosenpass: false rosenpass_permissive: false env_vars: [] @@ -35,6 +36,7 @@ schema: management_url: str? setup_key: str? hostname: match(^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*)?$)? + profile: match(^(?!null$)([a-zA-Z0-9_-]{1,64})?$)? rosenpass: bool rosenpass_permissive: bool env_vars: diff --git a/netbird/rootfs/etc/s6-overlay/s6-rc.d/netbird/run b/netbird/rootfs/etc/s6-overlay/s6-rc.d/netbird/run index b4ba634..898f9e6 100755 --- a/netbird/rootfs/etc/s6-overlay/s6-rc.d/netbird/run +++ b/netbird/rootfs/etc/s6-overlay/s6-rc.d/netbird/run @@ -11,18 +11,52 @@ declare value # Get the options configured in HASS GUI readonly CONFIG_OLD_PATH=/homeassistant/netbird/config.json -readonly CONFIG_PATH=/config/config.json - -[ -f "${CONFIG_OLD_PATH}" ] && mv "${CONFIG_OLD_PATH}" "${CONFIG_PATH}" +readonly CONFIG_DEFAULT_PATH=/config/config.json +readonly PROFILES_DIR=/config/profiles + +# Migrate the config from the pre-addon_config location, but never on top of an +# existing one: that would discard the current peer identity and credentials. +if [ -f "${CONFIG_OLD_PATH}" ]; then + if [ -f "${CONFIG_DEFAULT_PATH}" ]; then + bashio::log.warning "Ignoring ${CONFIG_OLD_PATH}: ${CONFIG_DEFAULT_PATH} already exists" + elif ! mv "${CONFIG_OLD_PATH}" "${CONFIG_DEFAULT_PATH}"; then + bashio::exit.nok "Could not migrate ${CONFIG_OLD_PATH} to ${CONFIG_DEFAULT_PATH}" + fi +fi admin_url="$(bashio::config 'admin_url')" management_url="$(bashio::config 'management_url')" setup_key="$(bashio::config 'setup_key')" hostname="$(bashio::config 'hostname')" +profile="$(bashio::config 'profile')" rosenpass="$(bashio::config 'rosenpass')" rosenpass_permissive="$(bashio::config 'rosenpass_permissive')" log_level="$(bashio::config 'log_level')" +# Select the profile to run (see issue #423). +# 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. Leaving +# the option empty keeps using the single default config, so existing installs +# are unaffected. +# bashio prints "null" for an option that is not set, and cannot tell that +# apart from the literal string; the schema rejects "null" as a profile name so +# the two can never collide here. +if [ "${profile}" = "" ] || [ "${profile}" = "null" ]; then + bashio::log.info "No profile set, using the default profile" + CONFIG_PATH="${CONFIG_DEFAULT_PATH}" +else + # The profile name becomes part of a file path, so re-validate it here + # instead of relying on the options schema alone. + if [[ ! "${profile}" =~ ^[a-zA-Z0-9_-]{1,64}$ ]]; then + bashio::exit.nok "Invalid profile name: ${profile} (allowed: letters, digits, '-' and '_', up to 64 characters)" + fi + bashio::log.info "Using ${profile} as profile" + mkdir -p "${PROFILES_DIR}" + CONFIG_PATH="${PROFILES_DIR}/${profile}.json" +fi +readonly CONFIG_PATH + # Self-heal a stale NetBird Cloud management URL (see issue #401). # Older installs persisted https://app.netbird.io as the Management URL in # config.json. app.netbird.io only serves the web dashboard and does not diff --git a/netbird/translations/en.yaml b/netbird/translations/en.yaml index 8f7cecc..052ff61 100644 --- a/netbird/translations/en.yaml +++ b/netbird/translations/en.yaml @@ -29,6 +29,20 @@ configuration: description: >- Hostname of the client (default "netbird-client") This is the name of the client that will be displayed in the NetBird dashboard. + profile: + name: Profile + description: >- + Name of the profile to connect with (default: the single, unnamed profile) + + Every profile keeps its own peer identity and credentials in its own + configuration file, so switching the profile makes the add-on join a + different NetBird network or account. Changing this option and restarting + the add-on switches profiles, which also lets Home Assistant automations + switch networks. A profile that has not been registered yet needs a + `setup_key`, or the login URL printed in the log, on its first start. + + Up to 64 characters of letters, digits, `-` and `_`. The name `null` is + not allowed, as it cannot be told apart from an unset option. rosenpass: name: Rosenpass description: >-