openflash

openflash

Docker app from Stoatworks Labs' Repository

Overview

Installs LineageOS and other alternative Android systems onto a phone from a browser, over WebUSB. Detects the handset and generates the procedure for that exact device. Note: WebUSB needs a secure context. Served over plain http at a LAN address there is no navigator.usb at all, so the container cannot reach a device — open it at http://localhost:PORT on the machine holding the USB cable, or put a TLS terminator in front of it. This fails silently rather than loudly: the page loads and simply offers no device. Without WebUSB the page is still a correct, ordered, device-specific adb and fastboot command list, which is most of its value.

openflash

AI-assisted project. This codebase was created with Claude (Anthropic), directed and reviewed by a human author. I've run it end to end on a Pixel 4a, from bootloader unlock through to a booted LineageOS 23.2 with MindTheGapps, and the plan generator is checked against all 733 devices in the LineageOS database. That's one device and one of the two install engines: the other 553 procedures come out of the same code but have never been run against the hardware they describe.

Install LineageOS and other alternative Android systems from a browser. It talks to the phone over WebUSB, does the parts a machine should do, and tells you straight which parts only you can do.

Live at openflash.stoatworks-labs.com. Deep links work: ?device=sunfish&os=lineageos opens straight onto the procedure for a Pixel 4a.

Device detected as a Pixel 4a, with six operating systems offered and their support status
pick a device, pick a system
The generated procedure for a Pixel 4a, with manual and automatic steps and their equivalent commands
the procedure it generates for that pair

What it does

Autodetects the phone. Plug it in and the page reads its codename, model, Android version, security patch level, bootloader version, slot layout and lock state, over ADB if it's booted or over fastboot if it's in the bootloader. If the codename doesn't match the procedure you've selected, it refuses to carry on rather than flash one device's build onto another.

Generates the procedure for that exact device. You don't get a generic guide with "(on some devices)" scattered through it. The Pixel 4a gets its dtbo partition step and its stock-Android-13 firmware requirement; a Xiaomi gets told that Mi Unlock and a seven-day wait stand between it and anything else on the page; a Samsung gets told this tool can't flash it at all, because Odin isn't fastboot.

