Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ In **Plugins → Install Plugin**, paste:
https://raw.githubusercontent.com/netbirdio/netbird-unraid/main/plugin/netbird.plg
```

Then open **Settings → Netbird** to configure.
Then open **Settings → Netbird**, enter a setup key, and select **Enable
NetBird: Yes**. Fresh installations remain disabled until that first successful
configuration.

## What you get

- The official upstream `netbird` Linux binary at `/usr/local/sbin/netbird`
- An rc.d service (`/etc/rc.d/rc.netbird`) that survives reboots
- An rc.d service (`/etc/rc.d/rc.netbird`) that survives reboots once enabled
- Dynamix WebGUI pages: **Settings**, **Status**, **Info** + a Dashboard tile
- Persistent identity and config on the USB flash drive, so reboots don't
forget who this peer is
Expand All @@ -34,8 +36,8 @@ plain init, so this plugin:
1. Downloads the upstream tarball from `github.com/netbirdio/netbird/releases`,
pinned by SHA256.
2. Drops the binary at `/usr/local/sbin/netbird`.
3. Runs `netbird service run` from `/etc/rc.d/rc.netbird` (foreground daemon,
logging to `/var/log/netbird.log`).
3. Once enabled, runs `netbird service run` from `/etc/rc.d/rc.netbird`
(foreground daemon, logging to `/var/log/netbird.log`).
4. Symlinks NetBird's two state paths onto the flash drive so the identity
survives Unraid's ephemeral rootfs:

Expand All @@ -44,8 +46,8 @@ plain init, so this plugin:
| `/etc/netbird` | `/boot/config/plugins/netbird/etc` |
| `/var/lib/netbird` | `/boot/config/plugins/netbird/lib` |

5. Adds an array-start event hook so the daemon comes up whenever the array
starts.
5. Adds an array-start event hook so an enabled daemon comes up whenever the
array starts. Disabled installations remain stopped.

## Notes

Expand Down
2 changes: 1 addition & 1 deletion plugin/ca_template.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Features:
- Installs the upstream NetBird Linux binary natively
- Persistent config and state on the USB flash drive
- Settings, Status, and Info pages under Network Services
- Survives reboots; auto-starts with the array
- Survives reboots; once enabled, auto-starts with the array
</Description>
<Icon>https://raw.githubusercontent.com/netbirdio/netbird-unraid/main/src/usr/local/emhttp/plugins/netbird/netbird.png</Icon>
<Category>Network: Tools: Productivity:</Category>
Expand Down
72 changes: 55 additions & 17 deletions plugin/netbird.plg
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,27 @@ Older releases: https://github.com/netbirdio/netbird-unraid/releases
<FILE Run="/bin/bash">
<INLINE>
<![CDATA[
# Capture the pre-install service state before replacing the in-RAM binary and
# package. Fresh installations default to disabled; upgrades preserve the old
# enabled/disabled behavior. Releases before this migration treated a missing
# persistent cfg as enabled, so migrate that specific upgrade case to an
# explicit enabled cfg.
NETBIRD_EXISTING=0
NETBIRD_HAD_CFG=0
NETBIRD_ENABLE_AFTER_INSTALL=0
[ -x /usr/local/sbin/netbird ] && NETBIRD_EXISTING=1
if [ -f /boot/config/plugins/netbird/netbird.cfg ]; then
NETBIRD_HAD_CFG=1
ENABLE_NETBIRD=1
# shellcheck disable=SC1091
. /boot/config/plugins/netbird/netbird.cfg
if [ "$ENABLE_NETBIRD" != "0" ] && [ "$ENABLE_NETBIRD" != "false" ]; then
NETBIRD_ENABLE_AFTER_INSTALL=1
fi
elif [ "$NETBIRD_EXISTING" = "1" ]; then
NETBIRD_ENABLE_AFTER_INSTALL=1
fi

# Clean any prior in-RAM install of plugin pages
if [ -d "/usr/local/emhttp/plugins/netbird" ]; then
rm -rf /usr/local/emhttp/plugins/netbird
Expand All @@ -157,6 +178,16 @@ fi
# Install plugin support package (pages, rc.d script, default config, etc.)
upgradepkg --install-new /boot/config/plugins/netbird/unraid-netbird-utils-]]>&version;<![CDATA[-noarch-1.txz

# Preserve the historical enabled default for existing installations that have
# never written netbird.cfg. New installations intentionally leave the cfg
# absent and inherit the new packaged disabled default.
if [ "$NETBIRD_EXISTING" = "1" ] && [ "$NETBIRD_HAD_CFG" = "0" ]; then
mkdir -p /boot/config/plugins/netbird
cp /usr/local/emhttp/plugins/netbird/default.cfg /boot/config/plugins/netbird/netbird.cfg
sed -i 's/^ENABLE_NETBIRD=.*/ENABLE_NETBIRD="1"/' /boot/config/plugins/netbird/netbird.cfg
echo " migrated existing installation to explicit ENABLE_NETBIRD=1"
fi

# Extract NetBird upstream binary into /usr/local/sbin
mkdir -p /usr/local/sbin
tar -xzf /boot/config/plugins/netbird/]]>&netbirdFile;<![CDATA[ -C /tmp/
Expand Down Expand Up @@ -198,23 +229,30 @@ NEOF
echo " wrote $NEC with include_interfaces=\"wt0\""
fi

# Restart the daemon synchronously (waits for the socket), then reload nginx as
# soon as wt0 appears (so the WebGUI binds to the NetBird IP and is reachable
# from other peers). "restart" (not "start") matters on upgrades: the old daemon
# is still running the previous binary, so a plain "start" would no-op on it and
# `netbird status` would keep reporting the old version until the next reboot.
# stop is a safe no-op on a clean install where nothing is running yet.
/etc/rc.d/rc.netbird restart
(
for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15; do
if ip -4 addr show wt0 >/dev/null 2>&1; then
/etc/rc.d/rc.nginx reload >/dev/null 2>&1
exit 0
fi
sleep 2
done
) >/dev/null 2>&1 </dev/null &
disown 2>/dev/null || true
# On upgrades or reinstalls with preserved settings, restart an enabled daemon
# synchronously so it runs the newly installed binary. Keep a saved disabled
# state stopped. A truly fresh install (no old binary or cfg) remains stopped
# until the user enables it with valid profile credentials; apply.sh handles the
# first nginx reload once wt0 appears.
if [ "$NETBIRD_EXISTING" = "1" ] || [ "$NETBIRD_HAD_CFG" = "1" ]; then
if [ "$NETBIRD_ENABLE_AFTER_INSTALL" = "1" ]; then
/etc/rc.d/rc.netbird restart
(
for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15; do
if ip -4 addr show wt0 >/dev/null 2>&1; then
/etc/rc.d/rc.nginx reload >/dev/null 2>&1
exit 0
fi
sleep 2
done
) >/dev/null 2>&1 </dev/null &
disown 2>/dev/null || true
else
/etc/rc.d/rc.netbird stop
fi
else
echo " NetBird installed disabled; configure and enable it from Settings"
fi

# Clean up older versions on flash
rm -f $(ls /boot/config/plugins/netbird/unraid-netbird-utils-*.txz 2>/dev/null | grep -v ']]>&version;<![CDATA[')
Expand Down
33 changes: 32 additions & 1 deletion scripts/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,38 @@ if command -v makepkg >/dev/null 2>&1; then
/sbin/makepkg -l y -c n "${DIST_DIR}/${PKG_FILE}"
else
echo "(makepkg not found; producing tar.xz with the same layout)"
tar --owner=0 --group=0 -cJf "${DIST_DIR}/${PKG_FILE}" .
# Passing "." to tar prefixes every payload member with "./". Slackware's
# installpkg repairs those names in its file manifest, but upgradepkg's
# temporary install-script relocation only matches "install/...". The
# result is a silently skipped doinst.sh on upgrades. Archive the top-level
# entries by name so install/doinst.sh is recognized in both install modes.
(
shopt -s dotglob nullglob
package_entries=(*)
if [ "${#package_entries[@]}" -eq 0 ]; then
echo "ERROR: package staging directory is empty" >&2
exit 1
fi
tar --owner=0 --group=0 -cJf "${DIST_DIR}/${PKG_FILE}" "${package_entries[@]}"
)
fi

# Guard the upgrade-sensitive package layout regardless of which builder was
# used. A Slackware package may contain the root member "./", but payload paths
# (especially install/doinst.sh) must not carry that prefix.
package_has_doinst=0
while IFS= read -r member; do
if [ "$member" = "install/doinst.sh" ]; then
package_has_doinst=1
elif [[ "$member" == ./* && "$member" != "./" ]]; then
echo "ERROR: invalid Slackware package member '$member' (unexpected ./ prefix)" >&2
exit 1
fi
done < <(tar -tJf "${DIST_DIR}/${PKG_FILE}")

if [ "$package_has_doinst" -ne 1 ]; then
echo "ERROR: package is missing install/doinst.sh at the archive root" >&2
exit 1
fi

cd "${repo_root}"
Expand Down
3 changes: 2 additions & 1 deletion src/install/doinst.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ fi
chmod 0644 etc/logrotate.d/netbird
chown root:root etc/logrotate.d/netbird

# Event hooks: restart daemon when array starts; stop on shutdown
# Event hooks: reconcile the daemon when the array starts (rc.netbird keeps a
# disabled installation stopped).
( cd usr/local/emhttp/plugins/netbird/event
rm -f array_started stopped started stopping_svcs
ln -sf ../restart.sh array_started
Expand Down
13 changes: 8 additions & 5 deletions src/usr/local/emhttp/plugins/netbird/Netbird-1-Settings.page
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ $plugin = 'netbird';
$cfg = function_exists('parse_plugin_cfg') ? parse_plugin_cfg($plugin) : [];

// Global daemon options live in netbird.cfg.
$enabled = $cfg['ENABLE_NETBIRD'] ?? '1';
$enabled = $cfg['ENABLE_NETBIRD'] ?? '0';
$enableSsh = $cfg['ENABLE_SSH'] ?? '0';
$manageDns = $cfg['MANAGE_DNS'] ?? '1';
$logLevel = $cfg['LOG_LEVEL'] ?? 'info';
Expand Down Expand Up @@ -85,7 +85,9 @@ _(Enable NetBird)_:
</select>

<blockquote class="inline_help">
Start the NetBird daemon automatically when the array starts. Disabling stops the service on the next apply.
Start the NetBird daemon automatically when the array starts. Fresh installations
remain stopped until NetBird is enabled and configured. Disabling stops the
service on the next apply.
</blockquote>

_(Editing Profile)_:
Expand Down Expand Up @@ -202,12 +204,13 @@ function nbViewProfile(name) {
function nbSaveSettings() {
var profile = $('#nb-edit-profile').val();
var form = document.getElementById('nb-settings-form');
var enableNetBird = $('select[name=ENABLE_NETBIRD]', form).val();
// A setup key is required only when registering: a profile that has no key
// stored yet (initial setup). Reconnecting an already-registered profile
// doesn't need one. (A management-URL/hostname change also re-registers; the
// server enforces that case and returns a clear error if the key is missing.)
var nbRequireKey = <?= empty($setupKey) ? 'true' : 'false' ?>;
if (nbRequireKey && !($('input[name=SETUP_KEY]', form).val() || '').trim()) {
if (enableNetBird === '1' && nbRequireKey && !($('input[name=SETUP_KEY]', form).val() || '').trim()) {
swal({ title: 'Setup key required',
text: 'Enter a setup key from your NetBird dashboard to register this profile. Interactive SSO login is not supported.',
type: 'warning' });
Expand All @@ -216,7 +219,7 @@ function nbSaveSettings() {
var data = {
action: 'save',
name: profile,
ENABLE_NETBIRD: $('select[name=ENABLE_NETBIRD]', form).val(),
ENABLE_NETBIRD: enableNetBird,
ENABLE_SSH: $('select[name=ENABLE_SSH]', form).val(),
MANAGE_DNS: $('select[name=MANAGE_DNS]', form).val(),
LOG_LEVEL: $('select[name=LOG_LEVEL]', form).val(),
Expand Down Expand Up @@ -250,7 +253,7 @@ function nbPollApply(profile, since, triesLeft) {
var done = res && res.profile === profile && res.ts >= since && (res.ok === true || res.ok === false);
if (done) {
swal(res.ok
? { title: 'Connected', text: res.message || 'Applied.', type: 'success' }
? { title: 'Settings applied', text: res.message || 'Applied.', type: 'success' }
: { title: 'Apply failed', text: res.message || 'Could not apply settings.', type: 'error' });
setTimeout(function(){ nbReloadTo(profile); }, 1500);
return;
Expand Down
44 changes: 33 additions & 11 deletions src/usr/local/emhttp/plugins/netbird/Netbird_dashboard.page
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,47 @@ try {
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
require_once "{$docroot}/plugins/netbird/include/common.php";

$cfg = Netbird\readCfg();
$enableCfg = strtolower(trim((string) ($cfg['ENABLE_NETBIRD'] ?? '0')));
$enabled = !in_array($enableCfg, ['0', 'false'], true);
$running = Netbird\daemonRunning();
$status = Netbird\statusJson();
// A disabled dashboard tile should be quiet even if a stop is still in
// flight. Avoid asking the daemon for status until the service is enabled.
$status = ($enabled && $running) ? Netbird\statusJson() : null;
$ip4 = $status['netbirdIp'] ?? '';
$ip6 = $status['netbirdIpv6'] ?? '';
$fqdn = $status['fqdn'] ?? '';
$prof = $status['profileName'] ?? '';
$mgmtOk = !empty($status['management']['connected']);

$err = trim((string) ($status['management']['error'] ?? ''));
if ($err === '') {
$err = trim((string) ($status['signal']['error'] ?? ''));
}
$expectedSetupError = stripos($err, 'no peer auth method provided') !== false;
$profiles = ($enabled && $running && !$mgmtOk) ? Netbird\listProfiles() : [];
$awaitingSetup = $enabled && $running && !$mgmtOk
&& (!$profiles || $expectedSetupError);

$row = static fn (string $label, string $value): string =>
"<tr><td><span class='w26'>{$label}</span>{$value}</td></tr>" . PHP_EOL;

if ($running && $mgmtOk) {
if (!$enabled) {
$state = "<span style='color:#888;'>Disabled</span>";
} elseif ($running && $mgmtOk) {
$state = "<span style='color:#2eaa2e;'>Connected</span>";
} elseif ($awaitingSetup) {
$state = "<span style='color:#d39e00;'>Awaiting configuration</span>";
} elseif ($running) {
$state = "<span style='color:#d39e00;'>Daemon running, not logged in</span>";
$state = "<span style='color:#d39e00;'>Daemon running, not connected</span>";
} else {
$state = "<span style='color:#c8331f;'>Stopped</span>";
}

$rows = $row('Status', $state);

if ($status) {
if ($running && $mgmtOk) {
if ($mgmtOk) {
// Peer breakdown by connection type (direct vs relayed).
$details = $status['peers']['details'] ?? [];
$peers = (int) ($status['peers']['total'] ?? count($details));
Expand Down Expand Up @@ -80,13 +98,17 @@ try {
$rows .= $row('Version', $verVal);
}

// Surface a control-plane error if one is present.
$err = trim((string) ($status['management']['error'] ?? ''));
if ($err === '') {
$err = trim((string) ($status['signal']['error'] ?? ''));
}
if ($err !== '') {
$rows .= $row('Error', "<span style='color:#c8331f;'>" . htmlspecialchars($err) . "</span>");
// "No peer auth method" is expected before initial setup and is already
// represented by the friendlier state above. Unexpected control-plane
// errors remain visible, but wrap so they cannot stretch the dashboard
// grid into an oversized tile.
if ($err !== '' && !$expectedSetupError) {
$rows .= $row(
'Error',
"<span style='color:#c8331f;white-space:normal;overflow-wrap:anywhere;word-break:break-word;'>"
. htmlspecialchars($err)
. '</span>'
);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/usr/local/emhttp/plugins/netbird/default.cfg
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
ENABLE_NETBIRD="1"
ENABLE_NETBIRD="0"
ENABLE_SSH="0"
MANAGE_DNS="1"
LOG_LEVEL="info"
Loading
Loading