netbird-docker-installer-wago

NetBird Docker Installer for WAGO

(BETA! THIS SCRIPT IS UNDER DEVELOPMENT)

Installer for running the NetBird agent as a Docker container on WAGO PLCs/Panels running Linux/PTXdist.

This project is designed for WAGO devices where:

Repository:

https://github.com/thomassandberg/netbird-docker-installer-wago

Installer URL:

https://raw.githubusercontent.com/thomassandberg/netbird-docker-installer-wago/refs/heads/main/netbird/install

Important notice

The scripts and WAGOupload files in this repository are provided as examples and must be reviewed, tested and adapted to your own WAGO device, firmware version, network setup and operational requirements.

The files in the wagoupload/ directory are example files only. They may need changes before they are used in a real firmware update workflow.

Running the installer, repair scripts, update scripts or WAGOupload scripts is done entirely at your own risk.

Test everything on a non-critical device before using it in production.


Features


Repository layout

netbird-docker-installer-wago/
  netbird/
    install
  wagoupload/
    run-netbird-restore.sh
    wupload.cfg
  README.md

netbird/install

Main installer script.

It supports:

install <NETBIRD_TAG> <SETUP_KEY> [MANAGEMENT_URL] [HEALTHCHECK_IP]
repair

wagoupload/run-netbird-restore.sh

Example script intended for WAGOupload/.appload.

It runs the local NetBird restore script on the controller:

/bin/sh /home/netbird/repair/restore-host-hooks.sh

wagoupload/wupload.cfg

Example WAGOupload configuration snippet showing how to run run-netbird-restore.sh at the end of a WAGOupload workflow.


Installed layout on the WAGO controller

The installer creates the following structure:

/home/netbird/
  state/
  config/
    netbird.env
    netbird.conf
  scripts/
    run-netbird.sh
    healthcheck-netbird.sh
    watchdog-netbird.sh
    upgrade-netbird.sh
    boot-netbird.sh
  versions/
    current-version
    previous-version
    pending-upgrade
  logs/
    upgrade.log
    watchdog.log
    restore-host-hooks.log
    wagoupload-restore.log
  lock/
  tmp/
  repair/
    restore-host-hooks.sh

The most important directory is:

/home/netbird/state

This directory is mounted into the NetBird container as:

/var/lib/netbird

Do not delete /home/netbird/state unless you intentionally want to remove the NetBird peer identity from the device.


Requirements

Run the installer as root.

The WAGO device must have:

Check Docker:

docker --version
docker ps

Check TUN:

ls -l /dev/net/tun

Installer modes

The installer supports two modes:

install <NETBIRD_TAG> <SETUP_KEY> [MANAGEMENT_URL] [HEALTHCHECK_IP]
repair

Use install for first-time installation.

Use repair after firmware updates or if cronjobs, wrappers, symlinks or init scripts have disappeared while /home/netbird is still intact.


First-time installation

Syntax

curl -fsSLk https://raw.githubusercontent.com/thomassandberg/netbird-docker-installer-wago/refs/heads/main/netbird/install \
  | sh -s -- install <NETBIRD_TAG> <SETUP_KEY> [MANAGEMENT_URL] [HEALTHCHECK_IP]

Arguments:

Argument Required Description
NETBIRD_TAG yes NetBird Docker image tag, for example 0.72.2
SETUP_KEY yes NetBird setup key
MANAGEMENT_URL no Self-hosted NetBird management URL
HEALTHCHECK_IP no Optional IP to ping through NetBird during healthcheck

If using NetBird Cloud and you also want to provide a healthcheck IP, pass an empty string for MANAGEMENT_URL:

""

NetBird Cloud

Use this when using NetBird Cloud and no healthcheck IP:

curl -fsSLk https://raw.githubusercontent.com/thomassandberg/netbird-docker-installer-wago/refs/heads/main/netbird/install \
  | sh -s -- install 0.72.2 SETUP_KEY

Example:

curl -fsSLk https://raw.githubusercontent.com/thomassandberg/netbird-docker-installer-wago/refs/heads/main/netbird/install \
  | sh -s -- install 0.72.2 AAAAAA-BBBBBB-CCCCCC

