Raspberry Pi上でTypescriptで書いたプログラムを動かしたいが、開発はPCでやりたい場合は、GitHubからのWebHookをトリガとしてJenkinsからビルド・実行する方法がメジャー(?)だと思う。ただ、JenkinsをRaspberry Pi上で動かすのは敷居が高く感じるし、まだ試行錯誤している段階で、WebHookを受け取るためにポートを解放するのも怖く感じる。
Jenkinsを導入していなくても、ちょっとしたことでCIっぽいことができるので、以下に方法をまとめる。複雑なことをやろうとしたらJenkinsの方が良いかもしれないが、取り敢えず動かしたいだけならそこそこ使えそうだった。
ここではNestJSのプログラムで試しているが特にプラットフォームやライブラリは問わない。
概要
Raspberry Piは非力なので、TypescriptのトランスパイルまでをPCで行う。手間を省くため、[1]ソースの編集があるたびに[2]変更を検出して自動的にトランスパイルを行う。(Eclipseであればエラーをすぐ見つけてくれるので、変更の度にビルドする必要はないが、たまにビルドしないと分からない変なエラーがあるので用意しておく)
PC上での編集が終わりRaspberry Pi上で動作確認する段階になったら、[3][4]ソースとトランスパイル結果をGitHubにpushする。[5]PCからのPushが完了したら、TortoiseGitのPost-push hook
によりRaspberry Piへpush完了を通知する。
[7]Raspberry Pi上の「通知受付」が[6]push完了の通知を受け取ったらgit pull
し、ソースとトランスパイル結果を更新する。[8]Raspberry Pi上のnodemonがトランスパイル結果の変更を検出したら、nodeを再起動し更新されたプログラムを開始する。
なお、Raspberry Pi上の「通知受付」はJenkinsでも実現できるものの、ここでは簡易的にHTTPリクエストを受け付けたらgit pull
するだけにする。簡単ではあるが、ローカルでWebHookに相当することができる。
ローカルならDDoS的なことは気にしなくてよいが、Pullの間隔を制限するなど工夫した方がベターだとは思う。
#準備: Node.jsのインストール
まずはNode.jsをインストールすることから始める。Raspberry Pi3 B+では「Node.js and Raspberry Pi」のコマンドを実行するだけでインストールできる。
curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
しかし、ずいぶん前に購入したRaspberry Pi B+はCPUがARMv6のため、以下のようなメッセージが出てインストールできなかった。
You appear to be running on ARMv6 hardware.
Unfortunately this is not currently supported by the NodeSource Linux distributions.
Please use the 'linux-armv6l' binary tarballs available directly from nodejs.org for Node.js 4 and later.
その場合は、対象を直接wgetして解凍、インストールしたい場所にコピーすることになる。ARMv6向けのバイナリは現時点のLTSである10.16.0と、サポートが無い11.xしかない。
wget https://nodejs.org/dist/latest-v10.x/node-v10.16.0-linux-armv6l.tar.gz
tar -zxvf node-v10.16.0-linux-armv6l.tar.gz
cd node-v10.16.0-linux-armv6l.tar.gz
sudo cp -R * /usr/local/
設定
人が行う操作[1][3]以外が行えるよう、設定・ちょっとしたプログラムの作成を行う。
##package.jsonの設定
[2]PC上の変更検出・自動トランスパイルのために、build:watch
コマンドを作る。このコマンドでは-w
(watch)オプションを付けてtsc
を実行することにより、tsconfig.build.json
で指定したソースディレクトリ内の変更を検出し、自動的にトランスパイルを開始する。
また、[8]Raspberry Pi上の変更検出・node再起動のために、start:raspi
コマンドを作る。このコマンドでは、nodemonによりトランスパイル結果dist
の中身が変更されたらnodeを再起動する。
{
"scripts": {
"build:watch": "tsc -w -p tsconfig.build.json",
"start:raspi": "nodemon --config nodemon-raspi.json",
},
}
###tsconfig.build.jsonの変更
tsconfig.build.json
(NestJSが作成したファイル)でdist
を変更の検出対象から除外することで、build:watch
でトランスパイル→トランスパイル結果dist
が変わる→変更を検出→トランスパイル…にならないようにする。(実際は無限ループは起きないが、エラーになる)。これにはtsconfig.build.json
(ベースとなるNestJSが生成したファイルの場合)に対して、exclude
にトランスパイル結果dist
を追加すればよい。
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "**/*spec.ts", "dist" ]
}
###nodemon-raspi.jsonの作成
トランスパイル結果dist
内の.js
ファイルが変更されたらnodeを再起動するよう、nodemonの設定ファイルを以下のように作成する。
{
"watch": ["dist"],
"ext": "js",
"exec": "node dist/main.js"
}
通知受付サーバの作成
Raspbery Pi上に、[5]PCからPush完了の通知を受け付け、[6]git pull
するための小規模なサーバを設ける。ここでは、Listenしているポートで何らかのリクエストを受信したらgit pull
するだけの簡易的な機能にとどめている。
const http = require( 'http' );
const spawn = require('child_process').spawn;
// git pullする。spawn( 'git pull' )にするとエラーになるので注意。
function executePull() {
const prc = spawn( 'git', [ 'pull' ] );
return new Promise( ( resolve, reject ) => {
prc.once( 'exit', code => {
resolve( code );
} );
prc.once( 'error', err => {
reject( err )
} );
} );
}
// 通知受付サーバを作る
const app = http.createServer( ( req, res ) => {
let message = '';
executePull().then( ( code ) => {
// 成功した場合
res.writeHead( 200, { 'Content-Type': 'text/plain' } );
message = `Pull succeeded. (code:${code})`;
} ).catch( ( err ) => {
// 失敗した場合
res.writeHead( 405, { 'Content-Type': 'text/plain' } );
message = `Pull failed. (code:${err})`;
} ).finally( () => {
console.log( message );
// 応答を返す
res.write( message );
res.end();
} );
} );
// 通知受付サーバを起動する。ポートは適当。
app.listen( 4000 );
Post-push Hookの追加
TortoiseGitの設定画面の「Hook Scripts」で、[5]Push完了をRaspberry Piに通知するためのスクリプトを追加する。フックはTortoiseGit全体に適用されてしまう(らしい)ので、「Run when working tree path is under:」で適用対象を絞るとよい。また、通知を行うコマンドはHTTPリクエストさえRaspberry Piに届けばよいので、curl <RaspberyPiのIP:簡易トリガのPort>
のように設定する。
起動
予め上記設定をしたプログラムをGitHubにpush済みの状態から、pushをトリガに変更が自動的に反映できるようにする。
ソースの入手/更新とプログラムの起動
Raspberry Pi上に何もない状態から開始する場合、git clone
でGitHubにpush済みのプログラムを入手する。既にclone済みであれば、git pull
により更新する。
# コードをGitHubから持ってくる。初回はclone、2回目以降はpullで良い。
git clone <repository> <clone先>
cd <clone先>
# パッケージをインストールする(これは実行するところでしかできない)
npm install
# package.json で追加した Raspberry Pi用起動コマンド。
# distの変更を検出したらnodeを再起動する。
npm run start:raspi
# Raspberry Piにトランスパイルからやらせたければ、NestJSで元々存在する以下のコマンドで起動する。
# Rapsberry Pi B+ではNestJSのテンプレ(Hello Worldレベル)でさえ3~4分かかるので、かなり遅い。
# npm run start:dev
通知受付サーバの起動
通知受付サーバは以下のようなコマンドで起動する。(「Linuxコマンド(Bash)でバックグラウンド実行する方法のまとめメモ」参照)
# フォアグラウンドで実行…画面上にpullできたか表示されるので安心
node index.js
# バックグラウンドで実行
node index.js &
# SSH切断後もバックグラウンドで実行
nohup node index.js &
まとめ
動作しているところの動画は用意していないが、以上のやり方でPCで行った変更をGitHub経由で反映し、動作確認することができるようになった。リソースが潤沢なPCであればJenkinsという汎用的な手段が使えると思うが、上記の設定だけでも似たようなことが軽量に実行できるので、お手軽に使いたい場合に役に立つかと思う。