Visual Studio CodeからMacの辞書(Dictionary.app)を開くエクステンションをつくった
SSHラッパーコマンドEsshのv1.0.0をリリースしました
Esshは、zsh補完やLuaスクリプトによる動的なコンフィグレーションなど、便利機能を実装した、sshのラッパーコマンドです。
ドキュメントをまとめたWebサイトも作成しました。
上記Webサイトのトップにasciinemaで撮ったターミナルオペレーションのデモを乗せましたので、そちらを見ていただければ、どういったものか大体のことはわかると思います。
開発の経緯
2015/11に以下の記事を書き、Zsshというコマンドを作成しました。
その後、CentOSのリポジトリにzssh
というパッケージが存在していたため、コマンド名をzssh
からessh
に変え、自分の利用にあわせて、適宜、機能の追加や変更を続けていました。そして、自分が使う上で必要十分な機能がそろって、大きな変更もなくなってきたので、この度これをv1.0.0
としてリリースすることにしました。なお2015/11時点のものとは仕様が大きく様変わりしており、以下に改めて、機能について説明します。
機能
EsshはGoで書かれたシングルバイナリのCLIツールで多機能sshコマンドとして利用できます。実装的には内部でssh
コマンドを実行するラッパーコマンドになっています。標準のsshコマンドでは使えない以下のような機能が利用できます。
- Luaによる設定ファイル。これによりSSH接続サーバの設定(ssh_config)をより動的に構築することができる。
- サーバ接続、切断時に発火するフックコマンドを設定できる。
- サーバにタグをつけられ、一覧をターミナルに出力できる。
- ZshとBash用の強力な補完を組み込みで提供。
- カレントディレクトリごとに設定ファイルの切り替え。
- Capistranoのような複数サーバに並行してコマンドを実行できるタスクランナー。
- 設定用のLuaコードはモジュールとしてGitリポジトリ上で共有できる。
一通りの機能に触れるためのチュートリアルを用意したので、利用の際はここから始めるとよいかと思います。
https://essh.sitespread.net/intro/ja/index.html
利用例
上記のチュートリアルには書いていないが、私個人が使っている機能などを中心にいくつか紹介します。
ログイン時にターミナルの色を変える
接続、切断時のフックコマンドhooks_before_connect
とhooks_after_disconnect
でターミナルの色を変えます。私は本番環境につなぐときは赤にしています。
-- ~/.essh/config.lua -- 事前にmacのターミナル設定でRedとBlackというプロファイルを作っておく local red_screen_command = "osascript -e 'tell application \"Terminal\" to set current settings of first window to settings set \"Red\"'" local black_screen_command = "osascript -e 'tell application \"Terminal\" to set current settings of first window to settings set \"Black\"'" host "webserver-01" { HostName = "192.168.56.32", Port = "22", User = "kohkimakimoto", description = "web server-01", hooks_before_connect = { red_screen_command }, hooks_after_disconnect = { black_screen_command }, tags = { "web", }, } host "webserver-02" { HostName = "192.168.56.33", Port = "22", User = "kohkimakimoto", description = "web server-02", hooks_before_connect = { red_screen_command }, hooks_after_disconnect = { black_screen_command }, tags = { "web", }, }
アプリケーションのデプロイ
リモートサーバにコマンドを実行します。以下はただのサンプルです。実環境ではもっと複雑なスクリプトを実行させています。
host "webserver-01" { HostName = "192.168.56.32", Port = "22", User = "kohkimakimoto", description = "web server-01", tags = { "web", }, } host "webserver-02" { HostName = "192.168.56.33", Port = "22", User = "kohkimakimoto", description = "web server-02", tags = { "web", }, } task "deploy" { backend = "remote", targets = {"web"}, prefix = true, parallel = true, script = [=[ echo "deploy app to $ESSH_HOSTNAME" # your deploy commands... echo "Done." ]=], }
以上です。日々のSSHライフのお供に、どうぞお使いください。m( )m
ReactNativeのiOSアプリをオフラインの実機で動かす
※2017-04-10現在のメモ。
先に結論。以下の#if !(TARGET_IPHONE_SIMULATOR)
で囲った部分のコードをios/AppName/AppDelegate.m
に仕込むといいです。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSURL *jsCodeLocation; #if !(TARGET_IPHONE_SIMULATOR) jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; #else jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil]; #endif RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation moduleName:@"AppName" initialProperties:nil launchOptions:launchOptions]; rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1]; self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; UIViewController *rootViewController = [UIViewController new]; rootViewController.view = rootView; self.window.rootViewController = rootViewController; [self.window makeKeyAndVisible]; return YES; }
経緯
ReactNativeでiOSアプリを作ってみてます。SwiftやObjective-Cで作ったアプリのように、開発中のアプリをUSBケーブルにつないだiPhoneにインストールして、動作確認してみようとしたら、うまくアプリが立ち上がらない。ドキュメントやGithubのissuesをみるとiPhoneが同じwifiにいないと動作しないらしいです。
「react native offline」とかで検索するとstackoverflowやgithubのissuesで、対処法が書いてあるが、今ひとつうまくいかない。というか、ReactNativeの更新が速いせいか、いまでは通用しない古い情報がまじっていてよくわからないです。で、Objective-Cは以前触ったこともあってソースが読めるので、直接ソースを追ってみました。それでわかったこととして
- 開発中ReactNativeは開発マシン上にhttpサーバを起動し、アプリはこのhttpサーバからJavaScriptコードを取得する。
- この仕組みのおかげで、開発中再ビルドなしで、変更を反映させられる。実機の場合も同様だが、オフラインだとhttpサーバに到達できず、エラーになる。
- ビルド時、リリースビルド用にJavaScriptを一つのファイルにまとめてアプリにバンドルするスクリプトが、XCodeのrun scriptで定義されている。
- リリースビルドの場合のみ、httpサーバからではなくファイルからJavaScriptを読むようにマクロで制御されている。
というわけで冒頭に記載したようにios/AppName/AppDelegate.m
内のコードを
#if !(TARGET_IPHONE_SIMULATOR) jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; #endif
のようにして「シミュレータ以外のとき」という条件で、JavaScriptのロケーションを設定している変数を、リリースビルドのときと同様に、バンドルされたJavaScriptファイルを読みに行くよう上書きすればOKです。この場合当然、実機上で動かすと、再ビルドなしでコードの変更を反映させることはできなくなるので、それが必要なときは都度この部分を削除やコメントアウトするなりして、対応すればいいかな、と。今のところは。
OmnibusでPHPアプリケーションをRPMにパッケージングする
要約: Omnibusを使うとApacheとPHPをバンドルしたオールインワンなPHPアプリケーションのRPMパッケージを作れます。
最近CLIツールやAPIサーバなどはGoで書くようになって、PHPをさわる頻度が減ってきているのですが、それでもPHPの実行環境が不必要になることはなく、その個人的な一つの理由にAdminerがあります。これは2012年に一度ブログにも書いたのですが、PHPのシングルファイルをDocumentRootにおくだけで動作する、データベース用のWebUIです。
Adminer - PHPの1ファイルを置くだけで簡単に使えるDB管理ツール - オープンソースこねこね
現在でもメンテは続いているようで、今ではElasticsearchやMongoDBなどにも対応しているそうな。個人的にもずっと使い続けています。
ただ、前述のようにGoの適用範囲が広がってきたので、新しく立ち上げたサーバとかだと、Adminerを使う以外にApacheやPHPが必要ない環境もあります。後々の運用管理のことも考えてサーバ構築にはプロビジョニングツールを使っていることもあり、これ一つのために、ApacheとPHPを入れてhttpd.confとphp.iniを書き換えるレシピ書いて。。。てなことがやや面倒くさいです。
Adminerの機能単体でぱっとインストール、アンインストールできればいいかもしれない、と考えたので、omnibusを使ってApacheとPHPをバンドルしたオールインワンなPHPアプリケーションのRPMを作成してみました。omnibusについては以前ブログに書いたのでそちらを参照してください(CentOS用にSupervisor3.3.0のRPMパッケージを作成した - オープンソースこねこね )。今回の成果物は以下
$ sudo yum install https://github.com/kohkimakimoto/omnibus-adminer-server/releases/download/v0.3.0/adminer-server-0.3.0-1.el7.x86_64.rpm
でインストールできます。これだけで/opt/adminer-server
にApacheとPHPランタイムを含めたアプリケーションがインストールされます。これらはもちろんシステムワイドにあるApacheやPHPとは干渉しません。あとは、
$ sudo systemctl start adminer-server
とすればApacheが14200ポートで起動しますのでブラウザでhttp://localhost:14200/adminer.php
にアクセスすると
と画面がでます。
補足
- サービス設定は現状systemdのみの対応なので、このRPMはCentOS7限定です。initスクリプトを書けばCentOS6などにも対応できますが、自分が使わないので書いてません。
- 設定ファイルは
/etc/adminer-server
、adminer.phpなどのPHPファイルは/var/lib/adminer-server
以下に配置してあります。 - 今回はやってないがMySQLなどDBもバンドルすれば、いわゆるLAMPアプリケーションがパッケージ配布可能になるんじゃないかと。
あとがき
こんなことしなくても、今はDocker使えばいいかも、とも思いますが、Systemdにサービスを登録するところまでyum installl
でできるのでミドルウェア的なものを扱うのなら、RPMも便利ですよと。。。
テストやビルドスクリプトをDockerコンテナで実行させるための便利ツールを作った
ある程度複雑なプログラムだと、ローカル環境やCIサーバ環境など、どこでも動くようにテストを書くのが難しくなったりします。また、昨今のWebアプリケーションだと、デプロイ前に何らかのビルドプロセスが必要になることがほとんどで、依存物をインストールしたり、Webpackを動かしたり、Goのビルドを走らせたりします。こういうタスクもnodeやらGoに依存していて、複数の環境で差異なく動かすことを考えると、いろいろツライものがあります。
そこで、こういったテストやビルド処理はシェルスクリプトを書いて、Dockerコンテナ内で動かすことによって環境に対しての依存を解消していました。
たとえばGo言語の場合以下のようなテスト実行スクリプトtest_run.sh
を書いておきます。
#!/usr/bin/env bash set -eu go test $GOTEST_FLAGS $(go list ./... | grep -v vendor)
そしてこれをDockerコンテナで実行するためのスクリプトとして以下のようなtest.sh
を書きます。
#!/usr/bin/env bash set -eu DOCKER_IMAGE=${DOCKER_IMAGE:-'kohkimakimoto/golang:centos7'} GOTEST_FLAGS=${GOTEST_FLAGS:--cover -timeout=360s} repo_dir=$(cd $(dirname $0); pwd) docker run \ --env GOTEST_FLAGS="${GOTEST_FLAGS}" \ -v $repo_dir:/build/src/github.com/username/repo \ -w /build/src/github.com/username/repo \ --rm \ ${DOCKER_IMAGE} \ bash ./test_run.sh
goコマンドなど、テストに必要な環境は全てDockerイメージとして用意しておきます。あとはリポジトリのルートにいる状態で./test.sh
を実行すれば、テストが実行されます。TravisCIのようなCIサーバはDockerに対応しているので、同様に./test.sh
を実行するだけで、ローカル環境と同じようにテストが実行できます。テストコード側で環境の違いを意識する必要はなくなります。
ところで、しばらくこの方式でやっていたところ、以下のような課題が浮き彫りになってきました。
- 新しいプロジェクトごとにシェルスクリプトをコピペ、修正して使いまわしている。
- これらのスクリプトはだいたい似たようなコードになりがちだが、利用するDockerイメージや
docker run
のオプションなどが微妙にちがう。 - テストやビルドなど個々のタスクごとに「実際のタスクを実行するスクリプト」と「それをDockerコンテナで実行するスクリプト」の2つのファイルを作る必要があり、数が増えるとファイルがごちゃごちゃしてきた。
ベタに書いたシェルスクリプトによる実装なので、プロジェクトが増えると、共通化などができずコードが冗長になりがちでした。そこで、大雑把に処理を整理してみると
などが共通な処理であったので、これらをまとめて、いい感じにDockerコンテナを起動してその中でスクリプトを実行するコマンドラインツールを作りました。
Goで実装したシングルバイナリなので、Githubのリリースページからバイナリをダウンロードして、パスの通ったディレクトリに配置すればすぐに使えます。
使い方
まずは単純に
$ buildsh
を実行してみます。これだけで、デフォルトでDockerイメージkohkimakimoto/buildsh:latest
(2Gくらいあります)をダウンロードして、カレントディレクトリをコンテナ内の/build
にマウントした状態でコンテナを起動します。そのままbashでログインした状態になるので、たとえばPHPのテストだったら
$ php phpunit
などを実行すればテストができます。PHPやPython,nodeなど主なLLのランタイムを入れてあるので、ホストマシンの環境に関係なくすぐにテストやビルドができます。作業が終わったら
$ exit
すれば、ホストマシンにもどってコンテナが破棄されます。特定の環境でちょっとしたことを動作確認したい場合などに便利に使えます。
そしてテストやビルドのシェルスクリプトを実行させたい場合は
$ buildsh test.sh
のようにスクリプトファイルを指定して実行すれば、そのスクリプトがDockerコンテナ内で実行されます。これで用意するスクリプトはテストを実行する部分のみでよくなり、「それをDockerコンテナで実行するスクリプト」を書く必要がなくなりました。
設定ファイル
使用するDockerイメージを変更したい場合などは、設定ファイルを利用することができます。.buildsh.yml
をカレントディレクトリに配置してください。以下のような設定ができます。
use_cache: true docker_image: kohkimakimoto/buildsh:latest additional_docker_options: --net=host -v=/var/run/docker.sock:/var/run/docker.sock environment: FOO: bar FOO2: bar2 home_in_container: /build/src/github.com/kohkimakimoto/buildsh
詳細はREADME.mdを参照していただくとして、いくつかピックアップして概要を説明します。
use_cache
use_cache
はtrueにするとカレントディレクトリ配下に.buildsh/cache
ディレクトリを作り、コンテナ内でパスを環境変数BUILDSH_CACHEDIR
に設定します。これはスクリプト実行ごとに破棄されてしまうコンテナ内のデータを保存するときに使うことができます。たとえばyarn install
を以下のようにすれば、キャッシュを保持できて、次回以降の処理の高速化が望めます。
$ yarn install --cache-folder=$BUILDSH_CACHEDIR/yarn
.gitignore
に.buildsh
を追加するのを忘れずに。。。
docker_image
docker_image
は利用するDockerイメージです。自分の用途に合わせて好きなイメージを使えます。
additional_docker_options
additional_docker_options
は内部で実行しているdocker run
に付け加えるオプションを指定できます。例のように-v=/var/run/docker.sock:/var/run/docker.sock
を利用すればDockerコンテナ内から新しくDockerコンテナを立ち上げることもできたりします。
home_in_container
home_in_container
はコンテナ内でホストのカレントディレクトリがマウントされるパスを変更できます。デフォルトは/build
ですが、例えばGoのテストやビルドをおこなうとき、GOPATHの関係上/build/src/github.com/kohkimakimoto/buildsh
のようにGOの流儀にそったパスに配置したいことがあります。この設定を使うことでそのようなケースに対応できます。
実装について
このツールはつまるところ、テストやビルド目的の使い捨てコンテナである場合docker run
にセットするオプションや引数がおおむね共通化できるので、それをまとめたラッパーコマンドというわけです。
もともとはbashのスクリプトで書いていたこともあって、実装は単純なdocker run
のラッパーコマンドになっているので、メインのコードはbuildsh.goのみです。なのでをこれを見れば何をやっているのか大体わかりますので、細かいことはソースを見たほうが早いかもしれません。
mattn/memoのzsh補完
Golang実装のメモコマンド
Big Sky :: golang でメモ専用コマンド「memo」作った。
GitHub - mattn/memo: 📓 Memo Life For You
が便利だったので、雑にzsh補完を書きました。
_memo_options() { local -a __memo_options __memo_options=( '--help:show help' '-h:show help' '--version:print the version' '-v:print the version' ) _describe -t option "option" __memo_options } _memo_sub_commands() { local -a __memo_sub_commands __memo_sub_commands=( 'new:create memo' 'n:create memo' 'list:list memo' 'l:list memo' 'edit:edit memo' 'e:edit memo' 'delete:delete memo' 'd:delete memo' 'grep:grep memo' 'g:grep memo' 'config:configure' 'c:configure' 'serve:start http server' 's:start http server' 'help:Shows a list of commands or help for one command' 'h:Shows a list of commands or help for one command' ) _describe -t command "command" __memo_sub_commands } _memo_list() { local -a __memo_list PRE_IFS=$IFS IFS=$'\n' __memo_list=($(memo list)) IFS=$PRE_IFS _describe -t memo "memo" __memo_list } _memo () { local state line _arguments \ '1: :->objects' \ '*: :->args' \ && ret=0 case $state in objects) case $line[1] in -*) _memo_options ;; *) _memo_sub_commands ;; esac ;; args) last_arg="${line[${#line[@]}-1]}" case $last_arg in edit|e|delete|d) _memo_list ;; *) ;; esac ;; *) _files ;; esac } compdef _memo memo
~/.zshrc
とかにコピペして使ってください。
※2017-02-10 追記: 本家リポジトリに取り込んでもらいました。https://github.com/mattn/memo/blob/master/misc/completion.zsh
systemd-nspawnで/sys/fs/selinuxがマウントできないので警告される件は無視することにした
ただのメモです。
CentOS7でsystemd-nspawn
の動作を検証してるなか、SELinuxを無効にした状態でコンテナを起動させると/sys/fs/selinux
がマウントできないと警告がでてきました。
# systemd-nspawn -D /var/tmp/mycontainer Spawning container mycontainer on /var/tmp/mycontainer. Press ^] three times within 1s to kill container. Failed to create directory /var/tmp/mycontainer//sys/fs/selinux: No such file or directory Failed to create directory /var/tmp/mycontainer//sys/fs/selinux: No such file or directory -bash-4.2#
/sys/fs/selinux
はSELinuxを無効にすると存在しないディレクトリ。調べたらsystemd-nspawnはHAVE_SELINUX
マクロでこのディレクトリのマウントを制御しています
systemd/nspawn.c at v219 · systemd/systemd · GitHub
そしてログを出力しているところはココ
systemd/nspawn.c at v219 · systemd/systemd · GitHub
ソースを見る限りその後の処理は続行しているし、問題なさそうかな〜と考えていたら、
で、同様の問題の当たっていたヒトがこの警告を消すためにログレベルをLOG_DEBUG
からLOG_WARNING
にさげるPRを送っていてmasterにマージされていました。なので、今後CentOS(systemd)のバージョンが上がれば、この警告は消えるので、現状無視することにした、というお話でした。