# SOCKET 子系統 — 所有 EXE / BAT 用途說明（2026-06-10；2026-06-16 補：備援 Relay / 每日備份 / 推設定 / token 啟用）

> **2026-06-15**：浮動 Relay（**後已廢案**,見七）+ Relay token（見八）。
> **2026-06-16 更新**：① **備援 Relay**（公司第二台 active/standby,見九）② **每日自動備份**（FS/WS + Stk_BackupFS/WS.bat,見十）③ Commander **「推設定」CONFIG_PUSH**（見四）④ **token 正式啟用**（四個 client register 都補送 token,見八）。

> 網頁版遠端桌面（StkWebScreen）整體架構共四個角色：
> - **客戶站台**（被遠端的電腦）：跑 **StkAgent** + **pusher**
> - **SERVER**（中央主機）：跑 **Relay** + **StkWebScreen 橋接**
> - **操作端 PC**：跑 **StkCommander**（桌面控制台）
> - **手機/平板/瀏覽器**：只開網頁，不裝任何東西
>
> 資料流：客戶站台 pusher → 推 H.264 到 SERVER 的 Relay(9530)→ Relay 轉給 StkWebScreen 橋接(8080)→ 瀏覽器看畫面；操控指令反向走。

---

## 一、Relay（中繼伺服器）— 跑在 SERVER

> 部署目錄：`C:\Stk\Socket\Relay`；對外埠 **9530**。

| 檔案 | 類型 | 用途 / 功能 | 使用時機 |
|---|---|---|---|
| **relay_server.exe** | EXE（常駐） | 中繼核心：9530 接收所有 agent 推流、commander 指令，轉發給 viewer/橋接；A/B 雙槽（relay_server_A/B.exe）。**(0615) 可選 token 驗證**：讀同目錄 `relay_agent.ini` 的 `TOKEN`（預設註解＝全收；見八）。**亦可由站內 Agent 自選當 leader 時 spawn**（浮動 Relay，見七） | 24h 常駐(SERVER)；或浮動模式由 leader Agent 拉起 |
| **install_relay.bat** | BAT | **一鍵安裝**：複製 A/B + 防火牆 + 註冊「開機自啟(STK_Relay_Server)」+「每5分自癒(STK_Relay_Watchdog)」+ 立即啟動 | 每台 SERVER **首次裝一次**；之後重設自啟時 |
| **watchdog_relay.bat** | BAT | **自癒核心**：挑最新 A/B、確保只有一個實例在跑且 9530 在聽，否則 kill 全部→等乾淨→啟動一個（防雙開搶埠） | 由排程每 5 分呼叫（不手動跑） |
| **update_relay.bat** | BAT | 更新 relay 版本：把新 exe 複製到 A/B 槽，watchdog 自動切到最新 | 要升級 relay 程式時 |
| **start_relay.bat** | BAT | 手動啟動 relay（前景，**僅 dev/test**；生產用排程跑 watchdog_relay.bat）。浮動 Relay 的 leader Agent 也用它拉起本機 relay。token 啟用請改 `relay_agent.ini`（見八），非此 bat | 測試 / 臨時手動開 / 浮動模式 spawn |
| **run_relay.bat** | BAT | 同上，最精簡的啟動 | 快速手動開 |
| **uninstall_relay.bat** | BAT | 移除排程 + 防火牆規則 | 要卸載 relay 時 |
| **install_service.bat** | BAT | ⚠️ **已淘汰**：自動導向 install_relay.bat | 不要用，留相容 |
| **relay_standby.exe** | EXE（備援機常駐） | **(0615) 備援 relay active/standby 控制器**：跑在公司**第二台**(備援機)；每 20s ping 主 relay，主死(遲滯)→啟本機備援 relay、主活→停。任一時刻只一台 relay 服務 → Agent/Commander 不 split。見「九、備援 Relay」 | 備援機常駐(排程 STK_Relay_Standby) |
| **relay_standby.ini** | INI | relay_standby 設定：`PRIMARY_HOST`(主 relay 區網 IP)/`PRIMARY_PORT`/`LOCAL_DIR`/`CHECK_INTERVAL`/遲滯 | 備援機 |
| **install_relay_standby.bat** | BAT | 備援機安裝 standby：掛排程 `STK_Relay_Standby`(onstart)+`_Watch`(5分) + **移除本機正常 STK_Relay_Server/Watchdog**(否則 relay 24h 常開破壞 standby) + 防火牆 9530 | 備援機**首次裝一次** |

