MacBook Air Mid 2013 を入手したのでOSXを起動せずにGentoo入れてみた

ことはじめ

おやおや,こんなところに Mid 2013 な感じの MacBook Air がありますね.ふうむ,電源を入れようと思ったが,そのまま起動しても面白くないな.OSXのセットアップしてる時間があったらGentooが入るんじゃないか?そうだ,ここはGentooInstallBattleっぽく,OSXを起動せずにGentooを入れてみようじゃないか!いやぁ,我ながら良い考えだなあ.

ぶーと

皆さんも2-3本はGentooのインストール用USBスティックを持ち歩いていると思います.色々役に立つし,出張先や飮み屋でもGentooInstallBattleができて非常に便利ですからね.もちろん,一応手元にはGentooのLiveCDがありますから,まずはインストールメディアの選択から始めるわけです.いかにもGentooらしいですね.

さて,Optionキーを押しながら電源を入れるとbootするメディアを選択できるので,USBなり外付けCDドライブなりから起動します.起動してしまえばすんなり動いてくれる…と思っていた時期が僕にもありました.あいにく手元にUSBなEthernetがありませんし,無線LANはもちろん動かないわけですので,ちょっとだけ困るわけですね.でも時代はクラウドスマホの時代,テザリングができるじゃありませんか.ネットワーク接続を手に入れるために,おもむろにスマートフォンMBAを接続します.USBテザリングを有効にしたら,普通にNICが見えますので,dhcpcdを叩いてあげると繋がります.やったね.

ぱーてぃしょん

ネットワーク接続を手に入れたら,次はパーティションを切りたくなりますね.全部消しちゃいたくなりますよね.OSXの領域とかリカバリ領域とか,使わないのに残しておいてももったいないですからね.しかし,このまま全部消してしまうと,起動時の「じゃーんwww」という音が消せなくなってしまうので,とりあえず外付けHDDとかをつないでddしておきます.この際,パーティションごとにddしたほうが後々使い勝手が良いです.MBRも忘れずにddしましょう.

dd if=/dev/sda of=/path/to/backup/MBR.img bs=512 count=1
dd if=/dev/sda1 of=/path/to/backup/sda1.img bs=1M
dd if=/dev/sda2 of=/path/to/backup/sda2.img bs=1M
dd if=/dev/sda3 of=/path/to/backup/sda3.img bs=1M

になっている(と思う)ので,sda2に関してはサイズも大きいし別にいいかなーと思います.MBRとsda1とsda3はあとで使うので必ずddしておきましょう.

パーティションはGPTで作ります.僕はこんな感じで切りました.

% sudo parted /dev/sda
GNU Parted 3.1
Using /dev/sda
Welcome to GNU Parted! Type 'help' to view a list of commands.
(parted) unit s
(parted) print
Model: ATA APPLE SSD TS0128 (scsi)
Disk /dev/sda: 236978176s
Sector size (logical/physical): 512B/4096B
Partition Table: gpt
Disk Flags:

Number Start End Size File system Name Flags
1 2048s 585727s 583680s fat32 non-fs bios_grub
2 585728s 1171455s 585728s ext4 ext4
3 1171456s 17172479s 16001024s linux-swap(v1) swap
4 17172480s 87171071s 69998592s ext4 ext4
5 87171072s 236976127s 149805056s ext4 ext4

インストールしてから気付いたのでもはや後戻りはしたくないですが,EFIだとISOイメージからブートできたりするので,/bootは大きめに切っておいたほうが良かったですね.あと,現状ではサスペンドしたあとにディスプレイ輝度が0%か100%の2択になってしまうので,必ずswap領域を作っておきましょう./etc/fstabはこんな感じです.
/dev/sda1   /boot/efi   vfat   noauto,errors=remount-ro                   0 2
/dev/sda2 /boot ext4 noauto,noatime,discard,errors=remount-ro 1 2
/dev/sda3 none swap sw 0 0
/dev/sda4 / ext4 noatime,discard,errors=remount-ro 0 1
/dev/sda5 /home ext4 defaults,discard,errors=remount-ro 0 0

いんすとーる

システムのインストールは何も考えなくていいです.普通にインストールして下さい.非常に簡単ですね.でもおそらく再起動できないので,UbuntuのライブCDとかからEFIでブートします.その後,

