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

Flet のウェブアプリを Apache ウェブサーバのリバースプロキシで動かす

Python でサクッとデスクトップアプリが作れる Flet ですが、同じコードに少しの変更をするだけでブラウザで動かせるようになります。今回の記事は、公式サイトにはない、Apache ウェブサーバで Flet アプリを動かす (セルフホスティングする) 方法です。

まず簡単に Flet とは

GUI やウェブフロントエンドの知識・経験がほとんど無くても、お手軽にデスクトップアプリやウェブアプリが作れる Python のフレームワークです。本家 (?) は Flutter という、Google さんが Dart という言語向けに開発しているモバイルアプリ用フレームワークです。それを Python から利用できるようにしたのが Flet と考えて良さそうです。実際、Flet 出書いたコードから吐き出されるエラーをネットで検索すると、Flutter に関するポストが多くヒットします。本記事では Flet も Flutter も深く触れませんので、詳細は他のサイトを漁ってください。

この記事で説明すること

キモは、Apache の TCP ポート指定を利用したリバースプロキシで Flet のウェブアプリを公開する、というところです。公開と言ってもボクの環境では、LAN にある mac からブラウザでアクセスできるようにするまでですが、パブリックに公開している Apache ウェブサーバでも同様の方法で公開できます。Flet の公式サイトでは、こちらの Self Hosting で NGINX ウェブサーバを使用した公開方法が紹介されていますが、それの Apache 版ということです。本記事のタイトルそのものズバリを説明しているサイトが見つからなかったので、まとめました。

環境

  • Ubuntu 20.04 LTS
  • Apache 2.4.41

ざっくりとした手順

  1. Ubuntu サーバに、Flet アプリの実行に必要なパッケージをインストール
  2. Python の仮想環境を作り、Flet をインストール
  3. Flet アプリを準備
  4. リバースプロキシに必要なモジュールを Apache で有効化
  5. Apache のコンフィグファイルを書く
  6. 自動的に起動する設定を書く

細かい手順

Ubuntu サーバに、Flet アプリの実行必要なパッケージをインストール

公式サイトの通り、Linux で Flet を動かすためには GStreamer のインストールが必要です。ここはサクッと入れてしまいましょう。

sudo apt-get update
sudo apt-get install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libgstreamer-plugins-bad1.0-dev gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-doc gstreamer1.0-tools gstreamer1.0-x gstreamer1.0-alsa gstreamer1.0-gl gstreamer1.0-gtk3 gstreamer1.0-qt5 gstreamer1.0-pulseaudio

Python の仮想環境を作り、Flet をインストール

ボクは pipenv を使っているので、こんな感じで仮想環境を作ります。手慣れた仮想環境とバージョンで、ここもサクッとどうぞ。サポートされている Python のバージョンは 3.8 以上です。

pipenv --python 3.11
pipenv shell
pip install flet

Flet アプリを準備

とりあえず、公式からそれっぽいものを持ってきましょう。インタラクションが確認できるので、ボクはこちらの Counter app を使用しました。とりあえず counter.py として保存し、最後の行を下のように編集します。

ft.app(target=main, view=None, port=8501)

簡単に説明すると、view=None でウィンドウやブラウザによる表示を行わず、ポート 8501 で待ち受けるよう指示をしています。ポートは同一サーバ上で重複が無ければ何でもかまいません。GUI とブラウザがインストールしてある環境であれば、python3 counter.py で実行すると、http://localhost:8501 にアクセスすればウェブアプリが開くと思います。次の手順以降で、外部にウェブアプリとして公開します。

リバースプロキシに必要なモジュールを Apache で有効化

Apache でリバースプロキシを行うには、いくつか必要なモジュールを追加する必要があります。Flet の場合、web socket も利用するため、wstunnel も必要です。以下は、モジュール追加、Apache の再起動、ステータス確認を実施しています。

sudo a2enmod proxy proxy_http proxy_wstunnel headers
sudo systemctl restart apache2
sudo systemctl status apache2

Apache のコンフィグファイルを書く

