Rescuing an UniFi Cloud Key Gen2 Plus

A non-working eBay item gets saved from the scrap heap, and I get a cheap upgrade.

Written .

When I found a non-working Cloud Key Gen2 Plus on eBay for 99 cents, I decided, why not? (This was before I heard that UniFi Network App 7.3 would drop support for the now-vintage first-generation Cloud Key.)

The Cloud Key Gen2 would be sitting inside a closet, so I wasn't too concerned that this thing had been physically damaged, cracked and dented, and sold without the internal hard drive. Fortunately, it arrived and looked better than I expected.

My new-to-me UniFi Cloud Key Gen2 Plus.
My damaged and unbootable UniFi Cloud Key Gen2 Plus. It looks fine from this angle, but there is cosmetic damage around the rear USB-C power port.

Disassembly

To start, remove the back plastic. It's glued on, so use a spudger or guitar pick to break the adhesive. Once you've gotten that off, there are seven Phillips-head screws underneath to remove.

Removing the back plastic of the UniFi Cloud Key Gen2 Plus. The UniFi Cloud Key Gen2 Plus once the back plastic has been removed.  Note the screws.
Remove the black plastic on the rear of the UniFi Cloud Key Gen2 Plus. You can see some damage on the black plastic around the USB-C power port.

To continue disassembly, we will need to remove the hard drive and the latch itself. Slide the latch over to remove the hard drive and the tray. Then, use the end of a flat-head screwdriver or metal spudger, gently press on one of the tabs to pop it loose. Hold the metal slider in position, then carefully remove the plastic tab.

The hard drive bay and latching mechanism inside a UniFi Cloud Key Gen2 Plus. The hard drive latch removed from a UniFi Cloud Key Gen2 Plus.
Gently push the latch's tabs to pop it free. Be careful not to break it. I'm sure there's a better tool to use to remove this thing.

Now, we need to remove two screws underneath the screen. The screen is held on by adhesive, like everything else these days. Use a spudger or plastic opening tool to carefully pry it up. Note that there is an unremovable ribbon cable on the bottom edge. Take care not to sever it. (Ubiquiti does not sell spare parts. I've tried.)

Breaking the screen adhesive on a UniFi Cloud Key Gen2 Plus. The display ribbon cable of a UniFi Cloud Key Gen2 Plus.
The display is glued on. Carefully break the adhesive. However, be careful not to break the display cable along the bottom edge.

Next, the 13-pin connector for the Cloud Key Gen2 Rack Mount Accessory needs to be removed. There are four latches on the underside to make sure that it sits flush. This also means that removing it is next to impossible. You can try to carefully pry it up, but I had luck pushing it up from underneath. If you try that, be sure to push up on the plastic, and not on the wires themselves!

Once you have all four tabs disengaged, unplug the connector. Be sure to mark it, or remember which way it goes so you can reassemble it later.

Removing the 13-pin rackmount port from the UniFi Cloud Key Gen2 Plus.
The rack mounting port is exceedingly tough to remove.

Finally, the entire plastic unit can be slid out of the aluminum housing. Normally, this will not be a problem. However, these batteries do seem to be defective, because they tend to swell up and fail, so I hear -- and now, so I see firsthand.

If your battery is bad, let's grab a plastic, non-conductive tool and gently break the adhesive holding it to the plastic tray.

Ubiquiti does not sell replacement batteries, instead asking you to spend $200 on a new Cloud Key. They also refused to issue an RMA for this spicy pillow, so into the e-waste bin this little fire hazard goes. Ubiquiti gave up on trying to fix the problem, instead removing the battery entirely from the latest revision of the Cloud Key Gen2 Plus.

The failed and swollen internal battery in a UniFi Cloud Key Gen2 Plus. The internal battery from a UniFi Cloud Key Gen2 Plus.
Remove the battery. If it's bad, please recycle it safely and properly.

The Cloud Key is now fully disassembled. If you were reading this to remove the battery, congratulations! You're done. Reverse these steps to put this back together (hopefully with a working battery).

Unflashable Firmware?

In my case, this Cloud Key wouldn't boot. I booted it up in recovery mode and attempted to re-flash the firmware, but it refused to accept any firmware image; it refused to accept an image of the same version 2.0.27, but it also refused to accept newer and older versions, too.

Since I already had this thing apart, I hooked up my TTL serial adapter to the three-pin header located at port J22.

The internal serial port (J22, circled) inside the UniFi Cloud Key Gen2 Plus.
Header J22 is a 3.3V TTL serial port. The other one seems to be, too, but I haven't figured out what that might be for.

After getting the pinout right, I was greeted with a serial console!

Please press Enter to activate this console. 

Boottime: 4.52s

cloudkey-apq8053 login: ubnt
Password: 
Login incorrect
cloudkey-apq8053 login: root
Password: 
				.--.__
	______ __ .--(    ) )-.   __ __
	|      |  (._____.__.___)_|  |  |__ _____ __ __
	|   ---|  ||  _  |  |  |  _  |    <|  -__|  |  |
	|______|__||_____|_____|_____|__|__|_____|___  |
		(c) 2018 Ubiquiti Networks, Inc.  |_____|

		Welcome to the CloudKey Recovery!
