# STK 更新完整流程

## 環境

| 機器   | 電腦名稱  |   IP          | 角色                                  |
|--------|----------|---------------|---------------------------------------|
| 主機   | S001      | 192.168.0.22  | C:\STK\ = W:\，跑 stk_updater + Agent |
| 工作站 | SERVER    | 192.168.0.253 | 跑 relay_server + Agent，用 W:\S002   |
| 工作站 | S998      | 192.168.0.134 | Agent，用 W:\S003                     |
| 開發機 | W10PX64NB | 192.168.0.180 | 只負責 build                          |

---

## 完整時序

```
開發機(W10PX64NB)                S001(主機)                         工作站(SERVER/S998)
192.168.0.180                    192.168.0.22                       192.168.0.253 / .134
──────────────────────────────────────────────────────────────────────────────────────────

① build 各程式
   pack_and_upload.bat
   → 打包 EXE/SET/TOOLS/字型/ZIP/SOCKET
   → ftp_upload.py → FTP Server
   （大小相同跳過不重傳）

   FTP 目錄結構：
   /H/stk-update/update-32/files/
     ├── EXE/          STK32.exe, PrnAsk.exe, 字型, INI
     ├── SET/          DBF+CDX（成對）
     ├── TOOLS/        共用工具（遞迴子目錄）
     ├── SOCKET/
     │   ├── agent/    Stk_Agent.exe, install/uninstall BAT
     │   ├── relay/    relay_server.exe, BAT
     │   ├── commander/ Stk_Commander.exe
     │   ├── stk_updater.exe
     │   └── stk_updater.ini
     ├── WebView2.zip  (370MB)
     ├── HDOS4.zip
     └── GoToINI.BAT

                                 ② 手動執行 stk_updater.exe
                                    讀 stk_updater.ini
                                    連 FTP → 下載 manifest.json
                                    比對 local_version.json

                                 ③ 下載 ZIP（每次都做）
                                    WebView2.zip → C:\TEMP\UPDATE\
                                    HDOS4.zip    → C:\TEMP\UPDATE\
                                    （已存在則跳過，省 370MB）

                                 ④ 下載更新檔（僅有新版本）
                                    EXE\ SET\ TOOLS\ SOCKET\ 等
                                    → C:\TEMP\UPDATE\各子目錄
                                    逐檔用 FTP MDTM 比對日期，相同跳過
                                    下載後同步本地檔案日期 = FTP 日期

══════════════════ 以下 ⑤~⑧ 僅有新版本時執行 ══════════════════

                                 ⑤ 透過 Relay 發 SHUTDOWN           Agent 收到 SHUTDOWN
                                    連 Relay（192.168.0.253:9530） →    在 STK_PATH 建 FLG
                                    發 SHUTDOWN（target=全部）           例：W:\S002\SHUTDOWN.FLG
                                    離線站 → 直接建 FLG                  ↓
                                      C:\STK\S00X\SHUTDOWN.FLG      STK32 CheckShutdown()
                                                                     hb_idleAdd 每 5 秒偵測
                                                                     [60秒][延長][立即]
                                                                     延長最多 3 次
                                                                     歸零 → QUIT
                                 ⑥ 等所有站退出                         ↓
                                    輪詢 STK32.EXE 檔案鎖定      ←  STK32 退出前自刪 FLG
                                    timeout → Relay 發 KILL_STK  →  Agent 強殺 STK32
                                    清掉所有殘留 FLG

                                 ⑦ 備份
                                    C:\STK\ → C:\STK_YYYYMMDD_HHMM\

                                 ⑧ 分發到各站
                                    從 C:\TEMP\UPDATE\ 複製到 C:\STK\S001~S020
                                    ┌───────────────┬───────────────────────┐
                                    │ EXE + 字型    │ 覆蓋（INI 只新增）      │
                                    │ SET           │ 日期不同才覆蓋          │
                                    │ TOOLS         │ 根目錄一份（遞迴）      │
                                    │ FRM/LBL/MEM   │ 只新增不覆蓋            │
                                    │ DBF           │ 不碰                   │
                                    └───────────────┴───────────────────────┘
                                    → 儲存 local_version.json

══════════════════ 以下 ⑨~⑪ 每次都執行 ══════════════════

                                 ⑨ PUSH_AND_UNZIP 部署 ZIP
                                    （見下方詳細流程）

                                 ⑩ 部署 SOCKET
                                    從 C:\TEMP\UPDATE\SOCKET\ 複製到：
                                    ┌─────────────────────┬──────────────────────────┐
                                    │ 來源                 │ 目標                      │
                                    ├─────────────────────┼──────────────────────────┤
                                    │ stk_updater.exe      │ W:\（日期比對覆蓋）        │
                                    │ stk_updater.ini      │ W:\（只新增不覆蓋）        │
                                    │ agent/*              │ W:\socket\agent\          │
                                    │ relay/*              │ W:\socket\relay\          │
                                    │ commander/*          │ W:\socket\commander\      │
                                    └─────────────────────┴──────────────────────────┘
                                    所有比對皆用日期時間（不比大小）
                                    檔案鎖定（正在跑）→ rename .old 再 copy

                                    Agent 分發到各站：
                                    W:\socket\agent\Stk_Agent.exe
                                      → rename W:\S001\Stk_Agent.exe → .old
                                      → copy 新檔 → 刪 .old
                                      → rename W:\S002\Stk_Agent.exe → .old
                                      → copy 新檔 → 刪 .old
                                      → ...（每個存在的站）
                                    全部複製完後 → Relay 發 RESTART_AGENT
                                      → 每個 Agent 啟動新版自己 → 舊的退出

                                 ⑪ 清理暫存 C:\TEMP\UPDATE\
                                    ┌───────────────────────┬────────┐
                                    │ 保留                  │ 刪除    │
                                    ├───────────────────────┼────────┤
                                    │ WebView2.zip (370MB)  │ EXE\   │
                                    │ HDOS4.zip             │ SET\   │
                                    │ deploy_status.json    │ 其他   │
                                    │ TOOLS\                │        │
                                    │ SOCKET\               │        │
                                    └───────────────────────┴────────┘
```

