はじめに
GitBucketにはGitHub Actionsのような標準のCIがありません。
GitHubに慣れているとpushしたらテストくらい回ってほしくなるので、どこまでできるのか試してみました。
Jenkinsを別に立てて連携するとか、Webhookを受けて自前でactを動かすとかも考えたんですが、一番手軽そうなgitbucket-ci-pluginでやります。
Dockerで立てて、pushしたらdotnet testが回ってバッジとチェックが付くところまでを書きます。
環境
macOS
GitBucket 4.47.0
gitbucket-ci-plugin 1.11.0
Dockerが入っていること
gitbucket-ci-plugin
takezoeさんが作っているプラグインです。
最新は1.11.0で、READMEに書いてある対応バージョンはGitBucket 4.35.x以上。
GITBUCKET_HOME/plugins/にjarを置いて起動すると読み込まれます。
Dockerfile
最初の詰まりどころ。
gitbucket/gitbucket:4.47.0を指定したらnot foundでした。
Docker Hubのタグを見ると2022年12月の4.38.4が最後になっています。
GitHubにあるgitbucket-dockerのDockerfileのほうは4.47.0を指しているので、イメージのpushだけ止まっているっぽいです。
なので公式Dockerfileの中身を自前のDockerfileに写します。(.NETを動かすのでそれも入れておく)
FROM eclipse-temurin:17
ARG GITBUCKET_VERSION=4.47.0
ARG DOTNET_CHANNEL=8.0
# libicuがないとdotnetが落ちる
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates curl git unzip libicu-dev libssl-dev zlib1g \
&& rm -rf /var/lib/apt/lists/*
ADD https://github.com/gitbucket/gitbucket/releases/download/${GITBUCKET_VERSION}/gitbucket.war /opt/gitbucket.war
# dotnet-install.shがarm64/amd64を判別してくれる
ENV DOTNET_ROOT=/usr/share/dotnet
RUN curl -fsSL https://dot.net/v1/dotnet-install.sh -o /tmp/dotnet-install.sh \
&& chmod +x /tmp/dotnet-install.sh \
&& /tmp/dotnet-install.sh --channel ${DOTNET_CHANNEL} --install-dir ${DOTNET_ROOT} \
&& ln -s ${DOTNET_ROOT}/dotnet /usr/local/bin/dotnet \
&& rm /tmp/dotnet-install.sh
ENV PATH="${PATH}:/usr/share/dotnet"
# ci-pluginがビルドごとにHOMEを差し替えるので、キャッシュはHOMEの外に置く
ENV NUGET_PACKAGES=/opt/nuget/packages
ENV DOTNET_CLI_HOME=/opt/dotnet-home
RUN mkdir -p /opt/nuget/packages /opt/dotnet-home
# ここから下は公式gitbucket-dockerと同じ
RUN ln -s /gitbucket /root/.gitbucket
VOLUME /gitbucket
EXPOSE 8080 29418
CMD ["sh", "-c", "java -jar /opt/gitbucket.war"]
libicu-devを入れ忘れるとdotnetがグローバリゼーション関連で落ちるので注意です。
キャッシュを使いたいとき
.NETのnugetみたいなやつはCIのたびに取ってくると時間がかかるのでキャッシュしたいですよね。
GitHub Actionsではactions/cacheでいい感じにやりますが、こちらは実質1コンテナなのでホスト側に置いておくだけでいいです。
ただしci-pluginはビルドごとにHOMEを使い捨てディレクトリに差し替えるので、デフォルトの~/.nuget/packagesだと毎回消えます。
DockerfileでNUGET_PACKAGESをHOMEの外に固定してあげて、その場所をcomposeのvolumeに載せるとキャッシュっぽい動きができまs。
# ci-pluginがビルドごとにHOMEを差し替えるので、キャッシュはHOMEの外に置く
ENV NUGET_PACKAGES=/opt/nuget/packages
ENV DOTNET_CLI_HOME=/opt/dotnet-home
RUN mkdir -p /opt/nuget/packages /opt/dotnet-home
volumes:
- ./data:/gitbucket
- ./plugins:/gitbucket/plugins
- nuget-cache:/opt/nuget/packages
16秒かかっていたビルドが2秒で終わるようになりました。
プラグインを読み込ませる
jarを取ってきて./pluginsに置きます。
$ curl -fsSL -o plugins/gitbucket-ci-plugin-1.11.0.jar \
https://github.com/takezoe/gitbucket-ci-plugin/releases/download/1.11.0/gitbucket-ci-plugin-1.11.0.jar
$ docker compose up -d --build
読み込まれたかはログでわかります。
$ docker compose logs | grep ci-plugin
INFO g.core.plugin.PluginRegistry - Initialize gitbucket-ci-plugin-1.11.0.jar
http://localhost:8080にroot/rootでSign inして、アイコン→System administration→左メニューのPlugins。
CI Pluginが並んでいれば入っています。
プラグイン自体は4.36.2でビルドされたものですが、4.47.0でも普通に読み込まれました。
レポジトリを作ってビルドを有効にする
sample-appというレポジトリを作っておきました。
Settings→Buildタブに行くとci-pluginが足したビルド設定が出てきます。
Enable buildにチェックを入れて、Build scriptにbash ci.shと書いてApply changes。
Build typeは2つあって、Build scriptがこの画面に直接スクリプトを書く方式、Build fileがレポジトリ内のスクリプトファイルを指定する方式です。
Skip wordsの初期値は[ci skip], [skip ci]、Run wordsの初期値はok to test, test this please。
Run wordsはPRのコメントに書くと再実行してくれるやつです。
E-mail notificationは失敗時のメール通知ですが、GitBucket側のSMTP設定が必要なので今回は入れていません。
ci.sh
実際のCI用スクリプトはただの四則演算をするもので試してみました。
テストが1件でも落ちれば終了して、GitBucket側のビルドがFailureになります。
#!/usr/bin/env bash
set -euo pipefail
cd "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
echo "==== 環境 ===="
echo "CI=${CI:-}"
echo "CI_BUILD_NUMBER=${CI_BUILD_NUMBER:-}"
echo "CI_BUILD_BRANCH=${CI_BUILD_BRANCH:-}"
echo "CI_COMMIT_ID=${CI_COMMIT_ID:-}"
echo "CI_REPO_SLUG=${CI_REPO_SLUG:-}"
dotnet --version
echo "==== restore ===="
dotnet restore SampleApp.sln
echo "==== build ===="
dotnet build SampleApp.sln --no-restore -c Release
echo "==== test ===="
dotnet test SampleApp.sln --no-build -c Release --logger "console;verbosity=normal"
echo "==== 成功 ===="
スクリプトの中で使える環境変数は以下です。
| 変数 | 中身 |
|---|---|
CI |
true |
HOME |
ビルドディレクトリ(ビルドごとに差し替わる) |
CI_BUILD_DIR |
ビルドディレクトリのルート |
CI_BUILD_NUMBER |
ビルド番号 |
CI_BUILD_BRANCH |
ブランチ名 |
CI_COMMIT_ID |
コミットのSHA |
CI_COMMIT_MESSAGE |
コミットメッセージ |
CI_REPO_SLUG |
owner/repo |
CI_PULL_REQUEST |
PR番号。PRでなければfalse
|
CI_PULL_REQUEST_SLUG |
PR元のowner/repo。PRでなければ空 |
準備ができたら手元からpushします。
$ git -c init.defaultBranch=main init
$ git add .
$ git commit -m "サンプルの電卓アプリとCIスクリプトを追加"
$ git remote add origin http://localhost:8080/git/root/sample-app.git
$ git push -u origin main
デフォルトブランチはmainです。
GitBucket 4.47の新規レポジトリはmainなので、masterで初期化してpushしても何も起きません。
最初これに気づかず、無反応なのでBuildの設定のほうを疑っていました。
ビルド履歴
左メニューのBuildにビルド履歴が出ます。
上に「You can add status badge by this URL」とバッジのURLが出ています。
Settingsのほうにあると思って最初探しました。
ビルド番号をクリックするとコンソール出力が見られます。
git cloneとgit checkoutから始まって、ci.shのechoがそのまま流れてきます。
日本語のテスト名も文字化けせずそのまま出ています。
Passed Calc.Tests.CalculatorTests.Multiply_掛け算の結果が返る [2 ms]
Passed Calc.Tests.CalculatorTests.Divide_ゼロ除算は例外になる [< 1 ms]
Passed Calc.Tests.CalculatorTests.Add_足し算の結果が返る(a: 1, b: 2, expected: 3) [< 1 ms]
Test Run Successful.
Total tests: 5
Passed: 5
failureしてみる
Calculator.Addをa - bにしてpushしてみましょう。
public static int Add(int a, int b) => a - b;
ビルド#2がFailureになりました。
上のバッジ表示もfailureに変わっています。
Failed Calc.Tests.CalculatorTests.Add_足し算の結果が返る(a: 1, b: 2, expected: 3) [< 1 ms]
Error Message:
Assert.Equal() Failure: Values differ
Expected: 3
Actual: -1
Test Run Failed.
Total tests: 5
Passed: 3
Failed: 2
EXIT CODE: 1
最後のEXIT CODE: 1はci-pluginが出しています。
GitHub Actionsのrunはbash -eで動くので途中で落ちれば止まりますが、ci-pluginは#!/bin/shで素のまま実行するだけです。
set -euo pipefailを書いておけば、途中のコマンドが落ちた時点で止まって、そのコマンドの終了コードがそのままビルドの結果になります。
バッジ
コミット一覧を見ると、GitHubのcommit statusと同じ位置にチェックが付いています。
READMEにバッジも貼っておきます。
[](http://localhost:8080/root/sample-app/build)
バッジがあると一気にそれっぽくなりますね。
PR
PRも見ておきましょう。
「All is well」とShow all checksが出ます。
普段の使い方としてはここまでできれば十分ですね。
ビルド履歴を見ると、同じコミット87671a8が2回ビルドされていました。
ブランチへのpushで#6、PRを作ったときに#7。
2秒で終わるので気になりませんが、重いビルドだとそこそこ無駄になりそう。(片方を止める設定があるのかは調べていない)
CircleCI互換APIは500になる
ci-pluginはCircleCI互換のAPIも持っているんですが、こちらは動きませんでした。
java.lang.NoSuchMethodError: 'void org.json4s.CustomSerializer.<init>(scala.Function1, scala.reflect.Manifest)'
at io.github.gitbucket.ci.api.JsonFormat$.<clinit>(JsonFormat.scala:17)
プラグインが古いjson4sに対してコンパイルされているためで、GitBucket側が進んで合わなくなったのだと思います。
同じスタックトレースのissueが2023年に立っていて、今も開いたままでした。
公開環境で使ってはいけない
ここまで動かしておいてなんですが、READMEのCautionsに書いてあります。
Note that you must not use this plug-in in public environment because it allows executing any commands on a GitBucket instance. It will be a serious security hole.
GitBucketのインスタンス上で任意のコマンドが実行できてしまうので、公開環境で使ってはいけないみたいです。
作者様のブログにも日本語で同じことが書いてありました。
このプラグインはリポジトリのオーナーにGitBucketが動作しているサーバ上で任意のコマンド実行を許可することになりますので、公開環境で運用しているGitBucketでは使用しないでください。
脆弱性ではなく、そういう作りだということですね。
どこまでいじれてしまうのかを一時的に以下へ差し替えて動かしてみました。
id
echo "HOME=$HOME"
pwd
ls /gitbucket
ls -l /gitbucket/data.mv.db
出力はこんな感じです。
uid=0(root) gid=0(root) groups=0(root)
HOME=/root/.gitbucket/repositories/root/sample-app/build/8
/gitbucket/repositories/root/sample-app/build/8/workspace
activity.log
data.mv.db
database.conf
gist
plugins
repositories
tmp
-rw-r--r-- 1 root root 221184 Sep 5 2026 /gitbucket/data.mv.db
がっつりrootでした。
作業ディレクトリがGitBucketのデータディレクトリの中なので、他のレポジトリの実体もdatabase.confのパスワードも丸見えです。
社内の閉じたGitBucketなら問題にはならないはずですが、昨今のAI時代このへんで迂回して勝手に何かやられそうではあります。
まとめ
ラップトップでやっているので、マシンスペックを気にしてたのですが割と動いてくれました。
10人規模くらいの社内サーバーに置く分には問題なさそうです。
rootを持ってしまうのは非常にまずいと思うので、本当に使うなら社内でも隔離した部分で使うのがまるそうではあります。
参考









