現在 Agent 的「解壓目錄就是安裝目錄」,客戶刪掉解壓目錄=服務設定、憑證掛載點、CLI 全滅; 換版走新目錄還會撞固定 container_name 靜默失敗(CM-1533 實證)。
本案把 Agent 改成跟主產品同一套形態:
| 現在(0.2.32) | 改造後 | |
|---|---|---|
| 安裝目錄 | 解壓目錄(家目錄,隨版號變) | /srv/guidant-ai-agent(固定) |
設定檔 .env |
解壓目錄內 | /srv/guidant-ai-agent/.env |
| bind mount(content/certs-in/factory) | 解壓目錄內相對路徑 | /srv/guidant-ai-agent/ 下絕對路徑 |
| 安裝紀錄 | /etc/guidant-agent-compose/install.conf |
/etc/guidant-ai-agent/install.conf(改名對齊,並與原生版 /etc/guidant-agent/ 分家) |
| 安裝 log | 解壓目錄內(目錄一刪就沒) | /var/log/guidant-ai-agent/(FHS,活過 uninstall) |
| CLI | /usr/local/bin/guidant-agent-compose(內容是 install.sh 副本) |
同左(名字不改,見 D5) |
| compose project | 隨目錄名(...-0231 / ...-0232,會分裂) |
name: guidant-ai-agent(釘死) |
| volume 名 | guidant-agent-compose-0232_agent-db(隨目錄變) |
guidant-ai-agent_agent-db(固定) |
| 解壓目錄 | 🔴 請勿刪除 | ✅ 裝完可整個刪 |
四段工:① install.sh 改成「複製到固定目錄再啟動」;② compose 加 name: 釘 project; ③ 升級/舊形態遷移邏輯;④ installer ⑥ 補「容器實際 image tag == 本次要裝的 tag」判定。
/etc/guidant-agent-compose/install.conf
GUIDANT_AGENT_INSTALL_DIR=/home/guidantai/guidant-agent-compose-0.2.32
docker volume ls
guidant-agent-compose-0231_agent-certs ← 0.2.31 那套的憑證,孤兒
guidant-agent-compose-0231_agent-db
guidant-agent-compose-0231_agent-upload
guidant-agent-compose-0231_seaweed-data
guidant-agent-compose-0232_agent-certs ← 現役(憑證是 runner 手動從 0231 複製過來的)
guidant-agent-compose-0232_agent-db
guidant-agent-compose-0232_agent-upload
guidant-agent-compose-0232_seaweed-data
docker ps -a --format '{{.Names}}\t{{.Label "com.docker.compose.project"}}'
guidant-ai-agent guidant-agent-compose-0232
guidant-agent-seaweedfs guidant-agent-compose-0232
一台機器上有兩組 volume,其中一組(0231)是純孤兒但佔著磁碟;憑證是人手搬過去的。 這就是 CM-1533 事故的殘骸——不是特例,是現行設計在「換版」這個必然動作下的預設結果。
/etc/guidant-agent-compose/install.conf
GUIDANT_AGENT_INSTALL_DIR=/tmp/t66-final/guidant-agent-compose-0.2.28
docker ps | grep agent
zen_sutherland guidant-ai-agent:0.2.30 Up 7 days ← 連 compose 都不是,裸 docker run
guidant-agent-seaweedfs chrislusf/seaweedfs:3.99 Up 12 days
安裝紀錄指向 /tmp。/tmp 重開機清空 → 所有維運子命令(status/logs/upgrade/ uninstall)當場全滅,且錯誤訊息會是「這台主機看起來還沒安裝過」。這台是我們自己的 build 機、由熟悉系統的人裝的,尚且如此;客戶端只會更糟。
| # | 問題 | 後果 | 現行設計為何必然導致 |
|---|---|---|---|
| P1 | 解壓目錄=安裝目錄 | 客戶刪目錄 → .env/compose/bind mount 全沒,CLI 報「還沒安裝過」 |
write_config 直接 ENV_FILE="${BUNDLE_DIR}/.env"、INSTALL_DIR="$BUNDLE_DIR"(install.sh:1577,1624) |
| P2 | 安裝目錄在家目錄 | 帳號停用/家目錄配額/權限變更都波及生產服務;188 甚至落在 /tmp |
沒有固定落點,客戶解到哪就是哪 |
| P3 | compose project 隨目錄名 | 新版目錄=新 project → 撞固定 container_name → 靜默失敗(fallback 把「舊容器還在跑」讀成成功) |
compose 無 name:,project 取目錄名 |
| P4 | volume 名隨目錄名 | 新 project 產生一組空 volume → 憑證消失 → 雲端 409 拒絕重註冊 | 同 P3,named volume 前綴=project 名 |
| P5 | fallback 判定不看 image tag | 換版失敗被判成功,status 讀 .env 宣告值也顯示新版 |
dc_up() 只檢查 container_state == running |
| P6 | 完成畫面印「🔴 請勿刪除」 | 與客戶心智模型對抗——這是設計缺陷的訊號,不是可以用文案解決的事 | P1 的下游 |
P3~P5 是同一個事故的三個環節,任一個修掉都能讓 CM-1533 不再靜默;三個都修才叫解決。
/srv/guidant-ai-agent vs /opt/guidant-agent| 選項 | 論據 | 判斷 |
|---|---|---|
**A. /srv/guidant-ai-agent ⭐ |
主產品用 /srv/guidant-ai(GUIDANT_DATA_DIR 預設)。FHS:/srv 是「本機對外提供之服務的資料」——agent 的 .env、bind mount、log 正是服務資料。同族命名,客戶一眼看得出是同一家。 |
採用** |
B. /opt/guidant-agent |
FHS:/opt 是「附加軟體套件」。agent 的程式本體在 docker image 內,宿主上放的全是資料與設定,語意對不上。另外 /opt/guidant-ai-be//opt/guidant-ai-agent 已被我們自己的 build 機源碼目錄佔用(188),同名不同義會製造混淆。 |
不採用 |
決定:/srv/guidant-ai-agent,可用環境變數 GUIDANT_AI_AGENT_DIR 覆寫(比照主產品 GUIDANT_DATA_DIR)。
為什麼是
guidant-ai-agent而不是guidant-agent(2026-09-03 user 指正,設計初稿寫錯): 檔案系統路徑的既定慣例是 kebab-case 的guidant-ai(實查:/srv/guidant-ai67 處、/etc/guidant-ai8 處、imageguidant-ai-be/guidant-ai-fe/guidant-ai-agent)。 全 repo 只有一處寫成連寫的guidantai,那是網域名guidantai-spec.jedicotech.com——DNS 標籤不能有底線、大小寫不敏感,與檔案系統無關,不構成先例。 初稿的guidant-agent把ai吃掉了,與 image 名guidant-ai-agent不一致。🔴 而且
/etc/guidant-agent/這個名字已被佔用(本次改名連帶修掉的真問題): 原生 tarball 版 agent 的設定就住在那裡(scripts/install/install.sh:113CONF_DIR="${CONF_DIR:-/etc/guidant-agent}",內含agent.env)。兩種形態裝在 同一台機器時,compose 版的install.conf會與原生版的agent.env混在同一個目錄, 而uninstall清目錄時會掃到對方的設定。改成/etc/guidant-ai-agent/之後兩者 天然隔離。
連帶影響:compose project 名為 guidant-ai-agent,於是 volume 是 guidant-ai-agent_agent-db 等四個。project 名與 agent 容器的 container_name (guidant-ai-agent)同字——不同命名空間,技術上無衝突(docker ps 只是兩個欄位 顯示同一串字)。接受這個小重複,而不是為它去改 container_name:那個名字已寫進 10-troubleshooting.md 的實際指令裡,改它的成本遠大於美觀收益。
配套落點(全部比照主產品三分離):
/srv/guidant-ai-agent/ 資料與設定(客戶不需進去,但備份要含它)
├── .env 600,含註冊 token 與儲存憑證
├── docker-compose.yml 服務定義(升級時被新版覆蓋,舊版留 .bak)
├── content/ 檢測 profile(唯讀掛載)+ content/cache(可寫)
├── certs-in/ 雲端自簽憑證信任錨
└── factory/ 出廠儲存設定快照
/etc/guidant-ai-agent/install.conf 安裝紀錄(644,不含密碼)
/var/log/guidant-ai-agent/ install-*.log/upgrade-*.log(見 D8,755)
/usr/local/bin/guidant-agent-compose 維運 CLI(install.sh 副本,名字不改)
| 選項 | 論據 | 判斷 |
|---|---|---|
| A. install.sh 複製所需檔到固定目錄 ⭐ | 解壓目錄與 tar.gz 用完可刪,這才是客戶的心智模型。與主產品 install_ops_cli() 完全同構(它就是 cp compose → 資料目錄 + install guidant → /usr/local/bin)。 |
採用 |
B. 就地安裝+/srv/guidant-ai-agent symlink 指回去 |
symlink 只是把「不能刪」這個約束藏起來——刪了目錄,symlink 變 dangling,錯誤訊息比現在更難懂(No such file or directory 指向一個看起來存在的路徑)。且解壓目錄仍在家目錄,P2 未解。等於什麼都沒修。 |
不採用 |
| C. 搬移(mv)而非複製 | 客戶重跑安裝時來源沒了;且 tar.gz 還在,「來源不可變」比較好推理。差異只有 3.4GB image tar 的磁碟(那些本來就該刪)。 | 不採用 |
要複製的東西(來自解壓目錄):docker-compose.yml、content/(含 cache/)、 certs-in/(若安裝當下產生了 cloud-ca.pem)、.env.example、README.md。 不複製:*.image.tar(已 docker load 進 docker)、install.sh(改為 install 到 /usr/local/bin/guidant-agent-compose,同現行做法)、image-manifest.sha256、INSTALL.txt。
冪等:所有複製都是覆蓋寫;
content/用cp -rn(不覆蓋既有檔)——那裡面可能有 客戶自己投放的檢測 profile,升級時絕不可被包內的空目錄蓋掉。
deploy/docker-compose.yml 頂層加:
# project 名顯式釘住(FR-073)。不釘會取「compose 檔所在目錄名」,於是
# 每個版本的解壓目錄各自成為一個 project,而本檔用的是固定 container_name——
# 新 project 建容器時撞上舊 project 還佔著的名字,compose 報 Conflict,
# 而 installer 的 fallback 會把「舊容器還在跑」讀成安裝成功(CM-1533 實證:
# 190 換 0.2.32 時整次換版被靜默吞掉,跑的仍是 0.2.31 的 image)。
# 連帶:named volume 前綴也是 project 名,不釘的話換版會產生一組空 volume,
# 憑證消失 → 雲端以 409(設備指紋已被另一台在線 agent 使用)拒絕重新註冊。
name: guidant-ai-agent同時對齊主產品 docker/production/docker-compose.yml:93 的 name: guidant——同一個 理由、同一個寫法,不另外發明。
釘 project name 後 volume 自動變成 guidant-ai-agent_agent-db 等四個,不隨目錄變。 不額外用 external: true/name: 硬釘:那會讓 uninstall 的 down -v 失效 (external volume compose 不刪),反而製造「以為刪乾淨其實沒有」。
install.sh 開頭先判「這台機器現在是什麼狀態」,三分流:
| 狀態 | 判定依據 | 行為 |
|---|---|---|
| N. 全新 | /etc/guidant-ai-agent/install.conf 不存在 ∧ 舊紀錄不存在 ∧ /srv/guidant-ai-agent/.env 不存在 |
正常首裝:問四題 → 建 /srv/guidant-ai-agent → 複製 → 寫 .env → up |
| U. 已是新形態 | /srv/guidant-ai-agent/.env 存在(或新 install.conf 指向它) |
走升級語意:沿用 .env(只換 AGENT_IMAGE)、覆蓋 compose、cp -rn content、up -d 換版。volume 因 project 固定而完全沿用 |
| **M. 舊形態(家目錄/任意目錄) | 舊紀錄 /etc/guidant-agent-compose/install.conf 存在,且其指向目錄有 .env |
遷移**(見 D3-d) |
🔴 狀態 U 不再要求客戶記得打
--upgrade。現行設計裡「首裝 vs 升級」是靠客戶 選對指令來區分的,而 CM-1533 正是「客戶(我們自己)走了首裝路徑」造成的。改為 由機器狀態決定語意,客戶打什麼都不會壞。--upgrade與upgrade <包>仍保留 (行為等價,且upgrade <包>對「手上只有 tar.gz」的情境更順手)。
事故最容易發生的一段,故先停舊、再搬、再起新,且全程可回頭:
install.conf 取得 OLD_DIR;確認 OLD_DIR/.env 存在。--yes 或 --config 帶 MIGRATE_CONFIRM=1(非互動): 「偵測到舊形態安裝於 <OLD_DIR>,將搬到 /srv/guidant-ai-agent,資料 volume 完整保留」。docker inspect 現役容器的 com.docker.compose.project label, 不是猜目錄名——目錄名可能被改過)。docker compose -p <舊project> --env-file <OLD_DIR>/.env -f <OLD_DIR>/docker-compose.yml down (🔴 絕不帶 -v)——停舊容器並釋放 container_name。/srv/guidant-ai-agent,複製舊的 .env(保原權限 600)、content/、certs-in/、factory/; compose 用新版包內那份(含 name:)。guidant-ai-agent。用一顆掛兩個 volume 的臨時容器 cp -a 逐一搬(四個:agent-db/agent-certs/agent-upload/seaweed-data)。 已存在同名新 volume 且非空 → 停下報錯,不覆蓋。
為什麼要真的搬而不是叫客戶接受重新註冊:
agent-certs內是舊雲端 CA 簽的身分憑證, 丟了就要重新 enroll,而雲端會以 409(設備指紋已被另一台在線 agent 使用)拒絕 ——CM-1533 實際撞到,最後靠人手複製救回。這條路必須由程式走完,不能留給客戶。
/etc/guidant-ai-agent/install.conf),刪掉舊的 /etc/guidant-agent-compose/install.conf(留著會讓下一次執行又判成狀態 M)。<OLD_DIR> 已不再使用,可自行刪除」(不主動刪客戶的目錄)。docker compose -p <舊project> -f <OLD_DIR>/docker-compose.yml --env-file <OLD_DIR>/.env up -d 即可還原。🔴 舊 volume 不自動刪。搬完保留(
_migrated-<日期>不改名,就是原名留著), 完成畫面告知可手動清。理由同 uninstall 的資料 volume 政策:能被腳本自動觸發的 刪資料動作太危險,而磁碟成本遠低於「證據永久取不回來」。
dc_up() 現行的 fallback 只問「容器在不在跑」。補一層:
啟動後(wait_healthy 通過後)逐一斷言:
docker inspect --format '{{.Config.Image}}' guidant-ai-agent == $AGENT_IMAGE_TAG
不符 → fail(退出碼 5),訊息點名「容器跑的是 <實際>,本次要裝的是 <預期>」,
並提示最可能原因(另一個 compose project 的舊容器佔著同名 container)。
三個要點:
dc_up 之後、獨立成 assert_running_image(),不塞進 dc_up 內部—— dc_up 的職責是「容器有沒有被建起來」,image 對不對是另一件事,兩件混在一起下次還會漏。| 對象 | 改動 |
|---|---|
status/logs/start/stop/restart/fingerprint/configure-storage/re-enroll/upgrade/uninstall |
全部經 resolve_installed_paths(),只需改該函式的優先序:① 環境變數 GUIDANT_AI_AGENT_DIR ② 新 install.conf ③ 舊 install.conf(相容) ④ 預設 /srv/guidant-ai-agent。不再 fallback 到 $BUNDLE_DIR——那正是 P1 的來源 |
uninstall |
清 /srv/guidant-ai-agent(逐項,比照主產品的一級系統目錄防護 case 已存在)+ /etc/guidant-ai-agent/+ CLI;volume 仍逐項確認;同時清舊 /etc/guidant-agent-compose/ 殘留(🔴 不可誤清 /etc/guidant-agent/ —— 那是原生版 agent 的設定目錄)。/var/log/guidant-ai-agent/ 不刪(見 D8) |
CLI 名 guidant-agent-compose |
不改名。理由:已寫進 user-manual 五章、FR-072 影片、客戶肌肉記憶;改名的收益只有美觀。(/etc 路徑改名是必要的——它是內部路徑,且要與 /srv/guidant-ai-agent 同族才不會下次又找錯) |
reset-190.sh(test repo training/scripts/console/) |
探測與清理路徑加 /srv/guidant-ai-agent、/etc/guidant-ai-agent、/var/log/guidant-ai-agent;既有的 /etc/guidant-agent//opt/guidant-agent//var/lib/guidant-agent(原生版落點)保留 |
| 完成畫面 | 刪掉「🔴 請勿刪除」,改為「解壓目錄與 tar.gz 已可刪除」+列出實際安裝落點 |
evidence-agent repo
deploy/install.sh:write_config/resolve_installed_paths/run_upgrade/cmd_uninstall/ print_next_steps/install_ops_entry/檔頭說明段(usage() 直接印檔頭,改了要一併檢查 sed -n '3,91p' 的行號範圍)+新增 detect_install_state/migrate_from_legacy/ assert_running_image/stage_into_install_dirdeploy/docker-compose.yml:加 name: guidant-ai-agent;三處 bind mount 由 ./content 等 改為 ${GUIDANT_AI_AGENT_DIR:-/srv/guidant-ai-agent}/content(絕對路徑——compose 的相對 路徑基準是 compose 檔所在目錄,而該檔現在住在固定目錄,理論上等價;但寫絕對路徑可讓 「有人把 compose 檔複製到別處執行」不會靜默指到別的資料)deploy/README.md:安裝形態段全改pyproject.toml:版號 → 1.0.0(見 D7)scripts/build/build_compose_bundle.sh:幾乎不動——它只負責把 deploy/ 下的檔複製進 staging,落點改變發生在客戶端執行期。要確認的是 INSTALL.txt 的文案(第 363~395 行附近) 與 image-prepare 相容性檢查(第 163 行 grep)不受影響BE repo
docs/spec-site/current/user-manual/agent/03-install.md(第 50、187、237 行:請勿刪除、 安裝目錄範例、log 落點)docs/spec-site/current/user-manual/agent/07-upgrade.md(7.2 表格第③列「覆蓋到既有安裝目錄」 仍成立,但要補「首裝指令對已安裝機器等同升級」)docs/spec-site/current/user-manual/agent/05-operations.md(「不需要知道安裝目錄在哪」—— 這句本來就是承諾,改造後才真正成立,可留)docs/spec-site/current/user-manual/agent/08-uninstall.md、10-troubleshooting.md、 11-certificates.md:路徑字樣docs/spec-site/current/user-manual/onprem/10-optional-features.md §10.5test repo
training/scripts/console/reset-190.shFR-072 影片④(CM-1527,Agent 安裝):畫面上會出現「安裝目錄 /srv/guidant-ai-agent」與 「解壓目錄可刪」——與現有腳本敘述不同,需重錄。在 FR-072 卡上標記,不在本卡執行。
現行 0.2.32 → 1.0.0。
我原本寫 0.3.0,錯在沒查 repo 實際慣例就下判斷。 實查版號史:
0.2.16 → … → 0.2.28 → 0.2.29 → 0.2.30 → 0.2.31 → 0.2.32——中間那位從頭到尾 沒動過,連 re-enroll 子命令、SonarQube connector、心跳自報 capabilities 這些 真功能也都只 +1 patch。0.3.0 是這個 repo 從未發生過的跳法。 user 裁示:這版準備對外發佈,不該再掛 0.x,直接進 1.0.0。
🔴 改版號必須重跑完整 build,不能只重打包:版號在 Nuitka 編譯之前就烙進 config/version_bake.py(build_agent_release.sh Step 2,產物要一起編進 binary), 另外還烙在 binary 的 --product-version、image tag 與交付包檔名。 --compose-only 會沿用既有的 version_bake.py,出來的包會被命名成舊版號 ——CM-1533 那棒實際踩過。故一律走完整 --with-compose。
安裝 log 落點改為 /var/log/guidant-ai-agent/install-<ts>.log(升級為 upgrade-*.log)。 現行落在解壓目錄,目錄一刪 log 就沒了——而那正是回報問題時最需要的東西。
為什麼不比照主產品的
<資料目錄>/log/(2026-09-03 user 提案,經評估採納): 初稿抄了主產品,但主產品那個選擇有個它自己沒發現的缺陷——uninstall會刪資料目錄, 於是 install/uninstall 的 transcript 在最需要事後檢討的那一刻一起消失。/var/log/三個好處: ① FHS 正解,維運人員不必學新地方,logrotate 天然接管(現行 log 完全不受輪替管理); ② 活過 uninstall —— 「上次移除為什麼失敗」的紀錄還在; ③ 接得住早期失敗 —— installer 一律 root,可在做任何事之前先mkdir -p/var/log/guidant-ai-agent,於是連「安裝目錄都還沒建就失敗」也有完整 log。 現行那種情況掉到/tmp(重開機清空,而客戶回報問題往往在重開機之後)。代價:與主產品 installer 分歧一處。主產品是範本不是戰場,本案不動它; 列為 follow-up(見 §6)。
uninstall 政策:
/var/log/guidant-ai-agent/不刪(連同上面 ② 的理由), 完成畫面告知路徑與「可自行刪除」。這與「資料 volume 逐項確認」同一個原則—— 能自動觸發的刪除動作要保守,何況這裡刪掉的正是排查證據。
檔頭「🔴 交付包目錄就是安裝目錄,裝完不要刪」整段改寫。
| 風險 | 評估 | 緩解 |
|---|---|---|
| 遷移搬 volume 出錯 → 憑證掉 → 409 掉線 | 承重牆。這是全案最危險的一段 | 先停舊再搬;舊 volume 不刪;失敗回頭路印在畫面;190 實測必須真的裝兩輪對照(驗收條件②③) |
| 已裝客戶受影響 | 實查:目前無外部客戶安裝。188 是 build 機(/tmp 那套是測試殘骸)、190 是測試機、189 POC 未裝 agent |
遷移邏輯仍要做(我們自己的 190/188 就要用它,且它是客戶端唯一的相容保證),但可以在真實環境反覆重裝驗證,不必一次到位 |
/srv/guidant-ai-agent 已被佔用 |
比照主產品:偵測到既有 .env 走升級語意;偵測到非本系統的檔案 → 停下報錯不覆寫 |
D3-c 狀態判定 |
| compose 檔 bind mount 改絕對路徑後,舊 compose 與新 compose 混用 | 升級時 compose 一律換新版;舊版 .bak 保留 |
已在 run_upgrade 內(現行邏輯沿用) |
改了檔頭註解導致 usage() 印錯 |
usage() 用 sed -n '3,91p' 寫死行號 |
改檔頭後必須實跑 ./install.sh --help 目視確認 |
反悔條件:若 190 實測發現「舊形態遷移」在真實資料上不可靠(volume 搬移出現資料損壞或 權限問題無解),退而求其次=不做自動遷移,改為偵測到舊形態即停下、印出人工遷移步驟 (因為目前無外部客戶,人工路徑可接受);但 D3-a/D3-b/D4(釘 project/固定 volume/ image tag 斷言)無論如何都要做——那三條才是 CM-1533 的真正修法。
| # | 缺陷 | 為什麼設計時看不出來 | 修法 |
|---|---|---|---|
| ① | 遷移用 docker run alpine:3 搬 volume——封閉網路下拉不到 |
寫「用臨時容器搬 volume」時預設 alpine 隨手可得,那是連網開發機的直覺。但本產品的前提就是離線安裝,交付包只帶 agent 與 SeaweedFS 兩顆 image。190 實查確認機器上沒有 alpine | 改用 AGENT_IMAGE_TAG(遷移前必然已 docker load,是唯一保證存在的)。已在 190 實測權限與擁有者完整保留(600 1000:1000) |
| ② | --check-only 沒判定狀態 → 全新機器跳過埠佔用檢查 |
check_environment 改成依 INSTALL_STATE 分流時,只想到主流程會先呼叫 detect_install_state,忘了 --check-only 是另一條入口。空字串讓 "!= fresh" 恆為真 |
--check-only 補呼叫 detect_install_state(只讀不寫,不違反「不做任何變更」) |
| ③ | INSTALL.txt(客戶第一眼看的檔)仍寫「🔴 請勿刪除」 |
它不在 deploy/ 下、是 build_compose_bundle.sh 用 heredoc 生成的,改 deploy/ 時整個沒掃到。出貨包會自打嘴巴:INSTALL.txt 說不能刪、完成畫面說可以刪 |
樣板同步反轉,並補「已裝過的機器同一條指令自動走升級」 |
共通教訓:「改文案」這件事的範圍不等於「文件目錄」——生成式產物(build 腳本內的 heredoc)與執行期輸出同樣是客戶讀到的文字。下次做這類反轉時,判準是 「客戶會在哪些地方讀到這句話」,而不是「哪些 .md 提到它」。
190 上,四條路徑:
rm -rf ~/guidant-agent-compose-1.0.0* → guidant-agent-compose status 仍正常、 容器 healthy、雲端心跳 200。docker volume ls 名稱不變、 agent-certs 內憑證未變、無 409、docker inspect 的 image tag 換新。already enrolled ... skip register)、 雲端 remote_agents 版號更新、status=active。/srv/guidant-ai-agent、/etc/guidant-agent、CLI 全清; volume 依提示保留或刪除;舊 /etc/guidant-agent-compose 殘留一併清掉。外加:負面驗證——刻意讓一顆舊 tag 的容器佔住 guidant-ai-agent 這個名字後跑安裝, 確認 D4 的 assert_running_image 會報失敗而不是報成功(證明安全網有牙齒)。
restart: unless-stopped + docker 開機自起已等價, 多一層只是多一個會壞的地方(與現行檔頭立場一致)。<資料目錄>/log/,會被自己的 uninstall 刪掉 (見 D8 的評估)。本案不動主產品,但同一個缺陷在那裡也成立,值得日後對齊到 /var/log/guidant-ai/。/opt/guidant-agent+/etc/guidant-agent+ /var/lib/guidant-agent,同樣沒對齊 guidant-ai 命名。本案只動 compose 形態 (那是現行出貨形態);原生版命名對齊屬獨立議題,且它沒有「解壓目錄=安裝目錄」這個 病根,優先度低。