initial aubox skeleton: web-UI, kirin DLOAD, firmware library

- FastAPI Web-UI auf 127.0.0.1:8080 mit Geräte-Live-Erkennung,
  sandboxed File-Browser, Firmware-Library (SQLite + Auto-Identifikation)
- Huawei update.app Parser: extrahiert Hardware-ID, Section-Layout,
  BOOT/SYSTEM-Vorhandensein direkt aus den Headern
- Kirin Download-Mode: hisi-idt-Protokoll-Implementation gegen pyusb
- USB-Erkennung für Huawei (DLOAD/Fastboot-D), Google, MediaTek, Qualcomm EDL
- Huawei-P10-Lite-Workflow (eRecovery + Testpoint-DLOAD-Pfade)
- Docker-Compose mit USB-Passthrough (Major 189) für Re-Enumeration
- udev-Regeln + Setup-Script für Debian/Ubuntu/Pi-OS

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Hacker
2026-04-26 12:09:39 +02:00
parent d0386b3c53
commit fb3534553b
35 changed files with 1883 additions and 0 deletions
+163
View File
@@ -0,0 +1,163 @@
"""FastAPI-App. Lokale Web-UI für aubox.
Start:
uvicorn aubox.web.app:app --host 127.0.0.1 --port 8080
oder über CLI:
python -m aubox web
"""
from __future__ import annotations
import os
from pathlib import Path
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from .. import filebrowse, p10lite, usb
from ..library import db as fwdb
from ..library import identify as fwid
WEB_ROOT = Path(__file__).resolve().parent
FIRMWARE_ROOT = Path(os.environ.get("AUBOX_FIRMWARE_ROOT", "./firmware")).resolve()
LOADER_ROOT = Path(os.environ.get("AUBOX_LOADER_ROOT", "./loaders")).resolve()
DB_PATH = FIRMWARE_ROOT / "firmware.db"
app = FastAPI(title="aubox")
app.mount("/static", StaticFiles(directory=WEB_ROOT / "static"), name="static")
templates = Jinja2Templates(directory=WEB_ROOT / "templates")
def _human_size(n: int) -> str:
for unit in ("B", "KB", "MB", "GB", "TB"):
if n < 1024:
return f"{n:.1f} {unit}" if unit != "B" else f"{n} B"
n /= 1024
return f"{n:.1f} PB"
templates.env.filters["humansize"] = _human_size
@app.get("/", response_class=HTMLResponse)
async def index(request: Request):
devices = usb.scan()
with fwdb.open_db(DB_PATH) as conn:
fw_count = conn.execute("SELECT COUNT(*) FROM firmware").fetchone()[0]
return templates.TemplateResponse("index.html", {
"request": request,
"devices": devices,
"fw_count": fw_count,
"firmware_root": FIRMWARE_ROOT,
"loader_root": LOADER_ROOT,
})
# ---------- Devices --------------------------------------------------------
@app.get("/devices", response_class=HTMLResponse)
async def devices_page(request: Request):
return templates.TemplateResponse("devices.html", {"request": request})
@app.get("/api/devices/html", response_class=HTMLResponse)
async def devices_partial(request: Request):
return templates.TemplateResponse("_devices.html", {
"request": request,
"devices": usb.scan(),
})
# ---------- Firmware Library ----------------------------------------------
@app.get("/firmware", response_class=HTMLResponse)
async def firmware_page(request: Request):
with fwdb.open_db(DB_PATH) as conn:
rows = fwdb.list_all(conn)
return templates.TemplateResponse("firmware.html", {
"request": request,
"firmware": rows,
"firmware_root": FIRMWARE_ROOT,
})
@app.post("/firmware/scan")
async def firmware_scan():
"""Walkt FIRMWARE_ROOT, identifiziert jede Datei, schreibt in DB."""
if not FIRMWARE_ROOT.is_dir():
raise HTTPException(404, f"Firmware-Root {FIRMWARE_ROOT} nicht gefunden")
seen: set[str] = set()
added = updated = 0
with fwdb.open_db(DB_PATH) as conn:
with fwdb.transaction(conn):
for path in FIRMWARE_ROOT.rglob("*"):
if not path.is_file():
continue
if path.name == "firmware.db" or path.name.startswith("."):
continue
rec = fwid.identify(path, FIRMWARE_ROOT)
seen.add(rec["rel_path"])
existed = conn.execute(
"SELECT 1 FROM firmware WHERE rel_path = ?",
(rec["rel_path"],),
).fetchone()
fwdb.upsert(conn, rec)
if existed:
updated += 1
else:
added += 1
removed = fwdb.delete_missing(conn, seen)
return JSONResponse({
"scanned": len(seen),
"added": added,
"updated": updated,
"removed": removed,
})
@app.get("/firmware/{fw_id}", response_class=HTMLResponse)
async def firmware_detail(request: Request, fw_id: int):
with fwdb.open_db(DB_PATH) as conn:
row = fwdb.get_by_id(conn, fw_id)
if row is None:
raise HTTPException(404)
return templates.TemplateResponse("firmware_detail.html", {
"request": request,
"fw": row,
"firmware_root": FIRMWARE_ROOT,
})
# ---------- File Browser (sandboxed auf FIRMWARE_ROOT) --------------------
@app.get("/browse", response_class=HTMLResponse)
async def browse(request: Request, path: str = ""):
try:
target, entries = filebrowse.list_dir(FIRMWARE_ROOT, path)
except (filebrowse.PathEscapeError, FileNotFoundError, NotADirectoryError) as e:
raise HTTPException(400, str(e))
return templates.TemplateResponse("browse.html", {
"request": request,
"rel": path,
"entries": entries,
"crumbs": filebrowse.breadcrumbs(path),
"firmware_root": FIRMWARE_ROOT,
})
# ---------- Workflows ------------------------------------------------------
@app.get("/workflows/p10lite", response_class=HTMLResponse)
async def workflow_p10lite(request: Request):
return templates.TemplateResponse("p10lite.html", {
"request": request,
"instructions": p10lite.erecovery_instructions(),
})
@app.get("/health")
async def health():
return {"ok": True}