About
Ubiquiti’s Bullet M line includes the Bullet M2HP along with three other devices. The bullet radios (and other Ubiquiti devices) have two different hardware revisions: the older XM and the newer XW (see Choosing the Correct Device Firmware on Ubiquiti Support). This page applies to the Bullet M2HP XW specifically.
Warning
The Ethernet port does not work with some versions of OpenWrt; see the bug report. The fix, in commit e405b96fe774, is now in the main, 22.03, and 23.05 branches and should be included in OpenWrt 22.03.7 and 23.05.3 when those are released. Until then, use 19.07.10 (which is no longer receiving updates) or a snapshot from one of those branches. To recover after upgrading to a broken build, see Using TFTP recovery below.
Installing OpenWrt 19.07.5
The OpenWrt operating system provides several advantages over the stock Ubiquiti firmware, including more features in the standard distribution, a large set of additional packages that can be installed, and the possibility of a common operating system across different types and models of embedded hardware.
Unfortunately Ubiquiti’s devices use signed firmware which prevents a straightforward installation of alternative firmware such as OpenWrt. However, True Systems has shared two workarounds for this in a GitHub repository. The following steps worked for me with OpenWrt 19.07.5 and a Bullet M2HP XW.
Warning
Flashing a router, particularly using a non-vendor-approved method, is risky and by following this process you risk permanently damaging your device. If you follow these steps, you do so at your own risk.
Download the official AirOS XW.v6.1.7.32555.180523.1754.bin firmware from Ubiquiti. Install it using the normal procedure via the web interface.
$ wget 'https://dl.ui.com/firmwares/XW-fw/v6.1.7/XW.v6.1.7.32555.180523.1754.bin' $ sha256sum XW.v6.1.7.32555.180523.1754.bin c6e36003ab82b588936b558dfaf06fd2e54e3bd63b84f17687e023de55f05b23 XW.v6.1.7.32555.180523.1754.bin
Reset the router to factory defaults using the web interface.
Make sure you can ping the router and log in via SSH. The default password is ubnt. If you get no matching host key type found then add the following to your ~/.ssh/config:
Host 192.168.1.20 HostKeyAlgorithms ssh-dss
$ ping -c2 192.168.1.20 PING 192.168.1.20 (192.168.1.20) 56(84) bytes of data. 64 bytes from 192.168.1.20: icmp_seq=1 ttl=64 time=0.718 ms 64 bytes from 192.168.1.20: icmp_seq=2 ttl=64 time=0.714 ms --- 192.168.1.20 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 14ms rtt min/avg/max/mdev = 0.714/0.716/0.718/0.002 ms
$ ssh ubnt@192.168.1.20 ubnt@192.168.1.20's password: XW.v6.1.7# exit
Clone the true-systems/ubnt-openwrt-flashing repository and make it your current working directory. Update the username and/or IP address in the Makefile if necessary (it should match the SSH user@hostname destination used in the previous step).
$ git clone https://github.com/true-systems/ubnt-openwrt-flashing $ cd ubnt-openwrt-flashing/ $ git rev-parse HEAD 9a8fb9f0bb131aae6449771ead59190528956c73 $ grep ^REMOTE_UBNT Makefile REMOTE_UBNT ?= ubnt@192.168.1.20
Download the OpenWrt 19.07.5 factory image for the device.
$ wget 'https://downloads.openwrt.org/releases/19.07.5/targets/ath79/generic/openwrt-19.07.5-ath79-generic-ubnt_bullet-m-xw-squashfs-factory.bin' $ sha256sum openwrt-19.07.5-ath79-generic-ubnt_bullet-m-xw-squashfs-factory.bin e2784584dc8a0dc8fca775d98ebac4796f94e85bde8f422b944511bb834be3b5 openwrt-19.07.5-ath79-generic-ubnt_bullet-m-xw-squashfs-factory.bin
Run the automated flashing process. A backup of the stock firmware will be saved to firmware-backup.bin—you may want to keep this file in case it is necessary to revert to the stock firmware in the future.
1 $ make flash-factory FW_OWRT=openwrt-19.07.5-ath79-generic-ubnt_bullet-m-xw-squashfs-factory.bin 2 scp ubnt@192.168.1.20:/sbin/ubntbox ubntbox 3 ubnt@192.168.1.20's password: 4 ubntbox 100% 698KB 1.5MB/s 00:00 5 ssh-copy-id ubnt@192.168.1.20 6 /usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/user/.ssh/id_rsa.pub" 7 /usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed 8 /usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys 9 ubnt@192.168.1.20's password: 10 11 Number of key(s) added: 1 12 13 Now try logging into the machine, with: "ssh 'ubnt@192.168.1.20'" 14 and check to make sure that only the key(s) you wanted were added. 15 16 Creating factory firmware backup 17 ssh ubnt@192.168.1.20 "cat /dev/mtd2 /dev/mtd3" > firmware-backup.bin 18 ssh ubnt@192.168.1.20 "umount /tmp; mount -t tmpfs tmpfs /tmp" 19 umount: can't unmount /tmp: Invalid argument 20 scp ubntbox.patched ubnt@192.168.1.20:/tmp/fwupdate.real 21 ubntbox.patched 100% 698KB 1.3MB/s 00:00 22 scp openwrt-19.07.5-ath79-generic-ubnt_bullet-m-xw-squashfs-factory.bin ubnt@192.168.1.20:/tmp 23 openwrt-19.07.5-ath79-generic-ubnt_bullet-m-xw 100% 4096KB 1.3MB/s 00:03 24 ssh ubnt@192.168.1.20 "/tmp/fwupdate.real -m /tmp/openwrt-19.07.5-ath79-generic-ubnt_bullet-m-xw-squashfs-factory.bin -d 2>&1" | tee flash-factory.log 25 Found mtd block: /dev/mtd0(u-boot) 26 Found mtd block: /dev/mtd1(u-boot-env) 27 Found mtd block: /dev/mtd2(kernel) 28 Found mtd block: /dev/mtd3(rootfs) 29 Found mtd block: /dev/mtd4(cfg) 30 Found mtd block: /dev/mtd5(EEPROM) 31 Got U-Boot variable: mtdparts = mtdparts=ath-nor0:256k(u-boot),64k(u-boot-env),1024k(kernel),6528k(rootfs),256k(cfg),64k(EEPROM) 32 Adding U-Boot partition: u-boot 9F000000 00040000 33 Adding U-Boot partition: u-boot-env 9F040000 00010000 34 Adding U-Boot partition: kernel 9F050000 00100000 35 Adding U-Boot partition: rootfs 9F150000 00660000 36 Adding U-Boot partition: cfg 9F7B0000 00040000 37 Adding U-Boot partition: EEPROM 9F7F0000 00010000 38 Calculating flash size: 39 Adding block: /dev/mtd0("u-boot") - size: 00040000 40 Adding block: /dev/mtd1("u-boot-env") - size: 00010000 41 Adding block: /dev/mtd2("kernel") - size: 00100000 42 Adding block: /dev/mtd3("rootfs") - size: 00660000 43 Adding block: /dev/mtd4("cfg") - size: 00040000 44 Adding block: /dev/mtd5("EEPROM") - size: 00010000 45 Total flash size: 00800000 46 Flash start: 9F000000 47 Flash end: 9F800000 48 Header MAGIC 'OPEN' 49 Current: XW.ar934x.v6.1.7.32555.180523.1754 50 51 New ver: XW.ar934x.v6.0.4-42.OpenWrt-r11257-5090152ae3 52 Versions: New(393220) 6.0.4, Required(393220) 6.0.4 53 FW Part: "kernel"(1), MAGIC: 'PART', Base: 0x9F050000, DLen: 0x00100000, PLen: 0x00100000 54 FW Part: "rootfs"(2), MAGIC: 'PART', Base: 0x9F150000, DLen: 0x00300004, PLen: 0x00660000 55 Signature verified 56 Signature MAGIC 'END.' 57 FW Part: "kernel"(1), MAGIC: 'PART', Base: 0x9F050000, DLen: 0x00100000, PLen: 0x00100000 58 FW Part: "rootfs"(2), MAGIC: 'PART', Base: 0x9F150000, DLen: 0x00300004, PLen: 0x00660000 59 Adding adjusted FW partition: 60 name:'kernel' 61 flash_base:0x9F050000 62 mem_base:0x80002000 63 size:0x00100000 64 entry_point:0x80002000 65 data_len:0x00100000 66 desc_cksum:0x00000000 67 file_cksum:0x00000000 68 =========================== 69 Adding adjusted FW partition: 70 name:'rootfs' 71 flash_base:0x9F150000 72 mem_base:0xBDBDBDBD 73 size:0x00660000 74 entry_point:0xBDBDBDBD 75 data_len:0x00300004 76 desc_cksum:0x00000000 77 file_cksum:0x00000000 78 =========================== 79 Signature MAGIC 'END.' 80 Working(1) with block: /dev/mtd0 81 Skipping: artificial: 0, unallocated: 0,writeable: 1024(WRITEABLE: 400, flags: C00), fw.flash_base: 9F050000 < blk->base: 9F000000 + blk->size: 40000 82 Copying FIS partition: 0 => 83 name:'u-boot' 84 flash_base:0x9F000000 85 mem_base:0xBDBDBDBD 86 size:0x00040000 87 entry_point:0xBDBDBDBD 88 data_len:0x00040000 89 desc_cksum:0xBDBDBDBD 90 file_cksum:0xBDBDBDBD 91 =========================== 92 Working(1) with block: /dev/mtd1 93 Skipping: artificial: 0, unallocated: 0,writeable: 1024(WRITEABLE: 400, flags: C00), fw.flash_base: 9F050000 < blk->base: 9F040000 + blk->size: 10000 94 Copying FIS partition: 1 => 95 name:'u-boot-env' 96 flash_base:0x9F040000 97 mem_base:0xBDBDBDBD 98 size:0x00010000 99 entry_point:0xBDBDBDBD 100 data_len:0x00010000 101 desc_cksum:0xBDBDBDBD 102 file_cksum:0xBDBDBDBD 103 =========================== 104 Working(1) with block: /dev/mtd2 105 Working(2) with block: /dev/mtd2 106 End check: 9F050000 + 00100000 <= 9F150000 107 Creating FIS partition: 2 => 108 name:'kernel' 109 flash_base:0x9F050000 110 mem_base:0x80002000 111 size:0x00100000 112 entry_point:0x80002000 113 data_len:0x00100000 114 desc_cksum:0x00000000 115 file_cksum:0x00000000 116 =========================== 117 Working(1) with block: /dev/mtd2 118 Skipping: artificial: 0, unallocated: 0,writeable: 1024(WRITEABLE: 400, flags: C00), fw.flash_base: 9F150000 < blk->base: 9F050000 + blk->size: 100000 119 Working(1) with block: /dev/mtd3 120 Working(2) with block: /dev/mtd3 121 End check: 9F150000 + 00660000 <= 9F7B0000 122 Creating FIS partition: 3 => 123 name:'rootfs' 124 flash_base:0x9F150000 125 mem_base:0xBDBDBDBD 126 size:0x00660000 127 entry_point:0xBDBDBDBD 128 data_len:0x00300004 129 desc_cksum:0x00000000 130 file_cksum:0x00000000 131 =========================== 132 Working(3) with block: /dev/mtd4 133 Copying FIS partition: 4 <= 134 name:'cfg' 135 flash_base:0x9F7B0000 136 mem_base:0xBDBDBDBD 137 size:0x00040000 138 entry_point:0xBDBDBDBD 139 data_len:0x00040000 140 desc_cksum:0xBDBDBDBD 141 file_cksum:0xBDBDBDBD 142 =========================== 143 Working(3) with block: /dev/mtd5 144 Copying FIS partition: 5 <= 145 name:'EEPROM' 146 flash_base:0x9F7F0000 147 mem_base:0xBDBDBDBD 148 size:0x00010000 149 entry_point:0xBDBDBDBD 150 data_len:0x00010000 151 desc_cksum:0xBDBDBDBD 152 file_cksum:0xBDBDBDBD 153 =========================== 154 New FIS entries count 6 155 Executing: '/bin/updatefix.sh 393479 393220' 156 Current ver: 393479 157 New version: 393220 158 No need to fix. 159 '/bin/updatefix.sh 393479 393220' result: 0 160 Working(1) with block: /dev/mtd0 161 Skipping: artificial: 0, unallocated: 0,writeable: 1024(WRITEABLE: 400, flags: C00), fw.flash_base: 9F050000 < blk->base: 9F000000 + blk->size: 40000 162 Copying FIS partition: 0 => 163 name:'u-boot' 164 flash_base:0x9F000000 165 mem_base:0xBDBDBDBD 166 size:0x00040000 167 entry_point:0xBDBDBDBD 168 data_len:0x00040000 169 desc_cksum:0xBDBDBDBD 170 file_cksum:0xBDBDBDBD 171 =========================== 172 Working(1) with block: /dev/mtd1 173 Skipping: artificial: 0, unallocated: 0,writeable: 1024(WRITEABLE: 400, flags: C00), fw.flash_base: 9F050000 < blk->base: 9F040000 + blk->size: 10000 174 Copying FIS partition: 1 => 175 name:'u-boot-env' 176 flash_base:0x9F040000 177 mem_base:0xBDBDBDBD 178 size:0x00010000 179 entry_point:0xBDBDBDBD 180 data_len:0x00010000 181 desc_cksum:0xBDBDBDBD 182 file_cksum:0xBDBDBDBD 183 =========================== 184 Working(1) with block: /dev/mtd2 185 Working(2) with block: /dev/mtd2 186 Unlocking /dev/mtd2(kernel) ... 187 Unlocking device failed: ioctl(MEMUNLOCK): Operation not supported 188 Writing 'kernel ' to /dev/mtd2(kernel ) ... 189 Writing: 0x00100000 bytes with offset 0x00000000 190 [%0 ] 191 Block on '/dev/mtd2' at 00000000(len: 00010000) has changes. [%6 ] 192 Block on '/dev/mtd2' at 00010000(len: 00010000) has changes. [%12 ] 193 Block on '/dev/mtd2' at 00020000(len: 00010000) has changes. [%18 ] 194 Block on '/dev/mtd2' at 00030000(len: 00010000) has changes. [%25 ] 195 Block on '/dev/mtd2' at 00040000(len: 00010000) has changes. [%31 ] 196 Block on '/dev/mtd2' at 00050000(len: 00010000) has changes. [%37 ] 197 Block on '/dev/mtd2' at 00060000(len: 00010000) has changes. [%43 ] 198 Block on '/dev/mtd2' at 00070000(len: 00010000) has changes. [%50 ] 199 Block on '/dev/mtd2' at 00080000(len: 00010000) has changes. [%56 ] 200 Block on '/dev/mtd2' at 00090000(len: 00010000) has changes. [%62 ] 201 Block on '/dev/mtd2' at 000A0000(len: 00010000) has changes. [%68 ] 202 Block on '/dev/mtd2' at 000B0000(len: 00010000) has changes. [%75 ] 203 Block on '/dev/mtd2' at 000C0000(len: 00010000) has changes. [%81 ] 204 Block on '/dev/mtd2' at 000D0000(len: 00010000) has changes. [%87 ] 205 Block on '/dev/mtd2' at 000E0000(len: 00010000) has changes. [%93 ] 206 Block on '/dev/mtd2' at 000F0000(len: 00010000) has changes. [%100] 207 208 End check: 9F050000 + 00100000 <= 9F150000 209 Creating FIS partition: 2 => 210 name:'kernel' 211 flash_base:0x9F050000 212 mem_base:0x80002000 213 size:0x00100000 214 entry_point:0x80002000 215 data_len:0x00100000 216 desc_cksum:0x00000000 217 file_cksum:0x00000000 218 =========================== 219 Working(1) with block: /dev/mtd2 220 Skipping: artificial: 0, unallocated: 0,writeable: 1024(WRITEABLE: 400, flags: C00), fw.flash_base: 9F150000 < blk->base: 9F050000 + blk->size: 100000 221 Working(1) with block: /dev/mtd3 222 Working(2) with block: /dev/mtd3 223 Unlocking /dev/mtd3(rootfs) ... 224 Unlocking device failed: ioctl(MEMUNLOCK): Operation not supported 225 Writing 'rootfs ' to /dev/mtd3(rootfs ) ... 226 Writing: 0x00300004 bytes with offset 0x00000000 227 [%0 ] 228 Block on '/dev/mtd3' at 00000000(len: 00010000) has changes. [%2 ] 229 Block on '/dev/mtd3' at 00010000(len: 00010000) has changes. [%4 ] 230 Block on '/dev/mtd3' at 00020000(len: 00010000) has changes. [%6 ] 231 Block on '/dev/mtd3' at 00030000(len: 00010000) has changes. [%8 ] 232 Block on '/dev/mtd3' at 00040000(len: 00010000) has changes. [%10 ] 233 Block on '/dev/mtd3' at 00050000(len: 00010000) has changes. [%12 ] 234 Block on '/dev/mtd3' at 00060000(len: 00010000) has changes. [%14 ] 235 Block on '/dev/mtd3' at 00070000(len: 00010000) has changes. [%16 ] 236 Block on '/dev/mtd3' at 00080000(len: 00010000) has changes. [%18 ] 237 Block on '/dev/mtd3' at 00090000(len: 00010000) has changes. [%20 ] 238 Block on '/dev/mtd3' at 000A0000(len: 00010000) has changes. [%22 ] 239 Block on '/dev/mtd3' at 000B0000(len: 00010000) has changes. [%24 ] 240 Block on '/dev/mtd3' at 000C0000(len: 00010000) has changes. [%27 ] 241 Block on '/dev/mtd3' at 000D0000(len: 00010000) has changes. [%29 ] 242 Block on '/dev/mtd3' at 000E0000(len: 00010000) has changes. [%31 ] 243 Block on '/dev/mtd3' at 000F0000(len: 00010000) has changes. [%33 ] 244 Block on '/dev/mtd3' at 00100000(len: 00010000) has changes. [%35 ] 245 Block on '/dev/mtd3' at 00110000(len: 00010000) has changes. [%37 ] 246 Block on '/dev/mtd3' at 00120000(len: 00010000) has changes. [%39 ] 247 Block on '/dev/mtd3' at 00130000(len: 00010000) has changes. [%41 ] 248 Block on '/dev/mtd3' at 00140000(len: 00010000) has changes. [%43 ] 249 Block on '/dev/mtd3' at 00150000(len: 00010000) has changes. [%45 ] 250 Block on '/dev/mtd3' at 00160000(len: 00010000) has changes. [%47 ] 251 Block on '/dev/mtd3' at 00170000(len: 00010000) has changes. [%49 ] 252 Block on '/dev/mtd3' at 00180000(len: 00010000) has changes. [%52 ] 253 Block on '/dev/mtd3' at 00190000(len: 00010000) has changes. [%54 ] 254 Block on '/dev/mtd3' at 001A0000(len: 00010000) has changes. [%56 ] 255 Block on '/dev/mtd3' at 001B0000(len: 00010000) has changes. [%58 ] 256 Block on '/dev/mtd3' at 001C0000(len: 00010000) has changes. [%60 ] 257 Block on '/dev/mtd3' at 001D0000(len: 00010000) has changes. [%62 ] 258 Block on '/dev/mtd3' at 001E0000(len: 00010000) has changes. [%64 ] 259 Block on '/dev/mtd3' at 001F0000(len: 00010000) has changes. [%66 ] 260 Block on '/dev/mtd3' at 00200000(len: 00010000) has changes. [%68 ] 261 Block on '/dev/mtd3' at 00210000(len: 00010000) has changes. [%70 ] 262 Block on '/dev/mtd3' at 00220000(len: 00010000) has changes. [%72 ] 263 Block on '/dev/mtd3' at 00230000(len: 00010000) has changes. [%74 ] 264 Block on '/dev/mtd3' at 00240000(len: 00010000) has changes. [%77 ] 265 Block on '/dev/mtd3' at 00250000(len: 00010000) has changes. [%79 ] 266 Block on '/dev/mtd3' at 00260000(len: 00010000) has changes. [%81 ] 267 Block on '/dev/mtd3' at 00270000(len: 00010000) has changes. [%83 ] 268 Block on '/dev/mtd3' at 00280000(len: 00010000) has changes. [%85 ] 269 Block on '/dev/mtd3' at 00290000(len: 00010000) has changes. [%87 ] 270 Block on '/dev/mtd3' at 002A0000(len: 00010000) has changes. [%89 ] 271 Block on '/dev/mtd3' at 002B0000(len: 00010000) has changes. [%91 ] 272 Block on '/dev/mtd3' at 002C0000(len: 00010000) has changes. [%93 ] 273 Block on '/dev/mtd3' at 002D0000(len: 00010000) has changes. [%95 ] 274 Block on '/dev/mtd3' at 002E0000(len: 00010000) has changes. [%97 ] 275 Block on '/dev/mtd3' at 002F0000(len: 00010000) has changes. [%99 ] 276 Block on '/dev/mtd3' at 00300000(len: 00000004) has changes. [%100] 277 278 End check: 9F150000 + 00660000 <= 9F7B0000 279 Creating FIS partition: 3 => 280 name:'rootfs' 281 flash_base:0x9F150000 282 mem_base:0xBDBDBDBD 283 size:0x00660000 284 entry_point:0xBDBDBDBD 285 data_len:0x00300004 286 desc_cksum:0x00000000 287 file_cksum:0x00000000 288 =========================== 289 Working(3) with block: /dev/mtd4 290 Copying FIS partition: 4 <= 291 name:'cfg' 292 flash_base:0x9F7B0000 293 mem_base:0xBDBDBDBD 294 size:0x00040000 295 entry_point:0xBDBDBDBD 296 data_len:0x00040000 297 desc_cksum:0xBDBDBDBD 298 file_cksum:0xBDBDBDBD 299 =========================== 300 Working(3) with block: /dev/mtd5 301 Copying FIS partition: 5 <= 302 name:'EEPROM' 303 flash_base:0x9F7F0000 304 mem_base:0xBDBDBDBD 305 size:0x00010000 306 entry_point:0xBDBDBDBD 307 data_len:0x00010000 308 desc_cksum:0xBDBDBDBD 309 file_cksum:0xBDBDBDBD 310 =========================== 311 New FIS entries count 6 312 FIS Change: change in partition entry 'kernel'. 313 FIS Change: change in partition entry 'rootfs'. 314 FIS Change: added partition terminator instead of 0xBD. 315 New partition count: 6, changes: 3 316 Done
At this point, or soon after, OpenWrt should be running and accessible at 192.168.1.1. You may need to press Ctrl-C to exit, due to the SSH session not being terminating properly when the final firmware update completes, but make sure you don’t interrupt any part of the flashing process (if in doubt, wait).
Updating OpenWrt
Once OpenWrt has been installed, it can be updated with an OpenWrt sysupgrade image.
Warning
See the warning above about the Ethernet port problem.
An OpenWrt sysupgrade has been tested with OpenWrt 19.07.10 and with r25153-869df9ecdf.
$ sha256sum openwrt-19.07.10-ath79-generic-ubnt_bullet-m-xw-squashfs-sysupgrade.bin
806ddc698b545b97ce3ed9d6cf4830de5c24da9ba31d1f1040be9f3d4e5a4965 openwrt-19.07.10-ath79-generic-ubnt_bullet-m-xw-squashfs-sysupgrade.bin
Using TFTP recovery
If something goes wrong at some point after a successful installation of OpenWrt (for example a failed upgrade or corrupted filesystem) and you need to flash OpenWrt again, you can use TFTP recovery as follows.
Warning
Do not use this method for the initial installation of OpenWrt; see Installing OpenWrt 19.07.5 above instead in that case.
Download the OpenWrt factory image to be flashed to the device. Save this file to /tmp/openwrt.bin or a similar location.
$ sha256sum openwrt-19.07.8-ath79-generic-ubnt_bullet-m-xw-squashfs-factory.bin d0f3fbdba489026fab60872f0646f43eb691eb2b27af014a4656c2f2ac69ca24 openwrt-19.07.8-ath79-generic-ubnt_bullet-m-xw-squashfs-factory.bin
Install the tftp tool. On Debian GNU/Linux for example, run apt install tftp.
Disconnect power from the device. Use a paperclip to press and hold the reset button (either on the device or on the power-over-Ethernet injector, if it has one). While holding the reset button, connect power. Wait 25 seconds, or until the LED indicates upgrade mode is active; then release the button.
Change to the OpenWrt image location, run tftp, and enter the commands shown here:
$ cd /tmp $ tftp tftp> connect 192.168.1.20 tftp> binary tftp> rexmt 1 tftp> timeout 60 tftp> put openwrt.bin
The file should begin transferring. When it completes, you should see Sent 4194716 bytes in 2.5 seconds (for example). The device will flash the firmware and reboot when it is finished (do not reboot it yourself). Once it is finished, you should be able to connect to the router at 192.168.1.1.
Restoring the stock firmware
Before the OpenWrt image is flashed in the previous section, a backup of the AirOS v6.1.7 firmware is written to firmware-backup.bin. To restore this firmware, use the corresponding make target, providing the correct SSH user@hostname destination for the device. See Restore from OpenWrt back to factory image in the true-systems/ubnt-openwrt-flashing README.
This worked for me from OpenWrt 19.07.8.
$ cd ubnt-openwrt-flashing/
$ stat -c "%s bytes" firmware-backup.bin
7733248 bytes
$ sha256sum firmware-backup.bin
b0fb2d237cc689402b0a6d4bb8adf9f5f97748ee23f03c5bc7b4b0a69b30e357 firmware-backup.bin
$ make restore REMOTE_OWRT=root@192.168.1.1
(Your firmware backup may not be the same as the one I tested.)
Credits
OpenWrt is an excellent Linux distribution for embedded devices.
True Systems shared two ways to work around Ubiquiti’s firmware signing, one of which is shown here.
See also
Ubiquiti Bullet M2 / M5 on the OpenWrt wiki
The true-systems/ubnt-openwrt-flashing repository on GitHub
AirMAX M Series Downloads from Ubiquiti
Ubiquiti Bullet versions compared on data-alliance
UniFi - TFTP Recovery for Bricked Access Points on Ubiquiti Support
Changes
2024-02-15: Add details about the Ethernet fix
2024-02-08: Update source repository commit and remove scp -O step that is now unnecessary
2023-10-21: Use AirOS default settings, add temporary SSH host configuration, update source repository commit, add scp -O option, update tested OpenWrt version to 19.07.10
2022-02-24: Updated the warning about OpenWrt 21.02.1
2022-02-12: Updated Restoring the stock firmware section since testing the restore process
2022-01-28: Added the Using TFTP recovery section
2022-01-28: Added a warning about OpenWrt 21.02.1 in the Updating OpenWrt section
2022-01-28: Updated the true-systems/ubnt-openwrt-flashing repository commit (no relevant changes upstream)
2021-04-03: Added the Updating OpenWrt section