Autarch/android_plan.md

377 lines
17 KiB
Markdown
Raw Normal View History

# AUTARCH Android Plan - Browser-Based Hardware Access
## darkHal Security Group
**Created:** 2026-02-14
---
## Problem Statement
The current hardware module (Phase 4.5) is **server-side only**: Flask routes call `adb`/`fastboot`/`esptool` as subprocess commands on the AUTARCH server. This works when devices are physically plugged into the server (e.g., Orange Pi), but does NOT allow a remote user to flash a device plugged into their own machine.
**Goal:** Add **browser-based direct USB/Serial access** using WebUSB and Web Serial APIs, so users can flash devices plugged into their local machine through the AUTARCH web interface. Keep the existing server-side mode as a fallback.
---
## Architecture: Dual-Mode Hardware Access
```
┌─────────────────────────────────────┐
│ AUTARCH Web Dashboard │
│ hardware.html │
│ │
│ ┌─────────┐ ┌──────────────┐ │
│ │ SERVER │ │ DIRECT │ │
│ │ MODE │ │ MODE │ │
│ │ │ │ │ │
│ │ Flask │ │ WebUSB / │ │
│ │ API │ │ Web Serial │ │
│ │ calls │ │ (browser JS) │ │
│ └────┬────┘ └──────┬───────┘ │
└────────┼────────────────┼───────────┘
│ │
┌────────▼────┐ ┌──────▼───────┐
│ AUTARCH │ │ User's │
│ Server │ │ Browser │
│ (Orange Pi)│ │ ↕ USB/Serial│
│ ↕ USB │ │ ↕ Device │
│ ↕ Device │ └──────────────┘
└─────────────┘
Server Mode: device ↔ server ↔ Flask API ↔ browser (existing)
Direct Mode: device ↔ browser (WebUSB/Web Serial) ↔ JS libs (NEW)
```
**Server Mode** = Existing implementation. Device plugged into server. Flask calls adb/fastboot/esptool as subprocesses. Works in any browser.
**Direct Mode** = NEW. Device plugged into user's machine. Browser talks directly to device via WebUSB (ADB, Fastboot) or Web Serial (ESP32). Requires Chromium-based browser (Chrome, Edge, Brave, Opera).
---
## JavaScript Libraries
### 1. ADB — ya-webadb / Tango
- **npm:** `@yume-chan/adb`, `@yume-chan/adb-daemon-webusb`, `@yume-chan/stream-extra`
- **License:** MIT
- **API:** WebUSB → ADB protocol (shell, file sync, reboot, logcat, install, scrcpy)
- **Source:** https://github.com/yume-chan/ya-webadb
- **Key classes:**
- `AdbDaemonWebUsbDeviceManager` — enumerate/request USB devices
- `AdbDaemonWebUsbDevice` — wrap USB device for ADB transport
- `AdbDaemonTransport` — handshake + auth
- `Adb` — main interface (shell, sync, subprocess, reboot)
- **Usage pattern:**
```js
const manager = new AdbDaemonWebUsbDeviceManager(navigator.usb);
const device = await manager.requestDevice(); // USB permission prompt
const connection = await device.connect();
const transport = await AdbDaemonTransport.authenticate({connection, ...});
const adb = new Adb(transport);
const output = await adb.subprocess.spawnAndWait('ls /sdcard');
```
### 2. Fastboot — fastboot.js (kdrag0n)
- **npm:** `android-fastboot`
- **License:** MIT
- **API:** WebUSB → Fastboot protocol (getvar, flash, boot, reboot, OEM unlock)
- **Source:** https://github.com/niccolozy/fastboot.js (fork of kdrag0n), used by ArKT-7/nabu
- **Key classes:**
- `FastbootDevice` — connect, getVariable, flashBlob, reboot, flashFactoryZip
- **Usage pattern:**
```js
const device = new FastbootDevice();
await device.connect(); // USB permission prompt
const product = await device.getVariable('product');
await device.flashBlob('boot', blob, (progress) => updateUI(progress));
await device.reboot();
```
### 3. ESP32 — esptool-js (Espressif)
- **npm:** `esptool-js`
- **License:** Apache-2.0
- **API:** Web Serial → ESP32 ROM bootloader (chip detect, flash, erase, read MAC)
- **Source:** https://github.com/niccolozy/esptool-js (Espressif)
- **Key classes:**
- `ESPLoader` — main class, connect/detectChip/writeFlash
- `Transport` — Web Serial wrapper
- **Usage pattern:**
```js
const port = await navigator.serial.requestPort();
await port.open({ baudRate: 115200 });
const transport = new Transport(port);
const loader = new ESPLoader({ transport, baudrate: 115200 });
await loader.main(); // connect + detect chip
console.log('Chip:', loader.chipName);
await loader.writeFlash({ fileArray: [{data: firmware, address: 0x0}],
flashSize: 'keep', progressCallback: fn });
```
---
## Build Strategy: Pre-bundled ESM
Since AUTARCH uses vanilla JS (no React/webpack/build system), we need browser-ready bundles of the npm libraries.
**Approach:** Use `esbuild` to create self-contained browser bundles, saved as static JS files.
```
web/static/js/
├── app.js # Existing (1,477 lines)
├── lib/
│ ├── adb-bundle.js # ya-webadb bundled (ESM → IIFE)
│ ├── fastboot-bundle.js # fastboot.js bundled
│ └── esptool-bundle.js # esptool-js bundled
└── hardware-direct.js # NEW: Direct-mode logic (~500 lines)
```
**Build script** (`scripts/build-hw-libs.sh`):
```bash
#!/bin/bash
# One-time build — output goes into web/static/js/lib/
# Only needed when updating library versions
npm install --save-dev esbuild
npm install @yume-chan/adb @yume-chan/adb-daemon-webusb @yume-chan/stream-extra android-fastboot esptool-js
# Bundle each library into browser-ready IIFE
npx esbuild src/adb-entry.js --bundle --format=iife --global-name=YumeAdb --outfile=web/static/js/lib/adb-bundle.js
npx esbuild src/fastboot-entry.js --bundle --format=iife --global-name=Fastboot --outfile=web/static/js/lib/fastboot-bundle.js
npx esbuild src/esptool-entry.js --bundle --format=iife --global-name=EspTool --outfile=web/static/js/lib/esptool-bundle.js
```
**Entry point files** (thin wrappers that re-export what we need):
```js
// src/adb-entry.js
export { AdbDaemonWebUsbDeviceManager, AdbDaemonWebUsbDevice } from '@yume-chan/adb-daemon-webusb';
export { AdbDaemonTransport, Adb, AdbSync } from '@yume-chan/adb';
// src/fastboot-entry.js
export { FastbootDevice, setDebugLevel } from 'android-fastboot';
// src/esptool-entry.js
export { ESPLoader, Transport } from 'esptool-js';
```
The pre-built bundles are committed to `web/static/js/lib/` so no npm/node is needed at runtime. The build script is only run when updating library versions.
---
## Implementation Phases
### Phase A: Build Infrastructure & Library Bundles
**Files:** `package.json`, `scripts/build-hw-libs.sh`, `src/*.js`, `web/static/js/lib/*.js`
1. Create `package.json` in project root (devDependencies only — not needed at runtime)
2. Create entry-point files in `src/` for each library
3. Create build script `scripts/build-hw-libs.sh`
4. Run build, verify bundles work in browser
5. Add `node_modules/` to `.gitignore` equivalent (cleanup notes)
**Deliverable:** Three bundled JS files in `web/static/js/lib/`
### Phase B: Direct-Mode JavaScript Module
**Files:** `web/static/js/hardware-direct.js` (~500 lines)
Core module providing a unified API that mirrors the existing server-mode functions:
```js
// hardware-direct.js — Browser-based device access
const HWDirect = {
// State
supported: { webusb: !!navigator.usb, webserial: !!navigator.serial },
adbDevice: null, // current ADB connection
fbDevice: null, // current Fastboot connection
espLoader: null, // current ESP32 connection
espTransport: null,
// ── ADB (WebUSB) ────────────────────────────────
async adbRequestDevice() { ... }, // navigator.usb.requestDevice()
async adbConnect(usbDevice) { ... }, // handshake + auth → Adb instance
async adbShell(cmd) { ... }, // adb.subprocess.spawnAndWait
async adbReboot(mode) { ... }, // adb.power.reboot / bootloader / recovery
async adbInstall(blob) { ... }, // adb install APK
async adbPush(blob, path) { ... }, // adb.sync().write()
async adbPull(path) { ... }, // adb.sync().read() → Blob download
async adbLogcat(lines) { ... }, // adb subprocess logcat
async adbGetInfo() { ... }, // getprop queries
async adbDisconnect() { ... },
// ── Fastboot (WebUSB) ────────────────────────────
async fbRequestDevice() { ... }, // FastbootDevice.connect()
async fbGetInfo() { ... }, // getVariable queries
async fbFlash(partition, blob, progressCb) { ... },
async fbReboot(mode) { ... },
async fbOemUnlock() { ... },
async fbDisconnect() { ... },
// ── ESP32 (Web Serial) ───────────────────────────
async espRequestPort() { ... }, // navigator.serial.requestPort()
async espConnect(port, baud) { ... }, // Transport + ESPLoader.main()
async espDetectChip() { ... }, // loader.chipName
async espFlash(fileArray, progressCb) { ... },
async espMonitorStart(outputCb) { ... },
async espMonitorSend(data) { ... },
async espMonitorStop() { ... },
async espDisconnect() { ... },
// ── Factory Flash (PixelFlasher PoC) ─────────────
async factoryFlash(zipBlob, options, progressCb) { ... },
};
```
### Phase C: UI Integration — Mode Switcher & Direct Controls
**Files:** `web/templates/hardware.html`, `web/static/js/app.js`
1. **Mode toggle** at top of hardware page:
```
[Connection Mode] ○ Server (device on AUTARCH host) ● Direct (device on this PC)
```
- Direct mode shows browser compatibility warning if WebUSB/Serial not supported
- Direct mode shows "Pair Device" buttons (triggers USB/Serial permission prompts)
2. **Modify existing JS functions** to check mode:
```js
// In app.js, each hw*() function checks the mode:
async function hwRefreshAdbDevices() {
if (hwConnectionMode === 'direct') {
// Use HWDirect.adbRequestDevice() / enumerate
} else {
// Existing: fetchJSON('/hardware/adb/devices')
}
}
```
3. **New UI elements for direct mode:**
- "Connect ADB Device" button (triggers WebUSB permission prompt)
- "Connect Fastboot Device" button (triggers WebUSB permission prompt)
- "Connect Serial Port" button (triggers Web Serial permission prompt)
- File picker for firmware uploads (local files, no server upload needed)
- Progress bars driven by JS callbacks instead of SSE streams
4. **Keep all existing server-mode UI** — just add the mode switch.
### Phase D: PixelFlasher Proof-of-Concept
**Files:** `web/static/js/hardware-direct.js` (factoryFlash section), `web/templates/hardware.html` (new tab/section)
Inspired by PixelFlasher's workflow, create a "Flash Factory Image" feature:
1. **Upload factory image ZIP** (via file input, read in browser — no server upload)
2. **Parse ZIP contents** (identify flash-all.sh/bat, partition images)
3. **Display flash plan** (list of partitions + images to flash, with sizes)
4. **Safety checks:**
- Verify device product matches image (getVariable product vs ZIP name)
- Check bootloader unlock status
- Warn about data wipe partitions (userdata, metadata)
- Show A/B slot info if applicable
5. **Options:**
- [ ] Flash all partitions (default)
- [ ] Skip userdata (preserve data)
- [ ] Disable vbmeta verification (for custom ROMs)
- [ ] Flash to inactive slot (A/B devices)
6. **Execute flash sequence:**
- Reboot to bootloader if in ADB mode
- Flash each partition with progress bar
- Reboot to system
7. **Boot image patching** (future — Magisk/KernelSU integration)
### Phase E: Polish & Testing
1. Error handling for all WebUSB/Serial operations (device disconnected mid-flash, permission denied, etc.)
2. Browser compatibility detection and graceful degradation
3. Connection status indicators (connected device info in header)
4. Reconnection logic if USB device resets during flash
5. Update `autarch_dev.md` with completed phase notes
---
## File Changes Summary
### New Files
| File | Purpose | Est. Lines |
|------|---------|-----------|
| `package.json` | npm deps for build only | 20 |
| `scripts/build-hw-libs.sh` | esbuild bundler script | 25 |
| `src/adb-entry.js` | ya-webadb re-export | 5 |
| `src/fastboot-entry.js` | fastboot.js re-export | 3 |
| `src/esptool-entry.js` | esptool-js re-export | 3 |
| `web/static/js/lib/adb-bundle.js` | Built bundle | ~varies |
| `web/static/js/lib/fastboot-bundle.js` | Built bundle | ~varies |
| `web/static/js/lib/esptool-bundle.js` | Built bundle | ~varies |
| `web/static/js/hardware-direct.js` | Direct-mode logic | ~500 |
### Modified Files
| File | Changes |
|------|---------|
| `web/templates/hardware.html` | Add mode toggle, direct-mode connect buttons, factory flash section, script includes |
| `web/static/js/app.js` | Add mode switching to all hw*() functions |
| `web/static/css/style.css` | Styles for mode toggle, connect buttons, compatibility warnings |
### Unchanged
| File | Reason |
|------|--------|
| `core/hardware.py` | Server-mode backend stays as-is |
| `web/routes/hardware.py` | Server-mode routes stay as-is |
| `modules/hardware_local.py` | CLI module stays as-is |
---
## Browser Compatibility
| Feature | Chrome | Edge | Firefox | Safari |
|---------|--------|------|---------|--------|
| WebUSB (ADB/Fastboot) | 61+ | 79+ | No | No |
| Web Serial (ESP32) | 89+ | 89+ | No | No |
**Fallback:** Users with Firefox/Safari use Server Mode (device plugged into AUTARCH host). Direct Mode requires Chromium-based browser.
---
## Security Considerations
1. **WebUSB requires HTTPS** in production (or localhost). AUTARCH currently runs plain HTTP. For direct mode to work remotely, either:
- Run behind a reverse proxy with TLS (nginx/caddy)
- Use localhost (device and browser on same machine)
- Use the server-mode fallback instead
2. **USB permission prompts** — The browser shows a native device picker. Users must explicitly select their device. No access without user gesture.
3. **Flash safety checks** — Same partition whitelist as server mode. Confirm dialogs before destructive operations. Product verification before factory flash.
---
## Implementation Order
```
Phase A → Phase B → Phase C → Phase D → Phase E
(libs) (JS API) (UI) (PoC) (polish)
~1 session ~1 session ~1 session ~1 session ~1 session
```
Start with Phase A (build the library bundles) since everything else depends on having working JS libraries available in the browser.
---
## PixelFlasher Feature Mapping
| PixelFlasher Feature | AUTARCH Implementation | Phase |
|---------------------|----------------------|-------|
| Factory image flash | ZIP upload → parse → flash sequence | D |
| OTA sideload | ADB sideload (server) / adb.install (direct) | C |
| Boot image patching (Magisk) | Future — extract boot.img, patch, flash back | Future |
| Multi-device support | Device list + select (both modes already do this) | C |
| A/B slot management | fastboot getvar current-slot / set_active | D |
| Dry run mode | Parse + display flash plan without executing | D |
| Partition backup | fastboot fetch / adb pull partition | Future |
| Lock/unlock status | fastboot getvar unlocked | D |
| Device state display | Product, variant, bootloader version, secure, etc. | C |
---
## Notes
- All npm/node dependencies are **build-time only**. The built JS bundles are static files served by Flask. No Node.js runtime needed.
- The `src/` directory and `node_modules/` are build artifacts, not needed for deployment.
- Library bundles should be rebuilt when upgrading library versions. Pin versions in package.json.
- The server-side mode remains the primary mode for headless/remote AUTARCH deployments where devices are plugged into the server.
- Direct mode is an enhancement for users who want to flash devices plugged into their own workstation while using the AUTARCH web UI.