維運與管理層
本頁說明 AutoControl 在 2026 年 4 月強化週期(第 22–29 輪)所加入的
維運層。每個功能都是 headless-first:每項都附 Python API、可在 JSON
動作腳本中使用的 AC_* executor 指令、可透過 HTTP 取用的 REST 端點,
以及在需要視覺互動時提供的 Qt GUI 分頁。
統一目標:讓 AutoControl 不依賴桌面 GUI 也能執行,可作為 daemon 部署在 遠端機器上並集中管理。
資料夾同步(增量鏡像)
以輪詢方式運作的資料夾鏡像,透過既有的遠端桌面檔案 channel 把新增與 修改過的檔案推送到對端。同步是 增量唯一 — 不會把本地刪除與重新命名 傳出去,因此即使在編輯途中啟用同步也不會默默破壞遠端內容。
Headless:
from pathlib import Path
from je_auto_control.utils.remote_desktop.file_sync import FolderSyncEngine
engine = FolderSyncEngine(
watch_dir=Path("/home/me/notes"),
sender=lambda local_path, remote_name: my_send(local_path, remote_name),
poll_interval_s=3.0,
include_subdirs=False,
)
engine.start()
...
engine.stop()
行為:
start()時建立初始快照但 不 傳送 — 既存檔案視為已同步。每個 tick 掃描資料夾;
mtime較快照新的檔案會被傳送。傳送失敗會在下一個 tick 重試(快照只記錄成功的傳送)。
本地刪除會停止追蹤但不會呼叫 sender。
GUI:WebRTC viewer 分頁中的 Folder sync 群組,含資料夾選擇器與啟動/ 停止按鈕。
coturn TURN 設定包
產生可部署的 coturn 設定,使用者可自架 TURN 中繼而不必付錢給服務商。 輸出四個檔案:
turnserver.conf— coturn 設定coturn.service— systemd unit 檔docker-compose.yml— 單容器部署(host 網路模式)README.txt— 含turn:/turns:URL、使用者名稱、密鑰的 快速參考
Headless:
from pathlib import Path
from je_auto_control.utils.remote_desktop.turn_config import write_bundle
write_bundle(
Path("./turn-bundle"),
realm="turn.example.com",
user="alice", secret="HUNTER2",
listen_port=3478, tls_port=5349,
tls_cert="/etc/letsencrypt/cert.pem",
tls_key="/etc/letsencrypt/key.pem",
external_ip="203.0.113.5",
)
CLI:
python -m je_auto_control.utils.remote_desktop.turn_config \
--realm turn.example.com --user alice \
--secret HUNTER2 \
--tls-cert /etc/letsencrypt/cert.pem \
--tls-key /etc/letsencrypt/key.pem \
--output-dir ./turn-bundle
若省略 --secret,會自動產生 32 字元的 secrets.token_urlsafe。
強化版 REST API
REST API 圍繞三個面向重建:bearer token 認證、稽核軌跡、以及 per-IP 速率限制。
認證閘道
除了
/health與/dashboard之外,所有端點都需要Authorization: Bearer <token>標頭。Token 為 URL-safe 隨機字串;以
secrets.compare_digest做常數 時間比較。Per-IP token bucket:每分鐘 120 次、burst 30。
失敗認證追蹤:60 秒內 8 次錯誤 token →
locked_out(回 429); 鎖定為 per-IP,不會誤殺其他使用者。
Headless:
from je_auto_control.utils.rest_api import (
RestApiServer, generate_token,
)
server = RestApiServer(host="127.0.0.1", port=9939, enable_audit=True)
server.start()
print("Bearer:", server.token)
CLI:
python -m je_auto_control.utils.rest_api --host 127.0.0.1 --port 9939
端點清單
唯讀(GET):
/health(未認證) — 存活檢查/screen_size— 目前螢幕解析度/mouse_position— 目前滑鼠座標/sessions— 遠端桌面 host + viewer 狀態/commands— 已註冊AC_*executor 指令清單/jobs— 排程任務清單/history— 最近執行紀錄/screenshot— base64 PNG 截圖/windows— 作業系統視窗清單(目前僅 Windows)/audit/list— 最近稽核紀錄(可篩選event_type、host_id、limit)/audit/verify— 雜湊鏈完整性檢查(見 稽核紀錄雜湊鏈)/inspector/recent//inspector/summary— WebRTC 統計/usb/devices— 連接的 USB 裝置/diagnose— 系統診斷報告/metrics— Prometheus 格式(text/plain)/dashboard— 網頁管理介面(HTML;JS 從 sessionStorage 讀 token)
動作(POST):
/execute— body{"actions": [...]}— 執行動作清單/execute_file— body{"path": "..."}— 執行 JSON 動作檔
Executor 指令:
AC_rest_api_start, AC_rest_api_stop, AC_rest_api_status
GUI:REST API 分頁 — 啟動/停止、host/port 輸入、稽核 checkbox、 複製 URL/token 按鈕。
Prometheus 指標
REST 伺服器在 /metrics 輸出 Prometheus exposition v0.0.4。
指標家族(counter / gauge):
autocontrol_rest_uptime_seconds— gaugeautocontrol_rest_failed_auth_total— counterautocontrol_rest_audit_rows— gaugeautocontrol_active_sessions— gauge(host + viewer)autocontrol_scheduler_jobs— gaugeautocontrol_rest_requests_total{method,path,status}— counter
與其他端點一樣需要認證 — Grafana scraper 必須帶 bearer token。
Headless:
from je_auto_control.utils.rest_api.rest_metrics import RestMetrics
metrics = RestMetrics()
metrics.record_request("GET", "/health", 200)
print(metrics.render())
多主機管理主控台
管理主控台維護一份遠端 AutoControl REST 端點的通訊錄。輪詢透過
ThreadPoolExecutor 並行;廣播會把同一份動作清單對 N 個主機跑一遍
並回傳每台主機的結果。
Headless:
from je_auto_control.utils.admin import (
AdminConsoleClient, default_admin_console,
)
client = default_admin_console()
client.add_host(label="lab-01",
base_url="http://10.0.0.5:9939",
token="...", tags=["lab"])
for status in client.poll_all():
print(status.label, status.healthy, f"{status.latency_ms:.0f} ms")
results = client.broadcast_execute(
actions=[["AC_get_mouse_position"]],
)
持久化:主機儲存在 ~/.je_auto_control/admin_hosts.json(POSIX 上
模式 0600)。建構時自動 reload。
健康探測使用 /sessions``(已認證的端點),所以 token 錯誤的主機會
顯示為 ``HTTP 401 不健康狀態,而非誤導性的「可達但無用」。
Executor 指令:
AC_admin_add_host, AC_admin_remove_host, AC_admin_list_hosts,
AC_admin_poll, AC_admin_broadcast_execute
GUI:Admin Console 分頁 — 註冊主機表單、含健康/延遲/任務數欄位的 主機表、廣播文字框。
稽核紀錄雜湊鏈
稽核紀錄改成可偵測竄改:每筆紀錄儲存
SHA-256(JSON([prev_hash, ts, event_type, host_id, viewer_id, detail])),
形成鏈狀。修改任何過去的紀錄會改變該筆的 row_hash,便不再吻合
下一筆的 prev_hash — 在下次 verify_chain() 時就會看到。
Headless:
from je_auto_control.utils.remote_desktop.audit_log import default_audit_log
log = default_audit_log()
log.log("rest_api", host_id="127.0.0.1", detail="GET /health -> ok:200")
result = log.verify_chain()
print(result.ok, result.broken_at_id, result.total_rows)
雜湊鏈為「初次使用即信任」:在欄位加入前就存在的紀錄,會在啟動時依 插入順序回填。
REST 端點:
GET /audit/list?event_type=rest_api&limit=50
GET /audit/verify
Executor 指令:
AC_audit_log_list, AC_audit_log_verify, AC_audit_log_clear
GUI:Audit Log 分頁 — 篩選表單、可捲動的表格、Verify Chain 按鈕, 顯示「Chain OK (N rows)」或「Chain broken at row id X of N」。
WebRTC 封包監測
由 WebRTC 分頁產生的 StatsPoller 餵入的程序級 WebRTC
StatsSnapshot 滾動視窗。預設容量 600 筆樣本(在 1 Hz 下約 10 分鐘)。
Headless:
from je_auto_control.utils.remote_desktop.webrtc_inspector import (
default_webrtc_inspector,
)
inspector = default_webrtc_inspector()
summary = inspector.summary()
recent = inspector.recent(60)
summary() 對 rtt_ms、fps、bitrate_kbps、
packet_loss_pct、jitter_ms 各回傳 last/min/max/
avg/p95。
REST 端點:
GET /inspector/recent?n=60
GET /inspector/summary
Executor 指令:
AC_inspector_recent, AC_inspector_summary, AC_inspector_reset
GUI:Packet Inspector 分頁 — 摘要列、各指標滾動標籤、最近樣本表格、 1 秒自動更新。
USB 裝置列舉
唯讀的 USB 裝置列舉。優先嘗試 pyusb(透過 libusb 跨平台);若
pyusb 不存在則退回平台特定指令。
後端:
Windows:
Get-PnpDevice -PresentOnly -Class USB | ConvertTo-Json(從 InstanceId 解析 VID/PID)macOS:
system_profiler -json SPUSBDataType(遞迴走訪)Linux:
/sys/bus/usb/devices(讀取 sysfs)
Headless:
from je_auto_control.utils.usb import list_usb_devices
result = list_usb_devices()
print(f"backend={result.backend} count={len(result.devices)}")
for dev in result.devices:
print(f" {dev.vendor_id}:{dev.product_id} {dev.product}")
REST 端點:
GET /usb/devices
Executor 指令:
AC_list_usb_devices
GUI:USB Devices 分頁 — 後端標籤、裝置表格(VID/PID/廠商/產品/ 序號/位置)、重新整理按鈕。
Phase 2(真正的 USB passthrough)分階段發布 — 協定與 backend ABC 見 USB Passthrough — 第二階段設計,端到端使用方式見 USB Passthrough — 操作員指南,外部安全審查清單見 USB Passthrough — Phase 2e 安全審查清單。
USB hotplug 事件
輪詢式 USB add/remove 監測。對連續的 list_usb_devices() 快照以
(vendor_id, product_id, serial, bus_location) 為 key 比對;
產生 UsbEvent 推入 callback 與 bounded、帶序號的 ring buffer
(預設 500),讓晚加入的訂閱者可用 recent_events(since=seq) 補進度。
Headless:
from je_auto_control.utils.usb import default_usb_watcher
watcher = default_usb_watcher()
watcher.start()
...
for event in watcher.recent_events(since=0):
print(event["seq"], event["kind"], event["device"])
REST 端點:
GET /usb/events?since=<seq>&limit=<n>
Executor 指令:
AC_usb_watch_start, AC_usb_watch_stop, AC_usb_recent_events
GUI:USB Devices 分頁加上 Auto-refresh + watch hotplug 勾選, 勾起時啟動單例 watcher 並顯示最近數筆事件。
系統診斷
針對 AutoControl 各子系統「目前正常嗎?」的探測。每項檢查是個小函式,
回傳 Check(name, ok, severity, detail);runner 對每項分別 catch
例外,所以單一壞掉的探針不會污染其他項目。
內建檢查:
platform— OS 與 Python 版本optional_deps— 選用模組清單(aiortc、av、pyusb、pyaudio、 pytesseract、cv2、PySide6),提供已裝/缺少的明細executor— 已註冊的AC_*指令數audit_chain— 雜湊鏈完整性(使用verify_chain())screenshot— 實際擷取一張螢幕影像mouse— 讀取目前滑鼠座標disk_space— 使用者家目錄剩餘空間(<1 GB warn、<100 MB error)rest_api— registry 單例狀態
Headless:
from je_auto_control.utils.diagnostics import run_diagnostics
report = run_diagnostics()
for check in report.checks:
print(f"[{check.severity}] {check.name}: {check.detail}")
print("ok:", report.ok)
CLI:
python -m je_auto_control.utils.diagnostics
# 全綠 exit 0、否則 exit 1
REST 端點:
GET /diagnose
Executor 指令:
AC_diagnose
GUI:Diagnostics 分頁 — 執行按鈕、依嚴重度上色的結果表、摘要列。
網頁管理 dashboard
掛在 REST API 上的單頁瀏覽器介面。Vanilla JavaScript(無 build step)
— 頁面是 /dashboard 上的薄殼,提示使用者輸入 bearer token,
以 sessionStorage 快取,每 5 秒輪詢既有端點。
面板:診斷、sessions、inspector、USB 裝置、稽核紀錄尾段。
頁面本身未認證(純靜態 HTML/CSS/JS);所有資料呼叫都透過已認證端點
搭配使用者提供的 token。sessionStorage 在分頁關閉時清除,token
不會在瀏覽器重啟後存活。
Path-traversal 防護:asset loader 比對白名單正規式
^[A-Za-z0-9_][A-Za-z0-9._-]*$,並驗證 Path.resolve() 仍在
dashboard 目錄之下。.. 與 URL 編碼的變形都會回 404。
在任何瀏覽器開 http://<host>:9939/dashboard,貼上 REST API 分頁
裡的 bearer token,就有可在手機上使用的即時運維視圖。
OpenAPI 3.1 + Swagger UI
REST 伺服器把完整路由表以 OpenAPI 3.1 文件對外提供,外部工具 (client SDK 產生器、API explorer、合約測試)可直接消費。
REST 端點:
GET /openapi.json — spec 本體,需 auth
GET /docs — Swagger UI 殼,未認證
(JS 會跳出 bearer token 輸入框並注入到
try-it-out 請求)
Headless:
from je_auto_control.utils.rest_api.rest_openapi import (
build_openapi_spec, known_endpoints,
)
spec = build_openapi_spec(server_url="http://my-host:9939")
for method, path in known_endpoints():
print(method, path)
驅動 spec 的 metadata 對應放在 rest_openapi._ENDPOINT_METADATA,
與生成器同檔。CI 上有 drift 測試(test_every_route_has_metadata),
新加的 _GET_ROUTES / _POST_ROUTES 條目若沒有對應 metadata
會被擋下。
每個端點宣告 summary、query 參數、request body schema(POST)、預期
回應,並繼承全域 BearerAuth security scheme — public 路徑
(/health、/dashboard、/docs)以顯式 security: []
覆蓋。
設定包
對 ~/.je_auto_control/ 下使用者設定的單檔 JSON 匯出/匯入。
allowlist 涵蓋 8 個編碼了實際操作員偏好的檔案(admin hosts、
address book、trusted viewers、known hosts、host service,加上
持久化的 remote_host_id、viewer_id 與 host_fingerprint)。
稽核紀錄(audit.db)刻意 不 在 allowlist —— 從 bundle 還原
會破壞可偵測竄改鏈。
Headless:
from je_auto_control.utils.config_bundle import (
export_config_bundle, import_config_bundle,
)
bundle = export_config_bundle()
# ... 把 bundle 送到新機器 ...
report = import_config_bundle(bundle)
print(report.written, report.skipped, report.backups)
匯入是非破壞性的:要覆寫的東西先 rename 成 <name>.bak.<unix_ts>。
壞版本、未知檔名、path-traversal 嘗試都會被拒;bundle 與 allowlist
之間的 format 不一致(例如 allowlist 期望 json 但 bundle 給
text)會被略過。
CLI:
python -m je_auto_control.utils.config_bundle export <檔案>
python -m je_auto_control.utils.config_bundle import <檔案>
[--dry-run]
REST:
POST /config/export — 將 bundle 直接放在回應 body
POST /config/import — body 即 bundle dict
Executor 指令:
AC_config_export, AC_config_import
GUI:REST API 分頁的 Export Config / Import Config 兩顆按鈕, 都帶檔案對話框與覆寫確認。