君たちは永遠にそいつらより若い

技術と音楽と日々のこと。

GitHubのリポジトリをissueも含めてバックアップできるBackHub

What?

f:id:kuteken:20160317213910p:plain

  • BackHub というドイツ発のサービス
  • GitHubリポジトリをissueも含めてバックアップできる
  • バックアップしたものをダウンロードしてローカルに保存可能
  • リポジトリだけでなく、Issuesもまるごとリストア可能(ちょっと形式が変わるけど)
  • パブリックリポジトリをバックアップする場合は、無料
  • プライベートリポジトリをバックアップする場合は、有料

backhub.co

サービスに必要な権限

  • Githubからログインする
  • 以下、権限が必要
    • Read org and team membership
    • Full control of private repositories
    • Access user email addresses (read-only)
  • 大事な情報を持ってる場合、普通に恐ろしいから安易に試せない。
  • 大したもの入ってなかったから試した次第(一応、サービス評価とか、運営会社の存在や付加価値税登録番号とか調べたけど)

f:id:kuteken:20160317213851p:plain

使い方

  • ログインしたら、リポジトリリストが並ぶので、バックアップボタンでバックアップする
  • そのまま、リポジトリごとにダウンロードできる
    • metadata.zip のファイル名にリポジトリ名が入ってなくて、かぶるからムカつく
    • metadataの中身は見た感じ、GitHubAPIのjsonをそのまま突っ込んだように見える

f:id:kuteken:20160317213911p:plain

  • リストアするときは別名でリポジトリをつくる。作成時にはPublic/Privateを選べる

f:id:kuteken:20160317213935p:plain

  • リストアされたissueは、微妙に形式が変わる

    • issueのtagはちゃんと残っていた
    • 元データのjsonにはassign情報が入っているのに、assignは外れてた
  • Before f:id:kuteken:20160317220110p:plain

  • After f:id:kuteken:20160317220125p:plain

これを使いたい人ってリポジトリを休眠させるときに、リポジトリだけじゃなくて、コメントとかissueを残したいからだと思うんだけど、リストアしなおすと形式が変わったり、情報が落ちたりするので、頻繁にバックアップしてリストアしてお金を節約〜という利用イメージはあまりつかなかった。これなら、Githubをダウングレードする前に一度ローカルに落としてから、潔くLockして眠らせておくほうがいいかな。

GitHubの課金を止めたら(ダウングレードしたら)プライベートリポジトリはどうなるか

お金の面でも、公開状態の面でもGitHubさん超優しかった。公開になるのかなーと思って、プライベートリポジトリをBuckup取って 消したけど、消さなくても良かったorz まぁ、新しいサービス試せたのでいいです!!

Q.

  • GitHubの課金を止めたら(ダウングレードしたら)プライベートリポジトリはどうなるか

A.

f:id:kuteken:20160317213203p:plain

  • 公開状態

    • 無理やりPublicにされることはない
    • Privateのまま、Lockされて中身が見れなくなる。
    • もちろんPush/Pullもできなくなる
  • 復活or中身を見る方法

    • 再課金する
    • Publicにしちゃう
  • Githubの優しさ

    • 月途中だったらお金Refundしてくれる

補足: 各ページのメッセージ表示

こんな感じのメッセージが表示される。

  • Billing Overview
@XXX’s private repositories are locked because they have exceeded the account’s plan limit. To unlock, upgrade your plan or make repositories public.
  • Repository Page
This repository has been disabled.
Your repository is currently disabled due to a billing issue. Please see your billing page for more information.

Google said 'If users can’t spell, it’s our problem.'

先週末、 Tuyên に誘われて、日曜にSAIGON TECH STARTUP FESTに寄ってきた。カンファレンス3個くらい聴いて、いつもどおり MY SQUAR の Nicolas に声掛けて、すぐ帰った。どのTECHイベントに行っても、本当にいつでもどこでも Nicolas がいる。

www.getlinks.co

今回の登壇者は、ベトナミーズイングリッシュではなかったので、聞き取りやすくて、寝ずに済んだ。

Google said 'If users can’t spell, it’s our problem.'

さて本題。Google の Head of Marketing Vietnam の Anh Nguyễn さんのお話の中の Google の信念が心に響いたので、メモ。

If users can’t spell, it’s our problem.
If they don’t know how to form the query, it’s our problem.
Even if there’s not enough content, it’s our problem.
If content is there but in a different language they don’t speak, it’s our problem.
Even if the web is too slow, it’s our problem.

