mcpField
H
haos-cloud-backup

haos-cloud-backup

github.com/caanioクラウド・DevOps036 回インストール1 日前に更新
リポジトリ

haos-cloud-backup について

Home Assistant OS のバックアップを rclone でクラウドへ送る手順をまとめたスキルです。アップロード制限、日付ベースの保持、バックアップの検証を扱います。

主な機能

  1. 01rclone バックアップジョブの設定
  2. 02帯域幅制限によるアップロード
  3. 03日付ベースの GFS 保持
  4. 04dry_run による削除ルールのテスト
  5. 05クラウドコピーとスケジュールの確認

活用シーン

  1. 01Home Assistant の日次バックアップを SD カード外へ移行
  2. 02家庭の上り回線でバックアップ通信を制限
  3. 03日次・週次・月次のバックアップを選択保持
  4. 04ファイル削除前に保持フィルターを検証

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/caanio/agent-skills --skill haos-cloud-backup

インストール後、Claude Code で/haos-cloud-backupを入力して読み込まれたか確認します。

詳細

HAOS バックアップ → クラウド(rclone、帯域制限、GFS 保持)

HA の日次自動バックアップを、家庭の小規模なアップリンクを飽和させることなく、単一の SD カードからクラウドストレージへ移し、両側で自動的に間引きます。 Raspberry Pi 4(HAOS 18.1、Core 2026.6.4)で jcwillox/hassio-rclone-backup v3.4.1 → Google Drive を使い、2026-07-11 に実環境で検証済みです。⚠️ は実際に遭遇した落とし穴を示します。 ha は haos-addon-deploy §0/§8 に従った SSH エイリアスです(bash -lc のルールが適用されます)。