---

## 二、StkWebScreen（網頁橋接）— 跑在 SERVER

> 部署目錄：`StkWebScreen_portable`（執行中的本尊在這）；對內埠 **8080**，對外經 Cloudflare Tunnel。
> ⚠️ 橋接與 Relay 是**分開的程序**：重啟 Relay 時橋接不關，瀏覽器才看得到「維護中」提示。

| 檔案 | 類型 | 用途 / 功能 | 使用時機 |
|---|---|---|---|
| **Stk_WebScreen.exe** | EXE（常駐） | 網頁橋接：連 Relay 當 viewer 收 H.264，對瀏覽器提供 **操控頁 /m**（mpegts+WebSocket，低延遲）與 **純看頁 /h**（HLS，iPhone 通用）；含登入、畫質設定、剪貼簿、維護中提示 | 24h 常駐 |
| **cloudflared.exe** | EXE（常駐） | Cloudflare Tunnel：把內網 8080 用 HTTPS 網域對外發布（免開路由器埠） | 對外提供網頁服務 |
| **ffmpeg7.exe** | EXE | 64-bit ffmpeg：橋接把 Relay 串流 `-c copy` 切成 **HLS** 片段給 iPhone/通用裝置 | 純看頁 /h、iOS HLS fallback |
| **auto_start.bat** | BAT | 開機自啟：同時拉起橋接 + cloudflared tunnel | 設成開機自動執行 |
| **start_webscreen.bat** | BAT | 手動啟動橋接（不含 tunnel） | 本機測試 |
| **start_external.bat** | BAT | 啟動橋接 + 對外 tunnel | 手動開對外服務 |
| **allow_8080.bat** | BAT | 防火牆放行 8080 | 首次設定 / 連不進來時 |
| **setup_named_tunnel.bat** | BAT | 設定 Cloudflare 具名 tunnel（綁網域） | 第一次設定對外網域 |
| **install_named_tunnel_service.bat** | BAT | 把 cloudflared 裝成 Windows 服務（開機自跑 tunnel） | 要 tunnel 開機常駐時 |

---

## 三、StkAgent（站台代理）— 跑在「客戶站台」

> 部署目錄：客戶機本機（例：`C:\STK\S001`）；**必須在使用者登入的桌面 session 跑**（要抓桌面，不能做 Service/Session 0）。

### EXE
| 檔案 | 類型 | 用途 / 功能 | 使用時機 |
|---|---|---|---|
| **Stk_Agent_A/B.exe** | EXE（常駐） | 站台代理：連 Relay 註冊、收 START_STREAM 啟動 pusher、收 INPUT 操控桌面（移鼠/鍵盤/貼上）、心跳、檔案/剪貼簿；A/B 雙槽熱抽換 | 登入後常駐（使用者 session） |
| **pusher.exe** | EXE | 推流：抓桌面 → ffmpeg 編 H.264 → 推到 Relay；**32 位元站台自動用 gdigrab、64 位元才用 ddagrab(GPU)** | 由 agent 在 START_STREAM 時啟動 |
| **ffmpeg.exe** | EXE | 32-bit ffmpeg 4.2.2：pusher 用 **gdigrab** 擷取桌面（通用，所有站台可用） | pusher 呼叫（gdigrab） |
| **ffmpeg7.exe** | EXE | 64-bit ffmpeg 8.x：pusher 用 **ddagrab** GPU 桌面複製（CPU 低，**僅 64 位元站台**） | pusher 呼叫（ddagrab） |

