mosh: MITからモバイル時代のSSH代替品

http://mosh.mit.edu/

  • ローミング可能
  • 断続的な接続でも平気
  • ローカルエコーで快適なレスポンス

などの機能をそなえたSSH代替ターミナルソフト。その名も「モッシュ

iPhone/iPadでウロウロしながらサーバ作業をするのを想定しているようだ。ドキュメントやパッケージの充実度からしてもかなり高質のプロジェクト。こいつら本気でSSHを越えようとしている。

こんな能書き

  • IPが変っても大丈夫
  • スリープ後もターミナルが生きている
  • 遅いリンクでもローカルエコーにより快適
    • 要は素早く打ち込んでいるときにスクリーンが止っちゃうことがない
    • フルスクリーンモードでも有効
    • サーバ側と同期化されていない入力はアンダーラインで記される
  • ルート権限がなくてもサーバのインストール可能
    • サーバは一般プログラムとしてインストールして、それをSSHで立ち上げる
    • SSHをinetdとして使っている感じだと思う。いったん立ち上がるとUDPでクライアントと通信しはじめる。
  • ユーザ認証はSSHに任せている
    • ので今迄通りに使える
    • セキュリティー上難しく危ない部分は実績のあるSSHにまかせているので安心できる
  • 頑丈なターミナルソフト
    • バイナリのエスケープシーケンスでセッションが文字化けするようなことが無い
    • 最初からutf8サポート。既存ターミナルのエンコーディング関係のバグをとってある。
  • Control-Cがいつでもきく

ローミングとか今やっていないので、全ての恩恵にはあずかれないが、いくつか検証できた。

  • ラップトップをスリープさせて起した後、ネットワークが接続するとターミナルが生き返った。これだけでも使う価値あり。
  • ローカルエコー: 素早く打ち込んでも全くラグがない。これは快適。

これならサーバにつなげっぱなしにしたターミナルをずっと開けていられる。カフェに移動したら再接続とかする必要がなくなる。

これだけでも凄いが、このプロジェクトは単にローミング可のターミナルを作ったのではない。State Synchronization Protocolという状態同期可プロトコルを開発し、moshはそれをもちいた「最初のアプリケーション」という位置付けになっている。単にGUI環境を作るのでなく、それを可能にするプロトコルとツールキットを開発したMITらしい壮大な発想だ。
学術的な話を読みたが、取りあえず使えるようにしよう。

「Getting Mosh」セクションに重なOS環境のパッケージが準備されている。
Lucidにはadd-apt-repository、apt-get update、installですんなり入った。
ソースからインストールする際のはprotocol buffersとboostライブラリとヘッダーが必要となるが、依存モジュールもちゃんと明記してある。

ライアントコマンドはなんとperlスクリプト。IO::Ptyが必要になる。debianならlibio-pty-perlで入る。あるいはcpan IO::Ptyで入れる。


$ mosh
Can't locate IO/Pty.pm in @INC (@INC contains: /etc/perl /usr/local/lib/perl/5.10.1 /usr/local/share/perl/5.10.1 /usr/lib/perl5 /usr/share/perl5 /usr/lib/perl/5.10 /usr/share/perl/5.10 /usr/local/lib/site_perl .) at /usr/local/bin/mosh line 24.
BEGIN failed--compilation aborted at /usr/local/bin/mosh line 24.


$ mosh
Usage: /usr/local/bin/mosh [options] [--] [user@]host [command...]
--client=PATH mosh client on local machine
(default: "mosh-client")
--server=PATH mosh server on remote machine
(default: "mosh-server")

--predict=adaptive local echo for slower links [default]
-a --predict=always use local echo even on fast links
-n --predict=never never use local echo

-p NUM --port=NUM server-side UDP port

--help this message
--version version and copyright information

Please report bugs to mosh-devel@mit.edu.
Mosh home page: http://mosh.mit.edu

とりあえずサーバに向けてみる

サーバ側にmoshがインストールされていないとエラーになる。sshがユーザのパス上のmosh-serverを探して立ち上げる。


$ mosh example.com
bash: mosh-server: command not found
Connection to example.com closed.
/usr/local/bin/mosh: Did not find mosh server startup message.

ファイアーウォール設定:

サーバの60000-61000のUDPポートを開ける。
sudo ufw allow 60000:61000/udp
これが閉まっていると、ずっと接続待ち状態になってしまう。
そういうときは「Control-^ .」(コントロール+シフト+6押してからポチ)で終了。

