mac でラズパイ起動用 SD カードや USB メモリをまるごとバックアップする簡単な Python スクリプト

ラズパイで使用している SSD が突然読み込めなくなったので、おおよそ1年前にバックアップしてあったディスクイメージを別の SSD にリストアし、諸々1年分を作り直しました。当然すごく面倒だったので、外付けメディアのバックアップを取る極シンプルな mac 用 Python スクリプトを書きました。ターゲットのデバイスを指定すると、バックアップの後イジェクトまでやってくれます。

いちおう、環境

  • macOS: Sonoma 14.2
  • Python: 3.11.6
  • 追加パッケージ等: 不要

普通にやるならこんな手順

mac に記憶デバイスを接続して一覧を表示、ターゲットとなるデバイス番号を確認、デバイスをアンマウントして、バックアップ開始、終了したらイジェクトして、記憶デバイスを物理的に取り外す、というのが一連の手順になります。Ubuntu がインストールされた 128GB SSD をバックアップしたときの例はこんな感じです ($ 以降が入力するコマンド)。

$ diskutil list external
... 省略 ...
/dev/disk4 (external, physical):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     FDisk_partition_scheme                        *128.0 GB   disk4
   1:             Windows_FAT_32 system-boot             268.4 MB   disk4s1
   2:                      Linux                         127.8 GB   disk4s2
... 省略 ...
$ sudo diskutil unmountDisk /dev/disk4
Password:
Unmount of all volumes on disk4 was successful
$ sudo dd if=/dev/rdisk4 of=RPi3BP_128GB_20231218-1.img bs=6M status=process
127999672320 bytes (128 GB, 119 GiB) copied, 2054 s, 62.3 MB/s
20350+1 records in
20350+1 records out
128035674112 bytes (128 GB, 119 GiB) copied, 2054.98 s, 62.3 MB/s
$ sudo diskutil eject /dev/disk4
Password:
Disk /dev/disk4 ejected

最初のコマンドは接続されている記憶デバイスの一覧を表示するのですが、external を付けて外付けディスクのみ表示させています。複数表示される場合は、表示される SIZE の値を頼りにするか、接続前に一度 diskutil list external を実行し、メディアを接続してから再度 diskutil list external で追加されたメディアの番号を確認します。

dd コマンドの説明:

  • if は、入力ファイルを指定。上の例の /dev/rdisk4 は、シーケンシャルにアクセスされるデバイスを指定しているので、/dev/disk4 とやるより速い
  • of は、出力ファイルを指定。上の例は、コマンドを実行したディレクトリに .img の拡張子を付けて書き出し
  • bs で、一度に処理するブロックサイズを指定。m1 mac mini (16GB RAM) でいくつか値を変えてテストした結果、6M (6メガバイト) が一番安定して速い速度で処理できた
  • status=process を付けると、実行中に進捗状況が表示される

mac 専用 Python スクリプト (とは言ってもほぼシステムコマンドを逐次実行)

import subprocess
import sys
import os
from datetime import datetime

if not os.getuid()==0:
    sys.exit(">> 管理者権限が必要です。'sudo python3 clibackup.py' の形式で再度実行し、パスワードを入力してください。")

subprocess.run(["diskutil", "list", "external"])
dnum = input(">> バックアップする記憶デバイスの番号を数字で入力してください('q' を入力すると終了)。\n>> (例: /dev/disk9 の場合は 9 を入力)\n>> /dev/disk?: ")
if dnum == 'q':
    print('終了しました。')
    sys.exit()
print(f">> ターゲット /dev/disk{dnum} をマウント解除します。")
subprocess.run(["diskutil", "umountDisk", "/dev/rdisk"+dnum])

timestamp = datetime.now().strftime("%Y%m%d%H%M")
#cmd = ["dd", "if=/dev/rdisk"+dnum, f"of=/Volumes/External HDD/SD_Card_Backup/backup_{timestamp}.img", "bs=6M", "status=progress"]
cmd = ["dd", "if=/dev/rdisk"+dnum, f"of=backup_{timestamp}.img", "bs=6M", "status=progress"]

print("\n>> 以下のコマンドを実行してバックアップします:")
for i in range(len(cmd)):
    print(cmd[i], end = " ")
print("\n")

process = subprocess.run(cmd)

print(f"\n>> バックアップが完了しました。デバイス /dev/disk{dnum} をイジェクトします。")
subprocess.run(["diskutil", "eject", "/dev/disk"+dnum])

保存先ディレクトリを指定する場合は、18行目の様に of= の後にパスを入れてください。ダブルクォーテーションでくくってあるので、スペースがあってもエスケープは不要です。作られるディスクイメージのファイル名は、年月日時分を含んだ backup_YYYYmmddHHMM.img になります。実行時の「分」までファイル名に含めているため、既存のファイルを上書きすることは無いでしょう。エラー処理の一切はしていないのであしからず。

使い方

