Bitbucket PipelinesのWindowsセルフホストランナーを、マシン再起動時に自動的に起動する様に設定する

Bitbucket Pipelinesでセルフホストランナーを構成する場合、デフォルトではPowershellスクリプトを使い、手動で起動する必要があります。

この場合、何らかの事情でマシンを再起動した際に、マシンに接続してランナープログラムを手動で再起動してやる必要が出てきて面倒です。

GitHub ActionsではWindowsはデフォルトでサービスとして動作しているので簡単に自動化ができるのですが、Bitbucket Pipelinesの場合は自動化するのにひと手間掛かります。

この記事では、備忘録的にBitbucket Pipelinesランナーをマシン再起動時に自動実行する方法について残しておきます。また、構成したランナー上でUnityを動かした際に多少詰まった事があったため、その内容についても触れています。

手法1:WinSWを使ってサービス化する

これは、Bitbucketの公式で案内されている方法です。

confluence.atlassian.com

この手法では、WinSWというOSSを利用します。

github.com

WinSWの実体は単独の実行ファイルで、ユーザーが作成した設定ファイルを元に、簡単にサービスを作成することができます。

基本的な設定方法は前に示したAtlassianのブログ記事にあるのですが、設定用xmlファイルの設定内容が分かり辛かったので、例を下記に示します。この例ではランナープログラムに同梱されているstart.ps1内で呼び出している.jarを直接WinSW側から呼び出すよう設定しています。

<service>
  <id>bitbucketrunner</id>
  <name>Bitbucket Runner (powered by WinSW)</name>
  <description>This service is a service created from a minimal configuration</description>
 
  <executable>java</executable>
  <arguments>-jar 
  -Dbitbucket.pipelines.runner.account.uuid={********-****-****-****-************}
  -Dbitbucket.pipelines.runner.repository.uuid={********-****-****-****-************}
  -Dbitbucket.pipelines.runner.uuid={********-****-****-****-************}
  -Dbitbucket.pipelines.runner.environment=PRODUCTION
  -Dbitbucket.pipelines.runner.oauth.client.id=****
  -Dbitbucket.pipelines.runner.oauth.client.secret=****
  -Dbitbucket.pipelines.runner.directory.working=../temp
  -Dbitbucket.pipelines.runner.runtime=windows-powershell
  -Dbitbucket.pipelines.runner.scheduled.state.update.initial.delay.seconds=0
  -Dbitbucket.pipelines.runner.scheduled.state.update.period.seconds=30
  -Dbitbucket.pipelines.runner.cleanup.previous.folders=false
  -Dlogback.configurationFile=logback.xml
  -Dfile.encoding=UTF-8
  -Dsun.jnu.encoding=UTF-8
  ./runner.jar
  </arguments>

  <serviceaccount>
    <domain>NT AUTHORITY</domain>
    <user>NetworkService</user>
  </serviceaccount>

  <delayedAutoStart/>

</service>

上記xmlの****は、それぞれがランナーをクラウドコンソール上で登録した際に例示されるコマンドに入っている値がそれぞれ対応しています。

xml側の引数 コマンド側の引数
-Dbitbucket.pipelines.runner.account.uuid -accountUuid
-Dbitbucket.pipelines.runner.repository.uuid -repositoryUuid
-Dbitbucket.pipelines.runner.uuid -runnerUuid
-Dbitbucket.pipelines.runner.oauth.client.id -OAuthClientId
-Dbitbucket.pipelines.runner.oauth.client.secret -OAuthClientSecret

※表中のDbitbucket.pipelines.runner.repository.uuidはワークスペース単位のランナーを登録する際は不要です。

xmlファイルを作成して配置したら、以下の様なコマンドを実行することでサービスを登録できます。

service.exe install ./service.xml
service.exe start

(補足)サービスアカウントでUnityを動かす際のupmの資格情報の置き場所について

