A firmware-upgrade-proof on-boot script runner for UniFi devices. Executes scripts from /data/on_boot.d/ on every boot and survives firmware upgrades using a self-restoring overlay symlink mechanism with ubnt-dpkg-cache as belt-and-suspenders.
The popular udm-boot / udm-boot-2x packages from unifios-utilities break on firmware upgrades because they:
- Don't register with
ubnt-dpkg-cacheβ package isn't cached for restore - Have no self-restore mechanism β package is gone after firmware rebuild
- Have an empty
postinstthat relies on debhelper magic β service never re-enables
unifi-on-boot solves this with a self-restoring overlay symlink mechanism that reinstalls itself automatically after firmware upgrades.
| Firmware | Status |
|---|---|
| UniFi OS 2.x | β Supported |
| UniFi OS 3.x | β Supported |
| UniFi OS 4.x | β Supported |
| UniFi OS 5.x | β Supported |
| UniFi OS 1.x | β Not supported (uses container architecture) |
Tested on: Enterprise Fortress Gateway (EFG)
# Download the latest release
VERSION=$(curl -fsSL https://api.github.com/repos/unredacted/unifi-on-boot/releases/latest | grep -o '"tag_name": "v[^"]*' | cut -d'v' -f2)
curl -fsSLO "https://github.com/unredacted/unifi-on-boot/releases/latest/download/unifi-on-boot_${VERSION}_all.deb"
# Install
dpkg -i unifi-on-boot_${VERSION}_all.debThe package automatically:
- Enables the
unifi-on-bootsystemd service (runs on next boot) - Sets up the self-restore mechanism (overlay symlinks + backup
.debin/data/) - Registers itself with
ubnt-dpkg-cachefor package caching - Saves its systemd status for service re-enablement after restore
- Creates
/data/on_boot.d/if it doesn't exist
# Remove (keeps /data/on_boot.d/ and scripts)
dpkg -r unifi-on-boot
# Purge (removes all persistent data + registrations)
dpkg -P unifi-on-bootNote: If you have
udm-bootorudm-boot-2xinstalled, this package will conflict with them. Remove them first:dpkg -r udm-boot udm-boot-2x
Place your scripts in /data/on_boot.d/ on the UniFi device:
# Create a script
cat > /data/on_boot.d/10-example.sh << 'EOF'
#!/bin/bash
echo "Hello from on-boot!"
EOF
chmod +x /data/on_boot.d/10-example.shScripts in /data/on_boot.d/ are processed in sorted order:
| Condition | Action |
|---|---|
File has +x (executable) flag |
Executed directly |
File ends in .sh but not executable |
Sourced (run in current shell) |
| Everything else | Ignored |
Use numeric prefixes for ordering:
/data/on_boot.d/
βββ 01-network-setup.sh
βββ 10-install-packages.sh
βββ 20-configure-services.sh
βββ 50-custom-script.sh
# View service status
systemctl status unifi-on-boot
# View journal logs
journalctl -u unifi-on-boot
# View persistent log
cat /var/log/unifi-on-boot.log# Re-run all on-boot scripts without rebooting
systemctl restart unifi-on-bootFor UniFi HA (High Availability) setups, unifi-on-boot automatically syncs /data/on_boot.d/ to the shadow gateway after running all scripts on the primary. This ensures the shadow gateway has an identical set of on-boot scripts.
- After running all scripts on the primary, the service pings
169.254.254.3(the shadow gateway link-local IP) - If reachable, it installs
rsyncon both gateways if not already present - Runs
rsync --deleteto ensure the shadow's/data/on_boot.d/exactly matches the primary (including removing stale scripts) - Installs
unifi-on-booton the shadow if not present (from the backed-up.deb) - Runs the on-boot scripts on the shadow with
--skip-shadowto prevent recursive syncing
Shadow sync is enabled by default and configured via /data/unifi-on-boot/shadow.conf:
# Set SHADOW_ENABLED=false to disable shadow gateway sync
SHADOW_ENABLED=true
SHADOW_IP=169.254.254.3
SHADOW_USER=root# Edit the config file
sed -i 's/SHADOW_ENABLED=true/SHADOW_ENABLED=false/' /data/unifi-on-boot/shadow.confNote: SSH key-based authentication must be set up between the primary and shadow gateway for sync to work. The primary must be able to
ssh root@169.254.254.3without a password prompt.
UniFi firmware upgrades rebuild the root filesystem, wiping all installed packages and systemd services. Ubiquiti's ubnt-dpkg-restore only restores packages listed in /etc/default/ubnt-dpkg-support, which resets to firmware defaults on every upgrade β so custom packages are excluded.
unifi-on-boot solves this with a self-restoring mechanism inspired by how tailscale-udm persists on UniFi devices:
Firmware Upgrade
β Root filesystem rebuilt (all packages + services lost)
β BUT: overlay upper dir (/mnt/.rwfs/data/) preserved
β Symlink survives: /etc/systemd/system/unifi-on-boot-install.service
β points to /data/unifi-on-boot/unifi-on-boot-install.service
β systemd finds the symlink, runs install.sh from /data/
β install.sh checks if package is installed
β Not installed: dpkg -i from /data/unifi-on-boot/unifi-on-boot.deb
β postinst enables unifi-on-boot.service
β On next boot (or later in same boot): runs /data/on_boot.d/* scripts
The package sets up three layers of persistence:
- Self-restore service β Copies
install.shand a service file to/data/unifi-on-boot/(persistent), and copies the service file into/etc/systemd/system/(overlay upper dir). The service file must be a copy, not a symlink to/data/, because systemd scans for units before the SSD containing/data/is mounted. - Backup
.debβ Copies the.debto/data/unifi-on-boot/and also letsubnt-dpkg-cachecache it in/persistent/dpkg/ - systemd status β Saves enable/disable state to
/persistent/dpkg/<distro>/status/sorestore_pkg_status()can re-enable the service
This repo includes an Ansible role at ansible/ for automated deployment.
Add the role to your playbook's requirements.yml:
- name: unifi-on-boot
src: git+https://github.com/unredacted/unifi-on-boot.git
version: mainInstall: ansible-galaxy install -r requirements.yml
- hosts: unifi_devices
roles:
- role: unifi-on-boot
vars:
# unifi_on_boot_version: "1.0.4" # defaults to latest in role defaults
unifi_on_boot_scripts:
- name: "10-setup-pathvector.sh"
src: "pathvector-setup.sh.j2"
mode: "0755"
unifi_on_boot_run_after_deploy: true
unifi_on_boot_debug: true| Variable | Default | Description |
|---|---|---|
unifi_on_boot_version |
"1.0.8" |
Version to install from GitHub releases (update to latest) |
unifi_on_boot_remove_conflicts |
true |
Remove udm-boot/udm-boot-2x if present |
unifi_on_boot_scripts |
[] |
List of scripts to deploy (see example above) |
unifi_on_boot_run_after_deploy |
false |
Run on-boot scripts immediately after deploy |
unifi_on_boot_debug |
false |
Show debug output and deployment summary |
The role will:
- Remove conflicting
udm-bootpackages (if enabled) - Download and install the
.debfrom GitHub releases - Deploy scripts from templates to
/data/on_boot.d/ - Optionally trigger the on-boot service
# Requires: dpkg-deb (available on Debian/Ubuntu)
./build.sh
# Output: dist/unifi-on-boot_<version>_all.debThe build uses dpkg-deb directly β no debhelper or other build system dependencies.
If something goes wrong after a firmware upgrade:
# Check if the package was restored
dpkg -l | grep unifi-on-boot
# If not, re-install manually from the self-restore backup
dpkg -i /data/unifi-on-boot/unifi-on-boot.deb
# Or download fresh (same method as initial install)
VERSION=$(curl -fsSL https://api.github.com/repos/unredacted/unifi-on-boot/releases/latest | grep -o '"tag_name": "v[^"]*' | cut -d'v' -f2)
curl -fsSLO "https://github.com/unredacted/unifi-on-boot/releases/latest/download/unifi-on-boot_${VERSION}_all.deb"
dpkg -i unifi-on-boot_${VERSION}_all.deb| Feature | unifi-on-boot | udm-boot-2x |
|---|---|---|
| Survives firmware upgrades | β Yes | β No |
| Self-restore overlay mechanism | β Yes | β No |
ubnt-dpkg-cache integration |
β Yes | β No |
Explicit systemctl enable in postinst |
β Yes | β Relies on debhelper |
| systemd status persistence | β Yes | β No |
| Clean uninstall (purge) | β Yes | |
| UniFi OS 4.x/5.x support | β Yes | |
| GitHub Actions CI | β Yes | β No |
GPL-3.0 β see LICENSE