もし、ユーザが言葉を正しく綴れないなら、それは僕たちの問題だ。
もし、ユーザがクエリでの検索がうまくできないなら、それは僕たちの問題だ。
もし、検索した先のページにユーザの探したい情報がない場合でも、それは僕たちの問題だ。
やっと見つけた情報が、ユーザの読めない言葉であっても、それは僕たちの問題だ。
たとえ、WEBがとても遅かったとしても、それは僕たちの問題なんだ。
※日本語は僕の超意訳

2009年くらいから色々なところで話されてるから、パターンも色々あるみたい。もし、それがユーザが「問題として認識していない問題」であっても、僕たちにとってはそれは解決すべき問題である。表面的な解決ではなく、潜在的な問題をも救い上げる、好きな考え方だ。

ユーザが「問題として認識していない問題」

情報検索時には、ユーザが「問題として認識していない問題」を抱えることがある。これについては、R. S. Taylor による情報要求のレベルに当てはめると、その段階を把握しやすくなる。

R. S. Taylor による情報要求のレベル

  • 直感的要求 (visceral need)
    • 現状に満足していないことは認識しているが、それを具体的に言語化してうまく説明できない状態
  • 意識された要求 (conscious need)
    • 頭の中では問題を整理できるが、あいまいな表現やまとまりのない表現でしか言語化できない状態
  • 形式化された要求 (formalized need)
    • 問題を具体的な言語表現で言語化できる状態
  • 調整済みの要求 (compromised need)
    • 問題を解決するために必要な情報の情報源が同定できるくらい問題が具体化された状態

※以下書籍から引用

情報検索と言語処理 (言語と計算)

情報検索と言語処理 (言語と計算)

上に引用した R. S. Taylor の情報要求(information need)のレベルにあるように、Googleでクエリを入力して検索できる状態のユーザは、形式化された要求 (formalized need)若しくは調整済みの要求 (compromised need)を持っている状態と言える。しかし、クエリを入力して検索できないユーザは、意識された要求 (conscious need)や直感的要求 (visceral need)のレベルにいる。直感的要求 (visceral need)のレベルのユーザは、問題を問題として認識することさえできていない。しかし、Googleは彼らを救いたいと願う。

有名な「世界中の情報を体系化し、ユーザに適切に届ける」と言うフレーズで切り落とされてしまった心の部分を、感覚的にわかりやすい言葉に紡いだものが、件の言葉であると、僕は解釈している。この言葉がスクリーンに表示された時、会場では憧れのような眼差しと共に、いたるところでシャッターが切られた。僕は、こういった人の心が動く瞬間に立ち会うとき、いつも感動を覚える。


ちなみに、こちらのイベントの主催者は、 GetLinks というアジア向けの求人サイトだった。CyberAgent Ventures や 500 Startupsから出資を受けてるバンコクのスタートアップ。

www.getlinks.co

