xcodeproj を CONFLICT しづらくする mergepbx

Xcode を使って開発していると、.xcodeproj が、論理的には CONFLICT していないのに Xcodeのバージョンアップ等で激しく CONFLICT することがある。 mergepbx を使うと、理不尽に CONFLICT されるケースをある程度解消してくれる。複数人で開発している時は入れておいて助かることが多い。

1年くらい使ってるけど、特に問題が起きたことはないです 🙂

Install

brew install mergepbx

Usage

以下の2ファイルに設定を追記(プロジェクト単位でもglobalでもOK)すれば、あとは git merge や git rebase とか通常の操作で mergepbx を使ってくれるようになります。

.gitconfig

下記を追加。

[merge "mergepbx"]
  name = Xcode project files merger
  driver = mergepbx %O %A %B

.gitattributes

下記を追加。

*.pbxproj merge=mergepbx
xcodeproj を CONFLICT しづらくする mergepbx

Awesome 某のスター付き一覧

Awesome Hoge のページをよく見ることがあり、特に iOS をよく見ていたのだが、スターの数をまとめて見たいと思い、主に自分用にスター付きの一覧を生成してみました。Awesome repositories の contributors と全てのレポジトリの developers の方々に感謝です。

URLはこれです。
Awesome repos with 🌟

GitHub API で取得した stargazers_count を使っています。 GitHub API、レポジトリの取得なら1時間に5,000回のLimitなので結構叩ける印象。

サーバは要らなそうだったので、AWSのサービスを組み合わせてサーバレスでページを生成しています。

  • Lambda でレポジトリ一覧を取得
  • まとめて GitHub API を叩くと Lambda がタイムアウトするので、一旦 SQS に JSON にしていれる。
  • CloudWatch Events で Lambda を定期的に起動して、 SQS のキューを受信しながら、 GitHub API を叩いて SimpleDB に結果を入れる
  • 定期的に、 SimpleDB のデータを Lambda で JSON にしてs3 に PUT する。

という風にしています。APIにも優しいように、1日かけて1ループするくらいの周期で処理をしています。

DynamoDB ではなく SimpleDB なのは、最後の処理で一括でデータを取得する必要があるのですが、DynamoDB はスループットの設定的に一括取得には向いていないので、課金的な意味で SimpleDB にしてます。無料枠を考えないと、ミニマムコストが SimpleDB はが$0で DynamoDB は$0.67なので。ただ、DynamoDBで読み書き1ずつにして、s3にPUTするときだけ scan しても、もしかしたら DynamoDB の方が安い、ということはあるかもしれません。この部分の比較は見たことがないというか、SimpleDBに関する情報をあまり見かけません。 Lambda からもデフォルトで使えるし、ちょっとした時に便利だと思うのですが、管理画面にも無いし、そのうちサービスが終わってしまうのではないかというのが心配です。

Lambda 開発環境は、最近知った node-lambda がシンプルで使いやすいです。手元での実行とデプロイが出来ます。

HTML側は、S3 + CloudFront です。最近 CloudFront が HTTP/2 に対応したので、個人的には静的ページの配信に HTTP/2 対応のサーバを立てる意味がほぼなくなりました。あと、React で描画してるので、対応してないブラウザでは見えないです。

JSON の URL はパブリックなので、直接叩いても良いです。見ればフォーマット何となくわかると思いますので、 jq とか使って絞り込めば、例えば 「Awesome iOSで、Buttonのカテゴリで、500スター以上付いているレポジトリを開く」とかもすぐ出来ます。

curl https://s3-ap-northeast-1.amazonaws.com/awesome-repositories/ios.json | gunzip | jq -r 'map(select(.category1 == "Button")) | map(select(.stargazers_count >= 500)) | "http://github.com/"+.[].path' | xargs open

※ 一気にブラウザ上でページが立ち上がるので、内容に注意して下さい!

最初 iOS だけ作ったら、他のものもほぼ同じコードで生成できたので、自分がよく使う言語だけですが、6個作りました。 Elixir 力を上げたい。

Awesome 某のスター付き一覧

グラフDB neo4j で経路検索

最近 NoSQL をいくつか使っていて、グラフDBの neo4j が面白かった。

グラフDBの特徴は、RDBでは難しい関連性の検索を得意としていることで、実際に経路検索やリコメンドエンジンなどで使われているようです。面白かったのは、RDBでは難しい、クレジットカードの詐欺検知などでも使えるということで、人間がロジックを作るのが苦手な部分が得意という面で、機械学習とかに通ずる楽しさがありそう。

