GREAT LOTUS · NIRVANA FRAMEWORK · HỆ ĐỒNG BỘ PROFILE CHROME

B36 PROFILE DASHBOARD

GIÁO TRÌNH SỬ DỤNG, KHỞI TẠO & VẬN HÀNH

Lotus Profile Fleet — kho profile Chrome trên NAS2 theo WP1 / WP2 / WP3
Khởi tạo từ đầu · Kết nối worker và n8n · Mổ xẻ từng thành phần giao diện · Dọn dẹp an toàn · Xử lý sự cố

Trang https://profiles.lotus1104.synology.me/ chụp ngày 18/09/2026 qua Chrome thật (B35).

Hạng mục

Nội dung

Đối tượng đọc

Người vận hành hệ Great Lotus, kỹ sư triển khai worker Windows, người viết workflow n8n cần pull/push profile, và học viên đào tạo nội bộ

Phiên bản

Bản 2 — biên soạn và cập nhật ngày 18/09/2026, đối chiếu trực tiếp mã nguồn và hệ thống đang chạy trên HP1. Bản 2 ghi nhận các lỗi đã sửa cùng ngày (Phụ lục F)

Đối tượng mô tả

Container lotus_profile_dashboard (B36) · IP 114.114.114.36 · cổng máy chủ 20360 · công khai tại https://profiles.lotus1104.synology.me/; kèm container trợ lực lotus_profile_broker (B37)

Phiên bản phần mềm

API FastAPI version="1.1.0" (app/server.py) · giao diện app.js?v=1.8.1 · image lotus/profile_dashboard:1.0 · B37 lotus/profile_broker:0.1

Nguồn sự thật