Unityで、認証が必要なupmレジストリにアクセスする為の資格情報は、ユーザーのプロファイルフォルダに設置します。しかしサービスとして動かす場合、この資格情報は、サービスアカウントのプロファイルではない別のフォルダに置く必要があるため、注意が必要です。

docs.unity3d.com

手法2:タスク スケジューラを利用して自動起動する

こちらの手法は、前述の手法と異なり、WinSWのようなサードパーティ製のツールに依存しません。

通常通りランナーをインストールした後、起動コマンドを新しく用意したps1ファイルに保存します。前の例のように直接jarを呼び出しても良いのですが、こちらの方が設定が楽なので今回はこうしています。

.\start.ps1 -accountUuid '{********-****-****-****-************}' -repositoryUuid '{********-****-****-****-************}' -runnerUuid '{********-****-****-****-************}' -OAuthClientId **** -OAuthClientSecret **** -workingDirectory '..\temp'

※保存場所は、start.ps1がある場所と同じ場所にして下さい。

起動コマンドをファイルに保存した後、 「タスク スケジューラ」を開き、基本タスクの作成を選んで以下の様に設定します。

  • 名前:(任意)
  • トリガー:コンピューターの起動時
  • 操作:プログラムの開始
    • プログラムの開始:
      • プログラム/スクリプト:pwsh
      • 引数の追加:(作成したスクリプトファイルの名前を入力してください。)
      • 開始:(作成したスクリプトファイルが配置されているフォルダのパスを入力してください。)

ウィザードでタスクを作成した後、プロパティを開き、「ユーザーがログオンしているかどうかにかかわらず実行する」にチェックを入れます。

選択後、OKを押して、ユーザーのパスワードを入力して完了です。

(補足)UnityのGUIが表示できるようにセットアップする

前述の二つの手段で起動したランナー上でUnityのGUIを表示する必要があるパイプラインを実行した場合、以下の様なエラーが発生して失敗することがあります。

d3d11: swap chain: w=32 h=32 fmt=28
d3d11: failed to create swap chain [0x887a0022]
Assertion failed on expression: 'SUCCEEDED(hr)'
d3d11: swap chain: w=1018 h=615 fmt=28
d3d11: failed to create swap chain [0x887a0022]

Assertion failed on expression: 'SUCCEEDED(hr)'
UnityEngine.StackTraceUtility:ExtractStackTrace ()
UnityEditor.GUIView:SetWindow (UnityEditor.ContainerWindow)
UnityEditor.HostView:SetWindow (UnityEditor.ContainerWindow)
UnityEditor.View:SetWindowRecurse (UnityEditor.ContainerWindow)
UnityEditor.View:AddChild (UnityEditor.View,int)
UnityEditor.SplitView:AddChild (UnityEditor.View,int)
UnityEditor.View:AddChild (UnityEditor.View)
UnityEditor.EditorWindow:CreateNewWindowForEditorWindow (UnityEditor.EditorWindow,bool,bool,bool)
UnityEditor.EditorWindow:Show (bool)
UnityEditor.EditorWindow:Show ()

情報が少ない為、直接的な原因は推測するしかないのですが、どうやらサービスとして実行する等、対話型インターフェースを利用しないプログラム上ではUnityのGUIを表示する処理が失敗する様です。

これは、タスク スケジューラ―を使用し、ユーザーとしてログインすることを必須とすることで回避が可能です。

合わせて、トリガーもログオン時に変更しておきます。ユーザーはタスクの実行時に使うユーザー アカウントと合わせておきます。

ログオン時の起動に変更する為、動作にはユーザーとしてのログオンが必須になります。ユーザーが一人かつパスワードが設定されていない場合は自動ログオンされますが、サーバーマシンでパスワードが設定されていない事はほぼ無いでしょう。完全に自動でランナーが起動するようにしたい場合は、別途Windowsの自動ログオンの設定をしてください。

learn.microsoft.com