EN | JA

はじめに

kanade — 奏 は多数の Windows 端末を一元管理するシステムです。operator はひとつの CLI / SPA から、数百台規模の PC に対してスクリプト実行・ソフトウェア配布・インベントリ収集・ライブのパフォーマンスデータ取得を一気にこなせます。構成要素:

構成要素役割
kanade-agent管理対象 PC 上で動く Windows サービス。NATS を購読し、コマンドを実行して結果を送り返します。
kanade-backendHTTP API + projector。状態を永続化し、SPA を配信し、operator 用エンドポイントを公開します。
kanade-clientオプションの Tauri デスクトップアプリ。エンドユーザー向け UI。
NATS serverコマンドのファンアウトと結果集約を担うメッセージブローカー。agent は NATS とのみ通信し、backend も NATS から読みます。
kanade CLIoperator が使うコマンドライン。バイナリの publish、ジョブの起動、状態の照会を行います。

このサイトは 2 種類の読者を想定しています:

  • 運用者 (kanade fleet の運用者) 向け — 各端末に ssh せずに各コンポーネントをアップデートする方法について解説しています(agent 経由のアップデート 参照)。
  • 開発者 (agent が実行する PowerShell ジョブを作成する人) 向け — スクリプトが動作する仕組みや制限事項、最近の agent バージョンにおける変更点について解説しています(agent 向けスクリプトを書く 参照)。

詳細なプロトコル / on-wire 仕様は Spec にあります (旧 single-page ドキュメント。docs サイトが整うにつれて章別に分割予定)。

開発者クイックスタート

このガイドでは、ローカルの kanade 開発環境をセットアップして起動する手順を説明します。

1. 前提条件

開始する前に、Windows マシンに以下がインストールされていることを確認してください。

  • Rust ツールチェーン (stable チャンネル)
  • cargo-make (cargo install --force cargo-make でインストール)
  • bun (SPA の依存関係管理とビルド実行用)
  • gsudo (ローカルのサービスデプロイテスト用)
  • nats-server (PATH から実行可能であること)

2. 初回セットアップ

ワークスペースのルートで以下のコマンドを実行し、git の pre-push フックを登録し、apm.yml で定義されたエージェントスキルをインストールします。

cargo make setup

3. 開発サンドボックスの起動

単一のコマンドを使用するだけで、ローカルホスト上に完全に分離されたマルチコンポーネント開発スタックを起動できます。

cargo make dev

このタスクは、ループバックサンドボックス内で以下のサービスを並行して実行します。

  1. nats-dev: ポート 4223 で待機する、認証なしの NATS ブローカー。
  2. backend-dev: 認証が無効化された、ポート 8081 で待機する開発用 API サーバー。
  3. agent-dev: ポート 4223 の開発用 NATS ブローカーと通信する、ローカルの開発用エージェント。
  4. web-dev: http://localhost:5173 で待機する、React SPA 用の Vite 開発サーバー。

Ctrl+C を押すことで、すべてのコンポーネントをクリーンに終了できます。

4. 複数エージェントフリートのシミュレーション

複数のマシンを管理しているときにのみ発生する動作(並行実行結果のプロジェクションや ID 衝突など)をデバッグするために、マルチエージェントサンドボックスを起動できます。

cargo make dev-fleet

これにより、NATS ブローカー、バックエンド、SPA に加えて、独立した ID(dev-pc-1dev-pc-2dev-pc-3)と隔離された状態データベースを持つ 3 つの個別開発用エージェントが起動します。

5. ローカルデプロイのテスト

Windows サービスとしてコンポーネントをインストールする全ライフサイクル(本番環境の模倣)をテストしたい場合は、ローカルデプロイスクリプトを使用します。

# Installs CLI, agent, backend, and NATS services locally via gsudo elevation
cargo make local-deploy

デプロイ後、実際の Windows サービスを確認して対話できます。完了したら、以下のタスクを使用してサービスを停止し、きれいに削除します。

cargo make local-undeploy

システムアーキテクチャ

kanade は、数百台の Windows エンドポイントを並行して、安全に、そして非同期で管理するように設計されています。

コンポーネントトポロジー

システムは 5 つの主要コンポーネントで構成されており、イベント駆動型の pub/sub 構造を介して調整されます。

graph TD
    subgraph Operator Session
        CLI[kanade CLI]
        SPA[React SPA]
    end

    subgraph Server Infrastructure
        Backend[kanade-backend]
        NATS[NATS Broker / JetStream]
    end

    subgraph Windows Endpoints
        Agent1[kanade-agent PC-1]
        Agent2[kanade-agent PC-2]
        Client[kanade-client Tauri App]
    end

    CLI -->|Command / Query API| Backend
    SPA -->|REST / WebSockets| Backend
    Backend <-->|State/PubSub| NATS
    Agent1 <-->|NATS-only Connection| NATS
    Agent2 <-->|NATS-only Connection| NATS
    Client <-->|Tauri IPC| Agent1

1. kanade-agent

各管理対象ホスト上で動作する高性能な Windows サービスです。

  • 役割: コア実行エンジン。
  • 通信: アウトバウンド専用の NATS 接続を確立します。インバウンドポートを開かないため、ファイアウォールに優しい設計です。
  • 機能: 安全に隔離された PowerShell サブプロセスを起動し、ハードウェア/ソフトウェアのスペック情報の収集、ライブパフォーマンスデータ(CPU、RSSメモリ、ディスクI/O)のストリーミング、およびローカルパッケージの管理を行います。

2. kanade-backend

中央の HTTP API およびプロジェクションサーバーです。

  • 役割: コマンドの調整、受信テレメトリの処理、およびオペレーター用 Web インターフェースの提供。
  • 状態管理: イベント、アクティビティログ、およびステータスレコードをローカルの SQLite データベースに永続化します。
  • プロジェクターパターン: NATS コマンドレスポンスストリームを購読し、受信したペイロードを解析して、リアルタイムで状態テーブルに反映(投影)します。

3. NATS ブローカー (with JetStream)

フリート全体のメッセージ転送レイヤーです。

  • 役割: 軽量かつ高スループットなメッセージブローカー。
  • JetStream: コマンドストリーム、ジョブ登録、およびファイルストレージ(パッケージやエージェントスクリプトを配信するための NATS オブジェクトストアバケットを使用)を保持します。
  • 疎結合: バックエンドとエージェントを切り離します。バックエンドがオフラインまたは再起動中であっても、エージェントは実行を継続して送信トレイにレコードをキャッシュし、接続が再開されたらそれらをプッシュします。

4. kanade-client

エンドポイント上のログインユーザーのデスクトップセッションで動作する、オプションの Tauri デスクトップアプリケーションです。

  • 役割: エンドユーザーとの対話(プロンプトダイアログ、通知、ユーザー向けダッシュボードなど)を提供します。
  • 通信: セキュリティで保護されたローカル IPC メカニズムを介して、ローカルの kanade-agent と状態を共有します。

5. kanade CLI

オペレーター向けの主要なコマンドラインツールです。

  • 役割: ソフトウェアアップデートのパッケージングと公開、ジョブマニフェストの送信と実行、およびコマンドラインからのライブフリートインベントリの照会。

セキュリティと信頼性の設計

アウトバウンド専用接続

エージェントは、アウトバウンド TCP 接続を開始することによってのみ NATS ブローカーと通信します。エンドポイント側でファイアウォールポートを開く必要がないため、横移動や外部からのポートスキャンのリスクを排除できます。