---

## ⑨ PUSH_AND_UNZIP 詳細流程

```
對每個 ZIP（WebView2.zip, HDOS4.zip）分別執行：

S001(stk_updater)                                              工作站(Agent)
──────────────────────────────────────────────────────────────────────────────

⑨a 讀 C:\TEMP\UPDATE\deploy_status.json
    比對該 ZIP 的修改時間（mtime）
      時間變了 = 新版 → 清空 deployed，全部重送
      沒變    → 只送 deployed 以外的站

⑨b 連 Relay（192.168.0.253:9530）
    註冊為 UPDATER
    發 LIST_AGENTS
    → 取得在線站清單 + 各站內網 IP（agent_ip）

⑨c 過濾 pending 清單
    在線 且 不在 deployed 裡 → pending
      全部已部署   → 跳過此 ZIP，結束
      沒有在線站   → 跳過此 ZIP，結束
      agent_ip 為空 → 該站 skip（不送）

──────────── 逐站迴圈：每個 pending 站 ────────────

⑨d 發 PUSH_AND_UNZIP 指令
    透過 Relay 轉發到該 Agent
    參數：
      target    = 該站名（如 Server）
      dest      = C:\（解壓目標上層）              Agent 收到指令
      check_dir = C:\WEBVIEW2（檢查目錄）    ←─    檢查 check_dir
      zip_name  = WebView2.zip（存檔名）            │
                                                   ├→ 已存在 → 直接回 OK（不接收）
                                                   └→ 不存在 → 開 TCP port 9529 等待

⑨e 等 1 秒後 TCP 直連
    連 Agent 內網 IP:9529
    送 C:\TEMP\UPDATE\WebView2.zip             →   Agent 接收 ZIP
    （32KB chunks 串流傳送）                        建立 C:\TEMP\（不存在時）
                                                   存為 C:\TEMP\WebView2.zip
                                                   （30 秒 timeout）

                                                   Agent 解壓
                                                   hb_unzip C:\TEMP\WebView2.zip
                                                   → 解壓到 C:\
                                                   → ZIP 內含 WEBVIEW2\ 目錄
                                                   → 產生 C:\WEBVIEW2\
                                                   ZIP 保留在 C:\TEMP\（不刪）

                                                   Agent 驗證
                                                   check_dir 存在？
                                                   ├→ 是 → 回報 OK + dir_exists=true
                                                   └→ 否 → 回報 FAIL + dir_exists=false

⑨f 記錄結果                                   ←   回報結果
    OK + dir_exists=true  → 加入 deployed（已驗證）
    OK + dir_exists=false → 不加，下次重試（解壓失敗）
    FAIL/無回應           → 不加，下次重試
    no agent_ip           → skip，不送

──────────── 逐站迴圈結束 ────────────

⑨g 存 C:\TEMP\UPDATE\deploy_status.json
    更新 deployed 清單 + mtime

    範例：
    {
      "WebView2.zip": {
        "deployed": ["Server", "S998"],
        "mtime": "2026-05-21 15:01:41"
      },
      "HDOS4.zip": {
        "deployed": ["Server", "S998"],
        "mtime": "2026-05-21 15:01:41"
      }
    }
```

