WSL対応 rsyncでリモートバックアップや世代管理するPowerShellスクリプト

更新履歴 見出しにジャンプ
日時 | 内容 |
---|---|
2019-08-27 |
|
2019-10-20 |
|
2020-07-30 |
|
記事更新履歴とは別に、スクリプト自体のコミットログは下記にある
Commits · nyanshiba/AutoBackupWSL
TL;DR 見出しにジャンプ
- WindowsのWSLでもLinuxでもrsyncが動作
- インクリメンタル世代管理バックアップで容量削減
- 世代管理しない差分コピーで大容量ファイルをバックアップ
- リモートバックアップ
- ディレクトリを選んでバックアップ
- ディレクトリ毎に前処理、後処理をプログラムする(例: バックアップ直前にアプリケーションを停止)
- DiscordやSlackに通知
これ全部できます。
![]() |
![]() |
動作確認環境
- Windows 10 Pro 1809 (WSL1 Ubuntu 18.04.2 LTS)
- Windows 10 Pro 2004 (WSL2 Ubuntu 20.04 LTS)
- Ubuntu 18.04.2 LTS
- Ubuntu 20.04 LTS
導入 見出しにジャンプ
1.まずはPowerShellのシンタックスハイライトが機能するテキストエディタをインストールしておきましょう。
オススメはVSCodeです。
https://code.visualstudio.com/Download
2.PowerShell Coreをインストールします。Windows PowerShellは古いので動作しません。
https://docs.microsoft.com/ja-jp/powershell/scripting/install/installing-powershell-core-on-windows
https://docs.microsoft.com/ja-jp/powershell/scripting/install/installing-powershell-core-on-linux
3.GitHubからスクリプトをダウンロードします。
https://github.com/nyanshiba/AutoBackupWSL
4.スクリプト内の#ユーザ設定
を編集します。
スクリプト内の設定例と照らし合わせつつ行うと良いでしょう。
設定1 | グローバル設定 | |
---|---|---|
項目 | 解説 | |
OS | pwshを実行するオペレーティングシステムを指定します。 Linuxでは rsync 、Windowsではwsl rsync が使用されます。Windowsでは関数 ConvertTo-WslPath によってWindowsからLinuxのフォーマットにパスが変換されます。 |
|
DateTime | ディレクトリの日付ではなく、ディレクトリ名によって世代管理を行うため、わからない場合は変更しないでください。 | |
Log | Path | ログを出力するディレクトリを指定します。 OSに合わせたパスのフォーマットにしてください。 |
CntMax | ログローテートの閾値を設定します。 | |
Post | hookUrl | Discord や Slack のWebhook URLを指定します。 |
設定2 | 各ディレクトリ毎の設定 | |
---|---|---|
項目 | 解説 | |
BeginScript EndScript |
処理順は 設定1 -> 設定2 -> 関数の定義 -> BeginScript -> MirList -> GenList -> EndScript 設定1の内容は設定2の中で使用可能( $Settings.DateTime 等)前処理、後処理ではPowerShellのコマンドレット以外に以下の独自の関数が使用可能です。 WebhookにPostするSend-Webhook # 既定値
Send-Webhook -UserName "AutoBackupWSL.ps1" -Content "投稿内容が未指定です" -hookUrl $Settings.Post.hookUrl
# 使用例
Send-Webhook -Content "Backup $($Settings.DateTime.Replace('\','')) Started."
トースト通知を行うSend-Toast # 既定値
Send-Toast -Icon "$PSHome\assets\Powershell_black.ico" -Title "AutoBackupWSL.ps1" -Text "通知内容が未指定です"
# 使用例
Send-Toast -Icon "$PSHome\assets\Powershell_av_colors.ico" -Text "バックアップ終了"
wslpath -uの上位互換ConvertTo-WslPath ConvertTo-WslPath -Path "Windowsのパス"
stdout、stderrを正しく受け取れるプロセス起動関数Invoke-Process # Windows
Invoke-Process -File "実行ファイルパス" -Arg "引数"
# Linux
Invoke-Process -File "実行ファイルパス" -ArgList "引数","引数"
|
|
MirList | *は必須。 ローカル/リモートの差分バックアップ用の設定 |
|
SrcPath* | コピー元のパスを指定します。 |
|
SrcClude | コピー元の中から除外または含める条件を指定します。 | |
DstPath* | コピー先のパスを指定します。 | |
Execute | リモートバックアップ時はSSHに関する引数を指定します。 | |
Begin | このディレクトリのバックアップの直前に実行されるカスタム処理を追加できます(ScriptBlock)。 | |
End | このディレクトリのバックアップの直後に実行されるカスタム処理を追加できます(ScriptBlock)。 | |
GenList | *は必須。 ローカルのインクリメンタル世代管理バックアップ用の設定 |
|
SrcPath* | コピー元のパスを指定します。 rsync -av --delete --delete-excluded $Clude --link-dest=`"$Link`" `"$Src`" `"$Dst`" |
|
SrcClude | コピー元の中から除外または含める条件を指定します。 | |
DstParentPath* | 世代管理先の親ディレクトリのパスを指定します。 | |
DstGenThold | 同じDstParentPathの最初*に世代数の閾値を指定します。 | |
DstGenExclude | 同じDstParentPathの最初に世代管理先の親ディレクトリと同等の場所に除外すべきディレクトリがある場合はここで指定します。 | |
DstChildPath | 世代管理先に作られるディレクトリを指定します。 | |
Begin | このディレクトリのバックアップの直前に実行されるカスタム処理を追加できます(ScriptBlock)。 | |
End | このディレクトリのバックアップの直後に実行されるカスタム処理を追加できます(ScriptBlock)。 |
Minecraftサーバの自動バックアップに使用した場合の設定例
5.#WSLのWindows上のファイルアクセス権の通り、wsl.confを編集します。
使い方 見出しにジャンプ
1.pwshで直に実行
./AutoBackupWSL.ps1 C:\bin\AutoBackupWSL.ps1
2.cmdやshからpwshを実行
pwsh AutoBackupWSL.ps1
3.タスクスケジューラやcronで自動実行
プログラムの開始: "C:\Program Files\PowerShell\6\pwsh.exe"
引数の追加: -WindowStyle Hidden -File "C:\bin\AutoBackupWSL.ps1"
(例)
# cronの設定ファイルを開く
vi /etc/crontab
# 毎日9時に実行
0 9 * * * minecraft /usr/bin/pwsh /home/minecraft/Servers/AutoBackupWSL.ps1
解説 見出しにジャンプ
なぜrobocopy・scpではなくrsyncなのか 見出しにジャンプ
-
robocopyはインクリメンタルバックアップに対応していない。
差分コピーは出来るが、コピー元・コピー先以外のディレクトリを参照してインクリメントバックアップを行うことは出来ない。
4TBのHDDに、1世代数百GBを数ヶ月分世代管理できるのはrsyncあってのもの。
実はrobocopyで擬似的にそれを再現した未公開のスクリプトがあるのだが...rsyncの方が上位互換であり存在意義が無くなってしまった。 -
scpは差分コピーに対応していない。
毎回フルバックアップ、というのはちょっと。
これを自前実装するのは困難と考え、WSL rsyncをWindowsのバックアップに使えるようにしたのがAutoBackupWSL.ps1。
なぜWindows PowerShellではなくPowerShell Coreなのか 見出しにジャンプ
-
日本語環境ではWindows PowerShellはASCII、PowerShell CoreはUTF-8。
Windowsにおける文字列恐怖症の貴方なら分かってくれるはず。うん。 -
Invoke-Command
でPowerShell Remorting over SSHができない。
ほぼ決定打。 -
Remove-Item
でパスが260char(文字)以上のアイテムが含まれるディレクトリを削除できない。
かなりしんどい。
因みにWSL上のpwshの場合260byte~不可。
WSLのWindows上のファイルアクセス権 見出しにジャンプ
初期設定では以下の通り。
/bin/mount -l
C:\ on /mnt/c type drvfs (rw,noatime,uid=1000,gid=1000,case=off)
wsl.confで以下の設定を行うことで、アクセス権が得られる。
# /etc/wsl.confを作成、以下のように設定
vi /etc/wsl.conf
[automount]
enabled = true
root = /mnt/
options = "rw,noatime,uid=1000,gid=1000,umask=22,fmask=11"
mountFsTab = true
# 確認
/bin/mount -l
C:\ on /mnt/c type drvfs (rw,noatime,uid=1000,gid=1000,umask=22,fmask=11,case=off)
https://devblogs.microsoft.com/commandline/chmod-chown-wsl-improvements/
インクリメンタルとリモートバックアップは同時にできない 見出しにジャンプ
リモートバックアップと--link-dest
を一行に書いたら、フルバックアップになってしまいました。
リモートバックアップの世代管理を行う場合、rsyncで差分リモートバックアップしてから、ローカルにあるリモートのファイルをインクリメンタルバックアップする、という流れになります。
GenListにリモートバックアップ機能が無く、MirListの後にGenListを実行しているのはそれが理由です。
wslpathでは変換できないパスがある 見出しにジャンプ
WSLにはwslpath
というWindowsとLinuxのパスを相互に変換するコマンドが用意されています。
ただしこれでは全てを網羅しません。
# wslpathではドライブレターのみを変換できない
$ wslpath -u 'D:'
wslpath: D:
# \があれば正常に変換できる
$ wslpath -u 'D:\'
/mnt/d/
関数ConvertTo-WslPath
として、自前正規表現を作って対処することにしました。
function ConvertTo-WslPath
{
param
(
[String]$Path
)
#"D:"に対応
return [Regex]::Replace($Path, "^([A-Z]):(\\.*)?", { "/mnt/" + $args.Groups[1].Value.ToLower() + $args.Groups[2].Value.Replace('\','/')})
}
> ConvertTo-WslPath -Path 'D:'
/mnt/d
> ConvertTo-WslPath -Path 'D:\'
/mnt/d/
標準出力・エラー出力が記録されない 見出しにジャンプ
PowerShellにおいて、ただwsl rsync
と書くのでは、引数が長くなるにつれて正しく扱えません。
また、Start-Process
ではstdout・stderr両方を直接1つのログファイルに出力出来ません。
.NETクラスSystem.Diagnostics.Process
を使用して、安全に購読します。
以下を丸パk参考にさせて頂きました。
私のスクリプトの方は最低限必要なところと、次項で説明するLinuxへの対応が含まれています。
https://github.com/guitarrapc/PowerShellUtil/blob/master/Invoke-Process/Invoke-Process.ps1
WindowsとLinuxの引数の違い 見出しにジャンプ
# Linux上のpwshでStart-Processからrsyncを実行
Start-Process "/bin/bash" -ArgumentList "-c '/usr/bin/rsync -av --delete --link-dest=`'/home/hoge/Backup/190627_104610`' `'/home/hoge/.ssh`' `'/home/hoge/Backup/190627_121149`''"
-av: -c: line 0: unexpected EOF while looking for matching `''
-av: -c: line 1: syntax error: unexpected end of file
シングルクォートをエスケープしたり、それを更にエスケープ…というのは可読性も低く、ユーザ設定が困難になる為却下。
関数Invoke-Process
内では、WindowsではStartInfo.Arguments
、LinuxではStartInfo.ArgumentList
プロパティを使用するようにして対応。
if ($Arg)
{
#Windows
$ps.StartInfo.Arguments = $Arg
} elseif ($ArgList)
{
#Linux
$ArgList | ForEach-Object {
$ps.StartInfo.ArgumentList.Add("$_")
}
}
https://github.com/dotnet/command-line-api/issues/354#issuecomment-469071462
rsyncのexcludeが効かないと思ったら見当違いだった話 見出しにジャンプ
関数Invoke-DiffBackup
内には以下のコードがあります。
Invoke-Process -File "wsl" -Arg "/usr/bin/rsync $Execute -av --delete --delete-excluded $Clude `"$Src`" `"$Dst`""
--delete
はコピー元にあってコピー先にないものを削除する引数ですが、--exclude
されたものが既にあろうと削除しません。
--delete-excluded
を指定することによって、--exclude
されたものも含めることが出来ます。
ちゃんと--exclude
されているにも関わらず、--exclude
されてねぇ!!!と奔走しましたが、原因は斜め上にありました。
https://serverfault.com/questions/279609/what-exactly-will-delete-excluded-do-for-rsync