### 安裝 / 自啟 / 維運 BAT
| 檔案 | 類型 | 用途 / 功能 | 使用時機 |
|---|---|---|---|
| **install_agent.bat** | BAT | **首次安裝/更新（互動）**：複製 A/B+pusher+ffmpeg、輸入 STATION/KMSN 寫 ini、防火牆、設**使用者 session 自啟**（啟動資料夾捷徑）、立即啟動 | 每台**首次裝一次**（要手動輸入站台資訊） |
| **install_agent_silent.bat** | BAT | **無互動安裝/更新**：讀現有 ini，不問任何問題；可遠端 EXEC/排程觸發 | 遠端批次部署、自動更新 |
| **watchdog_user.bat** | BAT | **使用者版自癒**：挑最新 A/B、確保單一實例、heartbeat 檢查、否則 kill→等乾淨→啟動一個 | 由 agent_autostart.vbs 每 60 秒呼叫 |
| **agent_autostart.vbs** | VBS | **登入自啟迴圈**：登入後常駐，每 60 秒隱藏視窗呼叫同目錄 watchdog_user.bat（自啟+自癒，且在桌面 session） | 放在「啟動資料夾」捷徑，登入即跑 |
| **setup_autostart_user.bat** | BAT | 把**既有安裝**轉成使用者 session 自啟：刪掉舊 SYSTEM 排程（Session 0 黑畫面）、建啟動捷徑、啟動 | 修「裝過舊版、想改正自啟」的機器 |
| **_restart_agent.bat** | BAT | 重啟 agent（A/B 切換部署用） | 部署新版後重啟 |
| **Stk_BackupFS.bat** | BAT | **(0616) 主機每日備份**（Agent 呼叫,非互動,新名稱不蓋舊 FSBackup.bat）：robocopy `/B` 把 `C:\STK`+`D:\images` 備到 `D:\Backup\<幾號>`+鏡 `E:\Stk`；`%1`=輪替資料夾；可自行編輯路徑。robocopy exit≥8 才算失敗 | Agent 每日自動呼叫（站號=S001 那台） |
| **Stk_BackupWS.bat** | BAT | **(0616) 工作站每日備份**（Agent 呼叫）：robocopy `/B` 把 `W:\`+`I:\` 備到本機 `D:\Backup\<幾號>`+鏡 `C:\Stk` | Agent 每日**輪值**呼叫（工作站每天輪一台,逆時遞補） |
| **uninstall_agent.bat** | BAT | 移除 agent + 相關排程 | 卸載站台代理 |
| **unblock_all.bat** | BAT | 解除 exe 的「封鎖」屬性（網路下載的 exe 被 Windows 擋） | 裝完被 SmartScreen/封鎖擋住時 |
| **watchdog_agent.bat / .vbs** | BAT/VBS | ⚠️ **已淘汰**：舊版 SYSTEM 排程自癒（會把 agent 開在 Session 0 → 黑畫面）。已由 watchdog_user 取代 | 不要用 |

---

## 四、StkCommander（桌面控制台）— 跑在「操作端 PC」

| 檔案 | 類型 | 用途 / 功能 | 使用時機 |
|---|---|---|---|
| **Stk_Commander.exe** | EXE | 桌面控制台：列出所有上線站台、遠端桌面（mpv 播 Relay 串流）、檔案傳輸、截圖、訊息、錯誤紅點通知、切站。**(0616)「推設定」按鈕**：一鍵把任一 ini key=value 推到選定站台（CONFIG_PUSH,Agent IniSetValue 只改該 key、保留其他;內建「備援位址」範本）。★真檔是 `stk_commander_qt.py`(PyQt5),非 `stk_commander.py`(舊 tkinter 已死) | 操作人員在自己 PC 操作 |
| **mpv.exe** | EXE | 影片播放器：Commander 用它播 Relay 的遠端桌面 H.264 串流 | Commander 開遠端桌面時呼叫 |

---

## 五、ErrorMonitor（錯誤監看）

| 檔案 | 類型 | 用途 / 功能 | 使用時機 |
|---|---|---|---|
| **ftp_error_monitor.exe** | EXE（常駐） | 每 30 秒掃 FTP 錯誤回報目錄，有新錯誤就推 **Telegram** 通知（含截圖） | 常駐（啟動資料夾），監看客戶端錯誤 |

---

## 六、建置工具（開發機）

| 檔案 | 類型 | 用途 / 功能 | 使用時機 |
|---|---|---|---|
| **_build_all_socket.bat** | BAT | **一鍵編譯四元件**（relay / webscreen / commander / agent），各自 build 後複製回來源目錄（避免上傳到舊 exe） | 改完源碼要出新版時 |
| **StkAgent/tools/_compile_screenshot.bat** | BAT | 重編 screenshot.c 的 .obj（截圖/剪貼簿 C 碼） | 改 screenshot.c 後 |

---

## 七、浮動 Relay 選舉（⚠️ 已廢案，程式碼已移除，僅留設計紀錄）

> 🚫 **本方案已作廢、agent 程式碼已移除**。原因：設計時誤以為 relay 要跑在客戶端,實際 relay 在公司端、客戶端只有 Agent 在 NAT 後（無法當公開 relay）→ 套錯場景。**正式的單點故障解法見「九、備援 Relay」（公司第二台 active/standby）。** 以下保留供設計參考。
>
> （原目標：取消「常駐單一 Relay」單點故障。同站 Agent 自選一台當 Relay；它死了剩下的自動重選。）

**開關**：`stk_agent.ini [Relay]` 的 `FLOATING_RELAY=1`（預設 `0`＝維持原固定 RELAY_HOST 行為，零回歸）。

**機制（租約檔選舉）**：
1. Agent 啟動讀 `W:\socket\relay_leader.json`：有健康 leader（TCP ping 得到）→ 當 client 連它的區網 IP。
2. 無 leader → 原子搶租約（`relay_leader.lock` 互斥建檔）→ 選上者 **spawn 本機 relay_server.exe**（經 `start_relay.bat`），每 3 秒續租（rewrite 租約讓 mtime 前進＝心跳）。
3. leader 死 → 租約 mtime 凍結逾 `LEASE_TTL`(10s) → 其他台重選（epoch+1）。

**ini 參數**（`[Relay]` 段）：
| key | 預設 | 用途 |
|---|---|---|
| `FLOATING_RELAY` | 0 | 1＝啟用站內自選 Relay |
| `RELAY_EXE_DIR` | C:\STK\Relay | 本機 relay_server.exe 佈署目錄（leader 從這拉起） |
| `LEASE_TTL` | 10 | mtime 凍結逾此秒數判 leader 死 |
| `LEASE_RENEW` | 3 | leader 續租間隔 |
| `LEASE_POLL` | 2 | client 輪詢間隔 |
| `LOCK_STALE` | 15 | 鎖檔殘留逾此秒數視為前手崩潰可清 |

**佈署位置（啟用浮動模式時）**：每台**客戶站台**的 `C:\STK\Relay\` 需放 `relay_server.exe`(A/B) + `start_relay.bat`（透過 stk_uploader 群組部署）。`W:\socket\` 需可寫（放租約檔）。

**遠端/跨站**：本期遠端 Commander、手機 WebScreen **維持連雲端 Relay 不變**（混合）；浮動 Relay 只服務站內。

**設計文件**：`doc/Floating_Relay_Election_Design_20260615.md`、`doc/Floating_Relay_Election_Detail_20260615.md`。

---

## 八、Relay 註冊 token 驗證（2026-06-16 已正式啟用）

> 目的：擋區網主機冒充 agent/commander 連 relay socket。register 握手帶 token，relay 比對。
>
> **現況（2026-06-16）：已啟用**。`relay_agent.ini TOKEN=stk2026`（小寫）；**所有 client 的 register 都已補送 token**：Agent / Commander(qt) / WebScreen 橋接 / stk_updater（uploader 不連 relay 不需要）。全部 token 值 `stk2026` 小寫一致,無大小寫問題。
> ★**教訓**：token 是「我加的新 relay 去讀 ini 既有的 optional TOKEN 才意外啟用的」,啟用後沒送 token 的 client（updater/webscreen register）會被 REJECT → 凌晨花很久才查出。**改 register-token 的 client 要四個都補**（agent/commander/webscreen/updater）。詳見各 client 的 register 段。

**啟用位置 = relay 機的 `relay_agent.ini`**（與 relay 讀 WEB_BRIDGE 同一個 ini，由 install_relay.bat 複製到 exe 同目錄）。
⚠️ **不是 start_relay.bat**！生產 relay 由排程 `STK_Relay_Server`/`STK_Relay_Watchdog` → `watchdog_relay.bat` 啟動，根本不跑 start_relay.bat。

**預設不強制**：`relay_agent.ini` 的 `TOKEN` key **預設註解掉**（`;TOKEN=stk2026`）＝全收（完全相容舊版、可漸進部署）。relay 讀 token 順序：env `STK_RELAY_TOKEN` > ini `TOKEN` > 空(不強制)。

**★ 啟用先後順序（務必照做，顛倒＝全部連線被 REJECT 鎖死）**：
1. **先** 部署「會送 token」的新版 client：`Stk_Agent.exe`、`stk_commander.exe`(Python)、（若仍用）Harbour commander。此時 relay ini TOKEN 仍註解＝全收，換版期間不斷線。
2. **全部 client 換完後**，才在 relay `relay_agent.ini` **取消註解** `TOKEN=stk2026`，重啟 relay（排程自啟，或 kill relay_server 讓 watchdog 5 分內拉起）→ 開始強制。
3. 浮動 Relay 模式：leader Agent 用 `start_relay.bat` 拉起本機 relay，relay 讀同目錄 `relay_agent.ini` 的 TOKEN，站內 Agent（已會送）正好對得上。

**涉及檔**：`relay_server.py`(驗證，`_load_token` 讀 ini)、`relay_client.prg`(送)、`stk_agent.prg`/`stk_commander_fw.prg`/`stk_commander.py`(呼叫帶 token)、`relay_agent.ini`(`TOKEN` 開關)。token 預設值 `stk2026`（與既有 UDP 指令 token 一致）。

---

## 九、備援 Relay（active/standby failover，2026-06-15 新增）

> 解決：公司唯一一台 RELAY_SERVER 掛了，所有客戶連不上。客戶端只有 Agent、在 NAT 後面（無法當公開 relay），所以備援必須放在**公開可達**處。本方案＝公司**第二台機器**當備援，**active/standby**（任一時刻只一台 relay 服務，避免 Agent/Commander 分落兩台互看不到）。

### 運作
- 備援機跑 `relay_standby.exe`：每 20s ping 主 relay。主死（連續 N 次）→ 啟本機備援 relay；主活→停。
- 同辦公室同一公開 IP，9530 只能 port-forward 一台 → **備援用第二對外埠**：路由器轉 `9531→備援機:9530`。
- 客戶端 failover 清單支援 `host:port`（`relay_client.prg`/`stk_commander.py` 已改）。

### 部署 runbook
**A. 備援機（公司第二台）**
1. 路由器：對外埠 `9531` → 備援機內網 `:9530`；防火牆放行 9530。
2. 放檔到 `C:\Stk\Socket\Relay`：`relay_server.exe`(會自動建 A/B) + `relay_standby.exe` + `relay_standby.ini` + `install_relay_standby.bat`；`relay_agent.ini` 的 token 狀態與主 relay **一致**。
3. 編輯 `relay_standby.ini`：`PRIMARY_HOST=` 主 relay 的**區網 IP**（同辦公室最可靠）。
4. 跑 `install_relay_standby.bat`（系統管理員）→ 掛 standby 排程、移除本機正常 relay 排程、防火牆。

**B. 客戶端（透過 per-key ini sync 推送）**
- Agent `stk_agent.ini`：`RELAY_HOST_ALT2 = asiaware.tplinkdns.com:9531`
- Commander：`RELAY_HOST_ALT = asiaware.tplinkdns.com:9531`
- （同一公開 DDNS + 第二外埠 → 主死時自動連到備援）

### 測試
kill 主 relay 機 → 備援機 standby 在 ~1 分內啟備援 → Agent + Commander **都**落到備援且彼此看得到 → 主復活 → 備援自停 → 客戶重連回主（自動收斂）。

### 限制（誠實標註）
1. 同辦公室備援只防「主機當機」；**停電/斷網兩台一起死**（要真備援得用不同網路/VPS）。
2. **串流**（pusher→relay 9531 stream）failover 未涵蓋，本方案只保命令通道（Commander↔Agent）。
3. 備援機是公司端設施，**手動部署**（不走 stk_updater）；客戶端 ini 的 ALT2 靠 per-key ini sync 推。

---

## 十、每日自動備份（2026-06-16 新增，Agent 內建，預設關閉）

> 自動化現有手動的 FS/WS 備份。三層：**主機每天 + 工作站輪流（線上日常救援）+ USB 手動氣隙（防勒索,絕不自動化）**。FS/WS 只防硬碟壞/誤刪/單機掛,**防不了勒索病毒**（線上備份會一起被加密）。

**運作**（由常駐的 Stk_Agent 在離峰時觸發）：
- **FS（主機）**：站號=S001 那台,每天備 → 跑 `Stk_BackupFS.bat`（C:\STK+D:\images → D:\Backup\<幾號>+鏡 E:\Stk）。
- **WS（工作站）**：每天「幾號 mod 在線工作站數」**輪一台** → 跑 `Stk_BackupWS.bat`（W:\+I:\ → 本機 D:\Backup\<幾號>+鏡 C:\Stk）；輪到的沒開機 → 逾時別台**逆時遞補**,不漏天。
- **SiteSingletonDaily**：W:\socket 原子認領（.run/.done 標記）保證整站只一台做、殘鎖逾10分可頂。
- **robocopy `/B`**：開啟中的鎖檔也備得到（解 STK32 沒退出 FAIL）；exit `≥8` 才算真失敗（0~7 皆成功,與 xcopy 相反）。
- **保留**：`<幾號>`(當月幾號)資料夾自動覆蓋,約 30 天；可改滾 N 天。

**INI（`stk_agent.ini [Backup]`,全可由 INI_EDITOR 設）**：
| key | 預設 | 用途 |
|---|---|---|
| `BACKUP_ENABLE` | 0 | 1＝啟用每日自動備份 |
| `BACKUP_ROLE` | auto | auto(S001=fs每天/其他=ws輪流) / fs / ws / off |
| `BACKUP_HOUR` | 2 | 幾點做 |
| `BACKUP_ROTATE` | day | day=當月幾號 / 數字N=滾N天 |
| `BACKUP_REQUIRE_IDLE` | 0 | 1=STK32在跑就跳過;0=照備(警示) |

> 來源/目的地/鏡像路徑**在 bat 內編輯**（`Stk_BackupFS.bat`/`Stk_BackupWS.bat`,新名稱不蓋原 FSBackup.bat/WSBackup.bat）。
> ⚠️ **USB 備份維持手動 + 備完拔掉**（離線氣隙=防勒索唯一保障）,絕不自動化。

---

## 速查：哪台跑哪些常駐程式

| 機器 | 常駐 EXE | 開機自啟方式 |
|---|---|---|
| **SERVER** | relay_server.exe、Stk_WebScreen.exe、cloudflared.exe | relay→排程(STK_Relay_Server/Watchdog)；橋接→auto_start.bat |
| **客戶站台** | Stk_Agent_A.exe（+ START_STREAM 時的 pusher.exe/ffmpeg）；**浮動模式**：被選為 leader 的那台另跑 relay_server.exe | 啟動資料夾捷徑 → agent_autostart.vbs（使用者 session）；relay 由 leader Agent 自動 spawn |
| **操作端 PC** | Stk_Commander.exe（操作時開） | 手動/捷徑 |
| **監看機** | ftp_error_monitor.exe | 啟動資料夾 |

---

## 重要觀念備忘

1. **agent / pusher 要抓桌面 → 只能在使用者 session 自啟**（啟動資料夾），**不能做 Service / SYSTEM 排程**（Session 0 = 黑畫面）。
2. **relay / 橋接無桌面 → 可放 SYSTEM 排程 / Service**（Session 0 OK）。
3. **32 位元站台**：pusher 會自動跳過 ddagrab/ffmpeg7（64-bit 跑不動，WinError 216），改用 gdigrab + 32-bit ffmpeg.exe。
4. **重啟 relay 時不要關橋接**：橋接活著，瀏覽器才會顯示「🔧 中繼站維護中，稍後自動恢復」並自動續播。
5. **(0615) 浮動 Relay 預設關**：`FLOATING_RELAY=0`。要啟用須先把 `relay_server.exe`+`start_relay.bat` 佈到各站 `C:\STK\Relay`、`W:\socket` 可寫，再設 `FLOATING_RELAY=1`。
6. **(0615) token 啟用務必先 client 後 relay**：先部署會送 token 的 Agent/Commander，**最後**才在 relay **`relay_agent.ini` 取消註解 `TOKEN=`**（不是 start_relay.bat；生產 relay 跑 watchdog_relay.bat）。顛倒＝全部 REJECT 鎖死。