modprobe efivars
コマンドを叩いてブート用ディスクを作ります(この辺はググったら出てきます).EFIブートできたら,Gentooから同様の作業をするといい感じになると思います.また,grub2-installするときに多分失敗するので,自分でEFIの設定を書き換える必要があります.efibootmgrを使ってこんな感じにブートローダ周りを設定してあげる必要があります.
# efibootmgr -v
BootCurrent: 0000
Timeout: 5 seconds
BootOrder: 0000
Boot0000* gentoo HD(1,800,8e800,9b3bd77d-799f-46f7-a569-95a251a4f759)File(\EFI\gentoo\grubx64.efi)
Boot0080* ACPI(a0341d0,0)PCI(1c,5)PCI(0,0)03120a00000000000000HD(2,64028,e066090,553ed906-2f93-4629-a5e6-c90ddbcec833)File(\System\Library\CoreServices\boot.efi)
BootFFFF* ACPI(a0341d0,0)PCI(1c,5)PCI(0,0)03120a00000000000000HD(2,64028,e066090,553ed906-2f93-4629-a5e6-c90ddbcec833)File(\System\Library\CoreServices\boot.efi)

きどうおんをけす

起動するときの「じゃーんwww」はOSXの音量とリンクしているらしいですが,もはやそんなものはありません.どうやら,リカバリ領域から起動したりリカバリUSBから起動すると良いらしいです.でも,最近のMacにはリカバリUSBなどというレガシーなものは同梱されていないようです.僕はレガシーな人間なので,仕方なくUSBスティックを作ります.

まずは,USBにMBR.imgをddします.次に,partedでこんな感じにパーティションを割ります.イメージとしては,USBにMBRをddした段階でパーティションテーブルができているので,テーブルの2と3を消します.次に,3番目のテーブルがリカバリ領域と同じになるように2番目のテーブルを切ります.あとは,1番目と3番目のパーティションにsda1とsda3をそれぞれddします.簡単ですね.

% sudo parted /dev/sdb
GNU Parted 3.1
Using /dev/sdb
Welcome to GNU Parted! Type 'help' to view a list of commands.
(parted) unit s
(parted) print
Model: silicon -power (scsi)
Disk /dev/sdb: 15654912s
Sector size (logical/physical): 512B/512B
Partition Table: gpt
Disk Flags:

Number Start End Size File system Name Flags
1 40s 409639s 409600s fat32 EFI System Partition boot
2 409640s 1000000s 590361s Customer
3 1000001s 2269536s 1269536s hfs+ Recovery HD

USBから起動したら,ターミナルを立ち上げてnvramを使ってブートサウンドを殺します.02で死ななかったら00や01や80なんかもあるらしいです.何のマジックナンバーかはよく知りません.
# nvram SystemAudioVolume=%02
# shutdown -h now
とか叩くといい感じになりました.

おわりに

あとで追記するか別記事で各項の詳細が書ければと思います

アクセス制限のあるネットワーク環境下でmikutterがしたいんです?

想定環境

みなさんはどんな環境でmikutterを利用しているでしょうか.様々な環境が想定されますが,今回はファイアウオールやアクセス制限によってTwitterにアクセスできなくなっているネットワークでmikutterを使う,という想定のもと記事を書こうと思います.今回の方法を適用するために必要な条件は「Proxy認証なしに外部にアクセス可能なポートが1つ以上存在する」ということだけです.そのポートから適当なVPSとかにsshかけてsocksしましょう.

やり方

まずsocksifyをインストールします.

gem install socksify

次に,適当なVPSとかにsshでログインします.例えば,53番が空いてるということが分かったら,

ssh -p 53 -D 8080 myvps.mydomain.com

みたいな感じでログインします.-pはポート指定のオプション,-DはSocksするポートの指定です.このコマンドを叩くと,localhost:8080をProxyに設定するとVPSからパケットが出て行きます.なお,このコマンドを叩く前にVPS側のssh待ち受けポートを53番に設定しておきましょう(もちろん非推奨です).その後,下記のコマンドでmikutterを起動します.

socksify_ruby localhost 8080 mikutter.rb

動きましたね!

問題点

sshのポートを53番に設定する気持ち悪さとTwitterしたさ,どちらが勝つかという問題

mikutterの Service.primary.post :message => "Hello, mikutter!" を旅してみる

ことはじめ

「mikutterでアイコンを設定したい」「mikutterから名前を変更したい」そう思ったことはありませんか?mikutterにはプロフィール周りを設定する機能がないような気がするので,mikutterからTwitterAPIを叩いて実現しようと考えました.mikutter/core/mikutwitter/ あたりにTwitterAPIを叩くときに関係するソースが置いてあるので,その辺を読めば使い方が分かります.ちなみに,TwitterREST API v1.1 に関する情報は https://dev.twitter.com/docs/api/1.1 にまとめられています.

Service.primary.post :message => "Hello, mikutter!"

mikutterでツイートするときは,タイトルにもあるように次のように入力します.

Service.primary.post :message => "Hello, mikutter!"

さて,Service.primary.postは何をしているのでしょうか? mikutter/core/service.rb をのぞいてみましょう!

