メインコンテンツまでスキップ

冪等なブートストラップスクリプトの設計

新しいマシンに開発環境を再現するための「導入スクリプト」を、何度実行しても安全(冪等) に設計するための要素技術です。

冪等性とは​

冪等(idempotent)とは、同じ操作を何度実行しても結果が変わらない性質です。環境構築スクリプトに求められる冪等性は「既に導入済みのものはスキップし、足りないものだけを追加する」ことです。

1回目: パッケージを導入する
2回目: 何もせず成功する (既に導入済み)

これにより、以下のような場面で安心して再実行できます。

  • 途中で失敗した後のリトライ
  • 定義を更新した後の差分反映(ドリフト修正)
  • 新規マシンでの一発復元

基本パターン: check-then-install​

冪等性の基本は「状態を確認してから、必要なときだけ実行する」ことです。以下は apt パッケージを冪等に導入する関数の例です。

install_apt() {
local -a pkgs=("$@")
local -a missing=()
local p
for p in "${pkgs[@]}"; do
dpkg-query -W -f='${Status}' "$p" 2>/dev/null \
| grep -q 'install ok installed' || missing+=("$p")
done
if [ ${#missing[@]} -eq 0 ]; then
echo "すべて導入済み"
return 0
fi
sudo apt-get update
sudo env DEBIAN_FRONTEND=noninteractive apt-get install -y "${missing[@]}"
}

ポイントは「導入済みかどうかを毎回判定」し、未導入のものだけを対象にすることです。

同様に、コマンドの存在確認には command -v を使います。

have() { command -v "$1" >/dev/null 2>&1; }

if have starship; then
echo "導入済み: $(starship --version | head -1)"
else
curl -fsSL https://starship.rs/install.sh | sh -s -- -y -b "$HOME/.local/bin"
fi

堅牢性のための定型句​

シェルスクリプトの信頼性を高める定番の設定を冒頭に置きます。

set -euo pipefail
オプション効果
-eコマンドが失敗したら即終了
-u未定義変数の参照でエラー
-o pipefailパイプライン途中の失敗も検知

さらに、ログ出力を統一するヘルパーを用意すると可読性が上がります。

log() { printf '\n==> %s\n' "$*"; }
warn() { printf 'WARN: %s\n' "$*" >&2; }

dry-run(確認モード)​

実際に変更を加える前に「何をするか」だけを表示する dry-run モードは、冪等スクリプトの重要な安全装置です。実行と表示を 1 つの関数にまとめます。

run() {
if [ "$DRY_RUN" = 1 ]; then
printf '[dry-run] %s\n' "$*"
else
"$@"
fi
}

リモートスクリプトの実行も、dry-run では URL 表示のみに留めるのが安全です。

remote_sh() {
local url="$1"; shift
if [ "$DRY_RUN" = 1 ]; then
echo "[dry-run] curl -fsSL $url | sh -s -- $*"
return 0
fi
curl -fsSL "$url" | sh -s -- "$@"
}
注意

curl | sh は便利ですが、実行前に内容を確認できないというセキュリティ上の注意点があります。信頼できる配布元のスクリプトに限定し、可能なら内容やハッシュを確認しましょう。

フラグによる出し分け​

「sudo を伴う処理」「オプション機能(デスクトップ環境など)」はフラグで出し分けます。

DRY_RUN=0; SKIP_SUDO=0; WITH_DESKTOP=0
for arg in "$@"; do
case "$arg" in
--dry-run) DRY_RUN=1 ;;
--no-sudo) SKIP_SUDO=1 ;;
--with-desktop) WITH_DESKTOP=1 ;;
*) echo "unknown option: $arg" >&2; exit 2 ;;
esac
done

sudo の要否は実行環境で変わるため、need_root で吸収するのが定石です。

need_root() {
if [ "$(id -u)" -eq 0 ]; then "$@"; else sudo "$@"; fi
}

対話確認を省略したい CI 用途には、環境変数による制御も有効です(例: ASSUME_YES=1)。

冪等性の境界と、いつ別ツールに切り替えるか​

シェルスクリプトは軽量で学習コストが低い一方、状態が複雑になるほど冪等性の担保が難しくなります。判断の目安は以下の通りです。

条件推奨
単一マシン・ツール導入が中心シェルスクリプト(今回の構成)
複数台・宣言的に状態を管理したいAnsible などの構成管理
環境ごと完全再現したいdevcontainer / Nix

「まず冪等シェルスクリプトで始め、複雑化したら Ansible や Nix へ」という段階的な移行が現実的です。

典型例: 冪等ブートストラップの構成​

ここまでのパターンを組み合わせた、冪等ブートストラップスクリプトの典型的な構成は以下の通りです。

  • install_apt による apt パッケージの冪等導入
  • have + remote_sh によるツール導入(nvm / starship / uv / 各種 CLI)
  • --dry-run / --no-sudo / --with-desktop フラグ
  • set -euo pipefail と log / warn ヘルパー
  • 最後に chezmoi apply で dotfiles を適用

構成の全体像は 開発環境のコード化 を、dotfiles 適用は chezmoi による dotfiles 管理 を参照してください。