初心者なので、用語の使い方がおかしかったり、クソコードだったりするかもしれませんが、その点はご容赦ください。。

試しに、電車の経路検索を作ってみます。

OSX で簡単に試せます。

準備

install

$ brew install neo4j

run

$ neo4j start

とやると background で立ち上がるのですが、何度やっても立ち上がらないことがあり、その場合は、

$ neo4j console

とやるとフォアグラウンドで、確実に立ち上がります。(謎)

reset

色々やっててデータを全消去したくなった時は、バージョンなどを読み替えて下記のコマンドで乱暴に消せます。 実行時は色々なことに注意してください!

$ neo4j stop
$ rm -rf /usr/local/Cellar/neo4j/3.0.4/libexec/data/databases/graph.db
$ neo4j start

ブラウザで見る

http://localhost:7474/

にアクセスすると、親切そうな画面が現れます。上のところにコマンド入れると色々実行できます。
↓のシェルは、; で区切って複数実行出来るのですが、ブラウザで見る場合は1回の実行で必ず1回しか実行できないので、同じ変数が2回現れたりするとエラーなので注意です。

シェルで見る

$ neo4j-shell

で立ち上がります。

ブラウザで見た時に、チュートリアルで俳優と映画と役のサンプルデータをロードして、試せるようになっているので、それをやるとなんとなく分かります。

簡単な概念の説明

neo4j は完全にスキーマレスです。INDEX を作成することは出来ますが、INDEX のスキーマも決めなくて大丈夫なようです。

  • ノード
  • リレーション

という登場人物がいます。言葉から連想される通りに、ノードが実際のモノを表していて、リレーションはノードとノードをつなぐ関連性を表します。どちらも、ラベル(プログラミングでいうところのクラスと同じと思ってOKそう)とプロパティ(これはそのままの意味)を持てます。

これは書籍からの受け売りですが、どちらも抽象的なので、RDBのように事前に設計することは難しく、データ構造は試行錯誤しながらトライアンドエラーで決めていくもののようです。

例えば、今やろうとしている路線検索に当てはめると、

  • ノード が 駅
  • リレーション が 線路

というパターンもありそうですが、普通、急行、特急でそれぞれ止まる駅が異なるので、「駅Aから駅Bに1つ進む」という情報をリレーションとして持つという方針にしてみます。

  • ノード が 駅(ラベルは Station) で、ここではプロパティとして 駅名(name)
  • リレーション が 経路(ラベルは Section)で、ここではプロパティとして 種別(kind), 所要時間(minutes) ※種別は、普通・急行・特急を表す

となります。

他のパターンとしては、経路を NormalSection (各停の経路) と ExpressSection (急行の経路) と分けたり、全駅間を Relation として持つなどもあると思います。とりあえず今回は、↑でやってみました。

経路検索をしてみる

この後は、空想の3路線をでっち上げて、路線検索を作ってみます。

データ定義

路線図

  • 赤路線、黄路線、青路線の3路線があります。
  • どの路線も必ず端から端までしか行かないとします。
  • 2つ以上の路線が交わる駅では乗り換えが出来ます。
  • 種別は、各駅停車と急行の2種類があります。
    • 各駅停車は、全部の駅に停車します。
    • 急行は、背景が黒い駅のみに止まります。
  • 線路に書いてある数字は、駅間の所要時間です。
    • 曲線で書いてあるところは、急行で移動した場合の所要時間です。

という具合です。

はじめてのノード

ブラウザでもシェルでも良いので、コマンドが打てる状態にして、下記を打ちます。

CREATE (station:Station {name: '海岸1'});

すると、

Added 1 label, created 1 node, set 1 properties, statement executed in 342 ms.

成功したっぽいメッセージが出たと思います。

その状態で

MATCH (n) RETURN n;

とすると、丸が1個だけ画面上に出てきたと思います。これがノードです。

はじめてのリレーション

リレーションは、ノード間の関係なので、ノードをもう1個作ります。

CREATE (station:Station {name: '海岸2'});

ノードを2個指定して、リレーションを作ります。

MATCH (from:Station { name: '海岸1' })
MATCH (dest:Station { name: '海岸2' })
CREATE (from)-[:Section{minutes:3, kind:'各停'}]->(dest);

MATCH で変数に束縛するのは、Elixir っぽいです。(最近使い始めた)

もう一度先ほどのコマンドを実行します。
この意味は、全てにマッチする n を返す。なので全部の Nodes が返ります。

MATCH (n) RETURN n;

こんな感じになりました。

node

MERGE

