v0.5.1 (2026-05-13)
Fixes the first-boot failure after mix burn:
erlinit: No release found in /srv/erlang.
erlinit: Erlang installation not found.Root cause: mix firmware baked the system-prebuilt
data/openwrt-one-initramfs.itb and data/openwrt-one-nand.ubi into
the .fw — both assembled at system build time, before the user's
Erlang release was merged into the squashfs. mix burn then wrote
the stale UBI to the recovery USB stick; after the USB dance, the
board booted into an initramfs with no /srv/erlang. The OTA path
(mix upload) avoided the bug by re-running wrap-firmware.sh at
upload time to rebuild a per-app .itb from the .fw's squashfs.
Fix: a new openwrt_one.firmware_post Mix task rebuilds both
resources from the .fw's combined squashfs and splices them back
into the .fw archive, updating length / blake2b-256 in
meta.conf and preserving zip entry order. Users wire it into the
mix firmware lifecycle with a one-line alias in their mix.exs:
defp aliases do
[firmware: ["firmware", "openwrt_one.firmware_post"]]
endWith the alias in place, mix firmware produces a .fw whose
bundled .itb and .ubi already match the user release, so
mix burn and mix upload both work natively against the bundled
resources — no per-invocation repack.
Changes:
- New
Mix.Tasks.OpenwrtOne.FirmwarePostinlib/mix/tasks/. Requiresubinize(frommtd-utils) andb2sum(fromcoreutils) on the host. scripts/upload-ota.sh: extractdata/openwrt-one-initramfs.itbdirectly from the.fwinstead of re-runningwrap-firmware.sh. Errors out with the alias snippet if the resource is missing or zero-sized.scripts/wrap-firmware.sh: also search~/.nerves/artifacts/.../host/{sbin,bin}forubinizeandmkenvimage(Nerves caches artifacts there, not under$SYSTEM_DIR/.nerves/artifacts); fail with an install hint instead of warning + skipping whenubinizecannot be found.mix.exs: shiplib/in the hex package so the Mix task is picked up when the system is used as a dep.README.md: documentmtd-utilsandcoreutilsas host prerequisites; new "Step 1: wire up the firmware-post alias" section before "build the firmware and prepare a USB stick".
v0.5.0 (2026-05-08)
Breaking: the embedded NervesSystemOpenwrtOne.UBootEnvKVBackend is
gone. It now lives in its own Hex package,
nerves_uboot_env_ubi,
under the Nerves.Runtime.KVBackend.UBootEnvUBI module.
Migration in your firmware app:
- Add to
mix.exsdeps:{:nerves_uboot_env_ubi, "~> 0.1"} - Update
config/target.exs:config :nerves_runtime, kv_backend: {Nerves.Runtime.KVBackend.UBootEnvUBI, []} - The system dep can now stay
runtime: false(the Nerves convention) since the system no longer ships any runtime BEAM modules.
Why: keeping the backend in the system forced users to drop
runtime: false on the system dep so the backend's BEAMs got
included in their release. That's a non-obvious footgun, and
extracting the backend lets it be properly tested + documented +
reused on other UBI-flash boards.
This release also rolls up the v0.4.x churn (which never made it to Hex):
nerves_system_br1.33.4 → 1.33.7 (OTP 28.5, fwup 1.16.0, Buildroot 2025.11.3) — already shipped as v0.2.3.- aarch64 toolchain 13.2.0 → 14.2.0 (GCC 13 → GCC 14) for multi-target compatibility with sibling Nerves systems.
BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SUPPORT=yso Buildroot's host mkimage works inside Docker on macOS.wrap-firmware.shno longer needssudo— usescpio --owner=0:0to force ownership andunsquashfsruns as the invoking user. Drops the--reproducibleflag which BSD cpio (macOS) doesn't support.BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SUPPORTandhost-dtcfor build-time mkimage;mix burnUSB-stick recovery flow; persistent/rootvia UBIFS on UBI volume 5 with lazy mkfs.ubifs;nerves_motd"Part usage" cell now reports real numbers.
v0.4.4 (2026-05-07)
wrap-firmware.sh: use numericcpio --owner=0:0instead ofroot:root. BSD cpio (macOS default) tries to look up "root" as a group name and fails because the gid-0 group on macOS is "wheel", not "root". Numeric IDs are portable everywhere.
v0.4.3 (2026-05-07)
wrap-firmware.sh: dropcpio --reproducible. It's a GNU cpio extension; BSD cpio (macOS default) rejects it withOption --reproducible is not supported. Reproducible byte output isn't actually needed — the cpio just has to be a valid initramfs.
v0.4.2 (2026-05-07)
Fix v0.4.1's regression: OTA boots failed because the bundled
build-time cpio.gz was the system-only rootfs without the user's
release at /srv/erlang, so erlinit died with "No release found"
and the device rolled back.
wrap-firmware.shis back to unpacking the combined squashfs rootfs and repacking as cpio.gz, but now withoutsudo:unsquashfsruns as the invoking user (files come out user- owned, content intact).cpio --owner=root:rootforces ownership in the output cpio regardless of disk owners, so the result is byte-equivalent to the old sudo-based path./dev/consoleis no longer pre-created withmknod;/initmounts devtmpfs first which provides it.
- Reverts the
rootfs.cpio.gzresource added tofwup.confin v0.4.1 — no longer needed, .fw shrinks by ~9 MiB.
v0.4.1 (2026-05-07)
OTA upload was supposed to work on macOS without sudo prompts. This release was BROKEN — see v0.4.2 for the actual fix.
wrap-firmware.shno longer unsquashes the rootfs and re-cpio's it on the host. Previously this requiredsudo unsquashfs,sudo mknod /dev/console, andsudo cpio, which broke when invoked non-interactively (e.g.mix uploadon macOS, where sudo can't prompt without a TTY).fwup.confnow bundles the build-timerootfs.cpio.gzalongsiderootfs.imgin the .fw archive.wrap-firmware.shextracts it directly into the FIT image — no transformation needed.- Costs ~9 MiB extra in the .fw (the cpio.gz copy), but eliminates the sudo dependency end-to-end.
v0.4.0 (2026-05-07)
Toolchain bump to align with sibling Nerves systems on aarch64.
nerves_toolchain_aarch64_nerves_linux_gnu13.2.0 → 14.2.0 (GCC 13 → GCC 14). Buildroot toolchain URL +BR2_TOOLCHAIN_EXTERNAL_GCC_*flag updated to match.- Enables
BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SUPPORT=yso Buildroot's host mkimage is built with FIT support and host-dtc as a dependency. Without it, building inside the Nerves Docker container on macOS failed at the post-image step with "sh: 1: -I: not found".
The toolchain change forces a full system rebuild on first build, but no source/API changes.
v0.3.0 (2026-05-07)
/root (and /data via the existing symlink) is now persistent
across reboots, matching standard Nerves convention.
rootfs_data(UBI volume 5) is now mounted at/rootvia a small--pre-run-execscript (/usr/sbin/mount-data.sh). The script lazily formats the volume with UBIFS on first boot and falls back to tmpfs if the persistent mount can't be brought up, so the device always boots even with unhealthy NAND. Without this,/rootlived in the volatile rootfs (initramfs) and SSH host keys, NervesTime RTC drift fixes, etc. were regenerated on every reboot.mkfs.ubifs(BR2_PACKAGE_MTD_MKFSUBIFS=y) is now built into the target image so the lazy-format step works.nerves_fw_application_part0_devpath=/root(andfstype=ubifs,target=/root) is now baked into the U-Boot env, sonerves_motdresolves the data partition for its "Part usage" cell instead of falling back to "not available".
Backwards compatibility: existing v0.2.x installs upgrade cleanly.
The first boot of v0.3.0 formats rootfs_data (which was an empty
placeholder volume in v0.2.x). Rollback to v0.2.x is fine — the
older slot's initramfs ignores the UBIFS, runs in volatile mode.
v0.2.3 (2026-05-06)
Routine upstream bump.
nerves_system_br1.33.4 → 1.33.7- Erlang/OTP 28.4.1 → 28.5
- fwup 1.14.0 → 1.16.0
- Buildroot 2025.11.2 → 2025.11.3
No system-side changes required.
v0.2.2 (2026-04-09)
mix burn support via OpenWrt's NOR full-recovery mode.
mix burnnow prepares a FAT32 USB recovery stick that OpenWrt's SPI NOR recovery U-Boot can use to flash the entire SPI NAND — no serial console, no TFTP server, no typing of U-Boot commands. The stick contains the snand-preloader.bin (BL2) and ouropenwrt-one-nand.ubi(renamed tofactory.ubi). See README.md "Initial install" for the full procedure.- Ship
prebuilt/openwrt-one-snand-preloader.binfrom OpenWrt 24.10 (same source + license as the FIP). fwup.conf: replace the erroringcompletetask with one thatmbr_writes +fat_mkfses +fat_writes the recovery files. The .fw file grows by ~33 MiB (the .ubi) + 234 KiB (preloader) butmix burnnow Just Works with no per-app config.post-createfs.sh: stage the preloader into images/ so fwup can find it at firmware-build time.- README: document USB stick recovery as the primary initial-install path; serial + TFTP demoted to "alternative".
v0.2.1 (2026-04-08)
USB mass storage support.
- Linux: enable
CONFIG_SCSI,CONFIG_BLK_DEV_SD,CONFIG_USB_STORAGE,CONFIG_FAT_FS+CONFIG_VFAT_FS,CONFIG_EXFAT_FS, and the matching NLS tables (CP437,ISO8859-1,UTF-8). USB sticks now enumerate as/dev/sdNand FAT/exFAT partitions mount and read.
v0.2.0 (2026-04-08)
OTA + A/B slot support, kernel bump, several bug fixes that turned session-1 workarounds into proper fixes.
Added
- A/B FIT slots: UBI layout now uses
fit_a(vol 3) andfit_b(vol 4) instead of a singlefitvolume. Each slot is sized at 50 MiB. Active slot is selected at boot viafit_${nerves_fw_active}substitution inubi_read_production. - Image-level boot fallback: if
bootmfails on the active slot, U-Boot'sboot_productionflipsnerves_fw_active,saveenvs, and retries the other slot. - Bootcount-based runtime rollback: OTA sets
upgrade_available=1bootcount=0; U-Boot'snerves_count_attemptscript swaps slots oncebootcount > bootlimit(default 3).Nerves.Runtime.StartupGuardclears the counters once the app is healthy.
scripts/upload-ota.sh+scripts/apply-ota.exs: volume-level OTA via SFTP +ubiupdatevol+fw_setenv. Designed to be aliased asmix uploadfrom the user app.NervesSystemOpenwrtOne.UBootEnvKVBackend: customNerves.Runtime.KVBackendthat reads via the ErlangUBootEnvlibrary and writes via the Cfw_setenv(which issuesUBI_IOCVOLUP). Without this the default backend returns:epermon everyKV.putand breaksvalidate_firmware/0.- Full OpenWrt 24.10 default U-Boot env baked into ubootenv volumes
via
prebuilt/uboot-env-template.txt, with the0x1F000env size matchingCONFIG_ENV_SIZEin OpenWrt's mt7981 U-Boot. Eliminates the cosmeticboardid: U-boot environment CRC32 mismatchwarning that was caused by sizing our env smaller than what U-Boot writes back. CONFIG_IP_ADVANCED_ROUTER=y,CONFIG_IP_MULTIPLE_TABLES=y,CONFIG_IP_ROUTE_MULTIPATH=yso VintageNet's policy-routing setup doesn't crash withRTNETLINK answers: Operation not supported.
Changed
- Linux 6.12 → 6.18.12.
- SPI NAND driver path: dropped the
spi-mtk-snfiattempt and the Etron 0x77-shifted-manufacturer-ID workaround. Now usesspi-mt65xxwith the OpenWrt SPI calibration patch stack (patches 121, 330, 431-435, 930) plusmtk_bmt. The chip is actually a Winbond 256 MiB part, not the 128 MiB Etron we initially guessed. - U-Boot env path in
fw_env.config:/dev/ubi0_0and/dev/ubi0_1instead of/dev/ubi0:ubootenv. The Erlanguboot_envlibrary uses plainFile.openand doesn't understand the:volnameshorthand that fwup-tool'sfw_printenvaccepts.
Fixed
- mkimage invocation in
wrap-firmware.shnow prefers/usr/bin/mkimageover Buildroot's host build, because the latter ships withMKIMAGE_DTC=""and explodes withsh: 1: -I: not foundwhenever it tries to run dtc internally.
v0.1.0 (2026-04-07)
Initial Nerves system for the OpenWRT One.
- Linux 6.12 mainline kernel with small patches
- Custom DTS based on mainline
mt7981b-openwrt-one.dtswith full peripheral enablement - WiFi 2.4 / 5 GHz with proper calibration from factory partition
- Both Ethernet ports (1 GbE LAN + 2.5 GbE WAN with EN8811H PHY)
- RTC, GPIO watchdog, LEDs, buttons
- NAND boot via UBI volumes (fip + fit + ubootenv + rootfs_data)
- Pre-built FIP from official OpenWrt 24.10 release