ビルドは仮想マシンで。

最近なにかと喧しい仮想化。遅ればせながら、僕も最近いろいろ試してみている。VMware を使って Windows Vista の上に Fedora を入れてみたりすると、なんだか非常に面白い。ブートローダや OS インストーラといった普段ならモニタを占有する者達がウィンドウの中にちんまりと収まっている様はいとあはれなり。
ところが、実用性となるとどうか。大して使い込んだわけでもないのにこんなことを言うと怒られそうだが、正直、実用は無理だ。なんていうか、ウザい。動きがもっさりとかはあんまり感じない*1が、マウスポインタの動き方とかが微妙に怪しい。管理コンソールとかが鬱陶しい。ちょっと我慢すればすぐに慣れるのかもしれないが、我慢するのは嫌いだ。
やっぱりアレだね。仮想化ってのは、OS インストールマニア*2のオモチャだね。動作検証とかで矢鱈に沢山のマシンを並べなきゃいけない、とかでもない限り、実際に使うところはないんじゃないかな?
……なんてほざいていたのは、昨日までの僕だ。仮想化はメチャクチャ便利な実用技術で、サーバ管理には必須の機能じゃないですか。
事の発端は、id:KENZ さんの一言:


落としてきた SRPM のビルドとか、Xen の上でやったらいいんじゃないですか?
これだ。これに尽きる。○○-devel とかが澱のように積もることもなく、そいつらが連れてくる得体の知れない依存パッケージに怯えることもなく、動作検証だって誰に気兼ねすることもない。GUI など元から無いので、操作感はいつもの端末画面と何ら変わらない。
パッケージのビルドなどという下賤な作業は下々の仮想マシンどもにやらせ、高貴なるサーバ本体様はその上前をハねるだけ。一度使ってしまうと、この運用の便利さは手放せない。

*1:時たま、何かの拍子にカクカクすることはあるが。

*2:実は、僕はちょっとそれだ。

SRPM も Yum で最新に保ちたい

Yum はいいね。最初に Debian から Fedora にしてみたときには apt-get がなくてキレそうだったけど、今ではすっかり Yum の虜。
サーバは CentOS にして*1、パッケージの少なさにまたもやキレそうだっけど、EPEL のおかげでそれも解決。そのついでに覚えた yum-priorities プラグインがこれまた最高。自分ビルドのパッケージを exclude やら lock やらで死守していたあの頃の手間はいったい何だったのか……。
ただ、まだ1つ、問題が残っている。YumSRPM を管理してくれない。SRPM に管理も何もないと思われるかもしれないが、こういう事態になって困るのだ:

  1. 出来合いのパッケージにやや不満 (最新バージョンが使いたい、ビルドオプション変えたい、パッチ当てたい、などなど)。
  2. Fedora とかから SRPM をもらってくる。
  3. rpmbuild とかして悦*2
  4. そんなことはすっかり忘れてしまった頃、Fedora の方でセキュリティフィックスが出る……。

そう。SRPM に手を出した途端、最新版が出てないか四六時中気にするあの不毛な生活に逆戻りだ。
というわけで、手持ちの SRPM が最新かどうかをチェックするスクリプトを作ってみました((yumdownloader --sourceSRPM が落とせるようになっているのと rpmdev-vercmp コマンドがある(EPEL の rpmdevtools パッケージにあります) のが前提になります。))。/var/local/srpm にある *.src.rpm よりも新しい SRPM が出てないかを報告してくるので、cron とかで回します*3:

#! /bin/sh

set -e

export LC_ALL=C
export LANG=C

cd /var/local/srpm

SrpmFileNamePattern='^\(.\+\)-\([^-]\+\)-\([^-]\+\)\.src\.rpm$'

function listSrpmUri()
{
        local Packages="$( ls *.src.rpm |sed -e "s/${SrpmFileNamePattern}/\1/" |sort |uniq )"
        yumdownloader --source --urls $Packages |grep -E '\.src\.rpm$'
}

function getSrpmFileNamePart()
{
        local SrpmFile="$1"
        local Part="$2"
        echo "$SrpmFile" |sed -e "s/${SrpmFileNamePattern}/\\${Part}/"
}

