天天看點

Raspberrypi 3 系統備份還原, 基于最小系統鏡像實作Raspberrypi 3 備份還原系統

Raspberrypi 3 備份還原系統

一、為什麼要備份系統?

1 經常在樹莓派上調試程式, 安裝各種軟體,越來越多的庫和程式的安裝帶來的系統更改幾乎是不可逆的,一旦某個程式或者驅動出現問題, 如果有備份, 可以快速友善的恢複到系統正常的狀态

2批量釋出樹莓派産品,當在一台樹莓派上把程式調試好後, 需要批量化到幾十上百個樹莓派, 如果每台樹莓派再去安裝一遍環境和程式, 需要耗費很多時間, 假如其中有用到apt-get安裝的軟體, 那更是不堪繁瑣

二、備份系統鏡像

    備份現有的系統鏡像, 需要的時候既可以用來恢複系統, 又可以用來批量化生産, 非常友善。

目前的備份系統大多是基于SD卡的完整備份, 比如SD卡16G, 備份鏡像也是16G,這帶來了許多空間浪費,并且用鏡像還原的時候, 新的SD卡容量必須要比鏡像檔案大,否則無法寫入SD卡,況且就算同型号大小的SD卡, 容量不一定相等, 往往相差一點,這就導緻鏡像寫入的時候經常出現SD卡容量就差那麼一點點,結果無法使用。

    是以, 需要制作最小系統鏡像, 按目前實際使用的空間來備份鏡像, 而并非完整備份SD卡。

三、制作步驟

環境: Windows 7 64位 + 虛拟機Ubuntu 16 

樹莓派 3 ,  Linux raspberrypi 4.9.35-v7+ #1014 SMP Fri Jun 30 14:47:43 BST 2017 armv7l GNU/Linux

1 如果直接在樹莓派系統内部進行操作, 需要SD卡剩餘空間大于已用空間, 一般來說大家使用的都是16G或者32G, 并沒有多少剩餘空間, 是以不采用此方案。是以我換到PC上的Ubuntu裡面, 直接對已有的完整SD卡鏡像進行處理。

2 在Wndows上先用Win32 Disk Imager把樹莓派的SD卡鏡像完整讀取出來放好, 我的鏡像名稱是rspi-backup-8G.img。

3 進入虛拟機Ubuntu16 , 你可以将SD卡完整鏡像拷貝到虛拟機, 或者将路徑共享到虛拟機, 我采用的後一種

4 安裝虛拟機需要用到的工具

   sudo apt-get -y install rsync dosfstools parted kpartx

5 虛拟SD卡完整鏡像為塊裝置

   sudo losetup -f --show 你的SD卡完整鏡像路徑

Raspberrypi 3 系統備份還原, 基于最小系統鏡像實作Raspberrypi 3 備份還原系統

  完成後會顯示路徑為 /dev/loop0

6 挂載虛拟檔案系統,即挂載上一步的/dev/loop0, 如下所示

Raspberrypi 3 系統備份還原, 基于最小系統鏡像實作Raspberrypi 3 備份還原系統

挂載完成後會在/dev/mapper/ 路徑下生成兩個路徑, 即/dev/mapper/loop0p1, /dev/mapper/loop0p2,

其中vfat格式的是boot分區, ext4的是root分區, 注意挂載後的路徑在/medai/root/ (我用的root使用者,是以在root路徑下)

PS: ubuntu會自動挂載,如果你的系統沒有自動挂載, 請手動挂載。

Raspberrypi 3 系統備份還原, 基于最小系統鏡像實作Raspberrypi 3 備份還原系統

7 這步比較關鍵了, 根據上一步看到的分區大小, 可以發現root分區大小7G,實際使用4.3G, 有2.5G的空餘空間

   我們要做的就是省下這部分空間。

   首先檢視SD卡完整鏡像大小,需要注意的是root分區是完整大小, root分區是使用的大小

Raspberrypi 3 系統備份還原, 基于最小系統鏡像實作Raspberrypi 3 備份還原系統

    建立一個鏡像檔案,boot空間大小保持不變, root空間大小根據實際使用的大小增加一定備援空間, 我取的30%備援空間

種。    

    sudo dd if=/dev/zero of=rspi-backup.img bs=1K count=$totalsz

    (totalsz =  42030+4449932*1.3)

