k-holyのPHPとか諸々メモ

Webで働くk-holyがPHP(スクリプト言語)とか諸々のことをメモしていきます。ソースコードはだいたいWindowsで動かしてます。

Windows7にVirtualBoxとVagrantをインストールしたメモ

開発環境に便利と話題のVirtualBoxとVagrantを、ようやくインストールしてみたメモです。

こちらの記事を参考にしました。

VirtualBoxを入れる

Downloads ? Oracle VM VirtualBox から VirtualBox 4.2.16 for Windows hosts x86/amd64 をダウンロードして実行。

Setup Wizardが起動するので Next Next。なんかOracle製のドライバ類が色々インストールされるけど気にしない。

Vagrantを入れる

Vagrant - Downloads から 最新版 v1.2.7 のページに飛び Vagrant_1.2.7.msi をダウンロードして実行。

Setup Wizardが起動するので Next Next。インストールが終わると「再起動しろ」と言われるので、再起動します。

Vagrant Boxファイルを入れる

A list of base boxes for Vagrant - Vagrantbox.es から、使いたい仮想サーバのイメージファイルを選択して、Vagrantに追加します。

今回は使い慣れた CentOSの最新版っぽい CentOS 6.4 x86_64 Minimal (VirtualBox Guest Additions 4.2.12, Chef 11.4.4, Puppet 3.1.1) を選択。

以下、コマンドはMSYSのコンソール、ホームディレクトリにて実行。

$ vagrant box add centos64_64 http://developer.nrel.gov/downloads/vagrant-boxes/CentOS-6.4-x86_64-v20130427.box
Downloading or copying the box...
Successfully added box 'centos64_64' with provider 'virtualbox'!

$ vagrant init centos64_64
A `Vagrantfile` has been placed in this directory. You are now
ready to `vagrant up` your first virtual environment! Please read
the comments in the Vagrantfile as well as documentation on
`vagrantup.com` for more information on using Vagrant.

ここまでで起動準備ができたようです。

※追記注

vagrant box add を自宅でやったら回線のせいかとんでもなく時間がかかったので、可能であれば別途入手してローカルから追加した方がいいと思います。 (パス部分はローカルでも大丈夫なので)

vagrant init コマンドを実行した場所に設定ファイルVagrantfileが作成されますので、プロジェクト毎に仮想サーバを変えたい場合やバージョン管理したい場合は対象のディレクトリに移動して実行すればOKです。

Vagrant を起動してみる

$ vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
[default] Importing base box 'centos64_64'...
[default] Matching MAC address for NAT networking...
[default] Setting the name of the VM...
[default] Clearing any previously set forwarded ports...
[default] Creating shared folders metadata...
[default] Clearing any previously set network interfaces...
[default] Preparing network interfaces based on configuration...
[default] Forwarding ports...
[default] -- 22 => 2222 (adapter 1)
[default] Booting VM...
[default] Waiting for VM to boot. This can take a few minutes.
[default] Forcing shutdown of VM...
[default] Destroying VM and associated drives...
c:/Applications/HashiCorp/Vagrant/embedded/gems/gems/net-ssh-2.6.8/lib/net/ssh/authentication/agent/socket.rb:81:in `neg
otiate!': unknown response from agent: 1, "\x00\x00\x00-\x01\x00\x00\x00(SSH-2.0-Ruby/Net::SSH_2.6.8 i386-mingw32" (Net:
:SSH::Authentication::AgentError)

なんか怒られました。 検索してみたところ、どうもWindowsでPuTTYのAgentを起動しているとこうなるみたいです。

AgentError, unknown response from agent on Windows with 64-bit PuTTY ・ Issue #1455 ・ mitchellh/vagrant

一旦Pageantを終了してもう一度…(普段の開発にはPageantないと不便なんだけどなぁ…)

$ vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
[default] Importing base box 'centos64_64'...
[default] Matching MAC address for NAT networking...
[default] Setting the name of the VM...
[default] Clearing any previously set forwarded ports...
[default] Creating shared folders metadata...
[default] Clearing any previously set network interfaces...
[default] Preparing network interfaces based on configuration...
[default] Forwarding ports...
[default] -- 22 => 2222 (adapter 1)
[default] Booting VM...
[default] Waiting for VM to boot. This can take a few minutes.
[default] VM booted and ready for use!
[default] Mounting shared folders...
[default] -- /vagrant

※追記注

vagrant upコマンド実行時に作成される .vagrant/machines/default/virtualbox/id ファイルにはVirtualBox VMのIDが書き込まれていて、このIDがホームディレクトリに作成される .VirtualBox/VirtualBox.xml で参照先の .vbox ファイルに関連付けられています。

VirtualBox単体の利用経験はないのですが、VirtualBox.xml内のコメントを見たところ、このファイルは本来は VBoxManage.exe や VirtualBox Manager GUI を通じて作成されるもので、vagrantコマンドがそれを代行してくれてるみたいです。

※追記その2 vagrant upの途中で固まってVMが起動しない場合

vagrant upコマンド実行後、"Waiting for VM to boot. This can take a few minutes."で固まってしまう場合は、Vagrantfileで以下の設定を有効にして、VirtualBoxGUIモードで起動してみてください。

config.vm.provider :virtualbox do |vb|
  vb.gui = true
end

GUI上に「仮想化支援機構(VT-x/AMD-V)を有効化できません」というダイアログが出てVMが起動していない場合、BIOSで機能が無効にされているかもしれません。

自宅のWindows8 VAIO(SVE14A2AJ)のBIOS初期設定で "Intel(R) Virtualization Technology" がDisabledに設定されており、この問題に当たりました。

仮想サーバに接続してみる

仮想サーバが起動したようなので、接続してみます。

$ vagrant ssh
Welcome to your Vagrant-built virtual machine.
[vagrant@localhost ~]$

何もすることなく、あっさり入れてしまいました。

[vagrant@localhost ~]$ sudo vi /etc/sysconfig/network
E437: terminal capability "cm" required
Press ENTER or type command to continue"/etc/sysconfig/network"

しかし、何かファイルを編集しようとするとエラーが発生し、画面表示がおかしくなってしまいます。

参考サイト Window 7 でVagrantでCent OS 6.3入れてみた - 僕の車輪の再発明 によればPuTTY推奨とのことなので、PuTTYに変えてみました。

ホスト vagrant@127.0.0.1 ポート 2222

パスワードを聞かれますが、"vagrant"で通りました。

[vagrant@localhost ~]$ sudo vi /etc/sysconfig/network
NETWORKING=yes
HOSTNAME=localhost.localdomain

PuTTYの場合は編集もばっちりです。

PuTTYで入ってる時にVMを停止したらどうなるかな。

$ vagrant halt
[default] Attempting graceful shutdown of VM...

[vagrant@localhost ~]$
Broadcast message from vagrant@localhost.localdomain
...
The system is going down for halt NOW!

普通に落とされました(笑)

一度起動すれば普通のサーバと同じなので、ユーザー作成して公開鍵設置してsshdの設定を適宜すれば、Pageantで公開鍵認証も普通にできると思います。

(Vagrantの起動時にPageant下げなきゃいけないのが面倒ですが…)

saharaプラグインを入れてみる

Vagrantのサンドボックスモードでコミットとロールバックを実現するというsaharaプラグインを入れてみます。

$ vagrant plugin install sahara
Installing the 'sahara' plugin. This can take a few minutes...
Installed the plugin 'sahara (0.0.15)'!

$ vagrant sandbox on
0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%

$ vagrant up

PuTTYで入って適当にディレクトリ作成してみます。

[vagrant@localhost ~]$ mkdir .bin
[vagrant@localhost ~]$ ls -lsa
total 32
4 drwx------  4 vagrant vagrant 4096 Aug 30 06:03 .
4 drwxr-xr-x. 4 root    root    4096 Apr 27 10:05 ..
4 -rw-------  1 vagrant vagrant  290 Aug 30 05:44 .bash_history
4 -rw-r--r--  1 vagrant vagrant   18 Feb 21  2013 .bash_logout
4 -rw-r--r--  1 vagrant vagrant  176 Feb 21  2013 .bash_profile
4 -rw-r--r--  1 vagrant vagrant  124 Feb 21  2013 .bashrc
4 drwxrwxr-x  2 vagrant vagrant 4096 Aug 30 06:03 .bin
4 drwx------  2 vagrant root    4096 Apr 27 10:05 .ssh

サンドボックスをコミット(結構時間かかります)

$ vagrant sandbox commit
0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%

ディレクトリを削除してみます。

[vagrant@localhost ~]$ rm -rf .bin
[vagrant@localhost ~]$ ls -lsa
total 28
4 drwx------  3 vagrant vagrant 4096 Aug 30 06:08 .
4 drwxr-xr-x. 4 root    root    4096 Apr 27 10:05 ..
4 -rw-------  1 vagrant vagrant  290 Aug 30 05:44 .bash_history
4 -rw-r--r--  1 vagrant vagrant   18 Feb 21  2013 .bash_logout
4 -rw-r--r--  1 vagrant vagrant  176 Feb 21  2013 .bash_profile
4 -rw-r--r--  1 vagrant vagrant  124 Feb 21  2013 .bashrc
4 drwx------  2 vagrant root    4096 Apr 27 10:05 .ssh

一度ログアウトして、サンドボックスロールバック

$ vagrant sandbox rollback
0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%

ログインし直して確認してみる。

[vagrant@localhost ~]$ ls -lsap
total 32
4 drwx------  4 vagrant vagrant 4096 Aug 30 06:03 ./
4 drwxr-xr-x. 4 root    root    4096 Apr 27 10:05 ../
4 -rw-------  1 vagrant vagrant  290 Aug 30 05:44 .bash_history
4 -rw-r--r--  1 vagrant vagrant   18 Feb 21  2013 .bash_logout
4 -rw-r--r--  1 vagrant vagrant  176 Feb 21  2013 .bash_profile
4 -rw-r--r--  1 vagrant vagrant  124 Feb 21  2013 .bashrc
4 drwxrwxr-x  2 vagrant vagrant 4096 Aug 30 06:03 .bin/
4 drwx------  2 vagrant root    4096 Apr 27 10:05 .ssh/

復活してます。よしよし

vagrant-vbox-snapshotプラグインを入れてみる(→削除)

VirtualBoxのスナップショット機能をvagrantコマンドで使えるようにするという、超便利なプラグインを入れてみます。

$ vagrant plugin install vagrant-vbox-snapshot
Installing the 'vagrant-vbox-snapshot' plugin. This can take a few minutes...
Installed the plugin 'vagrant-vbox-snapshot (0.0.2)'!
$ vagrant snapshot take centos64_64_1
$ vagrant snapshot list

参考記事の通り、本当にうんともすんとも言わない…Windowsで動かないのかな。それにしてもエラーメッセージひとつ返さないのってどうなの…。

よく分からないので、なかったことにします。

$ vagrant plugin uninstall vagrant-vbox-snapshot
$ vagrant plugin list
sahara (0.0.15)

実際、環境構築の自動化までいくと、そこまでの機能は使わないんじゃないかという気もしますし。

検索しても便利だ便利だという声しか聞こえてこないので、Windowsユーザーのためにここに書き残しておきます。

IPアドレスを変更してみる

IPアドレスがこのままの 127.0.0.1 だと競合して使えないので一旦終了、Vagrantfile設定ファイルを変更してみます。

ホームディレクトリに作成されていた初期状態の Vagrantfile は、以下の内容でした。

Rubyで書かれた設定ファイルのようです。

# -*- mode: ruby -*-
# vi: set ft=ruby :

# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  # All Vagrant configuration is done here. The most common configuration
  # options are documented and commented below. For a complete reference,
  # please see the online documentation at vagrantup.com.

  # Every Vagrant virtual environment requires a box to build off of.
  config.vm.box = "centos64_64"

  # The url from where the 'config.vm.box' box will be fetched if it
  # doesn't already exist on the user's system.
  # config.vm.box_url = "http://domain.com/path/to/above.box"

  # Create a forwarded port mapping which allows access to a specific port
  # within the machine from a port on the host machine. In the example below,
  # accessing "localhost:8080" will access port 80 on the guest machine.
  # config.vm.network :forwarded_port, guest: 80, host: 8080

  # Create a private network, which allows host-only access to the machine
  # using a specific IP.
  # config.vm.network :private_network, ip: "192.168.33.10"

  # Create a public network, which generally matched to bridged network.
  # Bridged networks make the machine appear as another physical device on
  # your network.
  # config.vm.network :public_network

  # If true, then any SSH connections made will enable agent forwarding.
  # Default value: false
  # config.ssh.forward_agent = true

  # Share an additional folder to the guest VM. The first argument is
  # the path on the host to the actual folder. The second argument is
  # the path on the guest to mount the folder. And the optional third
  # argument is a set of non-required options.
  # config.vm.synced_folder "../data", "/vagrant_data"

  # Provider-specific configuration so you can fine-tune various
  # backing providers for Vagrant. These expose provider-specific options.
  # Example for VirtualBox:
  #
  # config.vm.provider :virtualbox do |vb|
  #   # Don't boot with headless mode
  #   vb.gui = true
  #
  #   # Use VBoxManage to customize the VM. For example to change memory:
  #   vb.customize ["modifyvm", :id, "--memory", "1024"]
  # end
  #
  # View the documentation for the provider you're using for more
  # information on available options.

  # Enable provisioning with Puppet stand alone.  Puppet manifests
  # are contained in a directory path relative to this Vagrantfile.
  # You will need to create the manifests directory and a manifest in
  # the file centos64_64.pp in the manifests_path directory.
  #
  # An example Puppet manifest to provision the message of the day:
  #
  # # group { "puppet":
  # #   ensure => "present",
  # # }
  # #
  # # File { owner => 0, group => 0, mode => 0644 }
  # #
  # # file { '/etc/motd':
  # #   content => "Welcome to your Vagrant-built virtual machine!
  # #               Managed by Puppet.\n"
  # # }
  #
  # config.vm.provision :puppet do |puppet|
  #   puppet.manifests_path = "manifests"
  #   puppet.manifest_file  = "init.pp"
  # end

  # Enable provisioning with chef solo, specifying a cookbooks path, roles
  # path, and data_bags path (all relative to this Vagrantfile), and adding
  # some recipes and/or roles.
  #
  # config.vm.provision :chef_solo do |chef|
  #   chef.cookbooks_path = "../my-recipes/cookbooks"
  #   chef.roles_path = "../my-recipes/roles"
  #   chef.data_bags_path = "../my-recipes/data_bags"
  #   chef.add_recipe "mysql"
  #   chef.add_role "web"
  #
  #   # You may also specify custom JSON attributes:
  #   chef.json = { :mysql_password => "foo" }
  # end

  # Enable provisioning with chef server, specifying the chef server URL,
  # and the path to the validation key (relative to this Vagrantfile).
  #
  # The Opscode Platform uses HTTPS. Substitute your organization for
  # ORGNAME in the URL and validation key.
  #
  # If you have your own Chef Server, use the appropriate URL, which may be
  # HTTP instead of HTTPS depending on your configuration. Also change the
  # validation key to validation.pem.
  #
  # config.vm.provision :chef_client do |chef|
  #   chef.chef_server_url = "https://api.opscode.com/organizations/ORGNAME"
  #   chef.validation_key_path = "ORGNAME-validator.pem"
  # end
  #
  # If you're using the Opscode platform, your validator client is
  # ORGNAME-validator, replacing ORGNAME with your organization name.
  #
  # If you have your own Chef Server, the default validation client name is
  # chef-validator, unless you changed the configuration.
  #
  #   chef.validation_client_name = "ORGNAME-validator"
end

めっちゃ英語 (((;゚Д゚)))

でも、ここだけに注目すれば何とかなりそうです。

# Create a private network, which allows host-only access to the machine
# using a specific IP.
# config.vm.network :private_network, ip: "192.168.33.10"

公式ドキュメント Networking - Public Network - Vagrant Documentation

要は、ホストOSから見たIPアドレスをここで設定できるようです。

Webで検索すると :hostonly:bridged といった、旧バージョンの設定ファイルの書き方がたくさんヒットするので要注意です。

上記設定のコメントを削除して、vagrant reload で再起動します。(Pageantの常駐解除を忘れずに…)

Apache + PHP入れてみる

yumでガシガシ入れます。とりあえずの確認なので最小限のパッケージで、細かい設定はパス。

$ sudo yum -y install httpd
Installed:
  httpd.x86_64 0:2.2.15-29.el6.centos

Dependency Installed:
  apr.x86_64 0:1.3.9-5.el6_2                 apr-util.x86_64 0:1.3.9-3.el6_0.1  apr-util-ldap.x86_64 0:1.3.9-3.el6_0.1
  httpd-tools.x86_64 0:2.2.15-29.el6.centos  mailcap.noarch 0:2.1.31-2.el6

Complete!

$ sudo yum -y install php 
Installed:
  php.x86_64 0:5.3.3-23.el6_4

Dependency Installed:
  php-cli.x86_64 0:5.3.3-23.el6_4                           php-common.x86_64 0:5.3.3-23.el6_4

Complete!

$ sudo chkconfig httpd on
$ sudo service httpd start

iptablesが初期設定ではポート22のみ通す設定になってるので、80も通すように。

$ sudo vi /etc/sysconfig/iptables
# Firewall configuration written by system-config-firewall
# Manual customization of this file is not recommended.
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT
-A INPUT -j REJECT --reject-with icmp-host-prohibited
-A FORWARD -j REJECT --reject-with icmp-host-prohibited
COMMIT

$ sudo service iptables restart

phpinfo()を実行するスクリプトを設置します。

$ sudo vi /var/www/html/index.php
<?php phpinfo();

ブラウザで http://192.168.33.10/ を開くと…

http://cdn.mogile.archive.st-hatena.com/v1/image/k-holy/297783820098874484.png

キタワァ・゜゚・:.。..。.:*・゜(n'∀')η゚・

とりあえずここまでで、一旦ロールバックします。

$ vagrant sandbox rollback
0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%

終了する時はVagrantの停止を忘れずに。

$ vagrant halt
[default] Attempting graceful shutdown of VM...

$ vagrant status
Current machine states:

default                   poweroff (virtualbox)

The VM is powered off. To restart the VM, simply run `vagrant up`

今後のための参考

ホストOSとのディレクトリ共有設定とか、Vagrantfileはプロジェクト毎にバージョン管理せよとか、参考になる内容です。

環境設定の自動化は、よく名前を見るChefはなんか準備がめんどくさそう、SoloとかKnifeとか色々あってよう分からんし、私のような弱小PHPerからするとRubyっていうだけでもう…。 (((;゚Д゚)))

何より、すでにFabric使っててPythonの環境があるので、最近ちょくちょく見るAnsibleの方を試してみようかと…思ったのですが…。

For the central Ansible machine, you will need an environment with Python 2.6 or greater installed. If you are running Python 2.5 on an “Enterprise Linux 5” variant, we’ll show you how to add 2.6 to your distribution, but most platforms already have a new enough Python. (Note that Windows is not supported as the Ansible control machine.)

…。

Windows is not supported as the Ansible control machine.

。・゚・(ノД`)・゚・。