エージェントジョブのサンドボックス化

スクリプトを実行する際、エージェントは %ProgramData%\Kanade\agent-scripts 内にコマンドを配置(ステージング)し、カスタマイズされたランチャテンプレートを使用して実行します。管理者はジョブマニフェストを介して実行アイデンティティの設定を強制でき、run_as: system(昇格したシステム管理用)または run_as: user(制限されたディレクトリ ACL を使用して、アクティブなユーザーの資格情報の下で安全に実行する用)を指定できます。

運用概要

kanade の day-2 運用は 2 つのフローに分かれます:

  1. 直接インストール — まっさらなホストにバイナリと config を置いて Windows サービスを登録します。最初の agent、最初の backend、NATS サーバーをブートストラップするときに使います。スクリプト: scripts/deploy/agent.ps1scripts/deploy/backend.ps1scripts/deploy/nats.ps1。対象ホスト上で手動で実行します。

  2. agent 経由のアップデート — agent がいったん動き出せば、その agent 自身が ssh / RDP なしで同じホスト上の他コンポーネントをインストール・更新できます。operator はバイナリとスクリプト本体をブローカーに publish し、ジョブを起動。agent が fetch・検証・swap・サービス再起動を行います。day-2 運用の大半はこれ。

agent 経由フローの形は、何をアップデートする場合でも同じです:

operator host ─► kanade CLI ─► NATS broker ─► agent (on target host)
                    │                              │
                    ├── publish binary ────────────► fetches from
                    │   to OBJECT_APP_PACKAGES        OBJECT_APP_PACKAGES
                    ├── publish script ────────────► fetches from
                    │   to OBJECT_SCRIPTS             OBJECT_SCRIPTS
                    ├── register / update job ─────► reads job manifest
                    │                                 from `jobs` KV
                    └── exec job ──────────────────► PowerShell child
                                                      runs the script

コンポーネント別ガイド:

  • kanade-backend — HTTP / projector バイナリ
  • kanade-client — Tauri 製のエンドユーザーアプリ
  • NATS サーバー — ブローカー本体 (はい、ブローカー越しにブローカーをアップデートできます)
  • kanade-agent 自身 — agent の self-update 経路 (他 3 つと違い、汎用の OBJECT_APP_PACKAGES + script ペアではなく専用の rollout バケットを使います)

インストールとデプロイ

このセクションでは、本番環境またはステージング環境で kanade コンポーネントをネイティブの Windows サービスとしてブートストラップする方法を詳しく説明します。

デプロイモデル

本番ホストと管理対象エンドポイントは、kanade コンポーネントをバックグラウンドの Windows サービスとして実行します。これにより、高可用性と自動起動が保証されます。

サービス名トリプル / バイナリ設定ソース典型的なターゲット
KanadeNatsnats-server.exe保護されたレジストリ / レジストリ保存の CLI フラグ中央サーバー
KanadeBackendkanade-backend.exe保護されたレジストリ / 設定ファイル中央サーバー
KanadeAgentkanade-agent.exe保護されたレジストリ / ローカル状態 DB管理対象エンドポイント

1. 前提条件

  • ホスト OS: Windows 10/11 または Windows Server 2016+。
  • gsudo: 標準のユーザシェルから管理者権限でインストールを実行する(または管理者権限の PowerShell プロンプトからコマンドを実行する)ために必要です。
  • ネットワークルーティング: 管理対象エンドポイントが TCP 経由で NATS サーバーポート(デフォルト 4222)に到達できる必要があります。

2. NATS サーバー (ブローカー) のセットアップ

NATS サーバーはメッセージングのコアとして機能します。

  1. scripts/build-release.ps1 -Roles nats を使用して、デプロイメントバンドルをステージングします。
  2. 管理者権限でサービスをデプロイします:
    # Elevated PowerShell prompt
    & "dist\nats\deploy-nats.ps1" -NatsToken "your-secure-nats-token" -Recreate
    

これにより、KanadeNats サービスがインストールされ、ローカルシステムアカウントで実行されるよう構成され、JetStream データディレクトリがセットアップされ、安全な認証トークンが Windows レジストリ内にロックダウンされます。


3. バックエンド API & SPA のデプロイ