CREATE を使うと、同じものが何個も出来てしまうのですが、代わりに MERGE を使うと、同じ属性のものがあったらそれを上書きします。

MERGE (station:Station {name: '海岸1'});

とすると

(no changes, no rows)

となって、何も起きません。

なので、以後 MERGE で書きます。(但しこれが良い方法なのかは不明)

データ作成

諸々のデータを登録します。

サンプルデータ登録のクエリ

MATCH (n) RETURN n;

とすると、ごちゃっと出るのですが、ドラッグ&ドロップしてなんとなく並べると、先ほどの図のようになりました!
この後は、経路を検索してみます。

graph (9)

経路検索

とりあえず、2駅先までの経路情報を出してみます。

MATCH (from:Station {name: "海岸1"}), (to:Station {name: "海岸3"}), path=((from)-[:Section*1..2]->(to))
RETURN from, to, path

Sectionのところでは、1個から2個までのリレーションを挟む経路を指定しています。
海岸1から海岸3に行くには、各停で2個進むか、急行で1個進めば良いということがわかります。

graph (10)

次に、REDUCEを使って所要時間を算出してみます。

MATCH (from:Station {name: "海岸1"}), (to:Station {name: "山b"}), path=((from)-[section:Section*1..3]->(to))
RETURN from, to,path,
REDUCE(totalMinutes = 0, s in section | totalMinutes + s.minutes) as 所要時間
ORDER BY 所要時間
LIMIT 10

このような出力になりますが、何やらいっぱい出てます。

graph (11)

Rowsを見てみると、所要時間が表示されて所要時間順に並んでいることがわかります。

Neo4j
いい感じですね。

MATCH (from:Station {name: "海岸1"}), (to:Station {name: "山b"}), path=((from)-[section:Section*1..3]->(to))
WITH
EXTRACT(section in rels(path) | section.kind) as 経路,
EXTRACT(station in nodes(path) | station.name) as 乗り換え駅,
REDUCE(totalMinutes = 0, s in section | totalMinutes + s.minutes) as 所要時間
RETURN 所要時間, 乗り換え駅, 経路
ORDER BY 所要時間
LIMIT 10;

駅と区間をテキストで並べてみます。本当は 海岸1->急行->海岸3->各停->山b みたいに表示したが、まだそのやり方が分かっていない…。

Neo4j 2

最短経路検索

MATCH (from:Station {name: "海岸1"}), (to:Station {name: "大都会"}), path=allShortestPaths ((from)-[section:Section*]->(to))
WITH
EXTRACT(section in rels(path) | section.kind) as 経路,
EXTRACT(station in nodes(path) | station.name) as 乗り換え駅,
REDUCE(totalMinutes = 0, s in section | totalMinutes + s.minutes) as 所要時間,
path
RETURN 所要時間, 乗り換え駅, 経路, path
ORDER BY 所要時間
LIMIT 10;

allShortestPaths で、最短経路となったものの結果一覧が返ってきます。町側を経由していくのと、海→山と経由していく2つのルートが出ています。shortestPath にすると最短のものが返ってきます。

graph (8)

この例だと、町を経由していくほうが早いみたいです。

Neo4j 3

トラブル時の迂回経路の検索

山c -> 町E へ行くという経路を考えます。通常時は、山c -> 山d -> 町C -> 町D -> 町E と進むのが早いはずです。

今、町C山d の間で何らかのトラブルがあり、その区間の所要時間が120分になってしまいました!そのようなデータを作ります。

MATCH (from:Station {name: "山c"}),(to:Station {name: "町E"}), path=((from)-[section:Section*1..9]->(to))
WITH
EXTRACT(section in rels(path) | section.kind) as 経路,
EXTRACT(station in nodes(path) | station.name) as 乗り換え駅,
REDUCE(totalMinutes = 0, s in section | totalMinutes + s.minutes) as 所要時間
RETURN 所要時間, 乗り換え駅, 経路
ORDER BY 所要時間
LIMIT 10;

すると、下記のようなデータが返って来ます。

Neo4j 4

山d -> 町C という部分を通ってしまうと120分加算されてしまうので、海岸の方を迂回していく方が早いということが分かります。
一番早いのは、 海岸3 まで各停で行って、その後は急行を乗り継ぐパターンです。
二番目に早いのは、一旦 山d まで各停で行って、そこから急行に乗り 海岸1 を経由して行くパターンです。

遅延データをリアルタイムにDBに反映していけば、その時々で最短のルートを検索することが出来て便利そうです。

グラフDB neo4j で経路検索

