🧰

mise bootstrapでMacとUbuntuの環境構築をひとつのdotfilesにまとめる

この記事の本文は筆者が執筆し、LLMを使って添削しています。

はじめに

これまでMacの環境構築はNixで管理しており、Ubuntuは特に管理してませんでした。miseにパッケージ・dotfiles・OSの設定をまとめて宣言できるmise bootstrapが入ったので、いい機会だと思いそちらへ移行しました。

Nix自体素晴らしいソフトウェアです。しかしながら昨今の事情を鑑みて提供元がリリースしているバイナリを使いたかったこと、自分にとってはそこまで厳密な定義は不要だったことの2点から乗り換えました。

元々はMac専用の設定だったのですが、自宅のUbuntuデスクトップも同じリポジトリで管理したくなったので、OSごとに設定を分けられるように整理しました。

mise bootstrapとは

config.tomlにパッケージ・dotfiles・macOSのdefaults・systemdのサービスなどを宣言しておくと、mise bootstrapの1コマンドで環境構築が行えます。 miseはもともとランタイムやCLIツールの管理に使っていたので、ツールを増やさずに済むのが良いところです。

構成

最終的に次の形になりました。

  • .config/mise/miserc.toml: auto_env = true
  • .config/mise/config.toml: 共通のsettings・tools・dotfiles
  • .config/mise/config.macos.toml: Homebrew・macOSのdefaults・Touch IDでのsudo
  • .config/mise/config.linux.toml: apt・snap・aptリポジトリ・systemdのサービス
  • .zshrc / .zshenv: OSごとの差分は$OSTYPEで分岐

~/.config/mise自体をリポジトリへのシンボリックリンクにしておき、dotfilesは[dotfiles]でシンボリックリンクを張っています。

詳しくは以下のリポジトリをご覧ください。

auto_envでOSごとに設定を分ける

miseにはconfig.<env>.tomlを共通のconfig.tomlに加えて、config.<env>.tomlを環境ごとに重ねて読み込める仕組みがあります。 私物Macと仕事Macをmiserc.local.tomlのenvで切り替える使い方を紹介している記事もありました。

今回分けたいのはマシンではなくOSなので、auto_envを使いました。 miserc.tomlで有効にすると、linuxやmacosといったOS名が自動でenvとして扱われ、config.linux.tomlやconfig.macos.tomlが読み込まれます。

auto_env = true

マシンごとの設定ファイルが不要なので、cloneしてシンボリックリンクを張るだけで済みます。 明示的に指定したenvのほうが優先されるので、あとから私物・仕事で分けたくなってもconfig.work.tomlを足すだけで併用できます。

分けないとどうなるか

最初はMac用のconfig.tomlをそのままUbuntuで読ませてみましたが、brew:やbrew-cask:の項目まで処理しようとしてエラーになりました。 HomebrewはLinuxでも使えるので、Linuxでも有効なパッケージマネージャーとして扱われるようです。 エントリごとにos = "macos"を付けるやり方もありますが、項目数が多いのでファイルごと分けました。

Ubuntu側の設定

aptのパッケージ

Macでbrewから入れていたものをaptに置き換えつつ、Ubuntuで実際に使っているものを足していきました。

[bootstrap.packages]
"apt:git" = "latest"
"apt:tailscale" = "latest"
"apt:code" = "latest"
"apt:wezterm" = "latest"
"snap:spotify" = "latest"

漏れがないかはapt-mark showmanualの結果と突き合わせて確認しました。 思っていたより、aptで管理できていないものが多かったです。

特に沼ったのは1Password CLIで、miseのaqua-backendで管理しようと思ったところ、アプリのCLI結合が使えなかったためapt・brewで入れてあげる必要があります。

外部のaptリポジトリも宣言できる

VS CodeやChrome、Tailscaleなどは外部のaptリポジトリから入れる必要があります。 [bootstrap.apt.sources]にリポジトリと署名鍵を書いておくと、miseが/etc/apt/sources.list.d/mise-<name>.sourcesを作ってくれます。

[bootstrap.apt.sources.vscode]
uris = "https://packages.microsoft.com/repos/code"
suites = "stable"
components = "main"
key = "https://packages.microsoft.com/keys/microsoft.asc"
fingerprint = "BC52 8686 B50D 79E3 39D3 721C EB3E 94AD BE12 29CF"

fingerprintは必須で、ダウンロードした鍵のうち指定した指紋のものだけが使われます。 鍵はSigned-Byに埋め込まれるので、そのリポジトリだけで信頼されます。

いくつかハマりどころがありました。

  • もともとあったvscode.sourcesなどが同じリポジトリを別の鍵で指していると、aptがSigned-Byの衝突で失敗するので、古い方を消してから適用しました
  • WezTermのリポジトリはdists/*/という構成なので、suitesとcomponentsに"*"を指定しました
  • Slack・Discord・Zoomなど、aptリポジトリがなく.debを直接配っているアプリは宣言できません

systemdのサービス

[bootstrap.services]で既存のsystemdユニットの起動・有効化も宣言できます。 今回はtailscaledだけ書きました。

[bootstrap.services.tailscaled]
state = "running"
enabled = true

ほかにもユーザー単位のsystemdユニットやファイアウォール([bootstrap.linux.firewall])も書けます。

グローバルに置くツールとプロジェクトに置くツール

aptで入れていたcmakeやninjaなどは、一度miseのaquaバックエンドに寄せました。 ただ、ビルドツールはプロジェクトごとにバージョンが違うので、グローバルではなく各プロジェクトのmise.tomlで入れるようにしました。 グローバルに残したのは、bat・lsd・ripgrepなどどこでも使うCLIツールだけです。

tokeiは最新版のビルド済みバイナリが配布されておらず、aquaでは入れられなかったので、apt・brewのままにしています。

zshの設定

.zshrcはOSで分岐させて、1つのファイルを両方で使っています。

if [[ $OSTYPE == darwin* ]]; then
  # Homebrew(mise が /opt/homebrew に入れる)
  path=(/opt/homebrew/bin /opt/homebrew/sbin $path)
  fpath+=(/opt/homebrew/share/zsh/site-functions)
fi

if [[ $OSTYPE == linux* ]]; then
  # ローカルセッションのみ 1Password の SSH Agent を使う
  if [[ -z "$SSH_CONNECTION" && -S "$HOME/.1password/agent.sock" ]]; then
    export SSH_AUTH_SOCK="$HOME/.1password/agent.sock"
  fi
fi

新しいマシンでのセットアップ

dotfilesのリンク元のパスを固定しているので、決まった場所にcloneします。

curl https://mise.run | sh
git clone https://github.com/sou1118/dotfiles ~/ghq/github.com/sou1118/dotfiles
ln -s ~/ghq/github.com/sou1118/dotfiles/.config/mise ~/.config/mise
~/.local/bin/mise bootstrap

おわりに

移行しようと半年くらい前から思っていたものの、なかなか腰が上がらず放置していました。やってみたら快適に使えて嬉しいです。

mise自体新しめのソフトウェアなのでいろんな機能がガンガン入ってくるのが面白いし楽しいですね。

作者のjdx氏はソフトウェアサプライチェーンにも真剣に向き合っていて、色々と勉強させていただいています。