バックエンドは、オペレーター接続を管理し、イベントログを処理します。

  1. scripts/build-release.ps1 -Roles backend を使用して、バックエンドバイナリと React SPA バンドルをステージングします。
  2. サービスをデプロイします:
    # Elevated PowerShell prompt
    & "dist\backend\deploy-backend.ps1" `
        -NatsToken "your-secure-nats-token" `
        -StaticToken "your-operator-spa-bearer-token" `
        -ForceConfig -Recreate
    
  • -NatsToken: バックエンドをローカルの NATS サーバーに安全に接続します。
  • -StaticToken: オペレーターの CLI/SPA ログインに必要な API ベアラートークンを定義します。

デプロイスクリプトは、KanadeBackend Windows サービスを登録し、適切な ACL を設定して、エンドポイントを検証します。


4. 対象エンドポイントへのエージェントのインストール

管理対象のすべてのエンドポイント PC にエージェントをインストールします。

  1. scripts/build-release.ps1 -Roles agent を使用して、エージェントバンドルをステージングします。
  2. dist/agent フォルダの内容を対象 PC にコピーします。
  3. 対象 PC 上で、インストーラーを実行します:
    # Elevated PowerShell prompt
    & ".\deploy-agent.ps1" -NatsToken "your-secure-nats-token" -ForceConfig -Recreate
    

スクリプトの動作:

  • kanade-agent.exe をインストール先ディレクトリに配置します。
  • Windows レジストリパス (HKLM:\SOFTWARE\Kanade\agent) 内の設定と NATS トークンを保護します。
  • KanadeAgent サービスを登録して起動します。

サービスが起動すると、エージェントはアウトバウンド NATS 接続を確立し、コマンドストリームを購読し、オンラインのハートビートをフリートバックエンドに報告します。

agent 経由のアップデート

agent は万能インストーラーです。対象ホストで動き出してしまえば、他のどのコンポーネントをアップデートするときも operator がホストに直接触る必要はありません — agent が話している backend も、メッセージを運ぶブローカーも、agent 自身も対象です。

この章はコンポーネントごとに 1 ページずつあります:

全コンポーネントで共通の仕組み:

バケット / ストリーム用途
OBJECT_APP_PACKAGES汎用バイナリストレージ (backend、client、NATS サーバーなど)。キーは <name>/<version>
OBJECT_SCRIPTSマニフェストが script_object で参照する PowerShell スクリプト本体。キーは <name>/<version>
OBJECT_AGENT_RELEASESagent バイナリ専用。agent の rollout 専用の watcher / target_version フローを持つので APP_PACKAGES とは別バケットになっています。
agent_config (KV)レイヤード config — global / グループ別 / PC 別。target_version はここに置かれます。
jobs (KV)ジョブカタログ。各エントリは operator が exec できるマニフェストです。

CLI コマンド一覧:

コマンド動作
kanade app publish <name> <version> <file>OBJECT_APP_PACKAGES にアップロード。
kanade script publish <name> <version> <file>OBJECT_SCRIPTS にアップロード。
kanade job create <yaml>jobs KV にジョブマニフェストを upsert。
kanade exec <job-id> --pcs <pc> [--pcs <pc> …]登録済みジョブを PC 群に対して起動。
kanade agent publish <file>agent バイナリをアップロード (バージョンは PE VERSIONINFO から自動抽出)。
kanade agent rollout <version> --pc \| --group \| --global指定スコープの target_version を切り替え。各 agent は self-update watcher 経由でこれを拾います。

kanade-backend のアップデート

backend は管理対象ホスト 1 台 (またはそれ以上) で Windows サービスとして動いています。agent 経由のアップデートとは、そのホスト上で稼働する agent がサービスを停止し、バイナリを差し替えて再起動することを指します。これにより、operator は対象ホストに一度もログインする必要がありません。

end-to-end フロー

┌── operator host ──────────────────────────────────────────┐
│  1. build kanade-backend.exe                              │
│  2. kanade app publish kanade-backend <v> <exe>           │
│  3. edit deploy-backend.ps1 (set $AgentSource* knobs)     │
│  4. kanade script publish deploy-backend <v> <edited.ps1> │
│  5. kanade job create install-kanade-backend.yaml         │
│  6. kanade exec install-kanade-backend --pcs <host>       │
└────────────────────────────────────────────────────────────┘
                      │
                      ▼
┌── target host (running kanade-agent as LocalSystem) ──────┐
│  • agent receives the Command on commands.pc.<host>       │
│  • fetches deploy-backend.ps1 from OBJECT_SCRIPTS         │
│    sha-verifies it (`script_object` machinery, #214)      │
│  • stages it under                                        │
│    C:\ProgramData\Kanade\agent-scripts\<UUID>\            │
│    kanade-<UUID>.ps1                                      │
│  • runs `powershell -File <launcher>` (PR #230 fix)       │
│  • launcher invokes the user script via `& '...'`         │
│    so [CmdletBinding()] / param() headers parse           │
│  • script downloads kanade-backend.exe from               │
│    OBJECT_APP_PACKAGES (via /api/app-packages/…)          │
│    sha-verifies it (separate hash, on the exe itself)     │
│  • Stop-Service KanadeBackend                             │
│  • copy exe over C:\Program Files\Kanade\…                │
│  • Start-Service KanadeBackend                            │
│  • exit 0 — result published to NATS                      │
└────────────────────────────────────────────────────────────┘

sha チェックが 2 段ある理由は意図的: スクリプト本体のハッシュは agent が実行前に検証 (スクリプトの完全性)、バイナリのハッシュはスクリプトが swap 前に検証 (バイナリの完全性、operator が $AgentSourceSha256 で指定)。

ステップごとの手順

1. kanade-backend をビルド

cargo build --release -p kanade-backend

出力: target/release/kanade-backend.exe

2. バイナリを publish

kanade app publish kanade-backend 0.43.0 target/release/kanade-backend.exe

これでバイナリが OBJECT_APP_PACKAGES/kanade-backend/0.43.0 にアップロードされ、sha-256 digest が表示されます。digest を控えておきましょう — 後でスクリプトに lowercase-hex 形式で書き込みます。

3. scripts/deploy/backend.ps1 を編集

ローカルコピーを作って、先頭の 4 つの $Agent* ノブをセットします:

$AgentSourceUrl       = 'http://kanade-backend.example.com:8080'
$AgentSourceVersion   = '0.43.0'
$AgentSourceSha256    = '<kanade-backend.exe の lowercase hex>'
$AgentSourceAuthToken = '<backend HTTP API 用 bearer>'

スクリプトの他の部分は触らないでください。このノブによって「agent モード」(backend からダウンロード) と「手動インストールモード」(ローカルフォルダからの読み込み) が切り替わります。

$AgentSourceSha256Get-FileHash kanade-backend.exe -Algorithm SHA256 の hex 形式の値です。

kanade app publish の出力にある base64url 形式しか手元にない場合は decode します。CLI から出る base64 は URL-safe で padding なしのことがあるので、FromBase64String に通す前に padding を補う必要があります:

$b64 = '<paste the SHA-256= value here, without the SHA-256= prefix>'
$b64 = $b64.Replace('-', '+').Replace('_', '/')
if ($b64.Length % 4) { $b64 += '=' * (4 - $b64.Length % 4) }
[BitConverter]::ToString([Convert]::FromBase64String($b64)).Replace('-', '').ToLowerInvariant()

Python の場合: python -c "import base64; print(base64.urlsafe_b64decode('<b64>' + '=' * (-len('<b64>') % 4)).hex())"

$AgentSourceAuthToken は 2026-05-26 の live test 以降必須です。backend の /api/app-packages/<name>/<ver> エンドポイントは token なしだと HTTP 401 を返します。auth なしの lab 環境のときだけ空のままで問題ありません。

4. 編集したスクリプトを publish

kanade script publish deploy-backend 0.43.0 .\deploy-backend.edited.ps1

アップロード先は OBJECT_SCRIPTS/deploy-backend/0.43.0

5. ジョブを登録 / 更新

リポジトリの configs/jobs/installers/install-kanade-backend.yaml がテンプレートです。version:script_object: をいま publish したバージョンに合わせて編集してから upsert:

id: install-kanade-backend
version: 0.43.0
execute:
  shell: powershell
  script_object: deploy-backend/0.43.0
  timeout: 300s
  run_as: system
require_approval: true
kanade job create jobs\install-kanade-backend.yaml

run_as: system は必須です: Stop-Service / Start-Service / sc.exe はいずれも admin が必要です。本番環境では agent は LocalSystem で動いているので問題ありません。

6. 起動

kanade exec install-kanade-backend --pcs <backend-host>

CLI はすぐに exec_id を返します。実際のインストールは対象ホスト上で非同期に走ります。

7. 確認

backend の results エンドポイントを叩く (もしくは SPA の Activity ビューを見る):

curl -H "Authorization: Bearer <token>" `
  "http://<backend>/api/results?limit=5"

自分の exec_idexit_code: 0 で、stdoutkanade-backend <new-version> で終わっていれば成功です。

ありがちなトラブル

症状原因対処
stderr に [CmdletBinding()] / param() の parse erroragent が 0.42.2 より古い (-Command モードで動いている)まず kanade agent rollout で agent をアップグレード (agent self-update 参照)。
Start-BitsTransfer : HTTP status 401$AgentSourceAuthToken が空だが backend が auth を要求している値をセットする。
Start-BitsTransfer : The transfer encountered an error / job state TransientErrorBITS service not running, or target machine's WinHTTP can't reach $AgentSourceUrlGet-Service BITS; check WinHTTP proxy with netsh winhttp show proxy (BITS uses WinHTTP, not IE/WinINet).
sha256 mismatch — expected=<x> actual=<y>スクリプトの hash が publish されたバイナリと一致しない再 publish するか hash を計算し直す。スクリプトは swap の に abort するので、既存のインストールは無事です。
ジョブは実行されたが kanade-backend が起動してこない対象ホストでのサービス起動失敗 / config の不整合対象ホストの C:\ProgramData\Kanade\log\backend.*.log を読む。agent 経由で kanade logs <pc> で取れる (実装後) か、直接ファイルを引き上げる。

kanade-client のアップデート

Tauri デスクトップクライアントは backend と同じやり方で端末に配布します: バイナリは OBJECT_APP_PACKAGES、スクリプトは OBJECT_SCRIPTS、ジョブは jobs KV。形は backend のアップデート と同じで、スクリプト内容とパッケージ名だけが違います。

backend のアップデートとの違い

