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:
@@ -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}
|
||||
Reference in New Issue
Block a user