class Service
  include ConfigLoader

  # MikuTwitter のインスタンス
  attr_reader :twitter

  # 現在ログイン中のアカウント
  @@services = Set.new

  def self.services_refresh
    Service.new if(@@services.empty?) end

  # 存在するServiceオブジェクトをSetで返す。
  # つまり、投稿権限のある「自分」のアカウントを全て返す。
  def self.all
    Service.services_refresh
    @@services.dup end
  class << self; alias services all end

  def self.primary
    services.first end
  class << self; alias primary_service primary end

Serviceクラスには@@servicesというクラス変数があり,ここにログイン中のアカウント情報を格納しているようです. Service.primary はこの中の最初のインスタンスを指定しています.@@servicesに複数のServiceインスタンスを格納すれば,複垢対応も簡単にできそうですね.そして,上の方にコメントがある MikuTwitter が気になります.

さて,次は Service.primary.post に進みます.Serviceクラスの中にpostメソッドが定義されています.同じく mikutter/core/service.rb の中をのぞいてみます.

  # なんかコールバック機能つける
  # Deferred返すから無くてもいいんだけどねー
  def self.define_postal(method, twitter_method = method, &wrap)
    function = lambda{ |api, options, &callback|
      if(callback)
        callback.call(:start, options)
        callback.call(:try, options)
        api.call(options).next{ |res|
          callback.call(:success, res)
          res
        }.trap{ |exception|
          callback.call(:err, exception)
          callback.call(:fail, exception)
          callback.call(:exit, nil)
          Deferred.fail(exception)
        }.next{ |val|
          callback.call(:exit, nil)
          val }
      else
        api.call(options) end }
    if block_given?
      define_method(method){ |*args, &callback|
        wrap.call(lambda{ |options|
               function.call(twitter.method(twitter_method), options, &callback) }, self, *args) }
    else
      define_method(method){ |options, &callback| function.call(twitter.method(twitter_method), options, &callback) } end
  end

  define_postal(:update){ |parent, service, options|
    parent.call(options).next{ |message|
      notice 'event fire :posted and :update by statuses/update'
      Plugin.call(:posted, service, [message])
      Plugin.call(:update, service, [message])
      message } }

このあたりでupdateメソッドを定義しています.読みにくいですが,

class Foo
  define_method(:method_name) { |args..| ... }
end

の形でFooクラスにmethod_nameメソッドを定義することができます.ここではこの形でupdateメソッドを定義しています. define_postal(:update){...} からがメソッド定義開始になります.主要部分だけを抜き出すと,だいたい次のような形に展開できます.

def update(options, &callback)

  callback.call(:start, options)      #APIを叩くよー
  callback.call(:try, options)        #試してみる.Javaで言うtryブロック的なやつ

  deferred = twitter.update(options)  #APIを叩くとDeferredが戻ってくる

  deferred.next { |res|               #成功したときのブロックを記述
    callback.call(:success, res)
    res
  }.trap{ |exception|                 #失敗したとき.Javaで言うcatchブロック的なやつ
    callback.call(:fail, exception)
    Deferred.fail(exception)
  }

end

ここで,Deferredは mikutter/core/lib/deferred/ に定義されているクラスです.ネットワークを介しているので,リクエストの結果が戻ってくるまで時間がかかりますが,Deferredを使えばリクエストを別スレッドで実行して,あとから結果を取り出すことができます.
twitter.update(options) でTwitterAPIを叩いているように見えますね.twitterはMikuTwitterのインスタンスです.MikuTwitterはTwitterAPIにアクセスするための機能を提供しています. mikutter/core/mikutwitter/api_shortcuts.rb を見ると,APIを叩いてツイートしている部分が見つかります.

  def update(message)
    text = message[:message]
    replyto = message[:replyto]
    receiver = message[:receiver]
    data = {:status => text }
    data[:in_reply_to_user_id] = User.generate(receiver)[:id].to_s if receiver
    data[:in_reply_to_status_id] = Message.generate(replyto)[:id].to_s if replyto
    (self/'statuses/update').message(data) end
  alias post update

引数で受け取っているmessageはMessageオブジェクトですね.text = message[:message]はツイート本文です.textを追っていくと,最終的に (self/'statuses/update').message(data) のdataに入っていくことが分かります. self/'statuses/update' は不思議な形をしていますが, mikutter/core/mikutwitter/api_call_support.rb を見ると,MikuTwitterには / メソッドがあり,リクエストを作成していることが分かります.

  # APIのパスを指定する。
  # 例えば、 statuses/show/1234567890.json?include_entities=true を叩きたい場合は、以下のように書く。
  # (twitter/:statuses/:show/1234567890).json include_entities: true
  def /(api)
    Request.new(api, self) end

messageメソッドでdataを引数に渡していますが,ツイートするだけなら data は最小限で構わないようです.つまり,ここまでの結果を見ると,

(Service.primary.twitter/'statuses/update').message({:status => "Hello, mikutter!"})