---

## STK32 Fallback（DeployZipCheck）

```
STK32.EXE 啟動時自動執行 DeployZipCheck()：

  C:\WEBVIEW2 存在？ ──→ 跳過
       │ 不存在
       ↓
  C:\TEMP\WebView2.zip 存在？ ──→ 解壓到 C:\ → 產生 C:\WEBVIEW2\
       │ 不存在
       ↓
  等下次 stk_updater 推送（無法自救）

  C:\HDOS4 同理
```

---

## Agent A/B 切換 + 安裝

```
W:\socket\agent\ 內容（暫存區）：
  Stk_Agent.exe        ← 最新版（stk_updater 部署的）
  install_agent.bat    ← 安裝/更新腳本
  uninstall_agent.bat  ← 移除腳本
  watchdog_agent.bat   ← 監控腳本
  watchdog_agent.vbs   ← 隱藏視窗啟動 BAT
  stk_agent.ini        ← 範本 INI

完整安裝（第一次）：
  雙擊 install_agent.bat
  → 輸入站名、STK_PATH、KMSN
  → 複製 Stk_Agent_A.exe + Stk_Agent_B.exe 到 W:\S00X\
  → 複製 watchdog_agent.bat + .vbs 到 W:\S00X\
  → 產生專屬 stk_agent.ini 到 W:\S00X\
  → Registry HKCU\Run → StkAgentWD → wscript watchdog_agent.vbs
  → 排程工作 STK_Agent_Watchdog 每 5 分鐘
  → 啟動 Stk_Agent_A.exe

更新（自動，推薦）：
  stk_updater 的 deploy_socket：
  → is_file_locked 判斷哪個在跑（A 或 B）
  → 複製新版到不在跑的那個
  → 發 RESTART_AGENT → Agent 啟動另一個 → 舊的退出

更新（手動）：
  install_agent.bat update
  → kill A + B + 舊 Stk_Agent.exe
  → 複製新版到 A + B
  → 啟動 A

安裝後工作站目錄：
  W:\S002\
    ├── STK32.EXE           ← 原本就在
    ├── Stk_Agent_A.exe     ← 版本 A
    ├── Stk_Agent_B.exe     ← 版本 B
    ├── stk_agent.ini       ← 各站專屬
    ├── stk_agent.log       ← 共用 log
    ├── watchdog_agent.bat  ← 監控
    └── watchdog_agent.vbs  ← 隱藏執行

Watchdog 流程：
  每 5 分鐘 → _A 在跑？跳過 → _B 在跑？跳過
  → 都沒跑 → 啟動日期較新的那個

注意：不要用系統管理員執行（W:\ 網路磁碟在 admin session 不可見）
注意：install 會 kill 三個（Stk_Agent.exe + _A + _B）清除舊版
```

---

## KMSN 區分（多客戶同名站共存）

```
問題：不同客戶有相同站名（如 S001）

Relay key：
  有 KMSN → "0077-1995:S001"（不同客戶不互踢）
  沒 KMSN → "S001"（向下相容）

指令目標：
  "*"           → 全部 Agent
  "S001"        → 所有叫 S001 的（不分 KMSN）
  "0077:S001"   → 特定 KMSN 的 S001

Commander FindStation：
  Pass 1：精確比對 name + kmsn
  Pass 2：模糊比對 name（空 KMSN 填入，不同 KMSN 跳過）
  沒找到 + 有 KMSN → 建新列
  沒找到 + 沒 KMSN → 不建列（RETURN 0）

Agent 必須在 INI 設定正確的 KMSN，否則無法區分。
```