0. この方法を選ぶ理由

  • ⚠️ 帯域制限できるのは rclone だけです。 ネイティブの google_drive バックアップエージェントには帯域幅のオプションがなく、sabeechen/hassio-google-drive-backup の帯域制限リクエストは対応予定なしとしてクローズされています(issue #366)。アップロード速度が重要なら、選択肢は rclone しかありません。--bwlimit の単位は MBytes/s です(1.5M = 12 Mbit/s — 妥当な上限は、測定したアップリンクの約 30% です)。
  • rclone には GFS(祖父・父・息子)保持機能が組み込まれていませんが、アドオンは各バックアップの名前に日付を埋め込むため(Automatic_backup_<ver>_YYYY-MM-DD_HH.MM_<id>.tar、§3)、日付を基準にしたフィルターで同様の処理ができます(§4)。
  • HA の自動バックアップ時刻(デフォルトは約 05:00)の直後にアップロードをスケジュールします(05:30)。オフピーク時間帯に実行され、毎日移動する新しいファイルは 1 つだけです。

1. アドオンのインストール

ssh ha 'bash -lc "ha store add https://github.com/jcwillox/hassio-rclone-backup && ha store reload"'
ssh ha 'bash -lc "ha apps install 19a172aa_rclone_backup"'

リポジトリにはアドオンが 1 つだけあります。インストール済みのスラッグは 19a172aa_rclone_backup です(ハッシュはリポジトリ URL によって固定されています)。⚠️ add-repository ではなく ha store add を使います。無言で誤ったサブコマンドを実行する落とし穴については haos-https-tunnel §2 を参照してください。

2. クラウド認証(rclone.conf)

⚠️ 鶏と卵の問題:アドオンは起動時にすべてのジョブのリモートを検証し、rclone.conf がない場合、またはリモートがまだ定義されていない場合は、FATAL エラーで停止します。リモートが作成されるまで jobs: [] のままにしてください(そうしないと state: error になります。害はありませんが、紛らわしい状態です)。

設定ファイルは /homeassistant/rclone.conf にあります(configuration.yaml と同じフォルダーです。このパスはアドオンの config_path のデフォルトです)。設定方法は 2 通りあります。さらに、ジョブを作成した後に注意が必要な点が 2 つあります。

  • Web UI を使う方法:ジョブを空にした状態でアドオンを起動 → Open Web UI。⚠️ ログインページは見かけだけのものです。GUI は --rc-no-auth で実行されます。あらかじめ入力された URL はそのままにし、ユーザー名とパスワードは空欄のまま Login をクリックします。Configs → create remote の順に選択します。Ingress 経由の OAuth は localhost へのリダイレクトで失敗する場合があり、その場合は次の方法を使います。

  • トークンを貼り付ける方法(堅牢で、実環境で使用):ブラウザーを使える任意のマシンで rclone authorize "drive" を実行 → ユーザーが Google の同意手続きを完了 → コマンドがトークンの JSON ブロックを出力します。conf を作成して転送します。

    [gdrive]
    type = drive
    scope = drive
    token = {"access_token":"...","refresh_token":"...","expiry":"..."}
    

    chmod 600 を実行します。それ以降、リフレッシュトークンはデバイス上で自動更新されるため、ヘルパーマシンの rclone はアンインストールできます。コンテナー内から確認します。名前を一度 $C として解決し、§4 と §6 でも再利用します。プレフィックス(addon_ / app_)は Supervisor のバージョンによって異なるため、両方に一致させます。

    C=$(ssh ha "sudo docker ps -a --format '{{.Names}}' \
         | grep -E '^(app|addon)_19a172aa_rclone_backup$'") || true
    [ -n "$C" ] || { echo "no rclone_backup container — add-on installed?" >&2; exit 1; }
    ssh ha "sudo docker exec $C rclone --config /homeassistant/rclone.conf about gdrive:"
    
  • ⚠️ Web UI に表示されるのは rclone のリモートと進行中の転送だけです。ジョブ、スケジュール、bwlimit はアドオンのオプションです(HA → Settings → Apps → アドオン → Configuration タブ)。Web UI で設定を探しても見つかりません。

  • ⚠️ 破損に見えても問題のない表示上の癖:HA の YAML エディターで flags: マップが Python の repr 形式の文字列("{'bwlimit': '1.5M'}")として再表示される場合があります。これは表示上の問題です。エディターではなく、ジョブログ(Starting bandwidth limiter at 1.500Mi Byte/s)を信頼してください。同様に、日付フィルター(*_????-??-01_*)の ? 文字は rclone の単一文字グロブワイルドカードであり、文字化けではありません。どちらも壊れているという報告があります。

上記の about gdrive: チェックでエラーではなくリモートの使用状況が表示されれば、どちらの方法でも完了です。それから初めてジョブを追加してください(§3)。

3. ジョブ(アップロード+整理)

ジョブはアドオンのオプションに設定します。§7 の両方の方法で設定できます。

デバッグに実際の時間を要する現場での注意点:

  • ⚠️ delete ジョブでは、パスを destination ではなく source に指定します。指定しないと、アドオンは「at least 1 source must be specified」というメッセージとともに FATAL で停止します。
  • flags: はマップです(アンダースコアは自動的にダッシュに変換されます)。順序が重要な --filter ルールは extra_flags に指定してください。include:/exclude: 配列では順序が保証されません。
  • schedule のないジョブは、アドオンの起動時に実行されます。単発テストには便利ですが、削除ジョブでは危険です。
  • 名前ベースのフィルターを機能させるには、ファイル名の変更が必要です。HA はバックアップを <slug>.tar(8 桁の16進数で、判読できません)としてネイティブに保存します。アドオンは /backup 内のファイル名を {name}_{date}_{slug}.tar にその場で変更します(renamed/unrenamed N backups のログ行を確認してください。初回実行時は既存のバックアップも変更します)。これにより、以下および §4 の + Automatic_backup_* フィルターが変更後の名前に一致し、ローカルとリモートのファイル名が同一になります。HA への影響はありません。Supervisor はファイル名ではなく tar のメタデータでバックアップを識別するため、復元に影響はありません。末尾の slug は引き続き HA のバックアップ ID に対応します。
  • 最上位にある dry_run: true(jobs: と同じ階層)で、コストをかけずに事前確認を始めましょう。 推奨手順:最初のスケジュール実行日が終わるまでは有効にしておき、Skipped ... as --dry-run is set の行を読んで、各ジョブが意図したファイルだけを正確に選択していることを検証します。その後、dry_run: false に設定し(§7 の SSH 経由)、再起動します。⚠️ 有効な間は、アップロードも削除も一切行われません。それでもすべてのジョブが「finished」とログに記録され、一見するとログは正常に見えるため、何週間も忘れたままになりがちです。
jobs:
  - name: upload to cloud
    schedule: "30 5 * * *"
    command: copy            # copy, not sync — never mirror deletions upward
    sources: [/backup]
    destination: "gdrive:HA-Backups"
    flags: { bwlimit: 1.5M }
  - name: prune local autos
    schedule: "0 7 * * *"
    command: delete
    source: /backup
    flags: { min-age: 7d }   # SD card keeps ~7 dailies
    extra_flags: ["--filter", "+ Automatic_backup_*", "--filter", "- *"]
  - name: prune local others   # named/milestone + addon-update backups
    schedule: "5 7 * * *"
    command: delete
    source: /backup
    flags: { min-age: 90d }

4. リモート上の GFS 間引き(日付基準)

週次の保持対象日は月の1日/8日/15日/22日、月次の保持対象日は1日です。「毎週月曜日」ではありません。基準日は暦日で、密度は同じです。フィルターは先に一致したものが適用されます。基準日を保護し、次に日次パターンに一致させ、最後に残りを削除します。名前付きバックアップはパターンに一致しないため、永久に残ります。

  - name: thin to weekly (7d-28d)
    schedule: "10 7 * * *"
    command: delete
    source: "gdrive:HA-Backups"
    flags: { min-age: 7d, max-age: 28d }
    extra_flags: ["--filter", "- *_????-??-01_*", "--filter", "- *_????-??-08_*",
                  "--filter", "- *_????-??-15_*", "--filter", "- *_????-??-22_*",
                  "--filter", "+ Automatic_backup_*", "--filter", "- *"]
  - name: thin to monthly (28d-180d)
    schedule: "15 7 * * *"
    command: delete
    source: "gdrive:HA-Backups"
    flags: { min-age: 28d, max-age: 180d }
    extra_flags: ["--filter", "- *_????-??-01_*",
                  "--filter", "+ Automatic_backup_*", "--filter", "- *"]
  - name: purge (>180d)
    schedule: "20 7 * * *"
    command: delete
    source: "gdrive:HA-Backups"
    flags: { min-age: 180d }
    extra_flags: ["--filter", "+ Automatic_backup_*", "--filter", "- *"]

削除ジョブを信頼して実行する前に、コンテナー内で --dry-run を付けて、そのジョブのコマンドを正確に手動実行してください(sudo docker exec $C rclone …。$C は §2 を参照)。§2 と同じ --config を渡し、削除対象になるすべてのファイルが、そのジョブで削除する意図のあるファイルであることを確認します(対象の期間内にあり、そのジョブが保護する基準日以外の日付であり、該当する場合は Automatic_backup_* であること)。それ以外のファイルが含まれていれば、フィルターが正しくありません。Google Drive での削除は、デフォルトではゴミ箱に移動します(30 日間の追加の安全策)。クォータが厳しい場合に限り、drive-use-trash: false を追加してください。

5. 既存のバックログを整理する

保持設定を有効にする前から /backup にバックログがある場合:

  • ⚠️ rm ではなく Supervisor 経由で削除してください:ha backups remove <slug> を実行します。直接 rm を実行すると、Supervisor のバックアップ一覧が古い状態のままになります。
  • ⚠️ Supervisor の一覧は UTC の日時を表示し、ファイル名は現地時間を表示します。現地時間で 05:00 のバックアップは、一覧では前日として表示されます。日付の文字列ではなく、完全な ISO タイムスタンプ(または slug)で照合してください。
  • 操作の順序:最初にクラウド上のコピーの存在を確認し、その後でローカルのファイルを削除します。アップロードの初回テストでは、--bwlimit を付けて手動でコピーを実行し、リモート側のバイトサイズ(rclone ls)をソースファイルと比較してください。

6. エンドツーエンドで検証する

ssh ha 'bash -lc "ha apps logs 19a172aa_rclone_backup"'   # want: "scheduled jobs:" listing all
ssh ha "sudo docker exec $C rclone --config /homeassistant/rclone.conf ls gdrive:HA-Backups"   # $C from §2

scheduled jobs: の一覧に、§3 と §4 の6つすべてのジョブが表示されるはずです。dry_run: true が有効な間は、ls にジョブがアップロードしたファイルは表示されません。設定を切り替えた後(§7)に再実行してください。

bwlimit 1.5M で 611 MB のバックアップを転送するには約7分かかります。もっと速く完了する場合は、上限が適用されていません(ジョブログで、フラグがコマンドに渡されているか確認してください)。

7. CLI からオプションを変更する(例:dry_run をオフにする)

UI を使う人の場合、通常は Configuration タブ → ⋮ → Edit in YAML → Save → 再起動 が一般的な方法です。このセクションでは、SSH 経由でヘッドレス/エージェントによる変更を行う方法を説明します。

⚠️ ha apps には options サブコマンドがありません。オプションは Supervisor API 経由で設定してください。必ず オプションオブジェクト全体を送信します(取得 → 変更 → POST)。部分的なオブジェクトは送信しないでください。

ssh ha 'bash -lc "ha apps info 19a172aa_rclone_backup --raw-json"' > opts.json
python3 - <<'EOF'
import json
d = json.load(open('opts.json'))['data']['options']
d['dry_run'] = False
json.dump({'options': d}, open('payload.json', 'w'), ensure_ascii=False)
EOF
chmod 600 opts.json payload.json   # may hold password-typed options in plaintext — see below
ssh ha 'bash -lc "curl -sS -X POST -H \"Authorization: Bearer \$SUPERVISOR_TOKEN\" \
    -H \"Content-Type: application/json\" -d @- \
    http://supervisor/addons/19a172aa_rclone_backup/options"' < payload.json
ssh ha 'bash -lc "ha apps restart 19a172aa_rclone_backup"'
  • ⚠️ \\$SUPERVISOR_TOKEN としてエスケープしてください。 リモート側の親シェルは ログインシェルではないため、エスケープされていない $SUPERVISOR_TOKEN はそこで空に展開され、 API は誤解を招く 401: Unauthorized を返します(環境変数を読み込む bash -lc の内部で変数を展開する必要があります)。
  • Supervisor の REST info エンドポイントは password 型のオプションをマスクせずに返すため(haos-addon-deploy §4)、フィールド名からシークレットだと分かるとは限りません。変更のたびに chmod 600 を実行してください。
  • ブロックの実行後は、変更前の opts.json をロールバック用コピーとして保持し、 ha apps info --raw-json("dry_run": false であり、ジョブが保持されていること)とログの scheduled jobs: の一覧を確認してください。両方を確認できたら、 rm -f opts.json payload.json を実行してください。

コメント