Alpine Linux の Persistence 機構の一つ。 これを利用すれば、 diskless モード などの再起動後に保存したファイルが失われるモードで、変更内容を再起動後に持ち越すことができる。
立ち位置としては、 Ubuntu の casper-rw
などに似ている。 casper-rw
とは違い、変更点を Apkovl と呼ばれる単純な tar
ファイルに保存する。 そのため、別マシンで既存の Apkovl ファイルを編集することや、別マシンに変更点をデプロイすることが気軽に行える。
詳しい利用方法は Alpine Linux 公式 Wiki の Alpine local backup の項にまとまっている。
Apkovl ファイルを使ってシステムに変更点を反映するには、 Apkovl ファイルをフロッピーなど適当なメディアのルートディレクトリに置いておくだけでよい。 利用側としては手軽だが、 Alpine Linux がどのように Apkovl ファイルを探し出し、変更を反映しているのだろうか。
Apkovl を 見つけ出し rootfs に展開する機能は、 initramfs init
に実装されている。 ここからは、具体的な処理についてコードを交えながら見ていく。
Apkovl ファイルの場所は、カーネルコマンドラインの apkovl オプションで明示的に指定することができる。 この指定は必ずしも必要ではなく、未指定の場合は自動的に Apkovl ファイルを探索する。
実際に利用する Apkovl ファイルを決定する処理は init の 593 行付近に書かれている。実装を引用すると、
if [ -z "$KOPT_apkovl" ]; then
# Not manually set, use the apkovl found by nlplug
if [ -e /tmp/apkovls ]; then
ovl=$(head -n 1 /tmp/apkovls)
fi
elif is_url "$KOPT_apkovl"; then
# Fetch apkovl via network
MACHINE_UUID=$(cat /sys/class/dmi/id/product_uuid 2>/dev/null)
url="${KOPT_apkovl/{MAC\}/$MAC_ADDRESS}"
url="${url/{UUID\}/$MACHINE_UUID}"
ovl=/tmp/${url##*/}
wget -O "$ovl" "$url" || ovl=
else
ovl="$KOPT_apkovl"
fi
となっており、 カーネルコマンドラインに何らかの値が設定されているときは、その値を信用して $ovl
に設定していることがわかる。
apkovl
オプションが未指定の場合は、 use the apkovl found by nlplug
となっている。 ovl=$(head -n 1 /tmp/apkovls)
となっていることから、 /tmp/apkovls
は Apkovl ファイルのリストだと予想できる。 /tmp/apkovls
は、 init の 565 行付近にある、以下のコードによって作られる。
nlplug-findfs $cryptopts -p /sbin/mdev ${KOPT_debug_init:+-d} \
${KOPT_usbdelay:+-t $(( $KOPT_usbdelay * 1000 ))} \
$repoopts -a /tmp/apkovls
nlplug-findfs
というプログラムに対して /tmp/apkovls
という引数を与えており、 use the apkovl found by nlplug
が正しそうに見える。
nlplug-findfs は Alpine Linux の initramfs init 独自のユーティリティであり、 kobject_uevent
に流れているメッセージを利用してデバイスのマウントなどを行う。
nlplug-findfs
はファイルシステムのマウントに関するさまざまな処理を行っている。 Apkovl ファイルの検索で重要な関数としては scandev が挙げられる。 この関数は /media/*
をマウントするためのものだが、 Apkovl を検索する機能も持っている。
実際に Apkovl を検索するコードは scandev_cbであり、 Apkovl の検索を行う実装は、
} else if (fnmatch("*.apkovl.tar.gz*", opts->filename, 0) == 0) {
dbg("found apkovl %s", opts->path);
append_line(conf->apkovls, opts->path);
ctx->found |= FOUND_APKOVL;
}
になる。ここで、 append_line は第二引数のデータを第一引数のファイルに追加する関数であり、 conf->apkovls
は -a
オプションで指定した内容(今回の場合は /tmp/apkovls
)であることを踏まえると、 *.apkovl.tar.gz*
を満たすファイルを Apkovl としてリストしていることがうかがえる。
なお、 scandev_cb
を呼び出す scandev
で各デバイスのルートディレクトリしか探索しない設定がされているため、 /media/usb/foo/example.apkovl.tar.gz
のようなファイルは Apkovl ファイルとして認識されない。
読み出しは init の 614 行付近で行われる。実際の読み出し処理は unpack_apkovl に書かれている。
unpack_apkovl
を読むと、
local ovl="$1"
local dest="$2"
local suffix=${ovl##*.}
local i
ovlfiles=/tmp/ovlfiles
if [ "$suffix" = "gz" ]; then
tar -C "$dest" -zxvf "$ovl" > $ovlfiles
return $?
fi
# we need openssl. let apk handle deps
apk add --quiet --initdb --repositories-file $repofile openssl || return 1
if ! openssl list -1 -cipher-commands | grep "^$suffix$" > /dev/null; then
errstr="Cipher $suffix is not supported"
return 1
fi
local count=0
# beep
echo -e "\007"
while [ $count -lt 3 ]; do
openssl enc -d -$suffix -in "$ovl" | tar --numeric-owner \
-C "$dest" -zxv >$ovlfiles 2>/dev/null && return 0
count=$(( $count + 1 ))
done
のように、拡張子によって動作を分けていることがわかる。 これにより、 lbu commit -e が生み出す暗号化された Apkovl ファイルを読みだすことを可能にしている。 また、コードから、「暗号化されたファイルの Apkovl ファイルのキーファイルを指定する」機能はなく、起動時にユーザーが明示的にパスワードを入力する必要があるとわかる。
Alpine Linux の Alpine local backup は、 initramfs init で魔法を起こしている。 Alpine local backup は仕組みを知らなくても手軽に利用できるが、裏側の仕組みを知っておけばより愛着も沸くことだろう。