日本語論文をLaTeXで書いてtextlintで校正するテンプレートを作った
はじめに
この記事の対象者は主に修論・卒論を書く人です。英語論文についてはとくに言及しません。
初めて論文を書くことはとても大変です。しかし、そのひどい初々しい論文を校正する人はもっと大変です。しかも大抵の学生はいっぱい書いてから見て下さい!とお願いしてきます。もはや何から手を付けて良いかすらわからない……。こんなひどい状況を打破するべく、体裁の指摘を自動でやってくれて、内容の指摘に集中できるようになるテンプレートを用意しました!
これは実は原型のほとんどが実際にM2の時に研究室内で使っていた物なのですが、
- 去年は忙しくて公開できるような形に持っていく余裕が無かった
- でもこのまま忘れてしまうには惜しい
- ↑を作る上で利用させていただいたOSSのメンテナになったので宣伝したくなった
という事情で今年公開することにしました。Pull Requestお待ちしています。
テンプレートの使い方
リポジトリをフォークする、又は以下の画像にあるUse this templateというボタンをクリックしてください。
Creative Commons Zero(CC0)*1で公開していますので、著作者表記などに配慮することなく自由にご利用頂けます。
テンプレートの特徴
LaTeX on Docker
Word論文はリジェクト!とまでは言いませんが、こうした自動化の恩恵を受けやすいのは単純なテキストで書けるLaTeXです。分からない人は覚えて下さい。
昔はLaTeXの環境構築は大変だと言われていました(要出典)。今もこだわると大変ですが、どうしてもヒラギノを使わないと死んでしまう人以外はDockerで構築するのが良いでしょう。WindowsでもMacでもLinuxでも安定した結果を得られます。
僕は以下のリポジトリで管理し、tagを付ける度にDockerhubでビルドするようにしています。このテンプレートではこのイメージを利用していますが、任意のイメージに差し替えることも出来ます。
https://hub.docker.com/r/pddg/latex
Makefile
どんなコマンドを打つか思い出すのが面倒なので、このテンプレートではmakeを使います。Windowsではscoopを使ってmakeをインストールする他、開発環境をWSLに持ってしまうなどがおすすめです。
make
すればpdfができて、 make clean
すれば中間ファイル含めて全て削除される、そういうシンプルな世界がオススメです。ちなみにGNU Makeの使い方を解説した僕の資料もあるので、是非使ってみてください。
Git管理
今は2019年なのでGitで管理してGitHubにpushするのは、もはや特殊でも何でもありません。
現在はGitHub Actionsという超便利なものがあります。これを使って、このテンプレートでは以下の様なワークフローを組みます。textlintやreviewdogについては後述します。
VSCode対応
Visual Studio CodeはMac・Windows・Linuxのいずれでもよく使われており、非常に便利なエディタです。加えて、最近はDockerやWSLなどとの連携も強化されており、ポータブルな開発環境の構築が簡単になってきました。VSCodeのLaTeX用拡張であるLaTeX WorkshopはビルドをDockerコンテナ内で実行することに対応しており、以下の様な環境をすぐに構築出来ます。
PDFからソースへ、逆にソースからPDFの該当箇所へジャンプできます。また、保存時に自動ビルドが走ります。
Dockerがインストール済みのホストにVSCodeをインストールして、必要なエクステンションを入れれば上の画像のような環境が完成するようになっています。
まとめ
まずこのテンプレートによって2つの問題が解決します。
- 環境構築が難しい
- DockerとVSCodeが使用可能であればすぐにPDFを生成できる
- 使い方が難しい
- 最低限
make
を覚えていればPDFが生成できる
- 最低限
書き始めるまでに脱落する人をできるだけ救えることができれば幸いです。
実はもう一つ、タイトルにもある重要な特徴があるので次で紹介します。
textlintで校正する
論文では全体の体裁を意識して記述する必要があります。基本的に表記揺れや「ですます調」「である調」の混じった文などは許されません。しかし、これらを人間が意識して校正するのは多大な労力を必要とします。そこでこれらについてある程度ツールで指摘出来るようにします。これを実現するのがtextlintです。
環境構築
このような校正自体をローカルで実行する必要はないかなと思っています。PushしたときにCIでちゃんと警告されればそれでいいかなと。
一応Node製のツールなので、LaTeXより楽に環境を整えることができます。単にNode JSをインストールしてnpm installすれば環境構築は完了です。
textlint-plugin-latex2e
textlintは内部的に文章をASTへ変換して使用します。デフォルトではLaTeXファイルを読み込めないため、このプラグインを利用します。
昨年メンテナとして加えて頂き、サードパーティー製のLaTeXパーサへの移行のお手伝いをしました。
textlintのASTとLaTeXパーサが解析したASTをすり合わせる、というような細かい作業をしており、おそらく潜在的なバグがあります。もしうまく動かないときは積極的にissueを上げて頂ければと思います*2。
Rule
textlintで警告を出すために様々なルールを追加していきます。
とは言ってもtextlint-rule-preset-japaneseまたはtextlint-rule-preset-ja-technical-writingをインストールすれば終了です。これはいくつかのルールをまとめたもので、個別のルールに対して設定を書くことも出来ます。全部書くと多すぎるので、言及の必要があるものだけ書きます。
これらは様々なルールを一括で導入できて便利な反面、この部分ではこのルールを無視したいと思ったとき、(後述するtextlint-filter-rule-commentsを利用すると)毎回% textlint-disable ja-technical-writing/ルール名
として記述する必要があり、ちょっと面倒です。不要なルールがあったり、各ルールの最新版が常に反映されているわけではないなど、自分でルールを管理した方が全体を把握でき便利な場合もあります。とりあえず上記プリセットを突っ込んで使ってみて、不満があるなら自分で再構築するのが良いかと思います。
各ルールの設定は、上記プリセットを導入している場合と、そこに含まれるルールを個別に導入した場合で書き方が異なります。
導入しているとき
{ "rules": { "preset-ja-technical-writing": { "ルール名": { "設定名": "値" } } } }
導入していないとき
{ "rules": { "ルール名": { "設定名": "値" } } }
textlint-rule-sentence-length
https://github.com/textlint-rule/textlint-rule-sentence-length
一文当たりの単語数ではなく文字数に制限をかけます。英語の文章を含む場合、ignoreする必要があるかもしれません。
textlint-rule-no-mix-dearu-desumasu
https://github.com/textlint-ja/textlint-rule-no-mix-dearu-desumasu
である、ですます調を統一します。指定しない場合、全体から見て多い方が優先されるので、論文ではである調に統一します。
{ "rules": { "no-mix-dearu-desumasu": { "preferInHeader": "である", "preferInBody": "である", "preferInList": "である", // 文末以外でも、敬体(ですます調)と常体(である調)を厳しくチェックするかどうか "strict": true } } }
textlint-rule-ja-no-mixed-period
https://github.com/textlint-ja/textlint-rule-ja-no-mixed-period
「。」と「。」のミスはprh(後述)で修正するため、こちらはどちらかというとそもそも文末のピリオド抜けを検知するために使います。
{ "rules": { "ja-no-mixed-period": { "periodMark": "。", "allowPeriodMarks": [], "allowEmojiAtEnd": false, // --fix時に直すかどうか "forceAppendPeriod": true } } }
textlint-rule-no-doubled-joshi
https://github.com/textlint-ja/textlint-rule-no-doubled-joshi
連続して同じ助詞を使用しないようにします。「AにBにします」みたいな文章に対してエラーを出します。当初全角カンマをうまく取り扱えず誤検知が起きていたため、要望を出して改善して頂きました*3。バージョン3.7.0以降を推奨します。
{ "rules": { "no-doubled-joshi": { "min_interval" : 1, "strict": false, "allow": ["も","や"], "separatorCharacters": [ ".", ".", "。", "」" ], "commaCharacters": [ ",", "、" ] } } }
textlint-rule-prh
https://github.com/textlint-rule/textlint-rule-prh
表記揺れを修正するためのルールです。「.」と「。」、「サーバ」と「サーバー」など、間違えやすい語句を検知して修正します。
{ "rules": { "prh": { "rulePaths" :["prh.yml"] } } }
表記揺れの一覧とその修正はprh.yml
として別ファイルに書き込みます。
version: 1 rules: # Githubやgithubを修正する - expected: GitHub # ピリオドとカンマを修正 - expected: . pattern: /[。。]/g - expected: , pattern: /[、、]/g
LaTeXではリンクがリンクとしてパースされない(単なる文字列として処理される)ため、リンクを含む場所ではignoreする必要があるかもしれません。必要な表現をどんどん足していくことで、表記揺れを自動で検知できるようになります。
Filter
設定したルールに対して例外を設けることが出来ます。
textlint-filter-rule-whitelist
https://github.com/textlint/textlint-filter-rule-whitelist
例えばprhでGitHubの誤字を修正したい場合、リンクまで検知してしまうという問題がありましたが、ここでホワイトリスト製で例外を管理できます。他のルールで引っかかった語もこれで排除することが出来ます。
{ "filters": { "whitelist": { "allow": [ "github.com" ] } } }
textlint-filter-rule-comments
https://github.com/textlint/textlint-filter-rule-comments
一部だけlintをオフにしたい場合に使えます。
{ "filters": { "comments": { "enablingComment": "textlint-enable", "disablingComment": "textlint-disable" } } }
例えば英文では文字数制限を解除したい場合、以下の様に書きます。
% textlint-disable sentence-length \section{英語} hoge hoge fuga fuga % textlint-enable sentence-length \section {日本語} ほげほげふがふが
ルール名を指定しない場合、全てのルールを無視します。ルールはカンマ区切りで複数指定することが出来ます。
GitHub ActionsでCI/CD
textlintしてreviewdogを走らせ、masterにマージしたら自動でPDFをビルド、タグを打つとそれをリリースします。プライベートリポジトリでは
- Freeアカウント:2000分/月
- Proアカウント:3000分/月
のビルド時間が無料枠になります*4。GitHub Student Developer Packに登録することでPro相当の恩恵を得られるので、大学のメールアドレスを持っている人は登録しましょう*5。また、大学や研究室単位でTeamやOrganizationを使用している場合、上記に当てはまらない可能性があるため、そのOwnerに確認しましょう。
Travis CIやCircle CIを使う場合と比べて、トークン等を自分で取得・指定する必要がなく楽です。テンプレートにはすでにGitHub Actionsのworkflowが組み込まれており、特に設定することなく自動で実行されます。
Lint
LaTeXのコードを静的解析し、ルールに違反する文章を検出します。このテンプレートではPull Requestを発行/更新するとこのワークフローがトリガーされるようになっています。このワークフローの実行にはおよそ30~40秒かかります。詳細は .github/workflows/lint.yaml
を見てみて下さい。
違反した文章を修正しやすくするよう、reviewdogからコメントをもらえるようにしています。更新は全てPull Requestを経由することで、masterブランチは常にビルド可能かつルールに違反していない文章のみが存在するようになります。
↓修正箇所をこんな感じで指摘してくれます。
Build
masterブランチへのpushでビルドを実行し、その成果物をアップロードしてダウンロード出来るようにします。1分~1分半程度かかります。
この成果物はデフォルトでは90日間有効なようです*6。それ以上の期間更新したい場合、次章のようにタグを付けて自動リリースしましょう。
Release
タグを付けたらGitHub Releasesに新しいリリースを作ってPDFをアップロードします。1分~1分半程度かかります。
こちらはファイルの保持期限が特にないため、第一稿、第二稿、完成などのような段階ごとにタグを打っておくと分かりやすくて良いでしょう。
注意点
インライン数式に$
を使用すると大幅なパフォーマンス劣化を生じます。インライン数式は \( x^2 \)
のように記述することでパフォーマンス劣化を避け高速にlintを実行できます*7。
$
によるインライン数式が多数利用されたTeXソースに対するlintは全く終わらなくなってしまいます。そのため、同梱しているGitHub Actionsのワークフローではタイムアウトを10分に設定しており、これを超えると強制的にワークフローが終了します。これによりGitHub Actionsの無料ビルド時間を食い潰さないようになっていますので、もしタイムアウトしたらインライン数式によるパフォーマンス劣化を疑ってみて下さい。
原因を特定したので近く修正版を出します。これにより、インライン数式によるパフォーマンス劣化は起きない見込みです。
[追記 2020/12/7] こちらの問題を修正したv1.0.3がリリースされました。こちらのテンプレートもそれに合わせてバージョンアップしv1.0.0をリリースしました。
まとめ
LaTeXソースを静的解析するtextlintのプラグインを利用することで、校正作業の一部を自動化する方法を示し、そのテンプレートを紹介しました。体裁は自動で指摘し、内容を人が集中してレビューすることでレビュアーの負荷を少しでも下げることができれば幸いです。
私自身はアカデミアから遠く離れてしまい、既に論文を書く身ではなくなっていますが、textlint-plugin-latex2eへのコントリビュートは続けていきますのでissueやPRお待ちしています。
*2:ただしメンテナーはサポート担当ではないので、それを誤解されませんよう…
*3:https://github.com/textlint-ja/textlint-rule-no-doubled-joshi/issues/26
*4:他のプライベートリポジトリと共有であることに注意が必要です。
*5:https://help.github.com/ja/github/teaching-and-learning-with-github-education/applying-for-a-student-developer-pack
*7:これを書きながらインライン数式が(textlint-plugin-latex2eの不具合により)ASTを破壊している気がしてきたのでたぶん直します
Protocol BuffersのGolang用API v2を使ってgRPCでHello Worldする
v2とは???
以下の記事にまとまっています。
Protocol BufferのGo言語用APIの新しい実装です。v1.20から始まるという奇怪なバージョニングになっています。しかも、後方互換性のない変更でありAPIv2とまで呼ばれているのに、モジュールの方はメジャー番号が1というカオスな状況です*1。
2020年3月に出たばかりであり、まだまだ情報が少ないです。特に上の記事にもあるように新しいprotoc-gen-goはgRPC用のスタブを生成しなくなっており、そもそもこの新しいモジュールを使ってHello worldするのに試行錯誤したため、ここにメモを残しておきます。
ちなみに色々調べた後gRPCの公式ドキュメントを見たら普通に書いていました。最初から一次情報に当たれ、という話でした……
環境
- macOS Catalina 10.15.6
- Go 1.15.2
- protoc v3.13.0
- google.golang.org/protobuf/cmd/protoc-gen-go v1.23.0
- google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.0.1
Hello World
プロジェクトの準備
適当にgo mod initしてプロジェクトを作ります。
$ mkdir go-protobuf-v2-sample && cd go-protobuf-v2-sample $ go mod init github.com/pddg/go-protobuf-v2-sample
ツール類をインストールします。protocのインストール方法は何でもよいです。GitHub Releasesからダウンロードして展開するのが楽でしょう。
$ PROTOC_VERSION=3.13.0 $ PROTOC_PKG=protoc-${PROTOC_VERSION}-osx-x86_64.zip $ wget -q -O tmp/ https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/${PROTOC_PKG} $ unzip -q -o tmp/${PROTOC_PKG} $ ./bin/protoc --version libprotoc 3.13.0
protocのプラグインをインストールします。僕はGoのプロジェクトでGo製のCLIツールが必要な時はgexを使っています。
$ gex --add google.golang.org/protobuf/cmd/protoc-gen-go $ gex --add google.golang.org/grpc/cmd/protoc-gen-go-grpc
サービスを定義する
proto/hello.proto
に名前を与えると Hello ○○
と返してくれるだけのサービスを定義します。
syntax = "proto3"; option go_package = "github.com/pddg/go-protobuf-v2-sample/hello/pb"; message HelloRequest { string name = 1; } message HelloResponse { string message = 1; } service HelloService { rpc Hello(HelloRequest) returns(HelloResponse); }
gRPC用のコードを自動生成する
v1まではgithub.com/golang/protobuf/protoc-gen-goを使い、 --go_out=plugins=grpc:パス
といった形式で指定することでコードを自動生成していました。
v2からは2つのプラグイン(google.golang.org/protobuf/cmd/protoc-gen-go とgoogle.golang.org/grpc/cmd/protoc-gen-go-grpc)を使い、それぞれ役割の違うコードを2つ自動生成します。
今回は ./hello/pb
以下に自動生成したファイルを置くことにします。
$ mkdir -p hello/pb $ ./bin/protoc \ -I proto/ \ --plugin protoc-gen-go=bin/protoc-gen-go \ --plugin protoc-gen-go-grpc=bin/protoc-gen-go-grpc \ --go-grpc_opt paths=source_relative \ --go-grpc_out hello/pb/ \ --go_opt paths=source_relative \ --go_out hello/pb/ \ proto/hello.proto
これで hello/pb/hello.pb.go
と hello/pb/hello_grpc.pb.go
という2つのファイルが生成されます。
hello.pb.go
は、gRPCを使わずGoでProtocol Buffersを利用するために必要なコードが生成されるようです。HelloServiceの定義はここにはありません。
hello_grpc.pb.go
には、gRPCを利用するための定義が記述されています。サーバ側が満たすべきインターフェースと、クライアントの実装が含まれます。
サーバを実装する
github.com/pddg/go-protobuf-v2-sample/hello/pb
にある HelloServiceServer
というインターフェースを満たすようにサーバを実装します。 hello/hello.go
として記述します。
以下のサンプルを参考に実装しました。
package hello import ( "context" "github.com/pddg/go-protobuf-v2-sample/hello/pb" ) // HelloServiceの実際の実装。 type helloServer struct{ pb.UnimplementedHelloServiceServer } // Hello ○○するハンドラ func (hs helloServer) Hello(ctx context.Context, request *pb.HelloRequest) (*pb.HelloResponse, error) { return &pb.HelloResponse{Message: "Hello " + request.Name}, nil } // 実装したサーバを初期化する関数 func NewHelloServiceServer() pb.HelloServiceServer { return &HelloServer{} }
cmd/server/main.go
を作成し、サーバを起動するコマンドを実装します。オプションとしてリッスンするportとホスト名を取れるようにしました。
簡単のため、http2ではなくtcpでリッスンします。
package main import ( "flag" "fmt" "log" "net" "google.golang.org/grpc" "google.golang.org/grpc/reflection" "github.com/pddg/go-protobuf-v2-sample/hello" "github.com/pddg/go-protobuf-v2-sample/hello/pb" ) var ( port int host string ) func main() { flag.IntVar(&port, "port", 8080, "Port number") flag.StringVar(&host, "host", "0.0.0.0", "Listen host name") flag.Parse() listenAddr, err := net.Listen("tcp", fmt.Sprintf("%s:%d", host, port)) if err != nil { log.Fatalf("Failed to listen '%s:%d'\n", host, port) } log.Printf("HelloServiceServer is listening on tcp://%s:%d\n", host, port) s := grpc.NewServer() helloServer := hello.NewHelloServiceServer() pb.RegisterHelloServiceServer(s, helloServer) // Enable reflection reflection.Register(s) if err := s.Serve(listenAddr); err != nil { log.Fatal(err) } }
せっかくv2で実装するのでreflectionを有効化しました。これにより、grpcurlなどのクライアントツールで proto ファイルを読み込む必要がなくなります。
このコマンドを bin/hello-server
としてビルドします。
$ go build -o bin/hello-server ./cmd/server
クライアントコマンドの実装
cmd/client/main.go
に記述します。オプションとしてサーバのアドレスを渡せるようにしました。
package main import ( "context" "flag" "fmt" "log" "os" "time" "google.golang.org/grpc" "github.com/pddg/go-protobuf-v2-sample/hello/pb" ) var server string func main() { flag.StringVar(&server, "server", "localhost:8080", "Server address") flag.Parse() if flag.NArg() == 0 { flag.Usage() os.Exit(1) } args := flag.Args() ctx, cancel := context.WithTimeout(context.Background(), 10 * time.Second) defer cancel() conn, err := grpc.DialContext(ctx, server, grpc.WithInsecure()) if err != nil { log.Fatal(err) } client := pb.NewHelloServiceClient(conn) response, err := client.Hello(ctx, &pb.HelloRequest{Name: args[0]}) if err != nil { log.Fatal(err) } fmt.Println(response.Message) }
このコマンドを bin/hello-client
としてビルドします。
$ go build -o bin/hello-client ./cmd/client
動かしてみる
適当にターミナルを立ち上げ、サーバを起動します。
$ ./bin/hello-server -port 8080 -host 0.0.0.0
別のターミナルからクライアントコマンドを使用して接続し、Hello worldしてみます。
$ ./bin/hello-client -server localhost:8080 world
Hello world
やったぜ。
grpcurlで叩いてみる
grpcurlはコマンドラインで使えるgRPC用の汎用ツールです。curlのような感覚で使えます。
$ gex --add github.com/fullstorydev/grpcurl/cmd/grpcurl
grpcurl サーバアドレス サービス名.呼び出す関数
という形式で使えます。 データを送信したい場合、JSON形式でPOSTできます。レスポンスもJSON形式で返ってきます。
今回のHelloServiceを叩く場合は以下の様になります。
$ gex grpcurl -plaintext -d '{"name": "world"}' localhost:8080 HelloService.Hello { "message": "Hello world" }
まとめ
これまでのprotoc-gen-goが単体でGo用のProtocol Buffersのコード・gRPCのスタブコードの両方を生成するスタイルよりも、役割が明確になったのはよかったのではないでしょうか*2。
v2の恩恵をまだreflectionくらいしか理解していないので、もう少し色々探索していきたいですね。
[追記]
実装全体を以下で公開しました。
M75q-1 TinyをWindows以外からでもリモート管理したい
[2020/9/30 21:00 追記]
libcurl.so.4の置き換えは推奨されない旨と、bootorderの変更が実際には反映されない旨を追記しました。
M75q-1 Tinyって何?
オタク大好き小型激安高性能マシンです。
My new gear… pic.twitter.com/okz1tkhxdX
— 社会人 (@pudding_info) September 25, 2020
楽天リーベイツの20%ポイント還元を使って、35090円の7018ポイント還元で実質28072円で買えました*1。安く買う方法や詳細はのらねこ先生のブログおよびのらねこ先生のバイヤーズガイドをご覧下さい。 スペックは以下の通りです。
DASH
Desktop and mobile Architecture for System Hardware(DASH)とは、M75q-1 Tinyに搭載されているリモート管理機能です。これによって、リモートからマシンの電源を入れたり、BIOSの設定などを変更したりできます。サーバマシンなどに搭載されているIPMIなどと似たものです。BIOSの設定から有効にすることが出来ます(デフォルトはオフになっています)
これを利用するためのアプリケーションとして、Windows向けにAMD Management Console(AMC)というものがAMD公式で用意されており、以下のWebサイトからダウンロードできます。
上述したのらねこ先生のバイヤーズガイドでもこちらの利用方法が解説されています。しかしオタクたるもの、GUIアプリケーションよりも黒い画面から操作したいし、Windowsなんて持ってない……*2。そんな要望を満たすアイテムは無いものか…………
あるじゃん👉
AMDは神。
Windowsだけでなく、UbuntuおよびFedora向けのdebパッケージが公開されています。Dockerコンテナに押し込めてしまえば、WindowsだろうがMacだろうがLinuxだろうが、関係無くDASH CLIを使えそうな予感がします。というわけでやっていきしましょう。
Docker Image for DASH CLI
大変残念ながらwgetやcurlなどを使ってアーカイブを取得することができなかった*3ため、ダウンロードページ( https://developer.amd.com/tools-for-dmtf-dash/#downloads )から手動でdashcli-setup_3.0.0-285-Public_amd64_deb.tar.gzをダウンロードしてください。
同じディレクトリに以下のDockerfileを配置します。
FROM ubuntu:18.04 ARG DASHCLI_VERSION=3.0.0-285 ENV DASHCLI_PKG=dashcli-setup_${DASHCLI_VERSION}-Public_amd64 ENV TZ Asia/Tokyo ENV DEBIAN_FRONTEND nointeractive WORKDIR /opt/dashcli RUN apt-get update \ && apt-get install -y --no-install-recommends \ sudo \ libssl1.0 \ libsdl1.2debian \ libsdl2-2.0-0 \ libxml2 \ mono-devel \ libcurl3 \ && apt-get clean \ && apt-get autoremove -y --purge \ && rm -rf /var/lib/apt/lists/* ADD ${DASHCLI_PKG}_deb.tar.gz . RUN dpkg -i ${DASHCLI_PKG}_deb/${DASHCLI_PKG}.deb \ && dashcli version \ && rm -rf ${DASHCLI_PKG} ENTRYPOINT ["/usr/bin/dashcli"] CMD ["help"]
後はdocker buildするだけです。
$ docker build . -t dashcli:3.0.0-285 # 一時的にdashcliというエイリアスを作成しておく $ alias dashcli="docker run --rm -i dashcli:3.0.0-285" $ dashcli version DASH CLI v3.0.0.285
[2020/9/30 21:00 追記]
libcurl.so.4を置き換えないようにDockerfileを修正しました。
追記ここまで
注意
ちゃんとライセンスを読んでいないので確かなことは言えないのですが、勝手にこのバイナリを含むDockerイメージを公開する(==再配布する)行為はライセンス違反となる可能性があります。念のためプライベート・非商用での利用をお勧めします。
DASH CLIの使い方
使い方に関するドキュメントが非常に少ないです*4。僕自身もしばらく触っただけで、まだ全く活用は出来ていません。
実はdebパッケージにドキュメントが含まれており、 /usr/share/doc/dashcli-setup/
以下に配置される……はずなんですがこのディレクトリは空になっています(なんで?)。debパッケージを手動で展開し、手元にドキュメントを用意します。以下のコマンドで可能です。
$ DASHCLI_VERSION=3.0.0-285 $ docker run --rm -v $PWD/doc:/doc --entrypoint /bin/bash dashcli:${DASHCLI_VERSION} -c "dpkg-deb -x dashcli-setup_${DASHCLI_VERSION}-Public_amd64_deb/dashcli-setup_${DASHCLI_VERSION}-Public_amd64.deb dash_deb && cp -r ./dash_deb/usr/share/doc/dashcli-setup/* /doc/" $ ls doc/ DASHActiveDirectory.pdf License LinuxDASHCliUserGuide.pdf DASHCertificates.pdf LinuxDASHCliDeveloperGuide.pdf LinuxReleaseNotes.pdf
LinuxDASHCliUserGuide.pdfよりもLinuxDASHCliDeveloperGuide.pdfの方が情報が豊富でおすすめです。
ヘルプの表示
$ dashcli help DASH CLI v3.0.0.285 Command line utility to manage DASH capable systems. Usage: dashcli [options] commands Options allowed: -h <host> Host Name -p <port(s)> Server Port(s)(For discovery more than one ports can be specified seperated by commas) -u <username> User Name -P <password> Password -a <digest|basic|gss> Authentication Type [default=digest] -S <http|https> HTTP Scheme [default=http] -c <certificate> Certificate file (Alternatively certificate can be stored in certificate store) -C Ignore certificate/do not verify certificate -t <targetpath> Target Path -s <startip> Start IP address for discovery (only for discovery) -e <endip> End IP address for discovery (only for discovery) -T <timeout> Timeout in seconds -v <1|2> Verbose Level [ 1 - More explanation on error or 2-Dump WSMAN data] -o <verboseoutput> Verbose output file to dump wsman data [default is sdtout]. Commands allowed: help Display help version Show dashcli version enumerate Enumerate targets discover Perform discovery registeredprofile Checks the profile support indication Indication commands(subscribe for indication, create filters/destinations listenevents Listen for events/alerts textredirection Configure Text Redirection services usbredirection Configure USB Redirection services kvmredirection Configure KVM Redirection services raw Issue raw commands account Creates,Deletes and Manages the Account roles Manages the Roles shell Launch interactive DASH shell capabilities Display Capabilities of a target For commands specific to targets dashcli help target Where allowed targets are alertdestination asset battery bios bootconfig computersystem dhcpclient dnsclient ethernetport fan filtercollection indicationfilter indicationsubscription indicatorled ipconfiguration ipinterface kvmredirection logentry memory mediaredirection networkport opaquemanagementdata operatingsystem pcidevice physicalcomputersystemview platformwatchdog powersupply processor recordlog registeredprofile role sensor serviceprocessor software textredirection usbredirection user Example usage: Discovery example: dashcli -s 192.168.0.4 -e 192.168.0.15 -u admin -P admin -p 623 discover dashcli -s 192.168.0.4 -e 192.168.0.15 -p 623 discover dashcli -s 192.168.0.4 -e 192.168.0.15 -p 623,664,8889 discover dashcli -s 192.168.0.4 -e 192.168.0.15 -S http -p 623 discover dashcli -s 192.168.0.4 -e 192.168.0.15 discover info dashcli -h hounds-demo -S http -p 623 discover dashcli -h 192.168.0.8 -S http -p 623 discover dashcli -h hounds-demo -S http -p 623 discover info dashcli -h 192.168.0.8 -S http -p 623 discover info Enumerate target example: dashcli -h 192.168.0.4 -S https -p 664 -u admin -P admin -C enumerate computersystem dashcli -h 192.168.0.4 -S https -p 664 -u admin -P admin -C enumerate processor dashcli -h 192.168.0.4 -S https -p 664 -u admin -P admin -C enumerate bootconfig Executing commands on a target example: dashcli -h 192.168.0.4 -S https -p 664 -u admin -P admin -C -t computersystem[0] power on dashcli -h 192.168.0.4 -S https -p 664 -u admin -P admin -C -t processor[0] enumerate memory dashcli -h 192.168.0.4 -S https -p 664 -u admin -P admin -C -t bootconfig[0] changebootorder 1 0
DASH CLIにはCommandsとtargetというものがあるようです。Commandsは操作するコマンド、targetはコマンドによって操作される対象を表しています。ただ、上記helpに表示されているCommandsというものは、どのtargetに対しても有効な物です。各target特有のCommandは、 dashcli help ターゲット名
で表示することができます。
DASHが有効なホストの探索
IPアドレスの範囲を指定することで、自動で探索してくれます。DASHを有効化した後、IPアドレスを固定せず、MACアドレスも控え忘れたままディスプレイから引っこ抜いてしまったときに使えそうですね。
$ dashcli -s 192.168.0.2 -e 192.168.0.4 discover Checking 192.168.0.2 ... Checking 192.168.0.3 ... Checking 192.168.0.4 ... DASH system(s) discovered: 192.168.0.4:623
電源状態の取得・変更
各ホストの詳細の表示・変更にはログイン情報を渡す必要があります。デフォルトのユーザ名とパスワードは以下の通りです。
- ユーザ名:
Administrator
- パスワード:
Realtek
BIOSのUI上からではこれらを変更する方法はなく、このコマンド経由でのみパスワード変更等が可能です。 アカウントの作成・削除もできるため、後述する方法で新規ユーザの作成と、Administratorの無効化を行う方がセキュリティ的には良いかと思います。
電源状態を表示してみます。
$ dashcli -h 192.168.XX.YY -u Administrator -P Realtek -C -t computersystem[0] power status Power state : On
-C
を付けないと Error: Connection Failed : Trnasport initailization failed
というエラーが出ます。
電源のオン・オフ・再起動は以下のコマンドで可能です。
# 電源オン $ dashcli -h 192.168.XX.YY -u Administrator -P Realtek -C -t computersystem[0] power on # 電源オフ $ dashcli -h 192.168.XX.YY -u Administrator -P Realtek -C -t computersystem[0] power off # 再起動 $ dashcli -h 192.168.XX.YY -u Administrator -P Realtek -C -t computersystem[0] power restart
注意点として、これらのコマンドはその実行結果を保証しません。つまり、状態のリクエストはしますが、その状態に移行したことは保証してくれません。power onした後に実際にそのホストが起き上がってくるかどうかは、別の方法で検証する必要があります。
boot順の表示・変更
[2020/9/30 21:00 追記]
boot順の変更はSuccessするものの、試した限りではそれが実際のブートに反映されることはありませんでした。
変更後、setdefault
や setnext
、 setnextsingleuse
も試しましたが変化はありませんでした。
Windows版DASH CLI、AMD Management Console、Realtek Management Consoleのいずれでも同様でした。もし成功する方が居られれば、情報頂きたいです。
この事象についてLenovoにメール問い合わせをしましたが、技術的な内容はオンサイトの技術サポートに問い合わせて欲しいと言われました。
オンサイトは電話窓口しかないため、電話する気力が湧けば確かめてみたいと思っては居ます(BIOS経由でEnableできる特殊な機能のため、回答はあまり期待していません)。
一応AMDのDASHサポートにも問い合わせてみてみましたが、おそらくM75q-1 TinyのUEFIおよびRealtekのDASH firmwareの問題だと思われるため、こちらも良い回答は期待していません。
追記ここまで
まずはブート順の表示
$ dashcli -h 192.168.XX.YY -u Administrator -P Realtek -C enumerate bootconfig Boot Config Instance 0 Element Name : Bootcfgsetting:0 Instance ID : Bootcfgsetting:0 Is Default Configuration : No Is Current Configuration : No Is Next Configuration : Yes Is Next Single Configurat: No Boot Device(s) : Device 0 : CIM:Windows Boot Manager:1 Device 1 : CIM:UEFI: PXE IPV4 Realtek PCIe GBE Family Controller:1 Device 2 : CIM:UEFI: PXE IPV6 Realtek PCIe GBE Family Controller:1
WIndowsのブートマネージャーが最初に、次にIPv4でのPXEブートが来ているようです。この順序を逆にしてみます。
$ dashcli -h 192.168.XX.YY -u Administrator -P Realtek -C -t bootconfig[0] changebootorder 1 0 Boot Order Changed Successfully $ dashcli -h 192.168.XX.YY -u Administrator -P Realtek -C enumerate bootconfig Boot Config Instance 0 Element Name : Bootcfgsetting:0 Instance ID : Bootcfgsetting:0 Is Default Configuration : No Is Current Configuration : No Is Next Configuration : Yes Is Next Single Configurat: No Boot Device(s) : Device 0 : CIM:UEFI: PXE IPV4 Realtek PCIe GBE Family Controller:1 Device 1 : CIM:Windows Boot Manager:1 Device 2 : CIM:UEFI: PXE IPV6 Realtek PCIe GBE Family Controller:1
元に戻すときは再度 changebootorder 1 0
をすることで入れ替わります。この状態で前述のコマンドを使って再起動をかければ、PXEブートするはずです。
ただ、多くの場合は一時的にブート順序を変えたいのではないでしょうか?このコマンドは恒久的に変更してしまうため、このような変更を行うにはいいかんじのタイミングで再度ブート順序を変更する必要がありそうです。
他にもどうもsetnextとかsetnextsingleuseとかいうコマンドがあるようですが、使い方も意味も分かりません……*5
ユーザの作成・削除
さすがにデフォルト値のユーザを使い続けるのはセキュリティ的に良くありません。ユーザ一覧を見てもAdministratorしかいないことが分かります。
$ dashcli -h 192.168.XX.YY -u Administrator -P Realtek -C enumerate user User Instance 0 Name : Administrator User id : Administrator Element Name : Account Organization Name(s) : DMTF Enabled State : Enabled Requested State : Not Applicable Associated Role(s) : Role:0, Role:1, Role:2
DASHはRole Based Access Controlを採用しているようで、Roleごとに権限が与えられており、このRoleを特定のユーザに付与することで、そのユーザに実行可能な操作を限定できるようです。デフォルトのAdministratorには多くの権限が付与されているため非常に危険です。
$ dashcli -h 192.168.XX.YY -u Administrator -P Realtek -C enumerate role Role Instance 0 Role Name : Role:0 Privileges : priv:0 (BaseDesktopAndMobile(Execute)), priv:11 (SimpleIdentityManagement(Execute)), priv:12 (RoleBasedAuthorization(Execute)) Role Instance 1 Role Name : Role:1 Privileges : priv:13 (TextConsoleRedirection(Execute)), priv:14 (USBRedirection(Execute)), priv:21 (KVMRedirection(Execute)) Role Instance 2 Role Name : Role:2 Privileges : priv:0 (BaseDesktopAndMobile(Execute)), priv:1 (CPU(Execute)), priv:2 (BootControl(Execute)), priv:3 (PowerStateManagement(Execute)), priv:4 (Indications(Execute)), priv:5 (SystemMemory(Execute)), priv:6 (SoftwareInventory(Execute)), priv:7 (Sensors(Execute)), priv:8 (Fan(Execute)), priv:9 (PowerSupply(Execute)), priv:10 (PhysicalAsset(Execute)), priv:15 (DHCPClient(Execute)), priv:16 (IPInterface(Execute)), priv:17 (BIOSManagement(Execute)), priv:18 (OperatingSystem(Execute)), priv:19 (RecordLog(Execute)), priv:23 (EthernetPort(Execute)), priv:24 (NetworkPort(Execute))
まずはユーザを作成します。
$ dashcli -h 192.168.16.51 -u Administrator -P Realtek -C \ -t computersystem[0] \ user add ユーザ名 パスワード Organization名 User Added Successfully
なぜかOrganization名は反映されませんでした。
$ dashcli -h 192.168.XX.YY -u Administrator -P Realtek -C -t user[1] show User Instance 1 Name : ユーザ名 User id : ユーザ名 Element Name : Account Organization Name(s) : DMTF Enabled State : Enabled Requested State : Not Applicable Associated Role(s) : N/A
この状態ではRoleが付与されていないのでこのユーザでは何も出来ません。Administratorと同じRoleを付与します。
$ dashcli -h 192.168.XX.YY -u Administrator -P Realtek -C -t user[1] assignroles Role:0 Role:1 Role:2 Roles assigned Successfully $ dashcli -h 192.168.XX.YY -u Administrator -P Realtek -C -t user[1] show Name : ユーザ名 User id : ユーザ名 Element Name : Account Organization Name(s) : DMTF Enabled State : Enabled Requested State : Not Applicable Associated Role(s) : Role:0, Role:1, Role:2
これでこのユーザを使えるようになったはずです。
$ dashcli -h 192.168.XX.YY -u ユーザ名 -P パスワード -C -t computersystem[0] power on Power state 'On' was applied successfully
使えることが分かったので、Administratorは無効にしておきます。
$ dashcli -h 192.168.XX.YY -u ユーザ名 -P パスワード -C -t user[0] disable Error: Disabling User
えぇ…………
仕方が無いのでパスワード変更にします。
$ dashcli -h 192.168.XX.YY -u ユーザ名 -P パスワード -C -t user[0] changepassword 新パスワード User password changed Successfully
これで多少は安心できそうですね。
LEDも操作できる!?
できませんでした……。
$ dashcli -h 192.168.XX.YY -u Administrator -P Realtek -C enumerate indicatorled Indicator LED(s) not found
何台もM75q-1 Tinyがある場合に点滅させたりできたら楽しそうだなと思ったんですが残念……我々はテプラから逃れられないようです。
JSON形式での実行と表示
こんなよく分からない形式のデータを扱うのつらそう……と思いましたが、調べてみるとどうもJSONでのコマンド実行・出力が出来るようです。すごいですね。
ただちょっとクセがあります。
入力するJSONは位置引数以外は単にオプション名をキーに、その値をバリューとするように構成します。ただし、-C
のような値を取らないフラグは1を値として与えます。
また、位置引数は Commands
というキーに対して、文字列を空白区切りのarrayとして与えます。JSONとして成立しない壊れたものも頑張って読み込もうと知るため、思っているのとは違うエラーが出ることにも注意が必要です。
-jdo
を使う方法
-jdo
を付けると、標準入力からJSONを受け付け、実行結果をJSONで表示します。
$ echo '{"h": "192.168.XX.YY", "u": "ユーザ名", "P": "パスワード", "C": 1, "Commands": ["enumerate", "bootconfig"]}' > enumerate_bootconfig.json $ cat enumerate_bootconfig.json | dashcli -jdo | jq { "@odata.id": "/DASH/v1/BootConfigurationCollection", "@odata.type": "#BootConfigurationCollection.v1_0_0.BootConfigurationCollection", "Id": "BootConfigurationCollection", "Members": [ { "@odata.id": "/DASH/v1/BootConfigurationCollection/BootConfiguration", "@odata.type": "#BootConfiguration.v1_0_0.BootConfiguration", "BootDevices": [ { "Name": "CIM:Windows Boot Manager:1", "Value": 0 }, { "Name": "CIM:UEFI: PXE IPV4 Realtek PCIe GBE Family Controller:1", "Value": 1 }, { "Name": "CIM:UEFI: PXE IPV6 Realtek PCIe GBE Family Controller:1", "Value": 2 } ], "ElementName": "Bootcfgsetting:0", "Id": "BootConfiguration", "InstanceId": "Bootcfgsetting:0", "InstanceNumber": 0, "IsCurrentConfiguration": "No", "IsDefaultConfiguration": "No", "IsNextConfiguration": "Yes", "Name": "Boot Configuration" } ], "Name": "Boot Config Collection" }
ちゃんとjqで読み込める、まともなJSONが返ってきます。最高かな?
-ji
と -jo
を使う方法
入力するJSONファイルと、出力するJSONファイルのパスをそれぞれ指定する方法です。
Dockerを使う関係上少し面倒になってしまいますが、以下の様な感じです。
# カレントディレクトリを/workdirとしてマウント # /workdirを実行時のカレントディレクトリにする # 実行ユーザのUID・GIDをDockerホストと統一 $ alias dashcli="docker run --rm -i -v $PWD:/workdir --workdir /workdir -u $(id -u):$(id -g) dashcli:3.0.0-285" $ dashcli -ji enumerate_bootconfig.json -jo result.json $ jq "." result.json # 省略
微妙なところ
結構よくできている印象のCLIですが、やはりメインはWindows向けなのかいくつか微妙な点があります。
変なエラーが出る
[2020/9/30 21:00 追記]
AMDのDASHサポートにメールしたところ、このエラーが出る事象は把握しており、動作には問題が無いとのこと。
置き換えることでテストケースがfailするようなので、少々邪魔くさいですが諦めてエラーが表示されることは許容しようと思います。
また、修正版をリリースする予定もあるとのことなので、それを待つことにします。
(libcurl.so.4を置き換えないようにDockerfileも修正しました。)
追記ここまで
Dockerfile内で勝手に潰しているのですが、単にインストールするだけでは以下の様なエラーメッセージを毎回吐きます。
$ dashcli version /usr/bin/dashcli: /usr/bin/libcurl.so.4: no version information available (required by /usr/bin/libwsman_curl_client_transport.so.1) DASH CLI v3.0.0.285
この /usr/bin/libcurl.so.4
はdashcli-setupのdebパッケージ内に含まれており、インストール時に勝手に配置されます*6。
自分でlibcurl3をインストールして、そのlibcurl.so.4へのシンボリックリンクに勝手に置き換えるとちゃんと動いている上にエラーメッセージも出ません。これについてはサポートに問い合わせているので、何か返事があれば追記します。
インストール後に実行されるスクリプトがsudoを使う
debパッケージを展開するとわかるのですが、インストール後に実行されるpostinstというスクリプト内でsudoが使われています。
そのため、Docker内で実行すると sudo をインストールしていないとコケます。
うーん、 どうせパッケージインストール時に権限が必要なので、rootでないユーザは sudo dpkg -i
するだろうからこれは不要なのでは……*7。
出力に毎回変な改行が混ざる
できるだけ出力はそのままコピペしているので分かると思うんですが、毎回変な改行が先頭に入っています。なんやねんこれ。
-ji
と -jo
を使うときですらなぜかそうなるので微妙な気持ちになります。あんまり実害はないのでまだ許せますが。
まとめ
Linuxで使えるDASH CLIと、それをDockerコンテナに閉じ込めて使う方法の紹介でした。軽くググった限りでは日本語の記事はこれが初出なのではないでしょうか。
今後の展望としてPXEブートできる環境を用意し、このCLIを使用して電源操作・ブート順操作の自動化をすることで、物理マシンに任意のOSをインストールしゼロからシステムをデプロイできる環境の作成を目指しています。
[2020/9/30 21:00 追記]
ブート順を変更できないっぽいため、どうするか少し悩んでいます。PXE→iPXE→HTTPでiPXEスクリプトを問い合わせというフローでブートできることを確認したので、必要な時以外は単にexitするスクリプトを配布すればその後SSDからブートするため、常にPXEブートを最上位に持ってきておくことで対応出来るのでは?と考えています。
追記ここまで
自宅で物理マシンを使ってImmutable InfrastructureとInfrastructure as Codeを実現できそうなのは結構面白いのではないでしょうか。できればあと数台M75q-1 Tinyが欲しいところです。収入が足りない😢