猫の手なら貸せる

いろいろ共有できたらいいなとおもってます

iTunesフォルダをNASへ自動コピー

クライアントのMacbookからサーバのストレージに定期的にバックアップを取るため、cronを動かそうと考えましたが、どうもmacOSではlaunchdが推奨されるようなので、launchdを使います。

何番手封じ感はありますが、自身の備忘録がてら書いてみます。

今回実現したい内容

現在のシステムは次のようになります。ファイル共有にはSambaを利用しています。

絵です

今回はMacbookiTunesフォルダの完璧なクローンを、リモートコンピュータ(RaspberryPi4)の外付けディレクトリ上に定期的に作成します。

rsyncについて

rsyxcはmacOSに標準インストールされていますが、バージョンが古すぎて使い物にならないので、home brew 🍺 で最新版を入れます。
brew.sh

rsyncのインストール

homebrewを導入したらrsyncをインストールします。libiconvはファイル名の文字コードを合わせるために利用します。

$ brew update
$ brew install libiconv
$brew install rsync

rsyncの使い方

書式は以下になります。

$ rsync <オプション> <同期元> <同期先>

<同期元>と<同期先>にリモート上のディレクトリの指定が可能です。(user@192.168.0.2:/home/pi/など)

rsyncのオプション設定

今回はリモートディレクトリ上にホストの完全なクローンを作成するので、rsyncのオプションは以下のものを使用します。

オプション名 動作
a 全てのディレクトリに含まれるファイルをパーミッション・タイムスタンプ・その他諸々保持して処理する。
u 同期先のファイルが同期元より新しい場合はスキップする。スキップするだけで同期元の更新はしない。
z 圧縮して送信する。処理負荷を引き換えに通信速度を得る。
--delete 同期元にないファイルは同期先から削除する。取り扱い注意
--iconv=<同期元>,<同期先> 転送の際に文字コードを変換する。macOSUTF-8-Macが採用されており、UTF-8ではない。今回は--iconv=UTF-8-MAC,UTF-8を指定。ライブラリ「libiconv」が必要。
--exclude <ワイルドカードマスク> 除外ファイルを指定する。今回は.DS-Storeや._<削除したファイル名>といったMacが自動生成するごみファイルを同期しないために'.*'を設定
--log-file=<ファイル名> ログファイルを保存する。-vvvで見れる内容には日付が書いていないため、標準出力のリダイレクトではなくこちらを利用。

rsyncで実行するコマンドは次のようになります。 rsync/usr/local/bin/rsyncとして呼び出します。

$ rsync -auz --delete --iconv=UTF-8-MAC,UTF-8 --exclude '.*' --log-file=$log "$src" "$dest" 

スクリプトの作成

このコマンドを直接launchdで実行しても良いのですが、rsyncの開始時と終了時を簡単に識別したいので、出力ログに記述します。

#!/bin/bash

src=$1
dest=$2
log=$3

echo "$(date +'%Y/%m/%d %T') Start rsync $src => $dest"
/usr/local/bin/rsync -auz --delete --iconv=UTF-8-MAC,UTF-8 --exclude '.*' --log-file=$log "$src" "$dest" 
echo "$(date +'%Y/%m/%d %T') Finish rsync $src = $dest"

保存先はログインユーザーが参照可能なディレクトリならどこでも可能です。
保存した後はchmod 700 nasSync.sh等で実行権限を付与します。LaunchdAgentはログインユーザーで実行するため、所有者以外のパーティションの設定は不要です。

Lauchdの実行環境の作成

cronより書くことが多いです。cronとは違って実行できない場合はキューに保存し、実行できる時に実行してくれる機能があるらしいです。

plistファイルの作成・配置

Launchdの設定ファイル(ここではmycron.iTunesSync.plist)を~/Library/LaunchAgents/に配置します。
今回は毎日0時0分に動作するように設定しています。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
 <key>Label</key>
 <string>mycron.iTunesSync</string>
 <key>ProgramArguments</key>
 <array>
  <string> ここに.shのフルパスを入力 </string>
  <string> ここに.sh第一引数(src)を入力 </string>
  <string> ここに.sh第二引数(dest)を入力 </string>
  <string> ここに.sh第三引数(log path)を入力 </string>
 </array>
 <key>StartCalendarInterval</key>
 <dict>
  <key>Hour</key>
   <integer>0</integer>
  <key>Minute</key>
   <integer>0</integer>
 </dict>
 <key>StandardOutPath</key>
  <string> 標準出力先を入力 </string>
 <key>StandardErrorPath</key>
  <string> 標準エラー出力先を入力 </string>
</dict>
</plist>

スクリプトへの引数はタグで記述します。

SSH公開鍵を設定

Launchdでコマンドを実行するにあたり、SSH接続の際にリモートのパスワードを要求されてはうまくバックアップを作成できないので、SSH公開鍵をリモートのログインユーザーに保存します。
Macbookにて

$ mkdir ~/.ssh
$ cd ~/
$ ssh-keygen -t rsa

すると、.sshディレクトリ内にid_rsa.pubが作成されるので、これをリモートに送信します。マウント済みであればcpコマンドでよし、マウントしていなければscpコマンド等で送ります。
id_rsa.pubを送信したら、ログインユーザの.ssh/authorized_keysファイルに追記します。

$ mkdir ~/.ssh
$ cat <id_rsa.pubのパス> >> ~/.ssh/authorized_keys

最後にサーバーをknown_hostsに追加するため、一度接続を試みます。

$ ssh <リモートのauthorized_keysに登録したログインユーザー名>@<リモートのIPアドレス>

二度目の接続でパスワードを尋ねられなくなったら登録完了です。

Launchdの登録

前手順で作成したplistをlaunchdに読み込みます。(例ではmycron.iTunesSync.plist)

$ launchctl load ~/Library/LaunchAgents/mycron.iTunesSync.plist

これで全ての手順が完了です!

感想

cronがMacスリープ時に動かないからlaunchdを使う必要があったり、rsyncについて調べていたら2日くらいハマってしまい疲れました。
しばらくはログを見て今日もラズパイ生きてるなあと見守ってあげようと思います👏

トラブル対応について

ここで設定するまでにいろいろな問題が発生し、解決するために奮闘していました。
nkhnd.hatenablog.jp
以前はこの記事のおまけで記述していましたが、意外と多くの方が検索されているようだったため、問題解決を念頭に体裁を整え読みやすくしました。
この記事を読んでいて「どうしてこんな回りくどい設定方法なのだろう」「この記事の通りに設定したのにできなかった」時は、参考にしてください。