sh で自作システムのアップデートスクリプトを書く

ユーザに実行させること

curl を使ってシェルスクリプトを実行させる。

curl -fsSL https://example.com/path/to/update.sh | sh

スーパーユーザ権限が必要な場合は sudo あるいは su を実行させる。

シェルスクリプトを書く

次のような骨組みで書く。目を引くエラー表示を共通化した。

#!/bin/sh

## システム
SYS_ROOT=/path/to/system

## パッチファイルのあるディレクトリ
PATCH_URL="https://example.com/updates/update-201505.patch"

## エラー表示
function error_mark() {
	echo " "
	echo "################################################################"
	echo " ERROR"
	echo "################################################################"
}

## メインルーチン
## function() が閉じていなければエラーとなるはずなので、エラーチェックとして利用する
function update_wrap() {

if ! [ -d $SYS_ROOT ]; then
	error_mark
	echo "システムがインストールされていません。"
	exit 1
fi

#######
####### (これからここに書いていく)
#######

echo "アップデートが完了しました。"
return 0
}

update_wrap

メインルーチンを関数として定義しているのは、次のような危険性があるためだ。

スクリプトを取ってくる時にコネクションの問題等でスクリプトを中途半端に取ってきてしまい、 例えば

rm -rf ~/tmp/tmpdir/

の様なコマンドがあった場合、これが

rm -rf ~/

と言う形で実行されてしまう可能性があります。

cURLを使ったインストール方法の危険性

もしファイルが途中で途切れていた場合はシンタックスエラーになるため、中途半端に実行されることがない。

作業ディレクトリを作成する

シェルスクリプトにおいて $$ と書くと、プロセスIDが展開される。このスクリプトの場合、二重起動しないように制御しなければならないが、ここではしていない。多重起動が必要なスクリプトの備忘録として。

WORK_DIR="$SYS_ROOT/.update.$$"

mkdir $WORK_DIR
if ! [ -d $WORK_DIR ]; then
	error_mark
	echo "作業フォルダを作成できませんでした。"
	echo "システムへの書き込み権限があるかどうか確認してください。"
	exit 1
fi

cd $WORK_DIR

アップデートファイルを取得する

curl を使う。

curl -fsSL $PATCH_URL > update.patch
if [ $? -ne 0 ]; then
	error_mark
	echo "アップデートファイルを取得できませんでした。"
	echo "アップデートサーバがメンテナンス中の可能性があります。"
	exit 1
fi

オプションの意味は次の通り。

  • -f (--fail): サーバエラー発生時、黙って終了する
  • -s (--silent): プログレスなどのメッセージを表示しない
  • -S (--show-error): -s と併用し、失敗時にはエラーメッセージを表示する
  • -L (--location): HTTPリダイレクトがあった場合はリダイレクト先を取得する

パッチを作成する

diff コマンドを使う。

diff -u -r old-dir new-dir > patchfile

オプションの意味は次の通り。

  • -u: Unified 形式で出力する(よく使われる形式)
  • -r: ディレクトリ内のファイルを再帰的に処理する

パッチを適用する

patch コマンドを使う。

patch -f -s -p1 -d $SYS_ROOT < update.patch
if [ $? -ne 0 ]; then
	error_mark
	echo "アップデートに失敗しました。"
	echo "アップデート対象バージョンかどうか確認してください。"
	exit 1
fi

改行コードが混在していると、1 out of 1 hunk FAILED のようなメッセージが出てパッチが適用できない場合がある。
そのような場合、パッチの作成前のファイルとシステム上のファイル両方の改行コードが LF 以外(Windows で作業した場合 CR+LF)になっていないかチェックするとよいだろう。確認には nkf を使うことができる。

nkf -g filename

一括変換は find, sed, nkf を組み合わせて次のようにできる。

# 確認
find $SYS_ROOT -type f -name '*.php' -or -name '*.html' | sed 's/^/n kf -WwLu --overwrite /' | less

# 実行
find $SYS_ROOT -type f -name '*.php' -or -name '*.html' | sed 's/^/nkf -WwLu --overwrite /' | sh

sed を使って条件を絞り込むこともできる。この例では vendor および media ディレクトリ以下のファイルを除いている。

# 確認
find $SYS_ROOT -type f -name '*.js' -or -name '*.css' | sed -e 's/^/nkf -WwLu --overwrite /' -e '/^.*\/\(vendor\|media\)\/.*$/d' | less

後片付け

最後に作業ファイルを消しておく。

rm update.patch
cd $SYS_ROOT
rmdir $WORK_DIR

(rm -rf は怖い)