B36_Profile_Dashboard/app/*, static/*, B37_Profile_Broker/, docs/tools/profile_store.py, docker-compose.yml, B16_Caddy/Caddyfile và ảnh chụp hệ thống sống qua trình duyệt điều khiển B35

Nơi lưu tài liệu

DCP_PRODUCTION/B36_Profile_Dashboard/Giao_Trinh/ — cùng bố cục với giáo trình thư viện C12 (.docx + .pdf + html/)



GHI CHÚ · CÁCH ĐỌC GIÁO TRÌNH NÀY

Mỗi màn hình được chụp từ hệ thống ĐANG CHẠY và đánh số bằng vòng tròn chỉ tô viền, đặt bên ngoài phần tử để không che chi tiết. Ngay sau mỗi hình là một bảng giải thích từng thành phần mang số trên hình đó.

▸ Hộp NGUY HIỂM màu đỏ = thao tác làm mất dữ liệu hoặc mở đường cho mất dữ liệu.

▸ Hộp ĐÚNG THIẾT KẾ màu xanh lá = hành vi trông như lỗi nhưng là chủ ý.

▸ Hình ghi (mô phỏng) là trạng thái hiện KHÔNG có trên kho thật; được dựng lại chỉ trong trình duyệt bằng cách bọc phản hồi /api/snapshot — không một byte nào ghi xuống NAS, B36 hay B37. Profile mô phỏng mang số UID_9000xx để không lẫn với số thật.





Lời nói đầu

Great Lotus chạy hàng loạt profile Chrome trên các máy worker Windows ở ba workpool. Mỗi profile là một kho trạng thái đắt giá — cookie đăng nhập, lịch sử, cấu hình — nên nó được cất tập trung trên NAS2 dưới dạng gói .tar, và mỗi lần chạy worker lại kéo về, chạy xong đẩy lên. B36 Profile Dashboard là tấm gương soi toàn bộ kho đó: có bao nhiêu profile, cái nào đang chạy trên máy nào, cái nào lỗi, cái nào bị khoá treo, và thống kê sử dụng theo ngày/tuần/tháng. Từ phiên bản có broker B37, nó còn là bảng điều khiển: khôi phục profile về worker, gỡ khoá treo, đóng Chrome bị treo, cách ly, xoá hẳn và dọn dẹp.

Cuốn sách trả lời ba câu hỏi theo đúng thứ tự người mới gặp phải: dựng nó từ đầu thế nào (Phần III), nối nó với worker và n8n thế nào (chương 7–8), và đọc từng nút trên giao diện ra sao (Phần IV). Các phần sau là vận hành, an toàn, sự cố và bài thực hành.

ĐÚNG THIẾT KẾ · KỶ LUẬT NGUỒN

Mọi con số, tên phần tử, hành vi mô tả trong sách đều truy ngược được về một trong ba nguồn: (1) mã nguồn đang chạy (đọc thẳng file, không nhớ lại); (2) tài liệu kỹ thuật nội bộ có số đo thật (README.md của B36/B37, docs/, sổ ghi nhớ vận hành); (3) ảnh chụp và lệnh kiểm chứng trên hệ thống sống ngày 18/09/2026. Chỗ nào không truy được thì bỏ, không lấp bằng một con số nghe hợp lý.



Trạng thái hệ thống lúc biên soạn

Ảnh trong sách chụp ngày 18/09/2026, sau đợt sửa lỗi buổi tối cùng ngày. Bảng dưới là số đo thật lúc đó, để người đọc về sau biết sách mô tả hệ thống ở thời điểm nào.

Hạng mục

Giá trị đo được

Lệnh / nguồn

B36 container

Up 13 days, image lotus/profile_dashboard:1.0

docker ps

Trang công khai

không kèm mật khẩu: HTTP 401; có tài khoản: HTTP 200 (basic_auth ở Caddy từ 18/09)

curl -u

Kho NAS2

mounted: true, store: /nas

GET /api/health

Số profile

2, cả hai Sẵn sàng, cùng WP3, tổng 9,9 MB (10 383 360 byte)

GET /api/summary

Profile

UID_000010_GPM_Profile, UID_000011_GPM_Profile — mỗi gói 5 191 680 byte, 455 mục

tar tf trong container

Toàn vẹn

sha256 của cả hai .tar khớp manifest.json

sha256sum

Lần ghi kho cuối

2026-08-15T02:49:24Z — 34 ngày không có push/pull nào

manifest.updated

Sổ lịch sử

runs.jsonl 832 sự kiện (gồm dữ liệu demo tháng 7–8, xem 10.4)

GET /api/stats

B37 broker

tạo lại 18/09 để đọc B37_MYSQL_PASS từ .env; wp_topics = {WP3: WP3-PC1}, MySQL enabled: true

GET :20370/health

Worker WP3

Process Manager bật lại 18/09 (tắt từ 16/08); bắt mạch alive: true, rtt_ms: 640

GET /api/workers

Sổ A4 (MySQL)

2 hàng, cả hai khớp profile trên NAS (id 21, 22)

GET /api/mysql

Fleet Monitor B34

WP3 / PC_1 online sau khi bật lại Process Manager

GET :20340/api/state



LƯU Ý · KHO VẪN ĐANG NGHỈ DÙ WORKER ĐÃ BẬT LẠI

Từ 16/08 tới 18/09 máy worker WP3-PC1 không chạy Process Manager; nó đã được bật lại tối 18/09 và trả lời bắt mạch. Nhưng chưa có workflow nào chạy lại, nên kho chưa có lần ghi mới. Vì thế trong sách, các ảnh về trạng thái Đang chạy và Khoá treo là ảnh mô phỏng; còn các thao tác cần worker (Khôi phục, Xử lý treo, quét A1/A2 khi Dọn dẹp) chỉ được chụp tới bước hộp thoại xác nhận.



Những điều phát hiện khi đối chiếu

Đem tài liệu sẵn có và chính giao diện ra đối chiếu với mã nguồn đang chạy, thấy các điểm lệch dưới đây. Phần lớn đã được sửa ngay trong ngày 18/09/2026; cột cuối ghi tình trạng. Chi tiết ở Phụ lục F.

#

Chỗ lệch

Thực tế (đã kiểm chứng) · tình trạng

1

Ngăn kéo chi tiết gợi ý lệnh pull <id> --dest <thư mục> --lease, push <id> --dir <thư mục> --host %COMPUTERNAME%, lease steal <id> --host …

Cả ba bị profile_store.py từ chối (unrecognized arguments). Đúng là --host đặt trước lệnh con; pull <id> <dest>; push <id> <thư mục>. Đã sửa — mục 11.6

2

README.md của B36 mô tả dashboard chỉ đọc, không có nút hành động

Từ 13–14/08 đã có 5 nút một chạm + Dọn dẹp + Xoá hẳn, đi qua B37. Đã sửa README, /help, chân trang

3

README.md §12 nói kho đang chứa 6 profile demo

Kho giờ chỉ còn 2 profile thật; nhưng runs.jsonl vẫn giữ 776 sự kiện demo (id p_WP…) bên cạnh 56 sự kiện thật. Chưa dọn — chờ duyệt (mục 17.4)

4

Dải trống nằm giữa tiêu đề Profiles và đầu bảng

Là thanh chọn-nhiều bị CSS display:flex đè thuộc tính hidden. Đã sửa

5

Cột Thời lượng, Cache và nhãn WP trong lịch sử

Push từ node n8n ghi dur: 0, cache_mb: 0, wp: "". Đã sửa phần wp (tự suy từ tên máy); dur/cache vẫn phụ thuộc workflow truyền vào

6

Hạn lease mặc định

Ba giá trị khác nhau: profile_store.py 1800 s, .bat 300 s, GUI Process Manager 180 s. Giữ nguyên — xem F6

7

Trang công khai profiles.lotus1104.synology.me

Không có xác thực trong khi đã có nút ghi; cổng 443 của WP1 mở ra Internet. Đã sửa: basic_auth ở Caddy (chương 18)





Mục lục

Mở tài liệu bằng Microsoft Word rồi bấm F9 (hoặc chuột phải > Cập nhật trường) để điền số trang. Bản PDF đi kèm đã có số trang đúng.

⟳ Mở bằng Microsoft Word rồi bấm Ctrl+A → F9 để sinh mục lục kèm số trang.



PHẦN I

TỔNG QUAN





Chương 1 — B36 là gì và nằm ở đâu trong hệ thống

1.1 Bài toán B36 giải quyết

Sản phẩm chính của Great Lotus là một chuỗi tự động: n8n phát lệnh qua MQTT tới worker Windows (Process Manager APP_5); worker nhờ GPM Login mở Chrome kèm cổng CDP; n8n điều khiển ngược qua cổng đó. Profile Chrome — nơi chứa cookie đăng nhập và mọi dấu vết của một danh tính — không được để rải rác trên ổ cứng từng máy, vì máy hỏng là mất, và hai máy không dùng chung được. Lời giải là một kho tập trung trên NAS2.

Kho mà không nhìn thấy thì không vận hành được. B36 trả lời những câu mà người vận hành hỏi hằng ngày:

1.2 Ai dùng B36

Vai trò

Dùng B36 để

Chương nên đọc

Người vận hành

theo dõi kho hằng ngày, xử lý khoá treo và profile lỗi, dọn dẹp

9–14, 16–17, 20

Kỹ sư triển khai

dựng kho NAS2, container B36/B37, route Caddy từ đầu

3–6, 18

Người lo worker

nối Process Manager trên máy Windows với kho

7, 20

Người viết workflow

gọi pull/push profile từ node C12 của n8n

8, 17.3

Học viên

hiểu mô hình năm nơi + làm bài thực hành

1–2, 21



1.3 Một profile — năm nơi

Muốn dùng đúng B36 phải nắm mô hình năm nơi (chốt ngày 14/08/2026). Một profile cùng lúc hiện diện ở năm chỗ, mỗi chỗ một vai; tài liệu, mã nguồn và giao diện đều gọi bằng mã kèm tên để đọc tới đâu hiểu tới đó.

Hình 1.1. Năm nơi của một profile và hệ hình SAO quanh A3 (NAS).

Mã

Nơi

Vai trò

Ai được ghi

A1 (GPM)

GPM Login trên worker, API 127.0.0.1:19995

danh tính + entry để mở trình duyệt

worker (qua API localhost)

A2 (C_Temp)

C:\ZGPM_Loaded_Profiles\<mã>-<ngày>

dữ liệu lúc chạy — vứt được

worker

A3 (NAS)

\\172.16.20.201\lotus_profiles\profiles\<id>.tar

CHÂN LÝ dữ liệu

worker (push) + B37

A4 (MySQL)

bảng G01_GREAT_LOTUS_D02_Port_Pool.G01_GREAT_LOTUS_D03_GPM_Profile

sổ profile ↔ IP ↔ email cho n8n

n8n + B37

A5 (WebProfile)

chính B36 — profiles.lotus1104.synology.me

gương + bảng điều khiển

không ai (chỉ đọc kho)



NGUY HIỂM · ĐIỂM KHÔNG QUAY LẠI DUY NHẤT

Xoá nhầm A1, A2, A4 hay A5 đều cứu được từ A3. Nhưng xoá A3 khi A2 đã trống = mất vĩnh viễn. Vì vậy mọi nút xoá trên B36 mặc định chuyển sang cách ly hoặc thùng rác _deleted/, không xoá thật — trừ khi chính bạn tick Xoá VĨNH VIỄN.



GHI CHÚ · CÁCH GỌI CŨ

Trước 14/08/2026 thiết kế bỏ sót MySQL, nên web dashboard mang mã A4. Gặp tài liệu hay commit cũ ghi "A4 = dashboard" thì hiểu là A5 (WebProfile) ngày nay.



1.4 Ranh giới trách nhiệm: đọc và ghi

B36 chỉ đọc kho. Nó mount NAS2 bằng tài khoản WP2_GPM_Profile_RO và cờ ro — hai lớp chặn ghi độc lập, nên bản thân B36 không thể làm hỏng kho. Mọi thao tác ghi (khôi phục, gỡ khoá, xoá, dọn dẹp) được B36 chuyển tiếp sang B37 Profile Broker — container duy nhất có quyền ghi NAS (WP2_GPM_Profile_RW), nói MQTT với worker và được xoá hàng MySQL.

Thành phần

Đọc kho

Ghi kho

Nói MQTT

Ghi MySQL

B36 (A5)

có (RO)

không

không

không

B37 broker

có

có (RW)

có

có (user bot)

Worker (profile_store.py)

có

có (push, lease)

có (nhận lệnh)

không

n8n

gián tiếp

gián tiếp (qua worker)

có (phát lệnh)

có



ĐÚNG THIẾT KẾ · ĐÚNG THIẾT KẾ

Trình duyệt không bao giờ gọi thẳng B37. B37 chỉ nghe trong LAN/VPN (:20370); mọi nút bấm đi qua POST /api/command/<lệnh> của B36, B36 mới gọi B37 server-to-server.



1.5 B36 không làm gì

Chương 2 — Các khái niệm cốt lõi

2.1 ID profile: UID_xxxxxx_GPM_Profile

Từ 14/08/2026, ID của mọi profile do hệ thống tạo có dạng UID_000001_GPM_Profile — sáu chữ số tăng dần, không tái sử dụng. Chuỗi này là tên file ở A3 (<id>.tar), cột profile_id ở A4, khoá trong .lotus_profile_map.json ở A2, dòng hiển thị ở A5 — và là TÊN profile trong GPM (A1). GPM vẫn dùng UUID nội bộ, nhưng cầu nối giữa A1 và phần còn lại là tên: hỏi GPM "profile nào tên UID_000003_GPM_Profile" là ra UUID.

LƯU Ý · ĐỔI TÊN TRONG GPM = ĐỔI ID

Sửa tay tên UID_… trong GPM là cắt cầu nối. Dọn dẹp thấy mất con dấu nên chỉ báo cáo, không xoá — mất liên kết chứ không mất dữ liệu. Xem Phụ lục B.



2.2 Gói .tar không nén và upload nguyên tử

Kho lưu mỗi profile thành một gói TAR không nén. Lý do ghi trong đầu profile_store.py: profile 50 MB xả nén TAR mất 1,45 s, 0 % CPU; ZIP/ZSTD mất 3,12 s vì tốn CPU giải nén. Chrome không bao giờ chạy thẳng trên share (random I/O qua SMB đo được 166,7 s) — luôn xả ra SSD cục bộ trước.

Khi đẩy lên, gói được ghi vào incoming/<id>.<host>.tmp rồi os.replace sang profiles/<id>.tar. Đổi tên trong cùng một share là nguyên tử, nên người đọc không bao giờ thấy một gói ghi dở. Số đo thật qua VPN (05/08/2026, gói 50 MB): đóng gói 0,44 s · upload 4,84 s (~10 MB/s) · pull 0,24 s · xả 0,09 s.

2.3 Lease — khoá phiên có thời hạn

Hai máy chạy cùng một profile rồi cùng push là ghi đè lẫn nhau. Chống việc đó bằng lease: trước khi pull để chạy, worker tạo _locks/<id>.lock bằng O_EXCL (thử 10 tiến trình đua trên NAS2: đúng 1 thắng). Lease có hạn (TTL); trong lúc profile mở, một luồng nền gia hạn mỗi TTL/3 giây. Push tự từ chối nếu máy khác đang giữ lease còn hạn.

Hình 2.1. Vòng đời một phiên profile trên worker.

Hình 2.2. Lease hết hạn ra sao khi máy crash (ví dụ TTL 300 s).

Nơi đặt TTL

Giá trị mặc định

Nguồn

profile_store.py (LEASE_TTL_SEC)

1800 s

docs/tools/profile_store.py

.bat khởi động worker (LOTUS_PROFILE_LEASE_TTL)

300 s

ghi nhớ vận hành 05/08/2026

GUI Process Manager, ô Lease TTL (sec)

180 s

_default_values() của GUI



MẸO · CHỌN TTL

Có heartbeat nên TTL ngắn vẫn an toàn cho phiên dài. TTL ngắn = máy crash thì chỉ vài phút sau máy khác đã giành lại được. Máy crash rồi khởi động lại thì đòi lại lease của chính nó ngay (same-host reclaim).



2.4 Sổ cái, sổ lịch sử và vùng cách ly

Tệp

Là gì

B36 dùng để

_index/manifest.json

một bản ghi mỗi profile: size, sha256, status, work_pool, runs, lần chạy cuối, cache

cột bảng + ngăn kéo chi tiết

_index/runs.jsonl

sổ chỉ-thêm, mỗi lần push một dòng {ts,id,host,wp,dur,cache_mb,size}

khu thống kê

_locks/<id>.lock

ai giữ, lấy lúc nào, gia hạn lúc nào, expires_ts

Đang chạy / Khoá treo

quarantine/<id>.<ts>.tar

bản lỗi đã tách khỏi kho chính

Cách ly + bảng cách ly

_deleted/

thùng rác của Xoá hẳn (và bản sao hàng MySQL đã xoá)

không hiển thị



2.5 Năm trạng thái

B36 không đọc trạng thái từ đâu cả — nó suy ra trạng thái theo đúng thứ tự trong store_reader.snapshot():

Hình 2.3. Luật suy trạng thái, kiểm từ trên xuống.

Trạng thái

Nhãn trên giao diện

Nghĩa

Cần làm

running

Đang chạy (xanh lá)

có máy giữ lease còn hạn

không đụng vào

idle

Sẵn sàng (xanh dương)

có .tar, không ai chạy

không cần

quarantined

Cách ly (đỏ)

bản lỗi đã tách khỏi kho chính

điều tra, thay bản tốt

stale-lock

Khoá treo (vàng)

lease đã hết hạn — máy giữ có thể đã chết

kiểm máy, rồi gỡ khoá

missing

Thiếu file (xám)

manifest có nhưng không thấy .tar

push lại hoặc dọn





PHẦN II

KIẾN TRÚC & HẠ TẦNG





Chương 3 — Kiến trúc triển khai

3.1 Bức tranh toàn cảnh

Hình 3.1. Các khối và đường nối thật của B36/B37 trên HP1.

Mọi khối trong hình đều khai trong DCP_PRODUCTION/docker-compose.yml hoặc B16_Caddy/Caddyfile, trừ NAS2, broker MQTT và worker là máy ngoài HP1.

3.2 Container B36 — lotus_profile_dashboard

Thuộc tính

Giá trị

Ghi chú

Tên container

lotus_profile_dashboard

hostname PROFILE_DASHBOARD

Image

lotus/profile_dashboard:1.0

build từ B36_Profile_Dashboard/Dockerfile (python:3.12-slim)

IP nội bộ

114.114.114.36

mạng GreatLotus_Net

Cổng

20360:8080

quy ước Bxx: B36 → .36 → 20360

Tiến trình

uvicorn server:app --host 0.0.0.0 --port 8080

không bật --reload

Thư viện

fastapi==0.115.6, uvicorn[standard]==0.34.0

app/requirements.txt; giao diện không dùng CDN

Mount

app/ → /app/app:ro, static/ → /app/static:ro, nas2_profiles_ro:/nas:ro

code bind-mount chỉ đọc

Múi giờ

TZ=Asia/Ho_Chi_Minh + 2 mount zoneinfo

đủ 3 dòng theo quy định UTC+7

Khởi động lại

restart: unless-stopped

—



Biến môi trường của B36

Biến

Mặc định

Ý nghĩa

PROFILE_STORE_DIR

/nas

gốc kho trong container

STATIC_DIR

/app/static

nơi chứa SPA

SNAPSHOT_CACHE_SEC

2

gộp các lần poll /api/snapshot của nhiều tab trong 2 s

STATS_CACHE_SEC

30

cache /api/stats — thống kê không cần tươi từng giây

B37_URL

http://114.114.114.37:8080

nơi chuyển tiếp lệnh ghi



3.3 Cấu trúc mã nguồn B36

B36_Profile_Dashboard/

├── Dockerfile python:3.12-slim, pip fastapi/uvicorn, CMD uvicorn :8080

├── README.md tài liệu kỹ thuật (cập nhật 18/09 — ranh giới B36 đọc / B37 ghi)

├── app/

│ ├── requirements.txt

│ ├── server.py 13 endpoint + proxy lệnh sang B37 + phục vụ SPA

│ └── store_reader.py đọc kho: manifest/runs/locks/quarantine -> snapshot + stats

├── static/

│ ├── index.html khung trang chính

│ ├── app.js toàn bộ logic giao diện (vanilla JS, 1 328 dòng)

│ ├── style.css nền tối/sáng, huy hiệu, ngăn kéo, hộp thoại

│ ├── help.html + help.css trang /help

│ └── manual.html hướng dẫn đồng bộ năm nơi (HDID_00008)

└── Giao_Trinh/ giáo trình này



MẸO · SỬA CODE CÓ CẦN BUILD LẠI?

Không. app/ và static/ được bind-mount. Đổi file .py thì docker compose restart lotus_profile_dashboard (uvicorn không tự nạp lại); đổi html/js/css thì chỉ cần F5. Chỉ khi đổi Dockerfile hay requirements.txt mới phải up -d --build.



3.4 Container B37 — lotus_profile_broker

Thuộc tính

Giá trị

Tên / image

lotus_profile_broker · lotus/profile_broker:0.1

IP / cổng

114.114.114.37 · 20370:8080 — chỉ trong LAN/VPN

Mount kho

nas2_profiles_rw:/nas — ghi được, tài khoản WP2_GPM_Profile_RW

Mount công cụ

${REPO_ROOT}/docs/tools/profile_store.py:/app/profile_store.py:ro

MQTT

B37_MQTT_HOST/PORT/USER/PASS lấy từ khoá MQTT_BROKER_*_SGP trong .env (VPS 128.199.200.17:1883)

MySQL

B37_MYSQL_HOST=114.114.114.2, PORT=3306, USER=bot, DB=G01_GREAT_LOTUS_D02_Port_Pool, TABLE=G01_GREAT_LOTUS_D03_GPM_Profile; mật khẩu B37_MYSQL_PASS=${B37_MYSQL_PASS} lấy từ .env

Bảng WP → topic

B37_WP_TOPICS, mặc định {"WP3":"WP3-PC1"}

Bắt mạch worker

B37_PROBE_TIMEOUT mặc định 6 s

Kiểm thử

149 testcase: bash B37_Profile_Broker/verify/run_verify.sh



3.5 Bản đồ API

B36 có tài liệu tương tác tự sinh tại /docs (Swagger), /redoc và /openapi.json. Toàn bộ endpoint, đọc thẳng từ app/server.py:

Phương thức

Đường dẫn

Trả về / việc

Cache

GET

/api/health

{ok, mounted, store, server_time}

không

GET

/api/snapshot

profiles[] + summary + work_pools + quarantine[]

2 s

GET

/api/summary

chỉ tổng hợp + phân theo WP

2 s

GET

/api/profiles?wp=&status=&q=

danh sách có lọc

2 s

GET

/api/profiles/{id}

một profile, 404 nếu không có

2 s

GET

/api/locks

{running[], stale_lock[]}

2 s

GET

/api/quarantine

{count, quarantine[], profiles[]}

2 s

GET

/api/stats

series.{day,week,month} + windows.{day,week,month}

30 s

GET

/api/runs?limit=200

sự kiện gần nhất từ runs.jsonl (1–5000)

không

GET

/api/mysql

sổ A4 + phân loại, lấy qua B37

không

GET

/api/broker

/health của B37 (có wp_topics)

không

GET

/api/workers?timeout=

bắt mạch song song mọi WP qua B37

không

POST

/api/command/{lệnh}

chuyển tiếp sang B37: restore, kill_chrome, release_lock, delete_nas, cleanup, purge

không

GET

/, /help, /static/*

giao diện

—



GHI CHÚ · THỜI GIAN CHỜ KHI CHUYỂN LỆNH

B36 chờ B37 tối đa 140 s cho mỗi lệnh, riêng cleanup 220 s — cố ý dài hơn timeout MQTT của B37 (cleanup 180 s), để B36 không bỏ cuộc trước khi worker kịp trả lời. B37 tắt hay không tới được thì B36 trả 502 kèm B37 không phản hồi: ….



Chương 4 — Kho profile trên NAS2

4.1 Bố cục thư mục

Hình 4.1. Cây thư mục của share lotus_profiles.

Cây do profile_store.py init tạo (SUBDIRS = profiles, incoming, quarantine, _index, _locks, _uid, _deleted). Trên kho thật ngày 18/09 còn có _leases/ rỗng — tàn tích của phiên bản B37 đầu tiên từng tự dựng hệ khoá riêng rồi bỏ vì gây split-brain. Chỉ có một hệ khoá: _locks/.

4.2 manifest.json — bản ghi thật

Trích nguyên văn một bản ghi trong kho ngày 18/09/2026:

"UID_000011_GPM_Profile": {

"size": 5191680,

"sha256": "21c263c8cf097bfc4685f98b69378411311535703114e459f1ba502fbce4bc45",

"status": "active",

"mtime": "2026-08-15T02:49:24Z",

"last_host": "WP3-PC1",

"work_pool": "",

"runs": 3,

"last_run_time": "2026-08-15T02:49:22Z",

"last_run_machine": "WP3-PC1",

"last_run_duration_sec": 0,

"cache_limit_mb": 50,

"last_actual_cache_mb": 0.0,

"first_seen": "2026-08-15T02:49:14Z"

}



Trường

Ý nghĩa

Hiện ở giao diện

size, sha256

kích thước và băm của .tar lúc push

cột Cỡ; sha256 trong ngăn kéo (16 ký tự đầu)

status

active hoặc quarantined

góp phần suy trạng thái

mtime

lần kho được cập nhật

Cập nhật kho

work_pool

env LOTUS_WORK_POOL; thiếu thì giữ giá trị cũ, rồi suy từ tên máy WPn

cột WP (rỗng thì B36 đoán từ tên máy)

runs

tổng số lần push

cột Lần chạy

last_run_*

thời điểm, máy, thời lượng lần cuối

Chạy gần nhất, Thời lượng

cache_limit_mb, last_actual_cache_mb

trần cache AIMD và cache đo được

cột Cache

quarantine_*

chỉ có khi bị cách ly: thời điểm, lý do, tên file

bảng cách ly



LƯU Ý · WORK_POOL RỖNG

Cả hai bản ghi thật có work_pool: "" vì biến LOTUS_WORK_POOL chưa được đặt trên WP3-PC1. B36 vẫn hiện đúng WP3 nhờ đoán từ tên máy (_HOST_WP trong store_reader.py: chuỗi _WP3/wp3 → WP3). Tên máy không khớp mẫu nào thì hiện WP?. Từ 18/09/2026, profile_store.py tự lấy theo thứ tự: biến LOTUS_WORK_POOL → work_pool cũ trong manifest → chữ WPn trong tên máy (WP3-PC1 → WP3), nên các lần push sau sẽ không còn rỗng. Máy worker phải có bản profile_store.py mới (git pull) thì mới có tác dụng.



4.3 runs.jsonl và lock

# _index/runs.jsonl — dòng thật cuối cùng

{"ts": "2026-08-15T02:49:22Z", "id": "UID_000011_GPM_Profile", "host": "WP3-PC1",

"wp": "", "dur": 0, "cache_mb": 0.0, "size": 5191680}


# _locks/<id>.lock — định dạng (README B36 §4)

{"host": "WP3_PC6", "owner": "WP3_PC6/pid12345", "acquired_at": "...",

"acquired_ts": 1.7e9, "renewed_at": "...", "expires_ts": 1.7e9}



B36 tính ttl_left_sec = expires_ts − bây giờ và expired = expires_ts ≤ bây giờ ngay lúc đọc; đó là con số lease còn … hiện ở cột Đang chạy ở.

4.4 Cách đoán Workpool khi thiếu

Mẫu tên máy (regex, không phân biệt hoa thường)

Workpool

\bwp1\b, bắt đầu HP1, chứa NUC1, _WP1

WP1

\bwp2\b, chứa PC176, _WP2, MAC1

WP2

\bwp3\b, _WP3

WP3

không khớp

? — hiển thị WP?



Chương 5 — Mạng, tài khoản và quyền

5.1 Ba workpool

Hình 5.1. B36/B37 ở WP1, kho ở WP2, worker hiện có ở WP3.

Máy

Địa chỉ

Vai trò với B36

HP1

172.16.10.220 (WP1)

chạy Docker Compose: B16 Caddy, B36, B37

NAS2 (White NAS)

172.16.20.201 (WP2), DSM :5000

share lotus_profiles trên /volume1

WP3-PC1

172.16.30.101 (WP3)

worker: Process Manager + GPM, topic MQTT WP3-PC1

MQTT broker

128.199.200.17:1883 (VPS ngoài)

B37 ⇄ worker; phản hồi trên RES_<topic>



5.2 Tài khoản truy cập NAS2

Tài khoản

Quyền

Ai dùng

Cất ở đâu

WP2_GPM_Profile_RO

chỉ đọc

B36 (volume nas2_profiles_ro)

.env: WP2_GPM_PROFILE_RO_USER/PASS

WP2_GPM_Profile_RW

đọc + ghi

B37 (volume nas2_profiles_rw), mọi worker

.env: WP2_GPM_PROFILE_RW_USER/PASS; trên worker: Windows Credential Manager (cmdkey)

tài khoản admin DSM

quản trị

chỉ khi tạo share / tài khoản

không nằm trong hệ



ĐÚNG THIẾT KẾ · HAI LỚP CHẶN GHI Ở B36

Tài khoản RO và tuỳ chọn mount ro,file_mode=0444,dir_mode=0555. Đã kiểm trên mount thật (05/08/2026): RW ghi được, RO bị chặn ghi.



5.3 Credential không nằm trong mã



PHẦN III

KHỞI TẠO BAN ĐẦU & KẾT NỐI





Chương 6 — Khởi tạo từ đầu

Chương này dựng lại toàn bộ hệ từ một NAS trống và một HP1 chưa có B36/B37. Làm đúng thứ tự: mỗi bước dựa trên kết quả bước trước. Mỗi bước có một lệnh kiểm tra — chưa qua kiểm tra thì đừng sang bước sau.

Hình 6.1. Tám bước khởi tạo và bên thực hiện.

GHI CHÚ · HỆ ĐANG CHẠY THÌ BỎ QUA GÌ?

Ngày 18/09/2026 bước 1–6 đã xong và đang chạy. Thêm một máy worker mới thì chỉ làm bước 7–8 (chương 7). Thêm một workpool mới thì thêm cả mục 7.6 (khai topic cho B37).



6.1 Bước 1 — Tạo share trên NAS2

1. Đăng nhập DSM của NAS2 tại http://172.16.20.201:5000 bằng tài khoản quản trị.

2. Control Panel > Shared Folder > Create: tên lotus_profiles, đặt trên /volume1.

3. Bật SMB (Control Panel > File Services > SMB) nếu chưa bật; khuyến nghị tối thiểu SMB 3.

Share \\172.16.20.201\lotus_profiles hiện có được tạo ngày 05/08/2026 qua API SYNO.Core.Share của DSM với tài khoản quản trị — cách bằng giao diện ở trên cho cùng kết quả.

Kiểm tra

smbclient -L //172.16.20.201 -U <tài_khoản> # thấy lotus_profiles trong danh sách



6.2 Bước 2 — Hai tài khoản riêng: ghi và chỉ đọc

Tài khoản

Quyền trên lotus_profiles

Dùng cho

WP2_GPM_Profile_RW

Read/Write

worker (push/pull/lease) và B37

WP2_GPM_Profile_RO

Read only

B36 và mọi công cụ kiểm kê



LƯU Ý · ĐỪNG DÙNG MỘT TÀI KHOẢN CHO MỌI THỨ

Tách đọc/ghi là lý do B37 tồn tại. Nếu B36 mount bằng tài khoản RW, một lỗi trong B36 đủ làm hỏng kho; còn với RO thì NAS từ chối mọi lệnh ghi dù mã có sai.



Kiểm tra (trên HP1, mount thử rồi gỡ)

# ghi mật khẩu vào file tạm quyền 600 — KHÔNG gõ mật khẩu lên dòng lệnh

install -m 600 /dev/null /root/.nas2_test && printf 'username=WP2_GPM_Profile_RO\npassword=%s\n' "$PASS" > /root/.nas2_test

mkdir -p /mnt/nas2_test

mount -t cifs //172.16.20.201/lotus_profiles /mnt/nas2_test -o credentials=/root/.nas2_test,vers=3.0,ro

touch /mnt/nas2_test/x # phải báo lỗi: tài khoản RO không được ghi

umount /mnt/nas2_test && shred -u /root/.nas2_test



6.3 Bước 3 — Khởi tạo cây kho

Mount bằng tài khoản RW rồi chạy init một lần. Lệnh tạo đủ các thư mục chuẩn và một manifest.json rỗng; chạy lại cũng không hại gì.

mount -t cifs //172.16.20.201/lotus_profiles /mnt/lotus_profiles \

-o credentials=/root/.smbcred_rw,vers=3.0

python3 docs/tools/profile_store.py --store /mnt/lotus_profiles init

python3 docs/tools/profile_store.py --store /mnt/lotus_profiles list # kho rỗng, không lỗi



MẸO · THỨ TỰ THAM SỐ CỦA PROFILE_STORE.PY

--store và --host là tham số toàn cục, phải đứng trước lệnh con: profile_store.py --store X --host Y push …. Đặt sau lệnh con thì argparse báo unrecognized arguments (đã kiểm chứng). Ngăn kéo chi tiết của B36 in đúng thứ tự này từ bản 1.8.1.



6.4 Bước 4 — Khai báo .env trên HP1

Mở DCP_PRODUCTION/.env (gitignored). Các khoá B36/B37 dùng — tên khoá như dưới, giá trị điền theo tài khoản thật:

DCP_ROOT=<đường dẫn DCP_PRODUCTION>

REPO_ROOT=<gốc repo PRODUCTION_HOST_PC> # B37 mount docs/tools/profile_store.py từ đây

NAS2_PROFILE_SHARE=//172.16.20.201/lotus_profiles

WP2_GPM_PROFILE_RO_USER=WP2_GPM_Profile_RO

WP2_GPM_PROFILE_RO_PASS=<mật khẩu RO>

WP2_GPM_PROFILE_RW_USER=WP2_GPM_Profile_RW

WP2_GPM_PROFILE_RW_PASS=<mật khẩu RW>

MQTT_BROKER_HOST_SGP=128.199.200.17

MQTT_BROKER_PORT_SGP=1883

MQTT_BROKER_USER_SGP=<user MQTT>

MQTT_BROKER_PASS_SGP=<mật khẩu MQTT>



6.5 Bước 5 — Volume CIFS và hai service

Trong docker-compose.yml, khối volumes: khai hai volume CIFS — daemon Docker tự mount SMB, container không cần chạy privileged:

volumes:

nas2_profiles_ro:

driver: local

driver_opts:

type: cifs

device: ${NAS2_PROFILE_SHARE}

o: "username=${WP2_GPM_PROFILE_RO_USER},password=${WP2_GPM_PROFILE_RO_PASS},vers=3.0,ro,

uid=0,gid=0,file_mode=0444,dir_mode=0555,nobrl"

nas2_profiles_rw: # CHỈ B37 dùng

driver: local

driver_opts:

type: cifs

device: ${NAS2_PROFILE_SHARE}

o: "username=${WP2_GPM_PROFILE_RW_USER},password=${WP2_GPM_PROFILE_RW_PASS},vers=3.0,

uid=0,gid=0,file_mode=0644,dir_mode=0755,nobrl"



(Trong file thật, chuỗi o: nằm trên một dòng; ở đây ngắt cho vừa trang.) Khối service B36 rút gọn:

lotus_profile_dashboard:

container_name: lotus_profile_dashboard

build: { context: ${DCP_ROOT}/B36_Profile_Dashboard }

image: lotus/profile_dashboard:1.0

volumes:

- /usr/share/zoneinfo/Asia/Ho_Chi_Minh:/etc/localtime:ro

- /usr/share/zoneinfo:/usr/share/zoneinfo:ro

- ${DCP_ROOT}/B36_Profile_Dashboard/app:/app/app:ro

- ${DCP_ROOT}/B36_Profile_Dashboard/static:/app/static:ro

- nas2_profiles_ro:/nas:ro

environment:

- TZ=Asia/Ho_Chi_Minh

- PROFILE_STORE_DIR=/nas

- SNAPSHOT_CACHE_SEC=2

- B37_URL=http://114.114.114.37:8080

ports: ["20360:8080"]

restart: unless-stopped

networks: { GreatLotus_Net: { ipv4_address: '114.114.114.36' } }



Khối B37 tương tự với nas2_profiles_rw:/nas, mount thêm ${REPO_ROOT}/docs/tools/profile_store.py:/app/profile_store.py:ro, các biến B37_MQTT_* và B37_MYSQL_* (mục 3.4), cổng 20370:8080, IP 114.114.114.37. Mật khẩu MySQL cũng tham chiếu .env như mọi mật khẩu khác: B37_MYSQL_PASS=${B37_MYSQL_PASS}.

NGUY HIỂM · MÚI GIỜ: ĐỦ BA DÒNG

Mọi container mới phải có TZ=Asia/Ho_Chi_Minh và hai mount zoneinfo. Thiếu mount thứ hai trên image Alpine/musl thì giờ thành +0000. Đổi múi giờ phải up -d (tạo lại), restart không ăn.



Chạy và kiểm tra

cd DCP_PRODUCTION

docker compose config >/dev/null && echo CONFIG_OK # luôn validate trước khi up

docker compose up -d --build lotus_profile_broker lotus_profile_dashboard

curl -s http://127.0.0.1:20360/api/health

# {"ok":true,"mounted":true,"store":"/nas","server_time":"..."}

curl -s http://127.0.0.1:20370/health

# {"ok":true,"service":"B37_profile_broker","store":"/nas","locks_active":0,

# "wp_topics":{"WP3":"WP3-PC1"},"mysql":{"enabled":true,...}}



LƯU Ý · MOUNTED: FALSE

mounted chỉ đúng khi /nas/profiles và /nas/_index cùng tồn tại (StoreReader.mounted). Sai thì hoặc chưa chạy init (bước 3), hoặc volume CIFS mount hỏng — xem mục 20.1.



6.6 Bước 6 — Route công khai qua Caddy

# B16_Caddy/Caddyfile — khối hiện hành (từ 18/09/2026 có đăng nhập)

profiles.lotus1104.synology.me {

basic_auth {

lotus <băm bcrypt — KHÔNG phải mật khẩu>

}

reverse_proxy lotus_profile_dashboard:8080

}



1. Chọn mật khẩu, ghi vào .env cạnh compose dưới hai khoá B36_WEB_USER và B36_WEB_PASS để người vận hành tra lại được.

2. Tạo băm: docker exec lotus_caddy caddy hash-password --plaintext '<mật khẩu>', dán chuỗi $2a$14$… vào khối trên. Caddyfile chỉ giữ băm, không giữ mật khẩu.

3. Thêm khối vào B16_Caddy/Caddyfile, rồi docker exec lotus_caddy caddy validate --config /etc/caddy/Caddyfile phải in Valid configuration.

4. Chạy docker restart lotus_caddy — không dùng caddy reload (reload không nạp route mới).

5. Caddy tự xin chứng chỉ Let's Encrypt (HTTP-01). DNS *.lotus1104.synology.me là wildcard nên không cần khai tên miền mới.

6. Kiểm từ trong LAN: phải map tên miền về Caddy vì hairpin NAT chặn đường qua IP public (--host-resolver-rules=MAP profiles.lotus1104.synology.me 114.114.114.16 cho Chrome, hoặc curl --resolve profiles.lotus1104.synology.me:443:<IP Caddy>).

# kiểm tra (từ HP1; 114.114.114.16 là Caddy)

curl -sk -o /dev/null -w '%{http_code}\n' --resolve profiles.lotus1104.synology.me:443:114.114.114.16 \

https://profiles.lotus1104.synology.me/ # 401

curl -sk -o /dev/null -w '%{http_code}\n' --resolve profiles.lotus1104.synology.me:443:114.114.114.16 \

-u "lotus:$B36_WEB_PASS" https://profiles.lotus1104.synology.me/api/health # 200



NGUY HIỂM · KHÔNG BỎ ĐĂNG NHẬP

B36 có nút Xoá hẳn và Dọn dẹp, còn cổng 443 của WP1 mở ra Internet. Trước 18/09/2026 route này không có xác thực (mục 18.2). Truy cập trong LAN qua http://172.16.10.220:20360 không đi qua Caddy nên không hỏi mật khẩu — cổng đó không được forward ra ngoài.



6.7 Bước 8 — Kiểm tra đầu-cuối

Bước 7 (nối worker) là cả chương 7. Sau khi xong, kiểm toàn tuyến:

#

Lệnh / thao tác

Kết quả đúng

1

curl -s :20360/api/health

mounted: true

2

curl -s :20360/api/broker

ok: true và wp_topics có WP của máy mới

3

curl -s :20360/api/workers

alive: true kèm rtt_ms vài chục tới vài trăm

4

curl -s :20360/api/mysql

ok: true; orphan_free rỗng

5

chạy một workflow pull → mở → đóng → push

profile hiện trên bảng, Lần chạy tăng 1

6

mở https://profiles.lotus1104.synology.me/

chấm NAS xanh, chấm Live xanh



Chương 7 — Kết nối máy worker

7.1 Worker cần những gì

Thứ cần có

Ở đâu

Kiểm tra

docs/tools/profile_store.py

trong repo trên worker (git-tracked)

python docs\tools\profile_store.py --help

Library/B05_GPM_Login_Manager/profile_nas_sync.py

gitignored ⇒ chép bằng scp

so sha256 hai đầu

Process Manager APP_5 có pull_profile/push_profile/restore_profile/ping

Application/APP_5_…/_core/ (gitignored)

gửi manager.ping, không thấy Unsupported function

Credential NAS RW

Windows Credential Manager

cmdkey /list:172.16.20.201

Thư mục profile GPM

mặc định C:\ZGPM_Loaded_Profiles

tồn tại, GPM đang dùng



LƯU Ý · FILE GIT-TRACKED VẪN CÓ THỂ THIẾU

Vụ 12/08/2026: profile_store.py là file tracked nhưng worker checkout cũ nên không có. Luôn kiểm tồn tại thật trên worker; với file gitignored: so hash → scp → xoá __pycache__ → khởi động lại một instance → probe (Phụ lục C).



7.2 Tab Main — mục NAS Profile Connection

Từ 10/08/2026, cấu hình NAS nằm trong GUI Process Manager thay cho khối set LOTUS_PROFILE_* trong .bat. Khi bấm Kết nối hoặc Apply, GUI đặt lại os.environ tương ứng nên profile_nas_sync.py (đọc biến môi trường lúc chạy) dùng đúng giá trị.

Hình 7.1. Tab Main, mục NAS Profile Connection (chưa cấu hình — ô hiện chữ gợi ý).

#

Thành phần

Biến môi trường

Giá trị đúng cho WP3

Ghi chú

1

Bật đồng bộ NAS

LOTUS_PROFILE_NAS_SYNC

tick nếu muốn hook tự động

bật: tự pull trước khi mở, push sau khi đóng. Node C12 pull/push vẫn chạy độc lập

2

Profile Store (UNC)

LOTUS_PROFILE_STORE

\\172.16.20.201\lotus_profiles

UNC hoặc thư mục đã mount

3

NAS Username

—

WP2_GPM_Profile_RW

phải là tài khoản ghi được

4

NAS Password

—

mật khẩu RW

chỉ dùng để lưu qua cmdkey; ô hiện •

5

Local Profile Dir

LOTUS_GPM_PROFILE_DIR

C:\ZGPM_Loaded_Profiles

thư mục GPM giữ folder profile; bấm Kết nối sẽ tự tạo nếu chưa có

6

Kết nối / Test NAS

—

—

tạo Local Dir → cmdkey /add → thử os.path.isdir(store)

7

Đèn + dòng trạng thái

—

xanh NAS OK: <store>

đỏ: Không truy cập được kho: … (kiểm tra user/mật khẩu/mạng)



GHI CHÚ · VỀ ẢNH CHỤP

Ảnh chụp bằng chính mã GUI chạy trên màn hình X của B35 (Linux) nên các ô NAS để trống và hiện chữ gợi ý. Trên worker Windows đã cấu hình, đèn thứ ba ở góc phải trên cùng đổi thành NAS: đã kết nối.



MẸO · NÚT CONNECT CŨNG KẾT NỐI NAS

Nút Connect lớn ở dưới cùng kết nối MQTT/Fleet và tự chạy Kết nối / Test NAS nếu đã điền đủ Store + Username + Local Dir. NAS chạy ở luồng nền riêng nên không chặn MQTT.



7.3 Tab Advanced — mục NAS Sync Options

Hình 7.2. Tab Advanced, mục NAS Sync Options (cuộn xuống cuối).

#

Ô

Biến

Khuyến nghị

1

Profile Folder Glob

LOTUS_GPM_PROFILE_GLOB

{id} là mặc định; đổi sang {id}-* nếu GPM đặt thư mục có hậu tố ngày. Từ 14/08 pull/push hỏi thẳng GPM profile_path nên ô này chỉ còn là đường lui

2

Host Label (lease/audit)

LOTUS_PROFILE_HOST

để trống = tên máy. Đặt đúng tên máy (VD WP3-PC1) vì chuỗi này hiện ở cột Đang chạy ở và được dùng để đoán WP

3

Lease TTL (sec)

LOTUS_PROFILE_LEASE_TTL

300 (khớp .bat và placeholder node C12)

4

Lease Renew (sec)

LOTUS_PROFILE_LEASE_RENEW_SEC

để trống = TTL/3



GHI CHÚ · BIẾN CHƯA CÓ Ô TRÊN GUI: LOTUS_WORK_POOL

GUI chưa có ô cho biến này, nên hai profile thật trên kho đang có work_pool rỗng. Từ 18/09/2026 profile_store.py tự suy WP khi thiếu biến: giữ giá trị cũ trong manifest, hoặc lấy chữ WPn trong tên máy (WP3-PC1 → WP3). Máy có tên không chứa WPn thì vẫn đặt trong .bat: set LOTUS_WORK_POOL=WP3. Bản profile_store.py mới đến worker bằng git pull.



7.4 Cấu hình bằng .bat (cách cũ, vẫn chạy)

rem 2_GPM_multi_remote_browser_process_manager_gui.bat (dùng setlocal -> python kế thừa env)

set LOTUS_PROFILE_NAS_SYNC=1

set LOTUS_PROFILE_STORE=\\172.16.20.201\lotus_profiles

set LOTUS_GPM_PROFILE_DIR=C:\ZGPM_Loaded_Profiles

set LOTUS_PROFILE_HOST=WP3-PC1

set LOTUS_PROFILE_LEASE_TTL=300

set LOTUS_WORK_POOL=WP3

rem credential: MỘT LẦN, không nhúng mật khẩu vào .bat

cmdkey /add:172.16.20.201 /user:WP2_GPM_Profile_RW /pass



.bat hiện hành còn có khoá một instance: PowerShell giết mọi Process Manager cũ theo tên script trước khi mở cái mới. Hai instance cùng nghe một topic là nguồn gốc lỗi Unsupported function push_profile ngày 10/08 (Phụ lục C).

7.5 Kiểm tra worker từ HP1

curl -s http://127.0.0.1:20360/api/workers

# worker sống:

# {"ok":true,"probe_timeout_sec":6.0,"workers":{"WP3":{"alive":true,"rtt_ms":...}}}

# worker tắt (thực tế ngày 18/09/2026):

# {"WP3":{"topic":"WP3-PC1","alive":false,"rtt_ms":6138,"error":"timeout"}}


# probe tay theo đúng topic (công cụ git-tracked):

GPM_MQTT_TOPIC=WP3-PC1 python3 docs/tools/mqtt_send.py '<action_json>' '<inputs_json>'



LƯU Ý · TIMEOUT CHƯA CHẮC LÀ MÁY CHẾT

Kiểm topic trước: WP3-PC1 nghe WP3-PC1, không phải WP2_PC176 (topic cũ vẫn còn trong vài tài liệu và làm mặc định của mqtt_send.py). Rồi kiểm có đúng một Process Manager đang chạy. Xem Phụ lục D.



7.6 Thêm một workpool mới

1. Dựng worker theo 7.1–7.5 và đọc topic THẬT trong config đang chạy trên máy đó (GPM_multi_remote_browser_process_manager_gui_config.json > values.subscribe_topics).

2. Khai cho B37 trong compose: B37_WP_TOPICS={"WP3":"WP3-PC1","WP2":"<topic thật>"}.

3. docker compose up -d lotus_profile_broker (biến môi trường cần tạo lại container).

4. curl -s :20360/api/broker — wp_topics phải có WP mới.

5. Mở hộp thoại Dọn dẹp: WP mới được tick sẵn và hiện worker trả lời sau … ms.

GHI CHÚ · KHI B37 CHƯA BIẾT TOPIC CỦA MỘT WP

Hộp thoại Dọn dẹp vẫn liệt kê WP đó (nếu kho có profile thuộc nó) nhưng không tick sẵn, kèm dòng B37 chưa có topic MQTT cho … — gửi lệnh xuống topic không ai nghe chỉ làm người dùng ngồi chờ hết timeout.



Chương 8 — Kết nối từ n8n

8.1 Hai node C12: Pull và Push

Node greatLotusPyAutoWeb (thư viện C12) có hai action đặt trên cùng nhóm Manager — manager.pull_profile (hiển thị Pull_Profile_from_NAS) và manager.push_profile (Push_Profile_to_NAS). Cả hai không chạm GPM API: chỉ bảo worker chạy profile_store.py.

Action

Tham số

Mặc định / ghi chú

Pull

profile_id (bắt buộc)

ID dạng UID_xxxxxx_GPM_Profile

Pull

dest_dir

trống = LOTUS_GPM_PROFILE_DIR trên worker

Pull

acquire_lease

bật: giành lease + heartbeat; node Push sẽ nhả

Pull

host, lease_ttl, store

trống = tên máy / 300 s / LOTUS_PROFILE_STORE

Push

profile_id (bắt buộc)

—

Push

profile_dir

trống = tự dò theo LOTUS_GPM_PROFILE_DIR + glob

Push

duration, actual_cache_mb

nên điền — để trống thì lịch sử ghi dur 0, cache 0

Push

release_lease

bật: nhả lease sau khi push



LƯU Ý · VÌ SAO CỘT THỜI LƯỢNG VÀ CACHE TOÀN SỐ 0

Hai tham số duration và actual_cache_mb của Push không được workflow hiện tại điền ⇒ runs.jsonl ghi dur: 0, cache_mb: 0.0 ⇒ biểu đồ và cột Thời lượng mất ý nghĩa, AIMD không chạy (hạn mức kẹt 50 MB). Truyền thời gian mở→đóng và cache đo được vào hai ô này.



8.2 Trình tự chuẩn trong một workflow

1. Pull Profile (lease bật) — tải .tar mới nhất, xả vào thư mục GPM thật, trả dest_dir và addination_args (đọc từ config.json trong gói).

2. Open GPM Profile — node tự đọc config.json (load_args_from_config mặc định bật) để ghép --disk-cache-size, --media-cache-size=1, --disable-pdf-viewer.

3. Các bước tự động hoá.

4. Close Profile — để Chrome flush xong dữ liệu.

5. Push Profile (release bật) — đóng gói, upload nguyên tử, nhả lease.

NGUY HIỂM · PROXY KHÔNG ĐI THEO LỆNH MỞ

open_profile gọi GET /profiles/start/<id> của GPM, không nhận raw_proxy. Proxy là thuộc tính lưu sẵn của profile. Muốn đổi proxy phải update_profile (hoặc create_profile) có raw_proxy trước khi mở.



8.3 Tạo profile mới đúng chuẩn UID

Gọi create_profile với profile_name để trống (hoặc AUTO): worker tự xin UID_… qua profile_store uid next. Từ 14/08/2026 worker còn bỏ qua mọi tên caller gửi và luôn xin UID — trừ khi tên đã đúng chuẩn UID (đường khôi phục) hoặc caller bật keep_name=true. Xin UID thất bại thì không tạo profile.

// phản hồi create_profile — id chính danh nằm ngay tầng ngoài

{ "manager_action": "create_profile",

"profile_id": "UID_000003_GPM_Profile", "lotus_id": "UID_000003_GPM_Profile",

"gpm_id": "8a9f3f11-…", "gpm_http": { … } }



Workflow IP_Pool → GPM Profile → NAS (WP2) (id Lv1RQyThF9U9cT8Q, đang INACTIVE) đọc data.profile_id rồi mang đúng chuỗi đó đi push NAS, ghi MySQL (nhớ kèm link_email = JSON_ARRAY() vì cột NOT NULL) và mở/đóng profile.

LƯU Ý · TẠO BẰNG GPM GUI THÌ KHO KHÔNG BIẾT

Profile tạo tay trong phần mềm GPM không đi qua worker nên không được đẩy lên NAS — đó là lý do "tạo profile mà NAS vẫn rỗng". Tên tự đặt cũng không mang con dấu UID nên Dọn dẹp sẽ không tự xử.





PHẦN IV

GIAO DIỆN — MỔ XẺ TỪNG THÀNH PHẦN





Chương 9 — Trang chính

9.1 Toàn cảnh

Trang chính là một trang đơn (SPA) viết bằng JavaScript thuần, không thư viện ngoài. Mở http://172.16.10.220:20360/ trong LAN hoặc https://profiles.lotus1104.synology.me/. Trang có sáu vùng, từ trên xuống:

Hình 9.1. Toàn trang ở nền tối, dữ liệu thật ngày 18/09/2026.

#

Vùng

Nội dung

Mục

1

Thanh đầu trang

tên, đường dẫn kho, đèn NAS/Live, 6 nút

9.2

2

Ô tổng hợp

5 ô đếm theo trạng thái — bấm để lọc

9.3

3

Thanh lọc

chip Workpool, chip trạng thái, ô tìm

9.4

4

Bảng Profiles

một dòng mỗi profile, 10 cột

9.5

5

Thống kê sử dụng

biểu đồ + 3 bảng xếp hạng

chương 10

6

Chân trang

nhắc nguồn dữ liệu, liên kết Hướng dẫn và /docs

9.9



GHI CHÚ · TỰ LÀM MỚI

Trang gọi /api/snapshot mỗi 4 giây và /api/stats mỗi 60 giây (REFRESH_MS, STATS_MS trong app.js). Server gộp các lần gọi trong 2 s (snapshot) và 30 s (stats), nên mở nhiều tab cũng không quét SMB dồn dập.



9.2 Thanh đầu trang

Hình 9.2. Thanh đầu trang — 11 thành phần.

#

Thành phần

Công dụng

Lưu ý

1

Tên Lotus Profile Fleet

nhận diện trang; tab trình duyệt ghi Lotus Profile Fleet — NAS2

—

2

Dòng đường dẫn kho

\\172.16.20.201\lotus_profiles

chữ cố định trong HTML, không đọc từ cấu hình

3

Đèn NAS

xanh = kho đã mount (mounted: true); đỏ = chưa mount

đỏ kèm dải báo lỗi (mục 14.4)

4

Đèn Live

xanh = lần gọi gần nhất thành công

đổi chữ Tạm dừng khi dừng; đỏ + lỗi mạng khi gọi hỏng

5

cập nhật kho: … trước

tuổi của manifest.updated — lần ghi kho cuối

34 ngày trước = không có push nào từ 15/08

6

Nút chổi — Dọn dẹp

mở hộp thoại dọn dữ liệu mồ côi, lấy NAS làm chuẩn

chương 13

7

Nút sách — Hướng dẫn đồng bộ

mở /static/manual.html

mục 15.2

8

Nút dấu hỏi — Trợ giúp

mở /help

mục 15.1

9

Nút bánh răng — API

mở /docs (Swagger)

mục 15.3

10

Nút tạm dừng / tiếp tục

ngừng hoặc tiếp tục tự làm mới

mục 9.6

11

Nút mặt trời / mặt trăng

đổi nền sáng/tối

nhớ lựa chọn trong localStorage (b36-theme)



9.3 Năm ô tổng hợp

Hình 9.3. Năm ô tổng hợp — mỗi ô là một nút lọc.

#

Ô

Số hiển thị

Dòng phụ

Bấm vào

1

Tổng profile

summary.total

tổng dung lượng các .tar

bỏ lọc trạng thái

2

Đang chạy

summary.running

bấm để lọc

lọc running

3

Sẵn sàng

summary.idle

bấm để lọc

lọc idle

4

Cách ly (lỗi)

summary.quarantined

bấm để lọc

lọc quarantined

5

Khoá treo

summary.stale_lock

lease hết hạn — cần chú ý

lọc stale-lock



Bấm ô đang được chọn lần nữa thì bỏ lọc; ô đang lọc có viền sáng. Sau khi bấm, trang tự cuộn xuống bảng.

GHI CHÚ · KHÔNG CÓ Ô THIẾU FILE

Trạng thái Thiếu file không có ô riêng; nó chỉ xuất hiện dưới dạng chip lọc khi có ít nhất một profile ở trạng thái đó (hình 14.1).



9.4 Thanh lọc

Hình 9.4. Chip Workpool, chip trạng thái, ô tìm, bộ đếm dòng.

#

Thành phần

Công dụng

Lưu ý

1

Tất cả WP n

bỏ lọc Workpool

số = tổng profile

2

Chip WP (ở đây: WP3 2)

chỉ hiện profile của một WP

chỉ có chip cho WP đang có profile; WP không rõ hiện WP?

3

Mọi trạng thái

bỏ lọc trạng thái

—

4

Đang chạy n

lọc running

luôn hiện, kể cả khi bằng 0

5

Sẵn sàng n

lọc idle

luôn hiện; các chip Cách ly/Khoá treo/Thiếu file chỉ hiện khi > 0

6

Ô Lọc theo id / máy…

lọc theo chuỗi con, không phân biệt hoa thường

so với id, máy chạy gần nhất, và máy đang giữ lease

7

(n) cạnh chữ Profiles

số dòng đang hiện sau khi lọc

—

8

Dòng gợi ý

bấm một dòng để xem chi tiết … tick ô vuông để xóa hàng loạt

—



Hình 9.5. Gõ 000011 vào ô tìm: còn 1 dòng.

Hình 9.6. Bấm ô Đang chạy khi không có profile nào chạy: bảng báo không có profile khớp.

9.5 Bảng Profiles — mười cột

Hình 9.7. Đầu bảng: ô chọn tất cả và chín cột dữ liệu.

#

Cột

Nguồn

Cách hiển thị

1

Ô chọn (đầu bảng)

—

chọn/bỏ chọn mọi dòng đang hiện; nửa chọn khi chỉ tick một phần

2

Profile

id

chữ đơn cách, xanh; bấm dòng mở ngăn kéo

3

WP

work_pool hoặc đoán từ tên máy

huy hiệu màu: WP1 xanh dương, WP2 xanh lá, WP3 vàng

4

Trạng thái

suy ra (mục 2.5)

huy hiệu màu; rê chuột thấy câu giải thích

5

Đang chạy ở

lock

tên máy + lease còn Xm Ys; khoá treo: chữ vàng lease HẾT HẠN; không có: —

6

Cỡ

kích thước .tar trên đĩa

B / KB / MB / GB

7

Lần chạy

runs

số nguyên, — nếu chưa có

8

Chạy gần nhất

last_run_time

tương đối (34 ngày trước); rê chuột thấy giờ UTC

9

Thời lượng

last_run_duration_sec

s, m s, h m

10

Cache

last_actual_cache_mb / cache_limit_mb

0/50 MB; dấu ⓘ mở /help#cache



Sắp xếp: bấm tiêu đề cột để sắp tăng, bấm lần nữa để giảm. Mặc định sắp theo Profile tăng dần. Giá trị trống luôn dồn về một phía (chữ coi như ~, số coi như -1).

Hình 9.8. Bấm hai lần tiêu đề Chạy gần nhất: sắp mới nhất lên trên.

GHI CHÚ · DẢI TRỐNG PHÍA TRÊN ĐẦU BẢNG (ĐÃ SỬA)

Trước bản 1.8.1, một dải mỏng nằm giữa dòng Profiles và đầu bảng: đó là thanh chọn-nhiều bị ẩn không trọn vì CSS .bulk-bar{display:flex} thắng thuộc tính hidden. Ngày 18/09/2026 đã thêm .bulk-bar[hidden]{display:none!important}; giờ thanh chỉ xuất hiện khi có dòng được tick (mục 9.8). Còn thấy dải trống thì trình duyệt đang giữ CSS cũ — tải lại trang bằng Ctrl+F5.



9.6 Tạm dừng tự làm mới

Hình 9.9. Đang tạm dừng: đèn Live đổi chữ, nút thành dấu tam giác.

Tạm dừng hữu ích khi đang đọc một dòng mà không muốn bảng nhảy. Chỉ dừng /api/snapshot; thống kê vẫn tự làm mới mỗi 60 giây. Bấm lại để tiếp tục — trang gọi ngay một lượt.

9.7 Nền sáng

Hình 9.10. Cùng trang ở nền sáng.

9.8 Chọn nhiều dòng

Hình 9.11. Tick ô đầu bảng: cả hai dòng được chọn, thanh hàng loạt hiện ra.

#

Thành phần

Công dụng

1

Ô chọn tất cả

tick mọi dòng đang hiện theo bộ lọc

2

Dòng đã chọn

nền vàng nhạt

3

Đã chọn n profile

bộ đếm

4

Xoá hẳn (A1 (GPM)→A5 (WebProfile))

mở hộp thoại xoá hàng loạt (mục 12.5)

5

Bỏ chọn

xoá lựa chọn



MẸO · TICK Ô KHÔNG MỞ NGĂN KÉO

Ô tick nằm trong dòng có sự kiện mở ngăn kéo; mã chặn nổi bọt (stopPropagation) nên tick chỉ chọn dòng. Bấm vào bất kỳ chỗ nào khác của dòng mới mở ngăn kéo.



9.9 Chân trang

Nhắc nguồn dữ liệu (_index/manifest.json + _index/runs.jsonl + _locks/ + quarantine/) và hai liên kết Hướng dẫn & API (/help) và /docs. Từ bản 1.8.1 câu này ghi "đọc kho NAS2 (mount chỉ đọc); hành động ghi đi qua B37" — thay cho câu cũ "không sửa gì trên kho" vốn chỉ đúng với riêng B36.

Chương 10 — Khu Thống kê sử dụng

10.1 Các thành phần

Hình 10.1. Chế độ Ngày: 30 cột, mỗi cột một ngày.

#

Thành phần

Công dụng

1

Ngày / Tuần / Tháng

đổi cửa sổ: 30 ngày gần nhất · 12 tuần ISO · 12 tháng

2

Dòng tóm tắt

n lần chạy <hôm nay / 7 ngày qua / 30 ngày qua> · Σ thời lượng · tổng lịch sử N lần

3

Chú giải

màu WP1, WP2, WP3 và xám WP? (chưa biết)

4

Biểu đồ cột

số lần push mỗi khoảng, xếp chồng theo WP; rê chuột thấy nhãn · WP: n lần

5

Profile dùng nhiều nhất

top 10 trong cửa sổ; bấm một dòng mở ngăn kéo của profile đó

6

Theo Workpool

số lượt của WP1/WP2/WP3

7

Máy chạy nhiều nhất

top 10 tên máy



10.2 Đơn vị đếm

Một lần chạy là một dòng trong runs.jsonl, tức một lần push. Cửa sổ hôm nay tính theo ngày UTC (store_reader.stats() dùng datetime.now(timezone.utc).date()), nên từ 00:00 tới 07:00 giờ Việt Nam, "hôm nay" vẫn là hôm qua theo lịch Việt Nam.

10.3 Tuần và Tháng

Hình 10.2. Chế độ Tuần: 12 tuần ISO gần nhất (nhãn W27…W38).

Hình 10.3. Chế độ Tháng: dữ liệu tập trung ở 07/26 và 08/26.

10.4 Đọc đúng biểu đồ ngày 18/09/2026

Chế độ Ngày và Tuần trống vì không có push nào trong 30 ngày qua. Chế độ Tháng cho thấy hai cột lớn ở 07/26 và 08/26 — phần lớn là dữ liệu demo: runs.jsonl có 832 sự kiện, trong đó 776 sự kiện (id p_WP…) được bơm ngày 06/08 để minh hoạ giao diện; phần kho demo đã được dọn nhưng sổ lịch sử thì chưa. Phần màu xám là sự kiện thật có wp rỗng.

GHI CHÚ · MÀU XÁM TRONG CHÚ GIẢI

Biểu đồ vẽ nhóm ? bằng màu xám #8b949e. Trước bản 1.8.1 chú giải chỉ liệt kê WP1–WP3; nay có thêm mục WP? (chưa biết). Xám = sự kiện push ghi wp rỗng. Từ 18/09/2026 profile_store.py tự suy WP từ tên máy, nên các lần push mới sẽ không còn rơi vào nhóm này.



MẸO · MUỐN BIỂU ĐỒ SẠCH

Lọc bỏ các dòng id p_WP* khỏi _index/runs.jsonl (sao lưu trước; cần mount RW) — mục 17.4.



Chương 11 — Ngăn kéo chi tiết profile

11.1 Mở và đóng

Bấm một dòng của bảng Profiles, bảng Cách ly hoặc danh sách Profile dùng nhiều nhất. Ngăn kéo trượt ra bên phải; đóng bằng nút ✕, phím Esc hoặc bấm vùng tối bên trái. Nội dung tự làm mới theo nhịp 4 giây; nếu profile biến mất khỏi kho, ngăn kéo tự đóng.

Hình 11.1. Ngăn kéo của một profile Sẵn sàng (dữ liệu thật).

#

Thành phần

Công dụng

1

ID profile

tiêu đề ngăn kéo

2

Huy hiệu WP + trạng thái

cùng màu với bảng

3

Nút ✕

đóng ngăn kéo

4

Khôi phục về worker

đem bản NAS về máy worker của WP — mục 12.1

5

Xoá khỏi kho → Cách ly

chuyển .tar sang quarantine/ — mục 12.3

6

Xoá hẳn (mọi nơi)

xoá ở A1…A5 — mục 12.5

7

Hộp hướng dẫn

giải thích trạng thái + việc cần làm; với vài trạng thái có kèm lệnh CLI



11.2 Khối Chi tiết

Hình 11.2. Phần dưới ngăn kéo: 11 dòng chi tiết và ghi chú chân.

Dòng

Lấy từ

Ví dụ thật

Trạng thái

suy ra

Sẵn sàng

Workpool

work_pool

WP3

Kích thước .tar

kích thước file

5.0 MB

Số lần chạy

runs

3

Chạy gần nhất

last_run_time

2026-08-15 02:49:22 UTC (34 ngày trước)

Máy chạy gần nhất

last_run_machine

WP3-PC1

Thời lượng lần cuối

last_run_duration_sec

0s

Cache (thực/hạn mức)

last_actual_cache_mb / cache_limit_mb

0 / 50 MB

Lần đầu thấy

first_seen

2026-08-15 02:49:14 UTC

Cập nhật kho

mtime

2026-08-15 02:49:24 UTC

sha256

16 ký tự đầu

21c263c8cf097bfc…



11.3 Nút nào hiện với trạng thái nào

Tập nút do hàm actionsFor() trong app.js quyết định. Nút Xoá hẳn luôn có — kể cả với Thiếu file, vốn là ca cần nó nhất (một dòng rác trên bảng).

Trạng thái

Gỡ khoá treo

Xử lý treo

Khôi phục

Xoá → Cách ly

Xoá hẳn

Sẵn sàng



có

có

có

Đang chạy


có


có

có

Khoá treo

có

có

có

có

có

Cách ly



có


có

Thiếu file





có



11.4 Đang chạy (mô phỏng)

Hình 11.3. Profile đang chạy trên WP3-PC1, lease còn 4 phút 40 giây (mô phỏng).

#

Thành phần

Ý nghĩa

1

Khối hành động

chỉ có Xử lý treo, Xoá → Cách ly, Xoá hẳn

2

Xử lý treo (đóng)

gửi close_profile xuống worker

3

Hộp xanh

bình thường không cần làm gì; chỉ can thiệp khi chắc máy đã treo



11.5 Khoá treo (mô phỏng)

Hình 11.4. Lease của WP3-PC2 đã hết hạn (mô phỏng).

#

Thành phần

Ý nghĩa

1

Khối hành động

đủ 5 nút: Gỡ khoá treo, Xử lý treo, Khôi phục, Xoá → Cách ly, Xoá hẳn

2

Hộp vàng

máy còn giữ lease nhưng đã hết hạn — có heartbeat nên treo = đã chết thật

3

Ba bước

kiểm máy trên Fleet Monitor → nếu máy khởi động lại thì tự đòi lại → nếu chết hẳn thì steal

4

Khối lệnh + nút Copy

lệnh CLI chạy trên máy sẽ tiếp quản — giải thích ở 11.6



11.6 Lệnh CLI gợi ý

Ngăn kéo in các lệnh tương đương cho thao tác tay, kèm nút Copy. Tới bản 1.8.0 các lệnh này sai cú pháp (--host đặt sau lệnh con, --dest/--dir không tồn tại) và profile_store.py từ chối cả ba. Bản 1.8.1 (18/09/2026) đã sửa; bảy lệnh dưới đây đã chạy thử trên một kho tạm, tất cả trả mã 0:

Việc

Lệnh giao diện in (bản 1.8.1)

Bản 1.8.0 in (bị từ chối)

Xem ai giữ

python profile_store.py --store "\\172.16.20.201\lotus_profiles" lease status <id>

giống nhau

Giành lại lease

python profile_store.py --store "…" --host %COMPUTERNAME% lease steal <id>

… lease steal <id> --host %COMPUTERNAME%

Kéo về + giữ lease

python profile_store.py --store "…" --host %COMPUTERNAME% pull <id> <thu_muc> --lease

… pull <id> --dest <thu_muc> --lease

Đẩy bản tốt lên

python profile_store.py --store "…" --host %COMPUTERNAME% push <id> <thu_muc>

… push <id> --dir <thu_muc> --host %COMPUTERNAME%

Kiểm toàn vẹn / liệt kê

… verify <id> · … list

giống nhau



LƯU Ý · LỆNH ĐÃ CHÉP TỪ BẢN CŨ

Ai đã lưu lệnh chép từ giao diện trước 18/09/2026 (ghi chú, script, tin nhắn) thì kiểm lại: có --dest, --dir hoặc --host nằm sau lệnh con là bản cũ, sẽ báo unrecognized arguments.



MẸO · XẢ THẲNG VÀO THƯ MỤC GPM

pull <id> <dest> xả vào <dest>/<id>. Muốn xả thẳng vào thư mục GPM thật (tên dạng <mã>-<ngày>) thì thêm --into <thư mục GPM>.



11.7 Cách ly và Thiếu file (mô phỏng)

Hình 11.5. Profile bị cách ly kèm lý do (mô phỏng).

#

Thành phần

Ý nghĩa

1

Khối hành động

Khôi phục về worker + Xoá hẳn

2

Hộp đỏ

lý do (quarantine_reason) và tên file trong quarantine/

3

Bốn bước

tải bản lỗi về xem → sửa được thì push đè → không cứu được thì tạo mới → verify

4

Khối lệnh

đường dẫn SMB tới bản lỗi + lệnh push/verify (sửa cú pháp như 11.6)



Hình 11.6. Manifest có mà không thấy .tar (mô phỏng).

#

Thành phần

Ý nghĩa

1

Khối hành động

chỉ còn Xoá hẳn (mọi nơi)

2

Hộp vàng

dữ liệu lệch: xoá tay, upload hỏng, hoặc vừa bị cách ly

3

Ba bước

kiểm quarantine/ → có bản tốt thì push lại → bỏ thì để nguyên hoặc Dọn dẹp

4

Khối lệnh

list + push

5

Nút Copy

chép cả khối lệnh; đổi chữ Đã copy ✓ trong 1,4 giây



Chương 12 — Các hộp thoại hành động

Mọi nút hành động mở một hộp xác nhận trước. Hủy, phím Esc hoặc bấm ra ngoài đều đóng mà không gửi gì. Hộp thao tác phá huỷ có viền đỏ và nút Xác nhận xóa.

Hình 12.1. Đường đi của một nút bấm từ trình duyệt tới worker.

12.1 Khôi phục về worker

Hình 12.2. Hộp xác nhận Khôi phục.

#

Thành phần

Ý nghĩa

1

Tiêu đề

tên nút đã bấm

2

Nội dung

profile nào, về worker của WP nào; worker tạo lại profile trong GPM nếu A1 thiếu rồi tải data về

3

Hủy

đóng, không gửi

4

Xác nhận

gửi POST /api/command/restore {profile_id, work_pool}



Worker thi hành restore_profile: pull kèm lease và xả vào đúng thư mục GPM (hỏi GPM profile_path, hoặc tra .lotus_profile_map.json). 99 % ca GPM còn entry nên chỉ cần pull; ca máy trắng thì tạo lại entry GPM — nhánh này còn nợ kỹ thuật (Phụ lục E).

ĐÚNG THIẾT KẾ · THÀNH CÔNG THẬT LÀ GÌ

Với lệnh đi qua worker (Khôi phục, Xử lý treo), giao diện chỉ báo ✓ khi B37 nhận được phản hồi (ok: true) và worker trả return: "OK". Worker trả NG thì toast đỏ kèm lỗi; không ai trả lời thì toast worker không phản hồi (timeout).



Hình 12.3. Sau khi xác nhận: nút chuyển Đang gửi… và bị khoá tới khi có kết quả.

12.2 Xử lý treo (đóng Chrome)

Hình 12.4. Hộp xác nhận Xử lý treo (mô phỏng).

Gửi close_profile qua MQTT; worker đóng profile qua GPM API. Dùng khi profile treo, không phản hồi. Không cần remote_port — giao diện chỉ gửi profile_id và work_pool.

12.3 Xoá khỏi kho → Cách ly

Hình 12.5. Hộp xác nhận Xoá khỏi kho (viền đỏ).

#

Thành phần

Ý nghĩa

1

Tiêu đề

Xóa khỏi kho → Cách ly

2

Nội dung

bản này chuyển vào cách ly — hồi được; cảnh báo A3 có thể là bản sao cuối cùng

3

Hủy

—

4

Xác nhận xóa

gửi delete_nas {profile_id, confirm: true}; B37 tự làm, không cần worker



12.4 Gỡ khoá treo

Hình 12.6. Hộp xác nhận Gỡ khoá treo (mô phỏng).

NGUY HIỂM · CHỈ GỠ KHI CHẮC MÁY ĐÃ CHẾT

B37 xoá thẳng _locks/<id>.lock trên NAS. Gỡ nhầm khi máy kia vẫn chạy = mở đường cho hai máy cùng ghi (clobber) — bản push sau đè mất bản trước. Kiểm Fleet Monitor B34 trước.



12.5 Xoá hẳn — một hay nhiều profile

Hình 12.7. Xoá hẳn một profile từ ngăn kéo.

#

Thành phần

Ý nghĩa

1

Tiêu đề

Xóa hẳn n profile

2

Hộp cảnh báo

xoá ở MỌI NƠI: entry A1, thư mục A2, .tar A3, hàng A4, dòng A5

3

Danh sách theo WP

profile gom theo Workpool; WP không rõ thì chỉ xoá được A3/A4

4

Xoá VĨNH VIỄN

mặc định tắt: .tar sang _deleted/ (hồi được bằng tay trên NAS)

5

Xoá cả profile đang chạy (force)

mặc định tắt: bỏ qua chốt lock còn hạn

6

Xác nhận xóa

gửi purge {profile_ids[], work_pool, confirm, hard, force} — mỗi WP một bước



Hình 12.8. Xoá hẳn hai profile đã tick trên bảng.

Thứ

hard = tắt (mặc định)

hard = bật

profiles/<id>.tar

→ _deleted/<id>.<ts>.purge.tar

xoá thật

quarantine/<id>.*.tar

→ _deleted/

xoá thật

manifest, _locks, _rescue

xoá bản ghi

xoá bản ghi

hàng A4 (MySQL)

xoá, sao lưu _deleted/mysql/<id>.<ts>.json

như trái

entry A1 + thư mục A2

worker xoá (trước kho)

như trái



GHI CHÚ · THỨ TỰ CÓ CHỦ Ý: WORKER TRƯỚC, KHO SAU

Chết giữa chừng thì thứ còn lại là bản NAS — thứ cứu được. Worker không trả lời thì B37 hoãn (skipped) chứ không xoá phía kho, để không sinh đúng loại rác hệ này sinh ra để dọn. Hàng MySQL còn link_email không bị xoá trừ khi bật force.



Chương 13 — Dọn dẹp — lấy NAS làm chuẩn

Nút chổi trên thanh đầu trang. Nó dọn những thứ không còn liên kết với kho: bản ghi mồ côi, khoá mồ côi, mảnh upload dở, hàng MySQL mồ côi, entry GPM và thư mục cục bộ không còn trên NAS. Luôn xem trước — không xoá gì cho tới khi bạn bấm Thực thi.

Hình 13.1. Luồng Dọn dẹp và ba phạm vi.

13.1 Bước 1 — chọn phạm vi

Hình 13.2. Ngay khi mở: đang bắt mạch worker.

Hộp thoại gọi /api/workers ngay khi mở (song song, khoảng 6 giây). Máy không trả lời bị bỏ tick sẵn kèm lý do — để bạn biết trước khi bấm, thay vì ngồi chờ hết timeout 180 giây của lệnh nặng.

Hình 13.3. Sau khi bắt mạch: WP3 không phản hồi nên bị bỏ tick.

#

Thành phần

Mặc định

Ý nghĩa

1

Tiêu đề

—

Dọn dẹp — bước 1: chọn phạm vi

2

Giới thiệu

—

lấy A3 (NAS) làm chuẩn; luôn xem trước

3

Dòng WP

tick nếu B37 biết topic và worker sống

quét A1 + A2 trên worker đó; chữ đỏ không phản hồi trong 6s = Process Manager tắt

4

Dọn sổ A4 (MySQL)

bật

xoá hàng trỏ tới profile không còn trên NAS

5

Xoá cả hàng MySQL CÒN email

tắt

email ↔ IP là dữ liệu nghiệp vụ, mặc định chỉ báo cáo

6

Chỉ đụng profile mang con dấu UID

bật

tên khác chỉ báo cáo

7

Xoá cả thư mục A2 không map được id

tắt

thư mục lạ quá ngưỡng tuổi

8

Xoá cả thư mục A3 không biết id (force)

tắt

nguy hiểm: có thể là data chưa từng push

9

Chỉ đụng thư mục A2 cũ hơn (giờ)

24

0 = bỏ chốt tuổi

10

Xem trước

—

chạy với apply: false



GHI CHÚ · VÌ SAO CÓ CHỐT TUỔI 24 GIỜ

Thư mục vừa ghi có thể là phiên vừa đóng mà push còn đang thử lại; xoá nó có khi là xoá bản duy nhất. Chốt tuổi được hỏi trước mọi luật khác. Trước khi có ô này, người dùng bấm dọn mà thư mục vẫn còn và không hiểu vì sao.



13.2 Tiến trình

Hình 13.4. Đang chạy bước 1/2 — mỗi phạm vi là một bước riêng.

#

Thành phần

Ý nghĩa

1

Tiêu đề

Đang quét (xem trước)… hoặc Đang dọn dẹp…

2

Dòng tiến độ + đồng hồ

Bước i/n — … và số giây đã trôi

3

Thanh tiến độ

đo bằng số bước đã xong, không bịa phần trăm

4

Gợi ý

mỗi worker thường 5–60 s

5

Danh sách bước

○ chờ · vòng quay đang chạy · ✓ xong · ✗ lỗi, kèm ghi chú

6

Dừng chờ

chỉ ngừng chờ ở trình duyệt; lệnh đã gửi vẫn chạy nốt dưới worker



Trong lúc chạy, phím Esc và bấm ra ngoài bị vô hiệu — đóng nhầm là mất dấu lệnh đang chạy.

13.3 Bước 2 — kế hoạch

Hình 13.5. Kết quả xem trước thật ngày 18/09/2026: không có gì cần dọn.

#

Thành phần

Ý nghĩa

1

Tiêu đề

Dọn dẹp — bước 2: xem trước

2

Dòng tổng

số mục sẽ xoá ở kho/sổ, MySQL, GPM, thư mục; số giữ lại có chủ ý

3

Thẻ Kho A3 (NAS)

bản ghi mồ côi · khoá mồ côi · mảnh upload dở cũ · cờ cần cứu mồ côi

4

Thẻ A4 (MySQL)

hàng sẽ xoá · mồ côi còn email (giữ) · hàng khớp (không đụng)

5

Đóng

không thực thi



ĐÚNG THIẾT KẾ · KHÔNG THẤY NÚT THỰC THI — ĐÚNG THIẾT KẾ

Khi tổng số mục cần xoá bằng 0, hộp thoại ẩn nút Thực thi. Khi có việc, nút Thực thi dọn dẹp hiện bên cạnh Đóng và chạy lại đúng các bước với apply: true. Nếu có WP được quét, mỗi WP thêm một thẻ: entry GPM sẽ xoá, entry GPM lạ chỉ báo cáo, thư mục A2 sẽ xoá, thư mục giữ lại.



Phân loại thư mục A2 khi quét worker

Lớp

Nghĩa

Xử lý

safe

đã an toàn trên NAS (push sau lần sửa cuối, .tar tồn tại)

xoá

unlinked

NAS không biết id này

chỉ xoá khi bật force

unsafe

dữ liệu chưa được cứu

không bao giờ xoá

running

đang có lock còn hạn hoặc chrome.exe dùng

giữ

orphan

không map được id

chỉ xoá khi bật tuỳ chọn thư mục lạ

young

mới hơn chốt tuổi

giữ (không hiện trong danh sách giữ)



Thư mục có tên bắt đầu bằng . hoặc _ (ví dụ _backup) vô hình với cả Dọn dẹp lẫn janitor.

Chương 14 — Các trạng thái đặc biệt của trang

14.1 Kho có đủ năm trạng thái (mô phỏng)

Hình 14.1. Bốn profile mô phỏng thêm vào hai profile thật.

#

Thành phần

Để ý

1

Ô tổng hợp

đếm cả 6 profile; Tổng tính luôn dung lượng

2

Chip WP

xuất hiện WP1, WP2 vì kho có profile của các WP đó

3

Chip trạng thái

xuất hiện Cách ly, Khoá treo, Thiếu file vì mỗi loại > 0

4

Dòng Đang chạy

cột Đang chạy ở: tên máy + lease còn 4m 40s



14.2 Bảng Cách ly

Hình 14.2. Bảng Cách ly — chỉ hiện khi có ít nhất một bản cách ly (mô phỏng).

#

Thành phần

Ý nghĩa

1

Tiêu đề + số lượng

Cách ly (profile lỗi) (n)

2

cách xử lý →

mở /help#quarantine

3

Bảng

Profile · Lý do · Thời điểm (từ tên file YYYYmmdd_HHMMSS) · Cỡ · File; bấm dòng mở ngăn kéo



14.3 Lọc theo WP và theo trạng thái

Hình 14.3. Chip WP3: còn 4 dòng.

Hình 14.4. Bấm ô Khoá treo: còn đúng dòng có lease hết hạn.

14.4 Mất kết nối NAS

Hình 14.5. Khi B36 không đọc được kho (mô phỏng lỗi Host is down).

#

Thành phần

Ý nghĩa

1

Đèn NAS đỏ

mounted: false

2

Dải báo lỗi

Kho NAS2 chưa mount được + gợi ý kiểm volume nas2_profiles_ro và tài khoản RO + lỗi gốc

3

Bảng rỗng

Chưa đọc được kho NAS2.



Đèn Live vẫn xanh vì B36 vẫn trả lời; chỉ kho phía sau là hỏng. Cách xử lý ở mục 20.1.

Chương 15 — Các trang phụ

15.1 Trang /help

Hình 15.1. Đầu trang /help: mục lục bên trái, nội dung bên phải.

Mục

Nội dung

1 · Trang này là gì

vai trò, nguồn dữ liệu, ranh giới B36 đọc / B37 ghi (cập nhật 18/09/2026)

2 · Các trạng thái & cách xử lý

5 trạng thái, dấu hiệu, việc cần làm

3 · CACHE nghĩa là gì?

cache thực / hạn mức AIMD; vì sao Cache ≤ cỡ .tar

4 · Đọc biểu đồ thống kê

ngày/tuần/tháng, xếp chồng theo WP

5 · Dùng API cho script

bảng endpoint GET + ví dụ curl; ghi chú về POST /api/command/{action} (chuyển sang B37) và curl -u khi đi qua địa chỉ công khai

6 · Sự cố thường gặp

triệu chứng → nguyên nhân → lệnh kiểm (docker exec … ls /nas/_index/manifest.json, docker volume inspect …)



Hình 15.2. Mục 3 — giải thích cột Cache.

15.2 Hướng dẫn đồng bộ (manual.html)

Nút sách trên thanh đầu trang mở /static/manual.html — hướng dẫn người dùng cuối về mô hình năm nơi, cũng được đăng trên web tài liệu với mã HDID_00008. Mười mục:

Mục

Nội dung

1

Một profile — năm nơi

2

Hệ hình SAO — NAS ở giữa

3

Vòng đời bình thường của một profile

4

Luật vàng

5

Quy tắc đặt tên — cũng chính là ID

6

Lỡ tay xoá thì sao? — ma trận

7

Dùng A5 (WebProfile) — nút một chạm

8

Dọn dẹp & Xoá hẳn

9

Xử lý sự cố thường gặp

10

Làm sao để KHÔNG mất đồng bộ



Hình 15.3. Mục 2 của manual — hệ hình SAO quanh A3 (NAS).

Hình 15.4. Mục 6 — ma trận lỡ tay xoá ở từng nơi.

15.3 Tài liệu API tương tác /docs

Hình 15.5. Swagger UI tự sinh từ FastAPI.

Các endpoint gom theo nhóm meta, profiles, stats, command. Bấm một endpoint > Try it out > Execute để gọi thật từ trình duyệt.

Hình 15.6. Gọi thử GET /api/health: 200, mounted true.

NGUY HIỂM · TRY IT OUT CŨNG CHẠY ĐƯỢC LỆNH GHI

Nhóm command trong Swagger là thật: Execute POST /api/command/purge sẽ xoá thật qua B37. Chỉ dùng Swagger cho các endpoint GET.





PHẦN V

TÁC VỤ & VẬN HÀNH





Chương 16 — Quy trình các tác vụ chính

Mỗi quy trình ghi: khi nào dùng, các bước, kết quả mong đợi. Nút trên giao diện là đường chính; lệnh CLI là đường lui khi B37 hoặc worker không sẵn sàng.

16.1 Đem một profile về lại worker

Dùng khi: thư mục A2 trên worker đã bị xoá (Janitor, dọn đĩa) hoặc muốn chạy profile ở máy khác.

1. Mở Dọn dẹp và nhìn dòng WP — hoặc gọi /api/workers — để chắc worker của WP đó đang sống.

2. Bấm dòng profile (trạng thái Sẵn sàng, Khoá treo hoặc Cách ly) > Khôi phục về worker > Xác nhận.

3. Chờ toast. ✓ = worker trả return: OK; thư mục GPM đã có dữ liệu.

Kết quả: Lần chạy không đổi (khôi phục không phải push). Nếu pull có lease, dòng chuyển Đang chạy cho tới khi push/nhả.

16.2 Xử lý profile Khoá treo

1. Đọc tên máy ở cột Đang chạy ở (chữ vàng lease HẾT HẠN).

2. Kiểm máy đó trên Fleet Monitor B34 (http://172.16.10.220:20340): còn online không, còn tiến trình không.

3. Máy đang khởi động lại và sẽ chạy lại profile này ⇒ không làm gì: nó tự đòi lại lease (same-host reclaim).

4. Máy chết hẳn ⇒ ngăn kéo > Gỡ khoá treo > Xác nhận. Dòng về Sẵn sàng ở nhịp làm mới kế tiếp.

5. Cần chạy ngay ở máy khác ⇒ Khôi phục về worker (WP của máy mới) hoặc dùng lệnh lease steal + pull --lease (mục 11.6).

16.3 Chrome treo không phản hồi

1. Dòng đang Đang chạy nhưng workflow kẹt.

2. Ngăn kéo > Xử lý treo (đóng) > Xác nhận — worker nhận close_profile.

3. Chạy lại workflow. Workflow lỗi giữa chừng thì không push, nên NAS vẫn giữ bản tốt gần nhất.

LƯU Ý · KHÔNG DÙNG QUIT ALL

Lệnh Quit All giết luôn trình duyệt của các nhánh song song. Luôn đóng đúng một profile.



16.4 Cách ly một bản nghi hỏng rồi thay thế

1. Ngăn kéo > Xoá khỏi kho → Cách ly > Xác nhận xoá. Dòng chuyển Cách ly, bảng Cách ly hiện ra.

2. Chép quarantine/<id>.<ts>.tar về máy điều tra (qua SMB), xả ra, mở thử bằng Chrome.

3. Sửa được (thường là xoá file khoá SQLite/Singleton) ⇒ push đè bằng đúng id: profile_store.py --store … --host <máy> push <id> <thư_mục_tốt>.

4. Không cứu được ⇒ tạo profile mới bằng workflow (mang UID mới) hoặc Xoá hẳn id cũ.

5. profile_store.py --store … verify <id> — sha256 phải khớp manifest.

16.5 Bỏ hẳn một hay nhiều profile

1. Tick các dòng cần bỏ (hoặc dùng nút trong ngăn kéo cho một dòng).

2. Bấm Xoá hẳn (A1 (GPM)→A5 (WebProfile)), đọc danh sách gom theo WP.

3. Giữ nguyên hai ô Xoá VĨNH VIỄN và force ở trạng thái tắt, trừ khi có lý do rõ ràng.

4. Xác nhận. Theo dõi hộp tiến trình: mỗi WP một bước, ✓/✗ kèm số xoá n/m.

5. Đọc toast cuối: Đã xóa n/m — bỏ qua: … liệt kê profile bị hoãn và lý do (lock còn hạn, worker chết…).

16.6 Dọn dẹp định kỳ

1. Bấm nút chổi, đợi bắt mạch xong (khoảng 6 giây).

2. Giữ mặc định: Dọn MySQL bật, Chỉ đụng con dấu UID bật, chốt tuổi 24 giờ, các ô force tắt.

3. Xem trước, đọc từng thẻ. Mục nào lạ thì dừng lại điều tra.

4. Không có gì ⇒ nút Thực thi tự ẩn, bấm Đóng. Có việc ⇒ Thực thi dọn dẹp, đọc toast đã xóa n mục.

16.7 Cấp tên cho profile mới

# từ n8n / script (qua B37)

curl -s 'http://127.0.0.1:20370/uid/next?count=1'


# trên worker (công cụ)

python profile_store.py --store \\172.16.20.201\lotus_profiles uid peek # xem số kế tiếp, không cấp

python profile_store.py --store \\172.16.20.201\lotus_profiles uid next --json



Chương 17 — Vận hành hằng ngày

17.1 Kiểm tra năm phút mỗi sáng

#

Nhìn vào

Bình thường

Bất thường ⇒ làm gì

1

Đèn NAS

xanh

đỏ ⇒ mục 20.1

2

cập nhật kho: … trước

vài phút / vài giờ khi có workflow chạy

nhiều ngày ⇒ không có push nào: worker tắt hoặc workflow dừng (tình trạng ngày 18/09)

3

Ô Khoá treo

0

> 0 ⇒ quy trình 16.2

4

Ô Cách ly

0

> 0 ⇒ đọc lý do, quy trình 16.4

5

Chip Thiếu file

không có

có ⇒ Dọn dẹp (xem trước) hoặc push lại

6

Thống kê Ngày

có cột hôm nay

trống cả tuần ⇒ kiểm worker và n8n



17.2 Đọc số cho đúng

17.3 Dùng API cho script giám sát

B=http://127.0.0.1:20360/api

curl -s $B/health # {ok, mounted, store, server_time}

curl -s $B/summary | jq .summary # đếm theo trạng thái

curl -s "$B/profiles?status=stale-lock" | jq '.profiles[].id'

curl -s "$B/profiles?wp=WP3&q=UID_0000" | jq .count

curl -s $B/locks | jq '.stale_lock[] | {id, host: .stale_lock.host}'

curl -s "$B/runs?limit=20" | jq '.runs[-1]'

curl -s $B/workers | jq '.workers[] | {work_pool, alive, rtt_ms}'



Đầu ra thật của /api/summary lúc biên soạn:

{"ok": true, "mounted": true, "updated": "2026-08-15T02:49:24Z",

"summary": {"total": 2, "running": 0, "idle": 2, "quarantined": 0,

"stale_lock": 0, "missing": 0, "size_bytes": 10383360},

"work_pools": {"WP3": {"total": 2, "idle": 2, ...}}}



MẸO · CẢNH BÁO TỰ ĐỘNG GỢI Ý

Một cron trên HP1 gọi /api/locks mỗi 10 phút và gửi cảnh báo khi stale_lock khác rỗng, hoặc khi manifest.updated cũ hơn 24 giờ trong ngày làm việc — đó là hai dấu hiệu sớm nhất của worker chết.



17.4 Làm sạch sổ lịch sử khỏi dữ liệu demo

Thao tác ghi thẳng lên kho, cần mount RW. Sao lưu trước, lọc các dòng demo (id bắt đầu p_WP), ghi đè:

cp /mnt/lotus_profiles/_index/runs.jsonl /mnt/lotus_profiles/_index/runs.jsonl.bak-$(date +%Y%m%d)

grep -v '"id": "p_WP' /mnt/lotus_profiles/_index/runs.jsonl.bak-$(date +%Y%m%d) \

> /mnt/lotus_profiles/_index/runs.jsonl

wc -l /mnt/lotus_profiles/_index/runs.jsonl # còn 56 dòng thật (18/09/2026)



LƯU Ý · KIỂM ĐỊNH DẠNG KHOÁ TRƯỚC KHI LỌC

Mẫu grep phải khớp đúng cách ghi ("id": "…" có khoảng trắng sau dấu hai chấm như dòng thật ở mục 4.3). Chạy grep -c với mẫu đó trước; số dòng bị loại phải xấp xỉ số sự kiện demo.



Đã đếm ngày 18/09/2026: đúng 776 dòng có id p_WP… (máy WP1_PC1, WP1_PC2, WP2_PC3, WP2_PC4, WP3_PC5, WP3_PC6 — những tên máy không có thật), còn 56 dòng thật đều là UID_… từ WP3-PC1. Việc lọc chưa thực hiện: nó ghi đè dữ liệu dùng chung trên NAS nên chờ người phụ trách duyệt. Khi làm, có thể chạy ngay trong B37 (đã mount RW tại /nas) thay vì mount tay.



PHẦN VI

AN TOÀN, SAO LƯU & RỦI RO





Chương 18 — An toàn và sao lưu

18.1 Cái gì nằm ở đâu, mất thì lấy lại từ đâu

Thành phần

Nằm ở

Mất thì

Dữ liệu profile

profiles/<id>.tar trên NAS2

không có bản thứ hai ngoài A2 trên worker (nếu còn) — cần snapshot NAS

Bản đã cách ly

quarantine/

như trên

Bản đã Xoá hẳn (mềm)

_deleted/<id>.<ts>.purge.tar

chép ngược về profiles/<id>.tar, rồi push lại manifest bằng push

Hàng MySQL đã xoá

_deleted/mysql/<id>.<ts>.json

INSERT lại theo nội dung JSON

Sổ cái

_index/manifest.json (+ .bak)

dùng manifest.json.bak; hoặc push lại từng profile

Sổ lịch sử

_index/runs.jsonl

chỉ ảnh hưởng thống kê

Mã B36/B37

repo git riêng trong từng thư mục + bind-mount

git của thư mục đó; image dựng lại được

Cấu hình

docker-compose.yml, .env, Caddyfile

.env không có trong git — tự sao lưu riêng



NGUY HIỂM · KHO CHÍNH CHƯA CÓ BẢN SAO THỨ HAI ĐƯỢC XÁC NHẬN

Tài liệu nguồn không ghi nhận lịch snapshot hay replication nào cho share lotus_profiles trên NAS2. Nếu chưa có: bật Snapshot Replication của DSM cho share này (theo giờ, giữ vài ngày). Đây là lớp duy nhất cứu được khi chính NAS2 hỏng hoặc có người xoá tay trên share.



18.2 Trang công khai phải đăng nhập

Tới chiều 18/09/2026, khối Caddy profiles.lotus1104.synology.me { reverse_proxy lotus_profile_dashboard:8080 } không có basic_auth, trong khi cổng 443 của WP1 forward vào Caddy (đã ghi nhận khi mở B43 ra Internet). Bất kỳ ai biết tên miền đều xem được danh sách profile, tên máy, và — kể từ khi có B37 — gọi được POST /api/command/purge với hard: true.

Tối cùng ngày, khối site được thêm basic_auth (mẫu ở mục 6.6). Đã kiểm: không kèm tài khoản trả 401; có tài khoản trả 200 cho cả trang lẫn /api/*; route n8n và cổng LAN 20360 không bị ảnh hưởng. Tài khoản nằm ở .env cạnh compose (B36_WEB_USER, B36_WEB_PASS); Caddyfile chỉ giữ băm bcrypt.

Hình 18.1. Trang công khai sau khi đăng nhập bằng tài khoản basic_auth (chụp 18/09/2026).

Việc

Cách làm

Đổi mật khẩu

sửa B36_WEB_PASS trong .env; tạo băm mới bằng docker exec lotus_caddy caddy hash-password --plaintext '<mật khẩu>'; thay băm trong khối site; docker restart lotus_caddy

Script gọi API qua tên miền

thêm -u "$B36_WEB_USER:$B36_WEB_PASS" vào curl; trong LAN thì gọi thẳng http://172.16.10.220:20360 (không qua Caddy, không hỏi mật khẩu)

Siết thêm (tuỳ chọn)

chỉ cho LAN/VPN: @ngoai not remote_ip 172.16.0.0/16 114.114.114.0/24 + respond @ngoai 403; hoặc chặn riêng /api/command/* từ ngoài



LƯU Ý · CỔNG 20360 KHÔNG CÓ MẬT KHẨU

Đăng nhập chỉ đặt ở Caddy. Cổng 20360 của HP1 vẫn mở trong LAN/VPN không mật khẩu — chấp nhận được chừng nào cổng này không được forward ra Internet trên router.



18.3 Mật khẩu và quyền

18.4 Các lưới an toàn có sẵn — đừng gỡ

Lưới

Ở đâu

Chống

Cách ly thay vì xoá

delete_nas, purge mặc định

xoá nhầm bản gốc

Worker trước, kho sau

purge

mất bản NAS khi worker chết giữa chừng

Hoãn khi worker chết

purge + bắt mạch 6 s

kho sạch mà A1/A2 còn rác

Con dấu UID

managed_only của Dọn dẹp

xoá profile người dùng tự tạo

Ba điều kiện janitor + lớp unsafe

dọn A2

xoá thư mục có data chưa cứu

Chốt tuổi 24 giờ

max_age_h

xoá phiên vừa đóng mà push còn thử lại

Xem trước bắt buộc

Dọn dẹp bước 1

thực thi mù

Lease + từ chối push chéo

profile_store.py

hai máy ghi đè nhau

Upload nguyên tử

incoming/ → os.replace

người đọc thấy gói dở



Chương 19 — Rủi ro và thảm hoạ

Kịch bản

Dấu hiệu trên B36

Xử lý

Worker mất điện giữa phiên

Khoá treo sau ≤ 1 TTL

16.2; dữ liệu phiên dở không được push — NAS giữ bản cũ

NAS2 tắt / VPN WP1–WP2 đứt

đèn NAS đỏ, dải báo lỗi

20.1; worker cũng không push/pull được — workflow sẽ lỗi

B37 tắt

mọi nút báo B37 không phản hồi

docker compose up -d lotus_profile_broker; bảng vẫn xem được

B36 tắt

trang không mở

không ảnh hưởng worker/kho; bật lại là xong

Hai máy cùng ghi (clobber)

không thấy trực tiếp — số Lần chạy nhảy lạ

nguyên nhân thường là gỡ khoá khi máy kia còn sống. Tìm bản tốt trong A2 của máy kia, push lại

n8n ghi đè bản vá workflow

profile mới mang tên LOTUS_<ip> thay vì UID

Phụ lục B; hàng rào nay ở worker

Người lạ bấm nút trên trang công khai

profile biến mất khỏi bảng

18.2; lấy lại từ _deleted/ nếu chưa bật hard

Share bị xoá tay trên DSM

mọi thứ trống

chỉ cứu được bằng snapshot NAS (18.1)



NGUY HIỂM · THAO TÁC KHÔNG HOÀN TÁC ĐƯỢC

Ba thứ duy nhất trên giao diện có thể làm mất dữ liệu vĩnh viễn:

▸ Xoá hẳn với ô Xoá VĨNH VIỄN bật.

▸ Dọn dẹp với ô Xoá cả thư mục A3 (NAS) không biết id (force) bật.

▸ Gỡ khoá treo khi máy giữ vẫn đang chạy (dẫn tới ghi đè).





PHẦN VII

KHẮC PHỤC SỰ CỐ





Chương 20 — Bảng tra sự cố

20.1 Đèn NAS đỏ — Kho NAS2 chưa mount được

docker exec lotus_profile_dashboard ls /nas/_index/manifest.json

docker volume inspect dcp_production_nas2_profiles_ro

ping -c2 172.16.20.201 # NAS2 qua VPN WP1-WP2



Nguyên nhân

Dấu hiệu

Sửa

NAS2 tắt / VPN đứt

ping không tới

khôi phục mạng; B36 tự thấy lại khi SMB lành

Sai tài khoản RO trong .env

ls /nas báo Permission denied

sửa .env rồi xoá và tạo lại volume: docker compose down lotus_profile_dashboard, docker volume rm dcp_production_nas2_profiles_ro, docker compose up -d lotus_profile_dashboard

Share đổi tên / chưa init

/nas trống hoặc thiếu _index

profile_store.py init bằng tài khoản RW

Mount CIFS ôi (sau khi NAS khởi động lại)

ls treo lâu hoặc Host is down

docker compose restart lotus_profile_dashboard; không được thì tạo lại volume như dòng 2



GHI CHÚ · VÌ SAO ĐỔI .ENV MÀ VOLUME VẪN DÙNG MẬT KHẨU CŨ

Volume Docker ghi lại tuỳ chọn mount lúc tạo. Đổi .env không làm volume đổi theo; phải xoá volume (dữ liệu nằm trên NAS nên không mất gì) rồi để compose tạo lại.



20.2 Mọi nút đều báo lỗi

Thông báo (toast)

Nghĩa

Làm gì

B37 không phản hồi: …

B36 không gọi được B37 (HTTP 502)

docker ps xem lotus_profile_broker; curl :20370/health

worker không phản hồi (timeout)

B37 gửi MQTT nhưng không ai trả lời

Process Manager trên máy WP đó tắt, sai topic, hoặc hai instance tranh nhau (Phụ lục C, D)

worker_offline trong phản hồi

bắt mạch 6 s thất bại, lệnh nặng không được gửi

bật Process Manager rồi thử lại

worker báo NG / Unsupported function …

worker nhận nhưng không có hàm đó

worker chạy code cũ — deploy lại (Phụ lục C)

Chưa biết workpool → không gửi được lệnh cần worker

profile có WP ?

đặt LOTUS_WORK_POOL hoặc đặt tên máy có chữ WPn; Gỡ khoá và Xoá khỏi kho vẫn dùng được

Lỗi mạng khi gửi lệnh

trình duyệt không tới được B36

kiểm mạng/Caddy

Trình duyệt hỏi tên/mật khẩu; script nhận 401

đi qua tên miền công khai (basic_auth ở Caddy)

nhập tài khoản trong .env (B36_WEB_USER/PASS); script thêm curl -u, hoặc gọi thẳng :20360 trong LAN



20.3 Bảng không khớp thực tế

Triệu chứng

Nguyên nhân

Cách xử lý

Tạo profile trong GPM mà bảng không có

tạo tay không đi qua worker ⇒ không push

tạo bằng workflow (create_profile) hoặc push tay

Profile hiện WP?

manifest work_pool rỗng và tên máy không khớp mẫu

đặt LOTUS_WORK_POOL trên worker; hoặc cập nhật profile_store.py (bản 18/09 tự suy từ tên máy WPn)

Thời lượng 0s, Cache 0/50 MB

node Push không truyền duration, actual_cache_mb

mục 8.1

Xoá hẳn xong dòng vẫn hiện thành Cách ly

bug cũ trước 14/08 (purge bỏ quên quarantine/)

đã sửa: purge dọn cả quarantine/ sang _deleted/ (Phụ lục E)

Một profile Đang chạy rất lâu

phiên dài có heartbeat — bình thường

chỉ lo khi workflow thật sự kẹt (16.3)

Biểu đồ có cột lớn tháng 7–8 dù ít profile

776 sự kiện demo còn trong runs.jsonl

mục 17.4

cập nhật kho nhiều ngày trước

không có push nào

worker tắt hoặc workflow dừng; kiểm B34 và n8n



20.4 Dọn dẹp không dọn

Triệu chứng

Nguyên nhân

Cách xử lý

Thư mục A2 vẫn còn sau khi dọn

mới hơn chốt tuổi 24 giờ (lớp young)

đặt ô tuổi = 0 nếu chắc chắn

Thư mục ghi unsafe

dữ liệu chưa được push an toàn

không bao giờ tự xoá — push trước, rồi dọn

Entry GPM chỉ báo cáo

tên không mang con dấu UID

xoá tay trong GPM nếu chắc chắn

Hàng MySQL mồ côi còn email

email ↔ IP là dữ liệu nghiệp vụ

bật Xóa cả hàng MySQL CÒN email nếu chắc

WP không được tick sẵn

B37 chưa có topic, hoặc worker không trả lời 6 s

mục 7.6 / bật Process Manager

Không có nút Thực thi

kế hoạch rỗng

đúng thiết kế



20.5 Phía worker

Triệu chứng

Nguyên nhân thật đã gặp

Cách xử lý

pull đúng dữ liệu nhưng Chrome mở thư mục rỗng

xả vào <base>/<id> trong khi GPM mở <base>/<mã>-<ngày>

đã sửa: pull hỏi GPM profile_path; đường lui là --into

push/pull rc≠0 dù đã upload xong

stdout cp1252 ném UnicodeEncodeError khi in ký tự ✔

đã sửa trong profile_store.py (ép stdout UTF-8)

Lệnh mở profile báo Browser debug request failed … refused

truyền remote_port khi test tay

đừng truyền remote_port cho open_profile khi thử bằng tay

Workflow cũ tạo profile tên LOTUS_<ip>

bản vá n8n bị một lần Save ghi đè

Phụ lục B; hàng rào nay ở worker





PHẦN VIII

THỰC HÀNH





Chương 21 — Bài thực hành

Sáu bài, từ chỉ-đọc tới có ghi. Bài 1–3 làm được ngay trên hệ thật; bài 4–6 cần một profile thử nghiệm và một worker đang chạy.

Bài 1 — Đọc trạng thái kho bằng mắt và bằng API

Mục tiêu: Đối chiếu con số trên giao diện với API.

1. Mở trang, ghi lại năm ô tổng hợp và dòng cập nhật kho.

2. curl -s :20360/api/summary — so từng số.

3. Mở ngăn kéo một profile, so sha256 (16 ký tự) với manifest.json qua /api/profiles/<id>.

Đạt khi: mọi số trùng khớp; giải thích được vì sao Thời lượng bằng 0.

Bài 2 — Lọc, tìm, sắp xếp

Mục tiêu: Thành thạo thanh lọc.

1. Lọc theo WP3, rồi theo Sẵn sàng; bấm ô Đang chạy để thấy bảng rỗng.

2. Tìm theo tên máy WP3-PC1.

3. Sắp theo Chạy gần nhất giảm dần; tạm dừng làm mới; đổi nền sáng.

Đạt khi: chụp màn hình từng bước, bộ đếm (n) đúng số dòng.

Bài 3 — Xem trước Dọn dẹp

Mục tiêu: Hiểu bắt mạch và kế hoạch mà không xoá gì.

1. Bấm nút chổi, chờ bắt mạch; ghi lại trạng thái từng WP.

2. Xem trước với mặc định. Ghi số trong dòng tổng.

3. Đóng — không bấm Thực thi.

Đạt khi: giải thích được vì sao WP bị bỏ tick và vì sao không có nút Thực thi (nếu kế hoạch rỗng).

Bài 4 — Vòng đời một profile thử nghiệm

Mục tiêu: Thấy pull/push/lease hiện lên B36.

1. Tạo profile bằng workflow (tên để trống ⇒ nhận UID_…).

2. Chạy Pull (lease bật) — trên B36 dòng chuyển Đang chạy, cột Đang chạy ở đếm lùi.

3. Close rồi Push có điền duration và actual_cache_mb — dòng về Sẵn sàng, Lần chạy tăng.

Đạt khi: biểu đồ Ngày có cột hôm nay; Thời lượng khác 0.

Bài 5 — Mô phỏng máy chết và gỡ khoá

Mục tiêu: Luyện quy trình 16.2 an toàn.

1. Với profile thử nghiệm: Pull (lease, TTL 60 s) rồi tắt Process Manager bằng tay.

2. Chờ tới khi dòng chuyển Khoá treo; kiểm B34 thấy máy offline.

3. Gỡ khoá treo; dòng về Sẵn sàng. Bật lại Process Manager.

Đạt khi: không có hai máy nào ghi cùng lúc; lease status sau cùng báo không có lock.

Bài 6 — Cách ly rồi xoá hẳn có đường lui

Mục tiêu: Hiểu cách ly, _deleted/ và sao lưu MySQL.

1. Xoá khỏi kho → Cách ly profile thử nghiệm; đọc bảng Cách ly.

2. Xoá hẳn (không bật VĨNH VIỄN); dòng biến mất.

3. Trên NAS (mount RO), tìm _deleted/<id>.<ts>.purge.tar và _deleted/mysql/<id>.<ts>.json.

Đạt khi: chỉ ra được nơi lấy lại cả dữ liệu lẫn hàng MySQL.



PHẦN IX

PHỤ LỤC





Phụ lục A — Biên bản kiểm tra vận hành ngày 18/09/2026

Trước khi biên soạn, hệ được kiểm từ đầu tới cuối để trả lời câu hỏi: trang còn kết nối được NAS không, và profile đẩy lên/kéo về thế nào? Trình tự và kết quả:

#

Kiểm

Lệnh

Kết quả

1

Container B36

docker ps --filter name=lotus_profile_dashboard

Up 13 days

2

Trang công khai

curl -w '%{http_code}' https://profiles…/

200 · 0,74 s

3

Mạng tới NAS2

ping 172.16.20.201

0 % mất gói, ~6,5 ms

4

Đọc kho qua B36

ls -la /nas/profiles trong container

2 file .tar, cùng 5 191 680 byte

5

Chống bộ đệm

mount mới hoàn toàn bằng tài khoản RO, cache=none

tên, cỡ, sha256 trùng khớp mount của B36

6

Toàn vẹn

sha256sum so manifest.json

khớp cả hai

7

Nội dung gói

tar tf

455 mục — cấu trúc profile Chrome

8

Khoá / upload dở

ls _locks incoming quarantine

đều trống

9

Lịch sử pull/push

/api/state của B34

118 sự kiện pull/push, tất cả từ WP3/PC_1; 33 lỗi đều ngày 10/08

10

Worker

/api/workers; ping 172.16.30.101

lúc kiểm: không phản hồi MQTT trong 6 s; máy vẫn bật (SSH/RDP mở). Tối 18/09 đã bật lại Process Manager: alive: true, rtt_ms: 640



LƯU Ý · KẾT LUẬN

Trang và NAS ổn; dữ liệu nguyên vẹn. Nhưng không có push/pull nào từ 15/08 vì Process Manager trên WP3-PC1 không chạy. Đã bật lại tối 18/09/2026 qua SSH (tác vụ lên lịch /it vào phiên RDP 2 của meomay22): hai tiến trình Python ở phiên 2, kết nối 128.199.200.17:1883 ESTABLISHED, B34 thấy WP3 / PC_1 online. Việc còn lại: chạy một vòng pull → mở → push để xác nhận cả tuyến.



Phụ lục B — Hệ ID UID và bài học bản vá n8n bị ghi đè

Bối cảnh (14/08/2026). ID cũ là UUID của GPM — vô nghĩa, không tra được, không có số thứ tự. Hệ chuyển sang UID_xxxxxx_GPM_Profile, và node Parse IP + Proxy của workflow Lv1RQyThF9U9cT8Q được vá để để trống tên (trước đó tự đặt LOTUS_<ip>).

Sự cố. Vá qua API lúc 14:44, chạy đúng hai lần; tới 16:45 một cú Save trong trình soạn thảo n8n ghi đè lại bản cũ — bốn profile ra đời tên LOTUS_<ip>, .tar và MySQL mang UUID, không một dòng lỗi. Bấm Test workflow là n8n tự lưu trước khi chạy, nên một tab mở từ trước hay một lần Ctrl+Z là đủ xoá bản vá.

Truy vết. Bảng workflow_history trong database.sqlite của volume n8n (cột authors, createdAt, nodes) và workflowData trong từng execution cho thấy đúng mốc đổi.

ĐÚNG THIẾT KẾ · BÀI HỌC

Đừng coi n8n là chỗ đặt hàng rào. Hàng rào nay nằm ở worker: create_profile bỏ qua mọi tên caller gửi và luôn xin UID (trừ tên đã đúng chuẩn hoặc keep_name=true); xin UID lỗi thì không tạo. Đã thử thật: gửi LOTUS_103_249_99_99 → ra UID_000005_GPM_Profile.



Bẫy đi kèm. Khi đổi hệ ID, phép so "A3 có biết profile này không" phải đối chiếu cả UUID lẫn tên-mang-con-dấu. Chỉ so UUID thì mọi profile đời mới thành "mồ côi" và Dọn dẹp đề nghị xoá sạch A1. Có test hồi quy riêng (verify/test_profile_identity.py).

Phụ lục C — Unsupported function push_profile — hai instance và code gitignored

Triệu chứng (10/08/2026). Node C12 Pull/Push trả Unsupported function push_profile in group ManagerAction — 33 lượt lỗi còn ghi trong Fleet Monitor. File trên đĩa worker đã có hàm mới (findstr _handle_push_profile thấy).

Nguyên nhân thật. Hai Process Manager cùng chạy và cùng subscribe một topic. Instance cũ vẫn giữ code cũ trong RAM và trả lời trước. Lần khác, hàm nghiệp vụ đã ở file base (GPM_multi_remote_browser_manager.py) nhưng lớp định tuyến (GPM_multi_remote_browser_process_manager.py, cái GUI thật sự import) chưa nối — yêu cầu bị chặn trước khi tới base.

ĐÚNG THIẾT KẾ · QUY TRÌNH DEPLOY CODE GITIGNORED SANG WORKER

so sha256 ba file → scp → xoá __pycache__ → khởi động lại đúng một instance → probe từ HP1 bằng mqtt_send.py. Hết Unsupported, trả OK mới tính là xong. .bat nay có khoá một instance (PowerShell giết PM cũ trước khi mở).



Phụ lục D — Topic MQTT: WP3-PC1, không phải WP2_PC176

Topic thật của worker WP3-PC1 là WP3-PC1 (xác minh 13/08/2026 trong config đang chạy GPM_multi_remote_browser_process_manager_gui_config.json > values.subscribe_topics). WP2_PC176 là topic cũ thời điều tra C12 tháng 7 — vẫn còn trong vài tài liệu, trong mặc định của docs/tools/mqtt_send.py và của chính mã Process Manager (SUBSCRIBE_TOPICS = ["WP2_PC176"]).

LƯU Ý · TIMEOUT KHÔNG CÓ NGHĨA MÁY CHẾT

Kiểm topic trước, rồi kiểm số instance. B37 chỉ gửi tới topic trong B37_WP_TOPICS.



Khởi động lại Process Manager từ xa (đã kiểm 13/08): taskkill /PID <pid> /T /F rồi tạo scheduled task chạy .bat với /it (GUI Tkinter cần phiên tương tác) — cần người dùng đang đăng nhập.

Phụ lục E — Bug Xoá hẳn và vùng quarantine; các khoản nợ kiến trúc

Bug (14/08/2026). Bản đầu của purge chỉ nhìn profiles/ và đẩy bản mềm sang quarantine/. Hậu quả: profile đang Cách ly bị xoá mỗi cái sổ, file vẫn nằm trong quarantine/ và B36 dựng lại dòng từ thư mục — người dùng bấm xoá mà thấy không mất; còn profile vừa purge lại hiện thành Cách ly.

ĐÚNG THIẾT KẾ · QUY TẮC RÚT RA

quarantine/ là thứ người dùng thấy; _deleted/ là thứ họ đã bảo bỏ đi. Đừng trộn hai vùng.



Nợ

Vì sao để nợ

Máy trắng / A1 bị xoá: GPM create sinh ID mới, không nhận ID cũ

99 % ca không cần; cần remap ID ở tầng NAS — đại phẫu

config.json chưa mang danh tính GPM (tên/proxy/core/version)

nhánh tạo lại A1 hiện dựng từ {}

Fencing token trong _locks

chỉ thêm khi thấy clobber TOCTOU thật

Janitor --apply + lịch chạy

đang DRY-RUN; cần đọc profile_janitor.log vài ngày trước khi bật

Topic WP1/WP2 chưa khai cho B37

chưa xác minh topic thật



Phụ lục F — Lỗi phát hiện khi biên soạn và tình trạng sửa

Cột cuối ghi tình trạng sau đợt sửa tối 18/09/2026. Mã B36 nằm ở kho git riêng githublotus/B36_Profile_Dashboard (commit 5a5d6cc); bản sửa profile_store.py ở repo chính (commit e21af2b).

#

Lỗi

Bằng chứng

Cách sửa · tình trạng

F1

Lệnh CLI trong ngăn kéo sai cú pháp

chạy thử trên kho tạm: unrecognized arguments: --dir --host H, --dest, --host H

--host lên trước lệnh con; pull <id> <dest>; push <id> <dir> trong app.js và /help. Đã sửa, bảy lệnh chạy thử đều rc=0

F2

Thanh chọn-nhiều luôn hiện một dải trống

CSS .bulk-bar{display:flex} đè hidden

thêm .bulk-bar[hidden]{display:none!important} vào style.css. Đã sửa

F3

README.md B36 và /help còn ghi "chỉ đọc", "tất cả API chỉ đọc"

có /api/command/* từ 13/08

ghi rõ ranh giới B36 đọc / B37 ghi trong README, /help, chân trang, mô tả OpenAPI. Đã sửa

F4

runs.jsonl còn 776 sự kiện demo

đếm trực tiếp trên kho

lọc theo mục 17.4. Chưa làm — ghi đè dữ liệu dùng chung, chờ duyệt

F5

Lịch sử ghi wp rỗng, dur 0, cache 0

dòng thật cuối trong runs.jsonl

wp: đã sửa — profile_store.py tự suy từ tên máy (cần git pull trên worker). dur/cache: node Push phải truyền duration, actual_cache_mb — chưa đổi

F6

TTL mặc định lệch nhau: 1800 / 300 / 180 s

profile_store.py, .bat, GUI

thống nhất 300 s (khớp placeholder node C12). Giữ nguyên — đổi mặc định ảnh hưởng mọi workflow đang chạy

F7

Trang công khai không xác thực nhưng có nút ghi

Caddyfile dòng 70; WP1 forward 443

basic_auth ở Caddy. Đã sửa — 401 khi thiếu tài khoản (mục 18.2)

F8

Mật khẩu MySQL viết thẳng trong compose

khối lotus_profile_broker

B37_MYSQL_PASS=${B37_MYSQL_PASS} + .env, tạo lại B37. Đã sửa

F9

Chú giải biểu đồ thiếu màu xám (?)

renderLegend() chỉ vẽ WP1–WP3

thêm mục WP? (chưa biết). Đã sửa



F.10 Sự cố xảy ra khi chụp ảnh GUI Process Manager

Để chụp tab NAS, mã GUI của Process Manager được chạy trên màn hình X của B35 cùng một file cấu hình riêng trỏ MQTT về 127.0.0.1:1 (không tới đâu). Nhưng __init.py của APP_5 tự chdir về gốc repo, và GUI tìm cấu hình theo thư mục hiện hành — nên nó nạp file GPM_multi_remote_browser_process_manager_gui_config.json thật ở gốc repo, tự kết nối broker 128.199.200.17:1883, subscribe topic WP2_PC176 và gửi heartbeat lên Fleet Monitor khoảng mười giây mỗi lần chạy.

Hậu quả

Kiểm chứng

Tình trạng

Một máy lạ WP3 / NUC (hostname NOVNC_CHROME) xuất hiện trên B34

/api/state → pools

đã gỡ ngày 18/09/2026 bằng POST :20340/api/clear_pc (work_pool: WP3, pc_name: NUC)

Subscribe topic cũ WP2_PC176 ~10 s

log GUI

không nhận/trả lệnh nào; worker WP3 nghe WP3-PC1

5 file logs/faulthandler/*.log ở gốc repo

git status

đã xoá; git status sạch

File config thật ở gốc repo

mtime vẫn 26/07/2026

không bị ghi đè



NGUY HIỂM · BÀI HỌC: CHẠY GUI APP_5 NGOÀI WORKER

Đặt config cạnh kịch bản là không đủ — __init.py đổi thư mục làm việc. Muốn chạy cô lập phải chặn mạng của tiến trình (ví dụ docker run --network none) hoặc chạy trên bản sao repo có config riêng ở gốc. Và GUI luôn tự kết nối 300 ms sau khi mở.



Phụ lục G — API của B37 Profile Broker

Địa chỉ: http://127.0.0.1:20370 (host) hoặc http://114.114.114.37:8080 (mạng Docker). Chỉ LAN/VPN.

Phương thức

Đường dẫn

Việc

GET

/health

locks_active, wp_topics, trạng thái MySQL

GET

/lease/{id} · /locks

đọc lock của một profile / mọi lock

POST

/command/restore

MQTT restore_profile → worker

POST

/command/kill_chrome

MQTT close_profile → worker

POST

/command/release_lock

xoá _locks/<id>.lock (B37 tự làm)

POST

/command/delete_nas

chuyển sang quarantine/ (cần confirm: true)

POST

/command/cleanup

dọn theo chuẩn A3; apply:false = xem trước

POST

/command/purge

xoá hẳn 1..n profile ở mọi nơi

POST

/command/ping

bắt mạch một WP

GET

/workers

bắt mạch song song mọi WP (mặc định 6 s)

GET

/mysql/rows

sổ A4 + phân loại theo A3

GET

/uid/next?count=N

cấp tên UID_… (1–100)

GET

/profile/{id}/verify

cổng "đã an toàn trên NAS" (sha256)



Định dạng lệnh MQTT (great-lotus-pyauto-v1): action là object {key, group, function}, inputs là dict có kiểu {name: {type, value}}, phản hồi trên RES_<topic> mang lại meta.requestId. Sai dạng thì worker trả NG: Missing object action.

Phụ lục H — Biến môi trường phía worker

Biến

Mặc định

Ý nghĩa

LOTUS_PROFILE_NAS_SYNC

0

bật hook tự pull trước khi mở / push sau khi đóng

LOTUS_PROFILE_STORE

\\172.16.20.201\lotus_profiles

gốc kho

LOTUS_GPM_PROFILE_DIR

(trống)

thư mục gốc GPM giữ folder profile

LOTUS_GPM_PROFILE_GLOB

{id}

mẫu tên folder con; {id}-* nếu có hậu tố ngày

LOTUS_PROFILE_HOST

hostname

tên máy cho lease và lịch sử

LOTUS_PROFILE_LEASE_TTL

xem mục 2.3

hạn lease (giây)

LOTUS_PROFILE_LEASE_RENEW_SEC

TTL/3

nhịp gia hạn (sàn 10 s khi đặt bằng biến)

LOTUS_WORK_POOL

(trống ⇒ giữ giá trị cũ, rồi suy WPn từ tên máy)

WP ghi vào manifest và lịch sử

LOTUS_JANITOR_DELETE_UNKNOWN

0

janitor được xoá thư mục không map được id



Phụ lục I — Thuật ngữ

Thuật ngữ

Nghĩa trong sách

A1…A5

năm nơi của một profile: GPM · C_Temp · NAS · MySQL · WebProfile (mục 1.3)

AIMD

Additive-Increase / Multiplicative-Decrease — cách profile_store.py tự chỉnh trần cache

Cách ly (quarantine)

tách bản lỗi khỏi kho chính, vẫn giữ để điều tra

Con dấu UID

tên UID_xxxxxx_GPM_Profile — dấu hiệu profile do hệ quản lý

Heartbeat

luồng nền gia hạn lease mỗi TTL/3 giây khi profile đang mở

Khoá treo (stale-lock)

lease đã hết hạn nhưng file lock còn

Lease

khoá phiên có hạn: _locks/<id>.lock

Mồ côi

bản ghi/khoá/hàng/thư mục không còn liên kết với profile nào trên NAS

Same-host reclaim

máy tự đòi lại lease của chính nó ngay khi khởi động lại

Snapshot (API)

ảnh chụp trạng thái kho do /api/snapshot trả về

Topic

kênh MQTT của một worker; phản hồi trên RES_<topic>

Upload nguyên tử

ghi incoming/…tmp rồi os.replace sang profiles/

Workpool (WP)

một địa điểm/mạng: WP1, WP2, WP3



Phụ lục J — Nguồn và cách dựng tài liệu

Loại

Nguồn

Mã B36

B36_Profile_Dashboard/app/server.py, store_reader.py, static/index.html, app.js, style.css, help.html, manual.html

Mã B37

B37_Profile_Broker/README.md, app/server.py

Công cụ kho

docs/tools/profile_store.py (trợ giúp từng lệnh con được chạy thật)

Worker

Library/B05_GPM_Login_Manager/profile_nas_sync.py; GUI APP_5 (mục NAS)

n8n

GreatLotus.node.ts của C12 (ACTION_SPECS, ACTION_SUMMARIES)

Hạ tầng

docker-compose.yml, .env (chỉ tên khoá), B16_Caddy/Caddyfile, docs/network/

Kinh nghiệm

sổ ghi nhớ vận hành 05/08 → 18/09/2026

Số liệu sống

API B36/B37/B34, docker, ping, mount RO độc lập — 18/09/2026



Ảnh chụp bằng Chrome thật trên B35 (1600×900) qua Selenium, toạ độ phần tử lấy bằng getBoundingClientRect() rồi đánh số tự động; mọi lệnh ghi bị chặn ngay trong trình duyệt trừ Dọn dẹp ở chế độ xem trước. Sơ đồ vẽ bằng matplotlib. Sách dựng bằng python-docx (doc_kit), PDF bằng LibreOffice, bản HTML đăng qua web tài liệu HDID. Mã dựng: tmp/b36_gt/ (cap*.py, run_annotate.py, diagrams.py, c*.py, build.py).