sudo python3 clibackup.py で実行します (sudo を忘れると、メッセージを表示して終了します)。パスワードを入力すると、マウントされている外付け記憶デバイスの一覧が表示されるので、バックアップしたい USB ドライブなり SD カードなりのディスク番号を入力するとバックアップがスタートします。もし対象がわからない場合、一度接続していない状態で本スクリプトを実行し q で終了、その後メディアを接続して再度スクリプトを実行すると、前回は表示されていなかった /dev/disk があるはずなので、その番号を指定します。バックアップが完了すると自動でメディアをイジェクトするので、そのまま mac から取り外してかまいません。

実行例

Raspbian が入った 16GB の micro SD カード (SanDisk Ultra Class 10 A1 MicroSD HC I) を、mac mini の内蔵 SSD にバックアップしたときの実行例です。速度は 34MB/s、大体 8分ほどで完了しています。使用する記憶デバイスや接続の仕方次第で速度は結構違います (本記事頭で使用している SSD は、62.3MB/s)。自分は 2つのパーティションに分けた外付け 4TB HDD があるので、/dev/disk6~8 も表示されていますが、これらも環境によって変わります。

% sudo python clibackup.py 
Password:
/dev/disk4 (external, physical):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     FDisk_partition_scheme                        *15.9 GB    disk4
   1:             Windows_FAT_16 RECOVERY                1.7 GB     disk4s1
   2:                      Linux                         33.6 MB    disk4s5
   3:             Windows_FAT_32 boot                    72.4 MB    disk4s6
   4:                      Linux                         14.1 GB    disk4s7

/dev/disk6 (external, physical):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      GUID_partition_scheme                        *4.0 TB     disk6
   1:                 Apple_APFS Container disk8         2.0 TB     disk6s1
   2:                 Apple_APFS Container disk7         2.0 TB     disk6s2

/dev/disk7 (synthesized):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      APFS Container Scheme -                      +2.0 TB     disk7
                                 Physical Store disk6s2
   1:                APFS Volume External HDD            1.4 TB     disk7s1

/dev/disk8 (synthesized):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      APFS Container Scheme -                      +2.0 TB     disk8
                                 Physical Store disk6s1
   1:                APFS Volume handsome's mac mini...  673.7 GB   disk8s1
   2:                APFS Volume Buffalo HDD             1.0 TB     disk8s2

>> バックアップする記憶デバイスの番号を数字で入力してください('q' を入力すると終了)。
>> (例: /dev/disk9 の場合は 9 を入力)
>> /dev/disk?: 4
>> ターゲット /dev/disk4 をマウント解除します。
Unmount of all volumes on disk4 was successful

>> 以下のコマンドを実行してバックアップします:
dd if=/dev/rdisk4 of=backup_202312231503.img bs=6M status=progress 

  15904800768 bytes (16 GB, 15 GiB) transferred 474.073s, 34 MB/s   
2532+1 records in
2532+1 records out
15931539456 bytes transferred in 474.873141 secs (33549043 bytes/sec)

>> バックアップが完了しました。デバイス /dev/disk4 をイジェクトします。
Disk /dev/disk4 ejected

記憶デバイスへの書き込み

スクリプトを書いていないので、手動でいくつかコマンドを打ってください。手順は本記事最初のサンプルと同様ですが、入出力が逆になります。dd コマンドでは、if= (入力ファイル) にディスクイメージ、of= (出力ファイル) に USB や SD カードなどの /dev/rdiskディスク番号 を指定します。出力先を間違うと取り返しがつかないことになり得るので、注意してください (サンプル通りに実行して大事なデータが消えてしまっても当方では責任とれません)。以下は、/dev/disk4 に SD カードがマウントされているときの例です。メディアを抜いた状態で diskutil list external を一度実行し、メディアをさしてから再度実行すれば、増えた /dev/disk が該当の記憶デバイスになります。

(記憶デバイスを接続していない状態で実行)
$ diskutil list external

(記憶デバイスを接続して再度実行)
$ diskutil list external
... 省略 ...
/dev/disk4 (external, physical):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     FDisk_partition_scheme                        *15.9 GB    disk4
   1:             Windows_FAT_16 RECOVERY                1.7 GB     disk4s1
   2:                      Linux                         33.6 MB    disk4s5
   3:             Windows_FAT_32 boot                    72.4 MB    disk4s6
   4:                      Linux                         14.1 GB    disk4s7
... 省略 ...
$ sudo diskutil unmountDisk /dev/disk4
Password:
Unmount of all volumes on disk4 was successful
$ sudo dd if=backup_202312231503.img of=/dev/rdisk4 bs=6M status=process

Image by Stable Diffusion

Date:
2023年12月23日 16:42:10

Model:
fruity-mix_split-einsum_compiled

Size:
512 x 512

Include in Image:
comicbook-style, cloned sheep standing side by side

Exclude from Image:

Seed:
3723203146

Steps:
25

Guidance Scale:
20.0

Scheduler:
DPM-Solver++

ML Compute Unit:
CPU & Neural Engine

© Peddals.com