把一台快一年沒更新的 Home Assistant 一次拉到最新:Core 跨 21 個月、HACS 整合集體陣亡的實戰筆記

智慧家庭樞紐示意圖

這台樹莓派已經連續開機 161 天,上面跑著我家所有的燈、插座、Zigbee 感測器、ESPHome 自製裝置。它穩到我幾乎忘了它的存在——直到我想更新它,才發現 Home Assistant Core 停在 2025.1.4,而當下最新版是 2026.10.4。中間隔了 21 個月。

這篇是完整的實戰記錄:從「我連這台機器的 IP 都不知道」開始,一路把 HAOS、Supervisor、Core、add-on 拉到最新,順手做掉安全強化,最後對付那個大版本跳躍幾乎必然會踩到的坑——一堆 HACS 自訂整合在新 Core 上集體陣亡。如果你也有一台「很久沒碰、但也不敢亂碰」的 Home Assistant,這些坑你大概遲早會遇到。

ℹ️ 環境與前置條件

  • 硬體:Raspberry Pi 4(4GB),Home Assistant OS(board rpi4-64)
  • 起點版本:HAOS 14.1 / Core 2025.1.4
  • 操作方式:全程 SSH + ha CLI + Supervisor API,沒碰 GUI
  • 前置知識:基本 shell、jq、知道 Home Assistant 有 Core / Supervisor / OS / add-on 四層

第一步:先找到它、進得去、而且備份好

最諷刺的事情是:我要更新這台機器,卻連它的 IP 都不記得。家裡的 router 是 MikroTik RouterOS,但我不想猜密碼登 router 後台(而且自動化工具也會擋這種「對 router 猜密碼」的動作)。所以我走了更單純的路——直接掃區網。

先確認自己在哪個網段,再對整個 /24 做一輪 ping sweep,順便掃 Home Assistant 預設的 8123 埠:

# 找出預設閘道與自己的網段
route -n get default | grep -E 'gateway|interface'
# gateway: 192.168.0.1 → MikroTik

# 掃整個網段的 8123(HA 預設 web 埠)
for i in $(seq 2 254); do
  (nc -z -G1 -w1 192.168.0.$i 8123 2>/dev/null && echo "$i open") &
done; wait
# → 一台機器的 8123 有回應

一發命中。再用 mDNS 確認,homeassistant.local 剛好也解析到同一個 IP,身分確定。接下來的內網 IP 我都用佔位值 HA_IP 代稱。

SSH 金鑰的坑:~ 竟然解析到 /root

Home Assistant OS 本身是唯讀的,SSH 存取是靠「Advanced SSH & Web Terminal」這個 add-on 提供。我有帳密可以進去,但每次打密碼太煩,想設免密碼金鑰登入。這裡踩到第一個坑。

我照慣例把公鑰塞進 ~/.ssh/authorized_keys,結果死都登不進去。開 verbose 看,金鑰有送出、但被拒。權限、StrictModes 全對,百思不解。最後用 sshd -T 把「對這個使用者實際生效的設定」印出來,真相大白:

sudo /usr/sbin/sshd -T -C user=oliver | grep -i authorizedkeysfile
# authorizedkeysfile /etc/ssh/authorized_keys /root/.ssh/authorized_keys

看到沒?設定檔裡寫的是 ~/.ssh/authorized_keys,但這個 add-on 環境下的 ~ 被解析成 /root 而不是我的家目錄。我的金鑰寫到自己的家目錄下,sshd 根本不會去讀。乾。

正解有兩個:寫進它一定會讀的絕對路徑 /etc/ssh/authorized_keys,或者更持久的——直接寫進 add-on 設定裡的 ssh.authorized_keys。後者才是對的,因為 add-on 更新重啟時會從設定重新生成 authorized_keys,不會掉(下面用到的 $SUPERVISOR_TOKEN 在 SSH add-on 的 shell 環境裡本來就有,不必自己生):

# 把公鑰寫進 SSH add-on 設定(透過 Supervisor API,密碼不經過命令列)
PUB=$(cat ~/.ssh/id_ed25519.pub)
ha addons info <ssh_addon_slug> --raw-json \
  | jq --arg k "$PUB" '{options: (.data.options | .ssh.authorized_keys = ((.ssh.authorized_keys // []) + [$k] | unique))}' \
  > /tmp/ssh_opts.json