でツイートできるようです. https://api.twitter.com/1.1/statuses/update.json を見ると,確かにstatusだけがrequiredになっています.ここのmessageメソッドは mikutter/core/lib/mikutwitter/api_call_support.rb 中に定義されています.

    defparser :user, :users, Users
    defparser :message, :messages, Messages
    defparser :list
    defparser :id
    defparser :direct_message

defparser メソッドを呼び出していますが,先ほどの define_postal と同様に,defparserメソッド内でdefine_methodを呼んでいます.

    def self.defparser(uni, multi = :"#{uni}s", container = Array, defaults = {})
      parser = lazy{ MikuTwitter::ApiCallSupport::Request::Parser.method(uni) }
      define_method(multi){ |options = {}|
        type_strict options => Hash
        json(defaults.merge(options)).next{ |node|
          Thread.new{ container.new(node.map(&parser)).freeze } } }

      define_method(uni){ |options = {}|
        type_strict options => Hash
        json(defaults.merge(options)).next{ |node|
          Thread.new{ parser.call(node) } } }

      define_method(:"paged_#{multi}"){ |options|
        type_strict options => Hash
        json(defaults.merge(options)).next{ |node = {}|
          Thread.new {
            node[multi] = node[multi].map(&parser)
            node } } } end

さて,jsonメソッドでは twitter.api(api, options, force_oauth) を呼んでいます.

    # APIリクエストを実際に発行する
    # ==== Args
    # [options] API引数(Hash)
    # ==== Return
    # Deferredのインスタンス
    def json(options)
      type_strict options => Hash
      twitter.api(api, options, force_oauth).next{ |res|
        Thread.new{ JSON.parse(res.body).symbolize } } end

twitter.api は mikutter/core/lib/mikutwitter/query.rb に定義されています.

  # 別のThreadで MikuTwitter::Query#query! を実行する。
  # ==== Args
  # MikuTwitter::Query#query! と同じ
  # ==== Return
  # Deferredのインスタンス
  def api(api, options = {}, force_oauth = false)
    type_strict options => Hash
    promise = Thread.new do
      query!(api, options, force_oauth) end
    promise.abort_on_exception = false
    promise end

query!メソッドを呼んでいますね.

  # APIを叩く
  # ==== Args
  # [method] メソッド。:get, :post, :put, :delete の何れか
  # [api] APIの種類(文字列)
  # [options]
  #   API引数。ただし、以下のキーは特別扱いされ、API引数からは除外される
  #   :head :: HTTPリクエストヘッダ(Hash)
  # [force_oauth] 互換性のため
  # ==== Return
  # API戻り値(HTTPResponse)
  # ==== Exceptions
  # TimeoutError, MikuTwitter::Error
  def query!(api, options = {}, force_oauth = false)
    type_strict options => Hash
    resource = ratelimit(api.to_s)
    if resource and resource.limit?
      raise MikuTwitter::RateLimitError.new("Rate limit #{resource.endpoint}", nil) end
    method = get_api_property(api, options, method_of_api) || :get
    url = if options[:host]
            "http://#{options[:host]}/#{api}.json"
          else
            "#{@base_path}/#{api}.json" end
    res = _query!(api, options, method, url)
    if('2' == res.code[0])
      res
    else
      raise MikuTwitter::Error.new("#{res.code} #{res.to_s}", res) end
  rescue MikuTwitter::RateLimitError => e
    # 変数 resource の情報は振るい可能性がある(他のTwitterクライアントが同じエンドポイントを使用した時等)
    Plugin.call(:mikutwitter_ratelimit, self, ratelimit(api.to_s))
    raise e end

内部でさらに_query!メソッドを呼んでいますね.

  # query! の本質的な部分。単純に query_with_oauth! を呼び出す
  def _query!(api, options, method, url)
    query_uri = (url + get_args(options)).freeze
    MikuTwitter::Query.api_lock(query_uri) {
      cache(api, url, options, method) {
        retry_if_fail(method, query_uri){
          fire_request_event(api, url, options, method) {
            query_with_oauth!(method, url, options) } } } }
  end

さらにさらにquery_with_oauth!を呼んでいます. mikutter/core/lib/mikutwitter/connect.rb の中に定義部分があります.

  def query_with_oauth!(method, url, options = {})
    if [:get, :delete].include? method
      path = url + get_args(options)
      res = access_token.__send__(method, path, options[:head])
    else
      path = url
      query_args = options.melt
      head = options[:head]
      query_args.delete(:head)
      res = access_token.__send__(method, path, query_args, head) end
    if res.is_a? Net::HTTPResponse
      case res.code
      when '200'
      when '401'
        notice "#{res.code} Authorization failed."
        notice res.body
        notice "trigger request: #{path}"
        begin
          errors = (JSON.parse(res.body)["errors"] rescue nil)
          errors.each { |error|
            notice error
            if [INVALID_OR_EXPIRED_TOKEN].include? error["code"]
              atoken = authentication_failed_action(method, url, options, res)
              notice atoken
              return query_with_oauth!(method, url, options) if atoken end }
        rescue Exception => e
          notice e end
      when '429'
        raise MikuTwitter::RateLimitError.new("Rate limit #{url}", nil)
      end
    end
    res
  end