[追記]

参考記事と同じBlogですが、Windows環境でのAnsibleに挑戦されてました。

Cygwinで挑戦されてる方も。

その他参考になりそうな記事

ChocolateyというWindows版apt-getのようなパッケージ管理システムを使ってインストールされた方

VagrantからのSSHの接続情報を確認する方法とか

Vagrantは色々めんどくさいことを自動で(デフォルト設定で)やってくれてるようなので、先へ進む前に公式ドキュメントも目的に合わせて読んだ方が良さそうですね…。

Fabricタスクをモジュール分割して楽々composer更新

Windows7 (MinGW / MSYS) で Fabric シリーズ記事です。

今回は(今回も?)Fabricの使い方としてはPHP無関係ですが、タスクの内容が composer コマンドなのでPHPer向けといえばPHPer向けです。

fabfile.pyをパッケージにしてタスクをモジュール分割

公式ドキュメント Fabfile construction and use — Fabric 1.6 documentation に書かれている通り、 Fabricのfabコマンド用タスクは単一のモジュール fabfile.py に定義する方法の他に、fabfile ディレクトリを作成して __init__.py を置くことでパッケージ化し、モジュール分割する方法があります。

今回は composer のインストールと、composerでインストールしたライブラリの更新を題材に、Fabricタスクをモジュール分割してみます。

