Rustと私
このブログを書くのは5年ぶりらしい。
転勤を機に自宅をスマートホーム化したりしているが何も書いていない。
twitterでRustのコマンドラインツール群の紹介をしたい気分と書いたら、いいねがついちゃったので書かざるをえないので公開する。
機械学習に世の中がうぇーいする前から、私は諸々のツールの開発にPythonを使っていた。
Perlがイヤになったから。
Perlの事は私は当時Write Onlyが言語と呼んでいた。
同じ処理を記述するのに自由度が高過ぎて、自分で書いたコードでも後から読んで理解できない事が多かったから。
で、何にしよう?って思った時にインデントでスコープを表現するというただその1点と書き方の自由度が比較的少ない(これはそのうち自由度が高くなったというか、処理系にまかせた書き方にしないと処理が遅くなるので覚えないといけない事が増えた)のでこれにしようと思ったのだった。
最初に自宅をスマートホーム化した4年ほど前はPythonで書いていた。
一方、会社では、イントラのツールがクソ過ぎてつらかったので、改善するツールも書いていた。
最初は、Chromeのプラグインを書いてブラウザの中でDOMを書き換えて、本来必要な情報を補完表示するようなものだった。
その時にJavaScriptを書くようになった。
けど、当時はJSはなんとなくまどろっこしくって嫌いだった。
しかし、Chromeプラグインはストア以外での配布がうるさくなってきたのでだんだん社内での配布も難しくなってきたので、イントラ改善ツールの提供方法としては適さなくなってきた。
このため、Webのフロントエンドも書く事を考え始めた。
ただ、どうしてもPythonだとasyncを書くのがいまいちなのでフロントエンドにはあまり向いていないなと感じていた。
その時にVue.jsを使ってみてアプリを書いてみた。
なるほど、Node.jsというのはなかなかよくできているなと思った。
特に非同期が書きやすいのはすぐれていると思った。
処理系も世の中はどんどん進んでるんだと思った。
ただ、Vue.jsとReactとどっちがいい?って考えた時に、どうもReactの方がたくさん使われてそうなのでReactにしちゃおうって思った。
なかざんさんのブログとか見てたので、イントラの改善ツールもReact Native for Webで書いちゃえってなって、結局React Nativeで書いている。
会社で利用している LINC Biz(リンク ビズ)| ビジネスチャット × ビデオ会議 のプレゼンス情報を座席レイアウトに並べるアプリを作って、テレワーク環境でもなんとなくみんなそこにいる気分にさせてみたりとか。
一番は労務管理ツールなんだけど。
で、自宅のスマートホームもNode.jsに書き直してみた。
それなりに動いた。
現在の状況をモニターして、制御するUIを用意するくらいはどうとでもなった。
ただ、その時のシステム構成があまり気に入らなかったので、ずっとシステム構成をどうすべきか悩んでいた。
1. フロントエンドはLINCBizみたいなチャットツールにする
2. Prometheusに時系列DBを持たせてGrafanaで可視化する
3. 自宅のサーバで動かすけれどできるだけプアな環境でも動かしたい
4. 自宅のサーバで動かすけれどポートの外部公開はしたくない
5. 本当はGraphQL的なAPIも用意したかったけれど別にそれってWebSocketでセッションはってJSONの交換すればいいだけだからGraphQLにはこだわらない
あたりかなぁ…もう忘れた。
結局この時点でフロントエンドを積極的に自分で書く必要がなくなったので、自宅システムはできるだけ軽量な環境にのっけたいと思っていた。
で、GoかRustかなぁと思ったのだけどGoを最初に見た時に感じたのが、最初にJavaScriptを見た時と同じ感覚で、めんどくせって感じだったので、Rustにした。少なくともネイティブのバイナリまで落としこんでしまうその潔さにほれたという感じはある。
ということでRustにどっぷりはまりこんでいる。
結局のところ現時点ではフロントエンド書くならReactでそれ以外は全部Rustという感じになっていて、もはやPython書けなくなってきていると思う。
OpenCVで手軽に書く時だけPython使うかもしれない。
Rustのsysクレートをクロスコンパイルする環境を整備するのが面倒で、ARMクロスでバイナリを作りたい時に、opencv_sysをビルドできないためだ。
RasPi上で監視カメラの画像をフレーム間差分抽出してmp4にエンコードするなんてのは、Rustで書かずにPythonで書いた方が手間が少ないのでそういう用途ではまだ使っている。
でも、会社でExcelを読み書きしたり、イントラ改善する際のバックエンド処理(WebSocketのサーバとか)とか、オレオレRPAツール(まぁただのクローラーだけど)とか、チャットボットとか、自宅スマートホームとか、そういうのはもはや全部Rust化してしまった。
あと、Rustだけどできるだけ、gnueabiではなくmusleabiを使うようにしている。
Dockerコンテナにする際にmuslにする事でイメージのサイズを極限まで小さくできるため。
DockerfileはFROM scratchで書き始めておいて、OpenSSLの証明書とtzinfoだけホストマシンのファイルをmountすれば、極小のDockerイメージが作れる。
まぁ複数のコンテナを同時に走らせる運用をしていないので、もはやDockerコンテナ化する必要もないので、最近は普通にsystemdに登録して動かしているけど、それにしてもOSに依存するバイナリの数を極力減らせるので、変な依存関係の解決のための諸々の手続を省略できる。
上記のRustのmuslビルド環境はWSL2上に構築していて(自宅も会社もメインはWindowsなので…)、Dockerデーモンを起動するのが面倒なので、WSL2上に↓のイメージと同じ環境をDockerfileと同じように展開して作っておいて、デフォルトのビルドターゲットをmuslにしてビルドするようにしている。
Linuxのディストリビューションが何だろうがmuslビルドしているのでバイナリのまま配布できる。
ソースコード管理はGiteaでやってるけどGiteaのリリースにバイナリをそのまま添付しておいて、実行するサーバでバイナリをダウンロードして、実行するだけですぐにサービスが動いちゃうので、最近はイントラ改善ツールはホストして公開するのではなくて、自分でサーバとして動く単一バイナリを会社で配布している。
ただ、動かす人に対してハードルあげてるのはたしかなのでちょっと考えた方が良いかもしれないけど、WindowsとLinuxで1つずつバイナリを用意しておけば、どんなディストリビューションでも基本動くサーバ構築できるので、非常にポータビリティは高いと思う。
ついでに言うと、フロントエンドで動かすReact Nativeについてもzipで圧縮して上記のサーババイナリの中にinclude_bytes!で埋め込んでおき、起動時に自分でカレントディレクトリに展開するように作っているので、Webフロントエンドつきサーバだって1バイナリで配布できてしまう。
React Nativeももはや面倒なのでExpo使っておけばビルドするのもコマンド一発なのでそこは手をかけないようにしている。
どうせ私の書くのはお遊びレベルを越えないので。
ということでRustaceansの端くれとなって経緯を書いてみました。
あーツールの紹介してねーなっていうので、cargo install-update -lの結果だけさらしておきます。
Windows環境での実行結果。
Package Installed Latest Needs update bandwhich v0.20.0 v0.20.0 No bat v0.17.1 v0.17.1 No cargo-update v5.0.0 v5.0.0 No du-dust v0.5.4 v0.5.4 No fd-find v8.2.1 v8.2.1 No grex v1.1.0 v1.1.0 No hyperfine v1.11.0 v1.11.0 No nu v0.26.0 v0.26.0 No procs v0.11.3 v0.11.3 No racer v2.1.44 v2.1.44 No ripgrep v12.1.1 v12.1.1 No sd v0.7.6 v0.7.6 No starship v0.50.0 v0.50.0 No streampager v0.9.2 v0.9.2 No tealdeer v1.4.1 v1.4.1 No tokei v12.1.2 v12.1.2 No zoxide v0.5.0 v0.5.0 No
nuが肝になっていて、実はzoxideとかはnuに対応していないので、実質使えていないのだけど、とりあえず入れたままにはしている。
あと、vimの外部コマンド実行もnuは対応していないので、vim.rustとかはnu経由では使えないので、仕方ないからbash経由でvimは起動しないといけない。
まぁメインのエディタは例に漏れずVSCodeなのだけど、コマンドラインでちょいちょいっといじりたいケースもあってvimも使ってるので…。
まぁ入れてはみたけど使ってないものも多いかな…
よく使うのは…
かな。
byobuのデフォルトシェルをnuにしておいて、byobuを起動したらnuが起動するけど、そうじゃない場合はbashが起動するようにしておけば、byobuのtmuxが固まったりしても非byobuなシェルを用意できるのでnuのstartupに以下のようなコマンドを書いておいて、
def ssh [host] { ssh -t $host byobu }
ssh ホスト名でリモートログインした場合はbyobu + nuが起動、^ssh ホスト名でリモートログインした場合は、bashが起動、といった形でbyobuの設定でのログイン時に自動でbyobuを起動するオプション(よくはまる)をオンにしない運用にしている。
このへんはnuの話なのでまた別途トピックを分離して書こう。
nuってドキュメントがいまいち(リリースノートがドキュメントになってる…)とかいうのもあるので癖はあるけど非常によくできているので、そのへんの事もどっかで書いてみる。
starshipはnuを入れる前にbashやpowershellに導入してみたけど見た目が統一されるのも含めて非常に良い感じ。あまりにも統一され過ぎて、今、自分がWindowsのネイティブなのか、WindowsのWSLなのか、リモートのLinuxなのか、区別つかなくなってしまうくらい…。
その上シェルまでnuで全部統一したので、byobuのstatuslineでホスト名出しておかないと、さっぱりわからなくなってしまう…
なのでWindowsのネイティブ環境でも動くbyobuがあると良いなぁ…と。
まぁ当座はWindows Terminalのタブを見て推定するしかないかなと…。
Windows Terminalもいきなりnuが起動するようにしていて、かつ、デフォルト起動にしているので、シェルスクリプトの書き方で悩む事は減った。
nuのスクリプトの書き方はクセはあるけれど、トライアンドエラーで書けるし、Rust的な感じでエラーメッセージ眺めりゃなんとかなくので、WindowsネイティブでもLinuxでも同じスクリプトで同じ処理(Giteaに最新リリースを追加してビルドした成果物をリリースにアップロードする、とかそういうの)を書けるので、かなりプラットフォームを意識しないで作業できる環境が整いつつある。
Windowsでbat動かしてもページャーがきかなかったのはずっと悩ましいなと思っていたのだけど、streampagerを入れたら解決した。Linuxでやると、byobuとの相性が悪いのか、streampager起動後はカーソルの表示位置がおかしくなってしまうので、Linuxでは、pagerはbatを使うようにしている。
alias less = bat --paging=always --color=always -p alias cat = bat --paging=never --color=always -p
と書いておけば、悩まずに使えるので。
Windowsの場合はlessは
def less [file] { bat --paging=never --color=always -p $file | sp }
と書いておけば同様に使えるのでそれで良いことにしている。
WSL2側のcargo install-update -lは以下でもうちょっと入ってる。
Package Installed Latest Needs update bandwhich v0.20.0 v0.20.0 No bat v0.17.1 v0.17.1 No cargo-audit v0.13.1 v0.13.1 No cargo-deb v1.29.0 v1.29.0 No cargo-update v5.0.0 v5.0.0 No du-dust v0.5.4 v0.5.4 No exa v0.9.0 v0.9.0 No fd-find v8.2.1 v8.2.1 No gitui v0.11.0 v0.11.0 No grex v1.1.0 v1.1.0 No hyperfine v1.11.0 v1.11.0 No mdbook-graphviz v0.0.2 v0.0.2 No nu v0.26.0 v0.26.0 No procs v0.11.3 v0.11.3 No racer v2.1.44 v2.1.44 No ripgrep v12.1.1 v12.1.1 No rmesg v1.0.13 v1.0.13 No rustscan v2.0.0 v2.0.0 No sd v0.7.6 v0.7.6 No starship v0.50.0 v0.50.0 No streampager v0.9.2 v0.9.2 No tealdeer v1.4.1 v1.4.1 No tokei v12.1.2 v12.1.2 No ytop v0.6.2 v0.6.2 No zoxide v0.5.0 v0.5.0 No
まぁ入れたけどWindowsで積極的に使ってるもの以外はあまり使ってないかも。
あとは、ARMコアのNASでも動くように、上記のソフトウェアはsrcをダウンロードして、
GitHub - messense/rust-musl-cross: Docker images for compiling static Rust binaries using musl-cross
を使ってビルドして/opt/binとかにインストールするようにしている。
NASのシェル環境を変えられたのはかなり大きい。こいつもbyobuがないのだけど。
だいぶ書いたのでいっぺん公開する。
また次気が向いたら書きます。
嫁さんに自分の居場所を正直に申告する
ひたすら自動化正確、その9。今どこにいるのか嫁さんからわかる状態にしておく事は、いろんな意味で重要であると考えている。帰ってくるのが遅いので事故でもしたんじゃないかと思っても、位置情報が会社になってると、まだ仕事してるのか、と思うだけだし、それだけで意味がある。しかし、位置情報を嫁さんにだけうまく伝える方法が最近ではあまりないような気がしている。あるのかもしれんけど知らん。だったら安心ナビもどきを自動化の中で作ってしまえと思ってしまった。と、ここまで書いて、そう言えば安心ナビって今でもサービスしてるんだったと思い出した。まぁいいや。さすがに「探される方」に40のおっさんを指定するのって、お店の人に絶対そういう意味にとられるだろうから、申込めるわけがないし。
- Tasker: Received Text [ Type: SMS, Sender: 嫁さん, Content: イマドコ? ] → Get Location [ Source: GPS ] + Calendar Insert [ In/For: 15minutes, Title: イマココ, Description: "", Location: %LOC ]
- myThings: Google Calendarで特定のイマココを含む予定の開始時刻の5分前になったら、Gmailで嫁さんにGoogle Mapへのリンク(https://maps.google.com/maps?q={{場所}})を書き込んだメールを送信する
Google Calendarを経由するあたりが異常にトリッキーだし、SMSで受けてるのにEmailで返しちゃうあたりやっぱりトリッキーだし、myThingsでイベントが発生したかはどうやら15分間隔でポーリングしているようなので(絶対にイベントが発生するようなトリガーを設定して時刻未指定にすると15分おきに発動する。かけなびさんの日記のタイトルを読み上げる自動化を設定したら、15分おきにかけなびさんの日記のタイトルをエアコンが連呼していた。)カレンダーに15分後の予定を設定してポーリングのタイミングでようやく通知されるとか、今知りたいのにめっちゃゆっくり教えてくれるとか使い勝手悪いし、そもそも嫁さんがこの方法で私の居場所がわかる事を覚えていないし、作ってはみたものの使われてはいない。まぁ無理やりにでもやればできなくはないという1つの事例という事で。私に嫁さん以外からイマドコ?ってメール投げても位置情報は返りませんので、私の電話番号を知っててもSMS投げてこないように。
車の中では通知読み上げ
ひたすら自動化生活、その8。車を運転している最中にスマホのNotificationが届くと、どうしても気になってしまう。でも運転中にスマホを注視すると道路交通法違反でつかまってしまう。じゃあどうするか。正確に理解できなくってもいいので、とりあえず音声で教えてもらう事にする。アクションが必要なら自動車を駐車してから行えばいいし、駐車するかどうかの判断くらいできれば良いかなと。
- Tasker: BT Connected [カーナビのBT] + Notification [ * ] → Say [ Text: %NTITLE ]
ちなみにTTSはKDDI研究所さんのN2TTSにお世話になっている
自動車の中で音楽を聞く時にはGoogle Play Musicを利用しているので、カーナビのオーディオは常時BTを向くようにしている。なので、Notificationが届いてスマホがしゃべり出すと、ちゃんとカーオーディオからTTS発話で通知の内容が聞こえてくる。嫁さんからメールが届いていれば買い物してこいという指示かもしれないので駐車して内容を確認するという事が、自宅の駐車場に到達する前に実現できるのである。ただ、今電話帳に入れている嫁さんの名前が非常に短い名前になっているので、「要注意!この人の連絡は無視するといけないぜ」みたいな長い名前にしておくと、車の中でも確実に嫁さんからの連絡だと気付くに違いない。しないけど。
あと、子供が一緒に乗車していてDVD見せろと言わない限りは。DVDを見ている時はあきらめるしかない。車の中でDVDを視聴しているような状況で、スマホ本体がしゃべったところでどうせ聞こえないし。
出張の日はアラームを自動的に設定する
ひたすら自動化生活、その7。出張の日はアラームを自動的に設定する。出張の日はたいてい普段出社する日よりも早起きが必要。飛行機の始発は7:30くらいなので5:30起き、新幹線の場合は大阪に9時過ぎに入ろうと思うと5:00起きになる。寝過ごすとつらいのでアラームを自動的に設定する。
- myThings: Google Calendarで東京出張を含む予定の開始時刻が近づいたら自分に通知する
- Tasker: Notification [Owner Application: myThings, Title: 東京出張] → Set Alarm [Time: 5:30, Message: 東京出張]
同様に大阪出張の場合で5:00という組み合わせもmyThingsとTaskerの両方に設定しておく。そうすると以下の自動化とのコンボで以下のような挙動となる。
Outlookに出張という分類項目の予定を追加すると、Google Calendarにタイトルと時間と場所だけコピーする - しげぽん日記
Outlookに出張というカテゴリで「東京出張」という終日スケジュールを入力すると、それが自動的にGoogle Calendarに同期される。この予定の5分前(つまり出張する前の日の23:55)くらいになると、myThingsによって端末にNotificationがとんでくる。このNotificationを受けてTaskerがアラームをセットする。
なのでOutlookでその日だけ早起きするという事を予約できるようになる。
でも、最近、この組み合わせが正しく動作していないような気がする。Taskerがうまく反応していない模様。手動でTask実行すると設定されるようなので、Profileの設定が何か間違っているのかもしれないけれどよくわからん。結局、手動でアラームを設定している。
さらに言うと本当はActivite Popと連動させてサイレントアラームが設定できるとうれしいのだけど、withingsさんにはまだそういう機能は用意されていない模様。withingsさんのAPIの拡充が望まれる。
さりげなくテザリング
特定アプリは画面自動回転
- Tasker: Application: マルチメディア系のアプリ → Display AutoRotate [ Set:On ]
特定アプリは無音で動かす
- Tasker:Profile: Application: 特定のアプリ → Media Volume Level:0