ツイートするにはaccess tokenが必要ですね.

  attr_accessor :consumer_key, :consumer_secret, :a_token, :a_secret, :oauth_url

  def initialize(*a, &b)
    @oauth_url = 'https://twitter.com'
    super(*a, &b)
  end

  def consumer(url=oauth_url)
    OAuth::Consumer.new(consumer_key, consumer_secret,
                        :site => url) end

  def access_token(url=@oauth_url)
    OAuth::AccessToken.new(consumer(url), a_token, a_secret) end

やっとURLを叩けるようになりました.ここまできてやっとツイートができるんですね.

mikutterで適当なAPIを叩く方法

TwitterAPIリファレンスを見れば,実際にAPIを叩くことができるようになります.ここでは例としてプロフィールアイコンと名前を設定してみます.APIhttps://dev.twitter.com/docs/api/1.1/post/account/update_profile_image にまとめられています.これを見ると,Base64エンコードされた700kB以下のGIF,JPG,PNG画像であればアップロードできるようです.基本的にRequestオブジェクトを作成してjsonメソッドを呼べばよいので,割と簡単に実装できます.

require 'base64'
(Service.primary.twitter/'account/update_profile_image').json(:image => Base64.encode64(open('path/to/icon.png').read))

こんな感じでアイコンが変更できます.名前の変更も同様に叩けます.

(Service.primary.twitter/'account/update_profile').json(:name => "ぺんぎんさんだよー")

TwitterAPIが手で叩けるTwitterクライアントはたぶんmikutterだけなので,名前変更芸やアイコン芸が簡単にできそうですね!

mikutterの投稿ボックスを強化するプラグイン: mikutter_shell_post

mikutter_shell_postとは

特定の宛先に向けたリプライをコマンドとして解釈し,実行するプラグインです.C++Haskellのコードをコンパイルして実行したりもできます.また,式展開機能もあるので,気軽に使えるプラグインでもあります.現在存在している中では,おそらく最も気持ち悪い部類のmikutterプラグインの1つでしょう.

インストール方法

下記のコマンドを実行するだけです.

mkdir -p ~/.mikutter/plugin
cd ~/.mikutter/plugin
git clone git://github.com/penguin2716/mikutter_shell_post.git

機能紹介

mikutterコマンドの実行

@systemに向けたリプライはmikutter内部で実行され,実行結果がシステムメッセージとしてタイムラインに流れてきます.

@system
10.times do |x|
  Plugin.call(:update, nil, [Message.new(:message => "count#{x}", :system => true)])
end
シェルコマンドの実行

@shellに向けたリプライはシェルコマンドとして認識され,シェルでの実行結果がシステムメッセージとしてタイムラインに流れてきます.

@shell uname -a

@shellに向けたコマンドには自動的にtimeout 10が先頭についているので,pingのようなコマンドをうっかり叩いてしまってもちゃんと終了します.これでは端末を開いたりするときに都合が悪くなるので,@shell_p宛のコマンドはtimeoutしないようになっています.

@shell_p urxvt
RubyPythonPerlの実行

@shell_rb,@shell_py,@shell_plに向けたリプライはそれぞれRubyPythonPerlのコマンドとして解釈されます.

@shell_rb
10.times do |x|
  puts "count #{x}"
end
@shell_py
for i in range(10):
  print "count%d" % i
@shell_pl
for ($i = 0; $i < 10; $i++) {
    print "count$i\n";
}
C,C++のコードの実行

@shell_cと@shell_cppにむけたリプライはC,C++のコードとして解釈され,コンパイル後に実行されます.

@shell_c
#include <stdio.h>
int main(int argc, char **argv)
{
	printf("Hello, world!\n");
	return 0;
}
@shell_cpp
#include <iostream>
using namespace std;
int main(int argc, char **argv)
{
	cout << "Hello, world!" << endl;
	return 0;
}
任意のスクリプト言語の実行
@script 実行コマンド
コード...

の形式ですると,コードがファイルに書かれ,実行コマンドで実行されます.gnuplotの例では以下のようになります.

@script gnuplot -p
v0 = 0
g = 9.8
set yrange [0:10]
set xrange [0:2]
set xlabel "time [sec]"
set ylabel "height [m]"
set title "Free Fall"
unset key
plot v0 * x + 0.5 * (-g) * x **2 + 10 linewidth 2


任意のコンパイル言語の実行
@compile [保存するファイル名] [実行時のコマンド] コンパイルコマンド
ソース...

