← All projects

Cross-flashing Tripp Lite B093 console servers to Opengear firmware

Cross-flashing a Tripp Lite console server to Opengear firmware in my lab. The vendor's own upgrade tool bricked it, so I worked out the path that does work and built a fail-closed tool around it.

in progress
  • console servers
  • firmware
  • embedded linux
  • python
  • safety

Console servers are the thing you reach for when everything else is down. That makes them awkward to upgrade: if one fails mid-flash, the box you would normally use to rescue it is the box that failed.

This project is about cross-flashing a Tripp Lite B093 console server to Opengear ACM700x firmware. Under the Tripp Lite badge it’s Opengear ACM7000-family hardware, so the firmware should fit. The interesting question is whether it can be done safely over SSH alone, with nobody standing next to the box.

What the “normal” upgrade does

My lab unit has a serial cable attached, so I could recover it if things went wrong. They did.

netflash, the tool behind the web UI upgrade, zero-touch provisioning and the central management “node upgrade”, erased the image slot and then failed to write it (check failed, pos=0). The unit was left with nothing bootable.

That would be bad on any device. On this one it’s especially bad, because of the next finding.

What I learned about the hardware

  • There is no A/B fallback. U-Boot always boots image_1. If image_1 is bad, nothing else is tried. Without a serial cable, a failed write means someone has to go to the box.
  • What does work is a UBI write. The image slots are UBI volumes exposed as MTD devices through gluebi. Both ubiupdatevol on the volume and dd onto the gluebi MTD device worked, as long as the dd writes are aligned to the erase-block size reported in /proc/mtd. Gluebi maps MTD writes onto whole UBI logical erase blocks, so a misaligned write isn’t safe.
  • A hardware watchdog resets the unit after about 258 seconds without being fed. A unit that hangs mid-upgrade reboots on its own into whatever is in image_1, so the erase, write and verify window has to be short and must end with a verified image.
  • The U-Boot environment is CRC32-protected. Changing something like bootdelay means rebuilding the environment blob with a correct checksum. Get the CRC wrong and U-Boot silently falls back to its built-in defaults.
  • Other models may be different hardware. I’ve only proven this on a B093, so the B097 and B098 are treated as unknown.

I recovered the lab unit over serial using U-Boot’s program_image, and it now runs Opengear firmware.

Why a tool and not a runbook

Doing this by hand from a checklist, more than once, is exactly where mistakes happen, and one mistake means a dead unit. So I wrote a small web app and CLI (Python, FastAPI, SQLite) whose job is to make the process repeatable (same checks, same order, every unit) and auditable (every action logged with who, when and what).

Version 1 is deliberately read-only plus dry-run:

Feature Why it exists
Inventory with a fallback access path per unit If SSH dies mid-upgrade you need to know how else to reach that box.
Mandatory config backup, SHA-256 per file A failed upgrade can come back at factory defaults. No backup, no upgrade.
Read-only preflight: model, firmware, /proc/mtd, free space, UBI info Prove each unit matches the hardware we rehearsed on before anything touches flash.
Hard guards that block a unit Unknown model, unexpected flash layout, stale backup or low free space: blocked, with the reason shown.
Flash dry-run Prints the exact command sequence for that unit, using its real erase-block size, with the reason for each step.
Audit log Every import, backup, preflight and dry-run is recorded.

And one rule enforced by a test: the string netflash may not appear anywhere in the app code. The build fails if it does.

Fail closed

The write path lives in a single module, and everything else is checked against a list of forbidden commands (dd, flash_erase, fw_setenv, reboot, shell redirects and so on). Before a write would even be considered, four independent conditions all have to be true:

  1. A global kill switch is on (it’s off by default, and hard-coded off in the deployment file).
  2. The unit has been marked rehearsal-verified, which can only be done from the CLI, with a written note.
  3. Its latest preflight says eligible.
  4. It has a successful config backup from the last 24 hours, re-checked at flash time.

Each one guards against a different mistake: a global accident, the wrong unit, unexpected hardware, or losing the config. In v1 the write step refuses to run even with all four true.

What’s next

The write path stays disabled until a rehearsal on the lab unit passes, one phase at a time and with serial attached as the safety net:

  1. Rewrite image_1 with the identical image on a live system, then verify the hash on read-back. This proves the write path with zero functional change.
  2. Set bootdelay=3 by rewriting the U-Boot environment with a correct CRC32, so every future boot has a serial interrupt window.
  3. Revert the unit to Tripp Lite firmware so the next test starts from stock.
  4. Do the full upgrade over SSH only, with no serial interaction unless something goes wrong.

Only after all four pass does the real write step get written. I’ll update this page as each phase lands.

One more idea in the queue: both firmwares ship GPL components (Linux, U-Boot, busybox, mtd-utils), so requesting the corresponding source from Opengear and Eaton (Tripp Lite’s parent) would turn several of the observations above into confirmed facts.