function isNewerThan()
{
        local SrpmFile1="$1"
        local SrpmFile2="$2"
        local Version1="$( getSrpmFileNamePart "$SrpmFile1" 2 )"
        local Version2="$( getSrpmFileNamePart "$SrpmFile2" 2 )"
        local Release1="$( getSrpmFileNamePart "$SrpmFile1" 3 )"
        local Release2="$( getSrpmFileNamePart "$SrpmFile2" 3 )"

        rpmdev-vercmp '' "$Version1" "$Release1" '' "$Version2" "$Release2" \
                |grep -F "${Version1}-${Release1}" |grep -Fi 'newer' >/dev/null
}

function getSrpmId()
{
        SrpmPackageFileName="$1"
        echo $( getSrpmFileNamePart "$SrpmPackageFileName" 2 ) -  \
                $( getSrpmFileNamePart "$SrpmPackageFileName" 3 ) \
                |sed -e 's/ //g'
}

function checkUpdate()
{
        local SrpmUri="$1"
        local RemoteFile="$( echo "$SrpmUri" |sed -e 's|^.*/\([^/]\+\)$|\1|' )"
        local PackageName="$( getSrpmFileNamePart "$RemoteFile" 1 )"

        CurrentSrpms=''
        for LocalFile in *.src.rpm
        do
                if [ "$( getSrpmFileNamePart "$LocalFile" 1 )" != "$PackageName" ]
                then
                        continue
                fi

                if ! isNewerThan "$RemoteFile" "$LocalFile"
                then
                        return 0
                fi

                CurrentSrpms="${CurrentSrpms} $( getSrpmId "$LocalFile" )"
        done

        echo "${PackageName}:${CurrentSrpms} --> $( getSrpmId "$RemoteFile" )"
}

function main()
{
        for SrpmUri in $( listSrpmUri )
        do
                checkUpdate "$SrpmUri"
        done
}

main
exit 0

*1:2〜3年のスパンで見たときの管理の手間が、Fedora のときとは全然違うよ。当たり前だけど。

*2:もちろん、できた RPMrpm -ivh とかしたりはしない。サイトローカルのリポジトリに置いて yum だ。

*3:そういえば、yum-cron の方が yum-updates よりも全然いいね、サーバでは。どのパッケージが更新されたのかちゃんと報告してくれるし、「Hi」「Thank You, Your Computer」なんて馴れ馴れしいことも言わない。

cd してから実行する

いろいろとあって、svnservecd してから実行する、という必要に迫られた。tar-C オプションみたいなことがしたい((逆に、tar にわざわざ -C があるということは、うまい逃げ道はないのかも。))。
で、sh -c やら exec やら sudo やらをいろいろ弄ってみたわけだが、結局ダメ。仕方なく、こういうスクリプトに逃げましたとさ:

/usr/local/bin/run-at

#! /bin/sh

cd "$1"
shift
exec "$@"

前にこんな感じで悩みを吐露した素晴らしいアドバイスが頂けたので、味を占めてまたやってみます。
どなたか、もっといい方法をご存じないですか?
※今回は UNIX なので、WindowsSTART コマンドは使えません。

IMHO

英語のメーリングリストを眺めていてちょっと悔しいのは、フレーズの略語*1が分からないときだ。「分からないときだ」とかいうと大抵のやつは知っていてたまに分からない、みたいなニュアンスだが、実は、「ASAP」ぐらいしか知らない。すいません。
で、今日は「IMHO」が分からなかったので、神託を仰いだ。トップは『インテル用語集』*2で、この通り:


IMHO
読み方
アイ・エム・エイチ・オー
フルスペル
In My Humble Opinion

メールやニュース、チャットなどで利用される、短い略語の 1 つ。「in my hunble opinion (私のつたない考えでは) 」の略です。「私見によれば」「私の考えでは」と、やや控えめに自分の意見を述べる場合に利用されます。

なるほど、よく分かりました。ありがとう、Intel。なるべく Intel の CPU が載った PC を買うようにします。
ところで、語釈のところはスペルミスですよね、と思ったが、こういうときこそ IMHO か:

  • IMHO, "hunble" in the last paragraph seems a typo of "humble".

*1:「略語」じゃなくて、こういうのを指す専用の用語があったような気がするが、思い出せない。

*2:どうでもいいが、「インテル用語集」だと「«インテル «用語 集»»」じゃなくて「««インテル 用語» 集»」に見える。