~/fabfile.py

from fabric.api import env, run, local, put, cd
from fabric.contrib.files import exists

def gehirn():
    env.hosts = ['133.242.2.104']
    env.port = 22
    env.user = 'kholy'
    env.key_filename = '~/.ssh/id_rsa'
    env.password = 'passphrase-for-key'

def develop():
    env.hosts = ['192.168.1.10']
    env.port = 22
    env.user = 'develop'
    env.key_filename = '~/.ssh/develop.id_rsa'
    env.password = 'passphrase-for-key'

def composer_setup():
    if not exists('bin'):
        run('mkdir bin')
    with cd('bin'):
        run('curl -sS https://getcomposer.org/installer | php')
        run('mv composer.phar composer')

def composer_update(path=""):
    if path != "":
        cd(path)
    run('composer self-update')
    run('composer update')

gehirn と develop は接続先を指定するためのタスクです。

composer_setup で composer.phar をインストールして PATHが通されている .bin/composer に移動し、composer_update:path で指定された composer.json の設置先で composer update するという内容です。

この例ではまだ数は少ないですが、このような他のタスクの実行結果を前提としたタスクが増えていくと、タスク間の依存性を把握するのが大変になるので、モジュール分割した方が良いと思います。

fabfile ディレクトリを作成して、fabfile.py をそのまま fabfile/__init__.py に移動します。

fab --list でタスク一覧を表示すると、こうなります。

(dev)[k_horii@horii ~]$ fab --list
Available commands:

    composer_setup
    composer_update
    develop
    gehirn

fabfile.py の時と同じです。

このままでは何も変わっていないので、composer用のタスクを別のモジュール composer.py に分割します。

~/fabfile/composer.py

from fabric.api import run, cd
from fabric.contrib.files import exists

def setup():
    if not exists('bin'):
        run('mkdir bin')
    with cd('bin'):
        run('curl -sS https://getcomposer.org/installer | php')
        run('mv composer.phar composer')

def update(path=""):
    if path != "":
        cd(path)
    run('composer self-update')
    run('composer update')

分割したタスクを __init__.py から削除します。

~/fabfile/__init__.py

from fabric.api import env, run, local, put, cd
from fabric.contrib.files import exists

def gehirn():
    env.hosts = ['133.242.2.104']
    env.port = 22
    env.user = 'kholy'
    env.key_filename = '~/.ssh/id_rsa'
    env.password = 'passphrase-for-key'

def develop():
    env.hosts = ['192.168.1.10']
    env.port = 22
    env.user = 'develop'
    env.key_filename = '~/.ssh/develop.id_rsa'
    env.password = 'passphrase-for-key'

fab --list でタスクの一覧を表示すると、こうなります。

(dev)[k_horii@horii ~]$ fab --list
Available commands:

    develop
    gehirn

モジュール分割したタスクを認識させるためには import 定義を追加して、fabric.decorators.task デコレータを利用します。

taskデコレータ(@task)は __init__.pycomposer.py の双方に定義します。

ついでに、確認用として which:command も使えるようにしました。

~/fabfile/__init__.py

from fabric.api import env, run, local, put, cd
from fabric.contrib.files import exists
from fabric.decorators import task
import composer

@task
def gehirn():
    env.hosts = ['133.242.2.104']
    env.port = 22
    env.user = 'kholy'
    env.key_filename = '~/.ssh/id_rsa'
    env.password = 'passphrase-for-key'

@task
def develop():
    env.hosts = ['192.168.1.10']
    env.port = 22
    env.user = 'develop'
    env.key_filename = '~/.ssh/develop.id_rsa'
    env.password = 'passphrase-for-key'

@task
def which(command):
    run('which %s' % (command))

~/fabfile/composer.py

from fabric.api import run, cd
from fabric.contrib.files import exists
from fabric.decorators import task

@task
def setup():
    if not exists('bin'):
        run('mkdir bin')
    with cd('bin'):
        run('curl -sS https://getcomposer.org/installer | php')
        run('mv composer.phar composer')

@task
def update(path=""):
    if path != "":
        cd(path)
    run('composer self-update')
    run('composer update')

fab --list で関数のリストを表示すると、こうなりました。

(dev)[k_horii@horii ~]$ fab --list
Available commands:

    develop
    gehirn
    composer.setup
    composer.update

モジュール分割することで、こんなコマンドで Gehirnサーバにcomposerをインストールできるようになりました。

※catコマンドにパイプしているのは、Windowsでエスケープシーケンスを有効にするためです。参考 Windowsでphpunit --colors - iakioの日記

(dev)[k_horii@horii ~]$ fab gehirn composer.setup|cat
[133.242.2.104] Executing task 'composer.setup'
[133.242.2.104] run: mkdir bin
[133.242.2.104] run: curl -sS https://getcomposer.org/installer | php
[133.242.2.104] out: #!/usr/bin/env php
[133.242.2.104] out: Some settings on your machine may cause stability issues with Composer.
[133.242.2.104] out: If you encounter issues, try to change the following:
[133.242.2.104] out:
[133.242.2.104] out: Your PHP (5.3.3-7+squeeze15) is quite old, upgrading to PHP 5.3.4 or higher is recommended.
[133.242.2.104] out: Composer works with 5.3.2+ for most people, but there might be edge case issues.
[133.242.2.104] out:
[133.242.2.104] out: Downloading...
[133.242.2.104] out:
[133.242.2.104] out: Composer successfully installed to: /home/kholy/bin/composer.phar
[133.242.2.104] out: Use it: php composer.phar
[133.242.2.104] out:

[133.242.2.104] run: mv composer.phar composer

Done.
Disconnecting from 133.242.2.104... done.

whichで確認してみます。

(dev)[k_horii@horii ~]$ fab gehirn which:composer
[133.242.2.104] Executing task 'which'
[133.242.2.104] run: which composer
[133.242.2.104] out: /home/kholy/bin/composer
[133.242.2.104] out:

確かに、先ほど ~/bin にインストールした composer コマンドが利用されています。

composer updateしてみます。

(dev)[k_horii@horii ~]$ fab gehirn composer.update|cat
[133.242.2.104] Executing task 'composer.update'
[133.242.2.104] run: composer self-update
[133.242.2.104] out: You are using the latest composer version.
[133.242.2.104] out:

[133.242.2.104] run: composer update
[133.242.2.104] out: Loading composer repositories with package information
[133.242.2.104] out: Updating dependencies (including require-dev)
[133.242.2.104] out:   - Updating volcanus/routing dev-master (4893f2d => 01a256a)
[133.242.2.104] out:     Checking out 01a256aa3e5068c4f074ec118eef4a07c6c76f10
[133.242.2.104] out:
[133.242.2.104] out:   - Updating pinoco/pinoco dev-master (0.7.1 => 0.7.2)
[133.242.2.104] out:     Checking out 0.7.2
[133.242.2.104] out:
[133.242.2.104] out:   - Updating swiftmailer/swiftmailer dev-master (28873a8 => e77eb35)
[133.242.2.104] out:     Checking out e77eb358a1aa7157afb922f33e2afc22f6a7bef2
[133.242.2.104] out:
[133.242.2.104] out:   - Updating symfony/http-foundation 2.2.x-dev (v2.2.1 => 17ba72b)
[133.242.2.104] out:     Checking out 17ba72b450676370cfb406f7675da3f7bb742cfb
[133.242.2.104] out:
[133.242.2.104] out:   - Updating symfony/http-kernel 2.2.x-dev (a1e8288 => e2e0ba4)
[133.242.2.104] out:     Checking out e2e0ba4d7943ee414c497629f402432533df5780
[133.242.2.104] out:
[133.242.2.104] out:   - Updating symfony/finder dev-master (e2f07c7 => 04b4a4a)
[133.242.2.104] out:     Checking out 04b4a4a823cb6f269efc91a2c6659173a7c9f864
[133.242.2.104] out:
[133.242.2.104] out:   - Updating silex/silex dev-master (80f0abd => 1cd7f3f)
[133.242.2.104] out:     Checking out 1cd7f3f050b78dae4458d93892355720f56659f9
[133.242.2.104] out:
[133.242.2.104] out: Writing lock file
[133.242.2.104] out: Generating autoload files
[133.242.2.104] out:


Done.
Disconnecting from 133.242.2.104... done.

併せてプロジェクト別ディレクトリへの composer.json のアップロードを自動化すれば、更に便利になりそうです。

バージョン管理システムで同期させるのが理想なのでしょうけど、少しずつでも日々の作業を自動化していけば、いずれは憧れの本番環境デプロイ自動化に到達できるかなと楽観視しています…。

以下の記事を参考にしました。

真性PHPerでも分かった?FabricでWindowsからファイルアップロード

Windows7 (MinGW / MSYS) で Fabric の続きです。

ファイルは作業ディレクトリに上げてからサーバ上でPHPスクリプトを実行すれば思いのままに…!とか考えてましたが、色々やるうちにまどろっこしく感じてしまい、結局Python/Fabricで何とかすることにしました。

同じことで迷わないためのメモです。環境は Windows7 / MSYS 1.0 / Python 2.7 / Fabric 1.6 です。

ローカルにカレントディレクトリ以下のhogeディレクトリがあればサーバ上にも作成し、ディレクトリ内の拡張子phpのファイルをアップロードして所有者apacheで実行できるように

なんだかえらく状況を限定してますが、そういうことをしたかったのです。

ローカルでのファイル/ディレクトリのパス操作には os.path モジュールが利用できます。

Fabricでサーバのファイル/ディレクトリ操作には fabric.contrib.files モジュールが利用できます。

fabfile.py

from fabric.api import env, run, local, put
from fabric.contrib.files import exists
import os, glob

env.hosts = ['192.168.4.130']
env.port = 22
env.user = 'k_horii'
env.key_filename = '~/.ssh/id_rsa'
env.password = 'hoge'

def test_copy_dir_files():
    local_dir = '%s\\hoge' % (os.path.dirname(__file__))
    server_dir = '~/hoge'
    if os.path.exists(local_dir):
        if not exists(server_dir):
            run('mkdir -p %s' % (server_dir))
        put('%s\\*.php' % (local_dir), server_dir, use_sudo=True, mode=0755)
        run('sudo chown -R apache: %s' % (server_dir))

上記の変数 local_path ですが、Windowsなので os.path に使えるディレクトリの区切り文字は '\' になるため、あのような記述にしています。詳しくは後ほど。

Fabricの put() などでは適宜読み替えてくれるようですが、os.path ではそうはいきませんでした。~/hoge も解釈してくれません。

put() してから chown していますが、ローカルでも所有者や権限が適切に設定されていれば put(local_path, remote_path, mirror_local_mode=True) でいいかもしれません。

chown を sudo('chown') ではなく run('sudo chown') でやってるのは、sudo() だと ~/hoge が /root/hoge になってしまうためです。

put() のオプション引数に use_sudo と mode があるのは便利ですが user とか owner のようなオプションが、更に贅沢を言えばディレクトリがなければ再帰的に作ってくれるオプションがあれば非常に便利なのですが…。

なお、ディレクトリ内ファイルの取得は当初は glob モジュールを使ってみましたが、マニュアルをよく読めば put() でワイルドカードと設置先のディレクトリを指定できました。

参考 Operations — Fabric 1.6 documentation

実行結果はこうなります。

(oppy)[k_horii@horii ~]$ fab test_copy_dir_files
[192.168.4.130] Executing task 'test_copy_dir_files'
[192.168.4.130] run: mkdir -p ~/hoge
[192.168.4.130] put: c:\Users\k_horii\hoge\a.php -> /home/k_horii/hoge/a.php
[192.168.4.130] put: c:\Users\k_horii\hoge\b.php -> /home/k_horii/hoge/b.php
[192.168.4.130] put: c:\Users\k_horii\hoge\c.php -> /home/k_horii/hoge/c.php
[192.168.4.130] run: sudo chown -R apache: ~/hoge

