前回、 NanoPi NEO2 Blackに Node.js を入れて、OLED HATへの表示や、GPIOに繋がったプッシュボタンのイベント取得する為のスクリプトを組みました。今回はそんな Node.js アプリを機器起動時に 自動起動 させてみようと思います。
まず、NanoPi NEO2 BlackにインストールされたNode.jsの現状は以下の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$ node -v v14.16.0 $ npm -v 7.6.3 $ npm ls pi@ /home/pi ├── i2c-bus@5.2.1 ├── oled-font-5x7@1.0.3 ├── oled-i2c-bus@1.0.12 └── onoff@6.0.1 $ npm -g ls /usr/lib └── npm@7.6.3 |
foreverを試す
Node.jsで自動起動と言うとまずこのforeverがヒットすると思います。グローバルでインストールする必要がありました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
$ sudo npm install -g forever npm WARN deprecated urix@0.1.0: Please see https://github.com/lydell/urix#deprecated npm WARN deprecated resolve-url@0.2.1: https://github.com/lydell/resolve-url#deprecated npm WARN deprecated chokidar@2.1.8: Chokidar 2 will break on node v14+. Upgrade to chokidar 3 with 15x less dependencies. added 244 packages, and audited 245 packages in 44s 10 packages are looking for funding run `npm fund` for details 6 low severity vulnerabilities To address all issues, run: npm audit fix Run `npm audit` for details. $ whereis forever forever: /usr/bin/forever |
使い方は、「forever start ファイル名」でプロセスは実行を続けます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$ sudo forever start app.js warn: --minUptime not set. Defaulting to: 1000ms warn: --spinSleepTime not set. Your script will exit if it does not stay up for at least 1000ms info: Forever processing file: app.js $ sudo forever list info: Forever processes running data: uid command script forever pid id logfile uptime data: [0] Ov8D /usr/bin/node app.js 1900 1907 /home/pi/.forever/Ov8D.log 0:0:0:41.087 $ ps -ax | grep app.js | grep -v grep 1645 ? Ssl 0:02 /usr/bin/node /usr/lib/node_modules/forever/bin/monitor app.js 1652 ? Sl 0:01 /usr/bin/node /home/pi/app.js |
ログや標準出力情報を見るには、ファイル名を確認し、別途tailする必要が生じたので少し手間かも知れません。ログファイル名はforeverのオプションで指定出来るのですが、そのオプションを記述する位置に気をつけなくてはならないような指摘も散見され、私もうまく機能させることが出来ませんでした。
1 2 3 4 5 6 7 8 9 10 11 12 |
$ sudo forever logs info: Logs for running Forever processes data: script logfile data: [0] app.js /home/pi/.forever/Ov8D.log $ tail -f /home/pi/.forever/Ov8D.log (node:1684) Warning: Accessing non-existent property 'padLevels' of module exports inside circular dependency (Use `node --trace-warnings ...` to show where the warning was created) *** 0:ON *** *** 0:OFF *** *** 2:ON *** *** 2:OFF *** |
さらにforeverでOLED HATを表示させるスクリプトを動かしたまま機器を再起動させると、表示が残ったまま再起動してしまいます(もちろんまだrc.localやcronに起動登録させていないので、再起動後にスクリプトは動いていない)。さすがにこれでは気持ち悪いので、代替を探すことにしました。
pm2を試す
foreverより高機能と言われているのがこのpm2。forever同様、グローバルでインストールします。
1 2 3 4 5 6 7 |
$ sudo npm install -g pm2 npm WARN deprecated debug@4.1.1: Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797) npm WARN deprecated debug@4.1.1: Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797) added 201 packages, and audited 202 packages in 4m 9 packages are looking for funding run `npm fund` for details found 0 vulnerabilities |
pm2を使ってNode.jsスクリプトを自動起動させるにはまず、pm2をsystemdに登録して自動起動するようにし、続いてpm2にNode.jsスクリプトを登録させます。てっきりforeverの様にrc.localかcronに手書きするつもりでいましたが、なかなかスマート。systemdへの登録もコマンド一発でとても簡単です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
$ sudo pm2 startup [PM2] Init System found: systemd Platform systemd Template [Unit] Description=PM2 process manager Documentation=https://pm2.keymetrics.io/ After=network.target [Service] Type=forking User=root LimitNOFILE=infinity LimitNPROC=infinity LimitCORE=infinity Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin:/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin Environment=PM2_HOME=/home/pi/.pm2 PIDFile=/home/pi/.pm2/pm2.pid Restart=on-failure ExecStart=/usr/lib/node_modules/pm2/bin/pm2 resurrect ExecReload=/usr/lib/node_modules/pm2/bin/pm2 reload all ExecStop=/usr/lib/node_modules/pm2/bin/pm2 kill [Install] WantedBy=multi-user.target Target path /etc/systemd/system/pm2-root.service Command list [ 'systemctl enable pm2-root' ] [PM2] Writing init configuration in /etc/systemd/system/pm2-root.service [PM2] Making script booting at startup... [PM2] [-] Executing: systemctl enable pm2-root... Created symlink from /etc/systemd/system/multi-user.target.wants/pm2-root.service to /etc/systemd/system/pm2-root.service. [PM2] [v] Command successfully executed. +---------------------------------------+ [PM2] Freeze a process list on reboot via: $ pm2 save [PM2] Remove init script via: $ pm2 unstartup systemd |
foreverと同様に、「pm2 start ファイル名」でプロセスは実行を続けます。logオプションが標準出力まで網羅されていて、tailされるのでとにかく快適。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
$ sudo pm2 start /home/pi/app.js [PM2] Applying action restartProcessId on app [app](ids: [ 0 ]) [PM2] [app](0) ✓ [PM2] Process successfully started ┌─────┬────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐ │ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │ ├─────┼────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤ │ 0 │ app │ default │ N/A │ fork │ 2340 │ 0s │ 0 │ online │ 0% │ 25.4mb │ root │ disabled │ └─────┴────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘ $ sudo pm2 log app [TAILING] Tailing last 15 lines for [app] process (change the value with --lines option) /home/pi/.pm2/logs/app-error.log last 15 lines: 0|app | } 0|app | Error: Command failed: shutdown -h now 0|app | at checkExecSyncError (child_process.js:616:11) 0|app | at execSync (child_process.js:652:15) 0|app | at /home/pi/app.js:85:20 0|app | at /home/pi/node_modules/onoff/onoff.js:135:9 0|app | at Array.forEach (<anonymous>) 0|app | at pollerEventHandler (/home/pi/node_modules/onoff/onoff.js:134:32) { 0|app | status: null, 0|app | signal: 'SIGINT', 0|app | output: [ null, <Buffer >, <Buffer > ], 0|app | pid: 1758, 0|app | stdout: <Buffer >, 0|app | stderr: <Buffer > 0|app | } /home/pi/.pm2/logs/app-out.log last 15 lines: 0|app | *** end *** 0|app | *** end *** 0|app | *** end *** 0|app | *** end *** 0|app | *** end *** 0|app | *** 3:ON *** 0|app | *** 3:ON *** 0|app | *** end *** 0|app | *** 3:ON *** 0|app | *** end *** 0|app | *** 3:ON *** 0|app | *** end *** 0|app | *** 3:ON *** 0|app | *** end *** 0|app | *** end *** $ ps -ax | grep -e app.js -e pm2 | grep -v grep 652 ? Ssl 0:16 PM2 v4.5.5: God Daemon (/home/pi/.pm2) 2046 ? Dsl 5:47 node /home/pi/app.js |
スクリプトファイルを書き換え、その動作に反映させたい場合はrestart、そしてstartさせたスクリプトはsaveすれば、機器再起動後もpm2が自動的に開始してくれます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
$ sudo pm2 restart app Use --update-env to update environment variables [PM2] Applying action restartProcessId on app [app](ids: [ 0 ]) [PM2] [app](0) ✓ ┌────┬────────────────────┬──────────┬──────┬───────────┬──────────┬──────────┐ │ id │ name │ mode │ ↺ │ status │ cpu │ memory │ ├────┼────────────────────┼──────────┼──────┼───────────┼──────────┼──────────┤ │ 0 │ app │ fork │ 3 │ online │ 0% │ 25.1mb │ └────┴────────────────────┴──────────┴──────┴───────────┴──────────┴──────────┘ $ sudo pm2 save [PM2] Saving current process list... [PM2] Successfully saved in /home/pi/.pm2/dump.pm2 $ sudo reboot $ sudo pm2 status app ┌─────┬────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐ │ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │ ├─────┼────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤ │ 0 │ app │ default │ N/A │ fork │ 667 │ 60s │ 0 │ online │ 0% │ 43.2mb │ root │ disabled │ └─────┴────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘ |
逆に次回起動時には自動開始して欲しくない場合には、スクリプトをstopさせ、その状態をsaveさせれば良いことになります。
1 2 3 4 5 6 7 8 9 10 11 12 |
$ sudo pm2 stop app $ sudo pm2 save [PM2] Saving current process list... [PM2] Successfully saved in /home/pi/.pm2/dump.pm2 $ sudo reboot $ sudo pm2 status app ┌─────┬────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐ │ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │ ├─────┼────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤ │ 0 │ app │ default │ N/A │ fork │ N/A │ 0 │ 0 │ stopped │ 0% │ 0b │ root │ disabled │ └─────┴────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘ |
状態の確認はこのstatusオプションの他に、monitモニタオプションがログ機能も兼ねていて秀悦です。
起動直後にはIPv6アドレスしか無い?
前記事でLANポートのIPアドレスを取得するのに参照しているos.networkInterfaces()オブジェクト内のeth0項は平常時は次のようになっているのですが、どうも起動直後はIPv6がしかいないのかもしれません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
eth0: [ { address: '192.168.26.193', netmask: '255.255.255.0', family: 'IPv4', mac: '02:01:3e:##:##:##', internal: false, cidr: '192.168.26.193/24' }, { address: 'fe80::aae4:5b03:83ed:2797', netmask: 'ffff:ffff:ffff:ffff::', family: 'IPv6', mac: '02:01:3e:##:##:##', internal: false, cidr: 'fe80::aae4:5b03:83ed:2797/64', scopeid: 5 } ] |
実はこの現象により、OLED液晶に文字列をプリントする際、一度空白でその行を埋め尽くすという常套手段を思い出させてくれました。
1 2 3 4 5 6 7 8 9 10 11 |
// SHOW LAN IP REPEATEDLY var printlanip = function () { var networkInterfaces = os.networkInterfaces(); //ERASE LINE AND PRINT oled.setCursor(1, 1); oled.writeString(font, 1, ' ', 1, false); oled.setCursor(1, 1); oled.writeString(font, 1, 'LAN '+networkInterfaces.eth0[0].address, 1, false); } var timer = setInterval(printlanip, 2000); setTimeout(printlanip, 0); |
本来ならば範囲をきちんと指定してoled.fillRectした方が格好良いのかもしれませんが。
ともあれこれでNano Pi NEO2 + OLED HAT をNode.jsで操作するお膳立ては整いました。次回はいよいよギガビットイーサを活かした、インターネットスピードテスタの製作です。