NetBird Cloud with healthcheck IP

Use this when using NetBird Cloud and you want the watchdog to verify connectivity to a known NetBird IP:

curl -fsSLk https://raw.githubusercontent.com/thomassandberg/netbird-docker-installer-wago/refs/heads/main/netbird/install \
  | sh -s -- install 0.72.2 SETUP_KEY "" 100.80.10.1

Example:

curl -fsSLk https://raw.githubusercontent.com/thomassandberg/netbird-docker-installer-wago/refs/heads/main/netbird/install \
  | sh -s -- install 0.72.2 AAAAAA-BBBBBB-CCCCCC "" 100.80.10.1

Self-hosted NetBird

Use this when running your own NetBird management server:

curl -fsSLk https://raw.githubusercontent.com/thomassandberg/netbird-docker-installer-wago/refs/heads/main/netbird/install \
  | sh -s -- install 0.72.2 SETUP_KEY https://netbird.example.com

Example:

curl -fsSLk https://raw.githubusercontent.com/thomassandberg/netbird-docker-installer-wago/refs/heads/main/netbird/install \
  | sh -s -- install 0.72.2 AAAAAA-BBBBBB-CCCCCC https://netbird.example.com

Self-hosted NetBird with healthcheck IP

Use this when running your own NetBird management server and you want the watchdog to verify connectivity to a known NetBird IP:

curl -fsSLk https://raw.githubusercontent.com/thomassandberg/netbird-docker-installer-wago/refs/heads/main/netbird/install \
  | sh -s -- install 0.72.2 SETUP_KEY https://netbird.example.com 100.80.10.1

Example:

curl -fsSLk https://raw.githubusercontent.com/thomassandberg/netbird-docker-installer-wago/refs/heads/main/netbird/install \
  | sh -s -- install 0.72.2 AAAAAA-BBBBBB-CCCCCC https://netbird.example.com 100.80.10.1

What the installer starts

NetBird is started as a Docker container named:

netbird

The container is started with:

--network host
--restart unless-stopped
--cap-add NET_ADMIN
--cap-add SYS_ADMIN
--cap-add SYS_RESOURCE
--device /dev/net/tun:/dev/net/tun
--env-file /home/netbird/config/netbird.env
-v /home/netbird/state:/var/lib/netbird

The container image is:

netbirdio/netbird:<NETBIRD_TAG>

Repair after WAGO firmware update

WAGO firmware updates may remove files outside /home, for example:

/usr/local/bin/update-netbird
/usr/bin/update-netbird
/usr/local/bin/netbird
/usr/bin/netbird
/etc/init.d/netbird
root crontab entry

The NetBird state and configuration should still exist under:

/home/netbird

If /home/netbird is still intact, run repair mode:

curl -fsSLk https://raw.githubusercontent.com/thomassandberg/netbird-docker-installer-wago/refs/heads/main/netbird/install \
  | sh -s -- repair

Repair mode does not require:

Repair mode preserves:

/home/netbird/state
/home/netbird/config/netbird.env
/home/netbird/config/netbird.conf
/home/netbird/versions/current-version

Repair mode restores:

