Raspbian SDカードイメージファイルの縮小

Raspbian SDカードイメージファイルの縮小

Jetson nano のSDカードをバックアップする に改訂版を公開しました。そちらを参照してください。

イメージファイルの縮小

『イメージファイルの縮小』はUbuntuで実行することを前提に書いてあります。

SDカードに作成した、RaspberryPiのオリジナルのカスタムブートディスク (Raspbian BusterのインストールRaspbian Buster Lite版のインストール参照) は、イメージファイルにバックアップしておくと、 再度カスタマイズ作業を行わなくても同じ環境を作成できる。

ただ、このイメージファイルはSDカードを丸ごとファイル化するので、元のSDカードより小さなSDカードにコピーできない。
(スペック上同じ容量のSDカードでも微妙にサイズが違ったりするので入らないことがある)

そこで、イメージファイルを縮小しておけば、小さなSDカードにもコピーできる(コピー時間も短縮できて一石二鳥)。
このとき、ディスクイメージ内部のパーティション情報/ファイルシステム情報をきちんと縮小処理しておかないと ファイルシステムエラーになってしまうので、注意が必要。

今回はSDカードから作成したイメージファイルをUbuntuで縮小処理するスクリプトを書いてみた。
とりあえず、手元の環境では動いているが、詳細評価したわけではないので、 実行する場合は自己責任で。

また、途中エラーチェックは行っていないので、エラーが発生していないか、実行結果を注意深く確認すること。

Virtualbox 共有フォルダのマウント

SDカードのイメージファイルは大きいので、共有フォルダを使ってWindows側のフォルダをアクセスできるようにしておけばディスク領域を圧迫しなくて済む。
Virtualbox側で共有フォルダ「Share」を作成済みで、/Shareディレクトリにマウントする場合は以下のコマンドで。

sudo mount -t vboxsf Share /Share/

ツールのインストール

イメージファイル内のパーティションをそれぞれloopデバイスに割り当てるツールを使用する。
以下のコマンドでインストールできる。

sudo apt install kpartx

スクリプト

以下のスクリプトを適当な名前(例えばshrink_img.sh)で保存し、実行する(bash shrink_img.sh)。
実行する前に使用するファイル名を設定すること。

変数 内容
WORK_IMG_FILE 作業用イメージファイルのファイル名
NEW_IMG_FILE 小さくしたイメージファイルのファイル名

WORK_IMG_FILE で指定したファイルは書き変えられるので、オリジナルのイメージファイルは 別途残しておくこと。

内部でsudoを実行しているので、パスワードを聞かれたら入力する。

途中、『警告: パーティションを縮小するとデータを失うかもしれませんが、それでも実行しますか?』と聞かれたら「y」を入力する。


# イメージファイル
WORK_IMG_FILE=/Share/tmp.img            # 作業ファイル
NEW_IMG_FILE=/Share/shrink.img          # 縮小した(作成する)ファイル

# イメージファイルをマッピング(マッピング済みでも問題ない) 
# 前回の操作が終わるのを待つため-sを付けておく
sudo kpartx -asv ${WORK_IMG_FILE}

# loopデバイス名を取得
# 前回の操作が終わるのを待つため-sを付けておく
tmp_var=(`sudo kpartx -ls ${WORK_IMG_FILE}`)
LOOP_DEV=/dev/mapper/${tmp_var[6]}          # 結果の位置は決め打ちで(姑息だけど)

# 要求するブロックサイズの取得
tmp_var=(`sudo resize2fs -P ${LOOP_DEV}`)
req_fs_block=${tmp_var[-1]}                 # サイズは結果の最後に入っている