---

## Relay 安裝

```
W:\socket\relay\ 內容：
  relay_server.exe     ← 最新版
  install_relay.bat    ← 安裝（工作排程器，開機自動）
  uninstall_relay.bat  ← 移除
  start_relay.bat      ← 手動啟動

在 SERVER（192.168.0.253）上執行 install_relay.bat
→ 需要系統管理員（工作排程器 SYSTEM 帳號）
→ 開機自動啟動，不需登入
```

---

## 比對方式

| 階段 | 比對方式 |
|------|---------|
| FTP 下載（EXE/SET/TOOLS/SOCKET） | MDTM 日期比對，下載後同步 mtime |
| ZIP 下載（WebView2/HDOS4） | 存在就跳過 |
| 分發到各站（EXE/SET/TOOLS） | 日期比對 |
| SOCKET 部署 | 日期比對 |
| Agent A/B 分發 | 日期比對 + is_file_locked 判斷 A/B |

---

## 已知斷點

| 情境 | 結果 | 解法 |
|------|------|------|
| Agent 在線 + C:\WEBVIEW2 不存在 | PUSH_AND_UNZIP 送 ZIP → 解壓 | ✅ 正常 |
| Agent 在線 + C:\WEBVIEW2 已存在 | 回 OK 跳過 | ✅ 正常 |
| Agent 在線但 agent_ip 為空 | skip，送不到 | 已修：連線後立刻發 heartbeat |
| Agent 離線 + C:\TEMP\ 有 ZIP | STK32 啟動時 DeployZipCheck 補完 | ✅ Fallback |
| Agent 離線 + C:\TEMP\ 沒 ZIP | 無法自救 | 等下次 stk_updater 推送 |
| stk_updater 跑完後 Agent 才上線 | 錯過推送 | 下次跑 stk_updater 補送 |
| deploy_status.json 記錄已部署但實際失敗 | 不會重送 | 已修：Agent 驗證 dir_exists |
| relay_server.exe 正在跑 | 部署 SOCKET 時 copy FAIL | log 提示，手動重啟 |
| Agent exe 被 SMB 鎖住 | A/B 切換：寫入不在跑的那個 | ✅ 完全避免 |
| backup 遇到 .old 檔 | 備份前清 .old + backup_ignore | ✅ |
| Agent 死掉 | Watchdog 每 5 分鐘偵測並重啟 | ✅ |
| 同名站不同 KMSN | Relay 用 kmsn:station 做 key | ✅ 共存 |
| 備份失敗 | WARNING 繼續，不中斷 Step 6-9 | ✅ |

---

## 檔案清單

| 檔案 | 位置 | 用途 |
|------|------|------|
| stk_updater.py/exe | HBSTK/build/update_server/ | SERVER 端更新主程式 |
| stk_updater.ini | 同上 + W:\ | 更新設定 |
| ftp_upload.py | 同上 | 開發端上傳 |
| pack_and_upload.bat | 同上 | 一鍵打包+上傳（含 SOCKET） |
| relay_server.py/exe | SOCKET/Relay/ | Relay 中繼通訊 |
| stk_agent.prg | SOCKET/StkAgent/source/ | Agent 主程式 |
| console.c | SOCKET/StkAgent/tools/ | Tray icon + 右鍵選單 |
| stk_commander_fw.prg | SOCKET/StkCommander/source/ | Commander GUI |
| shutdown.prg | HBSTK/compat/ | CheckShutdown + DeployZipCheck |
| install_agent.bat | SOCKET/StkAgent/build/ | Agent 安裝/更新 |
| install_relay.bat | SOCKET/Relay/ | Relay 安裝 |
| update_flow.md | SOCKET/doc/ | 本文件 |
| prn_to_pdf.prg | HBSTK/compat/ | PDF 輸出（含 BarcodeMap 條碼字型對照） |
| tlib_c.c | HBSTK/c_code/ | FreezeWindow（DispBegin/DispEnd + InvalidateRect） |
| fonts/*.TTF | PRNASK/build/fonts/ | 條碼字型（C39/EAN/Code128） |
| Barcode_Manual.pdf | PRNASK/doc/ | 條碼列印使用說明 |
| watchdog_agent.bat/vbs | SOCKET/StkAgent/build/ | Agent A/B 監控重啟 |
