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}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Live-Refresh für Container mit data-refresh="<url>" und data-interval="<ms>"
|
||||
function startAutoRefresh() {
|
||||
document.querySelectorAll("[data-refresh]").forEach((el) => {
|
||||
const url = el.dataset.refresh;
|
||||
const interval = parseInt(el.dataset.interval || "2000", 10);
|
||||
const tick = async () => {
|
||||
try {
|
||||
const r = await fetch(url, { headers: { "Accept": "text/html" } });
|
||||
if (r.ok) el.innerHTML = await r.text();
|
||||
} catch (e) { /* netzkurz weg, nicht weiter schlimm */ }
|
||||
};
|
||||
tick();
|
||||
setInterval(tick, interval);
|
||||
});
|
||||
}
|
||||
|
||||
// Forms mit data-action posten und Antwort in data-target rendern
|
||||
function wireScanForms() {
|
||||
document.querySelectorAll("form[data-action]").forEach((form) => {
|
||||
form.addEventListener("submit", async (ev) => {
|
||||
ev.preventDefault();
|
||||
const btn = form.querySelector("button[type=submit]");
|
||||
const target = document.querySelector(form.dataset.target);
|
||||
btn.disabled = true;
|
||||
if (target) target.textContent = "läuft…";
|
||||
try {
|
||||
const r = await fetch(form.dataset.action, { method: "POST" });
|
||||
const j = await r.json().catch(() => null);
|
||||
if (target) {
|
||||
if (j) {
|
||||
target.textContent = `gescannt: ${j.scanned} · neu: ${j.added} · aktualisiert: ${j.updated} · entfernt: ${j.removed}`;
|
||||
} else {
|
||||
target.textContent = r.ok ? "fertig" : "Fehler";
|
||||
}
|
||||
}
|
||||
// Seite neu laden, damit die Tabelle die neuen Einträge zeigt
|
||||
if (r.ok) setTimeout(() => location.reload(), 800);
|
||||
} catch (e) {
|
||||
if (target) target.textContent = "Fehler: " + e;
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
startAutoRefresh();
|
||||
wireScanForms();
|
||||
});
|
||||
@@ -0,0 +1,136 @@
|
||||
:root {
|
||||
--bg: #1c1f24;
|
||||
--bg-card: #262a31;
|
||||
--fg: #e6e6e6;
|
||||
--muted: #8a8f98;
|
||||
--accent: #6cb4ff;
|
||||
--accent-hover: #8fc8ff;
|
||||
--border: #353a44;
|
||||
--ok: #7adf7a;
|
||||
--warn: #ffb86c;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
header {
|
||||
background: var(--bg-card);
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding: 1rem 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
header h1 { margin: 0; font-size: 1.4rem; }
|
||||
header h1 a { color: var(--fg); text-decoration: none; }
|
||||
|
||||
nav { display: flex; gap: 1.5rem; }
|
||||
nav a { color: var(--muted); text-decoration: none; font-weight: 500; }
|
||||
nav a:hover { color: var(--accent); }
|
||||
|
||||
main { max-width: 1200px; margin: 0 auto; padding: 2rem; }
|
||||
|
||||
footer {
|
||||
text-align: center; padding: 2rem; color: var(--muted);
|
||||
border-top: 1px solid var(--border); margin-top: 4rem;
|
||||
}
|
||||
|
||||
a { color: var(--accent); }
|
||||
a:hover { color: var(--accent-hover); }
|
||||
|
||||
code, pre {
|
||||
font-family: "JetBrains Mono", Menlo, Consolas, monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
pre {
|
||||
background: var(--bg-card);
|
||||
padding: 1rem;
|
||||
border-radius: 6px;
|
||||
overflow-x: auto;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
code { background: var(--bg-card); padding: 0.1em 0.4em; border-radius: 3px; }
|
||||
|
||||
.cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 1rem;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
.card {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
.card h2 { margin-top: 0; font-size: 1rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; }
|
||||
.card .big { font-size: 2.5rem; font-weight: 700; margin: 0.5rem 0; }
|
||||
|
||||
.info { margin-top: 3rem; }
|
||||
.info ul { list-style: none; padding: 0; }
|
||||
.info li { margin: 0.5rem 0; }
|
||||
|
||||
table.grid {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background: var(--bg-card);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
table.grid th, table.grid td {
|
||||
text-align: left;
|
||||
padding: 0.75rem 1rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
table.grid th { background: rgba(255,255,255,0.04); font-weight: 600; }
|
||||
table.grid tbody tr:last-child td { border-bottom: none; }
|
||||
table.grid tbody tr:hover { background: rgba(255,255,255,0.03); }
|
||||
|
||||
table.kv { border-collapse: collapse; }
|
||||
table.kv th { text-align: left; padding: 0.5rem 1rem 0.5rem 0; color: var(--muted); font-weight: 500; }
|
||||
table.kv td { padding: 0.5rem 0; }
|
||||
|
||||
button {
|
||||
background: var(--accent);
|
||||
color: #0d1117;
|
||||
border: 0;
|
||||
padding: 0.6rem 1.2rem;
|
||||
border-radius: 6px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
button:hover { background: var(--accent-hover); }
|
||||
button:disabled { opacity: 0.5; cursor: wait; }
|
||||
|
||||
.empty {
|
||||
background: var(--bg-card);
|
||||
border: 1px dashed var(--border);
|
||||
padding: 1.5rem;
|
||||
border-radius: 8px;
|
||||
color: var(--muted);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.muted { color: var(--muted); }
|
||||
|
||||
.crumbs {
|
||||
background: var(--bg-card);
|
||||
padding: 0.6rem 1rem;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 1rem;
|
||||
border: 1px solid var(--border);
|
||||
font-family: "JetBrains Mono", Menlo, monospace;
|
||||
}
|
||||
.crumbs a { color: var(--fg); text-decoration: none; }
|
||||
.crumbs a:hover { color: var(--accent); }
|
||||
|
||||
#scan-result { margin-left: 1rem; color: var(--muted); }
|
||||
@@ -0,0 +1,20 @@
|
||||
{% if not devices %}
|
||||
<p class="empty">Kein bekanntes Hersteller-Gerät am USB.</p>
|
||||
{% else %}
|
||||
<table class="grid">
|
||||
<thead>
|
||||
<tr><th>Bus:Addr</th><th>VID:PID</th><th>Hersteller</th><th>Modus</th><th>Hinweis</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for d in devices %}
|
||||
<tr>
|
||||
<td>{{ "%03d"|format(d.bus) }}:{{ "%03d"|format(d.address) }}</td>
|
||||
<td><code>{{ "%04x"|format(d.vid) }}:{{ "%04x"|format(d.pid) }}</code></td>
|
||||
<td>{{ d.mode.vendor }}</td>
|
||||
<td><strong>{{ d.mode.label }}</strong></td>
|
||||
<td>{{ d.mode.notes }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
@@ -0,0 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>{% block title %}aubox{% endblock %}</title>
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1><a href="/">aubox</a></h1>
|
||||
<nav>
|
||||
<a href="/">Übersicht</a>
|
||||
<a href="/devices">Geräte</a>
|
||||
<a href="/firmware">Firmware</a>
|
||||
<a href="/browse">Dateien</a>
|
||||
<a href="/workflows/p10lite">P10 Lite</a>
|
||||
</nav>
|
||||
</header>
|
||||
<main>
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
<footer>
|
||||
<small>aubox · lokale Web-UI</small>
|
||||
</footer>
|
||||
<script src="/static/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,36 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Dateien · aubox{% endblock %}
|
||||
{% block content %}
|
||||
<h2>Datei-Browser</h2>
|
||||
<p class="muted">Sandboxed auf <code>{{ firmware_root }}</code> — Path-Traversal blockiert.</p>
|
||||
|
||||
<nav class="crumbs">
|
||||
{% for label, p in crumbs %}
|
||||
<a href="/browse?path={{ p }}">{{ label }}</a>
|
||||
{% if not loop.last %} / {% endif %}
|
||||
{% endfor %}
|
||||
</nav>
|
||||
|
||||
{% if not entries %}
|
||||
<p class="empty">Verzeichnis ist leer.</p>
|
||||
{% else %}
|
||||
<table class="grid">
|
||||
<thead><tr><th>Name</th><th>Typ</th><th>Größe</th></tr></thead>
|
||||
<tbody>
|
||||
{% for e in entries %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if e.is_dir %}
|
||||
<a href="/browse?path={{ e.rel_path }}">{{ e.name }}/</a>
|
||||
{% else %}
|
||||
{{ e.name }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ "DIR" if e.is_dir else "FILE" }}</td>
|
||||
<td>{{ e.size|humansize if not e.is_dir else "—" }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,9 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Geräte · aubox{% endblock %}
|
||||
{% block content %}
|
||||
<h2>Angeschlossene Geräte</h2>
|
||||
<p>Aktualisiert sich alle 2 Sekunden.</p>
|
||||
<div id="devices" data-refresh="/api/devices/html" data-interval="2000">
|
||||
Lade…
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,38 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Firmware · aubox{% endblock %}
|
||||
{% block content %}
|
||||
<h2>Firmware-Library</h2>
|
||||
<p>Quelle: <code>{{ firmware_root }}</code></p>
|
||||
|
||||
<form id="scan-form" data-action="/firmware/scan" data-target="#scan-result">
|
||||
<button type="submit">Library scannen</button>
|
||||
<span id="scan-result"></span>
|
||||
</form>
|
||||
|
||||
{% if not firmware %}
|
||||
<p class="empty">Noch keine Einträge. Lege Firmware-Dateien unter
|
||||
<code>{{ firmware_root }}</code> ab und klicke "Library scannen".</p>
|
||||
{% else %}
|
||||
<table class="grid">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Vendor</th><th>Modell</th><th>SoC</th><th>Region</th>
|
||||
<th>Format</th><th>Größe</th><th>Pfad</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for fw in firmware %}
|
||||
<tr>
|
||||
<td>{{ fw.vendor or "—" }}</td>
|
||||
<td><a href="/firmware/{{ fw.id }}">{{ fw.model or "—" }}</a></td>
|
||||
<td>{{ fw.soc or "—" }}</td>
|
||||
<td>{{ fw.region or "—" }}</td>
|
||||
<td>{{ fw.format }}</td>
|
||||
<td>{{ fw.size|humansize }}</td>
|
||||
<td><code>{{ fw.rel_path }}</code></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,23 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ fw.model or fw.rel_path }} · aubox{% endblock %}
|
||||
{% block content %}
|
||||
<h2>{{ fw.model or "Unbekannt" }} <small>{{ fw.format }}</small></h2>
|
||||
|
||||
<table class="kv">
|
||||
<tr><th>Pfad</th><td><code>{{ fw.rel_path }}</code></td></tr>
|
||||
<tr><th>Vendor</th><td>{{ fw.vendor or "—" }}</td></tr>
|
||||
<tr><th>Modell</th><td>{{ fw.model or "—" }}</td></tr>
|
||||
<tr><th>SoC</th><td>{{ fw.soc or "—" }}</td></tr>
|
||||
<tr><th>Region</th><td>{{ fw.region or "—" }}</td></tr>
|
||||
<tr><th>Version</th><td>{{ fw.version or "—" }}</td></tr>
|
||||
<tr><th>Größe</th><td>{{ fw.size|humansize }}</td></tr>
|
||||
<tr><th>SHA-256</th><td><code>{{ fw.sha256 or "noch nicht berechnet" }}</code></td></tr>
|
||||
</table>
|
||||
|
||||
{% if fw.extra_json %}
|
||||
<h3>Format-spezifische Daten</h3>
|
||||
<pre>{{ fw.extra_json }}</pre>
|
||||
{% endif %}
|
||||
|
||||
<p><a href="/firmware">← zurück</a></p>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,39 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}aubox · Übersicht{% endblock %}
|
||||
{% block content %}
|
||||
<section class="cards">
|
||||
<article class="card">
|
||||
<h2>Geräte</h2>
|
||||
<p class="big">{{ devices|length }}</p>
|
||||
<p>{{ "angeschlossen" if devices else "keins erkannt" }}</p>
|
||||
<p><a href="/devices">öffnen →</a></p>
|
||||
</article>
|
||||
|
||||
<article class="card">
|
||||
<h2>Firmware-Library</h2>
|
||||
<p class="big">{{ fw_count }}</p>
|
||||
<p>Einträge in der Datenbank</p>
|
||||
<p><a href="/firmware">öffnen →</a></p>
|
||||
</article>
|
||||
|
||||
<article class="card">
|
||||
<h2>Dateien</h2>
|
||||
<p>Sandbox-Browser für die Firmware-Library</p>
|
||||
<p><a href="/browse">öffnen →</a></p>
|
||||
</article>
|
||||
|
||||
<article class="card">
|
||||
<h2>Huawei P10 Lite</h2>
|
||||
<p>FRP-Removal · WAS-LX1 · Kirin 658</p>
|
||||
<p><a href="/workflows/p10lite">öffnen →</a></p>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="info">
|
||||
<h3>Aktive Pfade im Container</h3>
|
||||
<ul>
|
||||
<li><strong>Firmware:</strong> <code>{{ firmware_root }}</code></li>
|
||||
<li><strong>Loader:</strong> <code>{{ loader_root }}</code></li>
|
||||
</ul>
|
||||
</section>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,15 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Huawei P10 Lite · aubox{% endblock %}
|
||||
{% block content %}
|
||||
<h2>Huawei P10 Lite — FRP-Removal</h2>
|
||||
<p class="muted">WAS-LX1 · Kirin 658 · EMUI 8.x</p>
|
||||
|
||||
<h3>Methode 1: eRecovery + SD-Karte (empfohlen)</h3>
|
||||
<pre>{{ instructions }}</pre>
|
||||
|
||||
<h3>Methode 2: Testpoint + Kirin DLOAD</h3>
|
||||
<p>Über CLI vorbereitet, Web-UI-Button folgt:</p>
|
||||
<pre>python -m aubox p10lite frp-remove --method dload-erase</pre>
|
||||
<p>Voraussetzung: Loader-Files in <code>/loaders/kirin/kirin960_lite/</code>
|
||||
(siehe Loader-README im Repo).</p>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user