upgrade-vm について
upgrade-vm は、IV 仮想マシン群を保守する担当者向けのアップグレードスキルです。通常は指定リリースのプロビジョナーを既存 VM で再実行し、新しいディスクやベースイメージが必要な場合のみ再作成します。検証と接続手順も定めています。
主な機能
- 01指定リリースで既存 VM を再プロビジョニング
- 02旧ディレクトリのチェックアウトを移行
- 03Shelley のバージョンとロックファイルを確認
- 04SSH 操作を順番に実行
- 05必要時に VM を破棄して再作成
活用シーン
- 01IV VM を指定リリースへアップグレード
- 02旧ディレクトリを使う VM を移行
- 03プロビジョニングと Shelley の稼働版を検証
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ kylelundstedt/ dotfiles --skill upgrade-vmインストール後、Claude Code で/upgrade-vmを入力して読み込まれたか確認します。
詳細
VM のアップグレード
IV VM は、LABEL exe.dev/install-shelley=true と独自のユニットによって Shelley を維持するベースイメージ ghcr.io/kylelundstedt/exeslim-dev で稼働し、ツール類は iv-provision リポジトリの provision-iv.sh から取得します。そのため、通常 VM の「アップグレード」は、プロビジョナーを新しいコミットで再実行するだけです。既存の VM 上で実行し、再作成もディスクの消去もテールネットの変更も必要ありません。
VM を完全に破棄して再作成するのは、今では例外的な対応です。ディスクを本当に新しくしたい場合や、再プロビジョニングでは反映できない形で exe.dev が管理するベースイメージが変更された場合に限り、必要となります。
パス A — 既存 VM での再プロビジョニング(デフォルト)
ユーザーは VM 名 と iv-provision リポジトリの 対象リリースタグ を指定します。
SSH コマンドは一度に 1 つだけ実行してください。並列 SSH は行わないでください。
テールネット経由で VM に接続する:<vm>.exe.xyz ではなく ssh <vm>
フリートの VM はテールネットノードなので、Tailscale SSH を使えば、キーなしで直接接続できます。exe.dev エッジ(ssh <vm>.exe.xyz)は、まだテールネットに参加していない VM のブートストラップ用の接続経路です。この接続には exe.dev の SSH キーが必要ですが、VM はそのキーを持っていないため、別の VM から実行すると完全に失敗します。この接続経路を使うのは、パス B でテールネットに参加する前だけにしてください。
必ず明示的なタグを指定する。--detach main は使わない
git checkout --detach main はローカルの main を解決します。フリートの VM では、これは最後にプロビジョニングされたときに取得されたままの状態であり、何か月も古いことがよくあります。コマンドは成功し、警告らしい表示も出さずに、古いレシピでプロビジョニングを実行します。タグを指定してください。
3.0.8 以降、プロビジョナーはプロビジョニング元のリビジョンを表示し、チェックアウトが上流より遅れている場合は警告します。実行するたびにこの行を確認してください。タイプミスが気づかれないままダウングレードされるのを防ぐ、唯一の手がかりです。
ssh <vm> "cd ~/iv-provision \
&& git fetch --tags --quiet \
&& git checkout --detach <tag> \
&& ~/iv-provision/provision-iv.sh \
&& ~/iv-provision/tests/smoke-provision.sh ~/iv-provision"
3.0.0 より前の VM の移行(~/iv-image)
名前変更より前にプロビジョニングされた VM では、チェックアウト先が ~/iv-image で、origin が旧名を指しています。ディレクトリ名を変更してリモートを書き換えるのではなく、削除して新たにクローンしてください。チェックアウトは破棄可能な成果物であり、VM の状態は保持していません。チェックアウトが生成するものはすべて別の場所(~/iv-provision.lock、~/.local/bin、~/.agents)にあるため、新たにクローンするほうが変更箇所が少なく、移行が不完全なリモート、古い detached HEAD、または誰も保持するつもりのなかったローカル編集を残すこともありません。
ssh <vm> "rm -rf ~/iv-image \
&& git clone --quiet https://github.com/kylelundstedt/iv-provision.git ~/iv-provision \
&& git -C ~/iv-provision checkout --detach <tag> \
&& ~/iv-provision/provision-iv.sh \
&& ~/iv-provision/tests/smoke-provision.sh ~/iv-provision"
github.int.exe.xyz ではなく、公開されている github.com からクローンしてください。古い repo-iv-image 統合は、名前が変更されて現在は解決できないリポジトリを対象としているため、HTTP 403 を返します。VM のリモートを決めつける前に、git -C ~/iv-image remote -v で確認してください。
~/iv-image にローカルコミットがある場合は、いったん停止して調査してください。VM のどの作業ツリーにも正式な権威はありませんが、作業を黙って破棄してもいけません。
これにより、ツールのバージョンが再固定され、ベンダー提供のスキルとエージェント設定が再インストールされ、~/iv-provision.lock が書き換えられます。次のように確認してください。
ssh <vm> "cat ~/iv-provision.lock"
Shelley のバージョン固定を、数分間隔で 2 回確認する
プロビジョニングログに問題がないことは、動作が正しい証拠にはなりません。kgl-songs では、2026-08-17 にバージョン 0.959 がインストール済みと報告されましたが、ソケットアクティベーションの競合(3.0.1 で修正済み)により、60 秒後には再び古いビルドがサービスを提供していました。バイナリをソケットで実際に応答しているものと比較し、直後と数分後にもう一度確認してください。
ssh <vm> "shelley version | jq -r .version; \
curl -fsS -H 'X-Exedev-Userid: probe' \
--unix-socket ~/.config/shelley/shelley.sock \
http://localhost/version | jq -r .version"
どちらもバージョン固定値と一致している必要があります。すでにバージョン固定値になっている VM では、Shelley が再起動されないこと(pin and self-update override already in effect; not restarting)と、Claude Code がダウングレードされないことを想定してください。claude と codex は最低バージョンであり、厳密なバージョン固定値ではありません。どちらかが発生した場合は、フリート全体の作業を続けず、停止して調査してください。
各 VM の処理後に ~/iv-provision.lock も確認してください。python3_version、tailscale_version、entire_version、entire_plugin_version、skills_count のすべてに値が入っている必要があります。
Claude Code と Codex に一度ログインする(Aperture リリース)
Aperture リリース以降、両方の CLI は VM 自身のサブスクリプションのログイン情報を使って
http://ai.dojo-sun.ts.net 経由で接続するため、それらがない VM では失敗します。Codex は
.../codex/responses から 401 Unauthorized、Claude Code は Not logged in が返されます。ログイン情報はホームディレクトリに保存されるため、その後のインプレースアップグレード後も保持されます。VM を再作成する場合(パス B)は、再度ログインが必要です。まず確認してください。
ssh <vm> "ls ~/.codex/auth.json ~/.claude/.credentials.json"
ログインが必要なものについては、所有者がブラウザーで承認します。
- Codex:
ssh <vm> 'codex login --device-auth'を実行すると URL と一度限りのコードが表示されます。https://auth.openai.com/codex/deviceで承認してください。 - Claude Code: 対話形式のみです。
ssh <vm>を実行し、claudeを起動してから/loginを実行してください。
モデルの上書きなしで、ゲートウェイ経由で両方が応答することを確認します。
ssh <vm> 'cd /tmp && claude -p "Reply with exactly: ok" \
&& codex exec --skip-git-repo-check "Reply with exactly: ok"'
ここでモデルエラー(例:claude-opus-5[1m] が「may not exist」と表示される)が発生する場合、CLI のデフォルトモデルは Aperture のプロバイダーリストにありません。ピン留めはゲートウェイ側にあります。VM ごとに回避策を講じず、ピンを更新してください。
本番 VM は最後に実施する
ほかのユーザーが依存するサービスを実行している VM(kgl-songs、telnyx-vm)は最後に、1 台ずつ実施し、その後もサービスが引き続き稼働していることを確認します。
~/iv-provision がまだ存在しない場合(古い VM)は、先にクローンしてください。bootstrap.md を参照してください。
正しく動作させるために、個人用オーバーレイを再実行する必要はなくなりました。 3.0.0 以降、provision-iv.sh は ~/.claude/settings.json をマージし、チームのファイルで定義されていないフックイベントを保持します。そのため、オーバーレイの SessionStart 自動更新はプロビジョニング後も維持されます。
以前は上書きされ、そのフックが気づかないうちに削除されていたため、この手順は修復のために存在していました。しかし、この対策は実際には機能しませんでした。iv-foundry-stage2 は 2026-08-17 にプロビジョニングされ、2026-08-18 に .hooks.SessionStart がなく、オーバーレイはインストール済みのままで、誰も気づいていない状態で見つかりました。フックのスクリプトは残るため、問題があるようには見えません。また、復元を担う更新フック自体が削除されたため、自動修復もできませんでした。
ただし、オーバーレイの独自の内容を最新にしたい場合(~/dotfiles のチェックアウトを更新する、新しい個人用スキルをインストールする場合など)は、実行する価値があります。
ssh <vm> "cd ~/dotfiles && git pull --ff-only && ./install.sh"
個人用 Mac:CLI もアップグレードする(dotfiles ユーザーのみ)
チームの手順には含まれません。Mac で個人用 dotfiles を使っていない場合はスキップしてください。dotfiles の install.sh は、すでにインストール済みの CLI をアップグレードしないため、単に再実行するだけでは、このリリースでフリート全体に引き上げられた最低バージョンに Mac が追いつきません。また、Mac も同じ Aperture ゲートウェイ経由で接続するため、古いままだと同じモデルエラーが発生します。フリート全体の作業が終わったら、各 Mac(klundstedt-mini、klundstedt-mbp)で次を実行してください。
cd ~/dotfiles && git pull --ff-only && ./install.sh --upgrade
mini はリモートから実施できます。ポート 22 は tag:dev にのみ開放されているため、開発用 VM を経由してください。ターミナルがない場合、install.sh は 1Password と Keychain のシークレット更新をスキップし、それ以外はすべて実行します(dotfiles 2026-10-10)。
ssh exedev@<dev-vm> 'ssh klundstedt@klundstedt-mini "cd ~/dotfiles && git pull --ff-only && ./install.sh --upgrade"'
2026-10-10 に確認済みです。この方法で Claude Code と Codex はアップグレードされます。Aperture に関係するのはこの部分です。GitHub リリースからインストールされたツール(gh、jq、rg、fzf など約 10 個)はアップグレードされません。バージョンの確認は匿名で行われます。gh のトークンはロックされた Keychain にあるため、GitHub の 1 時間あたり 60 回の API 制限に達し、現在のバージョンのままになります。削除されるものはありません。Claude Code の /login、シークレットの更新、これらのツールのアップグレードは、引き続き mini 自身のターミナルで実行する必要があります。mini のログイン Keychain は SSH セッションではロックされているためです。
パス B — 完全削除と再作成(必要な場合のみ)
これは VM のローカルディスクを消去します。状態を移行するのではなく、再プロビジョニングします。パス A では変更を反映できない場合にのみ使用してください。
順番に実行してください。SSH コマンドは一度に 1 つだけ実行してください。
1. ユーザーに確認する
これは破壊的な操作です。VM の名前と、ディスクの消去が許容されることを確認してください。
2. 古い VM を破棄する
置き換え用 VM を作成する前に、古い VM を破棄してください。古い Tailscale ノードはその後もホスト名で特定でき、新しい VM が参加する前に削除する必要があります。
ssh -o ConnectTimeout=30 exe.dev rm <vm>
3. 古い Tailscale ノードを削除する
置き換え用の VM が参加する前に古いノードを削除してください。削除しないと、新しい VM に -1 のサフィックスが付きます。このワークステーション固有の手順では、1Password CLI と Industry Vault アカウントを使用します。Tailscale OAuth クライアントから有効期間の短い(1 時間)アクセストークンを発行してください。以前の静的 API キーは失効済みです(2026-07)。OAuth の設定については tailnet.md を参照してください。認証情報が curl のプロセス引数に露出しないよう、標準入力経由で curl の設定を渡します。
TS_TOKEN=$(printf 'user = "%s:%s"\n' \
"$(op read 'op://Employee/Tailscale OAuth Dev/Client ID' --account industryvault.1password.com)" \
"$(op read 'op://Employee/Tailscale OAuth Dev/Client secret' --account industryvault.1password.com)" \
| curl --config - -fsS -d grant_type=client_credentials \
https://api.tailscale.com/api/v2/oauth/token \
| jq -er .access_token)
curl_with_tailscale_auth() {
printf 'header = "Authorization: Bearer %s"\n' "$TS_TOKEN" \
| curl --config - "$@"
}
NODE_ID=$(curl_with_tailscale_auth -fsSL \
https://api.tailscale.com/api/v2/tailnet/-/devices \
| jq -er --arg hostname "<vm>" \
'[.devices[] | select(.hostname == $hostname) | .id] | unique | if length == 1 then .[0] else error("expected exactly one matching node") end')
curl_with_tailscale_auth -fsSL -X DELETE \
"https://api.tailscale.com/api/v2/device/$NODE_ID"
unset TS_TOKEN
4. 古い SSH 状態を削除する
ssh-keygen -R <vm> 2>/dev/null || true
ssh -O exit <vm> 2>/dev/null || true
ssh -O exit <vm>.exe.xyz 2>/dev/null || true
5. 新しい VM を作成する(IV dev ベース)
exe.dev の VM タグ iv は、まだ統合のアタッチに結び付けられていません(2026-08-19 時点でこのタグが付いているのは iv-provision のみで、このタグによるアタッチはありません)。そのため、新しい VM ではステップ 7 で api-tailscale を明示的にアタッチする必要があります。この iv は exe.dev のタグであり、Tailscale の tag:dev とは無関係です。また、プロビジョニングリポジトリのリリースタグとも異なります。「2 つのタグシステム、同じ単語」については tailnet.md を参照してください。
VM のイメージは作成時に exe.dev によって固定されるため、ベースイメージを新しいものにするには、再作成するしかありません。現在の不変のビルド ID をパッケージページから取得してください。この手順の目的は既知のベースを使うことなので、ここでは変更可能なタグではなくビルド ID を使用します。exe.dev は変更可能なタグを最大 24 時間キャッシュします(:<date> と :<sha> も含みます。1 時間なのは latest/main/master のみです)。
ssh -o ConnectTimeout=30 exe.dev new --name=<vm> \
--image=ghcr.io/kylelundstedt/exeslim-dev:<date>.<run>.<attempt>
ユーザーが統合を指定した場合は、それらをアタッチします。
ssh -o ConnectTimeout=30 exe.dev integrations attach <integration> vm:<vm>
6. VM の起動を待つ
約 20 秒待ってから、ConnectTimeout=30 を指定して SSH を 1 回試します。
sleep 20
ssh -o ConnectTimeout=30 <vm>.exe.xyz echo "VM is up"
失敗した場合は 30~60 秒待って、もう一度だけ試してください。
7. tailnet に再参加してプロビジョニングする
join-tailnet スキルを使用し、その後 bootstrap.md に記載された起動手順一式を実行します(github-kylelundstedt-iv-provision をアタッチし、クローンして、provision-iv.sh を実行した後、作業リポジトリをクローンして provision-docsite を実行します)。
8. 検証する
tailscale status | grep <vm>
ssh <vm> "cat ~/iv-provision.lock"
SSH の運用ルール
- SSH は一度に 1 回だけ試してください。 SSH を並列で起動しないでください。
- 各コマンドが完了してから、次のコマンドを開始してください。
- SSH に失敗した場合は、30~60 秒待ってからもう一度だけ試してください。
- tailnet 上の操作には
ssh <vm>(Tailscale SSH)を使用してください。<vm>.exe.xyzはブートストラップ時のみ使用する接続先で、別の VM からは使えません。 - 3.0.9 以降、プロビジョナーは
iv-*という名前のプレフィックスではなく、tailnet のメンバーシップ(tailscale ip -4 %h)に一致する SSH 設定ブロックを書き込みます。そのため、フラグなしでssh kgl-songsとssh telnyx-vmを使用できます。3.0.9 より前に最後のプロビジョニングを行った VM では、再プロビジョニングされるまで-o StrictHostKeyChecking=accept-new -o User=exedevを渡してください。