Done.
Disconnecting from 192.168.4.130... done.

サーバ側で確認

[k_horii@cent6h01 ~]$ ls -lap ~/hoge
total 20
drwxrwxr-x. 2 apache  apache  4096 Apr 11 18:11 ./
drwx------. 7 k_horii k_horii 4096 Apr 11 18:11 ../
-rwxr-xr-x. 1 apache  apache     6 Apr 11 18:11 a.php
-rwxr-xr-x. 1 apache  apache     6 Apr 11 18:11 b.php
-rwxr-xr-x. 1 apache  apache     6 Apr 11 18:11 c.php

ちゃんと所有者と権限が設定されています。

真性PHPerでも分かる os.path

os.path モジュールとPHP関数の比較をしてみます。

PHP   : __FILE__
Python: __file__

PHP   : dirname()
Python: os.path.dirname()

PHP   : basename()
Python: os.path.basename()

PHP   : file_exists()
Python: os.path.exists()

PHP   : realpath()
Python: os.path.realpath()

PHP   : is_dir()
Python: os.path.isdir()

PHP   : is_file()
Python: os.path.isfile()

PHP   : filemtime()
Python: os.path.getmtime()

PHP   : filesize()
Python: os.path.getsize()

なんだか大丈夫そうな気がしてきませんか?

他にもPHPにはない os.path.normpath() や os.path.relpath() なんかもあります。

とりあえず、ローカルで事前に用意したファイルがあるかチェックしてサーバに設置するだけなら、この os.path モジュールを使って何とかできたということで。

Windowsのパス問題

前述の「os.path に使えるディレクトリの区切り文字は '\'」の問題について。

MSYSやFabricは ~/hoge といったローカルのパスを解釈してくれますが、os.path はそうはいきません。

たとえばMSYS環境でFabricを実行する場合はk_horiiのホームで fabric.api.local() を使って current_dir = local('pwd', capture=True) とすれば /c/Users/k_horii が返ります。

しかし、それを os.path.normpath() で変換すると \c\Users\k_horii に、os.path.realpath() で変換すると c:\c\Users\k_horii になり、結局いずれも os.path で扱えない無効なものになってしまいます。

ただ、ローカルでのファイル/ディレクトリの存在チェックやカレントディレクトリの参照が必要なければ、普通に fabric.api.put() だけで問題ないと思います。

それに、ファイルの設置先が接続中ユーザのホームディレクトリではなく固定のディレクトリであれば、もっと自然な形で書けそうです。

試しに書き換えてみました。

fabfile.py

from fabric.api import env, run, local, put
from fabric.contrib.files import exists

env.hosts = ['192.168.4.130']
env.port = 22
env.user = 'k_horii'
env.key_filename = '~/.ssh/id_rsa'
env.password = 'hoge'

def test_copy_dir_files():
    server_dir = '/tmp/hoge'
    if not exists(server_dir):
        run('mkdir -p %s' % (server_dir))
    put('~/hoge/*.php', server_dir, use_sudo=True, mode=0755)
    sudo('chown -R apache: %s' % (server_dir))

実行結果はこうなります。

(oppy)[k_horii@horii ~]$ fab test_copy_dir_files
[192.168.4.130] Executing task 'test_copy_dir_files'
[192.168.4.130] put: c:/Users/k_horii/hoge\a.php -> /tmp/hoge/a.php
[192.168.4.130] put: c:/Users/k_horii/hoge\b.php -> /tmp/hoge/b.php
[192.168.4.130] put: c:/Users/k_horii/hoge\c.php -> /tmp/hoge/c.php
[192.168.4.130] sudo: chown -R apache: /tmp/hoge

Done.
Disconnecting from 192.168.4.130... done.

put() のローカル側のパスがおかしなことになってますが、エラーにはなりませんでした。

PHPでもWindowsのディレクトリセパレータ対策は施されていますが、Fabricでも同様のようです。これは嬉しい。

サーバ側で確認

[k_horii@cent6h01 ~]$ ls -lap /tmp/hoge
total 20
drwxrwxr-x. 2 apache apache 4096 Apr 11 18:42 ./
drwxrwxrwt. 9 root   root   4096 Apr 11 18:42 ../
-rwxr-xr-x. 1 apache apache    6 Apr 11 18:42 a.php
-rwxr-xr-x. 1 apache apache    6 Apr 11 18:42 b.php
-rwxr-xr-x. 1 apache apache    6 Apr 11 18:42 c.php

ちゃんとファイルが設置され、所有者と権限も設定されました。結局、PHPer的なos.pathの説明は意味なかった…?

Fabricの機能もまだきちんと把握できていないので、もっと楽な方法があるかもしれません。引き続き勉強します…。

Fabricで接続情報を指定する方法いろいろ&FabricからPHPスクリプトを実行してみたメモ

Windows7 (MinGW / MSYS) で Fabric の続きです。

ですが、多分今回の内容は実行環境に関係なく共通だと思います。

fabコマンドでSSHの接続情報を指定する方法

接続情報は fabfile.py の中で以下のように指定するのが基本ですが…

from fabric.api import env

env.hosts = ['133.242.2.104']
env.key_filename = ['~/.ssh/id_rsa']
env.user = 'kholy'
env.password = 'hoge'

fabコマンドにはこれだけのオプションがあって、引数で接続情報を指定することもできます。

Usage: fab [options] <command>[:arg1,arg2=val2,host=foo,hosts='h1;h2',...] ...