# パーティション情報の取得
part_info=`sudo parted -m ${WORK_IMG_FILE} unit B print`
# 1行毎に分割
line=(${part_info//;/ })

# ext4のパーティション番号を探す
part_number=0
part_start=0
target_type="ext4"
for l in ${line[@]} ; do
    # 各要素に分割
    elm=(${l//:/ })
    elm_number=${elm[0]}             # パーティション番号
    elm_start=${elm[1]//B/}          # ついでに最後のBを取り除いておく
    elm_type=${elm[4]}               # ファイルシステムタイプ
    # echo $elm_number $elm_start $elm_type
    if [ -n "${elm_type}" ] ; then
        if [ ${elm_type} = ${target_type} ] ; then
            # 対象のパーティションが見つかった
            part_number=${elm_number}
            part_start=${elm_start}
            break
        fi
    fi
done
if [ ${part_number} = 0 ] ; then
    echo ${target_type} "のパーティションが見つかりませんでした"
else
    # 新しいパーティション終了位置を計算
    new_fs_block=$((req_fs_block + 100000))             # 少し余裕(100000block=390MB)を持たせる
    new_fs_byte=$((new_fs_block * 4096))                # byteに換算
    part_end=$((part_start + new_fs_byte + 2048))       # さらに少し余裕を持たせる
    img_size_mb=$(((part_end / (1024 * 1024)) + 10))    # MBに換算 ついでにしつこいくらいに余裕を持たせる 

    set -x          #--------------------------------------

    # ファイルシステムとパーティションの縮小
    sudo e2fsck -f ${LOOP_DEV} 
    sudo resize2fs -p ${LOOP_DEV} ${new_fs_block}
    parted ${WORK_IMG_FILE} unit B resizepart ${part_number} ${part_end}

    # 縮小コピー
    dd if=${WORK_IMG_FILE} of=${NEW_IMG_FILE} count=${img_size_mb} bs=1M

    set +x          #--------------------------------------
fi

# マッピング解除
sudo kpartx -d ${WORK_IMG_FILE}
 

新しいイメージファイルでブートSDを作る

縮小したイメージファイルから作成したSDカードはSDカードの容量をすべて使用できるようになっていない。
そこで、コピーしたSDカードでブートした後、パーティションを拡張する必要がある。

SDカードへの書き込みは通常通り

書き込んだSDカードでブートする

SDカードのパーティション拡張

色々手順がめんどっちいので、スクリプト作成してみた。
とりあえず、手元の環境では動いているが、詳細評価したわけではないので、 実行する場合は自己責任で。

以下のスクリプトを適当な名前(例えばexpand_partition.sh)で保存し、実行する(bash expand_partition.sh)
成功したらリブートすること。


# ターゲットのデバイス
target_device=/dev/mmcblk0

# パーティション情報の取得
part_info=`sudo parted -m ${target_device} unit s print free`

# 1行毎に分割
line=(${part_info//;/ })

# 最終行
# declare -i last_index=${#line[@]}-1
# last_line=${line[$last_index]}
last_line=${line[$((${#line[@]}-1))]}

# :で区切られた各要素に分割
elm=(${last_line//:/ })

# 変数名付け替え
# last_number=${elm[0]}
# last_start=${elm[1]}
last_end=${elm[2]}
# last_size=${elm[3]}
last_type=${elm[4]}

# 確認
# echo last_number=  $last_number
# echo last_start=   $last_start
# echo last_end=     $last_end
# echo last_size=    $last_size
# echo last_type=    $last_type

# 最後のパーティションがfreeか確認
if [ ${last_type} = free ] ; then
    # ext4のパーティション番号を探す
    part_number=0
    target_type="ext4"
    for l in ${line[@]} ; do
        # 各要素に分割
        elm=(${l//:/ })
        elm_number=${elm[0]}
        elm_type=${elm[4]}
        if [ -n "${elm_type}" ] ; then
            # echo $elm_number $elm_type
            if [ ${elm_type} = ${target_type} ] ; then
                part_number=${elm_number}
                break
            fi
        fi
    done
    if [ ${part_number} = 0 ] ; then
        echo "ext4のパーティションが見つかりませんでした"
    else
        # リサイズの実行
        set -x
        sudo parted ${target_device} resizepart ${part_number} ${last_end}
        sudo resize2fs ${target_device}p2
        set +x
        echo "リブートしてください"
    fi
else
        echo "最終パーティションがfreeではありません"
fi