の形式で投稿すると,ソースがファイルに書かれてコンパイルされ,実行されます.実行結果はmikutterのシステムメッセージとしてタイムラインに流れてきます.

@compile [src.cpp] [timeout 10 ./a.out] g++ -O3 -march=native
#include <iostream>
using namespace std;
int main(void)
{
  cout << "Hello, world!" << endl;
  return 0;
}

ちなみに,コンパイルに失敗すると次のようなシステムメッセージがタイムラインに流れます.

src.cpp: In function ‘int main()’:
src.cpp:7:1: error: expected ‘;’ before ‘}’ token

JavaGUIを作ることもできます.

@compile [Hello.java] [java Hello] javac
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
class Hello {
  public static void main(String args[]) {
    JFrame frame = new JFrame("Java on mikutter");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setLayout(new BorderLayout());
    frame.setSize(250,100);
    JButton button = new JButton("Exit");
    button.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        System.exit(0);
      }
    });
    frame.add(new JLabel("Java on mikutter"), BorderLayout.NORTH);
    JPanel panel = new JPanel();
    panel.setLayout(new FlowLayout(FlowLayout.CENTER, 20, 20));
    panel.add(button);
    frame.add(panel, BorderLayout.CENTER);
    frame.setVisible(true);
 }
}


Google検索とか

ブラウザとの連携もできます.Googleでmikutterを検索するには

@google mikutter

東京から京都までの電車を調べるには

@maps 東京から京都

特定のURLを開くには

@openurl http://bit.ly/teokure

のように入力してすると,ブラウザで目的のページが表示されます.

式展開機能

Rubyでは,文字列中等で#{...}のような括弧の中に式を記述すると,内部の式が先に評価されます.シェルスクリプトでいうバッククォートと似ていますね.このプラグインをインストールすると,式展開機能が使えるようになります.

次のように入力してしてみます.

2 ** 10 = #{2 ** 10}
2 ** 20 = #{2 ** 20}
2 ** 30 = #{2 ** 30}
2 ** 40 = #{2 ** 40}
2 ** 50 = #{2 ** 50}

すると,#{2 ** 10}が展開され,次のようになります.

2 ** 10 = 1024
2 ** 20 = 1048576
2 ** 30 = 1073741824
2 ** 40 = 1099511627776
2 ** 50 = 1125899906842624

mikutterにEmacsっぽい機能を提供するプラグイン: gtk_emacslike_textview

gtk_emacslike_textviewとは

mikutterの投稿ボックスを強化する,Emacsユーザのためのmikutterプラグインです.投稿ボックスでツイートを編集する際に思わずを叩いて全選択しちゃったりしたEmacsユーザも多いことと思います.そこで生まれたのがこのプラグインです.このプラグインではEmacsっぽいキーバインドとsnippet機能,mikutter_shell_postとの連携を念頭においたキーワードハイライト機能を提供します.キーワードハイライトでは,特定の言語に合わせたキーワードハイライトが利用できます.ちなみに,snippet機能とキーワードハイライトはmikutter_shell_postとの同時利用のために実装されたものです.そのままでも利用できますが,mikutter_shell_postを同時に利用するとその真価が発揮されるでしょう.

インストール方法

mikutterのバージョンが0.2.1.1119以上である必要があります.それ以前のバージョンでは動作しません.また,Gtk::SourceViewが必要になります.以下のコマンドでインストールして下さい.

gem install gtksourceview2

git_emacslike_textviewのインストールは以下のコマンドで行ってください.コマンドを実行したら,mikutterを再起動すれば投稿ボックスがEmacsっぽくなっていると思います.

mkdir -p ~/.mikutter/plugin
cd ~/.mikutter/plugin
git clone git://github.com/penguin2716/gtk_emacslike_textview.git

機能紹介

Emacsっぽいキーバインド

次のコマンドが利用できます.キーバインドがユーザ定義となっているものはmikutterの設定画面から設定して下さい.

キーバインド 動作内容
C-[fbnpae] 文字単位のカーソルの移動
C-[dh] 文字の削除
C-SPC 選択のトグル
C-[/z] 戻る
C-w 選択領域のカット
C-k 行末までカット
C-y カーソル位置に貼り付け
C-g 選択のトグルをOFF
C-A C-aの全選択がなくなったのでC-Aで全選択にしてみた
M-[fb] 単語単位でのカーソル移動
M-[ae] バッファの先頭/末尾に移動
M-w 選択領域のコピー
M-[dh] 単語単位の削除
M-[np] グローバルスタックの読み出し
ユーザ定義 ハイライトする言語の変更(下記参照)
ユーザ定義 snippetを展開(下記参照)

以下では説明のため,「ハイライトする言語の変更」をに,「snippetを展開」をに割り付けたと仮定します.

キーワードハイライト

投稿ボックスに次の内容を入力し,するとRubyのキーワードをハイライトするモードになります.

@@ruby