curl -sf -X POST -H "Authorization: Bearer $SUPERVISOR_TOKEN" \
  -H "Content-Type: application/json" --data @/tmp/ssh_opts.json \
  http://supervisor/addons/<ssh_addon_slug>/options

備份先行,而且要拉下來核對

這是整篇最重要的一句話:動任何更新之前,先做一顆完整備份。 我查了一下既有備份,最新一顆是 2024-10-28 的 partial backup——快兩年前,等於沒有。

Home Assistant 官方的更新建議第一條就是先備份、再讀 release notes 看 breaking changes,見 官方文件 。我做了一顆 full backup,然後——這點很多人會跳過——把它拉回本機,而且核對 SHA256。備份如果只躺在同一台待更新的機器上,那台要是更新到一半變磚,備份跟著陪葬:

# 在 HA 上建立完整備份
ha backups new --name "pre-update-20261103"

# SSH add-on 的 sftp subsystem 沒開,scp 會失敗,改用 cat 串流
ssh ha 'cat /backup/<slug>.tar' > ~/ha-backups/pre-update.tar

# 兩邊核對 SHA256,確認沒傳壞
shasum -a256 ~/ha-backups/pre-update.tar
ssh ha 'sha256sum /backup/<slug>.tar'
# → 兩邊 hash 一致,安心

從 router 定位 Home Assistant 並設定 SSH 金鑰

更新順序,與那些「不能自我更新」的雷

Home Assistant 有四層:OS、Supervisor、Core、add-on。官方文件其實沒硬性規定順序,社群共識是「大多數情況順序不重要」。不過也有用戶回報,Core 和 add-on 的更新有時會被擋住、得先更新 OS,見 社群討論串 。我自己採「由底層往上」:OS → Core → add-on;實際跑下來,OS 更新後系統沒回到 running 之前,下一步動作確實會被 Supervisor 擋(後面那段 startup 的錯誤就是現場)。

HAOS:A/B 雙分割區,更新相對安全

先更 OS。這裡有個讓人安心的設計:HAOS 用 A/B 雙分割區。更新時新版寫進「非啟用」的那個 slot,重啟才切過去;萬一新版開不起來,系統會自動回退到舊 slot,見 社群討論 。對一台遠端、無頭的樹莓派來說,這個自動回退機制等於是個保命符。

ha os update   # 寫入備用分割區 → 自動重啟切換

更新完我發現一件有趣的事:原本 OS「最新版」顯示 15.2,更完重開、Supervisor 自己也默默更新到最新之後,metadata 刷新,最新版變成了 18.4。我的推測是:舊的 Supervisor 拿到的版本清單還沒刷新,所以一開始只看得到 15.2。於是我又更了一次,15.2 → 18.4,才真的到頂。

⚠️ OS 更新後要等系統「running」才能再動作
第一次更完想馬上更第二次,結果被擋:

Error: 'OSManager.update' blocked from execution, system is not running - startup

大版本 OS 更新後首次開機,Supervisor 要把一堆容器重新拉起來,系統狀態會卡在 startup 一段時間(RPi4 上要好幾分鐘)。得輪詢到 state=running 才能繼續:

curl -sf -H "Authorization: Bearer $SUPERVISOR_TOKEN" \
  http://supervisor/info | jq -r .data.state
# startup ... startup ... running

Core 跨 21 個月:大膽但不莽撞

重頭戲是 Core 2025.1.4 → 2026.10.4。21 個月的 release,中間不知道累積了多少 breaking changes。更新前我做了兩件事:確認磁碟空間夠(新容器要下載解壓),以及跑一次設定檔檢查:

ha core check   # Command completed successfully → 現有設定至少是合法的

然後就讓它跑。Core 更新是下載新容器 + 重啟,不是整機重開,所以 SSH(由 add-on 提供)不會斷,只有 8123 會短暫中斷。RPi4 上整個過程大約十幾分鐘,中間可以透過 Supervisor 的 jobs API 看下載進度:

curl -sf -H "Authorization: Bearer $SUPERVISOR_TOKEN" \
  http://supervisor/jobs/info | jq '[.data.jobs[] | select(.done==false)]'
# [{ "name": "home_assistant_core_update", "progress": 78.3, "done": false }]

跑完,Core 到 2026.10.4,HTTP 回 200,Supervisor 的 resolution center 回報 unsupported: []、unhealthy: []。系統層面乾乾淨淨。本以為這樣就結束了——天真。

SSH add-on 不能更新自己

add-on 我放最後更。前幾個(Mosquitto、ESPHome、Samba、File editor、Let's Encrypt)都順利,唯獨 SSH add-on 卡住:

ha addons update <ssh_addon_slug>
# Error: App <ssh_addon_slug> can't update itself!

想想也合理:我正是透過這個 add-on 的終端在下指令,更新它會重啟容器、打斷正在執行更新的那個連線。Supervisor 直接禁止這種「自己更新自己」的操作,連 API 都擋。唯一的解是從別的地方觸發——HA 網頁 UI 用你的 HA 帳號點更新。所以這一個我就留著,改天從 UI 處理。

Home Assistant 版本升級階梯

順手做掉的安全強化

既然都進來了,順便把幾個安全問題清一清。

移除明文 FTP add-on。 這台裝了 FTP add-on,但 FTP 是明文傳輸、本質不安全,而且我已經有 Samba 做檔案分享,FTP 根本是多餘的攻擊面。直接停用 + 移除(反正它在剛才那顆備份裡,要還原隨時可以):

ha addons stop <ftp_slug>
ha addons uninstall <ftp_slug>

檢視對外暴露的服務。 我用一個 frp 類型的 add-on 把 HA 的 8123 開到公網(走我自己的 frp server,實際位址這裡就不公布了)。這是刻意為之,保留;但我也確認了 Let's Encrypt add-on 是停用狀態、沒在對外發憑證,沒有多餘的暴露。順帶一提,把 HA 這種帶登入的服務往公網開,一定要搭配夠強的密碼,有條件最好再上一層 TLS。盤點對外服務這件事,趁更新時一起做最省事。

真正的硬仗:「實體無法使用」背後的 breaking change

更新全部跑完,我以為可以收工了。結果 HA 跳出一則修復(Repairs)通知:

由 tapo 整合所提供的實體無法使用,假如該實體已不再使用,請於設定中進行移除。

如果照字面做——「不用的話就移除」——那就掉進陷阱了。這則訊息只是症狀,不是病因。我決定往下追。

追查:插座到底是離線,還是整合壞了?

tapo 是 TP-Link Tapo 智慧插座。我先查這顆裝置的三個實體,全部 unavailable:

for e in switch.tapo_plug sensor.tapo_plug_signal_level \
         binary_sensor.tapo_plug_overheat; do
  curl -sf -H "Authorization: Bearer $SUPERVISOR_TOKEN" \
    http://supervisor/core/api/states/$e | jq -r '"\(.entity_id) = \(.state)"'
done
# 三個全是 unavailable

直覺會想「啊插座大概離線了」。但我多做一步,從 config entry 撈出這顆插座登記的 IP,直接 ping:

ping -c 3 <plug_ip>
# 3 packets transmitted, 3 received, 0% packet loss

插座明明在線上。 那問題就在整合本身。我去看 config entry 的即時狀態:

curl -sf -H "Authorization: Bearer $SUPERVISOR_TOKEN" \
  http://supervisor/core/api/config/config_entries/entry \
  | jq -r '.[] | select(.domain=="tapo") | "\(.state) | \(.reason)"'
# setup_retry | 'ConfigEntries' object has no attribute 'async_forward_entry_setup'

破案。'ConfigEntries' object has no attribute 'async_forward_entry_setup'。

根因:被移除的 API

這個 async_forward_entry_setup(單數)是 Home Assistant 用來把 config entry 轉發給各平台(sensor、switch...)做初始化的方法。它在 2024 年 6 月就被官方宣告棄用,改用複數版的 async_forward_entry_setups,官方部落格原定在 Core 2025.6 移除,見 官方開發者 blog 。

我這顆 tapo 整合裝的是 HACS 社群版 1.6.1,還在呼叫那個已經被移除的單數方法。在舊 Core 上它活得好好的,一跳到 2026.10,方法不存在了,整合初始化直接拋 AttributeError,config entry 卡在 setup_retry,所有實體自然變 unavailable。

這不是個案。同一次掃描,我發現 xiaomi_miot 整合也在哀號,錯誤是另一個 breaking change:

RuntimeError: Detected code that calls `device_registry.async_get_or_create`
with a deprecated `via_device` parameter; use `via_device_id` instead.

via_device → via_device_id 的變更在 Core 2026.8 落地,舊用法原則上支援到 2027.8,見 官方 device registry blog 與 後續變更 blog 。而在我這台 2026.10 的實測裡,xiaomi_miot 就已經直接噴出上面那段 RuntimeError、那幾個門窗感測器實體加不進去——不同整合,同一類病因。

flowchart TD
    A["Repairs 通知: tapo 實體無法使用"] --> B{"插座離線嗎? ping plug_ip"}
    B -->|"0% loss,在線"| C{"config entry state?"}
    C -->|"setup_retry"| D["看 reason"]
    D --> E["ConfigEntries 沒有 async_forward_entry_setup"]
    E --> F["根因:舊 HACS 整合 呼叫已移除的 API"]
    F --> G["正解:更新整合 而不是刪實體"]
    B -.->|"若真離線"| H["去把裝置供電/連網"]

一句話:大版本跳躍的本質,就是把過去 21 個月累積的 breaking changes 一次性砸在你那些很久沒更新的自訂整合上。 官方整合跟著 Core 一起更新,不會有事;而 HACS 社群整合是獨立的版本線,你不主動更,它就停在會爆炸的舊版。

手動更新 HACS 整合的一套 SOP

照理說 HACS 整合該從 HACS UI 更新。但我想在純 CLI 完成,而且這台的對外 DNS 偶爾不穩(Supervisor 連某些 host 會 timeout)。於是我整理出一套「在本機下載、串流推上去替換」的流程,對 tapo、ble_monitor、xiaomi_miot 三個都適用。

flowchart LR
    A["Mac:抓 GitHub release zipball"] --> B["取出 custom_components/domain"]
    B --> C["tar 串流 ssh 推到 box"]
    C --> D["sudo 替換資料夾 chown root:root"]
    D --> E["ha core restart 自動裝 pip 相依"]
    E --> F["驗證 state=loaded 實體回來"]

以 tapo 為例,社群版最新是 3.5.4(我的是 1.6.1):

# 1. 在 Mac 下載並解壓(下載物當不信任資料,放隔離目錄)
curl -sL "https://codeload.github.com/petretiandrea/home-assistant-tapo-p100/tar.gz/refs/tags/3.5.4" -o tapo.tgz
tar xzf tapo.tgz

# 2. 先備份 box 上的舊版(可回滾)
ssh ha 'cd /homeassistant/custom_components && sudo tar czf /homeassistant/tapo_bak.tgz tapo'

# 3. 把新版的 custom_components/tapo 串流上去、替換
tar czf - -C .../home-assistant-tapo-p100-3.5.4/custom_components tapo \
  | ssh ha 'cat > /tmp/tapo_new.tgz'
ssh ha 'cd /homeassistant/custom_components \
  && sudo rm -rf tapo \
  && sudo tar xzf /tmp/tapo_new.tgz -C . \
  && sudo chown -R root:root tapo'

# 4. 重啟 Core,HA 會依新 manifest 的 requirements 自動 pip 安裝相依
ssh ha 'ha core restart'

重啟後,tapo 的 config entry 變 loaded,開關、訊號強度都回來了,還多了一個「韌體更新」實體——新版功能。ble_monitor(12.15.0 → 14.4.0)和 xiaomi_miot(1.0.18 → 1.1.5)如法炮製。光是 ble_monitor,就從 58 個 unavailable 的 BLE 感測器救回了 52 個(剩 6 個是被動感測器還沒廣播、或真的沒電)。整台機器的 unavailable 實體從 128 掉到 74,剩下的幾乎都是真的離線的裝置(Zigbee 電池裝置還沒回報、關機的 NAS、睡著的印表機),不再是軟體故障。

💡 unique_id 改版會留下「孤兒實體」
tapo 從 1.6.1 到 3.5.4,把 overheat 感測器的 unique_id 從 ..._overheat 改成 ..._Overheat(大小寫變了)。HA 會把它當成全新實體,舊的那個就變成沒有整合提供、永遠 unavailable 的孤兒——這時候那則「請移除實體」的訊息才真的該照做。

要在純 CLI 清掉,得停 Core、用 jq 從實體登錄檔濾掉那筆,再啟動(登錄檔在 HA 執行中改會被記憶體蓋回去,所以一定要先停):

ha core stop
sudo cp -a /homeassistant/.storage/core.entity_registry{,.bak}
sudo jq '(.data.entities) |= map(select(.entity_id != "binary_sensor.tapo_plug_overheat"))' \
  /homeassistant/.storage/core.entity_registry > /tmp/er.json
jq empty /tmp/er.json && sudo cp /tmp/er.json /homeassistant/.storage/core.entity_registry
ha core start

不是每個「未載入」都是故障

這裡還有個差點害我出包的地方:我一度把所有「not_loaded」的整合都當成故障,差點全部動手修。但仔細看每個 config entry 的 source 和 disabled_by 欄位才發現:

整合表面狀態真相
synology_dsmnot_loadedsource: ignore——我以前「忽略」的探索項,從沒設定過,不是壞掉
esphome(某裝置)not_loaded同樣是被忽略的探索項
meshtasticnot_loadeddisabled_by: user——我自己停用的
ble_monitornot_loaded這個才是真的被 Core 打壞,該修

掃描之後要先分類、再動手,別看到紅字就一股腦全修。

HACS 整合陣亡偵錯

幾個可以帶走的心得

分段升級,可能比一次跳 21 個月更省心。 我這次是一口氣跨過去,好處是快、一次到位;代價是 breaking changes 全部一起爆,debug 時要同時面對好幾個整合陣亡。如果你的系統很關鍵、整合又多又雜,其實可以考慮分段跳(例如先到 2025.7 確認無事再往上),每一步的爆炸半徑都比較小、也比較容易定位是哪一版、哪個整合出事。但不管哪種,OS 和 Supervisor 最好先更到夠新,否則 Core 更新容易被擋。

更 Core 之前,先把 HACS 整合全部更到最新。 這是這次最大的體會。官方整合跟 Core 綁在一起走,不會落隊;HACS 社群整合是獨立版本線,是跨版本更新時最大的風險點。先花十分鐘把它們更新完,能擋掉絕大多數「更完 Core 就有東西壞掉」的狀況。

別急著照修復訊息的字面做。 「實體無法使用,請移除」是個設計得很體貼、但也很容易誤導的訊息。實體 unavailable 的原因可能是裝置離線、可能是整合壞掉、也可能是真的孤兒。先 ping 一下裝置、看 config entry 的 state 和 reason,判斷根因再決定。在我的案例裡,絕大多數的 unavailable 是整合被 breaking change 打壞,正解是更新整合;只有那個大小寫改名的 overheat 才是真孤兒、該刪。順序反了,你會刪掉一堆其實還活著的實體。

這台機器現在 HAOS 18.4、Core 2026.10.4,除了那個不能自我更新、留待 UI 處理的 SSH add-on,其餘全線最新。unavailable 實體從 128 降到 74,而剩下的 74 幾乎都是當下真的離線的裝置(還沒回報的 Zigbee 電池裝置、關機的 NAS、睡著的印表機),不再有整合層的軟體故障。從一台「不敢碰」的黑盒子,變回一台我清楚它每一層狀態的機器——對一台管著全家大小電器的樞紐來說,這才是我真正要的。

參考資料

沒有留言:

張貼留言

把一台快一年沒更新的 Home Assistant 一次拉到最新:Core 跨 21 個月、HACS 整合集體陣亡的實戰筆記

這台樹莓派已經連續開機 161 天,上面跑著我家所有的燈、插座、Zigbee 感測器、ESPHome 自製裝置。它穩到我幾乎忘了它的存在——直到我想更新它,才發現 Home Assistant Core 停在 2025.1.4 ,而當下最新版是 2026.10.4 。中間隔了 ...