8 對虛拟鏡像檔案進行分區, 首先檢視完整SD卡鏡像的分區資訊

Raspberrypi 3 系統備份還原, 基于最小系統鏡像實作Raspberrypi 3 備份還原系統

可以獲知, root分區從8192開始, 到93596結束,共85405塊, root分區從94208開始,到15126527結束

我們建立的boot分區和這個保持一緻, 需要縮減的是root分區, 是以我們分區如下

sudo parted rspi-backup.img --script -- mklabel msdos
sudo parted rspi-backup.img --script -- mkpart primary fat32 8192s 93596s
sudo parted rspi-backup.img --script -- mkpart primary ext4  94208s -1
           

-1(這裡是數字1  不是字母L)

然後将此空鏡像檔案挂載進系統進行格式化

losetup -f --show rspi-backup.img
kpartx -va /dev/loop1
sudo mkfs.vfat /dev/mapper/loop1p1 -n boot
sudo mkfs.ext4 /dev/mapper/loop1p2
           

最後如下圖所示

Raspberrypi 3 系統備份還原, 基于最小系統鏡像實作Raspberrypi 3 備份還原系統

最下面的就是剛才挂載進來的空鏡像,(圖中我挂載的是已經制作好的鏡像,是以已用空間不是0)

PS: Ubuntu在執行kpartx -va ***.img後會自動挂載裡面的分區, 如果你在系統裡面找不到, 請手動挂載。

10 現在SD卡完整鏡像和空鏡像都挂載進來了, 直接完整拷貝檔案

sudo cp -rfpd $src_boot_path/* $dst_boot_path
sync
sudo cp -rfpd $src_root_path/* $dst_root_path
sync
           

其中 的src_boot_path等 為上圖中的boot和root路徑,自行檢視

11 關鍵的一步, 每個生成的img檔案的檔案系統uuid不一樣, 在完整拷貝的時候, 原來的鏡像裡面boot裡面記錄的UUID和root裡面記錄的UUID 是自己專有的, 拷貝到空img後, 和現有的不比對, 如果不修改的話會導緻系統boot完後無法jump到root分區, 導緻無法進入系統。是以要修改UUID

如下所示檢視UUID

Raspberrypi 3 系統備份還原, 基于最小系統鏡像實作Raspberrypi 3 備份還原系統

替換我們的img内的PARTUUID

opartuuidb=`blkid -o export ${device}p1 | grep PARTUUID`
opartuuidr=`blkid -o export ${device}p2 | grep PARTUUID`
npartuuidb=`blkid -o export ${device_dst}p1 | grep PARTUUID`
npartuuidr=`blkid -o export ${device_dst}p2 | grep PARTUUID`
echo "BOOT uuid old=$opartuuidb -> new=$npartuuidb"
echo "ROOT uuid old=$opartuuidr -> new=$npartuuidr"
sudo sed -i "s/$opartuuidr/$npartuuidr/g" $dst_boot_path/cmdline.txt
sudo sed -i "s/$opartuuidb/$npartuuidb/g" $dst_root_path/etc/fstab
sudo sed -i "s/$opartuuidr/$npartuuidr/g" $dst_root_path/etc/fstab
           

最後的解除安裝清理工作

sudo umount /dev/mapper/loop0p1
sudo umount /dev/mapper/loop0p2
sudo umount /dev/mapper/loop1p0
sudo umount /dev/mapper/loop1p1
kpartx -d /dev/loop0
kpartx -d /dev/loop1    
           

得到如下鏡像檔案

Raspberrypi 3 系統備份還原, 基于最小系統鏡像實作Raspberrypi 3 備份還原系統

拷貝到windows, 使用Win32 Disk Imager工具寫入SD卡或者dd寫入也行。 

樹莓派開機進入系統, sudo raspi-config->Advanced options->Expand Filesystem, 确認重新開機, 

分區就擴大到你現在的SD卡大小了。

最後附上完整腳本,使用的時候在腳本後輸入SD卡完整鏡像的路徑, 腳本運作完成後會在腳本運作路徑生成一個名為rspi-backup.img的鏡像檔案

#!bin/bash
# backup img file to new img file,  delete the empty space
# e.g source a.img = 8G, 
# run this script
# the new b.img maybe = 5G or less
#
# created by liubo 20170825
#
if [ -z $1 ]; then
    echo "no argument, assume the *.img file path please !"
    exit 0
else
    imgpath=$1
fi

loopdevice=`losetup -f --show $imgpath`
echo "======1 losetup img file ========"
device=`kpartx -va $loopdevice | sed -E 's/.*(loop[0-9])p.*/\1/g' | head -1`
device="/dev/mapper/${device}"
#echo $device $loopdevice
sleep 5
bootsz=`df -P | grep $device'p1' | awk '{print $2}'`
rootsz=`df -P | grep $device'p2' | awk '{print $3}'`
totalsz=`echo $bootsz $rootsz | awk '{print int(($1+$2)*1.3)}'`