同様に,次の内容でするとJavaのキーワードをハイライトするモードになります.

@@java

ハイライトしないモードに戻す場合は,次のように存在しない言語を@@に続けて入力し,します.

@@nil


snippet

snippet機能は,特定のキーワードのあとにコマンドを呼ぶと,そのキーワードがあらかじめ定義された内容に展開される機能です.試しに,以下の内容を投稿ボックスに入力してをタイプします.

chello

すると,以下のような内容が展開されます.

#include <stdio.h>
int main(int argc, char **argv)
{
	printf("Hello, world!\n");
	return 0;
}

このように,自分でsnippetを追加すれば,簡単に文章を作れるようになります.

snippetはsnippetsディレクトリ以下に保存されており,ユーザによる追加が可能です.新しくsnippetを登録する場合は,キーワードをファイル名とし,展開したい内容をファイルに記述して下さい.その際,ファイルの中に'$0'という文字列が含まれている場合は,展開後にその位置にカーソルが挿入されます.ファイルを追加したら,mikutterを再起動すると新しいsnippetが読み込まれます.

設定画面

今のところ,設定画面では色の設定ができます.140文字を超過した場合に背景色を変更すれば,文字数超過でつぶやけないことがわかります.

CloudStackで研究室内プライベートクラウドを作ってみた

この記事は CloudStack Advent Calendar jp:2012 のために書かれたものです.

研究室内にプライベートクラウドをつくる意味

研究を行う上で,サーバ環境を構築するのは面倒で悩ましいことです.
研究室内にプライベートクラウドを構築することで,研究が劇的に加速します.
以下ではプライベートクラウドを構築することによる利点をいくつか紹介します.

究極の使い捨て環境として

「研究に失敗はつきものです.失敗したら捨てればいいんです」
ある日,僕は研究室内Wikiを作ろうと思いましたが,どのWikiがいいのかわかりません.
結局いくつかインストールしてみましたが,気に入らないものもありました.
アンインストールするにも,綺麗な環境に戻るかどうかはわかりません.
また,インストールログを取ろうとした場合には,新しい環境でもう一度作業が必要になります.
またOSのインストールに手戻りするのは面倒なことこの上ありません.
そこで,テンプレートから新しい仮想マシンを作れば,再インストールの手間なしに15秒で環境が用意できます.
失敗したり不要になったりしたら,気軽に捨てて新しいのを用意すれば良いのです.

複数のサーバをCloudStackでまとめる

「1つの機能しか提供していないサーバが複数ありませんか?」
サーバを利用する際に,単純な機能しか提供しないなら,それは消費電力の無駄です.
サーバが待機していても電気は延々と使っているのですから,それが削減できればどれだけ省電力なことでしょう.

研究用のサーバが瞬時に用意できる

「大量のマシンが必要だけど用意が面倒?テンプレートを使えば一瞬ですね」
複数のマシンを使った並列計算の実験環境を用意するのは骨が折れるもの.インストールも面倒です.
そんなとき,1台だけ共通の設定を行い,テンプレートを作りましょう.
必要な分だけ仮想マシンを使えば量産は瞬時に完了します.

Linuxやその他のOSを気軽に利用できる

Linuxとか使ってみたいけど自分のマシンにはインストールしたくない?」
仮想マシンを利用すれば自分の環境は汚さなくても良いのです.
VirtualBoxVMwareさえもインストールの必要はありません.
お気に入りのブラウザを開いてCloudStackにアクセスするだけで,綺麗なWebUIから気軽にアクセスできますね.

構成

研究室内プライベートクラウドを構築した際の環境を示します.
下記の構成で現在も絶賛安定稼働中です.
CloudStackのバージョンは4.0を使用しました.

機器構成
  • 管理サーバ:ThinkPadEdge E430 を使用
  • コンピューティングノード(自作マシン3台)
  • プライマリ/セカンダリストレージ(1台構成)
    • CPU:Intel Core i7 3770 @ 3.40GHz
    • RAM:DDR3 8GB * 4枚
    • HDD:2TB * 4台 (RAID10構成)
    • OS:UbuntuServer 12.04 LTS
ネットワーク構成

研究室毎にクラスCのIPアドレス空間が割り当てられており,1つの研究室あたり255個のIPアドレスしかありません.
そのうちのほとんどは既に利用・予約されているため,新たにクラスBのLANを構築しました.
既存LAN内のIPアドレス消費数は1ですが,65000以上のIPアドレスが使えるようになりました.

ストレージ

現在はプライマリストレージ,セカンダリストレージ共にNFSで構築し,利用しています.
環境構築当初はコンピューティングノードをricciとluciでクラスタリングして,
NASからiscsiで提供されているディスクをGFS2でフォーマットしてプライマリストレージとして利用していました.
しかし,構成方法が悪かったのか安定動作しません.
多いときには3日に1回くらい障害対応をするハメになってしまいました.