Options:
  -h, --help            show this help message and exit
  -d NAME, --display=NAME
                        print detailed info about command NAME
  -F FORMAT, --list-format=FORMAT
                        formats --list, choices: short, normal, nested
  -I, --initial-password-prompt
                        Force password prompt up-front
  -l, --list            print list of possible commands and exit
  --set=KEY=VALUE,...   comma separated KEY=VALUE pairs to set Fab env vars
  --shortlist           alias for -F short --list
  -V, --version         show program's version number and exit
  -a, --no_agent        don't use the running SSH agent
  -A, --forward-agent   forward local agent to remote end
  --abort-on-prompts    abort instead of prompting (for password, host, etc)
  -c PATH, --config=PATH
                        specify location of config file to use
  -D, --disable-known-hosts
                        do not load user known_hosts file
  -e, --eagerly-disconnect
                        disconnect from hosts as soon as possible
  -f PATH, --fabfile=PATH
                        python module file to import, e.g. '../other.py'
  -g HOST, --gateway=HOST
                        gateway host to connect through
  --hide=LEVELS         comma-separated list of output levels to hide
  -H HOSTS, --hosts=HOSTS
                        comma-separated list of hosts to operate on
  -i PATH               path to SSH private key file. May be repeated.
  -k, --no-keys         don't load private key files from ~/.ssh/
  --keepalive=N         enables a keepalive every N seconds
  --linewise            print line-by-line instead of byte-by-byte
  -n M, --connection-attempts=M
                        make M attempts to connect before giving up
  --no-pty              do not use pseudo-terminal in run/sudo
  -p PASSWORD, --password=PASSWORD
                        password for use with authentication and/or sudo
  -P, --parallel        default to parallel execution method
  --port=PORT           SSH connection port
  -r, --reject-unknown-hosts
                        reject unknown hosts
  --system-known-hosts=SYSTEM_KNOWN_HOSTS
                        load system known_hosts file before reading user
                        known_hosts
  -R ROLES, --roles=ROLES
                        comma-separated list of roles to operate on
  -s SHELL, --shell=SHELL
                        specify a new shell, defaults to '/bin/bash -l -c'
  --show=LEVELS         comma-separated list of output levels to show
  --skip-bad-hosts      skip over hosts that can't be reached
  --ssh-config-path=PATH
                        Path to SSH config file
  -t N, --timeout=N     set connection timeout to N seconds
  -T N, --command-timeout=N
                        set remote command timeout to N seconds
  -u USER, --user=USER  username to use when connecting to remote hosts
  -w, --warn-only       warn, instead of abort, when commands fail
  -x HOSTS, --exclude-hosts=HOSTS
                        comma-separated list of hosts to exclude
  -z INT, --pool-size=INT
                        number of concurrent processes to use in parallel mode

たとえば以下のような fabfile.py が定義されているとして…

from fabric.api import run

def hostname():
    run('hostname')

ホスト(133.242.2.104) ポート(22) ユーザー(kholy) 秘密鍵(~/.ssh/id_rsa) パスフレーズ(hoge) をそれぞれ fab コマンドの引数で指定して上記 hostname() を実行する場合、こんな感じになります。

(oppy)[k_horii@horii ~]$ fab -H 133.242.2.104 -u kholy -i ~/.ssh/id_rsa -p hoge --port=22 hostname
[133.242.2.104] Executing task 'hostname'
[133.242.2.104] run: hostname
[133.242.2.104] out: s11
[133.242.2.104] out:


Done.
Disconnecting from 133.242.2.104... done.

この例での接続先は契約中のレンタルサーバー Gehirn RS2 ですが、Fabric からコマンドを実行できました。

関数内で実行中タスクの env.host 等を参照できますので、こんな風に run() の実行前に接続設定を切り替えることもできます。

from fabric.api import run, env

def hostname():
    if env.host == '133.242.2.104':
        env.port = 22
        env.user = 'kholy'
        env.key_filename = '~/.ssh/id_rsa'
        env.password = 'hoge'
    run('hostname')
(oppy)[k_horii@horii ~]$ fab -H 133.242.2.104 hostname
[133.242.2.104] Executing task 'hostname'
[133.242.2.104] run: hostname
[133.242.2.104] out: s11
[133.242.2.104] out:


Done.
Disconnecting from 133.242.2.104... done.

fab コマンドのオプションの説明を読むと分かりますが、ホスト指定は fab -H 192.168.1.1,192.168.1.2 のようにカンマ区切りで複数記述することもできます。

こちらの記事も参考になります。

Python製デプロイツール Fabricを初めて使う際に役立つTips

fab 関数1 関数2 という風に呼んで関数1で env を書き換えて関数2を実行する方法や、外部ファイルに定義する方法も紹介されています。

なお、今のところ Windows7 で Pageant を使った接続には成功していません…。

fabコマンドからPHPスクリプトを実行

そろそろ真性PHPerらしく、なるべくPythonを使わずに済むよう、PHPスクリプトを設置して実行してみようと思います。

~/php/test.php

<?php
printf('Hello, %s in %s', (isset($argv[1]) && strlen($argv[1]) >= 1) ? $argv[1] : 'anonymous', php_uname());

~/fabfile.py

from fabric.api import run, env, put
from fabric.contrib.files import exists

def _env():
    if env.host == '133.242.2.104':
        env.port = 22
        env.user = 'kholy'
        env.key_filename = '~/.ssh/id_rsa'
        env.password = 'hoge'

def php_test(name=''):
    _env()
    local_file = '~/php/test.php'
    server_dir = '~/php'
    server_file = '%s/test.php' %(server_dir)
    if not exists(server_dir):
        run('mkdir -p %s' %(server_dir))
    if exists(server_file):
        run('rm %s' %(server_file))
    put(local_file, server_file)
    hello = run('php -f %s %s' %(server_file, name))
    print hello

こんな感じで、run() で実行した結果の出力を変数に取得することもできます。

(oppy)[k_horii@horii ~]$ fab -H 133.242.2.104 php_test
[133.242.2.104] Executing task 'php_test'
[133.242.2.104] put: c:/Users/k_horii/php/test.php -> /home/kholy/php/test.php
[133.242.2.104] run: php -f ~/php/test.php anonymous
[133.242.2.104] out: Hello, anonymous in Linux s11 2.6.32-5-amd64 #1 SMP Mon Feb 25 00:26:11 UTC 2013 x86_64
Hello, anonymous in Linux s11 2.6.32-5-amd64 #1 SMP Mon Feb 25 00:26:11 UTC 2013 x86_64

Done.
Disconnecting from 133.242.2.104... done.

うんうん…

(oppy)[k_horii@horii ~]$ fab -H 133.242.2.104 php_test:k-holy
[133.242.2.104] Executing task 'php_test'
[133.242.2.104] run: rm ~/php/test.php
[133.242.2.104] put: c:/Users/k_horii/php/test.php -> /home/kholy/php/test.php
[133.242.2.104] run: php -f ~/php/test.php k-holy
[133.242.2.104] out: Hello, k-holy in Linux s11 2.6.32-5-amd64 #1 SMP Mon Feb 25 00:26:11 UTC 2013 x86_64
Hello, k-holy in Linux s11 2.6.32-5-amd64 #1 SMP Mon Feb 25 00:26:11 UTC 2013 x86_64

Done.
Disconnecting from 133.242.2.104... done.

よしよし。

コマンドの実行ユーザーには気をつけないといけませんが、とりあえずここまでできれば後はPHPで色々やれそうです。

Windows7 (MinGW / MSYS) に virtualenvwrapper を入れたメモ

Windows7 (MinGW / MSYS) に Python 2.7 + virtualenv + Fabric を入れたメモ の続き。