バッチファイルで、カレントディレクトリのパスが空白を含まないかを判定する

ちょっといじってみたいところがあって、Subversion を自分のところでビルドしている。
ところが、これがかなり鬱陶しい。Subversion 開発者の皆さんは UNIX ユーザが多いのだろうか? Windows 上でのビルドが一筋縄ではいかない。調子に乗って最新バージョンの 0.28.1 を使ったせいか、neon のビルドがこける。
で、あれやこれやといじった結果、カレントディレクトリのパスに空白があるとダメというショボそうな結論に至った。「$(Make) と書かなければいけないところが "$(Make)" になっているという」バグ*1neon-0.28.0 で修正されたらしいが、そのあたりが悪さをしているのかもしれない*2
で、やっと本題。
ビルド手順を間違えないようにバッチファイルを作っているのだが、これの先頭で、「カレントディレクトリのパスに空白が含まれていたらエラーで終了させる」ということがしたい。シェルスクリプトなら grep だし WSH なら JScript正規表現なり String.indexOf() なりで片付けられるんだが、バッチファイルだとどうやるんだろう?
恥ずかしながら、現状の解はこう:

@ECHO OFF

DIR %CD% >NUL 2>NUL
IF %ERRORLEVEL% NEQ 0 (
	ECHO [エラー] カレントディレクトリのパスに空白が含まれているとビルドに失敗します。>&2
	EXIT /B 1
)

どなたか、もっといい方法をご存じないですか?

*1:詳しくは知らないが、neon のバグというよりも NMAKE.EXE の動作変更らしい。

*2:僕の勝手な憶測。

Subversion の属性値もコピーしてくるシェルスクリプト

Subversion でベンダブランチを作る際、一次開発元も Subversion を使っている場合には、属性値も全部そのまま持ってきたい。なんか、そういうツールは絶対どこかにあるんじゃないかとは思うんだが、探せなくて作ってしまった:
適当なワーキングコピー中にあるディレクトリを引数に指定して実行すると、そのディレクトリ以下の全内容・全属性をカレントディレクトリの下に再現する:

#! /bin/sh

set -e

if [ "$1" != "--sub" ]
then
	VENDOR_ROOT="$1"
	if [ -z "$VENDOR_ROOT" ]
	then
		echo "usage: ${0##*/} <vendor-root>" 1>&2
		exit 1
	fi

	svn info "$VENDOR_ROOT" >/dev/null
	  # checking whether $VENDOR_ROOT is a working copy.

	if echo "$VENDOR_ROOT" |grep -v '/$' >/dev/null
	then
		VENDOR_ROOT="${VENDOR_ROOT}/"
	fi

	exec find "$VENDOR_ROOT" ! -name '.svn' ! -path '*/.svn/*' \
		-exec "$0" --sub "$VENDOR_ROOT" '{}' \;
fi

VENDOR_ROOT="$2"

function CopyProperty()
{
	local NODE_FROM="$1"
	local PROPERTY="$2"
	local NODE_TO="$3"

	if echo $PROPERTY |grep -E "^'" >/dev/null 2>&1
	then
		return
	fi

	if echo $PROPERTY |grep -E '^の属性:' >/dev/null 2>&1
	then
		return
	fi

	local VALUE="$( svn propget "$PROPERTY" "$NODE_FROM" )"
	svn propset "$PROPERTY" "$VALUE" "$NODE_TO"
}

function CopyNode()
{
	local NODE_FROM="$1"

	local NODE_TO="$( echo -n "$NODE_FROM" |sed -e "s|^$VENDOR_ROOT||" )"
	if [ -z "$NODE_TO" ]
	then
		NODE_TO='.'
	fi

	if [ -L "$NODE_FROM" ]
	then
		ln -s "$( readlink "$NODE_FROM" )" "$NODE_TO"
		svn add "$NODE_TO"
		return
	fi

	if [ -d "$NODE_FROM" ]
	then
		if [ "$NODE_TO" != '.' ]
		then
			svn mkdir "$NODE_TO"
		fi
	else
		cp -p "$NODE_FROM" "$NODE_TO"
		svn add "$NODE_TO"
	fi

	for PROPERTY in $( svn proplist "$NODE_FROM" )
	do
		CopyProperty "$NODE_FROM" "$PROPERTY" "$NODE_TO"
	done
}

CopyNode "$3"
exit 0