ある朝、いつも使っていた CLI の公式ページに「2026-06-18 をもって個人向けの提供を終了します」と書いてあったら、どうしますか。
あわてて手元のマシンからアンインストールして、後継ツールを curl | bash で入れ直す——それで一台は片付きます。一台は、です。開発マシンが複数あって、しかもセットアップを Nix で自動化していると、話はもう少しやっかいになるのですが、ここが見落としがちなところです。「次にセットアップを回したとき、古いツールが復活しないか」「後継ツールが二重に入らないか」を、設定ファイル側で保証しないといけない。手で消したつもりが、次の nix run でゾンビみたいにしれっと復活してくる——あれが復活してくるの、地味にイヤなんですよね。
この記事は、Gemini CLI のサポート終了アナウンスを受けて、dotfiles のセットアップスクリプトと Home Manager の activation 処理を組み替えた実践記録です。「設定ファイルを冪等に書く」という建前はよく聞くけど、突然の EoL 通知が来たとき実際どうなの? という話です。「無いときだけ入れる」存在チェックや、後継ツールが吐くローカルファイルの扱いなど、セットアップ側を壊れにくくする勘どころを実際のコードと一緒に見ていきます。
何が起きたか:パッケージ定義から 1 行消すだけでは済まない
きっかけは、Google が提供していた個人向け CLI のサポート終了でした。当時の作業メモには、こう残しています。
Google が 2026-06-18 に無料/個人ユーザー向け Gemini CLI の提供を終了し Antigravity CLI へ移行するため。後継の Antigravity CLI は既に home-manager で自動インストール済み。
ここで素直に考えると、やることは「パッケージ定義から gemini-cli の行を消す」だけに見えます。実際、mise(ツールバージョン管理)の設定から該当エントリを削除するコミットはそれ一発でした。
でも、これだけだと困ったことが起きます。
- 古い CLI をすでに入れてしまったマシンでは、設定から消しても実体が残る
- 後継ツールを入れる処理が雑だと、セットアップを回すたびに再ダウンロードや上書きが走る
- 後継ツールが吐き出すローカルファイルが、毎回 git の untracked として差分に現れ続ける
「定義を消す」と「環境を移行する」は別の作業です。後者をサボると、セットアップの再現性がじわじわ崩れていきます。順番に潰していきます。
後継 CLI は「無いときだけ入れる」——activation hook の冪等化
まず後継ツールの投入から。Home Manager には activation script という仕組みがあって、nix run .#update のようにセットアップを適用するたびに実行されます。ここに「後継 CLI のインストーラを毎回叩く」処理をベタ書きすると、適用のたびに curl | bash が走ってしまう。これは避けたい。
そこで「実行ファイルがまだ無いときだけインストーラを叩く」という、存在チェック付きの hook にしました。実装はこれだけです。
{
pkgs,
lib,
config,
...
}:
{
home.activation.installAntigravityCli = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
if [ ! -x "${config.home.homeDirectory}/.local/bin/agy" ]; then
export PATH="${
lib.makeBinPath [
pkgs.curl
pkgs.coreutils
pkgs.gnutar
pkgs.gzip
pkgs.perl
]
}:$PATH"
$DRY_RUN_CMD ${pkgs.curl}/bin/curl -fsSL https://antigravity.google/cli/install.sh \
| ${pkgs.bash}/bin/bash
fi
'';
}
ポイントは 3 つあります。
1. 存在チェックが冪等性の核。if [ ! -x ".../.local/bin/agy" ] で、実行ファイルがすでにある(-x で実行可能)なら何もしません。だから 2 回目以降の適用ではインストーラが一切走らない。新しいマシンの初回だけ curl | bash が実行されます。「無いときだけやる」を一行で表現できるのが、この書き方の良いところです。
2. インストーラに必要なコマンドを PATH に明示注入。curl だけでなく tar・gzip・perl まで makeBinPath で渡しています。インストーラの中身が何を呼ぶか分からないので、汎用的なアーカイブ展開系を先に通しておく。Nix の世界はシステムの /usr/bin に頼れない(純粋性を保つため PATH が絞られる)ので、ここを省くと「自分のマシンでは完璧に動くのに、まっさらなマシンだと tar: command not found で死ぬ」という、いちばん発見が遅れるタイプのハマり方をするわけです。(正直、一度やりました。)
3. $DRY_RUN_CMD を前置して dry-run に対応。Home Manager は --dry-run で適用を試せますが、$DRY_RUN_CMD を頭に付けたコマンドは dry-run 時に「実行されず表示されるだけ」になります。検証時にうっかり本物のインストーラを叩かない、という安全装置です。
そして hook の本体にはバージョン固定もアップデート処理も書いていません。最新版への追従は、ツール自身が持っているバックグラウンドの自己更新機能に丸投げしています。当時のコミットにもこう書きました。
~/.local/bin/agyが存在しない時のみ公式 install.sh を実行する activation hook を追加。以降は agy 自身の background self-update に最新版追従を委譲する。
これは意図的な責務分割です。「初回の存在保証」はセットアップ側の仕事、「最新版への追従」はツール側の仕事。両方をセットアップ側で抱え込むと、nix run .#update のたびに最新版チェックの HTTP リクエストが走って遅くなるし、ツール側の更新ロジックと二重管理になります。activation hook の冪等性を考えるときの定番パターンについては、symlink の入れ子問題を Nix で解決した記事でも別の角度から触れています。
GUI 側は宣言的に、ローカルファイルは無視リストへ
後継ツールには CLI だけでなくデスクトップアプリ版もあったので、こちらは Homebrew Cask の定義に 1 行足して宣言的に管理対象へ入れました。cask は「あるべき状態」を書けば Nix 側が差分を埋めてくれるので、CLI のような存在チェックは要りません。宣言的に書ける部分は宣言的に、インストーラ任せにせざるを得ない部分だけ手続き的な hook で補う——この線引きが、セットアップを薄く保つコツです。
もう一つ地味に効いたのが、後継ツールが生成するローカルディレクトリを .gitignore に追加したことです。
dotfiles リポジトリは「設定だけ」を git 管理したい。でもツールは初回起動時に .antigravitycli/ のようなキャッシュやステートのディレクトリをホームに作ります。これを無視リストに入れておかないと、git status が毎回汚れて、本当に見たい差分が埋もれます。後継ツールを導入した同じタイミングで .gitignore を手当てしておくと、後で「これ何の差分だっけ」と悩まずに済みます。
EoL 通知に対するセットアップ側の defensive coding まとめ
突然のサポート終了通知は、開発ツールを使っていれば避けて通れません。今回の移行で、セットアップ側がやるべきことを 4 つに整理できました。
- 定義の削除と環境の移行を分けて考える。パッケージ定義から 1 行消すのは移行の入口でしかなく、既存インストールの扱いと後継ツールの投入は別作業です。
- 後継ツールは「無いときだけ入れる」。存在チェック付きの activation hook にすれば、初回だけインストーラが走り、2 回目以降は何もしない。冪等性はこの一行の条件分岐に集約されます。
- 責務を分割する。初回の存在保証はセットアップ側、最新版への追従はツール自身の自己更新に委譲する。両方を抱え込むと遅くなるし二重管理になります。
- ローカルファイルは無視リストへ、依存は浅い経路へ。ツールが吐くステートは
.gitignoreに入れ、インストールは postinstall のような後段ダウンロードに頼らないバックエンドを選ぶ。
セットアップを自動化していると、つい「動いているから触らない」になりがちです。でも EoL 通知は、普段見ないインストール経路や存在チェックの抜けを点検する良いきっかけにもなります。dotfiles を Nix で組む全体像についてはNix Flakes で dotfiles を管理した実験記録にまとめてあるので、これからセットアップを宣言的にしていきたい方はあわせてどうぞ。