この例では、クライアント (PC 等のブラウザ) から flet.dev.peddals.com にアクセスすると Flet ウェブアプリが開く構成にしています。また、別記事に書いた様にこのドメインへの接続は HTTPS 接続になるので、Apache はポート 443 で待ち受け、内部的に 8501 ポートへリバースプロキシしています。このあたりはご自身の環境に合わせて指定してください。

13-14行目の wss:// の部分はひょっとしたら環境によっては必要ないかもしれません。

<VirtualHost *:443>
	ServerName flet.dev.peddals.com

	SSLEngine on
	SSLCertificateFile /etc/letsencrypt/live/dev.peddals.com/fullchain.pem	
	SSLCertificateKeyFile /etc/letsencrypt/live/dev.peddals.com/privkey.pem

	ProxyRequests Off
	ProxyPreserveHost On

	ProxyPass /ws ws://localhost:8501/ws
	ProxyPassReverse /ws ws://localhost:8501/ws
	ProxyPass /ws wss://localhost:8501/ws
	ProxyPassReverse /ws wss://localhost:8501/ws
	ProxyPass / http://localhost:8501/
	ProxyPassReverse / http://localhost:8501/

	ErrorLog ${APACHE_LOG_DIR}/flet.error.log
	CustomLog ${APACHE_LOG_DIR}/flet.log combined

</VirtualHost>

Apache に設定を読み込ませます。

sudo apachectl configtest
sudo systemctl reload apache2
sudo systemctl status apache2

この状態で一度 python3 counter.py で実行し、別のクライアント PC からサイトへアクセスし、動作するか確認してみましょう。wss:// の行を削除して読み込みが終わらない様でしたら追加してください。

自動的に起動する設定を書く

ここは公式のやり方を参考に編集します。自分の環境に合わせたものを貼っておきます。これを fletcounter.service として、counter.py と同じディレクトリに保存しています。

[Unit]
Description=Flet Counter Service
After=network.target

[Service]
User=handsome
Group=handsome
WorkingDirectory=/home/handsome/codes/flet
Environment="PATH=/home/handsome/.local/share/virtualenvs/flet-xuR7EMBP/bin/"
ExecStart=/home/handsome/.local/share/virtualenvs/flet-xuR7EMBP/bin/python3 /home/handsome/codes/flet/counter.py

[Install]
WantedBy=multi-user.target

いじる部分 (いじった内容) は以下の通りです:

  • Description= はご自由に
  • User=Group= には自分のユーザ名 (whoami)
  • WorkingDirectory= には、counter.py のあるディレクトリのパス
  • Environment="PATH= には、python3 のあるディレクトリのパス (which python3 の出力の bin/ まで)
  • ExecStart= の最初の引数は which python3 の出力全て、次の引数には counter.py のフルパス

そして最後にサービスとして起動、有効化します。これも公式のやり方に従います。シンボリックリンクの元ファイルは、上記のファイルを指定します。

cd /etc/systemd/system
sudo ln -s /home/handsome/codes/flet/fletcounter.service
sudo systemctl start fletcounter
sudo systemctl enable fletcounter
sudo systemctl status fletcounter

以上で設定はおしまいです。クライアント PC からアクセスし、カウンターが表示されれば OK です。可能であればサーバを再起動し、起動後にもカウンターが表示されることを確認しましょう。

ハマったところ

自分の環境で当初発生していた、読み込みが一生終わらない状態を解決するのにすごく時間がかかりました。原因は、ProxyPassProxyPassReverse にそれぞれ ws://http:// の両プロトコルだけしか指定していないことでした (公式の NGINX のリバースプロキシのコンフィグにも wss:// は無い) 。wss がウェブソケットのセキュア版 (http に対する https) と気づけなかったら諦めていたと思います。– なんて言いつつ、その後ラズパイの SSD が死に、リバースプロキシの設定をやり直したところ、wss:// の 2行が無くても問題なく動くようになっていました。ナゾ。

Image by Stable Diffusion

Date:
2023年11月25日 23:02:10

Model:
realisticVision-v20_split-einsum

Size:
512 x 512

Include in Image:
cartoon, clolorful,
modern ladies working at post office classifying letters

Exclude from Image:

Seed:
4084494267

Steps:
23

Guidance Scale:
11.0

Scheduler:
DPM-Solver++

ML Compute Unit:
CPU & Neural Engine

© Peddals.com