(アジア向けのサービスのくせに、なんでサラリー表示に通貨単位をつけないんだろうね…バーツなのか、VNドンなのか、USドルなのかわかんなかった…

はい、今日も僕は元気です。

現地の人に現地の人と間違われる話。

書かなすぎてだめだからどうでもいい話書こう。最近、かなりの確率でベトナム人にベトナム人と間違われるようになった。

バイクに乗ってたら道を聞かれ、「わかんねー(ベトナム語)」って言ってんのにひたすらベトナム語で話され、外国人慣れしている商売人にも素で間違われ、終いには普通に日本語で日本人と話してても「日本語お上手ですね!」と言われる始末。上手いよ、そりゃネイティブだからな!!!!!!!!!ベトナム人に韓国人に間違われるのは分かるけど、現地の人に現地の人と間違われたり、自国の人としゃべってるのに自国の人間と思われないあたりが、レベルが高い。

何が変わったか、自分ではわからないけれど、ベトナム人の友人にどの辺がベトナム人っぽいのか聞いてみたら「うーん!もう、全部ですね!だからとても親しみやすいです!!!」とキラッキラした目で言われた。だから通訳のみんな、年上年下問わず、俺にだけクッソタメ口なのかもしれない。よくわかんないんですけど、たぶん得してます。母上、ベトナム人っぽく見えるように産んでくれてありがとうございます。なにがかわったんだろほんと。アメリカ在住の友人曰く、「食が顔つきを変える」んだとな。たしかにそいつの顔は徐々に日本人離れしつつあるし、体の構成物質も変わってくだろう。

そういえば、日本人に「韓国人?」と聞いてもキレないけど、韓国人に「日本人?」と聞いたらブチキレられるので、どちらか迷ったら彼らは「韓国人?」と質問するらしいよ。アーハン。

Excelで式の区切り文字がセミコロンに変化して、更にエラーになる(グローバルな理由で)

現象

同じファイルを開いているのに、あるメンバー(ベトナム人メンバー)のExcelでだけ、以下のようになってしまう現象が起こって、ハマった。

  • 計算対象の値
    • 2014-09-06 10:58:48.0のような文字列でText型貼付け(案件の事情)
  • 現象
    • うまく数式が動かずエラーになる(#VALUE!になる)
    • 式の区切り文字がカンマでなく、セミコロンに変化する
      • 他の人: e.g. =IF(EXACT(1*U7,1*AP7),"○","×")
      • 問題の彼: e.g. =IF(EXACT(1*U7;1*AP7);"○";"×")

原因

  • Macの言語と地域の設定(Language & Region)で、数値の区切り記号(Number separators)の桁区切り(Grouping)が.、小数点(Decimal),になっていた
  • そのため、2014-09-06 10:58:48.02014-09-06 10:58:48,0にすると動く

ベトナムでは、数字の桁区切りにはピリオド.、小数点にはカンマ,を使う

f:id:kuteken:20150504145746p:plain

解決方法

  • Mac言語と地域の設定 > 詳細 (Language & Region > Advanced) から数値区切りの設定を変更し、Excelを再起動する
    • 書式の言語(Format language)をEnglishなどに変える

f:id:kuteken:20150504145749p:plain


他メンバーにとっては当たり前すぎて"I can't resolve!"とお手上げ。僕もまさか小数点が,であるなんて思いもしなかったので、ちょっと悩んだ。海外のメンバーと作業するときは、この辺の常識も疑ってかかろう。

MySQLのタイムゾーンの挙動差について調べた

作っている場所と使用される場所のタイムゾーンが違ったり、国際的なサービスを作っていたりする場合に、タイムゾーンまわりの挙動について気になったので、MySQLで実際に試したことのまとめ。

前提

mysql> SELECT @@global.system_time_zone, @@global.time_zone, @@session.time_zone;
+---------------------------+--------------------+---------------------+
| @@global.system_time_zone | @@global.time_zone | @@session.time_zone |
+---------------------------+--------------------+---------------------+
| ICT                       | SYSTEM             | SYSTEM              |
+---------------------------+--------------------+---------------------+
1 row in set (0.00 sec)

mysql> desc test_times;                                                                         
+-----------+-------------+------+-----+-------------------+-----------------------------+
| Field     | Type        | Null | Key | Default           | Extra                       |
+-----------+-------------+------+-----+-------------------+-----------------------------+
| type      | varchar(20) | YES  |     | NULL              |                             |
| timestamp | timestamp   | NO   |     | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
| datetime  | datetime    | YES  |     | NULL              |                             |
| date      | date        | YES  |     | NULL              |                             |
| time      | time        | YES  |     | NULL              |                             |
+-----------+-------------+------+-----+-------------------+-----------------------------+
5 rows in set (0.00 sec)
mysql> SET SESSION time_zone = '+07:00';
Query OK, 0 rows affected (0.00 sec)

mysql> insert into test_times values ('2000-01-01 00:00:00', '2000-01-01 00:00:00', '2000-01-01 00:00:00', '2000-01-01 00:00:00', '2000-01-01 00:00:00');
Query OK, 1 row affected (0.00 sec)

mysql> insert into test_times values ('now()', now(), now(), now(), now());
Query OK, 1 row affected, 1 warning (0.00 sec)

mysql> insert into test_times values ('utc_timestamp()', utc_timestamp(), utc_timestamp(), utc_timestamp(), utc_timestamp());
Query OK, 1 row affected, 1 warning (0.00 sec)

mysql> SET SESSION time_zone = '+09:00';                                                              
Query OK, 0 rows affected (0.00 sec)

mysql> insert into test_times values ('2000-01-01 00:00:00', '2000-01-01 00:00:00', '2000-01-01 00:00:00', '2000-01-01 00:00:00', '2000-01-01 00:00:00');
Query OK, 1 row affected (0.00 sec)

mysql> insert into test_times values ('now()', now(), now(), now(), now());
Query OK, 1 row affected, 1 warning (0.00 sec)

mysql> insert into test_times values ('utc_timestamp()', utc_timestamp(), utc_timestamp(), utc_timestamp(), utc_timestamp());
Query OK, 1 row affected, 1 warning (0.00 sec)

結果

mysql> SET SESSION time_zone = '+07:00';
Query OK, 0 rows affected (0.00 sec)

mysql> select * from test_times;
+---------------------+---------------------+---------------------+------------+-----------+
| type                | timestamp           | datetime            | date       | time      |
+---------------------+---------------------+---------------------+------------+-----------+
| 2000-01-01 00:00:00 | 2000-01-01 00:00:00 | 2000-01-01 00:00:00 | 2000-01-01 | 00:00:00  |
| now()               | 2015-04-17 02:18:06 | 2015-04-17 02:18:06 | 2015-04-17 | 02:18:06  |
| utc_timestamp()     | 2015-04-16 19:18:06 | 2015-04-16 19:18:06 | 2015-04-16 | 19:18:06  |
| 2000-01-01 00:00:00 | 1999-12-31 22:00:00 | 2000-01-01 00:00:00 | 2000-01-01 | 00:00:00  |
| now()               | 2015-04-17 02:18:15 | 2015-04-17 04:18:15 | 2015-04-17 | 04:18:15  |
| utc_timestamp()     | 2015-04-16 17:18:15 | 2015-04-16 19:18:15 | 2015-04-16 | 19:18:15  |
+---------------------+---------------------+---------------------+------------+-----------+
6 rows in set (0.00 sec)

mysql> SET SESSION time_zone = '+09:00';
Query OK, 0 rows affected (0.00 sec)

mysql> select * from test_times;
+---------------------+---------------------+---------------------+------------+-----------+
| type                | timestamp           | datetime            | date       | time      |
+---------------------+---------------------+---------------------+------------+-----------+
| 2000-01-01 00:00:00 | 2000-01-01 02:00:00 | 2000-01-01 00:00:00 | 2000-01-01 | 00:00:00  |
| now()               | 2015-04-17 04:18:06 | 2015-04-17 02:18:06 | 2015-04-17 | 02:18:06  |
| utc_timestamp()     | 2015-04-16 21:18:06 | 2015-04-16 19:18:06 | 2015-04-16 | 19:18:06  |
| 2000-01-01 00:00:00 | 2000-01-01 00:00:00 | 2000-01-01 00:00:00 | 2000-01-01 | 00:00:00  |
| now()               | 2015-04-17 04:18:15 | 2015-04-17 04:18:15 | 2015-04-17 | 04:18:15  |
| utc_timestamp()     | 2015-04-16 19:18:15 | 2015-04-16 19:18:15 | 2015-04-16 | 19:18:15  |
+---------------------+---------------------+---------------------+------------+-----------+
6 rows in set (0.00 sec)
時刻関数
データ型

まとめ

EC2 をmac起動時に自動起動、定時にシャットダウンする

EC2を毎日使うけど、夜中は落として節約したい案件だったので、mac起動時に自動起動する、定時にシャットダウンするようにした。

自動起動する方法

  • StartupItemsにServiceとplistを置いて、awscliで立ち上げることで、対応する。
  • awscliを使うので、クレデンシャル情報は作業前に登録しておくこと
  • <Service name>, <instance-id>, <Service description>には適当な名称を入れること。
  • StartupItemsはroot権限で動くので、ファイルのユーザはroot, グループはwheelへ。ディレクトリは755 (rwxr-xr-x)にしとくこと。
  • ドキュメントはこちら ( Startup Items )
$cat /Library/StartupItems/<Service name>/<Service name>
#!/bin/sh

. /etc/rc.common

StartService () {
  # Insert start command below.
  aws ec2 start-instances --instance-ids <instance-id>
}

StopService () {
}

RestartService () {
  StartService;
}

RunService "$1"
$ cat /Library/StartupItems/<Service name>/StartupParameters.plist
{
  Description = "<Service description>";
  Provides = ("<Service description>");
}

定時シャットダウンする方法

  • 普通に crontab で仕込む。
  • 時差やサーバの時間に注意
$ date
2015年 4月17日 金曜日 00時10分13秒 ICT
$ sudo crontab -e
# every day JST 23:00 shutdown
0 21 * * * /sbin/shutdown -h now
$ sudo crontab -l