Một nền tảng tự động hoá trình duyệt quy mô fleet: người dùng bấm chạy một workflow trên n8n, lệnh bay qua MQTT tới một máy Windows ở site khác, máy đó mở Chrome bằng GPM Login, rồi n8n điều khiển ngược lại chính trình duyệt đó qua cổng CDP.
WP2_PC176).remote_port / profile_id.http://127.0.0.1:19995/api/v3) để mở profile Chrome, hoặc mở
Chrome local.RES_<topic>, khớp bằng meta.requestId..tar không nén — CHƯA CÓ CODE03_NEW → 04_USING →
05_DONE.
NGƯỜI DÙNG NGƯỜI DÙNG (giám sát)
│ │ │
│ https://n8n.lotus1104.synology.me │ │
▼ ▼ ▼
┌──────────────────────────── HP1 · WP1 · 172.16.10.220 ────────────────────────────┐
│ B16 lotus_caddy :80/:443 — reverse proxy HTTPS cho *.lotus1104.synology.me │
│ ├── n8n. → B15 lotus_n8n:5678 (host 20150 = API, 20151) │
│ ├── gpmfleet. → B34 lotus_GPM_Fleet_Monitor:8080 (host 20340) │
│ ├── scanIpProxy → B30 lotus_python_proxy_script:8080 (host 20300) │
│ ├── web. → B06 lotus_mqtt:8081 (share file tĩnh — trang bạn đang đọc)│
│ └── novnc. → B35 lotus_novnc_chrome:7900 (20350 xem / 20351 Selenium) │
│ │
│ B27 lotus_redis (Redis Stack) :6379 → host 20270 ← IP POOL + LOTUS:01_MAN:* │
│ B28 lotus_redisinsight → host 20280 (GUI Redis) │
│ B06 lotus_mqtt (mosquitto) — broker NỘI BỘ (MQTTS 20061 / WSS 20063) │
└───────────────────────────────────────────────────────────────────────────────────┘
│ (1) PUBLISH lệnh ▲ (4) heartbeat HTTP mỗi 2s
▼ │
┌───────────────────────────────┐ │
│ MQTT BROKER ĐANG DÙNG THẬT │ │
│ 128.199.200.17:1883 │◄── KHÔNG phải B06! │
└───────────────┬───────────────┘ │
│ (2) topic = WP2_PC176 │
▼ │
┌──────────────────── WORKER (Windows) ────────────────────┐│
│ NUC1 · WP3 · 172.16.30.101 (hostname WP3-PC1) │┘
│ APP_5 → 2_GPM_multi_remote_browser_process_manager_gui │
│ ├── engine A12 Playwright (payload great-lotus-v2) │
│ ├── engine A13 PyAutoGUI (great-lotus-pyauto-v1) │
│ └── GPM Login http://127.0.0.1:19995/api/v3 │
│ └── mở Chrome + cổng CDP ──► (3) RES_<topic> │
└──────────────────────────────────────────────────────────┘
│ cần │ cần
▼ ▼
┌──────────────────────────┐ ┌─────────────────────────────────────┐
│ NAS2 White NAS · WP2 │ │ IP POOL (Redis B27) ← ScanIpProxy │
│ 172.16.20.201 │ │ 03_NEW → 04_USING → 05_DONE │
│ kho profile .tar │ │ ▲ DCOM 4G ở DELL_Proxy │
│ (THIẾT KẾ — chưa có code)│ │ │ 172.16.20.202:6868 │
└──────────────────────────┘ └─────────────────────────────────────┘
Ba site nối bằng VPN IPSec full-mesh ⇒ mọi máy gọi thẳng IP LAN của site khác. Chi tiết hạ tầng mạng: xem tài liệu Bản đồ hệ thống mạng 3 Workpool.
| Trang | Dùng để | Đi tới đâu |
|---|---|---|
https://n8n.lotus1104.synology.me/ |
Dựng & chạy workflow | B15 lotus_n8n:5678 |
https://gpmfleet.lotus1104.synology.me/ |
Giám sát đội máy worker | B34 :8080 |
https://scanipproxy.lotus1104.synology.me/ |
Giám sát vòng đời IP proxy | B30 :8080 |
| Hạng mục | Sự thật đã xác minh |
|---|---|
| URL người dùng | https://n8n.lotus1104.synology.me/ (qua Caddy B16 → lotus_n8n:5678) |
| Container | lotus_n8n · IP 114.114.114.15 · host 20150 (=5678, cũng là API),
20151 |
| REST API | http://127.0.0.1:20150/api/v1/… · header X-N8N-API-KEY · key ở
N8N_API_KEY trong .env cạnh docker-compose.yml
|
| Credential đăng nhập | N8N_EMAIL / N8N_PASSWORD trong cùng .env |
| Workflow mẫu | 8SEPoybAbRtA7oe4 = 02_RegHotMail_WEB_PyAutoGUI_TEST_MCP — 77 node, dùng cả
C10 lẫn C12 trong cùng workflow, tất cả publish về topic WP2_PC176 |
N8N_SECURE_COOKIE ⇒ không đăng nhập
được
qua http://lotus_n8n:5678. Muốn mở GUI bằng trình duyệt tự động (container B35) phải đi HTTPS
kèm --host-resolver-rules=MAP n8n.lotus1104.synology.me 114.114.114.16.Cả hai đều là MQTT command emitter thuần: không import Playwright/PyAutoGUI,
chỉ mqtt.connect → publish → (tuỳ chọn) chờ RES_.
C10 n8n-nodes-c10_great_lotus |
C12 n8n-nodes-c12_pyautogui_web |
|
|---|---|---|
| Tên node | GREAT LOTUS · nodeVersion 2 |
GREAT LOTUS PYAUTO WEB · nodeVersion 1 |
payload.version |
great-lotus-v2 |
great-lotus-pyauto-v1 |
| Engine ở worker | A12 Playwright — CDP, DOM thật, chạy song song được | A13 PyAutoGUI — chuột/bàn phím vật lý, bị mutex tuần tự hoá |
| Action GPM profile | có đủ 8 action (list/get/create/open/close/update/delete profile, list_groups) +
gpm_connect
|
không có; gpm_connect đã bị xoá |
| Kích thước mã | ~4269 dòng TS | ~1819 dòng TS |
| Vai trò | Topic | Ai publish | Ai subscribe |
|---|---|---|---|
| Request | <topic> người dùng nhập, VD WP2_PC176 |
node C10/C12 | Worker |
| Response | RES_<topic> — tự suy ra, không cấu hình được |
Worker | node (chỉ khi waitForResponse=true) |
// REQUEST node → worker
{
"version": "great-lotus-v2", // hoặc great-lotus-pyauto-v1
"action": { "key":"mouse.click", "group":"MouseAction", "function":"click" },
"inputs": { "remote_port": {"type":"number","value":8888},
"x": {"type":"number","value":320} }, // ô để TRỐNG bị loại khỏi payload
"meta": { "requestId":"a1b2c3d4e5f60718", "requestTopic":"WP2_PC176",
"responseTopic":"RES_WP2_PC176", "waitForResponse":true,
"responseTimeoutSec":60 }
}
// RESPONSE worker → node
{ "return":"OK"|"NG", "result":true, "error":"…", "data":{…},
"meta":{ "requestId":"a1b2c3d4e5f60718" } } // PHẢI echo đúng requestId
action.function = khoá dispatch phía Python. Đổi nó ⇒ phá worker.return: "NG" ⇒ node ném lỗi, đọc chi tiết theo thứ tự error →
message → data.error.
image.wait_color: result === false cũng là fail dù return
không NG.meta.requestId — nếu không, node sẽ chấp nhận response đầu tiên bất
kỳ ⇒ nhận nhầm khi chạy song song trên cùng topic.lotus_mqtt (mosquitto, MQTTS 20061 / WSS 20063),
nhưng luồng chạy thật lại dùng một broker công cộng bên ngoài: 128.199.200.17:1883
— cấu hình y hệt ở cả worker (*_config.json) lẫn ScanIpProxy (docker-compose.yml).
Đã kiểm chứng cổng này mở và đang phục vụ.
Đây là rủi ro tập trung: mất VPS đó là mất cả đường điều khiển lẫn đường quản lý proxy.
B06 đang chạy nhưng không nằm trên đường điều khiển chính.
Application/APP_5_GPM_GREAT_LOTUS/App/2_GPM_Multi_Remote_Browser_Process_Manager_GUI/
| File | Vai trò |
|---|---|
2_GPM_…_gui.bat |
Bootstrap: dò Python → tạo venv trên ổ LOCAL
%LocalAppData%\GPM_ProcessManagerGUI\… → cài 12 gói pip → chạy GUI
|
GPM_multi_remote_browser_process_manager_gui.py~2900 dòng |
GUI Tkinter: cấu hình, log màu, bảng worker, heartbeat fleet |
_core/GPM_multi_remote_browser_process_manager.py~2472 dòng |
Bộ định tuyến MQTT → worker process; _parse_route(), mutex desktop, faulthandler
|
_core/GPM_multi_remote_browser_manager.py~1942 dòng |
Lớp session: gọi GPM HTTP API, chọn engine, dựng addination_args |
.bat — đừng phá:
venv phải nằm ổ local (pip qua SMB/UNC cực chậm, nhìn như GUI treo) ·
tên thư mục venv cố định (map UNC sang chữ ổ khác nhau mỗi phiên không được đẻ venv mới) ·
không cd vào UNC path (cmd.exe âm thầm rơi về C:\Windows\System32).
*_config.json| Khoá | Ý nghĩa |
|---|---|
broker / port / username / password |
MQTT |
subscribe_topics |
Topic worker lắng nghe — phải khớp ô Topic của node n8n |
response_prefix |
RES_ |
gpm_base_url |
http://127.0.0.1:19995 — GPM Login API, không kèm /api/v3 |
work_pool / pc_name |
Cặp định danh trên Fleet Monitor — trùng cặp = đè lên cùng một card |
monitor_server_url |
https://gpmfleet.lotus1104.synology.me/ |
max_request_workers |
mặc định 8 — số luồng xử lý request đồng thời |
enable_fleet_monitor |
true mới gửi heartbeat |
pc_name = "NUC", nhưng NUC1 thật đang báo cáo
pc_name = "PC_1", hostname = WP3-PC1, Windows 11.
File repo chỉ là bản mẫu — luôn đọc trạng thái thật qua Fleet Monitor API, đừng tin file config trong
git.
_parse_route()Một máy worker chạy nhiều trình duyệt cùng lúc. Mỗi session = một
worker_key = một tiến trình con riêng.
| Trường hợp | worker_key |
|---|---|
manager.open_profile (có profile_id) |
profile:<profile_id> + restart worker |
manager.local_browser_connect |
local:<session_id> (mặc định local:default) |
manager.gpm_connect có profile_id / chỉ có port |
profile:<id> / remote:<port> |
Action thường có profile_id đã map |
worker đang giữ profile đó |
Action thường có remote_port |
worker đang giữ cổng đó |
manager.quit + quit_all = true |
__quit_all__ — đóng SẠCH mọi session |
others.random, General Debug |
control:utility_random, control:general_debug — không cần session |
127001 và chuỗi sessionremote_port = 127001 nghĩa là "session local" (gợi nhớ 127.0.0.1) —
không phải cổng TCP. Chọn số ngoài dải 1..65535 để không bao giờ đụng cổng CDP thật.
0 vẫn được chấp nhận nhưng không dùng nữa vì 0 là
falsy → dễ bị coi nhầm là "chưa nhập" khi đi qua expression n8n.
remote_debugging_port (tên C10) và remote_port (tên C12), cùng giá trị
⇒ nối chéo C10 ↔ C12 trong một workflow đều chạy.
Remote Port của action thường mặc định ={{$json.remote_debugging_port}}
⇒ cổng tự chảy xuôi chuỗi node.127001 ra,
nếu không create_connection(127001) sẽ wrap thành 61465 và dò nhầm sang dịch
vụ khác.[open_profile] ──► Client mở trình duyệt qua GPM, trả về cổng CDP
│ C10 rút cổng từ response và GẮN vào output:
▼ json.remote_debugging_port = <port>
[mouse.click] ──► ô remote_port mặc định ={{$json.remote_debugging_port}}
│ → attach đúng phiên → click đúng cửa sổ
▼
[element.get_text] ─► tương tự, cùng session…
Anti-detect browser: mỗi profile = một danh tính số độc lập (fingerprint, cookie,
cache riêng), chạy nhiều Chrome song song. Cổng 16813 cũng xuất hiện trong repo —
phiên bản GPM khác nhau đổi cổng, luôn lấy từ gpm_base_url.
| Action | HTTP | Endpoint | Tham số đáng nhớ |
|---|---|---|---|
list_profiles |
GET | /profiles |
page, per_page, group_id, search |
get_profile_info |
GET | /profiles/{id} |
|
create_profile |
POST | /profiles/create |
profile_name*, group_name*, raw_proxy, fingerprint
flags |
open_profile |
GET | /profiles/start/{id} |
addination_args, remote_port, win_scale/pos/size |
close_profile |
GET | /profiles/close/{id} |
|
update_profile |
POST | /profiles/update/{id} |
raw_proxy, group, fingerprint… |
delete_profile |
GET | /profiles/delete/{id}?mode= |
mode mặc định 2 |
list_groups |
GET | /groups |
open_profile không nhận proxy MỚI khi mở.
open_profile = GET /profiles/start/<id>, tham số chỉ có
addination_args/remote_port/win_* — không có ô proxy. Profile vẫn chạy được với
proxy, nhưng là proxy đã lưu sẵn trên nó. Chỉ create_profile và
update_profile mới nhận raw_proxy. ⇒ muốn gán/đổi proxy (luồng IP pool)
thì phải update_profile(raw_proxy=…) TRƯỚC, rồi mới open_profile.
Định dạng GPM: host:port:user:pass (bỏ :user:pass nếu proxy không auth).
_handle_open_profile (chỉ query
addination_args/remote_port/win_*) vs _handle_create_profile/_handle_update_profile
(có raw_proxy).
| Hạng mục | Sự thật |
|---|---|
| URL | https://gpmfleet.lotus1104.synology.me/ |
| Container | lotus_GPM_Fleet_Monitor · 114.114.114.34 · host 20340 → 8080 |
| Image | python:3.12-alpine · không dependency ngoài (http.server thuần)
|
| Giao thức | HTTP JSON — KHÔNG dùng MQTT cho kênh giám sát |
| Nhịp | client POST /api/heartbeat mỗi 2s · offline nếu > 10s |
| Định danh 1 máy | cặp (work_pool, pc_name) — trùng cặp = đè cùng card |
| Chiều ngược | request_screenshot, kill_workers, restart_service,
restart_pc
|
WP3 có 1 máy
PC_1 (WP3-PC1, Windows 11, CPU 4/8 nhân, RAM 23.9GB, uptime ~10.5 ngày),
MQTT connected, 0 worker đang mở, 120 sự kiện gần đây đều là action tới topic
WP2_PC176 với worker_key = local:default. ⇒ Đường n8n → MQTT → worker → B34
đang chạy tốt.
Nguồn: báo cáo UID_00001_profile_optimization_report.html — thiết kế cho quy mô
10.000 profile.
| Nguyên tắc | Nội dung |
|---|---|
| Kho tập trung | Mỗi profile đóng thành <profile_id>.tar KHÔNG NÉN trên NAS |
| Config phi tập trung | Mỗi profile tự mang config.json trong gói — không cần DB trung tâm |
| Vận hành cục bộ | Tải .tar → xả nén ra SSD local (C:\temp_profiles) → đọc config → chạy →
đóng gói lại → upload đè → xoá sạch thư mục tạm |
| Khống chế dung lượng | Cấu hình Chromium giữ profile ≤ 50MB thay vì 300MB |
| Cờ | Giá trị | Mục đích |
|---|---|---|
--disk-cache-size |
lấy từ config.json |
cache thích ứng 30–100MB |
--media-cache-size |
1 |
tắt hẳn cache video/audio — chặn YouTube làm phình profile |
--disable-pdf-viewer |
bật | PDF về Downloads thay vì nằm trong cache |
| (khuyến nghị) | uBlock Origin | giảm thêm 40–60% dữ liệu tải |
// config.json nằm ở gốc mỗi profile
{ "profile_id": "profile_101", "status": "active",
"cache_settings": { "current_cache_limit_mb": 50, "last_actual_cache_mb": 42.5 },
"audit_logs": { "total_runs": 124, "last_run_time": "2026-07-19T18:12:00Z",
"last_run_machine": "CLIENT-PC-04", "last_run_duration_seconds": 320 } }
Sau mỗi lần chạy, tính lại hạn mức cache bằng AIMD (dùng gần chạm trần → +20MB, tối đa 100MB;
dùng ít → giảm) rồi ghi đè config.json trước khi đóng gói.
config.json — để GPM
tự quản, tránh trùng lặp nguồn sự thật. Đây chính là lý do proxy phải đi đường
update_profile(raw_proxy).
.tar? — số đo quyết địnhProfile 50MB, LAN ~40–50MB/s.
| Cách làm | Thời gian khởi chạy | Ghi chú |
|---|---|---|
| TAR không nén (đề xuất) | 1.45 s | 0% tải CPU local, tải tuần tự nhanh nhất |
| ZIP/ZSTD có nén | 3.12 s | mạng nhanh hơn nhưng tốn 1.5–3s CPU giải nén |
| Chạy thẳng từ NAS qua SMB | 166.7 s | ❌ random I/O trên SMB rơi còn ~1.5 MB/s |
.tar rồi chạy trên SSD local.Trong luồng chạy thật của worker, mắt xích "tải profile từ NAS" vẫn chưa được nối:
grep toàn repo (APP_5, A12_PlayWright, A13_PyAutoGUI_Web,
B05_GPM_Login_Manager) với smb|cifs|webdav|nas_|profile*.tar|temp_profiles
không có một dòng nào. Thư viện Library/C02_NAS/nas.py chỉ là SSH tới NAS1
(172.16.10.201:222), không liên quan kho profile.
Cái worker đang chạy thật vẫn chỉ là open_profile (mở profile có sẵn trên máy) và
sync_master_profile_to_local_test_profile (copy master cục bộ).
docs/tools/profile_store.py — hiện thực đầy đủ vòng đời thiết kế
(init / push / pull / quarantine / list / verify), TAR không nén, upload nguyên tử
incoming/ → os.replace → profiles/, config.json + AIMD, sha256 trong manifest, chống
path-traversal khi giải nén. Chạy được cả trên Linux (CIFS mount) lẫn Windows worker (UNC
\\172.16.20.201\lotus_profiles). Đã kiểm chứng end-to-end TRỰC TIẾP trên NAS2 qua
VPN (share đã tạo) — xem thẻ "Kiểm chứng cơ chế" bên dưới.⇒ Còn thiếu để thành production: (1) share đã tạo — còn tách quyền
lotus_worker (rw) / lotus_ro (chỉ đọc) thay cho tài khoản admin;
(2) gọi profile_store.pull trước open_profile và push sau khi
đóng, ngay trong Process Manager của APP_5; (3) chính sách xử lý xung đột khi 2 máy cùng đụng một
profile (xem cột "Đánh giá / rủi ro").
| Port | Dịch vụ | Trạng thái đo được |
|---|---|---|
| 22 | SSH | OPEN |
| 139 / 445 | SMB / CIFS | OPEN — tài liệu mạng cũ chưa ghi 2 cổng này |
| 873 | rsync | OPEN |
| 5000 / 5001 | DSM Web + FileStation API | OPEN — đã gọi thử SYNO.API.Info → success: true |
| 5005 / 5006 | WebDAV / WebDAVS | ĐÓNG — package chưa bật |
| 6690 | Synology Drive | OPEN |
Trên HP1 có sẵn mount.cifs và rsync. Worker là Windows ⇒ SMB là
công dân hạng nhất.
Kênh chính cho đường nóng: tải & upload file .tar.
Seed hàng loạt lần đầu, mirror NAS1 ↔ NAS2, backup định kỳ. Không ở đường nóng —
thuật toán delta vô ích với .tar thay đổi toàn bộ, và Windows không có rsync bản địa.
n8n chạy trong container Linux, không mount SMB → muốn liệt kê/kiểm kê profile thì gọi HTTPS API này là gọn nhất. Cũng là đường dự phòng khi worker bị chặn 445. Chậm hơn, cần session token.
Đang tắt trên NAS2 (5005/5006 đóng). Bật lên phải thêm package + cổng + chứng chỉ, rồi vẫn chậm hơn SMB vì gánh thêm tầng HTTP/nginx cho một tác vụ truyền khối.
Là daemon đồng bộ liên tục hai chiều — chống lại hẳn mô hình "tải theo yêu cầu, xoá sau khi xong", và sẽ đẻ ra conflict-file khi 2 máy đụng cùng profile.
Chỉ để chạy script bảo trì trên NAS (dọn rác, thống kê). Không dùng để truyền profile.
.tar, mỗi lần chạy chỉ là
1 file lớn, đọc/ghi tuần tự — đúng thứ SMB3 làm tốt nhất (large MTU, multichannel, không bị
phạt round-trip như random I/O). Con số 1.45s ở trên chính là đo trên SMB.
.bat bootstrap
đã phải cài 12 gói pip; thêm webdavclient3/paramiko/smbprotocol
là thêm bề mặt lỗi trên mọi máy worker, đổi lấy 0 lợi ích.\\172.16.20.201\<share> bằng IP LAN, không cần
port-forward, không phụ thuộc DDNS động của WP2 (118.69.136.48 là IP động).<id>.tar.tmp rồi rename trong cùng
share
— rename cùng volume là nguyên tử, người đọc không bao giờ thấy file tar dở dang.\\172.16.20.201\lotus_profiles\<id>.tar.
KHÔNG map chữ ổ, KHÔNG cd/pushd vào UNC
— bài học đã ghi ngay trong .bat của APP_5.
.tar. Không bao giờ --user-data-dir trỏ vào share.net use một lần lúc khởi động dịch
vụ;
không nhúng mật khẩu vào code.
\\172.16.20.201\lotus_profiles\
├── profiles/<profile_id>.tar ← kho chính (không nén)
├── incoming/<profile_id>.<host>.tmp ← vùng ghi tạm, rename sang profiles/ khi xong
├── quarantine/<profile_id>.<ts>.tar ← bản lỗi/hỏng, giữ để điều tra
└── _index/manifest.json ← sổ cái (size, sha256, status, audit) cho FileStation API đọc nhanh
Công cụ tham chiếu docs/tools/profile_store.py đã chạy full vòng đời trực tiếp trên
NAS2 — share \\172.16.20.201\lotus_profiles (SMB3, /volume1) — với
profile GPM Chrome thật lấy từ …/03_Trans/03_GPM_Profile/.
lotus_profiles trên NAS2 + dựng cây thư mục thật.
Mount SMB3 từ HP1 (WP1) sang NAS2 (WP2) qua VPN IPSec bằng tài khoản meomay22
(là admin DSM), chạy đủ 8 phép thử, rồi xác minh độc lập qua FileStation API (không qua
mount) thấy đúng profiles/ incoming/ quarantine/ _index/manifest.json + README.txt.
Số throughput dưới đây là cross-VPN WP1→WP2 thật.| Phép thử (chạy thật) | Kết quả đo được | Kết luận |
|---|---|---|
init cây kho trên share |
Tạo profiles/ incoming/ quarantine/ _index/ + manifest rỗng |
ĐẠT |
push 3 profile GPM thật (~250 KB) qua VPN |
pack 0.14–0.15s · upload+rename 0.15–0.20s | ĐẠT |
Upload nguyên tử: sau push, incoming/ có còn .tmp? |
incoming/ rỗng — người đọc không bao giờ thấy tar dở dang |
ĐẠT |
pull + xả nén ra SSD + diff byte-for-byte với nguồn |
GIỐNG HỆT (trừ config.json do tool sinh) |
ĐẠT |
| Profile 50MB cross-VPN (WP1 HP1 → WP2 NAS2, dữ liệu không nén được) | pack 0.44s · upload+rename 4.84s (~10 MB/s) · pull 0.24s · extract 0.09s · round-trip giống hệt | ĐẠT |
AIMD: đẩy --actual-cache-mb 46.5 (≥ 90%×50) |
hạn mức cache tự nâng 50 → 70 MB; audit total_runs +1 |
ĐẠT |
quarantine một profile "hỏng" |
rename sang quarantine/<id>.<ts>.tar; manifest ghi
status=quarantined |
ĐẠT |
Phát hiện hỏng: lật 1 byte trong .tar rồi verify |
SHA256 lệch ⇒ báo lỗi, chặn dùng profile hỏng | ĐẠT |
| Xác minh độc lập qua FileStation API (không qua mount) | thấy đúng cây profiles/ incoming/ quarantine/ _index/ + README.txt |
ĐẠT |
pull + xả nén — đo lại cross-VPN ở đây còn tốt hơn (~0.33s cho 50MB).
4.84s là đường GHI (đóng gói + upload qua VPN, ~10 MB/s), xảy ra sau khi phiên chạy
xong nên không nằm trên đường nóng khởi động — đúng như thiết kế. (Đọc 50MB ở đây trúng cache
RAM của NAS vì vừa ghi; đọc nguội thực địa sẽ chậm hơn — nhưng vẫn là pull 1 file tuần
tự, không phải random I/O.)| Vấn đề | Mức độ | Xử lý |
|---|---|---|
Khoá phiên: 2 máy cùng pull rồi cùng push một profile ⇒
bản ghi sau đè bản trước (mất lịch sử duyệt của bản kia) |
XONG | Đã thêm lease acquire/release/status/steal (_locks/<id>.lock
tạo bằng O_EXCL, có TTL); push tự chối nếu máy khác đang giữ.
Test 10 tiến trình đua → đúng 1 thắng (thẻ dưới). |
| Manifest ghi đè lẫn nhau: read-modify-write không nguyên tử giữa nhiều máy | TRUNG BÌNH | Manifest chỉ là chỉ mục tăng tốc, không phải nguồn sự thật — sự thật là các file
.tar trong profiles/. Có thể rebuild manifest bằng cách quét thư mục.
Nếu cần chính xác thời gian thực ⇒ chuyển sổ cái sang Redis (B27 đã có). |
Phân quyền share: tách read-only / read-write cho lotus_profiles |
XONG | Tạo WP2_GPM_Profile_RW (rw) + WP2_GPM_Profile_RO (ro); verify
mount thật: RW ghi được, RO ghi bị chặn (thẻ dưới). SMB3 bật; SMB1 tắt. |
Nối vào worker: APP_5 gọi pull trước open_profile,
push sau khi đóng |
XONG (gated) | Đã tạo profile_nas_sync.py + nối 2 hook vào _run_profile_action.
Mặc định TẮT (LOTUS_PROFILE_NAS_SYNC=1 để bật), nuốt mọi lỗi. Còn 1 việc:
xác nhận LOTUS_GPM_PROFILE_DIR trên worker thật (thẻ dưới). |
Dọn rác SSD khi worker crash giữa chừng (thư mục temp_profiles còn sót) |
THẤP | Quét dọn lúc khởi động dịch vụ; profile chưa push thì coi như mất phiên đó
(kho vẫn còn bản cũ nhất quán). |
| Seed 10.000 profile lần đầu lên NAS2 | THẤP | Dùng rsync (873 mở) một lần — không đi đường nóng. push chỉ cho
cập nhật lẻ về sau. |
Lệnh đã chạy để kiểm chứng nằm trong docs/tools/profile_store.py (mục
docstring có ví dụ đầy đủ). Chi tiết văn bản: docs/great-lotus-e2e-architecture.md §7.5.
| Tài khoản | Quyền đặt | Đo khi mount |
|---|---|---|
WP2_GPM_Profile_RW | read-write | đọc OK, ghi OK ĐẠT |
WP2_GPM_Profile_RO | read-only | đọc OK, ghi bị chặn (Permission denied) ĐẠT |
Worker mount bằng RW; n8n/kiểm kê (container Linux) dùng RO. Credential ở tầng OS
(net use … /user:WP2_GPM_Profile_RW / credentials= file), không nhúng code.
| Phép thử | Kết quả | |
|---|---|---|
A acquire → B acquire cùng profile |
B thua (exit 3), thấy A đang giữ | ĐẠT |
B push khi A giữ lease |
TỪ CHỐI push (chống đè bản của A) | ĐẠT |
10 tiến trình đua acquire cùng 1 profile |
đúng 1 thắng / 9 thua — O_EXCL trên CIFS nguyên tử |
ĐẠT |
| Lease hết hạn (TTL 1s) → máy khác | giành lại được (steal tự động) | ĐẠT |
release lock của máy khác |
bị chặn (chỉ chủ mới nhả) | ĐẠT |
Vòng đời đầy đủ: A pull --lease → B bị chặn → A push (tự nhả) → B vào được |
đúng thứ tự, total_runs tăng dần | ĐẠT |
profile_nas_sync.py + 2 hook, mặc định TẮT_run_profile_action gọi nas_sync_before_open (giành lease +
pull --lease) trước khi mở, và nas_sync_after_close (push +
nhả lease) sau khi đóng. Import có guard + nuốt mọi lỗi ⇒ NAS treo/mount rớt
không bao giờ làm hỏng thao tác browser.
| Phép thử (sync BẬT) | Kết quả | |
|---|---|---|
| Sync TẮT (mặc định) | hook no-op 0.000s, không log | ĐẠT |
before_open profile mới (chưa có trên NAS) |
giành lease, báo "mới", không lỗi | ĐẠT |
after_close → push + nhả lease |
tar lên NAS, _locks/ trống | ĐẠT |
before_open lần 2 (đã xoá bản cục bộ) |
pull kéo lại, khôi phục Cookies đúng byte | ĐẠT |
WORKER2 before_open khi WORKER1 giữ lease |
degrade êm — cảnh báo, KHÔNG crash, mở bản cục bộ | ĐẠT |
LOTUS_GPM_PROFILE_DIR (+ LOTUS_GPM_PROFILE_GLOB) — thư mục GPM Login giữ
profile cục bộ (mẫu quan sát: <id>-<ngày>/Default/…) là tuỳ chọn của app GPM
trên từng máy, không suy ra được từ xa. Đặt đúng rồi mới bật LOTUS_PROFILE_NAS_SYNC=1.
Cấu hình đầy đủ nằm trong docstring của profile_nas_sync.py.Lease luôn có TTL (env LOTUS_PROFILE_LEASE_TTL), và trong khi profile MỞ worker
tự động gia hạn (heartbeat) mỗi TTL/3 giây. Đã test trên NAS2 (TTL 15s, renew 10s):
| Kịch bản | Điều gì xảy ra | |
|---|---|---|
| Profile đang chạy (phiên dài hơn TTL) | luồng nền lease renew mỗi TTL/3 → lease luôn tươi, không hết hạn giữa
chừng (đo: không lần nào HẾT HẠN) | ĐẠT |
Workflow lỗi (không close_profile) |
không push (NAS giữ bản tốt gần nhất); heartbeat chết theo | — |
| Worker crash → heartbeat dừng | lease hết hạn trong ≤ 1 TTL (đặt ngắn, VD 300s) → máy khác giành lại nhanh | ĐẠT |
| Chính máy đó chạy lại | đòi lại lease của chính nó NGAY (same-host reclaim), không chờ | ĐẠT |
| Máy khác cần profile khi TTL còn | bị chặn (đúng — máy kia có thể vẫn chạy); sau TTL tự giành lại | ĐẠT |
| Đọc dữ liệu khi có lock | pull thường (không --lease) luôn đọc được bản mới nhất —
chỉ ý-định-GHI mới bị khoá | ĐẠT |
| Mở khoá thủ công | lease steal <id> (xem ai giữ: lease status) |
ĐẠT |
LOTUS_PROFILE_LEASE_RENEW_SEC (mặc định TTL/3).profile_nas_sync.py và file worker nằm dưới
Library/* & Application/* — bị .gitignore theo đúng
quy ước repo (toàn bộ APP_5/Library không track). Đã có trên HP1; tới worker qua cùng đường đồng bộ
như phần còn lại của code.Mặc định TẮT — worker chạy y như trước. Bật khi đã sẵn sàng, chỉ trên máy worker (Windows).
Cấu hình đặt ngay trong file .bat khởi động (dùng setlocal nên python và
các lệnh profile_store.py con tự kế thừa):
App\2_GPM_Multi_Remote_Browser_Process_Manager_GUI\2_GPM_multi_remote_browser_process_manager_gui.bat
Bước 1 — Lưu credential một lần (KHÔNG gõ mật khẩu vào file). Chạy trong CMD trên worker:
cmdkey /add:172.16.20.201 /user:WP2_GPM_Profile_RW /pass
rem (Windows hỏi mật khẩu → gõ pass của WP2_GPM_Profile_RW. Từ đó truy cập \\172.16.20.201\... không cần đăng nhập lại)
Bước 2 — Điền thư mục GPM (dòng đã có sẵn trong .bat). Tìm đường dẫn trong
app GPM Login → Cài đặt → Đường dẫn lưu trữ (nơi có các folder <id> hoặc
<id>-<ngày>):
set "LOTUS_GPM_PROFILE_DIR=C:\...\GPM_Profiles" <-- ĐIỀN đúng thư mục này
set "LOTUS_GPM_PROFILE_GLOB={id}" <-- đổi thành {id}-* nếu tên folder có hậu tố ngày
Bước 3 — Bật cờ rồi chạy lại .bat:
set "LOTUS_PROFILE_NAS_SYNC=1"
| Biến | Ý nghĩa | Mặc định / gợi ý |
|---|---|---|
LOTUS_PROFILE_NAS_SYNC | Bật/tắt toàn bộ sync | 0 (tắt) → đổi 1 để bật |
LOTUS_PROFILE_STORE | Đường dẫn kho (UNC hoặc mount) | \\172.16.20.201\lotus_profiles |
LOTUS_GPM_PROFILE_DIR |
Thư mục GPM giữ profile cục bộ — thứ DUY NHẤT phải xác nhận trên máy | (trống → phải điền) |
LOTUS_GPM_PROFILE_GLOB | Mẫu tên folder con của profile | {id} · dùng {id}-* nếu có hậu tố ngày |
LOTUS_PROFILE_HOST | Tên máy (cho lease/audit) | %COMPUTERNAME% |
LOTUS_PROFILE_LEASE_TTL | Thời hạn khoá phiên (giây) | 180–300 — heartbeat tự gia hạn nên để ngắn (phục hồi crash nhanh) |
LOTUS_PROFILE_LEASE_RENEW_SEC | Nhịp tự gia hạn (giây) | (trống) = TTL/3 |
WP2_GPM_Profile_RW cho worker
(đọc-ghi); WP2_GPM_Profile_RO chỉ dành cho n8n/kiểm kê (chỉ đọc). Mật khẩu lưu bằng
cmdkey/Credential Manager, không nhúng vào .bat.open_profile → close_profile cho một profile, rồi nhìn
\\172.16.20.201\lotus_profiles\profiles\ sẽ thấy <id>.tar. Hoặc push tay:
py -3 <repo>\docs\tools\profile_store.py --store \\172.16.20.201\lotus_profiles push <id> "<dir_profile>".
Xem log worker có dòng [profile_nas_sync].Container lotus_profile_dashboard (IP 114.114.114.36) — một dashboard
CHỈ ĐỌC giống ScanIpProxy, đọc thẳng kho lotus_profiles trên NAS2 và hiển thị
theo WP1 / WP2 / WP3: số lượng, dung lượng, profile đang chạy ở máy nào, profile lỗi + lý do,
số lần chạy, thời lượng, chạy gần nhất, hạn mức cache.
| Mục | Chi tiết |
|---|---|
| Truy cập | http://<host>:20360 · công khai
https://profiles.lotus1104.synology.me (qua Caddy) · /help
(hướng dẫn + xử lý sự cố) · /docs (Swagger API) |
| Nguồn dữ liệu | _index/manifest.json + _index/runs.jsonl
(sổ lịch sử) + _locks/*.lock (đang chạy) + quarantine/*.tar (lỗi)
— KHÔNG mở tar (nhanh) |
| Truy cập NAS2 | Docker CIFS volume nas2_profiles_ro mount
\\172.16.20.201\lotus_profiles bằng WP2_GPM_Profile_RO (chỉ đọc)
— daemon Docker tự mount, không cần container privileged |
| Kỹ thuật | FastAPI + SPA vanilla JS dark-glass (không CDN), tự làm mới mỗi 4s, lọc theo WP/trạng thái, tìm kiếm, sáng/tối |
| Trạng thái suy ra | Đang chạy (lock chưa hết hạn) · Sẵn sàng · Cách ly (lỗi) · Khoá treo (lease hết hạn) · Thiếu file |
| Tương tác | Tiles bấm để lọc theo trạng thái · bấm 1 dòng → ngăn chi tiết kèm hướng dẫn xử lý + lệnh copy-được (steal lease / push / verify) đúng cho từng trạng thái |
| Thống kê | Biểu đồ cột runs theo ngày / tuần / tháng (xếp chồng theo WP) +
top profile dùng nhiều nhất + phân bổ theo WP + máy chạy nhiều nhất — đọc từ
runs.jsonl |
| API (chỉ đọc) | /api/health snapshot summary profiles profiles/{id} locks
quarantine stats runs — OpenAPI ở /docs, /redoc,
/openapi.json |
Cache = thực/hạn mức | Cache HTTP Chrome đo lần chạy gần nhất /
trần AIMD mà profile_store.py tự chỉnh. Cache là phần con của profile
⇒ thực tế Cache ≤ cỡ .tar (số demo chỉ minh hoạ) |
profile_store.py trên máy worker, dashboard chỉ
gợi ý đúng lệnh. work_pool + audit lấy từ manifest do push bơm vào;
runs.jsonl cũng do push ghi (chỉ-thêm).# build + chạy (trên HP1)
cd .../DCP_PRODUCTION
docker compose up -d --build lotus_profile_dashboard
# đổi code Python -> restart để uvicorn nạp lại; static bind-mount nên sửa là thấy ngay
docker compose restart lotus_profile_dashboard
DCOM USB-4G (ZTE/OLAX) ──USB──► DELL_Proxy 172.16.20.202 ──admin :6868──► RapidProxyClient
(nhà mạng cấp IP mobile) (trang trại proxy HTTP/Socks5) (Docker trên Pi1
nội bộ 10.10.10.x / 10.10.11.x) 172.16.20.203)
│ MQTT
▼
RapidProxyHost ──► Redis B27
│
ScanIpProxy (B30, quét mỗi 1s) ◄─────────┘
└─ MQTT topic SEND_TO_RAPID_PROXY_MAN
({type: changeIpRequest | rebootDcomRequest})
LOTUS:Đã liệt kê trực tiếp trên container lotus_redis (B27, host 20270).
| Key | Kiểu | Ai ghi | Nội dung |
|---|---|---|---|
LOTUS:03_PROXY:01_INFO_JSON_MAN |
ReJSON | RapidProxyHost | Toàn bộ DCOM theo Workpool > ProxyName: ISP, localIPv4Address,
publicIPv4Address, httpPort, socks5Port,
signal, proxyStatus, lastReset
|
LOTUS:03_PROXY:02_CLIENT_ALIVE_JSON_MAN |
ReJSON | RapidProxyHost | Heartbeat từng Raspberry Pi client |
LOTUS:03_PROXY:03_NEW_IP |
LIST (mỗi phần tử 1 chuỗi JSON) | ScanIpProxy | IP mới, hợp lệ, sẵn sàng dùng |
LOTUS:03_PROXY:04_USING_IP |
LIST | n8n / worker | Đang dùng; có mốc enteredUsingAt |
LOTUS:03_PROXY:05_DONE_IP |
LIST | n8n / worker | Xong việc, chờ xoay IP; có enteredDoneAt |
LOTUS:03_PROXY:CONFIG:USING_TIMEOUT_SEC |
String | Dashboard | Hiện 3600 |
LOTUS:03_PROXY:CONFIG:AUTO_MOVE |
String (JSON) | Dashboard | Hiện
{"new_to_using":true,"using_to_done":true,"done_to_new":false,"using_to_done_delay":0}
|
LOTUS:03_PROXY:HOST_HEALTH |
String (JSON) | RapidProxyHost | uptime, mqtt user/dev side, số client |
LOTUS:01_MAN:* · LOTUS:02_QUEUE:* |
ReJSON / LIST | hệ NewPyWorker cũ | Hệ thống KHÁC (schedule/profile/node/queue của luồng Docker cũ). Đừng nhầm với IP pool. |
03/04/05{ "workpool": "WP2",
"dcomEntry": { "id":"eth1", "name":"eth1", "isp":"VIETNAMMOBILE",
"publicIPv4Address":"103.249.23.109", "localIPv4Address":"192.168.164.100",
"httpPort":2001, "socks5Port":4001, "proxyStatus":"Running", … },
"enteredNewAt": 1785868525.12 } // hoặc enteredUsingAt / enteredDoneAt
Mật khẩu Redis nằm ở REDIS_ARGS --requirepass trong docker-compose.yml
— đừng chép ra tài liệu/chat.
03_NEW_IP — ScanIpProxy thấy DCOM có publicIPv4Address mới & hợp lệ →
RPUSH.04_USING_IP — (n8n/worker phải tự làm) lấy IP ra, đặt
enteredUsingAt.
USING_TIMEOUT_SEC (3600s): ScanIpProxy ghi audit
ip_timeout, đẩy sang 05_DONE_IP, publish rebootDcomRequest,
POST webhook cảnh báo về n8n → Webex.
05_DONE_IP — xong việc. ScanIpProxy publish changeIpRequest để Client
xoay IP rồi xoá khỏi hàng đợi. IP mới xuất hiện lại ở 03_NEW_IP.RESET_TIMEOUT_SEC) — publish rebootDcomRequest
(reset cổng USB) — self-healing.Ngoài Redis còn 2 SQLite trong WebDashboard/data/:
audit.db (sự kiện 90 ngày, xếp hạng DCOM hay reboot → tìm cổng USB hỏng) và
ip_stats.db (tỉ lệ IP trùng, kích thước pool thật của nhà mạng).
Base: http://172.16.10.220:20300 hoặc
https://scanipproxy.lotus1104.synology.me/
| Method | Đường dẫn | Việc |
|---|---|---|
| GET | /api/redis/new-ip · /using-ip · /done-ip · /all
|
đọc từng hàng đợi |
| POST | /api/redis/move-ip |
body {from_key, to_key, public_ip} — chuyển trạng thái 1 IP |
| GET/POST | /api/redis/config/using-timeout |
đọc/đặt timeout |
| GET/POST | /api/redis/config/auto-move |
đọc/đặt AUTO MOVE |
| DELETE | /api/redis/clear/{alias} |
xoá sạch một key |
| WS | /ws |
đẩy full_update mỗi 2s cho dashboard |
AUTO_MOVE đang BẬT — nó tự tiêu thụ IP, worker không giành
nổi.WebDashboardServer.py chạy một vòng lặp nền mỗi ~1s: nếu new_to_using=true thì
tự chuyển phần tử đầu của 03_NEW_IP sang 04_USING_IP;
using_to_done=true thì tự đẩy tiếp sang 05_DONE_IP.
Hiện cả hai đều true ⇒ hàng đợi bị "chảy" tự động ~1.8s một nhịp,
độc lập hoàn toàn với worker thật. Đây là chế độ mô phỏng/demo.new_to_using=false, using_to_done=false
(POST /api/redis/config/auto-move), để việc claim/release do workflow quyết định.
move_ip_between_states() KHÔNG nguyên tử.read_list_key() → lọc trong Python → pipe.delete(from_key) +
rpush(remaining).
Đây là read-modify-write: hai người claim đồng thời có thể nhận cùng một IP, và một lần ghi
của ScanIpProxy xen vào giữa sẽ bị xoá mất.LMOVE/LPOP nguyên tử hoặc Lua script,
hoặc bọc bằng một khoá phân tán (SET NX EX) trước khi gọi API.
01_INFO: WP2 / DELL_PROXY_01 / DCOM eth1 (ZTE, VIETNAMMOBILE,
LTE, signal 5, proxyStatus: Running) nhưng publicIPv4Address đang là chuỗi
rỗng,
lastReset cập nhật liên tục ⇒ DCOM đang xoay IP / chưa bắt được mạng.
03_NEW_IP, 04_USING_IP, 05_DONE_IP đều rỗng.
Redis xoá key khi list rỗng — key không xuất hiện trong KEYS * là bình thường,
không phải mất dữ liệu.02_ALIVE: Pi1 (WP2_Rasp_Pi3_b827eb729e41) aliveStatus: true
⇒ đường Client → Host → Redis đang sống.172.16.20.202:6868 — truy cập được từ mạng khác không?| Đường thử | Kết quả |
|---|---|
Từ HP1 (WP1) qua VPN IPSec → 172.16.20.202:6868 |
❌ không ping được, cổng đóng/bị lọc |
Qua port-forward WAN → lotus2020.synology.me:20251 |
❌ không kết nối được |
| Từ Pi1 (cùng LAN WP2) | ✅ đang hoạt động — Redis vẫn nhận dữ liệu DCOM từng giây |
172.16.0.0/16, hoặc
(2) giữ nguyên và luôn đi qua API của B30 (/api/mqtt/*) — đã có sẵn đường MQTT
SEND_TO_RAPID_PROXY_MAN để ra lệnh changeIpRequest / rebootDcomRequest.
Khuyến nghị (2) — an toàn hơn và không phải mở thêm bề mặt tấn công.
Bản mô tả gốc có 6 bước và ghi "có thể chưa hoàn thiện". Dưới đây là bản đã bổ sung các bước thiếu.
n8n workflow Worker (NUC1) Hạ tầng
───────────── ────────────── ──────────
(0) lease profile ─────────────────────────────────────► Redis B27 SET NX EX [THIẾU]
(1) claim IP ──────────────────────────────────────────► Redis 03_NEW → 04_USING [THIẾU]
(2) kiểm tra sức khoẻ proxy ───────────────────────────► CONNECT thử qua proxy [THIẾU]
(3) tải + xả .tar ─── MQTT ──► worker ─── SMB ─────────► NAS2 \\…\profiles\<id>.tar [ĐÃ NỐI (C12): node pull_profile → profile_store.py pull]
(4) update_profile(raw_proxy) ─ MQTT ──► GPM API [CÓ]
(5) open_profile ────────────── MQTT ──► GPM API ──► Chrome + cổng CDP [CÓ]
(6) xác minh IP ra ngoài ───── MQTT ──► browser.url ip-echo → so IP đã claim [THIẾU]
(7) chạy nghiệp vụ ─────────── MQTT ──► C10 Playwright / C12 PyAutoGUI [CÓ]
(8) quit(remote_port) ──────── MQTT ──► đóng đúng 1 session [CÓ]
(9) close_profile ──────────── MQTT ──► GPM flush profile ra đĩa [CÓ]
(10) đóng .tar + upload nguyên tử ── SMB ──► NAS2 incoming/ → rename → profiles/ [ĐÃ NỐI (C12): node push_profile → profile_store.py push]
(11) release IP 04 → 05 ───────────────────────────────► Redis [THIẾU]
(12) release lease + xoá thư mục tạm local [THIẾU]
--user-data-dir bằng SingletonLock, và tệ hơn là 2 máy cùng upload
.tar sẽ ghi đè lẫn nhau ⇒ mất cookie. Dùng Redis B27 (đã có sẵn, đừng dựng hạ tầng mới):
SET LOTUS:04_PROFILE:LEASE:<profile_id> "<wp>/<pc>/<run_id>" NX EX 5400.
Không lấy được ⇒ chọn profile khác hoặc dừng. TTL bắt buộc để worker chết không khoá vĩnh viễn.
03_NEW_IP, chọn 1 phần tử, chuyển sang 04_USING_IP.
Từ dcomEntry dựng <proxyDevIp>:<httpPort> (HTTP) hoặc
:<socks5Port> (Socks5), kèm user/pass nếu cần → định dạng GPM
host:port:user:pass.
⚠ Trước khi làm phải tắt AUTO_MOVE và dùng đường nguyên tử (xem tab IP Pool).
proxyStatus: "Running" nhưng
publicIPv4Address rỗng. Mở browser với proxy chết = mất 30–60s timeout rồi hỏng cả run.
Kiểm tra rẻ tiền: TCP connect tới host:port + 1 request qua proxy tới trang echo IP.
Hỏng ⇒ trả IP về 05_DONE_IP (kích hoạt xoay IP) rồi claim IP khác.
manager.pull_profile (nhóm Manager, trên cùng)
→ worker chạy profile_store.py pull (giành lease + heartbeat), xả nén ra
LOTUS_GPM_PROFILE_DIR\<id>\, đọc config.json lấy
current_cache_limit_mb.
Profile chưa tồn tại trên NAS ⇒ nhánh "tạo mới": manager.create_profile rồi coi như profile
trắng. C10 sẽ nhân bản node này sau. Điều kiện worker: có profile_store.py + đã mount kho NAS2 (RW).
manager.update_profile(profile_id, raw_proxy=…).
🔴 Bắt buộc là bước riêng. open_profile (= GET /profiles/start/<id>)
chỉ nhận addination_args/remote_port/win_* — không có tham số proxy. Profile vẫn chạy
được với proxy, nhưng là proxy đã lưu sẵn trên nó (đặt bằng
create_profile/update_profile). Muốn đổi proxy MỚI (lấy IP từ pool) ⇒
update_profile TRƯỚC rồi mới open.
manager.open_profile(profile_id, addination_args=…).
✅ AUTO-ARGS (C12, 2026-08-09): bật load_args_from_config (mặc định) ⇒ worker tự đọc
config.json (do bước 3 tải về) rồi nhét
--disk-cache-size=<current_cache_limit_mb×1MB>, --media-cache-size=1,
--disable-pdf-viewer + additional_args vào addination_args (không đè cờ gõ tay).
Node C10 rút cổng CDP từ response và gắn vào output remote_debugging_port +
remote_port ⇒ mọi node sau tự nối đúng session.
user-data-dir). Điều hướng tới một trang echo IP và so với IP đã claim.
Lệch ⇒ hủy run ngay — đừng chạy nghiệp vụ với IP sai, đó chính là thứ làm cháy tài khoản.manager.quit với quit_all = false + đúng
remote_port.
🔴 Bật Quit All sẽ giết luôn browser của các nhánh song song.
manager.close_profile(profile_id) — để GPM/Chrome flush hết
cookie/SQLite
ra đĩa. Đóng gói khi Chrome còn sống sẽ ra bản .tar thiếu dữ liệu (nằm trong
-wal).
Mẹo đã kiểm chứng ở A13: nếu buộc phải chụp khi Chrome còn chạy, dùng SQLite backup API
cho mọi file có header SQLite format 3 rồi xoá -wal/-shm
— đo được 501 cookie so với 500 khi chỉ copy thô.
manager.push_profile (nhóm Manager, trên cùng)
→ worker chạy profile_store.py push: đo cache thật → tính AIMD → ghi config.json
→ tar không nén → ghi ra incoming/<id>.<host>.tmp → rename sang
profiles/<id>.tar (nguyên tử cùng share) → nhả lease. Chạy sau
close_profile. C10 sẽ nhân bản node này sau.
04_USING_IP → 05_DONE_IP. ScanIpProxy sẽ tự phát
changeIpRequest và xoá khỏi hàng đợi.
DEL lease key; rmtree C:\temp_profiles\<id>.
Không dọn = ổ SSD đầy sau vài trăm run.| Hỏng ở đâu | Cơ chế cứu | TT |
|---|---|---|
| Workflow chết giữa chừng, IP kẹt ở USING | ScanIpProxy: USING_TIMEOUT_SEC=3600 → tự sang DONE + rebootDcomRequest +
webhook |
CÓ |
| Worker chết, profile bị khoá | Redis TTL lease EX 5400 tự hết hạn |
THIẾU |
| Thư mục tạm rác trên SSD | janitor quét C:\temp_profiles, xoá thư mục cũ và không có lease sống |
THIẾU |
.tar upload dở |
quy ước: file *.tmp không bao giờ được đọc; janitor xoá .tmp cũ |
THIẾU |
| Profile hỏng (Chrome không mở nổi) | chuyển sang quarantine/<id>.<ts>.tar, tạo profile trắng, ghi audit |
THIẾU |
| Máy worker mất kết nối | B34 Fleet Monitor: >10s không heartbeat = offline; có
kill_workers/restart_service/restart_pc
|
CÓ |
| Bước | Đặt ở đâu | Vì sao |
|---|---|---|
| 0, 1, 11, 12-lease | n8n | Là quyết định điều phối. n8n nói HTTP tới B30 và Redis dễ; giữ worker "ngu" và không trạng thái. |
| 2 | worker (khuyến nghị) | Kiểm tra từ worker mới phản ánh đúng đường mạng thật của worker. |
| 3, 10, 12-tmp | worker | Chỉ worker mới thấy ổ local. Nên thêm action Manager mới cho C10/C12 (VD
manager.fetch_profile_from_nas / manager.publish_profile_to_nas) thay vì để
n8n điều khiển từng lệnh copy qua MQTT.
|
| 4, 5, 7, 8, 9 | worker | Đã có sẵn. |
| 6 | worker | Dùng browser.url + developer.get_full_code là đủ. |
| Mô tả ban đầu | Thực tế đã xác minh | Việc cần làm |
|---|---|---|
| "Profile load từ NAS về, giải nén, đọc config" | Không có bất kỳ code NAS/SMB nào trong APP_5/A12/A13/B05. Chỉ có bản thiết kế HTML. | Viết 2 action Manager mới + module SMB |
| "Profile chạy với proxy từ IP Pool" | Không có code đọc 03_NEW_IP. open_profile không nhận
raw_proxy
|
Thêm bước update_profile + logic claim IP |
| "Chuyển IP 03 → 04 khi mở browser" | AUTO_MOVE đang tự làm việc này ~1.8s/nhịp, độc lập worker; API move không nguyên
tử |
Tắt AUTO_MOVE + claim nguyên tử |
| "Xong việc chuyển 04 → 05, nén & upload" | Không có code | Viết bước 10–11 |
| "NUC1 gửi gói thông tin đến B34" | ✅ Đúng và đang chạy — HTTP heartbeat 2s, đang thấy WP3/PC_1 online |
— |
| "n8n điều khiển qua port control bằng Playwright hoặc PyAutoGUI" | ✅ Đúng — cơ chế remote_port chảy xuôi node, C10/C12 nối chéo được |
— |
| Broker MQTT | Đang dùng VPS ngoài 128.199.200.17, không phải B06 nội bộ |
Cân nhắc chuyển về B06 |
172.16.20.202:6868 từ mạng khác |
❌ Không (firewall trên DELL_Proxy). Chỉ Pi1 cùng LAN gọi được | Đi qua API B30 |
displayOptions VẪN gửi giá trị lên.
Mọi quan hệ "switch điều khiển ô khác" phải được cưỡng chế lại phía Python, không tin vào việc
ô đã bị ẩn. Đã dính 2 lần: use_local_test_profile và quit_all.Quit All giết cả nhánh khác.
Trong workflow đa session, một node dọn dẹp cuối nhánh bật Quit All sẽ đóng luôn browser
của nhánh song song. Dùng quit + remote_port chính xác.%TEMP%\great_lotus_local_test_profiles_in_C10_GPM\chromium\default%TEMP%\great_lotus_local_test_profiles_in_C12_pyAutoGUI\chromium\defaultuser-data-dir mà chạy đồng thời thì Chrome xung
đột.
--user-data-dir là thư mục User Data gốc. Nếu sync từ
…/User Data/Default thì cookie nằm sai tầng và Local State (chứa khoá giải mã
cookie)
không được chép theo ⇒ bản clone bị đăng xuất.
Luôn sync từ …/Chrome/User Data; bỏ trống ô Master Profile Path là an toàn nhất.
Library/* và Application/* đều bị gitignore.
Sửa A12_PlayWright/**, A13_PyAutoGUI_Web/** hay _core/*.py xong thì
phải chép tay sang máy worker rồi restart Process Manager GUI. Không có git nào mang giúp.
Đối chiếu bằng certutil -hashfile <file> MD5.
pygetwindow,
ctypes.windll). Trên Linux chúng trả None và code phải suy giảm êm —
khi test trên host Linux phải mô phỏng, đừng kết luận "không chạy được".
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
ping -c1 172.16.30.101 # NUC1 (WP3)
ping -c1 172.16.20.201 # NAS2 (WP2)
curl -s http://127.0.0.1:20340/api/state | python3 -m json.tool | head -40
curl -s http://127.0.0.1:20300/api/redis/all
curl -s http://127.0.0.1:20300/api/redis/new-ip
curl -s http://127.0.0.1:20300/api/redis/config/auto-move
COMPOSE=Application/APP_1_GREAT_LOTUS_PROJECT/App/A0_DCP_GREATE_LOTUS_NETWORK/DCP_PRODUCTION/docker-compose.yml
export REDISCLI_AUTH=$(grep -m1 -oP '(?<=--requirepass )\S+' "$COMPOSE")
docker exec -e REDISCLI_AUTH lotus_redis redis-cli --no-auth-warning KEYS 'LOTUS:03_PROXY:*'
.envKEY=$(grep -m1 '^N8N_API_KEY=' …/DCP_PRODUCTION/.env | cut -d= -f2-)
curl -s -H "X-N8N-API-KEY: $KEY" http://127.0.0.1:20150/api/v1/workflows?limit=100
for p in 22 139 445 873 5000 5001 5005 6690; do
timeout 3 bash -c "echo > /dev/tcp/172.16.20.201/$p" 2>/dev/null \
&& echo "$p OPEN" || echo "$p closed"; done
lotus_novnc_chrome): điều khiển bằng Selenium ở
http://127.0.0.1:20351, người dùng xem trực tiếp ở
http://172.16.10.220:20350/?autoconnect=1&resize=scale.
| Việc | Đường dẫn |
|---|---|
| Docker Compose production |
Application/APP_1_GREAT_LOTUS_PROJECT/App/A0_DCP_GREATE_LOTUS_NETWORK/DCP_PRODUCTION/docker-compose.yml
|
| Caddy (map domain → container) | …/DCP_PRODUCTION/B16_Caddy/Caddyfile |
| Fleet Monitor server (bản chạy) | …/DCP_PRODUCTION/B34_GPM_Fleet_Monitor_System/App/gpm_fleet_monitor_server.py |
| ScanIpProxy (IP pool daemon) | …/DCP_PRODUCTION/B30_Python_Proxy_Script/APP_B02_RapidProxyScript/App/ScanIpProxy.py
|
| Web Dashboard IP pool | …/APP_B02_RapidProxyScript/WebDashboard/ |
| PRD + Architecture IP pool | …/APP_B02_RapidProxyScript/_bamd-ouptut/PRD.md, Doc/architecture.md |
| Báo cáo thiết kế profile NAS |
…/DCP_PRODUCTION/B06_MQTT_Broker/www/share/02_Link/UID_00001_profile_optimization_report.html
|
| Worker Process Manager | Application/APP_5_GPM_GREAT_LOTUS/App/2_GPM_Multi_Remote_Browser_Process_Manager_GUI/
|
| Node C10 (Playwright) | Library/C10_N8N_MQTT_PlayWright/ — 3 file Agent_*.md |
| Node C12 (PyAutoGUI) | Library/C12_N8N_MQTT_PyAutoGUI_Web/ — 4 file Agent_*.md |
| Node C11 (chỉ GPM) | Library/C11_N8N_GPM_Login_Man/ |
| Client Playwright / PyAutoGUI | Library/A12_PlayWright/, Library/A13_PyAutoGUI_Web/ |
| Wrapper GPM API | Library/B05_GPM_Login_Manager/libWrap.py |
| Tài liệu mạng | docs/network/network-overview.md (+ -WP1/-WP2/-WP3.md,
index.html)
|
| Bản Markdown của trang này | docs/great-lotus-e2e-architecture.md |
| Chính file HTML này | docs/html_doc/HDID_00001_Great_Lotus_E2E_Architecture.html — bind-mount thẳng vào web
server, không có bản thứ hai |
Thứ tự ưu tiên nguồn sự thật: thiết bị/API thật > mã nguồn > file cấu hình trong git > sơ đồ/báo cáo HTML.
File config trong repo có thể lệch với máy worker thật (đã gặp: pc_name).
Trang HTML này và bản Markdown docs/great-lotus-e2e-architecture.md
phải được cập nhật cùng nhau.
docs/html_doc/ trong repo
được bind-mount thẳng vào web server (lotus_mqtt, http_dir /mosquitto/www)
tại /mosquitto/www/share/02_Link/FID_00005_HTML_DOC ⇒ sửa file trong Git là web đổi ngay,
không cần publish. Thêm tài liệu mới: đặt file UID_<5 số>_<Ten>.html vào thư mục đó
rồi chạy python3 docs/tools/gen_html_doc_index.py.