それなりに安心な install.sh を作るツール insmith
https://github.com/Songmu/insmith
度々話題になる悪名高い curl | sh インストーラーだが、告白すると私が開発しているGo製のOSSは大体、リポジトリルートに、install.sh を配置していて、それを使って実行バイナリを curl インストールできるようにもなっている。GitHub ReleasesのAssets上のアーカイブをダウンロードして展開して実行バイナリを配置している。例えば、gocredits だとこういう感じ。
$ curl --proto '=https' --tlsv1.2 -fsSL https://raw.githubusercontent.com/Songmu/gocredits/main/install.sh | sh -s
デフォルトではカレントディレクトリ以下の ./bin/gocredits に配置する。
これは、直接の利用を推奨しているわけではない。ただ、私はリポジトリ上で、GitHub Actions上に実行バイナリをインストールするためのカスタムGitHub Actionも同時に提供することが多く、 gocredits も最近そうした。そのときに、この install.sh を利用している。この場合は curl は使わず、 $GITHUB_ACTION_PATH/install.sh の形で呼び出してはいる。
これによって uses: Songmu/gocredits@v1 や uses: Songmu/gocredits@v1.0.0 のように指定すれば、GitHub Actions上に実行バイナリをインストールできる。勿論ハッシュ指定もできる。
当然、Acitons上で go install github.com/Songmu/gocredits/cmd/gocredits@v1.0.0 としても良いが、GitHub Actions形式には以下のメリットがある。
- ビルド済みバイナリを配置するだけなので高速である
- Dependabotでバージョンアップを検知できる
go install でも、 go.mod の tool ディレクティブで管理して、Dependabotにバージョンアップを検知してもらうという手もありそうですが、残念ながらまだ未対応です。
- Support Go tool dependencies · Issue #12050 · dependabot/dependabot-core
- Add support for tool directive in Go modules by akimon658 · Pull Request #13655 · dependabot/dependabot-core
imsmith による install.sh の改善
ということで、私のGo製OSSでは install.sh を配置していますが、これはもともとはgoreleaser/godownloader が生成していた install.sh をベースにしていた。新規OSSを作る時に godzil がプロジェクトに合わせた install.sh 自動生成するようになっている。
ただ、 godownloader はアーカイブされて久しく、この install.sh も古くなってきているのを薄々感じていた。最近のサプライチェーンセキュリティの観点からも改善することにした。そのために作ったのが、この insmith というツール。
https://github.com/Songmu/insmith
これはプロジェクトに即した install.sh を生成してくれるCLIだ。とは言っても、かなり僕のプロジェクト向けに作られていて、デフォルト値の決め方なんかも含め、オレオレツールクオリティではあります。使いたい方がいれば要望などは応えたいです。
具体的に取り組んだ改善ポイントは以下。
アセットの検証を SHA256SUMS ではなく gh attestation verify を優先して使うように
これが一番やりたかったこと。
元々はリリースアセットに SHA256SUMS というチェックサムファイルも上げておき、 インストールするアーカイブとのハッシュ値比較をしていた。よくある方法ではあるが、これだと、チェックサムとアーカイブが同じ場所に置かれているので、ダウンロードファイルの破損検出くらいにしか役に立ちません。
GitHubにはArtifact Attestationsという、GitHub Actions上でビルドした成果物に対して証明を付与できる機能があり、これを使えば、リリースアセットの検証をより強固に出来る。アセットのAttestationは以下のように検証できる。これを install.sh の中で実施している。
gh attestation verify "$artifact" \
--repo "$REPOSITORY" \
--source-digest "$commit" \
--signer-digest "$commit" \
--signer-workflow "$WORKFLOW" || return 1
gh attestation がない環境では、従来通りの SHA256SUMS による検証に今のところはフォールバックしている。
OSS開発者側のビルド時のAttestationsの付与方法についてはまた別途記事を書くつもりだが、tagprのrelease.yaml が参考になると思う。また以下の記事も参照されたし。(ただ少し情報が古い)
client9/shlib 由来のシェル関数を最新化
元々の godownloader 由来の install.sh でも client9/shlib 由来のシェル関数を使っていたが、これを最新のものに更新した。
install.sh が最終行まで読み込まれないと実行されないように
具体的には、 install.sh の最終行で main "$@" を呼び出すようにして、それ以前の行には変数宣言や関数定義しか書かれていない。これによって、install.sh が最終行まで読み込まれないと実行されないようになっている。install.sh ダウンロード中に途中で切れてしまったりしても、中途半端に実行されることを防げる。
CLIによる再生成アプローチ
これまではプロジェクトをscaffoldingした際の install.sh を悪い秘伝のタレ的に大事に使い続けていて、更新が難しいという問題があったが、生成CLIを切り出して作ったので、もし仮に今後更新が必要になったら、 insmith を更新してから、 $ insmith > install.sh のように再生成すれば良くなった。
生成された install.sh を bashka でも検証して、問題ないことも確認しました。
まとめ
ということで、このエントリーは、私の最近の取り組みの紹介と、安心して使ってもらえるように整備していますという報告でした。とはいえ、この領域は不安や心配が尽きず、見落としもあろうかと思うので、問題がありそうであればお知らせいただけると助かります。
おまけ: k1LoW/gh-setup
今回の私の動機は、 GoのバイナリをインストールするカスタムGitHub Actionを提供したいという話だでした。
実は、その目的であれば、 k1LoW/gh-setup というGitHub Actionsが既に存在していて、これを使えば、GitHub Actions上にGoのバイナリをインストールするカスタムGitHub Actionを簡単に作れて、Attestationsの検証機能もある。
なので、今回はこれを利用することも検討したのだが、これまでのやり方からの移行差分の少なさや、 --signer-workflow の指定などをデフォルトでしたかったこともあり、自前やってみた次第です。
参考: tblsをセットアップするGitHub Actionとしてsetup-tbls(を作るツールとしてgh-setup)を作った - Copy/Cut/Paste/Hatena