設定のまとめ

  • クライアントとサーバにmoshをインストール
  • サーバでUDP 60000-61000を開ける

これだけ。

SSHなんてもう完結していて進化の余地が無いなんて思ってしまうのは想像力な無い証拠なんだな。どんなに優れたものでも、さらに向上の余地はある。それをイメージする想像力と実現する能力のある奴が歴史をつくっていくと感じさせるプロジェクトだ。

Russ Coxの自己複製zipファイル

Quine の圧縮ファイル版のつくりかたをCox氏が種明かしする。

http://research.swtch.com/zip

なんと次のようなファイルを作ることが可能なのだ:


$ gunzip < r.gz > r
$ cmp r r.gz
$

内容

  • 「turtles all the way down」の逸話
  • 「the way down」テーマのユーモラスな画像
  • Quineの歴史とpython, scheme, go言語による実例
  • Lempel-Zivの仕組み
  • zipフォーマットレベルでロジックを解説
  • 自己複製するファイル実例: r.gz r.tar.gz r.zip
  • これらを生成するgoプログラム

こんな難問を娯楽とするぐらいでなければ偉大なハッカーにはなれないんだろうか…

いつか理解したいポスト: http://research.swtch.com/zip

お宝発見! Paul Vixieのサイン入り初版K&R

古本屋でK&Rなどの古典書物を見付けると必ず裏表紙を見るようにする。ひょっとしたら有名人のサイン入りかもという淡い期待があるからだ。今日その長年の努力が実った。




バークレーの古本屋で見付けた。「Berkeley Internet Name Domain」(BIND)の開発者だったから納得いく。コンディションが良いのでVixie氏がC言語を覚えるのに使った本ではなさそうだ。しかし、間違いなくお宝と言えるだろう。

Python小技: 辞書をプロパティ風にアクセス可能にする機能的ラッパー

foo=dict(bar=42)がある場合、いちいちfoo['bar']など書かず、JavaScriptのようにfoo.barと書きたい。
pythonはこういうことを簡単にさせてくれる:


class dotdict(dict):
__getattr__ = dict.__getitem_
http://odiak.net/blog/post/1618
あるいは

class Struct:
def __init__(self, **entries): self.__dict__.update(entries)
http://norvig.com/python-iaq.html

でも上記のやりかただと、この機能は一つの辞書で終ってしまい、オブジェクトグラフをdot表記で辿ることはできない。
foo=dict(bar=dict(baz=42))とある場合、foo.bar.bazとアクセスするにはもうちょっと工夫が必要だ。そこで:


class DotAccessible(object):
"""オブジェクトグラフ内の辞書要素をプロパティ風にアクセスすることを可能にするラッパー。
DotAccessible( { 'foo' : 42 } ).foo==42

メンバーを帰納的にワップすることによりこの挙動を下層オブジェクトにも与える。
DotAccessible( { 'lst' : [ { 'foo' : 42 } ] } ).lst[0].foo==42
"""

def __init__(self, obj):
self.obj=obj

def __repr__(self):
return "DotAccessible(%s)" % repr(self.obj)

def __getitem__(self, i):
"""リストメンバーをラップ"""
return self.wrap(self.obj[i])

def __getslice__(self, i, j):
"""リストメンバーをラップ"""

return map(self.wrap, self.obj.__getslice__(i,j))

def __getattr__(self, key):
"""辞書メンバーをプロパティとしてアクセス可能にする。
辞書キーと同じ名のプロパティはアクセス不可になる。
"""

if isinstance(self.obj, dict):
try:
v=self.obj[key]
except KeyError:
v=self.obj.__getattribute__(key)
else:
v=self.obj.__getattribute__(key)

return self.wrap(v)

def wrap(self, v):
"""要素をラップするためのヘルパー"""

if isinstance(v, (dict,list,tuple)): # xx add set
return self.__class__(v)
return v

if __name__=='__main__':

これだと芋蔓式に.accessができて便利だ。

辞書の中にリストがあってその中にまた辞書があったりするオブジェクト


da=DotAccessible(dict(foo=42,
dct=dict(foo=42),
lst=[dict(foo=42)],
tpl=[dict(foo=42)]))
print da
DotAccessible({'lst': [{'foo': 42}],
'tpl': [{'foo': 42}],
'bar': {'dct': 89},
'foo': 42})