ハイパーバイザ

ハイパーバイザにはKVMを使用しています.
XenServerでも良かったのですが,ライセンスの取得に使うXenCenterを動かすWindows環境を用意するのが億劫だったので・・・.

利用用途と利用傾向

現在,研究室の15-20名程度がCloudStackのアカウントを作成し,仮想マシンを起動しています.
以下では,多く見られる利用用途と利用傾向について紹介します.

VNCでデスクトップを表示して利用している人が多い

VNCがすぐに利用できるよう設定済みのUbuntuテンプレートをこちらで用意しました.
普段はWindowsMacを利用している人が多いため,CUI画面には抵抗があるようです.
うちの研究室にはMac利用者が多いため,VNCが標準で利用できます.
WebUIからアクセスするよりも直接VNCでアクセスしたほうが快適に利用できるため,
GUIを使いたい場合はWebUIからのアクセスよりもVNCでのアクセスをおすすめしています.

膨大な実験データが出力される人の解析用

研究によっては膨大な実験データを解析する必要があります.
そんな人は,複数の仮想マシンを用意して,そこにデータを分割して投げています.
解析中に自分のマシンが使えない,あるいはCPU使用率が専有されることがなくなるため,
データの解析中でも他のタスクにマシンを使えます.

ファイルサーバとして

実験データを保存する用途に限らず,ファイルサーバは便利なものです.
USBメモリにデータを保存するよりも,仮想マシン上のファイルサーバに保存したほうが楽ではないでしょうか.
あとはネットワーク経由でアクセスできるため,USBの上下を間違えてイライラすることもありません.
また,自分のマシンはRAID構成にしていなくても,仮想マシンRAID構成になっているので安心です.
ファイルサーバとして利用している人は100GBから200GBくらいストレージを確保する傾向があるようなので,
ファイルサーバ用途が多く出そうな環境では,ストレージ容量はある程度多めに見積もった方が良いでしょう.

MPIやHadoopを使ったプログラムのテスト

OpenMPIを使った並列計算環境を物理マシンで用意しようとすると,
マシンの確保だけでなく,OSのインストールもしんどいものです.
テスト環境をつくるだけなら,テンプレートを使って並列計算の実験環境が瞬時に構築できます.
早急に実験環境を用意しなければならなかった人がいたのですが,
テンプレートを活用することで,実験環境の構築が一瞬で終わりました.

今後の展望:究極のシンクライアント環境

ライブマイグレーションVNCやX11Forwarding等のデスクトップ転送技術を組み合わせれば,
どこにいても高速アクセス可能な究極のシンクライアント環境が実現できます.
必要な要素は以下の通りです.

クラウド上の仮想マシンにメイン環境を用意する

普段利用するPC環境をクラウド上の仮想マシンに用意します.
もうディスクが壊れる心配をしなくて良いだけでなく,世界中どこからでもアクセスできるようになります.
認証方法を工夫すれば,IC社員証Felicaポートに載せるだけで
瞬時に自分の環境にログインできるようなシステムが構築できます.
いつでも,どこでも,アクセスできるユビキタスクラウド環境が目標です.

シンクライアントを各地に点在させる

おしゃれなカフェにsshとXがインストールされたシンクライアントを用意しましょう.
そのマシンから自分の仮想マシン環境にアクセスすれば,いつでも自分の環境が利用できます.
重いラップトップを持ち歩くのは嫌なので,カード1枚でアクセスしたいところですね.

位置情報に応じた仮想マシンのライブマイグレーション

ネットワーク遅延を最小化するため,全国各地にデータセンタを用意します.
この際,利用者が持っているスマートフォンで位置情報を取得し,
その位置をもとに仮想マシンを適切なデータセンタにマイグレーションさせます.
理想的には,シンクライアントが置いてある場所にハイパーバイザがあって,
自動的に仮想マシンマイグレーションさせれば,いつでもLAN内アクセスが可能になります.

おわりに

僕が研究室で構築したプライベートクラウド環境の概要と,
今後実現できそうな(?)クラウドの利用用途を紹介しました.
クラウド環境を利用することによるメリットはかなり大きいと思うので,
様々な場面で利用できるようなユースケースがあると良いですね.

!追記!

コミックマーケット83では「俺のクラウドがこんなに安いわけがない」というタイトルで薄い本を販売します!
研究室内のメンバー数人で記事を寄せ集めたものです.
すべての記事がクラウドに関連しているわけではありませんが,楽しんで読んで頂けるのではないかと思います.
予価500円です!

      • -

期日:12月31日(月)
会場:東京ビッグサイト
サークル名:らぼちっく;げーと
出展位置:東2ホール Y-17a
URL:http://www.laboticgate.net/

      • -

にて販売しておりますので,ぜひお立ち寄り下さい.
お待ちしております!