echo "======2 make empty img file======"
echo 'boot :'$bootsz'K'
echo 'root :'$rootsz'K'
echo 'total:'$totalsz'K'

sudo dd if=/dev/zero of=rspi-backup.img bs=1K count=$totalsz


bootstart=`sudo fdisk -l $loopdevice | grep $loopdevice'p1' | awk '{print $2}'`
bootend=`sudo fdisk -l $loopdevice | grep $loopdevice'p1' | awk '{print $3}'`
rootstart=`sudo fdisk -l $loopdevice | grep $loopdevice'p2' | awk '{print $2}'`
echo "======3 format empty img========="
echo "boot: $bootstart >>> $bootend, root: $rootstart >>> end"
sudo parted rspi-backup.img --script -- mklabel msdos
sudo parted rspi-backup.img --script -- mkpart primary fat32 ${bootstart}s ${bootend}s
sudo parted rspi-backup.img --script -- mkpart primary ext4  ${rootstart}s -1

loopdevice_dst=`sudo losetup -f --show rspi-backup.img`
device_dst=`kpartx -va $loopdevice_dst | sed -E 's/.*(loop[0-9])p.*/\1/g' | head -1`
sleep 1
device_dst="/dev/mapper/${device_dst}"
sleep 1
sudo mkfs.vfat ${device_dst}p1 -n boot
sleep 1
sudo mkfs.ext4 ${device_dst}p2
sleep 1

echo "======4 copy file to img========="
sleep 5
src_boot_path=`sudo df -lhT | grep $device'p1' | awk '{print $7}'`
src_root_path=`sudo df -lhT | grep $device'p2' | awk '{print $7}'`

dst_boot_path=`sudo df -lhT | grep $device_dst'p1' | awk '{print $7}'`
dst_root_path=`sudo df -lhT | grep $device_dst'p2' | awk '{print $7}'`

sudo cp -rfpd $src_boot_path/* $dst_boot_path
sync

#sudo rsync -aP $src_root_path/ $dst_root_path/
sudo cp -rfpd $src_root_path/* $dst_root_path
sync

# replace PARTUUID
echo "======5 replace PARTUUID========="
opartuuidb=`blkid -o export ${device}p1 | grep PARTUUID`
opartuuidr=`blkid -o export ${device}p2 | grep PARTUUID`
npartuuidb=`blkid -o export ${device_dst}p1 | grep PARTUUID`
npartuuidr=`blkid -o export ${device_dst}p2 | grep PARTUUID`
echo "BOOT uuid old=$opartuuidb -> new=$npartuuidb"
echo "ROOT uuid old=$opartuuidr -> new=$npartuuidr"
sudo sed -i "s/$opartuuidr/$npartuuidr/g" $dst_boot_path/cmdline.txt
sudo sed -i "s/$opartuuidb/$npartuuidb/g" $dst_root_path/etc/fstab
sudo sed -i "s/$opartuuidr/$npartuuidr/g" $dst_root_path/etc/fstab


echo "Create backup img done, clear job ? Y/N"
read key
if [ "$key" = "y" -o "$key" = "Y" ]; then
    sudo umount $src_boot_path
    sudo umount $src_root_path
    sudo umount $dst_boot_path
    sudo umount $dst_root_path
    kpartx  -d $loopdevice
    losetup -d $loopdevice
    kpartx  -d $loopdevice_dst
    losetup -d $loopdevice_dst    
fi
echo "==========Done==================="
exit 0

           

如有錯誤請指正。謝謝。

參考連結

http://blog.csdn.net/talkxin/article/details/50464313