項目kanade-backendkanade-client
(再) 起動するサービスKanadeBackend (Windows サービス)なし — クライアントはユーザーが起動する
インストール先%ProgramFiles%\Kanade\kanade-backend.exe%ProgramFiles%\Kanade\kanade-client.exe
リポジトリ内のスクリプトscripts/deploy/backend.ps1configs/jobs/installers/scripts/install-kanade-client.ps1 (マニフェストの script_file パス)
マニフェストの参照方法script_object: deploy-backend/<v>script_file: scripts/install-kanade-client.ps1 (マニフェスト YAML からの相対パス; kanade job create 時に inline 展開)
atomic swap のやり方サービス停止 → コピー → サービス起動<exe>.new にステージ → Move-Item<exe>.old を削除
インベントリへの射影なし (backend は自分自身でバージョンを報告)inventory: ブロックが PC ごとのクライアントバージョンを SPA Inventory ページに出す

両方の形が使えます — script_object (OBJECT_SCRIPTS から hash で参照、agent が必要時に fetch) と script_file (kanade job create 時にスクリプト本体をマニフェストに inline 展開)。client マニフェストは歴史的経緯で どちらの形式もサポートされています — script_object (OBJECT_SCRIPTS からハッシュで参照、agent が必要に応じて取得) と script_file (kanade job create 時にスクリプト本体をマニフェストにインライン展開)。クライアントマニフェストは歴史的経緯で script_file を使用し、backend マニフェストは Object Store パスのテストのために script_object へ書き換えられました。

ステップごとの手順

1. kanade-client をビルド

cargo build --release -p kanade-client

出力: target/release/kanade-client.exe

2. バイナリを publish

kanade app publish kanade-client 0.42.0 target/release/kanade-client.exe

3. configs/jobs/installers/scripts/install-kanade-client.ps1 を編集

先頭の 3 つのノブをセット:

$BackendBase    = 'http://kanade-backend.example.com:8080'
$Version        = '0.42.0'
$ExpectedSha256 = '<kanade-client.exe の lowercase hex>'