辞書要素をプロパティとしてアクセス


assert da.foo==42

もともとあるプロパティは以前のようにアクセスできる


print 'items:', da.items()

ラップされたオブジェクト下の辞書もラップされている…


assert isinstance(da.dct, DotAccessible)

これにより連鎖的に.アクセスが可能になる


assert da.dct.foo==42

リストもラップされている


assert isinstance(da.lst, DotAccessible)

ので、リスト内の辞書要素も.アクセスが可能。


da.lst[0].foo==42
da.lst[:3][0].foo==42

tupleも同じく。


da.tpl[0].foo==42

doc stringの実例のテスト


assert DotAccessible( { 'lst' : [ { 'foo' : 42 } ] } ).lst[0].foo==42
assert DotAccessible( { 'foo' : 42 } ).foo==42

ユーザ定義クラスでも


class Foo(object):
def __init__(self):
self.dct=dict(foo=42)

assert DotAccessible(Foo()).dct.foo==42

dictのサブクラスを使うことを強制されないので、コントロール外のコードから来た辞書でも.アクセスができるようになるのがラッパーの利点だ。ラップして思う存分.アクセスした後はまた元のオブジェクトを関数にわたしたり返したりできるので、__getattr__などをオーバライドしていることによる弊害も.アクセスを使いたい自分のコードだけに限定できて安全だ。

何でも.アクセスできるようになってpythonを使っていて唯一javascriptが羨ましくなる問題が解消できた。

もろもろハッカーニュースアンケート

プログラミングはじめて何年?


Poll: How long have you been programming?


http://news.ycombinator.com/item?id=3786926



Less than 6 months (22)



1 year (29)



2 years (48)



3-5 years (180)



7 years (163)



10+ years (500)



20+ years (202)



30+ years (76)



50+ years (1)


メインOSは?

最近ハンケート多すぎない?


[dead]


None



yes (500)



no (227)


ハッカーニュースアンケートグラフ生成プログラム。Unixパイプに限界を感じたのでズルしてpythonで書いた。あとはこれをwebでホスティングすれば、完全自動化できる。


#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys,os
import re
import time
import lxml.html
import urllib2
import urllib
import jinja2

default_template="""
<div>
<a href="{{original_url}}"><h3>{{title}}</h3></a>
<p>
<a href="{{original_url}}">{{original_url}}</a>
</p>

{% for vote in votes %}
<span>{{vote.0}} ({{vote.1}})</span>
<div style="width:{{vote.1}}px; height: 1em; background:#cceecc; border: 1px solid #000; margin-bottom: 1em;" >

</div>
{% endfor %}
</div>
"""

def normalized_votes(trs, max_width=1000):
vts=list(votes(trs))
max_score=max([ pair[1] for pair in vts ])
return [ (label, max_width*score/max_score) for label, score in vts ]

def votes(trs):
"""vote row elements to sequence of pairs (label, score)
"""
while True:
try:
item=trs.next()
comment=trs.next()
spacer=trs.next()
except StopIteration:
break

m=re.match(r'^(\d+)\s*\w+', comment.text_content())
if not m:
raise RuntimeError("failed to parse vote: " + str(item))
score=int(m.group(1))

yield (item.text_content(), score)

def parse(html_str, **opt):
""" html_string --> page data dict to be rendered.
"""
lx = lxml.html.fromstring(html_str)
votes_table=lx.find('./body/center/table/tr/td/table/tr/td/table')
title_td=lx.xpath('.//td[@class="title"]')[0]
title_link=title_td.find('.//a')
return dict(votes=normalized_votes(iter(votes_table), **opt),
title=title_td.text_content(),
original_url=None if title_link is None \
else 'http://news.ycombinator.com/' + title_link.get('href'),
)

def resolve_template(template=None):

# tdir,tname=os.path.split(template)
# env=jinja2.Environment(loader=jinja2.FileSystemLoader(tdir))
# return env.get_template(tname)

if template is None:
template_str=default_template
elif os.path.exists(template):
template_str=file(template).read().decode('utf8')
else:
template_str=template
return jinja2.Environment().from_string(template_str)

def fetch(url):
"""Resolve url|or-html-file to html string.
A simple caching is implemented so that you dont get banned from hn for repeated use.
"""
cache_file=os.path.join('/var/tmp/', 'hn-poll-'+urllib.quote(url).replace('/','-'))