Runs the parts a machine should run. Unlocking, flashing partitions and adb sideload of the ROM zip all happen over WebUSB, with progress. The parts only a human can do (holding Volume Down, answering the phone's own unlock prompt, picking "Format data" out of a recovery menu) are marked as such and wait for you.

Shows the equivalent commands anyway. Every automated step prints the adb and fastboot commands it stands in for. That's what makes the page useful in Firefox and Safari, which have no WebUSB (you get a correct, ordered, device-specific command list instead), and it's how you check what the page is about to do before you let it.

Checks what you feed it. LineageOS publishes a machine-readable build manifest, so files you drop in are hashed in the page and checked against the SHA-256 for that exact build. A file that doesn't match doesn't get flashed.

Devices

The device database is generated from the LineageOS wiki's own _data/devices/*.yml, which is what drives their install pages. That's 733 devices across 28 install methods.

devices
Full procedure generated 554
Not flashable from a browser (Odin, dd, APX, EDL, …) 179
…of the 554, unlocked by a fastboot command 217
…unlocked only through the vendor's own portal 328
…already unlocked, or no unlock step 9

Regenerate after an upstream change:

npm run devices

Systems

how it installs what this tool knows
LineageOS recovery + sideload Everything: live build list, exact filenames, sizes and SHA-256 per build
/e/OS recovery + sideload Procedure only — Murena's servers block browser reads, so bring your own files
CalyxOS factory image zip Procedure only, and their device list moves; check it first
GrapheneOS factory image zip Procedure only — and they have their own official web installer, which you should use instead
iodéOS, DivestOS recovery + sideload Via JSON profiles in public/profiles/
Another ROM either, your choice Nothing — you bring the files and the project's own guide; the page drives the flash for your exact device

Any other ROM can be added as a profile: a dozen lines of JSON naming the project, its download page and which of the two engines it uses. No code.

Running it

npm install && npm run dev

It's a static site with no server component, so npm run build produces something you can drop on any static host. Deep links work: ?device=sunfish&os=lineageos opens straight onto the procedure for a Pixel 4a.

WebUSB needs Chrome, Edge, or another Chromium browser. Everything except the flashing itself works everywhere.

Stop your adb server first with adb kill-server. A running adb server grabs any Android device the moment it appears, and the browser then gets refused the same USB interface. It bites after the phone reboots into recovery, so the step that fails isn't the step that caused it.

Deploy: every push to main deploys through .github/workflows/deploy.yml (a static-assets Worker, not a Pages project). cf-run npm run deploy still does it by hand.

Checks

npm run check
  • check:sha256: the hand-written incremental SHA-256 against Node's crypto, at every length where the padding changes behaviour.
  • check:unlock: every distinct unlock command in the database parses into something sendable. This caught a real bug: free-form fastboot commands go on the wire with spaces (flashing unlock), not colons, and two Nubia devices carry a three-line unlock sequence with a reboot in the middle.
  • check:plans: a plan is generated for all 733 devices. No step may require a file the plan never asks for, and no plan may reach a sideload without formatting data first.

None of that touches a phone, which is the limitation that matters. See below.

Verified on hardware

A Pixel 4a (sunfish), 2026-08-25, complete run: device detection over ADB, fastboot flashing unlock, dtbo flashed, Lineage Recovery flashed to boot, data formatted, LineageOS 23.2 sideloaded, MindTheGapps sideloaded, first boot into a working LineageOS. Every step driven from the page.

Two defects that only a real handset could have found, both fixed along the way:

  • The USB interface was never released between transports. The phone reboots between bootloader and recovery several times per install, and the handle from before the reboot still held the interface, so the next claim failed with The device is already in used by another program. A local adb server causes the identical symptom, which is why the error now leads with adb kill-server.
  • A silent hang on the add-ons step. Add-on packages aren't signed with the OS project's key, so recovery refuses the signature and waits on an Install anyway? prompt on the handset. The browser can't see that prompt, and said nothing, so the page looked frozen while the phone was asking a question. It now reports how far the transfer got and where to look.

What that does and doesn't cover. One device, one vendor, one install method (fastboot_nexus), one OS. The factory-zip engine (GrapheneOS, CalyxOS) has never been run at all. The 553 other recovery-sideload procedures are generated by the code that produced this one, which is a reason for some confidence and not a substitute for trying it.

What this is not

It isn't a replacement for the LineageOS wiki. The wiki is the authority; this page is generated from its data and links back to the page for your device on every screen. Where the two disagree, the wiki is right and this is a bug.

It doesn't download ROMs for you, because it can't: the mirrors send no CORS headers, so a web page isn't allowed to fetch from them. You download, you drop the file in, and the page checks it.

It won't pretend a vendor-locked bootloader is one command away.

Run your own copy

openflash is a static page, so hosting it yourself is one container serving the built files — the same files the hosted copy serves, running somewhere that still works when the venue has no internet.

Docker. The image is built by this repo's docker.yml workflow on every push and published as ghcr.io/stoatworks-labs/openflash:

docker run -d --name openflash --restart unless-stopped -p 8536:80 ghcr.io/stoatworks-labs/openflash:latest

Or docker compose up -d with the docker-compose.yml in this repo, which maps the same port. Either way it is then at http://localhost:8536/.

Unraid. Search Community Applications for openflash — the template is templates/openflash.xml in stoatworks-unraid, which is what the CA feed reads.

Stoatworks Burrow lists it under Self-hosted, with the compose file a click away.

Note: WebUSB needs a secure context. Served over plain http at a LAN address there is no navigator.usb at all, so the container cannot reach a device — open it at http://localhost:PORT on the machine holding the USB cable, or put a TLS terminator in front of it. This fails silently rather than loudly: the page loads and simply offers no device. Without WebUSB the page is still a correct, ordered, device-specific adb and fastboot command list, which is most of its value.

The Dockerfile, docker-compose.yml, docker/ and the workflow are generated from fleet.json in stoatworks-unraid. Change them there and regenerate rather than editing them here.

Licence

MIT. See LICENSE and ATTRIBUTIONS.md.

Related apps

Explore more like this

Explore all

Details

Repository
ghcr.io/stoatworks-labs/openflash:latest
Last Updated2026-10-20
First Seen2026-09-07

Runtime arguments

Web UI
http://[IP]:[PORT:80]/
Network
bridge
Shell
sh
Privileged
false

Template configuration

WebUI PortPorttcp

Host port for the web interface.

Target
80
Default
8536
Value
8536