/home/netbird/scripts/*
/home/netbird/repair/restore-host-hooks.sh
/usr/local/bin/update-netbird
/usr/bin/update-netbird
/usr/local/bin/netbird
/usr/bin/netbird
/etc/init.d/netbird
root crontab entry

Repair mode also checks whether NetBird is healthy. If not, it tries to recreate the container using the version stored in:

/home/netbird/versions/current-version

Local repair script for WAGOupload/.appload

The installer creates a local repair script:

/home/netbird/repair/restore-host-hooks.sh

This script is intended for WAGOupload/.appload after firmware updates.

It does not require:

It restores local host hooks from files already stored under /home/netbird.

The command to run is:

/bin/sh /home/netbird/repair/restore-host-hooks.sh

You can also run it manually:

/home/netbird/repair/restore-host-hooks.sh

It restores:

/usr/local/bin/update-netbird
/usr/bin/update-netbird
/usr/local/bin/netbird
/usr/bin/netbird
/etc/init.d/netbird
root crontab entry

It also tries to start or repair the NetBird container using the version stored in:

/home/netbird/versions/current-version

Check the local repair log:

tail -100 /home/netbird/logs/restore-host-hooks.log

WAGOupload/.appload support

This repository includes example WAGOupload files in:

wagoupload/

Files:

wagoupload/run-netbird-restore.sh
wagoupload/wupload.cfg

These files are examples that can be used when creating a WAGOupload .appload package for firmware upgrades.

The goal is:

Firmware update
→ /home is preserved
→ WAGOupload runs run-netbird-restore.sh
→ run-netbird-restore.sh runs /home/netbird/repair/restore-host-hooks.sh
→ NetBird host hooks are restored
→ NetBird container is started or repaired

Inside the .appload package:

wupload.cfg
run-netbird-restore.sh

or, if you prefer a subdirectory:

wupload.cfg
scripts/
  run-netbird-restore.sh

Make sure the path in wupload.cfg matches the actual location of the script inside the .appload package.


Example: wagoupload/run-netbird-restore.sh

The repository includes this example file:

wagoupload/run-netbird-restore.sh

Recommended content:

#!/bin/sh

LOG="/home/netbird/logs/wagoupload-restore.log"

mkdir -p /home/netbird/logs

echo "$(date -Iseconds) WAGOupload NetBird restore started" >> "$LOG"

if [ ! -f /home/netbird/repair/restore-host-hooks.sh ]; then
  echo "$(date -Iseconds) ERROR: /home/netbird/repair/restore-host-hooks.sh not found" >> "$LOG"
  exit 1
fi

/bin/sh /home/netbird/repair/restore-host-hooks.sh >> "$LOG" 2>&1

RC="$?"

echo "$(date -Iseconds) WAGOupload NetBird restore finished with exit code $RC" >> "$LOG"

exit "$RC"

Important:

On Linux, WSL or macOS, fix line endings and permissions before packaging:

sed -i 's/\r$//' wagoupload/run-netbird-restore.sh
chmod +x wagoupload/run-netbird-restore.sh

Verify that the file does not contain CRLF:

file wagoupload/run-netbird-restore.sh

Good:

POSIX shell script, ASCII text executable

Bad:

POSIX shell script, ASCII text executable, with CRLF line terminators

You can also inspect the first line:

head -1 wagoupload/run-netbird-restore.sh | od -An -t x1

Good:

23 21 2f 62 69 6e 2f 73 68 0a

Bad:

23 21 2f 62 69 6e 2f 73 68 0d 0a

If WAGOupload logs an error like this:

sudo: unable to execute /tmp/run-netbird-restore.sh: No such file or directory

even though the file was uploaded successfully, the most likely cause is CRLF line endings or an invalid shebang.


Example: wagoupload/wupload.cfg

The repository includes this example file:

wagoupload/wupload.cfg

If run-netbird-restore.sh is placed in the root of the .appload package, use:

[ExecuteScriptAtEnd]
File1=run-netbird-restore.sh
Timeout1=600

If run-netbird-restore.sh is placed in a scripts directory inside the .appload package, use:

[ExecuteScriptAtEnd]
File1=scripts\run-netbird-restore.sh
Timeout1=600

Use the variant that matches your .appload layout.

The timeout is set to 600 seconds because the repair script may need to wait for Docker, restore cron/wrappers, recreate the NetBird container and run healthchecks.

If [ExecuteScriptAtEnd] already exists, add it as the next file number:

[ExecuteScriptAtEnd]
File1=scripts\existing-script.sh
Timeout1=300
File2=run-netbird-restore.sh
Timeout2=600

Creating a WAGOupload .appload package

A practical workflow is:

1. Create or open the WAGOupload application package.
2. Add run-netbird-restore.sh to the package.
3. Add or merge the [ExecuteScriptAtEnd] section in wupload.cfg.
4. Make sure the File1 path in wupload.cfg matches the script location.
5. Make sure run-netbird-restore.sh uses LF line endings.
6. Run the firmware update with this .appload package.
7. WAGOupload uploads and executes run-netbird-restore.sh at the end.
8. NetBird repair is triggered automatically.

Recommended command executed indirectly by WAGOupload:

/bin/sh /home/netbird/repair/restore-host-hooks.sh

WAGOupload should not need the NetBird setup key.

WAGOupload should not need access to GitHub.

WAGOupload should not restore /var/lib/docker.


WAGOupload firmware update workflow

Recommended firmware update workflow:

1. NetBird is installed and working.
2. /home/netbird/repair/restore-host-hooks.sh exists and has been tested.
3. WAGOupload performs firmware update.
4. /home is preserved.
5. WAGOupload runs run-netbird-restore.sh at the end.
6. run-netbird-restore.sh runs:
   /bin/sh /home/netbird/repair/restore-host-hooks.sh
7. restore-host-hooks.sh restores:
   - update-netbird
   - netbird CLI wrapper
   - /etc/init.d/netbird
   - watchdog cron entry
8. restore-host-hooks.sh starts or repairs the NetBird container.
9. Remote access through NetBird should come back.

Test WAGOupload repair before firmware update

Before relying on WAGOupload/.appload in production, test the local repair script manually on the controller:

/home/netbird/repair/restore-host-hooks.sh

Verify:

which update-netbird
which netbird
crontab -l
docker ps | grep netbird
netbird status
ip link show wt0
tail -100 /home/netbird/logs/restore-host-hooks.log

Simulate missing host hooks

You can simulate a firmware update removing host-side files:

rm -f /usr/bin/update-netbird
rm -f /usr/bin/netbird
rm -f /usr/local/bin/update-netbird
rm -f /usr/local/bin/netbird
rm -f /etc/init.d/netbird
crontab -l 2>/dev/null | grep -v "/home/netbird/scripts/watchdog-netbird.sh" | crontab -

Then run local repair:

/home/netbird/repair/restore-host-hooks.sh

Verify:

which update-netbird
which netbird
ls -l /etc/init.d/netbird
crontab -l
netbird status

Test the WAGOupload script manually on the controller

Copy the WAGOupload script to the controller:

scp wagoupload/run-netbird-restore.sh root@<wago-ip>:/tmp/run-netbird-restore.sh

SSH to the controller:

ssh root@<wago-ip>

Run it explicitly through /bin/sh:

/bin/sh /tmp/run-netbird-restore.sh

If this works, but executing the file directly fails:

/tmp/run-netbird-restore.sh

then the problem is likely one of:

Fix locally and rebuild the .appload package:

sed -i 's/\r$//' wagoupload/run-netbird-restore.sh
chmod +x wagoupload/run-netbird-restore.sh

Download first, inspect, then run locally

If you want to inspect the installer before running it:

curl -fsSLk https://raw.githubusercontent.com/thomassandberg/netbird-docker-installer-wago/refs/heads/main/netbird/install \
  -o /home/install-netbird-master.sh

Make it executable:

chmod 700 /home/install-netbird-master.sh

Run first-time installation with NetBird Cloud:

/home/install-netbird-master.sh install 0.72.2 SETUP_KEY

Run first-time installation with NetBird Cloud and healthcheck IP:

/home/install-netbird-master.sh install 0.72.2 SETUP_KEY "" 100.80.10.1

Run first-time installation with self-hosted NetBird:

/home/install-netbird-master.sh install 0.72.2 SETUP_KEY https://netbird.example.com

Run first-time installation with self-hosted NetBird and healthcheck IP:

/home/install-netbird-master.sh install 0.72.2 SETUP_KEY https://netbird.example.com 100.80.10.1

Run repair mode:

/home/install-netbird-master.sh repair

Verify installation

Check container:

docker ps | grep netbird

Check NetBird status using the host wrapper:

netbird status

Equivalent command without wrapper:

docker exec netbird netbird status

Check NetBird interface:

ip link show wt0

Check watchdog cron entry:

crontab -l

Expected cron entry:

* * * * * /home/netbird/scripts/watchdog-netbird.sh >/dev/null 2>&1

Check update command:

which update-netbird
ls -l /usr/bin/update-netbird
ls -l /usr/local/bin/update-netbird

Check NetBird CLI wrapper:

which netbird
ls -l /usr/bin/netbird
ls -l /usr/local/bin/netbird

Check configured healthcheck IP:

grep HEALTHCHECK_IP /home/netbird/config/netbird.conf

Check current installed NetBird version:

cat /home/netbird/versions/current-version

Check logs:

tail -100 /home/netbird/logs/watchdog.log
tail -100 /home/netbird/logs/upgrade.log
tail -100 /home/netbird/logs/restore-host-hooks.log
tail -100 /home/netbird/logs/wagoupload-restore.log

Update NetBird later

After installation, the installer provides this command:

update-netbird

Usage:

update-netbird <NEW_NETBIRD_TAG>

Example:

update-netbird 0.72.3

The update runs in the background using the local upgrade script:

/home/netbird/scripts/upgrade-netbird.sh

The update process:

  1. pulls the new Docker image
  2. stores the previous version
  3. starts the new NetBird container
  4. performs healthcheck
  5. keeps the new version if healthy
  6. rolls back to the previous version if healthcheck fails

Follow update progress:

tail -f /home/netbird/logs/upgrade.log

Check current version after update:

cat /home/netbird/versions/current-version

Check NetBird status:

netbird status

NetBird CLI wrapper

The installer creates a host-side netbird wrapper.

This means you can run:

netbird status

instead of:

docker exec netbird netbird status

Other examples:

netbird version
netbird debug

The wrapper is installed here:

/usr/local/bin/netbird

And symlinked here:

/usr/bin/netbird

The wrapper executes the NetBird CLI inside the running container:

docker exec netbird netbird "$@"

If the wrapper is missing after a firmware update, restore it with:

/home/netbird/repair/restore-host-hooks.sh

Manual rollback

If needed, manually roll back to the previous version:

/home/netbird/scripts/run-netbird.sh "$(cat /home/netbird/versions/previous-version)"

Verify:

netbird status
ip link show wt0

Re-running the installer

The installer can be run again.

For a normal first-time installation or full reconfiguration, use:

curl -fsSLk https://raw.githubusercontent.com/thomassandberg/netbird-docker-installer-wago/refs/heads/main/netbird/install \
  | sh -s -- install 0.72.2 SETUP_KEY https://netbird.example.com 100.80.10.1

This will overwrite:

/home/netbird/config/netbird.env
/home/netbird/config/netbird.conf
/home/netbird/scripts/*
/home/netbird/repair/restore-host-hooks.sh
/usr/local/bin/update-netbird
/usr/bin/update-netbird
/usr/local/bin/netbird
/usr/bin/netbird

It will not delete:

/home/netbird/state

Because /home/netbird/state is preserved, the NetBird peer identity should remain intact.

For firmware repair only, use:

curl -fsSLk https://raw.githubusercontent.com/thomassandberg/netbird-docker-installer-wago/refs/heads/main/netbird/install \
  | sh -s -- repair

Repair mode does not overwrite:

/home/netbird/config/netbird.env
/home/netbird/config/netbird.conf
/home/netbird/state

For fully local repair without GitHub access, use:

/home/netbird/repair/restore-host-hooks.sh

Removing the setup key after installation

After the device has been registered successfully, you may remove the setup key from:

/home/netbird/config/netbird.env

For NetBird Cloud, the file can be empty or removed if the existing state is valid.

For self-hosted NetBird, keep:

NB_MANAGEMENT_URL=https://netbird.example.com

Restart NetBird:

docker restart netbird

Verify:

netbird status

If you later run install mode again, the setup key will be written back to /home/netbird/config/netbird.env.

If you only need to restore cron, wrapper, symlink or init script after firmware updates, use repair mode or local restore mode instead.


About curl -k

Most examples use:

curl -fsSLk

The -k option disables TLS certificate verification.

This can be useful on embedded devices with missing or outdated CA certificates, but it is less secure.

If the device has working CA certificates, prefer:

curl -fsSL https://raw.githubusercontent.com/thomassandberg/netbird-docker-installer-wago/refs/heads/main/netbird/install \
  | sh -s -- install 0.72.2 SETUP_KEY

Troubleshooting

Docker is not running

docker ps

If this fails, Docker is not available or not running.


TUN device is missing

ls -l /dev/net/tun

NetBird requires /dev/net/tun.


NetBird container logs

docker logs --tail=200 netbird

NetBird status

netbird status

Equivalent command:

docker exec netbird netbird status

Watchdog logs

tail -100 /home/netbird/logs/watchdog.log

Upgrade logs

tail -100 /home/netbird/logs/upgrade.log

Local firmware restore logs

tail -100 /home/netbird/logs/restore-host-hooks.log
tail -100 /home/netbird/logs/wagoupload-restore.log

Check generated config

cat /home/netbird/config/netbird.conf
cat /home/netbird/config/netbird.env

update-netbird command not found

Check where it is installed:

ls -l /usr/local/bin/update-netbird
ls -l /usr/bin/update-netbird
echo $PATH

On WAGO/PTXdist, /usr/local/bin may not be in PATH.

The installer creates this symlink:

/usr/bin/update-netbird -> /usr/local/bin/update-netbird

If it is missing, run repair mode:

curl -fsSLk https://raw.githubusercontent.com/thomassandberg/netbird-docker-installer-wago/refs/heads/main/netbird/install \
  | sh -s -- repair

Or run fully local repair:

/home/netbird/repair/restore-host-hooks.sh

netbird command not found

Check where it is installed:

ls -l /usr/local/bin/netbird
ls -l /usr/bin/netbird
echo $PATH

The installer creates this symlink:

/usr/bin/netbird -> /usr/local/bin/netbird

If it is missing, run:

/home/netbird/repair/restore-host-hooks.sh

WAGOupload says: No such file or directory

Example error:

sudo: unable to execute /tmp/run-netbird-restore.sh: No such file or directory

If WAGOupload says the file was uploaded successfully but execution fails, the most likely causes are:

Fix locally:

sed -i 's/\r$//' wagoupload/run-netbird-restore.sh
chmod +x wagoupload/run-netbird-restore.sh

Verify:

file wagoupload/run-netbird-restore.sh

Then rebuild the .appload package and try again.


Recommended production usage

For NetBird Cloud with healthcheck IP:

curl -fsSLk https://raw.githubusercontent.com/thomassandberg/netbird-docker-installer-wago/refs/heads/main/netbird/install \
  | sh -s -- install 0.72.2 SETUP_KEY "" 100.80.10.1

For self-hosted NetBird with healthcheck IP:

curl -fsSLk https://raw.githubusercontent.com/thomassandberg/netbird-docker-installer-wago/refs/heads/main/netbird/install \
  | sh -s -- install 0.72.2 SETUP_KEY https://netbird.example.com 100.80.10.1

After WAGO firmware update, preferably through WAGOupload/.appload:

/bin/sh /home/netbird/repair/restore-host-hooks.sh

Or by using the included WAGOupload example files:

wagoupload/run-netbird-restore.sh
wagoupload/wupload.cfg

After WAGO firmware update, manually through GitHub repair mode:

curl -fsSLk https://raw.githubusercontent.com/thomassandberg/netbird-docker-installer-wago/refs/heads/main/netbird/install \
  | sh -s -- repair

For later NetBird updates:

update-netbird 0.72.3

For NetBird status:

netbird status

Security notes

The setup key should be treated as a secret.

The installer stores the setup key in:

/home/netbird/config/netbird.env

After successful registration, consider removing the setup key from that file.

Avoid using latest as the NetBird Docker tag. Always use a specific version, for example:

0.72.2

This makes rollback predictable.

Do not delete:

/home/netbird/state

unless you intentionally want the device to lose its NetBird identity.

Do not restore or overwrite:

/var/lib/docker

as part of WAGOupload/.appload unless you have a very specific reason and have tested it thoroughly.

WAGOupload/.appload should only run the local restore script:

/bin/sh /home/netbird/repair/restore-host-hooks.sh

License

MIT