backend auth が有効なら $ClientSourceAuthToken を backend の bearer にセットしてください(agent が /api/* に使うのと同じトークンです)。/api/app-packages/kanade-client/<v> ルートが認証なしの開発環境やスモークテスト環境では空のままで問題ありません。scripts/deploy/backend.ps1$AgentSourceAuthToken ノブと同じ構成です。

4. ジョブを登録 / 更新

configs/jobs/installers/install-kanade-client.yaml:

id: install-kanade-client
version: 0.42.0
execute:
  shell: powershell
  script_file: scripts/install-kanade-client.ps1   # `job create` 時に inline 展開 (マニフェスト YAML からの相対パス)
  timeout: 180s
  run_as: system

require_approval: true

inventory:
  display:
    - { field: version, label: Version }
    - { field: path,    label: Install path }
  summary:
    - { field: version, label: Client version }
kanade job create jobs\install-kanade-client.yaml

inventory: ブロックは projector に「スクリプトの stdout は単一の JSON blob で、version / path フィールドが SPA の Inventory ページに反映される」と伝えます。operator は fleet 全体のテーブルから取り残し端末を見つけられ、ssh は不要です。

5. 起動

kanade exec install-kanade-client --pcs <host> [--pcs <host> …]

もしくはグループ単位で:

kanade exec install-kanade-client --groups office

6. SPA で確認

SPA の Inventory ページを開く (もしくは /api/inventory?app=kanade-client を叩く) と、対象ホストが新しいバージョンを報告しているはずです。

NATS サーバーのアップデート

管理対象ホスト上のブローカーをアップデートするのは最も特徴的なケースです。なぜなら agent は ブローカー越しにブローカーと話す ためです。NATS を停止するとジョブの途中で agent との接続が切断されます。これに対しては、2 つの仕組みが組み合わさって対処しています:

  1. 再接続。 agent の NATS クライアントはブローカー再起動時に自動再接続します。人の介入は不要。
  2. outbox。 ブローカーが落ちている間に発生したジョブ結果は %ProgramData%\Kanade\outbox\ に貯められ、接続が戻り次第再送されます。新しい NATS サーバーが立ち上がるとすぐに result row が backend に届きます。

したがってフローは backend のアップデート と同じです。スクリプトはサービスを停止してバイナリを差し替え再起動し、agent はブローカーの切断期間を透過的に許容します。

NATS アップデート固有の注意点

懸念実際
result row はロストする?いいえ — outbox がブローカー停止中を跨いで保持し、再接続時に drain します。
SPA からアップデートできる?はい、他のジョブと同じです — kanade exec install-kanade-nats --pcs <broker-host>
NATS が再起動してこなかったら?result は outbox にずっと残ります。operator はブローカーホストの outbox/ を復帰失敗の早期サインとして監視することが推奨されます。
新しい NATS バージョンに互換性がなかったら (JetStream のアップグレードなど)?まず canary 1 台に rollout (--pcs <one-broker>)、outbox と backend の健全性を見てから fleet 全体に展開。SPA クエリには 5 分の cache TTL があるので canary の状態は数分以内に反映されます。

手動インストール (ブートストラップ)

初回インストール (ブローカーホストにまだ agent がない状態) では直接ワークフローを使います:

.\scripts\build-release.ps1 -Roles nats       # fetches nats-server.exe
                                              # from github.com/nats-io/nats-server/releases
.\scripts\deploy\nats.ps1 -NatsToken '<token>'

これで nats-server.exe%ProgramFiles%\Kanade\ に、nats-server.conf%ProgramData%\Kanade\config\ にインストール (bearer token を平文で保存するので ACL を SYSTEM + Administrators のみに絞ります)、KanadeNats Windows サービスを登録、TCP 4222 (broker) と 8222 (monitoring HTTP) を開放、サービスを起動します。

agent 経由のアップデート (定常運用)

scripts/deploy/nats.ps1 ships the $AgentSource* knobs (#234), so the broker can be upgraded through the fleet — no RDP to the broker host.

1. nats-server.exe をビルド or 取得

どちらか:

.\scripts\build-release.ps1 -Roles nats   # fetches the binary

…もしくは github.com/nats-io/nats-server/releases から直接ダウンロード。

2. バイナリを publish

kanade app publish nats-server 2.10.20 .\nats-server.exe

3. deploy-nats.ps1 を編集

The pattern matches deploy/backend.ps1:

$AgentSourceUrl       = 'http://kanade-backend.example.com:8080'
$AgentSourceVersion   = '2.10.20'
$AgentSourceSha256    = '<nats-server.exe の lowercase hex>'
$AgentSourceAuthToken = '<backend HTTP API 用 bearer>'

4. publish + 登録 + exec

kanade script publish deploy-nats 2.10.20 .\deploy-nats.edited.ps1
kanade job create jobs\install-kanade-nats.yaml
kanade exec install-kanade-nats --pcs <broker-host>

The job manifest ships at configs/jobs/installers/install-kanade-nats.yaml:

id: install-kanade-nats
version: 2.10.20
execute:
  shell: powershell
  script_object: deploy-nats/2.10.20
  timeout: 300s
  run_as: system
require_approval: true

5. 確認

ブローカーが復帰すると outbox が drain され、/api/results に result row が出てきます。新しい NATS バージョンはブローカーの monitoring エンドポイントで確認:

curl http://<broker>:8222/varz | python -m json.tool | rg version

なぜ独立した「broker update」機構が要らないのか

初期の設計では「ブローカー越しにブローカーを更新する鶏卵問題」を避けるために専用のブートストラップチャネル (agent が broker update 専用に使う並列 NATS 接続) を検討していました。outbox + 再接続のペアによってこれは不要に: 結果は「失われる」のではなく「遅延するだけ」。トランスポートはひとつ、メンタルモデルもひとつで済みます。

kanade-agent 自身のアップデート

agent の self-update は唯一 OBJECT_APP_PACKAGES + script_object ジョブを 使わない コンポーネントです。agent は ssh なしで現在実行中の自分自身のバイナリを差し替える必要があり、汎用 install ジョブよりタイトなループのため専用の仕組みを持っています。

仕組み

バケット / キー用途
OBJECT_AGENT_RELEASESagent バイナリ。キーは <version>。rollout watcher が agent アップデートだけに反応するように、OBJECT_APP_PACKAGES とは別バケット。
agent_config.<scope>.target_version各スコープ (global / グループ / pc) があるべきバージョン。agent の self_update ループがこれを watch しています。

フロー:

1. agent.self_update watches agent_config for target_version
2. If target_version != my agent_version:
   a. Pull `OBJECT_AGENT_RELEASES/<target_version>` to <exe>.new
   b. Sha-verify against the bucket's recorded digest
   c. Atomic swap: <exe> ← <exe>.new (via SCM stop/start)
   d. New binary boots, watcher arms again, loop closes

rollout watcher は cold broker (ホスト再起動後に agent と broker が同時に立ち上がる、など) に耐えなければなりません。#226 以前は最初の get_object_store 呼び出しで Err(_) => return; してしまうと watcher が恒久的に死んでいて、その起動セッション中は agent が二度と self-update しませんでした。#226 以降は watcher が backoff で再試行し、broker に到達できるまであきらめません。

ステップごとの手順

1. agent をビルド

cargo build --release -p kanade-agent

出力: target/release/kanade-agent.exe

2. publish

kanade agent publish target/release/kanade-agent.exe

CLI は PE VERSIONINFO リソースからバージョンを自動抽出します — --version フラグ不要、ラベルとバイナリの不一致もあり得ません。

3. roll out

スコープを選びます。canary 1 台から:

kanade agent rollout 0.42.2 --pcs canary-01

ping で確認:

kanade ping canary-01     # agent_version should flip to 0.42.2
                          # within a few seconds

問題なければ範囲を広げる:

kanade agent rollout 0.42.2 --groups office --jitter 5m
# or fleet-wide
kanade agent rollout 0.42.2 --global --jitter 30m

--jitter は実際の swap タイミングをある幅で分散させ、大きな fan-out 時に全ホストの OS サービスマネージャを同時に叩かないようにします。100 台以上の fleet では推奨。

4. 確認

kanade agent current
# → target_version = 0.42.2 (global)

あとは SPA の Agents ページ (もしくは /api/agents) で fleet 全体をスポットチェック: agent_version 列は jitter + 約 30 秒の heartbeat 間隔以内に新バージョンに収束するはず。

ありがちなトラブル

症状原因対処
kanade agent rollout が "version not in OBJECT_AGENT_RELEASES" と言うtypo もしくはスコープ違いkanade agent currentkanade jetstream object list agent_releases で再確認。
数分経っても kanade ping <host> が古いバージョンを返すagent が self-update しなかった — watcher が死んでいる (#226 以前の agent) か、ホストが broker に到達できない対象ホストの %ProgramData%\Kanade\log\agent.*.log をチェック。self_update が無言なら (「checking target_version」ログがない)、agent が古すぎる; deploy-agent.ps1 で手動ブートストラップ。
agent が flap する: 起動するも exit_code: 1 ですぐ落ちるこのホストでは新バイナリが起動できない (config の不整合、依存欠落など)。SCM の failure-actions により再起動が試みられるが再びクラッシュする — Event Viewer に Service Control Manager のエラーが連続して記録されるロールバック: kanade agent rollout <prev-version> --pcs <host>。次の watcher tick でホストは元のバージョンに戻ります。

なぜ別のバケット / スコープか?

OBJECT_APP_PACKAGES<name>/<version> をキーにした汎用 blob ストアです。agent rollout パターンに必要なのは:

  • agent の変更だけに反応する watcher (たくさんの名前が入ったバケットを poll するのではなく、特定の 1 つの KV キーを安価に watch)。
  • 「既知の全バージョン」ではなくスコープごとの「現在の target」セマンティクス — agent_config.<scope>.target_version こそが「自分は何を動かすべきか」の答えで、agent 側で列挙する必要がない。
  • operator 向け UX (kanade agent publish / rollout) が kanade app publish とは別サブコマンドツリーに分けるに足るくらい違う。

というわけで agent は OBJECT_AGENT_RELEASES + レイヤード config KV を、他のコンポーネントは OBJECT_APP_PACKAGES + アプリ別ジョブを共有します。

kanade をホストから取り除く (undeploy)

本番でのロールバック経路。rollout で何かが壊れた、ホストを廃止する、再インストール用にまっさらな状態に戻したい — そんなときホストから kanade を剥がすために、deploy と対になる undeploy スクリプトをコンポーネントごとに 1 本ずつ用意しています。

構成要素DeployUndeploy
Agentscripts/deploy/agent.ps1scripts/undeploy/agent.ps1
Backendscripts/deploy/backend.ps1scripts/undeploy/backend.ps1
NATS serverscripts/deploy/nats.ps1scripts/undeploy/nats.ps1
Client (Tauri)configs/jobs/installers/scripts/install-kanade-client.ps1 (agent-driven)scripts/undeploy/client.ps1

All four are admin-only and idempotent — safe to re-run after a partial uninstall, safe to run when the component is already gone (each step logs "not present, skipping" and moves on).

デフォルトの姿勢: 安全側

フラグなしで実行するとスクリプトは:

  • Windows サービスを停止します。
  • SCM からサービスを unregister します (エントリが実際に消えるまで待つので、後続の再 deploy が pending な削除と race しません)。
  • %ProgramFiles%\Kanade\ からインストール済みバイナリを削除します。中途半端な <exe>.new / <exe>.old の swap 残骸もまとめて削除。
  • deploy スクリプトが作成した inbound のファイアウォールルールを削除します (-KeepFirewall で skip 可 — 外部の WAF / グループポリシーがルールを管理している場合に有用)。
  • %ProgramData%\Kanade\ 配下 (config、log、JetStream データ、SQLite DB、…) は 残します。フォレンジック / rollback / 再 deploy が state を失わずに進められるように。
  • HKLM:\SOFTWARE\kanade\<role>\* のレジストリ secret も 残します

That's enough for the common case: "this host's kanade is misbehaving, get it off without destroying state".

-Purge: 破壊的クリーンアップ

追加で:

  • そのコンポーネント固有の %ProgramData%\Kanade\ 配下エントリを削除します。重要なのは そのコンポーネント自身のファイルだけ — agent / backend / NATS は同じ root を共有しているので、各スクリプトは他のコンポーネントのファイルには触れません。
  • 対応する HKLM:\SOFTWARE\kanade\<role>\* キーを削除します (-KeepSecrets を併せて渡すとスキップ — 複数コンポーネントで同じ bearer を共有しているときに有用)。
構成要素-Purge が削除するもの
Agentconfig\agent.tomllogs\agent.*.logoutbox\HKLM:\SOFTWARE\kanade\agent\
Backendconfig\backend.tomldata\*.db* (SQLite — 過去の results / inventory が消える)、logs\backend.*.logHKLM:\SOFTWARE\kanade\backend\
NATSconfig\nats-server.confnats\ (JetStream — KV / Object Store / streams すべて消える)、logs\nats*.log
Client追加なし (per-user な state はまだ存在しない)

⚠️ 危険なのは undeploy-nats.ps1 -Purgeundeploy-backend.ps1 -Purge。前者は fleet 全体の JetStream state (agent_releases、app_packages、scripts、jobs、agent_config、results stream) を消し、後者は projector の過去の SQLite を消します。どちらも out-of-band バックアップ無しではリカバリ不能。スクリプトは実行前に目立つバナーを出します。

ロールバックの定石

canary 1 台で rollout が壊れたとき

# On the canary, as Admin:
.\scripts\undeploy\agent.ps1            # safe default
# kanade is now off the host. Re-deploy when ready:
.\scripts\deploy\agent.ps1 -SourceDir C:\path\to\prev-version

ホストを恒久的に廃止する

.\scripts\undeploy\agent.ps1 -Purge

dev box を再インストール用にまっさらにする

.\scripts\undeploy\agent.ps1 -Purge
.\scripts\undeploy\backend.ps1 -Purge   # ⚠️ SQLite gone
.\scripts\undeploy\nats.ps1 -Purge      # ⚠️ JetStream gone
.\scripts\undeploy\client.ps1
# Now nothing about kanade exists on the box.

state は触らず壊れたサービスだけ作り直す

.\scripts\undeploy\backend.ps1          # safe default: SQLite intact
.\scripts\deploy\backend.ps1 -Recreate  # fresh service registration, same data

undeploy がやらないこと

  • It doesn't notify the rest of the fleet that this host has gone away — the backend will keep listing it under "agents" until its heartbeat ages out (/api/agents staleness threshold). If you want it removed from the SPA immediately, delete the row via the backend API after undeploy.
  • It doesn't roll back the deployed binary to a previous version. "Roll back" in this script's vocabulary means "remove entirely"; if you want to swap to an older version, re-run the matching deploy-*.ps1 against a folder containing the older binary.
  • agent を取り除いても NATS 側の state には触れません — agent の target_version エントリは agent_config.pcs.<pc> 配下の KV に残ります。必要なら server 側で kanade jetstream kv del agent_config pcs.<pc>.target_version で掃除してください。

Broker sizing & scaling

kanade runs on a single NATS + JetStream broker. As the fleet grows (hundreds → thousands of agents) the broker, not the agents, is the scaling bottleneck. This page covers the per-agent footprint, what the #512 work changed, the single-node limits to watch, and how to capture real numbers during a scale-up so you can decide whether to add a startup splay or grow/cluster the broker.

Per-agent consumer footprint

Each running agent holds a handful of JetStream consumers — one ordered push consumer per KV watch, plus its durable command-replay consumer:

ConsumerSource
agent_config watch (key-filtered)config_supervisor
agent_groups watch (membership → effective config)config_supervisor
agent_groups watch (membership → subscriptions)groups.rs
schedules watchlocal_scheduler
jobs watchlocal_scheduler
fleet_config freeze watch (single key)local_scheduler
EXEC durable replaycommand_replay

That is ~7 consumers per agent. The count is roughly fixed per agent — it does not shrink with key-filtering — so the broker-side total grows linearly with the fleet:

Fleet size~Consumers on the broker
15~105
500~3,500
3,000~21,000

What v0.43.96 changed (and what it did not)

#512 (shipped in v0.43.96) attacked the super-linear costs, not the consumer count:

  • #832agent_config key-filtered watch. The agent watches only global, pcs.<self>, and its groups.<g> keys instead of the whole bucket (watch_all). This removes two blow-ups:
    • Per-PC write fan-out: a write to one pcs.<id> no longer reaches all N agents (it used to, with N−1 classifying-and-dropping it).
    • Reconnect re-sync storm: re-sync is now 3–5 direct gets per agent instead of a keys() + per-key walk over the whole bucket — aggregate O(N²) → O(N).
  • #839 — single-key freeze watch. fleet_config holds only KEY_FREEZE, so the freeze watcher uses watch(KEY_FREEZE) instead of watch_all + a client-side filter.

What is unchanged: the ~7-consumers-per-agent footprint, and the schedules / jobs watch_all (those are a shared catalog every agent must evaluate for targeting — removing them needs server-side targeting, a separate change). So at 3,000 agents you still provision for ~21,000 consumers and the connection/consumer-create burst of a synchronised reconnect.

Single-node limits and the reconnect herd

Two things to size for on a single-node JetStream:

  1. Steady-state footprint — ~21,000 consumers at 3,000 agents live in the JetStream meta (Raft) layer and cost memory + file handles. Size the broker host's RAM and max_file/max_memory JetStream limits accordingly, and watch the meta layer's health.

  2. The reconnect herd — when many agents reconnect at the same instant (broker restart, the morning power-on wave, a network event hitting many PCs), they re-establish connections and recreate their consumers in a burst. Key-filtering already cut each agent's re-sync to a few cheap gets, so the dangerous O(N²) read-storm is gone — but the connection + consumer-create burst is still O(N) and synchronised.

    Note that random, unsynchronised reconnects (one laptop's wifi flap) are not a herd — only fleet-wide synchronised events are.

Levers, in order of preference

  1. Broker sizing first. Give the single node enough RAM / file limits for the steady-state consumer count at your target N. This is the primary lever; everything else is secondary.
  2. Startup splay — only if measured. A deterministic per-PC delay (hash(pc_id)) before the reconnect re-sync would smear the consumer-create / connection burst across a window. It is not in the product yet by design: PR1 already removed the quadratic term, and async-nats' reconnect backoff + nats_retry's ±25% jitter already spread the burst somewhat. A splay also adds latency to every single reconnect (including herd-less blips), so it is a net cost unless a herd actually stresses the broker. Decide from data (next section): if a synchronised event shows consumer-create latency, connection backlog, or JetStream API errors, add the splay (gated to the first sync after a Disconnected → Connected, capped a few seconds).
  3. Reduce consumers per agent. Fold watches where possible (the freeze watch is already a single key; the two agent_groups watches are a candidate to merge) and keep KV history shallow (agent_config / agent_groups are at history: 1).
  4. Cluster JetStream. Beyond what one node can hold, move to a JetStream cluster. This is the last resort and the biggest change.

Measuring at scale (retro-analysis)

You usually cannot watch a production broker live during a ramp. Capture the numbers instead, with the bundled collect: job, and review the bundle afterwards.

Run it on the backend / NATS host, ideally during or right after a synchronised reconnect event (the herd moment is what decides the splay question):

kanade exec collect-broker-health --pcs <backend-host-id>

The job (configs/jobs/collect-broker-health.yaml) samples the broker over ~3 minutes and uploads a bundle to OBJECT_COLLECTIONS; download it from the SPA Collect page (or hand the zip to your reviewer). It is read-only and needs zero pre-setup: it reads NATS' unauthenticated HTTP monitoring port (default 8222 — the same /jsz endpoint kanade already curls for jetstream status), so no nats CLI on the SYSTEM PATH and no token are required. Tune the window with KANADE_BH_SAMPLES / KANADE_BH_INTERVAL_SEC (and KANADE_BH_MON_PORT if the broker's http_port differs) on the target if needed.

The bundle contains:

  • Time series (connz-*.json, jsz-*.json) — connection count (/connz) and JetStream consumer count (/jsz) per sample. A spike here at the herd moment is the splay signal.
  • Consumer footprint (jsz-full.json) — /jsz?consumers=true&streams=true: the full consumer list; confirms the ~7N total and which streams hold them.
  • Server health/resources (varz.json, healthz.json) — /varz (memory, CPU, connections, slow consumers) and /healthz status.
  • Log tails (redacted) — backend and nats-server.

What to look for

  • Smooth consumer/connection counts across the samples, healthy /healthz, head-room on mem/cpu in /varz → the broker absorbed the event; no splay needed, just keep sizing ahead of N.
  • Spiky connection backlog or consumer-create latency at the event, JetStream API errors, or mem/cpu pegged → the herd is real → add the startup splay (lever 2) and/or grow the broker.

The #828 downgrade-flap regression is checked separately from the OBS_EVENTS agent_update timeline (via the backend API), not by this job — that data is already queryable fleet-wide.

agent 向けスクリプトを書く

agent が実行する PowerShell スクリプトは ほぼ 通常の .ps1 ファイルです。このページは、スクリプトのソースを見るだけでは分からない罠をまとめます。

agent はスクリプトをディスクに staging してから -File で実行する

PR #230 (agent version 0.42.0+) 以降、agent は次の動作をします:

  1. スクリプト本体を temp の .ps1 に書き出します。Windows なら %ProgramData%\Kanade\agent-scripts\<UUID>\kanade-<UUID>.ps1、それ以外 (dev のみ) なら $TMPDIR/kanade-agent-<UUID>/kanade-<UUID>.ps1
  2. その隣に launcher .ps1 を書き、UTF-8 コンソールエンコーディングを設定してから & '<your-script>' @args でユーザースクリプトを呼びます。
  3. powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File <launcher> を spawn します。

結果として、あなたのスクリプトでは:

  • 先頭に [CmdletBinding()]param(...)書けます。launcher の call-operator 境界が独自スコープを生み、その中ではヘッダーが有効です。
  • $PSCommandPath が operator のソースパスと一致することを 期待しないでください — staged 後の temp ファイルパスになります。
  • $PSScriptRoot書き込まないでください (次節参照)。

Pre-0.42.0 agents used powershell -Command "<body>", which parses the body as a command-line expression and rejects [CmdletBinding()] as a syntax error. If you see "Unexpected token '[CmdletBinding()]'" in stderr, the host's agent is too old — upgrade it (see agent self-update).

run_as: user のとき $PSScriptRoot は読み取り専用

run_as: user (または system_gui) のとき、子プロセスはログオン中のユーザーとして動きます — staged ファイルを書いた LocalSystem の agent ではありません。staging ディレクトリは %ProgramData% から ACL を継承していて、Users には Read & Execute は許可されるが Modify は許可されません

つまり:

# OK from any run_as
Get-ChildItem $PSScriptRoot              # list contents
Get-Content   $PSScriptRoot\anything     # read

# NG from run_as: user (access denied)
New-Item    -Path $PSScriptRoot\out.txt
Set-Content -Path $PSScriptRoot\log.log

代わりに $env:TEMP$env:LOCALAPPDATA、ユーザープロファイル配下の絶対パスのいずれかに書いてください。run_as: system (SYSTEM が自分の staged dir に書ける) であっても、スクリプト終了時にディレクトリは掃除されるので、隣接ファイル書き込みはいずれにせよ壊れやすいです。

実行 identity 一覧

run_as: (マニフェスト)子プロセスの identity$PSScriptRoot を読める$PSScriptRoot に書けるadmin 権限あり
system (デフォルト)LocalSystem✓ (ただし無意味、GC される)はい
userログオン中のユーザー✗ access deniedいいえ
system_guiLocalSystem (ユーザーセッション内)✓ (ただし無意味、GC される)はい

system_gui is the "PsExec -i -s" pattern — admin privilege but visible in the user's desktop session (useful for GUI tools that need both elevation and an interactive window).

stdout vs Write-Host

backend の result projector はスクリプト出力として stdout を読みます。マニフェストに inventory: ブロックがあると、stdout は単一の JSON blob としてパースされます。

Do NOT use Write-Host for progress chatter in an inventory script. Contrary to a common assumption, Write-Host does NOT stay on a separate host stream once the agent runs your script with stdout redirected — its output bleeds INTO the captured stdout. That extra text breaks the projector's single-JSON-blob parse (serde_json::from_str over the whole stdout, in crates/kanade-backend/src/projector/results.rs::upsert_inventory), and your inventory fact is silently dropped (the backend logs stdout was not JSON).

Send progress chatter to stderr via [Console]::Error.WriteLine(...). stderr is captured into the result's separate stderr field, which the projector ignores, so stdout stays a single clean JSON line.

[Console]::Error.WriteLine("Downloading...")  # → stderr (logged, ignored by projector)
Write-Output ($obj | ConvertTo-Json)          # → stdout (the ONE JSON line, parsed)

Keep stdout to exactly one Write-Output — anything else on stdout (a stray Write-Host, Write-Output, or a cmdlet's pipeline output) that isn't the expected JSON will fail the inventory parse. See configs/jobs/installers/scripts/install-kanade-client.ps1 for a worked example.

デフォルトで UTF-8

launcher はスクリプト起動前に [Console]::OutputEncoding = UTF-8$OutputEncoding = UTF-8 を設定するので、ホストのシステムコードページに関係なくあなたの stdout / stderr は UTF-8 になります。日本語 / DE / KR / CN を含む operator スクリプトも、ホスト個別の workaround なしに SPA Activity ビューで正しく表示されます。

明示的に OEM / CP932 / Shift-JIS 出力が必要なら ($OutputEncoding を無視する legacy CLI を呼ぶケースなど)、launcher の prelude が走ったあとにスクリプト内で自分で設定してください — あなたの代入が優先されます。

ネイティブコマンドの exit code

スクリプトが成功した native コマンドで終わると全体の exit は 0 — これは PowerShell のデフォルトです。native コマンドが失敗 ($LASTEXITCODE -ne 0) してハンドリングしないと、PowerShell は依然として 0 で終了します — $ErrorActionPreference = 'Stop'救ってくれません

Windows PowerShell 5.1 (Windows エンドポイントのデフォルト、agent の powershell.exe が解決する先) は $ErrorActionPreference に関わらず native コマンドの非ゼロ exit を non-terminating として扱います。PowerShell 7.3+ で $PSNativeCommandUseErrorActionPreference = $true が追加されてこれを terminating にできますが、デプロイターゲットでは使えません。必ず $LASTEXITCODE を明示的にチェックしてください。

agent も $LASTEXITCODE を自動 propagate しません — そうしてしまうと、スクリプトが native エラーを正しく処理した場合でも非ゼロで終わってしまうからです。特定の native 呼び出しの exit code をスクリプトの exit に反映させたい場合は自分で propagate してください:

& git pull
if ($LASTEXITCODE -ne 0) { throw "git pull failed with exit code $LASTEXITCODE" }
# or, if you want the exact native code propagated:
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

通常は throw のほうが望ましい — クリーンな PowerShell エラーレコードを生成し (trap { … break } クリーンアップパターンが捕まえられる) かつ非ゼロで終わるからです。exit $LASTEXITCODE は呼び出し側が正確な exit code を気にする場合に向きます。

タイムアウト

マニフェストの timeout: は agent によって適用されます。発火すると agent は PowerShell プロセスに child.kill() を呼びます — graceful な shutdown も trap も finally もありません。それを前提に設計してください:

  • スクリプトが timeout * 0.6 で終わるように実行時間を見積もり、余裕を残す。
  • 明示的解放が要るリソース (staging dir、lock ファイル) のクリーンアップには trap { ... ; break } を使う — trap は terminating エラーで発火し、agent の kill では発火しません。timeout ケースには頼らないこと。
  • If you need cooperative cancellation, poll a sentinel file or a registry value and exit early. The agent has no way to send the script a graceful "wrap up" signal.

実行中ジョブの kill

kanade kill <exec_id> は agent が購読する kill メッセージを publish します。受信すると agent は child.kill() を呼びます — timeout 経路と同じ hard-kill。operator にはただちに Killed マークの result row が届き、終了前に agent がキャプチャできた stdout / stderr が付きます。

開発者ワークフローとコントリビューション

このドキュメントでは、kanade コードベースで作業するコントリビューター向けの標準的な開発ワークフロー、lint/テストの要件、および VCS ブランチのガイドラインについて説明します。


1. クオリティゲート (Pre-Push & CI)

プッシュや PR の送信を行う前に、ローカルのテストスイートがすべてグリーンである必要があります。これは GitHub Actions で実行される自動チェックと同じものです。

# フォーマットチェック、clippy チェック、ターゲットテスト、および cargo ロックチェックを実行しますcargo make check
  • FMT & Clippy: 私たちは厳格なゼロワーニングポリシーを維持しています。強力な設計上の正当な理由がない限り、#[allow(clippy::...)] を散布しないでください。
  • TDD (テスト駆動開発): Kent Beck の TDD 手法に従ってください。最初に失敗するテストを書いて「何を」行うかを定義し、次にそれを満たすコードを実装します。

2. renri によるワークツリー管理

隔離された機能開発のために、私たちは renri を使用して軽量なリポジトリワークツリーを管理します。これにより、ステージの汚染を防ぎ、メインチェックアウトをクリーンに保ち、瞬時にタスクを切り替えることができます。

なぜ renri なのか?

Git と Jujutsu (jj) がコロケーションされた環境では、ワークツリーを手動で管理するのは複雑になります。renri は、VCS 固有のワークツリー作成(設定されている場合は jj を優先)とクリーンアップを自動的にラッピングすることで、これを簡素化します。

よく使うコマンド

# 隔離されたワークツリーを作成します (存在するならデフォルトで Jujutsu を使用)renri add feat/your-awesome-feature

# Git ネイティブのワークツリーを強制的に作成します (jj をバイパス)renri --vcs git add feat/your-awesome-feature

# マージ後にワークツリーをクリーンアップして削除しますrenri remove feat/your-awesome-feature

# 古くなった、または破損したワークツリーをガベージコレクションしてクリーンアップしますrenri prune

注意: ワークツリー作成時に自動的に cargo-make の on-add フックが呼び出され、リモートの参照を取得して APM 設定を直ちにセットアップします。


3. コロケーションされた Jujutsu (jj) & Git ワークフロー

開発環境は、Git と Jujutsu がコロケーションされるよう設定されています。ローカルのバージョン管理には、安全で競合のないコミットモデルを持つ jj を好んで使用します。

ガイドライン

  • main への直接プッシュ禁止: すべての変更は Pull Request 経由でマージされる必要があります。
  • ブランチ/ブックマークの命名規則:
    • feat/... は新機能用。
    • fix/... はバグ修正用。
    • chore/... はインフラ、依存関係の更新、またはリリース用。
  • コミットメッセージ: コミットメッセージ、PR タイトル、本文は英語で記述してください。
  • バージョン更新: リリースのバージョン更新は、main への PR と自動タグ付けパイプラインを介してのみ管理されます。手動で git tag を実行しないでください。

4. ドキュメントポリシー

ドキュメントは、コードの変更と常に完全に同期している必要があります。機能を追加または変更した場合は常に、以下を実行してください:

  • コード内のコメントや docstring を更新し、「なぜ」そのようにしたのかを説明します(「どのように」やっているかを単に再記述するコメントは避けてください)。
  • 関連する book ページ(book/src/ 配下に英語で記述)を更新します。
  • 翻訳テンプレートジェネレーターを実行して、ローカライズカタログ(ポファイル)を同期します。

仕様 (旧 single-page)

フルのプロトコル / on-wire 仕様はまだ book に取り込まれていません。オーソリティはリポジトリの single-file 版です:

docs/SPEC.md on GitHub

このセクション配下に章分割するのは、operator / 開発者ガイドが落ち着いてからの follow-up とします。

設定リファレンス

kanade サービスは、TOML ファイル、環境変数、またはレジストリパスから読み込まれる構造化された設定に依存しています。


1. エージェント設定

エージェントは KANADE_AGENT_CONFIG 環境変数を介して設定を検索し、存在しない場合はネイティブパスにフォールバックします。

開発用設定 (configs/agent.dev.toml)

# 開発用設定スキーマ
[agent]
id = "dev-pc"
nats_url = "nats://localhost:4223"
data_dir = "target/dev-data/agent"

[log]
level = "debug"
file = "target/dev-data/agent/logs/agent.log"

設定パラメータ

フィールド説明環境変数による上書き
agent.idStringユニークなハードウェア識別子 (pc_id)。KANADE_DEV_AGENT_ID (テンプレート化)
agent.nats_urlStringNATS ブローカー of ネットワークアドレス。KANADE_NATS_URL
agent.data_dirPath送信トレイのスクリプト、状態データベース、およびローカルの補完データをキャッシュするルートパス。KANADE_AGENT_DATA_DIR
log.levelStringログ出力レベルの冗長度 (error, warn, info, debug, trace)。RUST_LOG
log.filePathローリングログの書き出し先ファイルパス。-

2. バックエンド設定

バックエンドの調整レイヤーは KANADE_BACKEND_CONFIG で指定されたファイルから設定を取得し、指定がない場合はデフォルトの構造体を登録します。

開発用設定 (configs/backend.dev.toml)

[backend]
listen_addr = "127.0.0.1:8081"
nats_url = "nats://localhost:4223"
database_url = "sqlite://target/dev-data/backend/state.db"

[auth]
# 認証設定

設定パラメータ

フィールド説明環境変数による上書き
backend.listen_addrStringHTTP/WebSocket トラフィック用のネットワークバインド文字列。KANADE_BIND_ADDR
backend.nats_urlString対象とする NATS ブローカーの URL。KANADE_NATS_URL
backend.database_urlStringSQLite データベースへの接続文字列。DATABASE_URL
auth.disableBooleanオペレーターのトークン検証を無効にするには true を設定します (開発環境でのみ有効)。KANADE_AUTH_DISABLE

3. Windows レジストリ統合

本番環境では、セキュリティに敏感なトークン(NATS クライアントトークンや管理用 API ベアラートークンなど)は、プレーンテキストファイルではなく、保護された Windows レジストリに保存されます。

キーパス

  • エージェント設定: HKLM:\SOFTWARE\Kanade\agent
  • バックエンド設定: HKLM:\SOFTWARE\Kanade\backend

これらのレジストリパスはローカルの ACL 設定で保護されており、SYSTEM および指定されたオペレーターにのみ読み取り権限が厳密に制限されます。