if not url.lower().startswith('http://'):
# accept local file
html_file=url
return file(html_file).read().decode('utf8')

elif os.path.exists(cache_file) and time.time()-os.stat(cache_file).st_mtime<=3600:
# cache hit
print >>sys.stderr, 'cache-hit:', cache_file
html=file(cache_file).read()

else:
# fetch
print >>sys.stderr, 'fetching:', url
html=urllib2.urlopen(url).read()
tmp_file=cache_file+'.tmp'
file(tmp_file, 'w').write(html)
os.rename(tmp_file, cache_file)

return html.decode('utf8')

if __name__=='__main__':

import baker
import json

@baker.command
def as_json(html_file, indent=None):
"""dumps (item,vote) pairs as json"""

print json.dumps(parse(file(html_file).read()),
indent=indent if indent is None else int(indent))

@baker.command
def as_html(hackernews_poll_url, template=None, max_width=1000):
"""render hackernews poll url into html graph.
"""

html=fetch(hackernews_poll_url)
template=resolve_template(template)
print template.render(**parse(html, max_width=int(max_width)))

baker.run()

glibcの歴史

普段あまり意識しないしないlibcだが、カーネルの門番とも言える重要なコンポーネントだ。Linuxで標準のlibcはGNU libc、略してglibc。最近このglibcのsteering committeeが解散した。これをうけてlwn.netのJonathan Corbetがglibcの歴史をふりかえる。
http://lwn.net/SubscriberLink/488847/cb91a5cc3d179f3c/

とても良く書けている記事なので原文を読むことをすすめる。以下、面白いなと思ったところをいくつか紹介。

フォーク

FSFのプロジェクト管理に嫌気がさしたLinux開発者が90年代前半にlibcをフォークしていたとは知らなかった。当時のディストロたちは「Linux libc」と呼ばれるバージョンを使っていたそうな。
90年代半ばからUlrich Drepperがこつこつ開発する。その結果、glibc2はLinux libcより良いもになりdistroglibcに戻る。

Uli時代

Ulrich Drepperはglicの独裁者的存在(これはkaratenの言葉)になる。殆どのコミットは彼がし、数少ない他コミッターは彼の承認なしには手を加えられない状況が続く。

変化の兆し

2009にコード管理をGitに移行。この際に元祖管理者のRoland McGrathがDrepperに釘をさす:
「コニュニティーが改善したい簡単な事項を、漠然とした保守主義や根拠のない不信により邪魔することのないように」

一つの時代の終り

2010年9月、UlrichはRed Hatを辞め、Goldman Sachsの技術部門へ。これでglibcのUlrich時代は幕をとじたと考えていいだろう。長いあいだご苦労さまでした。

コミュニティーによるglibc

2012年3月、RolandはSteering Committeeの解散を公表。その理由は「ボランティア活動の活性化により開発者コミュニティーが自己統治できるようになった」からだ。

新たな約束

さらに、開発者の一人はこう呼び掛ける:「以前、無礼な仕打ちをうけ、glibcへの貢献を諦めた人もまた試してみてください。今度はそういうことはありません。約束します」

新のリーダーとは

元記事はふれていないが、元祖コミッターのRolandがUlrichに実権を譲りながらも25年間ものあいだglibcを見守ってきたことに着目したい。協調性のないハッカーにプロジェクトを任せたり、コミュニティーに委ねたりと、臨機応変にプロジェクトを守ってきた影のリーダーがいなかったらこのプロジェクトは消滅していたかもしれない。

オープンソースプロジェクトのガバナンスについて学ぶことが多いglibcの歴史でした。

ECMAchine: ブラウザのなかでうごくschemeマシン+おもちゃOS

https://github.com/AlexNisnevich/ECMAchine

repl: シェルの役割をはたす

ファイルシステムlispコマンドで操作できる

JavaScript相互運用: JavaScriptの関数が呼び出せる

プロセス管理:

ミリ秒毎にコンテキストスイッチするスケジューラでプロセスを実装しているよう。つまりブラウザの中でバックグラウンド処理が可能ってことになる。

実用が目的でなく、SICPで勉強したことを応用してみたかったようだ。しかし、思わぬ用途が見付かるかもしれない。時間があるときにゆっくり吟味してみたいプロジェクトだ。