Hey, look! A serial console! I figured the username and password would be one of the usual two.

Fortunately, the default usernames and password are well-known. Once I logged in, I began to browse the recovery filesystem. I checked the boot messages and dmesg output but found nothing of interest.

However, I found an app in the filesystem called ubnt-tool, which has a mode called fwupdate. I figured it'd be worth a shot. I grabbed the URL of the latest firmware blob, downloaded it to the device, and installed it.

cd /tmp
wget https://fw-download.ubnt.com/data/unifi-cloudkey/6fac-UCKP-2.5.11-c5a57cf5d5344114a762782ab4d3a940.bin
ubnt-tool fwupdate 6fac-UCKP-2.5.11-c5a57cf5d5344114a762782ab4d3a940.bin
View the uninteresting output:
Firmware file: '6fac-UCKP-2.5.11-c5a57cf5d5344114a762782ab4d3a940.bin'
DEBUG: Firmware version: 'UCKP.apq8053.v2.5.11.b2ebfc7.220801.1419'
DEBUG: Partition: sbl1 [0]
DEBUG:   Partition size: 0x80000
DEBUG:   Data length: 380572
DEBUG: Partition: devcfg [1]
DEBUG:   Partition size: 0x40000
DEBUG:   Data length: 37772
DEBUG: Partition: boot [2]
DEBUG:   Partition size: 0x4000000
DEBUG:   Data length: 11079680
DEBUG: Partition: rootfs [3]
DEBUG:   Partition size: 0x80000000
DEBUG:   Data length: 633630720
DEBUG: found ENDS at 0x2673e434 
DEBUG: Message Digest successfully verfied.
DEBUG: writing 'sbl1' to '/dev/mmcblk0'...
blk_size: 800000, to_verify: 1
DEBUG: 
Writing: 0x0005ce9c bytes with offset 0x00006800
[%0  ]DEBUG: 
DEBUG: Block at 00006800(len: 0005CE9C) has no changes.
[%100]DEBUG: 
DEBUG: writing 'devcfg' to '/dev/mmcblk0'...
blk_size: 800000, to_verify: 1
DEBUG: 
Writing: 0x0000938c bytes with offset 0x00606800
[%0  ]DEBUG: 
DEBUG: Block at 00606800(len: 0000938C) has no changes.
[%100]DEBUG: 
DEBUG: writing 'boot' to '/dev/mmcblk0'...
blk_size: 800000, to_verify: 1
DEBUG: 
Writing: 0x00a91000 bytes with offset 0x34200000
[%0  ]DEBUG: 
DEBUG: Block at 34200000(len: 00800000) has no changes.
[%75 ]DEBUG: 
DEBUG: Block at 34A00000(len: 00291000) has no changes.
[%100]DEBUG: 
DEBUG: writing 'rootfs' to '/dev/mmcblk0'...
blk_size: 800000, to_verify: 1
DEBUG: 
Writing: 0x25c47000 bytes with offset 0x3c200000
[%0  ]DEBUG: 
DEBUG: Block at 3C200000(len: 00800000) has no changes.
[%1  ]DEBUG: 
DEBUG: Block at 3CA00000(len: 00800000) has no changes.
[%2  ]DEBUG: 
DEBUG: Block at 3D200000(len: 00800000) has no changes.
[%3  ]DEBUG: 
DEBUG: Block at 3DA00000(len: 00800000) has no changes.
[%5  ]DEBUG: 
DEBUG: Block at 3E200000(len: 00800000) has no changes.
[%6  ]DEBUG: 
DEBUG: Block at 3EA00000(len: 00800000) has no changes.
[%7  ]DEBUG: 
DEBUG: Block at 3F200000(len: 00800000) has no changes.
[%9  ]DEBUG: 
DEBUG: Block at 3FA00000(len: 00800000) has no changes.
[%10 ]DEBUG: 
DEBUG: Block at 40200000(len: 00800000) has no changes.
[%11 ]DEBUG: 
DEBUG: Block at 40A00000(len: 00800000) has no changes.
[%13 ]DEBUG: 
DEBUG: Block at 41200000(len: 00800000) has no changes.
[%14 ]DEBUG: 
DEBUG: Block at 41A00000(len: 00800000) has no changes.
[%15 ]DEBUG: 
DEBUG: Block at 42200000(len: 00800000) has no changes.
[%17 ]DEBUG: 
DEBUG: Block at 42A00000(len: 00800000) has no changes.
[%18 ]DEBUG: 
DEBUG: Block at 43200000(len: 00800000) has no changes.
[%19 ]DEBUG: 
DEBUG: Block at 43A00000(len: 00800000) has no changes.
[%21 ]DEBUG: 
DEBUG: Block at 44200000(len: 00800000) has no changes.
[%22 ]DEBUG: 
DEBUG: Block at 44A00000(len: 00800000) has no changes.
[%23 ]DEBUG: 
DEBUG: Block at 45200000(len: 00800000) has no changes.
[%25 ]DEBUG: 
DEBUG: Block at 45A00000(len: 00800000) has no changes.
[%26 ]DEBUG: 
DEBUG: Block at 46200000(len: 00800000) has no changes.
[%27 ]DEBUG: 
DEBUG: Block at 46A00000(len: 00800000) has no changes.
[%29 ]DEBUG: 
DEBUG: Block at 47200000(len: 00800000) has no changes.
[%30 ]DEBUG: 
DEBUG: Block at 47A00000(len: 00800000) has no changes.
[%31 ]DEBUG: 
DEBUG: Block at 48200000(len: 00800000) has no changes.
[%33 ]DEBUG: 
DEBUG: Block at 48A00000(len: 00800000) has no changes.
[%34 ]DEBUG: 
DEBUG: Block at 49200000(len: 00800000) has no changes.
[%35 ]DEBUG: 
DEBUG: Block at 49A00000(len: 00800000) has no changes.
[%37 ]DEBUG: 
DEBUG: Block at 4A200000(len: 00800000) has no changes.
[%38 ]DEBUG: 
DEBUG: Block at 4AA00000(len: 00800000) has no changes.
[%39 ]DEBUG: 
DEBUG: Block at 4B200000(len: 00800000) has no changes.
[%41 ]DEBUG: 
DEBUG: Block at 4BA00000(len: 00800000) has no changes.
[%42 ]DEBUG: 
DEBUG: Block at 4C200000(len: 00800000) has no changes.
[%43 ]DEBUG: 
DEBUG: Block at 4CA00000(len: 00800000) has no changes.
[%45 ]DEBUG: 
DEBUG: Block at 4D200000(len: 00800000) has no changes.
[%46 ]DEBUG: 
DEBUG: Block at 4DA00000(len: 00800000) has no changes.
[%47 ]DEBUG: 
DEBUG: Block at 4E200000(len: 00800000) has no changes.
[%48 ]DEBUG: 
DEBUG: Block at 4EA00000(len: 00800000) has no changes.
[%50 ]DEBUG: 
DEBUG: Block at 4F200000(len: 00800000) has no changes.
[%51 ]DEBUG: 
DEBUG: Block at 4FA00000(len: 00800000) has no changes.
[%52 ]DEBUG: 
DEBUG: Block at 50200000(len: 00800000) has no changes.
[%54 ]DEBUG: 
DEBUG: Block at 50A00000(len: 00800000) has no changes.
[%55 ]DEBUG: 
DEBUG: Block at 51200000(len: 00800000) has no changes.
[%56 ]DEBUG: 
DEBUG: Block at 51A00000(len: 00800000) has no changes.
[%58 ]DEBUG: 
DEBUG: Block at 52200000(len: 00800000) has no changes.
[%59 ]DEBUG: 
DEBUG: Block at 52A00000(len: 00800000) has no changes.
[%60 ]DEBUG: 
DEBUG: Block at 53200000(len: 00800000) has no changes.
[%62 ]DEBUG: 
DEBUG: Block at 53A00000(len: 00800000) has no changes.
[%63 ]DEBUG: 
DEBUG: Block at 54200000(len: 00800000) has no changes.
[%64 ]DEBUG: 
DEBUG: Block at 54A00000(len: 00800000) has no changes.
[%66 ]DEBUG: 
DEBUG: Block at 55200000(len: 00800000) has no changes.
[%67 ]DEBUG: 
DEBUG: Block at 55A00000(len: 00800000) has no changes.
[%68 ]DEBUG: 
DEBUG: Block at 56200000(len: 00800000) has no changes.
[%70 ]DEBUG: 
DEBUG: Block at 56A00000(len: 00800000) has no changes.
[%71 ]DEBUG: 
DEBUG: Block at 57200000(len: 00800000) has no changes.
[%72 ]DEBUG: 
DEBUG: Block at 57A00000(len: 00800000) has no changes.
[%74 ]DEBUG: 
DEBUG: Block at 58200000(len: 00800000) has no changes.
[%75 ]DEBUG: 
DEBUG: Block at 58A00000(len: 00800000) has no changes.
[%76 ]DEBUG: 
DEBUG: Block at 59200000(len: 00800000) has no changes.
[%78 ]DEBUG: 
DEBUG: Block at 59A00000(len: 00800000) has no changes.
[%79 ]DEBUG: 
DEBUG: Block at 5A200000(len: 00800000) has no changes.
[%80 ]DEBUG: 
DEBUG: Block at 5AA00000(len: 00800000) has no changes.
[%82 ]DEBUG: 
DEBUG: Block at 5B200000(len: 00800000) has no changes.
[%83 ]DEBUG: 
DEBUG: Block at 5BA00000(len: 00800000) has no changes.
[%84 ]DEBUG: 
DEBUG: Block at 5C200000(len: 00800000) has no changes.
[%86 ]DEBUG: 
DEBUG: Block at 5CA00000(len: 00800000) has no changes.
[%87 ]DEBUG: 
DEBUG: Block at 5D200000(len: 00800000) has no changes.
[%88 ]DEBUG: 
DEBUG: Block at 5DA00000(len: 00800000) has no changes.
[%90 ]DEBUG: 
DEBUG: Block at 5E200000(len: 00800000) has no changes.
[%91 ]DEBUG: 
DEBUG: Block at 5EA00000(len: 00800000) has no changes.
[%92 ]DEBUG: 
DEBUG: Block at 5F200000(len: 00800000) has no changes.
[%93 ]DEBUG: 
DEBUG: Block at 5FA00000(len: 00800000) has no changes.
[%95 ]DEBUG: 
DEBUG: Block at 60200000(len: 00800000) has no changes.
[%96 ]DEBUG: 
DEBUG: Block at 60A00000(len: 00800000) has no changes.
[%97 ]DEBUG: 
DEBUG: Block at 61200000(len: 00800000) has no changes.
[%99 ]DEBUG: 
DEBUG: Block at 61A00000(len: 00447000) has no changes.
[%100]DEBUG:
The ubnt-tool app can be used to update the firmware via a console. The copy of wget included with this old version of the recovery firmware didn't support TLS 1.2, so I had to change that URL to a plain HTTP one.

There were a lot of unchanged blocks for being so many years behind on firmware updates, but one reboot later, my Cloud Key was back up and running!

Links

UniFi Network App 7.3.69 Release Notes
https://community.ui.com/releases/UniFi-Network-Application-7-3-69/d801e00a-8d7d-4e52-b6db-d4d8d87835fb
Official response saying the UCK-G2-PLUS no longer contains a battery
https://community.ui.com/questions/Could-Key-Gen-2-Battery-DOA/7ccd440a-761f-40c4-8aea-1275f235639c#answer/8cbd5ae6-dca8-4235-8a09-84b78874f40d
List of Ubiquiti products' default credentials
https://help.ui.com/hc/en-us/articles/204909374-UniFi-Login-with-SSH-Advanced-
Console output containing boot and startup messages
http://colincogle.name/blog/unifi-cloud-key-rescue/UCK-G2-PLUS+boot+messages+v2.0.27.txt
Ubiquiti Downloads
https://www.ui.com/download/unifi/unifi-cloud-key-gen2/uck-g2-plus