Nginx で Let’s Encrypt 使う時の設定

Using Free SSL/TLS Certificates from Let’s Encrypt with NGINX

これに比べると Caddyを使う場合 が簡単すぎて泣ける。

追記

と思っていたが、 Let’s Encrypt は Certbot でものすごく簡単に設定できるようになった。

公式を見たら手順が分かりやすい。

インストール

wget https://dl.eff.org/certbot-auto
chmod a+x certbot-auto
./certbot-auto

とすると、必要なものが全部インストールされる。

証明書の発行

Nginx の場合、

./path/to/certbot-auto certonly

で、聞かれることに答えていくと、自動的に認証されて証明書が発行される。
この時、証明書を取得するドメインの80番ポートが空いていることが必要で、 webroot を指定する必要があるので事前に調べておくと良い。

もしくは、Nginxなどを使わない場合、スタンドアロンで80番ポートをリッスンするHTTPサーバになって認証をしてくれる模様。

./path/to/certbot-auto certonly --standalone -d example.com -d www.example.com

Let’s Encrypt は認証用の情報を、証明書取得予定のドメインの80番ポートの特定URLにおいて、Let’s Encrypt本家からそのURLにアクセスしにきて、認証OKとなったら証明書を発行してくれる仕組みのよう。

Nginx の設定

例えばこの ecpplus.net ドメインの場合、デフォルトでは下記の場所に証明書が出来る。(発行時に標準出力されてるメッセージを見れば書いてある)

