haos-addon-deploy について
自作の長時間稼働アプリを Home Assistant OS または Supervised のローカルアドオンとして動かすためのデプロイスキルです。Raspberry Pi への配置を含め、パッケージ化や転送、Supervisor 操作、重複マニフェストなどの注意点を扱います。
主な機能
- 01アドオンのファイルと設定の整理
- 02SSH によるコードスナップショット転送
- 03ローカルアドオンのパスと slug の管理
- 04Supervisor CLI と API の利用
- 05接続状態とマニフェスト重複の確認
活用シーン
- 01HAOS にポーラーやモニターをデプロイ
- 02コミット前のホットフィックスを転送
- 03アドオン更新の不反映やコンテナ名変更の調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ caanio/ agent-skills --skill haos-addon-deployインストール後、Claude Code で/haos-addon-deployを入力して読み込まれたか確認します。
詳細
HAOS ローカルアドオンのデプロイ(RPi、24 時間 365 日稼働)
自作の長時間稼働アプリ(ポーラー、モニターなど)を、スリープするワークステーションではなく Raspberry Pi 上で 24 時間 365 日実行するローカルアドオンとしてデプロイします。
以下の各手順は実機で検証済みです。注意点にはすべて ⚠️ を付けています。
例では、<alias> は HA ボックスの SSH エイリアス、<slug> はアドオンのスラッグ(インストール後は local_<slug> になります)、$C は以下の「対象範囲」のスニペットで実行時に解決される Docker コンテナー名です。Supervisor のバージョンによってプレフィックスが異なるため、決してハードコードしないでください。値はご自身のものに置き換えてください。ここにホスト固有の内容はありません。
対象範囲
- Home Assistant OS(または Supervised)のインストールのみが対象です。Container/Core のインストールには Supervisor、
/addons、haCLI がないため、この手順は適用できません。 - Raspberry Pi 4(aarch64)、HAOS 17.x–18.1(Supervisor + Docker アプライアンス。以下のコンテナー名は Supervisor 2026.07.5 で確認)と、Advanced SSH & Web Terminal アドオン(§8 に従って設定。Protection mode はオフ。§0 を参照)で実機検証済みです。
- ⚠️ 最近の HAOS では、UI と CLI(
ha apps)でアドオンを 「Apps」 と表示するようになっていますが、名称変更は一部のみ適用されています。各画面でどちらの表記が使われるか決めつけず、必ず確認してください。- 旧表記
addonのまま:/addonsの配信パス、Supervisor REST API(/addons/<slug>/…)、Docker イメージ名(local/<arch>-addon-<slug>:<version>) - 新表記
app:CLI(ha apps)、Supervisor 独自のストアパス(/data/apps/local/…)、Docker コンテナー名(Supervisor 2026.07.5 で確認されたapp_local_<slug>) - ⚠️ HAOS のバージョンを基準に判断しないでください。 コンテナー名は、OS イメージとは別に自動更新される Supervisor が生成します。そのため、「HAOS が新しい ⇒
app_」というルールは当てになりません。また、ここで実際に確認できているのは 1 つの Supervisor バージョンのみです。どちらのプレフィックスもハードコードせず、$Cを一度解決して、以降は常に再利用してください。
このガードがないと、空の# -a を指定すると、停止中のアドオンも見つかります。調査が必要なのはまさにこのようなアドオンです C=$(ssh <alias> "sudo docker ps -a --format '{{.Names}}' \ | grep -E '^(app|addon)_local_<slug>$'") || true [ -n "$C" ] || { echo "no container for <slug> — installed under a different slug?" >&2; exit 1; }$Cによってdocker exec $C python3 …がdocker exec python3 …になり、Docker は「no such container: python3」と報告します。原因が分かりにくく、誤った場所を調べることになります。
- 旧表記
0. 全般的なルール(最初にお読みください)
- ⚠️ バックアップ、デリバリー、ビルドを行う前に、まず到達可能性を事前確認してください。
ssh -n -o ConnectTimeout=20 <alias> trueを、短い実時間制限(30 秒以内 — ご利用のツール独自のタイムアウト。macOS にはtimeoutコマンドがありません)を設けて実行します。制限時間内に終了コード 0 で終了した場合、事前確認は成功です。それ以外はすべて、エラー、タイムアウト、バックグラウンドへの移行を含めて、作業を停止し、失敗したエイリアスとワークステーションが接続しているネットワークを報告して、リモートでの手順を実行する前にユーザーの指示を待ってください。- 理由:ホーム LAN の外では、
homeassistant.localのような mDNS 名はすぐに失敗しません。sshは何も出力せず、名前解決でブロックすることがあります。ConnectTimeoutが制限するのは TCP 接続時間であり、これには適用されません。タイムアウトを 10 分に設定してバックアップを送ると、単に遅いように見えてバックグラウンドで止まったままになり、実際に約 1 時間を無駄にしたケースがあります。 cloudflared access ssh経由のトンネルを使うエイリアスでは、20 秒が必要です。5 秒ではタイムアウトしましたが、20 秒以内に成功しました。また、最初にブラウザーでのユーザー認証を求める場合があります。
- 理由:ホーム LAN の外では、
- ネットワークが変わるたびに事前確認を再実行し、想定される所要時間を過ぎても出力が空のリモート手順は、遅いのではなく到達不能と判断してください。
- ⚠️
haCLI/Supervisor API の呼び出しには、必ずログインシェルが必要です。 ログインシェルでSUPERVISOR_TOKENが読み込まれます。これがないと、呼び出しはunauthorizedで失敗します。通常のdocker/ls/grepには影響しません。トークンが必要なのはhaとhttp://supervisorへの直接呼び出しだけです。- 単一コマンド:
ssh <alias> 'bash -lc "ha …"' - 複数の手順:
ssh <alias> bash -l -s <<'EOF' … EOF— ヒアドキュメントを動作させるには-lが必要です。単にbash -sとしたヒアドキュメントは非ログインシェルとなり、その中のha呼び出しはすべて失敗します。 - ⚠️ 失敗時の見え方に注意してください。
haはError: unauthorized: missing or invalid API tokenを標準エラーに出力し、終了コード 1 で終了します。そのため、標準出力だけを処理するパイプライン(ha apps info … | grep …)では空の結果になり、「呼び出しに失敗した」ではなく、まさに「そのフィールドが設定されていない」ように見えます。空の出力から何かを判断する前に、終了コードを確認するか、2>&1を追加してください。
- 単一コマンド:
- ⚠️
sshは標準入力を読み取るため、各sshには必要な標準入力を渡してください。 どちらの方向も実際のデプロイで問題になっており、どちらも何も通知されずに失敗します。エラーは出ず、コマンドが実行されないだけです。- リモートで実行するスクリプトをヒアドキュメントで渡す場合 →
-nを付けずにssh <alias> bash -l -s <<'EOF'とします。-nは標準入力を/dev/nullに向けるため、ヒアドキュメントがリモートシェルに届きません。 - 標準入力から読み込まれるスクリプト内(
bash <<'EOF'のラッパーや、bashにパイプで渡すスクリプトなど)で単発のssh <alias> '…'を実行する場合 →-n(または</dev/null)を付けます。付けないと、そのsshがローカルスクリプトの残りを標準入力として読み込み、その後の行がすべてスキップされます。 - 見分け方:手順の出力が単に抜けている場合です。出力ブロックがない場合は「実行されなかった」とみなし、決して「成功した」と判断せず、その手順だけを再実行して確認してください。
- リモートで実行するスクリプトをヒアドキュメントで渡す場合 →
- ⚠️ SSH アドオンの保護モードはオフにしてください。 オンのままでは Docker ソケットにアクセスできません(§ スコープ判定の
docker psを含むsudo docker …)。ユーザーはアドオンの情報ページで保護モードを切り替えます。エージェントはユーザーに確認し、オンのままの場合はログに基づく検証に切り替えてください(その理由は§5で説明します)。 - ⚠️ CLI 名:
ha appsを使ってください(ha addonsは非推奨です)。ローカルアドオンを再検出するにはha store reloadを使ってください(ha apps reloadは存在しません)。 - ⚠️ 同じアプリを 2 台のマシンで同時に実行しないでください(ワークステーションのデーモンと RPi のアドオンなど)。外部 API/サイトへの負荷が 2 倍になり、トークンを定期更新するものは、2 つのインスタンスが互いをログアウトさせます。移行後はワークステーション上のコピーを停止し、自動起動を無効にしてください。
- SD カード 1 枚のみの RPi には冗長性がありません。何かに手を加える前に HA の完全バックアップを取り、カード外にダウンロードしてください。Web 経由のダウンロードは遅いです(HA のストリーミングがボトルネックになります)。
scp <alias>:/backup/<file>.tar ~/backups/のほうが明らかに高速です。
1. リポジトリのレイアウト
HA 固有のファイルはすべてリポジトリ内の haos/ フォルダーに置き、配布時にフラット化します:
haos/
├── config.yaml # add-on manifest (name/slug/version/arch/options/schema)
├── Dockerfile
├── run.sh # entrypoint: create /data subdirs, then exec the main app
└── .dockerignore # exclude .env, logs/, __pycache__, .git/
config.yaml の要点:
name: My App
slug: my_app # installed as local_my_app
version: "1.0.0" # ⚠️ bump this to make HA detect a code update
arch: [aarch64, amd64]
startup: application
boot: auto
map:
- data:rw # /data = persistent volume, survives rebuild/update
init: false
options: # user-editable settings (HA web UI "Configuration" tab)
some_secret: ""
schema:
some_secret: password # the password type masks the value in the UI
⚠️ ベースイメージの選択(musl と glibc):純粋な Python/requests であれば python:3.12-alpine
で問題ありません(軽量です)。ただし、onnxruntime / opencv / numpy のエコシステム
(例:ddddocr)に依存するものは、必ず python:3.12-slim(Debian の glibc)を使ってください。
これらのパッケージには musllinux ホイールがないため、Alpine では pip がソースからのビルドに
切り替わり、停止したり失敗したりします。ヘッドレス版ではない opencv には、さらに
apt-get install libgl1 libglib2.0-0 が必要です。
HA 公式ベースイメージを使わない場合は bashio が使えないため、アプリは独自の設定を
/data/options.json から読み込みます。
慣用的な設定の読み込み方法:/data/options.json が存在する場合は、各キーを大文字にして
環境変数に設定します(監視リストなどのリスト型フィールドは直接解析します)。存在しない場合
(ローカル開発時)は .env にフォールバックします。
2. コードを配布する(ワークステーションで実行し、/addons に配置)
デバイスではクローンを行いません(プライベートリポジトリの認証情報が不要になります)。 代わりにワークステーションからスナップショットをプッシュします。次のいずれかを選んでください:
コミット済みツリー(リポジトリにコミットがある場合のデフォルト): HEAD の内容だけを正確に
配布するため、未完了の編集やデバッグスクリプトなどのローカルファイルが誤って含まれることは
ありません。この保証こそが、これをデフォルトにする理由です。リポジトリに別のアドオンの
フォルダーが含まれる場合は、HEAD 全体を対象にする行の代わりに、下記「2 つのディレクトリで
同じ slug を宣言してはならない」のパス指定アーカイブを使ってください。
# /addons is owned by root; create the folder once to gain write access
ssh <alias> 'sudo mkdir -p /addons/<slug> && sudo chown $(whoami) /addons/<slug>'
git archive --format=tar HEAD | ssh <alias> 'tar -x -C /addons/<slug>'
git archive --format=tar HEAD haos | ssh <alias> 'tar -x --strip-components=1 -C /addons/<slug>'
ssh <alias> 'rm -rf /addons/<slug>/haos' # remove the duplicate haos/ subfolder the full-HEAD archive brought along
コミットせずに作業ツリーを直接配布する場合 — リポジトリにまだコミットがない場合、または
今すぐデバイスにホットフィックスを適用する必要がある場合に使います。コミットは別途後で行う
手順であり(git-helper を参照)、デプロイの前提条件ではありません。重複 slug の修正で
下記のパス指定 git archive を使うのと同様に、このアドオンが実際に必要とするパスだけを指定して
ください。プロジェクトのルート全体を無条件に tar にまとめてはいけません(その理由は修正の
説明を参照してください):
ssh <alias> 'sudo mkdir -p /addons/<slug> && sudo chown $(whoami) /addons/<slug>'
tar -cf - <app files/dirs, e.g. main.py requirements.txt app/> | ssh <alias> 'tar -x -C /addons/<slug>'
tar -cf - -C haos . | ssh <alias> 'tar -x -C /addons/<slug>'
⚠️ tar は .dockerignore を読み込みません(これは Docker のビルド時の仕組みであり、tar の
仕組みではありません)。明示的に除外しなければ、__pycache__、.venv、不要な .pyc ファイルなど、
ローカルに存在するものがすべて一緒に配布されます。混入した場合は、配布先のディレクトリから
削除してください:ssh <alias> "find /addons/<slug> -name __pycache__ -type d -exec rm -rf {} +"。
⚠️ このパスでは、未コミットの作業も含め、現時点でディスク上にあるものがそのまま配布されます。 コミット済みツリーの方法のようなフィルタリングはありません。意図的なホットフィックスには 適していますが、コミットがまだないリポジトリでは、一度限りの初期セットアップとして扱い、 最初のコミットができたらコミット済みツリーの方法に切り替えてください。常用する方法では ありません。いずれの場合も、ホットフィックスは後で独自にコミットする必要があります。この方法は、 修正がデバイスに届くタイミングとコミットするタイミングを切り離すだけです。
⚠️ デプロイ用ディレクトリは フラットで自己完結している 必要があります:config.yaml/Dockerfile/run.sh
はすべて /addons/<slug>/ の最上位に置いてください。
⚠️ 2 つのディレクトリで同じスラッグを宣言してはいけません(前述のどちらの配布方法でも発生する可能性があります)
Supervisor のローカルストアは、ローカルのアドオンツリーを再帰的に走査します。そのため、ソースでは
path.glob("**/config.*")(supervisor/store/data.py)を使用しており、深さを問わず config.yaml、
config.yml および config.json を受け付けます。コミット済みツリーを使う方法の git archive --format=tar HEAD
では、リポジトリ全体 が含まれます。そのため、
リポジトリに別のアドオンのフォルダーも含まれている場合(1 つのリポジトリに 2 つのアドオンがある場合。例:ポーラーと
取り込みサービス)、2 つ目のマニフェストがこのアドオンのデプロイディレクトリに入り、同じスラッグを宣言する
2 つのディレクトリができてしまいます。
⚠️ どちらが優先されるかは一定ではありません。 Supervisor は走査中に単純に上書きするため(apps[app_slug] = app)、最後に訪問したものが優先されます。Path.glob が返すのは、ソート順でも更新日時順でも、最高バージョン順でもない、ファイルシステムの scandir の順序です。再配布後や再起動後に結果が変わることがあるため、「この環境では問題ない」という確認を一度しただけでは何も証明できません。安全な状態は、同じスラッグを宣言するマニフェストが 2 つ存在しないことだけです。
古いコピーが優先された場合、もう一方のアドオンでは次のことが起こります。
version_latestが古いコピーのバージョンを報告するため、update_availableはfalseのままになり、ha apps updateは何度本物のディレクトリを再配布しても何もしません- Docker ビルドコンテキストも古いディレクトリになるため、成功したように見える
ha apps rebuildで古いコードがイメージに組み込まれます ha store reload、ha supervisor restart、docker builder prune -a、HAOS の完全な再起動、ha apps uninstall+installを実行しても何も変わりません。Supervisor が古いのではなく、優先されたディレクトリ自体が本当に古いのです。(実例:Supervisor のバージョン追跡のバグだと誤診され、丸一日を無駄にしました。)
最善の修正方法は、リポジトリ全体を送らないことです。 このアドオンに必要なパスだけを送れば、 衝突は発生しません。
git archive --format=tar HEAD -- <app-dir> <shared-dir> | ssh <alias> 'tar -x -C /addons/<slug>'
(もう一方のアドオンのディレクトリに .gitattributes の export-ignore を設定しても同じ効果が得られます。)
このパスを限定したアーカイブは、前述のコミット済みツリーブロックにある HEAD 全体を指定する行だけを置き換えます。
haos をフラット化する行は引き続き実行してください。rm -rf …/haos の行が必要なのは、指定したパスに haos
が含まれている場合だけです。
すべてを送らなければならない場合は、送信後にもう一方のアドオンのディレクトリを削除してください。ただし、削除をガードしてください。ここで変数が未設定だと rm -rf /addons// に展開され、ボックス上のすべてのアドオンが削除されます。
ssh <alias> bash -l -s <<'EOF'
set -euo pipefail
SLUG=<slug>; OTHER=<other-addon-dir>
ls -la -- "/addons/${SLUG:?}/${OTHER:?}" # look before you delete
rm -rf -- "/addons/${SLUG:?}/${OTHER:?}"
EOF
その後、前述のどちらの方法を使った場合でも、配布のたびに、この機械的なチェックで検証してください(重複があるとゼロ以外で終了します)。終了コードが 0 で、config.yaml が /addons/<slug>/ の直下にあれば §2 は完了です(前述のフラットレイアウトのルール)。このチェックで比較するのは config.yaml / config.yml のスラッグだけです。config.json のマニフェストはスラッグなしで一覧に表示され、比較対象にならないため、手作業で確認してください。
ssh <alias> bash -l -s <<'EOF'
find /addons -name 'config.*' ! -name '.*' -not -path '*/rootfs/*' \
-exec sh -c 'printf "%s → " "$1"; grep "^slug:" "$1"' _ {} \;
find /addons -name 'config.*' ! -name '.*' -not -path '*/rootfs/*' \
-exec grep -h "^slug:" {} \; | sort | uniq -d > /tmp/dupe_slugs
[ ! -s /tmp/dupe_slugs ] || { echo "DUPLICATE SLUG:"; cat /tmp/dupe_slugs; exit 1; }
EOF
すでにデプロイ済みの場合の診断方法 — コンテナ内にあるソースファイルのバイト数と、ディスク上の同じファイルのバイト数を比較します。異なる場合、ビルドコンテキストは配布先のディレクトリではないため、別のコピーを探してください(§Scope の $C)。
# WORKDIR is whatever this add-on's Dockerfile sets, so resolve it rather than hardcoding
ssh <alias> "sudo docker exec $C sh -c 'wc -c \"\$(pwd)/<file>.py\"'"
ssh <alias> 'wc -c /addons/<slug>/<file>.py' # what you delivered
# host-side sources of this container's mounts: where this install keeps Supervisor data
# (not a manifest list — the duplicate-slug check above lists manifests)
ssh <alias> "sudo docker inspect $C --format '{{range .Mounts}}{{.Source}}{{\"\n\"}}{{end}}'"
「Supervisor がディスク上の正しいバージョンを読み取れること」と「Supervisor がそのコピーを使っていること」は別々の主張です。前者だけを確認すると、このバグが解決不可能に見えてしまいます。
3. インストール
ssh <alias> 'bash -lc "ha store reload && ha apps install local_<slug>"'
# First build takes minutes (longer with heavy deps like onnxruntime/opencv);
# the build runs in the Supervisor background, so a dropped SSH session doesn't matter
ssh <alias> 'bash -lc "ha apps info local_<slug> | grep -E \"state:|version:\""' # a version line = installed
4. 設定(アドオンのオプション)
⚠️ ha apps には options サブコマンドがありません — 次の2つの方法から選んでください。
- HA ウェブ UI(最も確実):アドオン → Configuration タブ → 値を入力して保存します。
シークレット(トークン、プッシュトピック)は必ずここに設定してください —
.envにも、リポジトリにも保存しないでください。 - Supervisor REST API(スクリプトから実行可能):
echo '{"options":{...complete object...}}' \ | ssh <alias> 'bash -lc "curl -sS -X POST -H \"Authorization: Bearer \$SUPERVISOR_TOKEN\" \ -H \"Content-Type: application/json\" -d @- http://supervisor/addons/local_<slug>/options"'
⚠️ この POST は options オブジェクト全体を置き換えます — パッチではありません。 変更したいフィールドだけを送信すると(例:{"options":{"some_token":"..."}})、他のすべてのオプションが失われます。また、必須フィールドが不足した状態になると、Supervisor は結果を拒否します。
"App local_<slug> has invalid options: Missing option '<required_option>' in root..."。
単一のフィールドを追加または変更する場合は、まず現在のオブジェクト全体を GET し、変更をローカルでマージしてから、マージ済みのオブジェクト全体を POST してください — 一部だけを手書きしてはいけません。
ssh <alias> 'bash -lc "curl -sS -H \"Authorization: Bearer \$SUPERVISOR_TOKEN\" \
http://supervisor/addons/local_<slug>/info"' > opts.json # full current options
python3 - <<'EOF'
import json
d = json.load(open('opts.json'))['data']['options']
d['some_token'] = 'new-value'
json.dump({'options': d}, open('payload.json', 'w'))
EOF
chmod 600 opts.json payload.json # password-typed options come back in plaintext — see below
ssh <alias> 'bash -lc "curl -sS -X POST -H \"Authorization: Bearer \$SUPERVISOR_TOKEN\" \
-H \"Content-Type: application/json\" -d @- http://supervisor/addons/local_<slug>/options"' < payload.json
注:info エンドポイントは、password 型のスキーマフィールドをマスクせず平文で返します — これによりマージが可能になりますが、POST 前に両方のファイルへ chmod 600 を実行する理由でもあります。この一連のやり取りでは実際のシークレットを扱うものとして対応してください(マージ済みのオブジェクトをログやチャットの記録に出力しないでください)。
POST 後は、変更前の opts.json をロールバック用コピーとして保持したまま、再度 GET info を実行し、変更するつもりのなかったフィールド(例:リフレッシュトークン)が空でないことを確認してください — マージを誤ってローテーション中の認証情報を消してしまうほうが、POST が拒否されるよりはるかに深刻です。また、古い値が残っているのは opts.json だけです。消えていた場合は、同じ方法で {"options": <opts.json's data.options>} を POST してください。問題ないことを確認したら、rm -f opts.json payload.json を実行してください。
スキーマは辞書のリストに対応しています(ユーザーが UI で項目を追加・削除でき、監視対象などに使えます)。
options:
targets:
- name: example
date: "2026-01-01"
schema:
targets:
- name: str
date: str
⚠️ スキーマで宣言されるのは型だけです — 形式の検証は起動時にアプリ側で行います。誤っている項目とその理由を報告してから、起動を中止してください(ユーザーがログですぐに確認できるようにします)。
5. 起動と検証
ssh <alias> 'bash -lc "ha apps start local_<slug>"'
ssh <alias> 'bash -lc "ha apps logs local_<slug>"'
§5 は、以下のフォールバック段落に記載されたログ確認が通れば完了です(新しいコードだけが出力する行があり、エラーの grep で何も見つからないこと)。docker exec によるテストは追加の証拠になります。
コンテナー内でのテスト(例:プッシュ通知を手動で1件送信する):
ssh <alias> "sudo docker exec $C python3 -c '…'"
# ⚠️ the SSH add-on user has no docker socket permission — sudo required
# ⚠️ $C must come from the resolver in §Scope — never hardcode app_local_/addon_local_,
# and never skip its empty check (an empty $C makes docker read the next arg as the
# container name, giving a "no such container: python3" that looks unrelated)
⚠️ docker exec を使うには、SSH アドオンの Protection mode を オフ にする必要があります(§0)。オンの場合、このバイトフィンガープリント/exec ベースの検証方法は利用できません。 自律型コーディングエージェントの場合、自分でトグルを切り替えられると思い込まないでください — Protection mode をオフにするのは Supervisor のセキュリティ設定の変更であり、権限を重視するエージェントハーネスが、ユーザーの確認なしに切り替えることを(正しく)拒否する場合があります。この制限を回避しようとしてはいけません。実際の境界として扱ってください。代わりにログベースの検証を行ってください — 通常はこれで十分です。起動/更新後に ha apps logs local_<slug> を実行すると、新しいバージョンのログ文字列(行、フレーズ、起動バナーなど)が表示されるはずです。これらは、今回提供したコードにのみ存在するものです。さらに、grep -iE "error|traceback|exception|Errno|Fatal" で該当箇所がないことも確認してください。デプロイで到達可能なエンドポイントも追加した場合は、実際にアクセスするほうが、静的なバイト数の確認より強い証拠になります(§2 の「異なる2つの主張」を参照 — 推測より、観察可能な動作を信頼してください)。
6. 更新(⚠️ 3 つから 1 つを選択してください。間違えると拒否されます)
| 変更内容 | コマンド |
|---|---|
| オプションのみ(コード変更なし) | ha apps restart local_<slug> |
| コード変更あり、config.yaml のバージョンは未更新 | §2 のデリバリーをやり直す → ha apps rebuild local_<slug> |
| コード変更あり、バージョンを更新済み | §2 のデリバリーをやり直す → ha store reload → ha apps update local_<slug>(この場合、リビルドは拒否されます:「ローカルのバージョンとストアのバージョンが異なります。リビルドではなくアップデートを使用してください」) |
⚠️ コードを更新するときは、必ず version を更新してアップデートの手順を実行してください。 リビルドすると、新しいコードを実行しているのにデバイスは古いバージョンを報告するため、ha apps info では実際に実行中のコードを判別できなくなり、後のデバッグに支障をきたします。リビルドは、バージョン履歴をまだ気にする必要のない、素早く反復するテストサイクルに限って使用してください。
⚠️ アップデートを実行する前に、ストアでバージョンの更新が反映されたことを必ず確認してください。 この確認を行えば、1 日がかりの謎解きを 2 分で解決できます。
ssh <alias> 'bash -lc "ha apps info local_<slug> | grep -E \"^(version|version_latest|update_available):\""'
version_latest が、直前にデリバリーしたバージョンと一致している必要があります。ha store reload の後も古いバージョンが表示される場合は、停止してください。リビルドや再インストールを開始してはいけません。 Supervisor が、あなたのスラッグを宣言している別のディレクトリを読み込んでいます。§2 の「同じスラッグを宣言するディレクトリを 2 つ作らない」を確認してください。
アップデート後は、§5 と同様にリビルドまたは再起動し、動作を確認してください。
7. 永続化とログ
/data/は永続ボリュームです(Supervisor コンテナ内では/data/apps/data/local_<slug>/):DB、ログ、トークンはすべてここに保存され、リビルドやアップデート後も保持されます。ha apps logsで確認できるように標準出力へ、またローテーションするファイルへ書き込むために/data/logs/へログを出力してください。- ⚠️ 起動ログにシークレットを絶対に出力しないでください(
ha apps logsは HA を開ける人なら誰でも確認できます)。出力するのはオン/オフの状態だけにしてください。
8. SSH の前提条件(新しい HA ホストに初めて接続するときのみ)
ワークステーションの ~/.ssh/config にエイリアス(HostName、User、IdentityFile)を設定し、公開鍵を Advanced SSH & Web Terminal アドオンの設定(YAML モード)に追加してください。
ssh:
username: <account>
authorized_keys:
- >-
ssh-ed25519 AAAA... public key
⚠️ 保存した設定が元に戻る場合、よくある原因は秘密鍵ファイルの権限が緩すぎること(chmod 600)か、YAML のネスト位置が間違っていることです。SSH の接続先はSSH アドオンのコンテナです(HAOS ホストでも、HA のコアコンテナでもありません)。そこで sudo を使用できます。