refactor code, ẩn thông tin ssh
This commit is contained in:
141
core/api_flash.py
Normal file
141
core/api_flash.py
Normal file
@@ -0,0 +1,141 @@
|
||||
"""
|
||||
LuCI HTTP Firmware Flasher for OpenWrt devices.
|
||||
|
||||
Tự động hoá 3 bước flash qua web interface LuCI:
|
||||
1. Login → lấy sysauth cookie + stok token
|
||||
2. Upload firmware.bin (multipart)
|
||||
3. Confirm (Proceed) → thiết bị flash và reboot
|
||||
|
||||
Tương thích:
|
||||
- Barrier Breaker 14.07 (field: username/password)
|
||||
- OpenWrt mới hơn (field: luci_username/luci_password)
|
||||
"""
|
||||
|
||||
import re
|
||||
import time
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
import requests
|
||||
|
||||
|
||||
def _extract_stok(text: str) -> Optional[str]:
|
||||
"""Tách stok token từ URL hoặc body HTML. Trả về None nếu không tìm thấy."""
|
||||
m = re.search(r";stok=([a-f0-9]+)", text)
|
||||
return m.group(1) if m else None
|
||||
|
||||
|
||||
def flash_device_api(ip, firmware_path, username="root", password="",
|
||||
keep_settings=False, status_cb=None):
|
||||
"""
|
||||
Flash firmware lên thiết bị OpenWrt qua LuCI HTTP.
|
||||
|
||||
Args:
|
||||
ip : địa chỉ IP thiết bị
|
||||
firmware_path : đường dẫn file .bin trên máy tính
|
||||
username : LuCI username (mặc định "root")
|
||||
password : LuCI password (mặc định rỗng — Barrier Breaker không có pass)
|
||||
keep_settings : True = giữ cấu hình cũ sau flash
|
||||
status_cb : callback(str) cập nhật tiến độ lên UI
|
||||
|
||||
Returns:
|
||||
"DONE" — flash thành công
|
||||
"FAIL: …" — thất bại, kèm lý do
|
||||
"""
|
||||
base_url = f"http://{ip}"
|
||||
login_url = f"{base_url}/cgi-bin/luci"
|
||||
session = requests.Session()
|
||||
|
||||
try:
|
||||
# ── STEP 1: Login ────────────────────────────────────────────
|
||||
if status_cb:
|
||||
status_cb("Logging in...")
|
||||
|
||||
# Phát hiện field name tương thích Barrier Breaker vs OpenWrt mới
|
||||
try:
|
||||
page_html = session.get(login_url, timeout=10).text
|
||||
except Exception:
|
||||
page_html = ""
|
||||
|
||||
if 'name="luci_username"' in page_html:
|
||||
login_data = {"luci_username": username, "luci_password": password}
|
||||
else:
|
||||
login_data = {"username": username, "password": password}
|
||||
|
||||
resp = session.post(login_url, data=login_data,
|
||||
timeout=10, allow_redirects=True)
|
||||
|
||||
if resp.status_code == 403:
|
||||
return "FAIL: Login denied (403)"
|
||||
|
||||
# Lấy stok từ URL → body → redirect history
|
||||
stok = (_extract_stok(resp.url)
|
||||
or _extract_stok(resp.text)
|
||||
or next(
|
||||
(_extract_stok(h.headers.get("Location", ""))
|
||||
for h in resp.history
|
||||
if _extract_stok(h.headers.get("Location", ""))),
|
||||
None
|
||||
))
|
||||
|
||||
if not stok and "sysauth" not in str(session.cookies):
|
||||
return "FAIL: Login failed — no session"
|
||||
|
||||
flash_url = (
|
||||
f"{base_url}/cgi-bin/luci/;stok={stok}/admin/system/flashops"
|
||||
if stok else
|
||||
f"{base_url}/cgi-bin/luci/admin/system/flashops"
|
||||
)
|
||||
|
||||
# ── STEP 2: Upload Firmware ──────────────────────────────────
|
||||
if status_cb:
|
||||
status_cb("Uploading firmware...")
|
||||
|
||||
filename = os.path.basename(firmware_path)
|
||||
extra = {"keep": "on"} if keep_settings else {}
|
||||
|
||||
with open(firmware_path, "rb") as f:
|
||||
resp = session.post(
|
||||
flash_url,
|
||||
data=extra,
|
||||
files={"image": (filename, f, "application/octet-stream")},
|
||||
timeout=300,
|
||||
)
|
||||
|
||||
if resp.status_code != 200:
|
||||
return f"FAIL: Upload HTTP {resp.status_code}"
|
||||
|
||||
body = resp.text.lower()
|
||||
if "invalid image" in body or "bad image" in body:
|
||||
return "FAIL: Invalid firmware image"
|
||||
if "unsupported" in body or "not compatible" in body:
|
||||
return "FAIL: Firmware not compatible"
|
||||
if "verify" not in body or "proceed" not in body:
|
||||
return ("FAIL: Upload ignored by server"
|
||||
if 'name="image"' in resp.text
|
||||
else "FAIL: Unexpected response after upload")
|
||||
|
||||
# ── STEP 3: Confirm (Proceed) ────────────────────────────────
|
||||
if status_cb:
|
||||
status_cb("Confirming (Proceed)...")
|
||||
|
||||
confirm = {"step": "2", "keep": "on" if keep_settings else ""}
|
||||
try:
|
||||
session.post(flash_url, data=confirm, timeout=300)
|
||||
except (requests.exceptions.ConnectionError,
|
||||
requests.exceptions.ReadTimeout):
|
||||
pass # Đứt kết nối khi reboot = bình thường
|
||||
|
||||
if status_cb:
|
||||
status_cb("Rebooting...")
|
||||
time.sleep(3)
|
||||
return "DONE"
|
||||
|
||||
except requests.ConnectionError:
|
||||
return "FAIL: Cannot connect"
|
||||
except requests.Timeout:
|
||||
return "DONE (rebooting)"
|
||||
except Exception as e:
|
||||
return f"FAIL: {e}"
|
||||
finally:
|
||||
session.close()
|
||||
Reference in New Issue
Block a user