server {
        listen 443 ssl http2;
        server_name ecpplus.net;

        ssl on;
        ssl_certificate     /etc/letsencrypt/live/ecpplus.net/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/ecpplus.net/privkey.pem;
...

cron の設定

一応90日の有効期限があるが、注意書きに1日2回の更新で、分をランダムにするように。というのがおすすめされてるので、そのように設定する。更新の必要がないときは、何もしないって書いてあった。

21 */12 * * * /usr/local/bin/certbot-auto renew --quiet --no-self-upgrade
22 */12 * * * /etc/init.d/nginx reload
Nginx で Let’s Encrypt 使う時の設定

AWS API Gateway を使ってサーバレスな ifconfig API を作ってみる

サーバが自分自身のグローバルIPアドレスを調べるとき面倒なことがよくあるので、API Gateway を使って ifconfig のようなことをする。単純に、リクエストしてきたIPアドレスを返すだけ。(こういうサービスは他にもあるけど、レスポンスが遅めだったり、信用できるか分からなかったりするので、自分で作るのが安全そう)

curl https://ip.ecp.plus

としたら自分のIPアドレスが返るというのがゴール。ブラウザで見ても同じ。ちなみに↑はもう動いているのでご自由にお使いください。

最初、API Gateway -> Lambda として、Lambda に IPアドレスを渡して、Lambda はそれを返して、API Gateway はレスポンスをそのままスルーする。というのをやったが、実は Lambda なしで出来たようだ。

はじめ Lambda を使ったバージョンをやったので、両方書く。

API Gateway の設定

Create Method

URLは短くしたいので、ドメイン直下をGETしたときに返したい。 / に対して Create Method で GET を指定する。

Method Request は認証などしないのでそのまま。

/ – GET – Method Execution

API Gateway にアクセスしてきた IP アドレスは、下記のようにして取れる。

{
"ipaddress" : "$context.identity.sourceIp"
}

のだが、API Gateway のデフォルトで割り振られる URL は非常に長く、自分のドメインで使いたい。Custom Domain Names を使うためには、TLS の証明書が必要なのだが、お手軽さが無いのでやめた。既に動いている Caddy でリクエストを転送することにした。

ただ、そうするとリクエスト元のIPアドレスが Caddy が動いているサーバの IPアドレスになってしまうため、ヘッダーの X-Forwarded-For を見ることにする。ここの先頭に、オリジナルのIPアドレスが入ってる。

{
"ipaddress" : "$input.params().header.get('X-Forwarded-For')"
}

Lambda

ipaddress として渡ってきたデータの中から、一番最初のIPアドレスを返すだけの処理を書く。

exports.handler = function(event, context) {
context.succeed([event.ipaddress.split(/\s*,\s*/)[0], "\n"].join(''));
};

/ – GET – Integration Response

再び API Gateway に戻ってきて、レスポンスの加工をする。一見、Output passthrough で良さそうだが、これだとダブルクオーテーションで囲まれたものが返ってしまう。

Content-Type: application/json で Mapping Template として下記のようにする。

$input.path('$')

これでAWS側の実装は完了。

Caddy の設定

ip.ecp.plus に来たものを API Gateway に飛ばす。API Gateway 側で GET / を叩くために、転送先の末尾にスラッシュを忘れず付ける必要がある。

ip.ecp.plus {
proxy / https://EXAMPLE.execute-api.ap-northeast-1.amazonaws.com/prod/
}

とりあえずここまでで完成。

だが、Lambda がどう見てもあまり意味がなさそうだったので、API Gateway だけで出来ないか見ていたところ、開発中に使う用途と思っていた Mock Integration を使えば出来た。

API Gateway の設定(再)

/ – GET – Method Execution

Integration type として Mock Integration を選択。Mapping template として

{
"statusCode" : 200
}

のようなものが定義されてるが、これがないと勝手にエラーにされてしまうのでそのままにしておく。

/ – GET – Integration Response

どうやら、Response 側でも、リクエストで渡ってきた値がそのまま取れるようなので、こちらにIPアドレスを返す処理を書けば良い。

#set($ipAddresses = $input.params().header.get('X-Forwarded-For').split(','))
$ipAddresses[0]
#set($dummy = "dummy")

最後の行でよくわからないことをしているが、レスポンスの末尾に改行を入れたいためこうしている。”\n” をどうにか入れたかったが、うまく行かずバッドノウハウ的な解決方法になっている…。本当は良い方法があるはずだ。

ここまでで完成。

尤も、こんな単純なものなら Caddy 動かしてるサーバで処理しろよという感じだけれど、最近 API Gateway にハマっているので作ってみた。API Gateway 単体で動かないと、Caddy のサーバが落ちたときに使えなくて激しく意味がないが、Custom Domain Names の設定までやるのは面倒であった。Route53 使ってたらシームレスに連携出来たりすれば良いのに、と思った。

(追記)CloudFront を使ってサーバレスに出来た

その後、Caddy の代わりに CloudFront を使えば、TLS証明書も無料で、好きなドメインで出来ることが分かった。

CloudFront の画面から、Create Distribution -> Web の Get Started で、作成画面へ入る。

Origin Domain Name は、API Gateway の Invoke URL を指定する。https:// を付けても勝手に外してくれるので、URLコピペで良い。stage名は消す。つまりドメイン部分だけを記述する。
Origin Path は、stage名を入れる。 prod なら /prod とする。

あとは項目を読みながら設定すれば良い。CloudFront は独自ドメインのTLS証明書が無料なので、とてもありがたい。注意点としては、 CloudFront にキャッシュされると前に実行した人のIPアドレスが返ってしまうので、キャッシュさせないようにしなければいけない。
Behaviors のタブで、初期から存在しているものを編集して、Object Caching : Customizeとして、Minimum TTL : 0, Maximum TTL : 0, Default TTL : 0 とすれば良い。

これで、自己管理するインフラ無しで、当初のやりたかったことが出来た。

API Gateway から Export した Swagger 用の YAML

https://gist.github.com/ecpplus/1373c676f602326955f8

AWS API Gateway を使ってサーバレスな ifconfig API を作ってみる

HamlでTextarea(Rails)

Rails + Haml で textarea を出力しようとすると、textarea 内に表示しようとしたテキストまでインデントされてしまう。

find_and_preserve というヘルパーメソッドを使えば、中身のインデントは保持されるようだ。

= find_and_preserve(f.text_area)

Simple Form を使ってる場合も、同様にいける。

= find_and_preserve(f.input :comment, as: :text)

参考URL: How do I stop Haml from indenting the contents of my pre and textarea tags?

HamlでTextarea(Rails)

MacでIEの確認をリモートで行う

IEの確認で、Azure の RemoteIE というのが知らないうちに出てた。これだと、手元に virtual machines のイメージを置いておかなくて良いので、ディスク容量の節約になる。

登録すれば無料で使えるようだが、inactive な時間が10分続くか、 active な状態でも60分経過すると切断される制限がある。短時間の確認なら問題無さそうだが、長時間の確認は繋ぎ直す必要がありそう。

まずは、RemoteIE で Register for access して、自分の Region を選ぶ。日本なら East Asia が良い。

Mac で使う場合は、Microsoft Remote Desktop アプリを App Store からダウンロードして、Azure RemoteApp というところをクリックすると、ログイン画面になるので、ログインする。
すると、InternetExplorer(email: iewebeco@microsoft.com) という Invitations があるので、チェックを入れると自動的に起動する。いきなり IE の画面が開いているので検証できる。

MacでIEの確認をリモートで行う