Files
Mira_Firmware_Loader/README.md
2026-03-09 19:33:00 +07:00

288 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ⚡ Mira Firmware Loader
Công cụ desktop dùng để **scan, phát hiện và flash firmware hàng loạt** cho các thiết bị OpenWrt trong mạng LAN.
Hỗ trợ nạp **thủ công** (chọn thiết bị → flash) và **tự động hóa** (scan → phát hiện → flash → retry).
> **Tech stack:** Python 3.9+ · PyQt6 · Paramiko/SCP · Scapy · Requests · PyInstaller
> **Phiên bản:** `1.2.0`
---
## 📁 Cấu trúc dự án
```text
Mira_Firmware_Loader/
├── main.py # UI chính (PyQt6) — App + AutoFlashWindow
├── version.txt # Số phiên bản ứng dụng
├── requirements.txt # Danh sách dependencies
├── core/
│ ├── scanner.py # Quét thiết bị mạng đa lớp (Ping sweep + ARP + Scapy)
│ ├── workers.py # ScanThread — chạy scanner trong background thread
│ ├── api_flash.py # Flash firmware qua LuCI HTTP API
│ ├── ssh_utils.py # SSH/SCP transport helpers dùng chung
│ ├── ssh_new_flash.py # Luồng SSH cho chế độ Nạp Mới (Telnet → set passwd → SSH)
│ ├── ssh_update_flash.py # Luồng SSH cho chế độ Update (SSH trực tiếp)
│ ├── flash_new_worker.py # NewFlashThread — QThread điều phối Nạp Mới FW
│ ├── flash_update_worker.py # UpdateFlashThread — QThread điều phối Update FW
│ └── auto_flash_worker.py # AutoFlashWorker — QThread tự động scan → flash → retry
├── ui/
│ ├── components.py # Custom Qt Widgets (CollapsibleGroupBox)
│ └── styles.py # Stylesheet toàn ứng dụng (STYLE + AUTO_STYLE)
├── utils/
│ ├── network.py # Helper IP / network (get_local_ip, get_default_network)
│ └── system.py # Lấy thông tin máy, resource path cho PyInstaller
├── docs/
│ ├── api_flash_docs.md # Tài liệu kỹ thuật LuCI API flash
│ ├── load_fw_ssh_docs.md # Tài liệu kỹ thuật SSH flash (cả 2 luồng)
│ ├── scanner_docs.md # Tài liệu kỹ thuật scanner
│ └── auto_flash_docs.md # Tài liệu kỹ thuật tự động hóa nạp FW
├── run.sh # Script khởi chạy (macOS/Linux)
├── run.bat # Script khởi chạy (Windows)
└── build_windows.bat # Script đóng gói thành .exe (Windows, PyInstaller)
```
---
## 🔧 Cài đặt & Chạy
### Yêu cầu
- Python **3.9+**
- Thư viện: `PyQt6`, `scapy`, `requests`, `paramiko`, `scp`, `pyinstaller` (để build)
- _Windows:_ Cài [Npcap](https://npcap.com/) để Scapy có thể gửi ARP broadcast (không bắt buộc — có fallback)
### Khởi chạy nhanh
**macOS / Linux:**
```bash
./run.sh
```
**Windows:**
```bat
run.bat
```
> Script tự tạo `venv` và cài dependencies nếu chưa có.
### 📦 Build file chạy độc lập (.exe) cho Windows
```bat
build_windows.bat
```
Output: `dist\Mira_Firmware_Loader.exe` — không cần cài Python trên máy đích.
---
## 🏗 Kiến trúc hệ thống
```
┌──────────────────────────────────────────────────────────┐
│ main.py (UI) │
│ │
│ Machine Info │ Firmware Selector │ Network Scan │
│ ───────────────────────────────────────────────────── │
│ Device Table [ ] IP │ MAC │ Status │
│ ───────────────────────────────────────────────────── │
│ Flash Controls │
│ Flash Mode: [ New Flash | Update Firmware ] │
│ Method: [ API (LuCI) | SSH (paramiko) ] │
│ Concurrent devices: [SpinBox] │
│ [ ⚡ FLASH SELECTED DEVICES ] │
│ ───────────────────────────────────────────────────── │
│ [ 🤖 Tự động hóa nạp FW ] │
└────────────────────────┬─────────────────────────────────┘
┌─────────────────┼─────────────────┐
│ │ │
┌──────▼──────┐ ┌───────▼──────────┐ ┌───▼──────────────────┐
│ scanner.py │ │ Flash Workers │ │ AutoFlashWorker │
│ │ │ (thủ công) │ │ (tự động hóa) │
│ 1. Ping │ │ │ │ │
│ Sweep │ │ NewFlashThread │ │ Phase 1: Scan LAN │
│ 2. arp -a │ │ ├─ api_flash │ │ (tối đa 15 lần) │
│ 3. Scapy │ │ └─ ssh_new_flash│ │ Phase 2: Flash │
│ ARP │ │ │ │ (auto-retry x3) │
└─────────────┘ │ UpdateFlash │ │ ├─ api_flash │
│ Thread │ │ └─ ssh_new_flash │
│ └─ ssh_update │ └──────────────────────┘
└──────────────────┘
┌──────────▼──────────┐
│ ssh_utils.py │
│ (Transport Layer) │
│ _create_ssh_client │
│ _upload_firmware │
│ _verify_firmware │
│ _sync_and_sysupgr │
└─────────────────────┘
```
---
## 🔄 Luồng hoạt động chi tiết
### 1. Quét mạng (Network Scan)
`scanner.py` dùng chiến lược 3 lớp, đảm bảo phát hiện đầy đủ mà không cần quyền Root:
| Giai đoạn | Mô tả |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Ping Sweep** | Gửi ping đồng thời tới toàn bộ host trong dải `/24` (tất cả cùng lúc, không batching) để đánh thức thiết bị và điền ARP cache |
| **ARP Table** | Đọc `arp -a` bằng regex, hỗ trợ cả định dạng Windows (`cc-2d-...`) và macOS/Linux (`aa:bb:...`) |
| **Scapy ARP** | Chạy **song song** với ARP Table — gửi ARP broadcast để lấp khoảng trống. Yêu cầu Npcap (Windows) hoặc root (Linux); tự động bỏ qua nếu không khả dụng |
Kết quả được merge theo IP và sort tăng dần trước khi trả về UI.
### 2. Bảng thiết bị (Device Table)
- Hiển thị cột: checkbox, IP, MAC, Status
- Mặc định ẩn gateway và IP máy tính đang chạy (có thể bật "Show all")
- Thiết bị đã flash trong session được đánh dấu "Already Flashed" và tự bỏ tick
### 3. Flash Firmware (Thủ công)
Có 2 chế độ và 2 method, tổng cộng 3 luồng thực thi khác nhau:
#### Chế độ `New Flash` — dùng cho thiết bị vừa reset cứng
| Method | Luồng | Mô tả |
| -------------- | ------------------ | ------------------------------------------------------------------------------------------- |
| **API (LuCI)** | `api_flash.py` | Đăng nhập LuCI → upload firmware → Proceed. Hỗ trợ Barrier Breaker 14.07 và OpenWrt mới hơn |
| **SSH** | `ssh_new_flash.py` | Kết nối Telnet → đặt password mới → SSH → SCP upload → sysupgrade |
**Luồng SSH New Flash chi tiết:**
1. Telnet port 23 (thiết bị vừa reset, chưa có pass)
2. Đặt password `admin123a` qua lệnh `passwd`
3. SSH vào với password vừa đặt
4. SCP upload firmware lên `/tmp/`
5. Verify file tồn tại
6. `sync && sysupgrade -F -v -n` → thiết bị reboot (connection drop = DONE)
#### Chế độ `Update Firmware` — dùng cho thiết bị đang chạy OpenWrt
Luồng `ssh_update_flash.py`, SSH trực tiếp (không qua Telnet):
1. SSH kết nối với `root` / `admin123a` (fallback: `admin`)
2. SCP upload firmware lên `/tmp/`
3. Verify file tồn tại
4. `sync && sysupgrade -F -v -n` → thiết bị reboot
> ⚠️ Update Mode hiển thị cảnh báo nếu IP thiết bị khác `192.168.11.102` và yêu cầu xác nhận.
### 4. 🤖 Tự động hóa nạp FW (MỚI)
Tính năng nạp FW hoàn toàn tự động — chỉ cần cấu hình và nhấn bắt đầu:
```
Cấu hình → Auto Scan LAN → Phát hiện đủ thiết bị → Auto Flash → Auto Retry → Thông báo
```
#### 4.1. Quy trình
| Phase | Mô tả |
| --------------------- | ------------------------------------------------------------------------- |
| **Phase 1: Scan LAN** | Scan mạng liên tục mỗi 5 giây, tối đa **15 lần**, cho đến khi đủ thiết bị |
| **Phase 2: Flash** | Nạp FW song song qua ThreadPoolExecutor, tự động **retry tối đa 3 lần** |
#### 4.2. Cấu hình
| Tham số | Mô tả | Mặc định |
| --------------- | --------------------------------------------- | ------------------- |
| **Firmware** | File firmware (.bin/.hex/.uf2) | Lấy từ cửa sổ chính |
| **Mạng** | Dải mạng LAN cần scan | Tự suy từ IP host |
| **Số lượng** | Số thiết bị cần nạp | 5 |
| **Phương thức** | API (LuCI) hoặc SSH | API (LuCI) |
| **Song song** | Số thiết bị nạp cùng lúc (0 = không giới hạn) | 10 |
#### 4.3. Auto-Retry nạp FW
Khi một thiết bị nạp thất bại, hệ thống tự động retry:
```
Lần 1 → FAIL: Connection timeout
⚠️ Log cảnh báo, chờ 2 giây...
Lần 2 → FAIL: Upload error
⚠️ Log cảnh báo, chờ 2 giây...
Lần 3 → DONE ✅ (hoặc ❌ báo lỗi sau 3 lần)
```
- Tối đa **3 lần retry** mỗi thiết bị (`MAX_FLASH_RETRIES`)
- Chờ **2 giây** giữa mỗi lần retry để thiết bị ổn định
- Nếu hết 3 lần vẫn fail → đánh dấu ❌, tiếp tục thiết bị tiếp theo
#### 4.4. Scan Timeout
- Nếu scan **15 lần** mà chưa đủ thiết bị → dừng và hiện cảnh báo
- Gợi ý kiểm tra: thiết bị đã bật chưa, dải mạng có đúng không
#### 4.5. Quy tắc bảo vệ IP `192.168.11.102`
| Chế độ | Được nạp 192.168.11.102? | Cơ chế |
| ------------------------ | :----------------------: | ----------------------------------- |
| **New Flash (thủ công)** | ❌ Không | Chặn trước khi flash, hiện cảnh báo |
| **Update FW (thủ công)** | ✅ Có | Cho phép bình thường |
| **Tự động hóa** | ❌ Không | Tự động lọc khỏi kết quả scan |
#### 4.6. Lịch sử nạp
Kết quả nạp được lưu ở 2 cấp:
| Nơi lưu | Phạm vi | Format |
| ------------------------------- | ---------------------- | ------------------------------------ |
| `AutoFlashWindow._auto_history` | Phiên tự động hiện tại | `list[(ip, mac, result, timestamp)]` |
| `App.flashed_macs` | Toàn bộ session app | `dict{MAC: (ip, mac, result, ts)}` |
- Cả thành công ✅ lẫn thất bại ❌ đều được ghi lại
- Nút "📋 Lịch sử nạp" hiển thị cùng format ở cả 2 cửa sổ: `[HH:MM:SS] ✅/❌ IP (MAC) — result`
### 5. Xử lý song song
| Tham số | Mô tả |
| ---------------------- | ------------------------------------------------------------------------ |
| **Concurrent devices** | Số thiết bị flash đồng thời (`ThreadPoolExecutor`). `0` = không giới hạn |
---
## ⚙️ Cấu hình mặc định
| Tham số | Giá trị mặc định | Mô tả |
| ---------------------- | ------------------------------------ | --------------------------------- |
| **Network** | Tự suy ra từ local IP (`x.x.x.0/24`) | Dải mạng để quét |
| **Flash Mode** | `New Flash` | Nạp mới hoặc Update |
| **Method** | `API (LuCI)` | Phương thức flash cho New Flash |
| **SSH User** | `root` | Hardcoded, không hiển thị trên UI |
| **SSH Password** | `admin123a` | Hardcoded, không hiển thị trên UI |
| **Concurrent devices** | `10` | Số luồng flash song song |
| **Show all** | Tắt | Ẩn gateway và IP máy host |
| **MAX_FLASH_RETRIES** | `3` | Số lần retry nạp FW (tự động) |
| **MAX_SCAN_ROUNDS** | `15` | Số lần scan tối đa (tự động) |
---
## 🛡️ Xử lý lỗi tổng hợp
| Tình huống | Hành vi |
| -------------------------------- | --------------------------------------------------- |
| Scan exception (network error) | Log lỗi, chờ 3s, scan lại (đếm vào MAX_SCAN_ROUNDS) |
| Scan 15 lần chưa đủ thiết bị | Hiện popup cảnh báo, dừng tự động |
| Flash thất bại lần 12 (tự động) | Log cảnh báo, chờ 2s, retry tự động |
| Flash thất bại sau 3 lần retry | Log lỗi, đánh dấu ❌, tiếp tục device tiếp theo |
| User nhấn DỪNG | Set stop flag, dừng scan/flash an toàn |
| IP 192.168.11.102 + New Flash | Chặn ngay, hiện cảnh báo |
| Chưa chọn firmware | Hiện popup cảnh báo, không cho bắt đầu |
| Mạng không hợp lệ | Hiện popup cảnh báo, không cho bắt đầu |
---
## 🔒 Lưu ý bảo mật & Quyền
- **Scapy (chế độ sâu):** Cần Npcap (Windows) hoặc `sudo` (macOS/Linux). App vẫn hoạt động mà không cần quyền Admin nhờ fallback Ping Sweep + `arp -a`.
- **CREATE_NO_WINDOW:** Khi gọi subprocess (`ping`, `arp`), ứng dụng dùng flag `CREATE_NO_WINDOW` trên Windows để ngăn cửa sổ console hiện ra.
- **HTTP thuần:** Firmware được upload qua HTTP (không HTTPS). Chỉ dùng trong mạng LAN nội bộ, không nên dùng trên mạng mở.
- **Credentials cố định:** SSH credentials (`root`/`admin123a`) được hardcode trong `flash_update_worker.py` và truyền từ `main.py`, không hiển thị trên UI.