cmd.exe で virtualenv できるようになりましたが、virtualenvに拡張機能を提供する virtualenvwrapperMinGW / MSYS でも使えるようなので、これを入れてみたメモです。

以下、コマンドは MSYS のシェルにて。

virtualenvwrapper をインストール

virtualenv と同様に easy_install で入れます。

[k_horii@horii ~]$ /c/Python27/Scripts/easy_install virtualenvwrapper
Searching for virtualenvwrapper
Reading http://pypi.python.org/simple/virtualenvwrapper/
Reading http://www.doughellmann.com/projects/virtualenvwrapper/
Best match: virtualenvwrapper 3.7.1
Downloading http://pypi.python.org/packages/source/v/virtualenvwrapper/virtualenvwrapper-3.7.1.tar.gz#md5=17fd8cfe4ef7f569f62f2f3453e3bc02
…中略…
Installed c:\python27\lib\site-packages\virtualenvwrapper-3.7.1-py2.7.egg
…中略…
Installed c:\python27\lib\site-packages\stevedore-0.8-py2.7.egg
…中略…
Installed c:\python27\lib\site-packages\virtualenv_clone-0.2.4-py2.7.egg
Finished processing dependencies for virtualenvwrapper

依存ライブラリと一緒にインストールされました。

こちらを参考に .bashrc に設定を追加します。dhellmann / virtualenvwrapper / source / docs / ja / install.rst — Bitbucket

今後のため、ついでにPythonの関連ディレクトリにもパスを通しておきます。

~/.bashrc

export PATH=/c/Python27:/c/Python27/Scripts:$PATH
export WORKON_HOME=$HOME/.virtualenvs
export MSYS_HOME=/c/MinGW/msys/1.0
source /c/Python27/Scripts/virtualenvwrapper.sh

反映させて…

[k_horii@horii ~]$ source ~/.bashrc

mkvirtualenv コマンドで仮想環境を作成

[k_horii@horii ~]$ mkvirtualenv oppy
New python executable in oppy\Scripts\python.exe
Installing setuptools.................done.
Installing pip...................done.
(oppy)[k_horii@horii ~]$

プロンプトが変わりました。

(oppy)[k_horii@horii ~]$ ls ~/.virtualenvs/oppy
Include/  Lib/  Scripts/

$WORKON_HOME/仮想環境名 に仮想環境が作成されています。

引数なしの workon コマンドで環境を一覧表示。

[k_horii@horii ~]$ workon
oppy

workon 仮想環境名 で切替えられます。

[k_horii@horii ~]$ workon oppy
(oppy)[k_horii@horii ~]$

前回と同様、仮想環境ディレクトリ内の Lib/distutils/distutils.cfg に MinGW でコンパイルさせるための設定を追加します。

(oppy)[k_horii@horii ~]$ echo -e "\n[build]\ncompiler=mingw32\n" >> $WORKON_HOME/oppy/Lib/distutils/distutils.cfg

直接パスを指定して、Pywin32 を入れます。

(oppy)[k_horii@horii ~]$ easy_install ~/Python/pywin32-218.win32-py2.7.exe

Fabric を入れます。

(oppy)[k_horii@horii ~]$ pip install fabric
…中略…
(oppy)[k_horii@horii ~]$ fab --version
Fabric 1.6.0
Paramiko 1.10.1

OKです。

virtualenvwrapper の日本語版ドキュメントがこちらにありました。virtualenvwrapper 3.5 — virtualenvwrapper 3.5 documentation

仮想環境の作成時にフックスクリプトを実行する

ユーザカスタマイズ — virtualenvwrapper 3.5 documentation によれば、仮想環境を作成する際 $WORKON_HOME 直下に決められたファイル名で配置したフックスクリプトを実行できるようです。

前述の Lib/distutils/distutils.cfg への追記は premkvirtualenv フック(仮想環境が作成された後、環境が切り替わる前に実行される。引数は新しい環境名)に書けば良いでしょうか。

試しに書いてみます。

~/.virtualenvs/premkvirtualenv

#!/bin/sh
# This hook is run after a new virtualenv is created and before it is activated.

echo -e "\n[build]\ncompiler=mingw32\n" >> $WORKON_HOME/$1/Lib/distutils/distutils.cfg

これでいけるはず…新たな仮想環境 chippy を作成してみます。

$ mkvirtualenv chippy
…中略…
(chippy)[k_horii@horii ~]$ cat $WORKON_HOME/chippy/Lib/distutils/distutils.cfg
# This is a config file local to this virtualenv installation
# You may include options that will be used by all distutils commands,
# and by easy_install.  For instance:
#
#   [easy_install]
#   find_links = http://mylocalsite

[build]
compiler=mingw32

うまくいきました。

Pywin32 のインストールは postmkvirtualenv フック(新しい環境が作成されてアクティブ化された後で読み込まれる)に書けば良いでしょうか。

Tips を見ると「新しい環境に共通ツールを自動的にインストールする」という項目がありました。

Tips とトリック — virtualenvwrapper 3.5 documentation

私はこの postmkvirtualenv を基本的なセットアップを行うインストールに使用します。

$ cat postmkvirtualenv
#!/usr/bin/env bash
curl -O http://python-distribute.org/distribute_setup.p... />python distribute_setup.py
rm distribute_setup.py
easy_install pip==dev
pip install Mercurial

上記の例にあるように easy_install や pip コマンドも実行できるようです。

~/.virtualenvs/postmkvirtualenv

#!/bin/sh
# This hook is run after a new virtualenv is created and before it is activated.

easy_install ~/Python/pywin32-218.win32-py2.7.exe
pip install fabric

一旦削除して…

(chippy)[k_horii@horii ~]$ deactivate && rmvirtualenv chippy
Removing chippy...

作りなおしてみます。

$ mkvirtualenv chippy
…中略…
Successfully installed fabric paramiko pycrypto
Cleaning up...
(chippy)[k_horii@horii ~]$ fab --version
Fabric 1.6.0
Paramiko 1.10.1

ちょっと、いや、かなり時間はかかりますが、仮想環境を作成するたびに Fabric がインストールされるようになりました。

ここまでやっといてアレですけど、当初の目的(PHPでWebからfabコマンド実行)を考えても PyWin32 と Fabric は素直に実環境に入れた方がいいような気がしてきました…。

まあローカルでの実行環境をどうするかはおいといて、一応 Windows7 (MinGW/MSYS) でも Fabric が使えるようになりましたので、今まで原始的なコピー&ペーストで行っていた諸作業を、どんどん自動化してみようと思います。