refactor code, ẩn thông tin ssh

This commit is contained in:
2026-03-09 11:59:55 +07:00
parent 9f6bd8c35a
commit 1297511122
13 changed files with 1043 additions and 504 deletions

View File

@@ -1,155 +0,0 @@
# Tài liệu Flash Firmware — IoT Firmware Loader
## Tổng quan
Ứng dụng tự động hóa quá trình nạp firmware cho thiết bị **OpenWrt Barrier Breaker 14.07** thông qua giao diện web **LuCI**. Thay vì thao tác thủ công trên trình duyệt, ứng dụng thực hiện 3 bước HTTP tự động cho mỗi thiết bị.
---
## Luồng hoạt động
```mermaid
flowchart TD
A[Chọn Firmware .bin] --> B[Scan LAN]
B --> C[Hiển thị danh sách thiết bị]
C --> D{Chọn thiết bị ☑}
D --> E[Nhấn Flash Selected Devices]
E --> F[FlashThread chạy background]
F --> G["ThreadPoolExecutor\n(max_workers = N)"]
G --> H1[Device 1 → flash_device]
G --> H2[Device 2 → flash_device]
G --> H3[Device N → flash_device]
H1 & H2 & H3 --> I[All Done → Thông báo]
```
### Chi tiết `flash_device()` cho mỗi thiết bị
```mermaid
flowchart TD
S1["STEP 1: Login\nGET /cgi-bin/luci\nPOST username=root, password=empty"] --> C1{Thành công?}
C1 -->|Có cookie sysauth + stok| S2
C1 -->|403 Denied| F1["FAIL: Login denied (403)"]
C1 -->|Không có session| F2["FAIL: Login failed — no session"]
S2["STEP 2: Upload Firmware\nPOST /flashops\nField: image=firmware.bin\nkeep=KHÔNG gửi (bỏ tích)"] --> C2{Response?}
C2 -->|Trang Verify + Proceed| S3
C2 -->|HTTP ≠ 200| F3["FAIL: Upload HTTP xxx"]
C2 -->|invalid image| F4["FAIL: Invalid firmware image"]
C2 -->|unsupported| F5["FAIL: Firmware not compatible"]
C2 -->|Vẫn hiện form upload| F6["FAIL: Upload ignored by server"]
S3["STEP 3: Proceed\nPOST step=2, keep=empty\nXác nhận flash"] --> C3{Response?}
C3 -->|200 Flashing...| R["DONE ✅\nThiết bị đang reboot"]
C3 -->|Connection dropped| R
C3 -->|Read timeout| R
```
---
## Bảng Status
### Status trên cột "Status" trong bảng thiết bị
| Icon | Status | Điều kiện hiển thị |
| ---- | ---------------------------------------- | ------------------------------------------------------------- |
| — | `READY` | Sau khi scan, thiết bị chưa được flash |
| ⏳ | `Logging in...` | Đang POST login vào LuCI |
| ⏳ | `Uploading firmware...` | Đang upload file .bin (~30MB) lên thiết bị |
| ⏳ | `Confirming (Proceed)...` | Đã upload xong, đang gửi lệnh xác nhận flash |
| ⏳ | `Rebooting...` | Thiết bị đang reboot sau khi flash |
| ✅ | `DONE` | Flash thành công, thiết bị đang khởi động lại |
| ✅ | `DONE (rebooting)` | Flash thành công nhưng timeout khi chờ response (bình thường) |
| ❌ | `FAIL: Cannot connect` | Không kết nối được tới thiết bị (sai IP, khác mạng) |
| ❌ | `FAIL: Login denied (403)` | Thiết bị từ chối đăng nhập (sai mật khẩu) |
| ❌ | `FAIL: Login failed — no session` | Login không trả về cookie hoặc token |
| ❌ | `FAIL: Upload HTTP xxx` | Server trả mã lỗi HTTP khi upload |
| ❌ | `FAIL: Invalid firmware image` | File firmware không hợp lệ |
| ❌ | `FAIL: Firmware not compatible` | Firmware không tương thích với thiết bị |
| ❌ | `FAIL: Upload ignored by server` | Server nhận file nhưng không xử lý (sai form field) |
| ❌ | `FAIL: Unexpected response after upload` | Không nhận được trang xác nhận Verify |
---
## Chi tiết kỹ thuật HTTP
### Step 1: Login
```
GET http://{IP}/cgi-bin/luci → Lấy trang login, phát hiện field name
POST http://{IP}/cgi-bin/luci → Gửi username=root&password=
```
| Kết quả | Điều kiện |
| -------------- | ------------------------------------------------------------------ |
| **Thành công** | Response chứa `sysauth` cookie VÀ/HOẶC `stok` token trong URL/body |
| **Thất bại** | HTTP 403, hoặc không có cookie/token |
**Field names tự động phát hiện:**
- OpenWrt Barrier Breaker 14.07: `username` / `password`
- OpenWrt mới hơn: `luci_username` / `luci_password`
### Step 2: Upload Firmware
```
POST http://{IP}/cgi-bin/luci/;stok={TOKEN}/admin/system/flashops
Content-Type: multipart/form-data
Fields:
image = [firmware.bin] (file upload)
keep = (KHÔNG gửi) (bỏ tích Keep Settings)
```
| Kết quả | Điều kiện |
| -------------- | ----------------------------------------------------------------------- |
| **Thành công** | Response chứa "Flash Firmware - Verify" + "Proceed" + checksum |
| **Thất bại** | Response chứa "invalid image", "unsupported", hoặc vẫn hiện form upload |
### Step 3: Proceed (Xác nhận)
```
POST http://{IP}/cgi-bin/luci/;stok={TOKEN}/admin/system/flashops
Fields:
step = 2
keep = (empty string)
```
| Kết quả | Điều kiện |
| -------------- | ------------------------------------------------------------------------------- |
| **Thành công** | Response "The system is flashing now" HOẶC connection bị ngắt (thiết bị reboot) |
| **Thất bại** | Hiếm khi xảy ra — nếu đã qua Step 2 thì Step 3 gần như luôn thành công |
---
## Xử lý song song
```
FlashThread (QThread - background)
└── ThreadPoolExecutor (max_workers = N)
├── Thread 1 → flash_device(IP_1)
├── Thread 2 → flash_device(IP_2)
├── ...
└── Thread N → flash_device(IP_N)
```
| Config | Giá trị | Ý nghĩa |
| ------------------------- | --------- | ------------------------------ |
| Concurrent devices = `10` | Mặc định | Flash 10 thiết bị song song |
| Concurrent devices = `0` | Unlimited | Flash tất cả thiết bị cùng lúc |
| Concurrent devices = `1` | Tuần tự | Flash từng thiết bị một |
**Mỗi thiết bị có session HTTP riêng** → không bị lẫn cookie/token giữa các thiết bị.
---
## Files liên quan
| File | Chức năng |
| --------------- | --------------------------------------------- |
| `flasher.py` | Logic flash 3 bước (login → upload → proceed) |
| `main.py` | UI PyQt6, FlashThread, quản lý song song |
| `debug_full.py` | Script debug — chạy 3 bước với log chi tiết |
| `scanner.py` | Scan mạng LAN tìm thiết bị |
""", "Complexity": 3, "Description": "Created flash documentation file with workflow diagrams, status conditions, HTTP details, and parallel processing explanation.", "EmptyFile": false, "IsArtifact": false, "Overwrite": false, "TargetFile": "/Users/nguyennhatminh/Documents/file code/Smatec/iot_fw_loader/FLASH_DOC.md

123
docs/api_flash_docs.md Normal file
View File

@@ -0,0 +1,123 @@
# Tài liệu Kỹ thuật: Flash Firmware qua LuCI API (`core/api_flash.py`)
Module `api_flash.py` tự động hoá quá trình nạp firmware cho thiết bị **OpenWrt** thông qua giao diện web **LuCI HTTP**. Được dùng trong chế độ **Nạp Mới FW** với method `"api"`.
---
## 1. Kiến Trúc — Vai Trò File
| File | Vai trò |
| -------------------------- | ----------------------------------------------------------------------- |
| `core/api_flash.py` | Logic flash 3 bước (login → upload → proceed) qua LuCI |
| `core/flash_new_worker.py` | `NewFlashThread` — dispatch tới `flash_device_api()` khi `method="api"` |
| `main.py` | UI PyQt6, chọn mode/method, truyền tham số vào worker |
---
## 2. Sơ Đồ Luồng
```mermaid
flowchart TD
A[NewFlashThread - method=api] --> B[flash_device_api]
B --> S1["STEP 1: Login\nGET /cgi-bin/luci — phát hiện field name\nPOST username=root & password="]
S1 --> C1{Thành công?}
C1 -->|sysauth cookie + stok| S2
C1 -->|HTTP 403| F1["FAIL: Login denied (403)"]
C1 -->|Không có session| F2["FAIL: Login failed — no session"]
S2["STEP 2: Upload Firmware\nPOST /flashops\nmultipart: image=firmware.bin"] --> C2{Response?}
C2 -->|Trang Verify + Proceed| S3
C2 -->|HTTP ≠ 200| F3["FAIL: Upload HTTP xxx"]
C2 -->|invalid image| F4["FAIL: Invalid firmware image"]
C2 -->|unsupported| F5["FAIL: Firmware not compatible"]
C2 -->|Vẫn hiện form upload| F6["FAIL: Upload ignored by server"]
S3["STEP 3: Proceed\nPOST step=2 & keep=empty"] --> C3{Response?}
C3 -->|Connection dropped / Timeout| R["DONE ✅ — Device đang reboot"]
C3 -->|200 OK| R
```
---
## 3. Chi Tiết Kỹ Thuật HTTP
### Step 1 — Login
```
GET http://{IP}/cgi-bin/luci → Lấy HTML login, phát hiện field name
POST http://{IP}/cgi-bin/luci → Đăng nhập
```
**Tự động phát hiện field name tương thích:**
| Phiên bản | Field |
| --------------------- | --------------------------------- |
| Barrier Breaker 14.07 | `username` / `password` |
| OpenWrt mới hơn | `luci_username` / `luci_password` |
**Lấy session:** `stok` token được tìm tuần tự trong URL → body HTML → redirect history. Cookie `sysauth` là fallback nếu không có `stok`.
### Step 2 — Upload Firmware
```
POST http://{IP}/cgi-bin/luci/;stok={TOKEN}/admin/system/flashops
Content-Type: multipart/form-data
image = firmware.bin (file upload)
keep = (không gửi) → bỏ tích "Keep Settings" = Clean Flash
```
Thành công khi response trả về trang **"Flash Firmware - Verify"** chứa từ khoá `verify` + `proceed`.
### Step 3 — Confirm (Proceed)
```
POST http://{IP}/cgi-bin/luci/;stok={TOKEN}/admin/system/flashops
step = 2
keep = (empty)
```
**Đứt kết nối = Thành công:** Device bắt đầu flash → SSH/HTTP request bị drop là hành vi mong muốn. `ConnectionError``ReadTimeout` đều được bắt và trả về `"DONE"`.
---
## 4. Bảng Status UI
| Icon | Status | Điều kiện |
| ---- | ---------------------------------------- | ------------------------------------------ |
| ⏳ | `Logging in...` | Đang POST login vào LuCI |
| ⏳ | `Uploading firmware...` | Đang upload file .bin |
| ⏳ | `Confirming (Proceed)...` | Đang gửi lệnh xác nhận flash |
| ⏳ | `Rebooting...` | Chờ device khởi động lại |
| ✅ | `DONE` | Flash thành công |
| ✅ | `DONE (rebooting)` | Flash thành công, timeout khi chờ response |
| ❌ | `FAIL: Cannot connect` | Không kết nối được (sai IP / khác mạng) |
| ❌ | `FAIL: Login denied (403)` | Sai mật khẩu LuCI |
| ❌ | `FAIL: Login failed — no session` | Không có cookie/token sau login |
| ❌ | `FAIL: Upload HTTP xxx` | Server trả lỗi HTTP khi upload |
| ❌ | `FAIL: Invalid firmware image` | File firmware không hợp lệ |
| ❌ | `FAIL: Firmware not compatible` | Firmware không tương thích thiết bị |
| ❌ | `FAIL: Upload ignored by server` | Server không xử lý file (sai form field) |
| ❌ | `FAIL: Unexpected response after upload` | Không nhận được trang Verify |
---
## 5. Xử Lý Song Song
```
NewFlashThread (QThread)
└── ThreadPoolExecutor (max_workers = N)
├── Thread 1 → flash_device_api(IP_1)
├── Thread 2 → flash_device_api(IP_2)
└── Thread N → flash_device_api(IP_N)
```
| Concurrent devices | Ý nghĩa |
| ------------------ | --------------------------------- |
| `10` (mặc định) | Flash 10 thiết bị song song |
| `0` | Unlimited — flash tất cả cùng lúc |
| `1` | Tuần tự — từng thiết bị một |
Mỗi thiết bị có `requests.Session()` riêng — không bị lẫn cookie/token.

View File

@@ -1,60 +1,166 @@
# Tài liệu Kỹ thuật: Nạp Firmware qua SSH (`core/ssh_flasher.py`)
# Tài liệu Kỹ thuật: Flash Firmware qua SSH
Module `ssh_flasher.py` chịu trách nhiệm nạp Firmware lên các thiết bị OpenWrt nạp mới hoặc nạp lại thông qua hai giao thức Telnet và SSH. Nó được thiết kế với độ an toàn cao để xử lý đa luồng (ví dụ: quét và nạp hàng loạt thiết bị cùng lúc), có cơ chế tự động thử lại (Retry) và cơ chế dự phòng mt khẩu khi thiết bị bị kẹt mật khẩu cũ.
Tài liệu này gộp toàn bộ logic SSH cho cả hai quy trình: **Nạp Mới FW** (Factory Reset)**Update FW** (thiết bị đang chạy). Luồng SSH được tách thành nhiều file với trách nhiệm rõ ràng thay vì gói gọn trong mt module duy nhất.
## 1. Sơ đồ Luồng Hoạt Động (Operational Flow)
---
## 1. Kiến Trúc Tổng Quan — Vai Trò Từng File
```
core/
├── ssh_utils.py ← Transport layer dùng chung (không có business logic)
├── ssh_new_flash.py ← Luồng SSH Nạp Mới FW (Telnet → SSH → sysupgrade)
├── ssh_update_flash.py ← Luồng SSH Update FW (SSH trực tiếp → sysupgrade)
├── flash_new_worker.py ← QThread điều phối Nạp Mới (API LuCI hoặc SSH)
└── flash_update_worker.py← QThread điều phối Update FW (SSH only)
```
### `core/ssh_utils.py` — Transport Layer Dùng Chung
Chứa các helper function cấp thấp, **không mang logic nghiệp vụ**, được import bởi cả 2 luồng SSH:
| Hàm | Mô tả |
| ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- |
| `_create_ssh_client(ip, user, password, timeout)` | Tạo SSH client với `AutoAddPolicy`, retry 3 lần. Nếu lỗi `AuthenticationException` thì raise ngay (không retry vô nghĩa). |
| `_upload_firmware(client, firmware_path, status_cb)` | Upload file `.bin` qua SCP lên `/tmp/<filename>`. Retry 3 lần, timeout SCP là 350 giây. Trả về `remote_path`. |
| `_verify_firmware(client, remote_path, status_cb)` | Chạy `test -f && ls -lh` để xác nhận file tồn tại trên device sau khi upload. |
| `_sync_and_sysupgrade(client, remote_path, status_cb)` | Chạy `sync` rồi `sysupgrade -F -v -n`. Đứt kết nối = thành công. Trả về `"DONE"` hoặc `"FAIL: ..."`. |
### `core/ssh_new_flash.py` — Luồng Nạp Mới FW
Xử lý thiết bị **vừa Factory Reset** hoặc **chưa có mật khẩu**. Bao gồm:
- **`_SimpleTelnet`**: Telnet client thủ công bằng raw socket — thay thế `telnetlib` bị xoá khỏi Python 3.13+.
- **`set_device_password()`**: Đặt mật khẩu thiết bị, thử Telnet port 23 trước, fallback sang SSH nếu Telnet đóng.
- **`flash_device_new_ssh()`**: Hàm flash chính — gọi `set_device_password` (nếu `set_passwd=True`) rồi kết nối SSH và gọi pipeline `_upload → _verify → _sync_and_sysupgrade` từ `ssh_utils`.
### `core/ssh_update_flash.py` — Luồng Update FW
Xử lý thiết bị **đang chạy MiraV3**, SSH đã mở sẵn với pass đã biết. Không có Telnet, không có set_passwd:
- **`flash_device_update_ssh()`**: Kết nối SSH (thử `password`, fallback `backup_password` nếu auth fail), rồi gọi thẳng pipeline `_upload → _verify → _sync_and_sysupgrade` từ `ssh_utils`.
### `core/flash_new_worker.py` — QThread Nạp Mới
`NewFlashThread` — điều phối flash song song cho chế độ **Nạp Mới**:
- Nếu `method="ssh"` → gọi `flash_device_new_ssh()` từ `ssh_new_flash`.
- Nếu `method="api"` → gọi `flash_device()` từ `flasher` (LuCI HTTP).
- Nhận đủ credentials từ UI: `ssh_user`, `ssh_password`, `ssh_backup_password`, `set_passwd`.
### `core/flash_update_worker.py` — QThread Update FW
`UpdateFlashThread` — điều phối flash song song cho chế độ **Update FW**:
- Luôn dùng SSH, credentials hardcode an toàn trong module (`root` / `admin123a`, backup `admin`).
- Không nhận credentials từ UI — tránh nhập sai gây flash nhầm.
---
## 2. Sơ Đồ Luồng — Nạp Mới FW (SSH)
```mermaid
graph TD
A[Bắt đầu Flash SSH] -->|Jitter Delay 0.1s - 1.5s| B{Có yêu cầu Set Password?}
B -- Không --> F[Bước 1: Kết nối SSH]
B -- --> C[Thử kết nối Telnet port 23]
A[NewFlashThread.run] --> B{method = ssh?}
B -- api --> API[flash_device - LuCI HTTP]
B -- ssh --> C[flash_device_new_ssh]
C -- Thành công --> C1[Gửi lệnh `passwd` > admin123a] --> F
C -- Lỗi / Timeout / Bị từ chối --> D[Thử SSH với mật khẩu rỗng]
C -->|set_passwd=True| D[set_device_password]
C -->|set_passwd=False| Jitter[Jitter 0.1s - 1.5s]
D -- Thành công --> D1[Gửi lệnh `passwd` > admin123a] --> F
D -- Lỗi 'Authentication Failed' --> E[Thử SSH với Backup Password]
D --> D1[Thử Telnet port 23]
D1 -- Thành công --> D2[passwd > new_password via Telnet]
D2 --> D3[Chờ 3s để Dropbear khởi động]
D1 -- Lỗi / Timeout --> D4[Fallback SSH với old_password rỗng]
D4 -- Auth Fail --> D5[Thử backup_password]
D5 -- Auth Fail --> D6[Thử chính new_password - idempotent]
D6 -- Fail --> Z[DỪNG - Báo FAIL]
D3 --> F
D4 -- OK --> F
D5 -- OK --> F
D6 -- OK --> F
E -- Thành công --> E1[Gửi lệnh `passwd` > admin123a] --> F
E -- Thất bại --> E2[Thử SSH với Password cấu hình]
E2 -- Thành công --> F
E2 -- Thất bại --> Z[DỪNG LẠI - Báo Lỗi]
F -->|Kết nối SSH thành công sau Retry 3 lần| G[Bước 2: Push Firmware SCP vào /tmp/]
G -->|Tối đa 3 lần Retry| H[Bước 3: Xác minh File `test -f`]
H --> I[Bước 4: Đồng bộ bộ nhớ `sync`]
I --> J[Bước 5: Gọi `sysupgrade -F -v -n /tmp/...`]
J --> K{Kết nối bị đứt?}
K -- Có --> L[Khởi động lại thành công - Báo DONE]
K -- Không --> M[Báo lỗi Sysupgrade]
Jitter --> F[_create_ssh_client - retry 3 lần]
F --> G[_upload_firmware via SCP vào /tmp/]
G -->|retry 3 lần| H[_verify_firmware - test -f]
H --> I[_sync_and_sysupgrade]
I --> J[sync rồi sysupgrade -F -v -n]
J --> K{Kết nối đứt?}
K -- Có --> L[DONE - Device đang Reboot]
K -- Không trong 4s --> M[FAIL - Lấy log /tmp/sysup.log]
```
## 2. Phân Tích Logic Cốt Lõi
## 3. Sơ Đồ Luồng — Update FW (SSH)
### 2.1 Cơ Chế Dự Phòng Mật Khẩu Đa Tầng (Fallback Mechanism)
```mermaid
graph TD
A[UpdateFlashThread.run] --> B[flash_device_update_ssh]
B --> C[_create_ssh_client với password chính]
C -- Auth OK --> F
C -- AuthenticationException --> D[Thử backup_password]
D -- Auth OK --> F
D -- Fail --> Z[DỪNG - Báo FAIL]
Điểm mạnh của thuật toán là khả năng "lì lợm" lấy được quyền Root để đặt mật khẩu cho thiết bị. Các lớp dự phòng bao gồm:
F[_upload_firmware via SCP vào /tmp/] -->|retry 3 lần| G[_verify_firmware - test -f]
G --> H[_sync_and_sysupgrade]
H --> I[sync rồi sysupgrade -F -v -n]
I --> J{Kết nối đứt?}
J -- Có --> K[DONE - Device đang Reboot]
J -- Không trong 4s --> L[FAIL - Lấy log /tmp/sysup.log]
```
1. **Telnet (Port 23):** Router OpenWrt khi mới xuất xưởng hoặc ngay sau khi Factory Reset sẽ chặn kết nối SSH bằng tài khoản `root` mất gốc, nhưng lại mở Telnet không có mật khẩu. Script sẽ luôn thử bẻ khóa qua đường này đầu tiên. Bất kỳ ngoại lệ nào phát sinh ở luồng này (Refused, Timeout, Broken Pipe) cũng sẽ kích hoạt fallback.
2. **SSH (Mật khẩu rỗng):** Nếu Telnet bị đóng cửa (chứng tỏ thiết bị đã được kích hoạt sơ), phần mềm thử mở cổng 22 với User `root` và mật khẩu rỗng `""`.
3. **SSH (Backup Password):** Nếu mật khẩu rỗng bị từ chối bằng `AuthenticationException`, script định nhận diện máy đã bị kẹt một pass được cài đặt trước đó và chèn `backup_password` ra thử nghiệm.
4. **SSH (Target Password):** Nếu trượt tới lớp thứ 3, script thử nốt với chính mật khẩu mục tiêu của dự án (mặc định: `admin123a`) để chặn trường hợp thiết bị đã đổi Pass thành công từ lần trước nhưng quá trình nạp Firmware chưa diễn ra.
---
### 2.2 Xử Lý Đa Luồng Chống Nghẽn (Concurrency Limits)
## 4. Phân Tích Logic Cốt Lõi
Thư viện `paramiko` thường dễ đổ vỡ cấu trúc và báo lỗi `[Errno 64] Host is down` hoặc `[Errno 32] Broken Pipe` khi có vài chục luồng (threads) cùng mở port đập vào Network Stack OS (hoặc Switch/Router) ở đúng một chớp mắt. Lỗi này là dạng Transient (có tính tạm thời). Do đó hệ thống được nâng cấp:
### 4.1 Cơ Chế Dự Phòng Mật Khẩu Đa Tầng (Nạp Mới)
- **Jitter (Độ trễ ngẫu nhiên):** Sử dụng `time.sleep(random.uniform(0.1, 1.5))` trước khi chọc vào thiết bị. Sự sai lệch mili-giây này phân tán chùm yêu cầu dội vào mạng, tránh tự gây ra một cuộc tấn công từ chối dịch vụ (DDoS) nội bộ.
- **Vòng lặp (Retry Hooks):** Quá trình đăng nhập (`_create_ssh_client`) cũng như upload payload (`SCPClient.put`) đều được bọc trong bộ đếm thử lại cực đỉnh (thử lại 3 lần sau mỗi 1-2 giây nghỉ). Việc này triệt hạ 99% các lỗi truyền TCP/IP và Timeout.
Thuật toán `set_device_password` có 4 lớp dự phòng để chiếm được quyền Root trên thiết bị dù ở trạng thái nào:
### 2.3 Cơ Chế Khôi Phục File (RAM Disk SCP)
1. **Telnet (Port 23):** OpenWrt ngay sau Factory Reset mở Telnet không cần mật khẩu nhưng chặn SSH. Script luôn thử cổng này đầu tiên. Mọi exception (Refused, Timeout, Broken Pipe) đều kích hoạt fallback.
2. **SSH mật khẩu rỗng `""`:** Nếu Telnet đóng, thử SSH với pass rỗng — trường hợp thiết bị đang ở trạng thái semi-configured.
3. **SSH Backup Password:** Nếu pass rỗng bị `AuthenticationException`, thiết bị đang kẹt một pass cũ — thử `backup_password`.
4. **SSH Target Password (Idempotent):** Thử lại với chính `new_password` để chặn trường hợp device đã đổi pass thành công từ lần flash trước nhưng chưa được flash firmware.
- Firmware được tống thẳng tới phân vùng ảo RAM cục bộ đích (ví dụ: `/tmp/<tên_file>.bin`) thông qua giao thức SCP. Ghi file trực tiếp vào RAM cho phép đạt tốc độ tuyệt đối nhanh và tránh gây hao mòn sinh học lên FlashNOR của router.
- Gửi lệnh `sync` qua kênh Shell để ép thiết bị lưu transaction xuống vùng nhớ cứng, chuẩn bị môi trường ổn định nhất.
### 4.2 Cơ Chế Dự Phòng Mật Khẩu (Update FW)
### 2.4 Thủ Thuật Gọi `sysupgrade -F`
Đơn giản hơn: thử `password` chính → nếu `AuthenticationException` → thử `backup_password`. Không có Telnet, không có set_passwd. Nếu cả hai đều fail thì trả về `FAIL` ngay.
- OpenWrt đời mới khá kén phần Metadata của Firmware Image. Cờ `-F` cởi trói kiểm duyệt đó và ép hệ điều hành nạp đè File bất chấp sự vắng mặt của metadata uImage. Cờ `-n` khiến hệ thống mất sách cấu hình cũ (Clean Flash).
- Lệnh `sysupgrade` sẽ cắn xén cả phần OS dưới lõi máy sau đó Tắt mạng đột ngột. Do đó, script được lập trình để xử lý Exception: Sự kiện **MẤT KẾT NỐI CHỦ ĐỘNG** trong khu vực này là dấu hiệu ăn mừng của việc cài đặt thành công thay vì báo hỏng kết nối.
### 4.3 Xử Lý Đa Luồng Chống Nghẽn
`paramiko` dễ báo lỗi `[Errno 64] Host is down` hoặc `[Errno 32] Broken Pipe` khi hàng chục thread cùng hit network stack cùng một lúc. Hệ thống chống nghẽn bằng 2 cơ chế:
- **Jitter ngẫu nhiên:** `time.sleep(random.uniform(0.1, 1.5))` phân tán chùm request theo thời gian, tránh tự gây DDoS nội bộ.
- **Retry Hooks:** `_create_ssh_client` retry 3 lần (nghỉ 1s giữa mỗi lần). `_upload_firmware` retry 3 lần (nghỉ 1.5s). Triệt 99% lỗi TCP transient.
### 4.4 RAM Disk SCP
Firmware được upload thẳng vào `/tmp/<tên_file>.bin` — phân vùng RAM ảo của OpenWrt. Lợi ích:
- Tốc độ ghi nhanh nhất (RAM vs Flash).
- Không gây hao mòn FlashNOR của router.
- File biến mất sau reboot, không để lại rác.
### 4.5 Thủ Thuật `sysupgrade -F -v -n`
| Cờ | Tác dụng |
| -------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `-F` (Force) | Bỏ qua kiểm tra Image Metadata — bắt buộc với file build Raw / `tim_uImage` để tránh lỗi _"Image metadata not present"_. |
| `-v` (Verbose) | Log chi tiết vào `/tmp/sysup.log` để debug khi cần. |
| `-n` (No-keep) | Clean Flash — không giữ cấu hình cũ, tránh xung đột config giữa các phiên bản. |
**Đứt kết nối = Thành công:** `sysupgrade` phá kernel hiện tại rồi reboot nạp firmware mới → SSH session bị đứt là kết quả tất yếu và mong muốn. Script bẫy exception này và trả về `"DONE"`. Ngược lại, nếu sysupgrade fail sớm (< 4 giây, còn giữ kết nối), script đọc `/tmp/sysup.log` trả về error code đầy đủ.
---
## 5. Trình Gỡ Lỗi Nhanh
Cả hai luồng đều gọi `status_cb(msg)` tại mỗi bước toàn bộ tiến độ như _"Connecting SSH"_, _"Uploading firmware"_, _"Syncing filesystem"_ hiển thị trực tiếp tại cột **Status** trên giao diện chính.
Để debug chi tiết hơn, chạy app qua terminal:
```bash
./run.sh
```
Mọi exception đều bị bắt trả về chuỗi `"FAIL: <lý do>"` hiển thị lên UI không silent failure.

View File

@@ -1,59 +0,0 @@
# Tài liệu Kỹ thuật: Cập Nhật Firmware (Update FW)
Quy trình `Update FW` là phương thức tối ưu hóa để nâng cấp Firmware mới vào các thiết bị OpenWrt Live (đã và đang chạy sẵn Firmware của hệ sinh thái MiraV3). Cơ chế này dùng chính giao thức SSH thông qua `core/ssh_flasher.py` nhưng lược bỏ hoàn toàn các bước dò tìm và thiết lập mật khẩu thừa thãi.
## 1. Sơ đồ Luồng Hoạt Động (Operational Flow)
```mermaid
graph TD
A[Bắt đầu Update FW] --> B{Kiểm tra danh sách IP}
B -- Có IP lạ != 192.168.11.102 --> C[Bật MessageBox cảnh báo]
C -- Người dùng No --> Z[HỦY BỎ]
C -- Người dùng Yes --> D
B -- Chỉ có 192.168.11.102 --> D
D[Truyền tham số cấu hình tĩnh] --> E[ssh_user: root<br>ssh_password: admin123a<br>set_passwd: False]
E -->|Jitter Delay 0.1s - 1.5s| F[Kết nối thẳng SSH Port 22]
F -- Thành công --> G[Push Firmware SCP vào /tmp/]
F -- Lỗi / Sai Pass --> Y[DỪNG LẠI - Báo Lỗi]
G -->|Tối đa 3 lần Retry| H[Xác minh File `test -f`]
H --> I[Đồng bộ bộ nhớ `sync`]
I --> J[Gọi Sysupgrade: `sysupgrade -F -v -n /tmp/...`]
J --> K{Kết nối bị đứt?}
K -- Có --> L[Khởi động lại thành công - Báo DONE]
K -- Không --> M[Báo lỗi Sysupgrade]
```
## 2. Phân Tích Logic Cốt Lõi
### 2.1 Cơ Chế Bỏ Qua Telnet (Skip Set Password)
Điểm khác biệt lớn nhất của luồng Update FW so với Nạp Mới:
- Thông qua UI `Chế độ Flash > Update Firmware`, hệ thống hiểu rằng thiết bị đích đã từng được cài đặt hệ sinh thái MiraV3 và chắc chắn đã sở hữu lớp bảo mật SSH cơ bản (root/admin123a).
- Module `main.py` tự động kích hoạt cờ tuỳ chọn ngầm `set_passwd = False` khi gọi `FlashThread`.
- **Bỏ Bầu Trời Khởi Động:** Hệ thống ngay lập tức rẽ ngang vào luồng SSH trên Port 22. Tool bỏ qua hoàn toàn quy trình truy vấn cổng Telnet (23) chậm chạp cũng như các lệnh Set Password (vốn mất thêm từ 3-5 giây chờ đợi). Tốc độ tổng thể tăng tốc cực đáng kể, phục vụ trực tiếp cho quá trình Mass-Update.
### 2.2 Ràng Buộc An Toàn (Safety Checks)
Được kiểm soát từ tầng UI (`main.py`) để chống Flash nhầm hàng hoạt:
- Khi người dùng chọn chế độ Update, script quét qua danh sách MAC/IP đang tích chọn. Chỉ duy nhất thiết bị có IP cấu hình cứng `192.168.11.102` mới được âm thầm qua cửa (được xem là IP mặc định an toàn cho việc test Firmware).
- Nếu phát hiện **Bất kỳ IP lạ nào** (ví dụ: 192.168.1.5, 192.168.11.9), hệ thống lập tức chặn quy trình Flasher lại và bật MessageBox cảnh báo màu đỏ hỏi kỹ thuật viên xác nhận lại.
### 2.3 Quá Trình Sysupgrade (Hủy diệt và Tái sinh)
Cũng giống như luồng nạp mới, quá trình xả file dùng chung tệp lệnh: `sysupgrade -F -v -n /tmp/<tên_file>`
- **Cờ `-n`**: Clean Flash sạch trơn, không giữ Configuration rác của bản cũ.
- **Cờ `-F` (Force)**: Ép hệ điều hành OpenWrt bỏ qua bước kiểm tra Image Metadata tại Local. Tuỳ chọn này sống còn đối với các tệp tin Firmware trần trụi (như build Raw `tim_uImage`) để tránh việc sysupgrade từ chối file, văng lỗi "Image metadata not present" và ngắt ngang tiến trình.
- Kịch bản hoàn hảo nhất của quá trình này lại chính là **bị đứt kết nối mạng đột ngột**. Script đã bẫy một khoảng thời gian chờ (time.sleep) để canh việc Server SSH sập nguồn. Đứt mạng nghĩa là Kernel chuẩn bị Reboot nạp File, và hệ thống sẽ bắt Catch đánh giá là Thành Công (`DONE`). Ngược lại, nếu Sysupgrade thất bại, nó sẽ phun ra Log và giữ nguyên kết nối, lúc đó Script sẽ chụp lại Error Code và in ra UI.
---
## 3. Trình Gỡ Lỗi Nhanh (Debugging Update flow)
Lưu ý: Bạn đã xóa các file script debug đơn lẻ khỏi thư mục gốc (`debug_ssh.py`). Hiện tại luồng Update FW nên được gỡ lỗi trực tiếp bằng cách in Print/Log ra terminal của lệnh `./run.sh` lúc chạy App UI. Mọi tiến độ như "Đang kết nối SSH", "Đồng bộ file" được hiển thị minh bạch tại cột **Status** trên giao diện chính.