sysadmin向けPowershellスクリプト:サーバーからPCへバックアップを定期的にダウンロード

最近、友人が突然廃業したVPSプロバイダーの問題に遭遇しました。残念ながら、安価さを求めて、彼女はニッチなプロバイダーを選びました。データダウンロード機能すら提供していませんでした。さらに悪いことに、彼女は定期的にバックアップをダウンロードする習慣もありませんでした。一晩で、彼女のサイトはそこに保存されていたすべてのデータとともに消えてしまいました。

これにショックを受け、私はサーバーからローカルコンピューターに定期的にバックアップを自動的にダウンロードするPowershellスクリプトを作成しました。追加のオブジェクトストレージサービスを購入したり、他のものにお金をかけたりする必要はありません。必要なのは、定期的に使用している(そして十分なハードドライブ容量がある)インターネットに接続されたWindowsコンピューターだけです。

スクリプトは5日前のバックアップを自動的にクリーンアップします。自動バックアップ間隔とローカルに保存される最も古いバックアップ時間をニーズに合わせて設定できます。

$ssh_port = 22
$ssh_address = "username@your.site"

Write-Output "Discourseバックアップダウンロードタスクを開始します..."
Write-Output "------------------------"
Write-Output "最新のバックアップファイルをフェッチしています..."
Write-Output ""

while ($true) {
    $filename = ''

    while ($true) {
        try {
            Write-Output "> ssh -p $ssh_port $ssh_address 'cd /var/discourse/shared/standalone/backups/default/ && ls -t | head -n 1'"
            Write-Output ""
            $filename = ssh -p $ssh_port "$ssh_address" 'cd /var/discourse/shared/standalone/backups/default/ && ls -t | head -n 1'
            break
        }
        catch {
            $filename = ''

            Write-Output "フェッチに失敗しました... ログはこちらです:"
            Write-Output "-------------"
            Write-Output $_

            $answer = Read-Host "再フェッチしますか? (y/N)"
            if ($answer -ne 'y') {
                break
            }
            Write-Output ""
        }
    }

    if ([String]::IsNullOrEmpty($filename)) {
        Write-Output "エラー: ファイル名 $filename のフェッチに失敗しました"
        Write-Output ""
        $answer = Read-Host 'リトライしますか?(y/N)'

        if ($answer -eq 'y') {

        }
        else {

            exit 1
        }

    }
    else {
        Write-Output "最新のバックアップ: $filename"
        Write-Output ""

        $need_download = $true
        if (Test-Path ".\backups\$filename") {
            $answer = Read-Host ".\backups\$filename は既に存在します。再度ダウンロードしますか?(y/N)"
            Write-Output ""
            if ($answer -ne 'y') {
                $need_download = $false
            }
        }
        if ($need_download) {
            Write-Output "ダウンロードを開始します..."
            Write-Output ""

            while ($true) {
                try {
                    Write-Output "scp -p $ssh_port ${ssh_address}:/var/discourse/shared/standalone/backups/default/$filename .\\backups\\"
                    Write-Output ""

                    scp -p $ssh_port "${ssh_address}:/var/discourse/shared/standalone/backups/default/$filename" .\\backups\

                    Write-Output "ダウンロードが完了しました"
                    Write-Output ""

                    break
                }
                catch {

                    Write-Output "ダウンロードに失敗しました >_< ... 以下のログを参照してください:"
                    Write-Output ""

                    Write-Output $_

                    $answer = Read-Host "再度ダウンロードしますか? (y/N)"
                    Write-Output ""
                    if ($answer -ne 'y') {
                        break
                    }
                }
            }
        }

        Write-Output "古いバックアップファイルのクリーンアップを試行します..."
        Write-Output ""

        $count = 0
        $backupfiles = Get-ChildItem -Path .\\backups\

        foreach ($file in $backupfiles) {
            if ($file.CreationTime -le (Get-Date).AddDays(-5)) {
                try {
                    Write-Output "古いバックアップファイル $file を削除します ..."
                    Write-Output ""
                    $file.Delete()
                    $count = $count + 1
                } catch {
                    Write-Output "古いバックアップファイル $file の削除中にエラーが発生しました >_<"
                    Write-Output "-------------------"
                    Write-Output $_
                    Write-Output "-------------------"
                }
            }
        }

        if ($count -ge 0) {
            Write-Output "$count 個の古いバックアップファイルをクリーンアップしました"
            Write-Output ""
        }
        else {
            Write-Output "クリーンアップする必要のある古いバックアップファイルはありません"
            Write-Output ""
        }

        Pause

        exit 0

    }

}

上記のスクリプトを scriptname.ps1 として、バックアップをダウンロードしたいパスに保存します。「PowerShellで実行」を試してみてください。成功したら、次のステップに進むことができます。

タスクのスケジュール設定

  1. 「タスクスケジューラ」を検索します。
  2. 「タスクの追加」をダブルクリックします。タスクスケジューラウィザードが表示されます。
  3. 「次へ」をクリックし、「参照」をクリックします。「スケジュールするプログラムの選択」ダイアログが表示されます。
  4. 作成したスクリプトに移動し、クリックしてから「開く」をクリックします。タスクスケジューラウィザードに戻ります。
  5. タスクに名前を付けるか、デフォルトの名前(ファイル名)のままにして、スクリプトを実行する頻度を指定してから、「次へ」をクリックします。
  6. 開始時刻と日付(「毎日」、「毎週」、「毎月」などを指定した場合)および繰り返し間隔を指定してから、「次へ」をクリックします。この項目は、Discourseの自動バックアップサイクルと一致する必要があります。
  7. スクリプトを実行するアカウントのユーザー名とパスワードを入力してから、「次へ」をクリックします。
  8. 詳細設定を構成する場合は、チェックボックスを選択してから「完了」をクリックします。
「いいね!」 15

リモートバックアップの重要性を改めて認識させられます。別のメタユーザーも数ヶ月前に同様の問題を経験しました。

私のインスタンスの1つは、組み込みのS3バックアップ機能を使用しており、他のインスタンスはRcloneとCRONタスクを使用してGoogle Driveにバックアップを送信しています。このためのガイドがあります:Use rclone to sync backups to Dropbox or Google Drive

スクリプトを共有してくれてありがとう、Linca :+1:

「いいね!」 3

確かに、非常に良いリマインダーです。バックアップファイルの最終アクセス時刻が古すぎる場合にチェックして警告するよう、アップデート機能に組み込めることを提案しました(こちら)。2〜3ヶ月に一度アップデートするので、そのチェックは頻繁には実行されず、アップデートのタイミングは安全なバックアップがあることを確認するのに良い時期です。バックアップファイルをどこかにコピーする手段は、最終アクセス時刻を更新するはずです。

「いいね!」 3

良い点ですね。スクリプトありがとうございます。rsyncのバージョンを共有します :slight_smile:

以下の例では、sqlと添付ファイルは別々に同期されます。添付ファイルをバックアップファイルに含める必要はありません。古いバックアップを削除する必要もありません。

cwrsync をインストールするために Chocolatey を使用しています。インストール後、cmdファイルに数行を追加し、スケジュールを作成するだけで済みます。

C:\ProgramData\chocolatey\lib\rsync\tools\cwrsync.cmd

例:

rsync -rvm --delete --ignore-errors --ignore-existing --size-only --chmod=ugo=rwX -e "ssh -i /cygdrive/c/Users/user1/.ssh/id_rsa" login@host:/var/discourse/shared/standalone/backups/default/ /cygdrive/d/backup/forum/db/

rsync -rvm --delete --ignore-errors --ignore-existing --size-only --chmod=ugo=rwX -e "ssh -i /cygdrive/c/Users/user1/.ssh/id_rsa" login@host:/var/discourse/shared/standalone/uploads/ /cygdrive/d/backup/forum/uploads/

注1:C: の代わりに /cygdrive/c/ を使用してください。参照と構文については、cmdファイルをお読みください。

注2:sshキー(PEM形式)の代わりにパスワードを使用するようにコマンドを変更できます。

注3:dump/ の後にスラッシュを付けないと、rsyncはフォルダ全体をコピーしますが、付けるとフォルダの内容のみをコピーします。

注4:chocolateyがcwrsyncをアップグレードすると、以前のcmdのバックアップが同じフォルダに作成されます。バックアップを継続するには、手動で新しいcmdにコマンドをコピーする必要があります。

重要:OPが書いたのと同じルールをスケジューラに設定してください。スケジューラインは次のとおりです。

C:\ProgramData\chocolatey\lib\rsync\tools\cwrsync.cmd >> d:\backup\cwrsync.txt 2>&1

これにより、出力がファイル cwrsync.txt に追加されます。以上です。

「いいね!」 3

私は、このようなことが突然起こるのではないかという偏執的な考えから、rclone を設定してバックアップをリモート (自宅のセルフホスト NAS) に自動的にコピーする習慣があります。この投稿は、すべての卵を 1 つのバスケットに入れてはいけないというリマインダーとして非常に役立ちます。

「いいね!」 5

ついでに、/var/discourse/containers のすべてのコピーも入手する必要があります。

「いいね!」 4

良いアイデアです!アップデートをデプロイします

「いいね!」 3

「スケジュールされたタスク」はどこで検索すればよいですか? (初心者です、よろしくお願いします)

「スケジュールされたタスク」はタスクスケジューラのことです。Windows 10以降では、Windowsキーを押して直接入力することで検索できます。

「いいね!」 2

こんにちは。このエラーはどうして発生するのですか?

これを読んでいる方へ。OPの指示で少し苦労したので、より良い/最新の指示を以下に示します。

  1. このスクリプトを保存するフォルダを作成/使用します。次に、そのフォルダ内に backup という名前のフォルダを作成します

  2. スクリプトをメモ帳にコピー/貼り付けます。注意: VPSを使用している場合は、VPSのユーザー名とパブリックIPアドレスを $ssh_address = \"username@your.site\" の行に使用します。anyname.ps1 として保存し、ドロップダウンボックスを「すべてのファイル」に設定することを忘れないでください。以下に示します。
    hhhh

  3. Windowsキー ⊞ を押して「タスク」と入力し、「タスクスケジューラ」をクリックします。

  4. 右側で「基本タスクの作成」をクリックします。

  5. 任意の名前/説明を入力します。

  6. 毎日/毎週/毎年などのオプションを選択します。毎日をお勧めします。

  7. 実行したい日時を設定します。繰り返しを1に設定します。必ず Discourseサイトの管理設定 > バックアップに移動してください。自動バックアップを有効にする の横にあるチェックボックスをオンにします。
    次に、バックアップ頻度 を好きなように設定します。1をお勧めしますが、重要なのは タスクスケジューラの設定と一致させることです。バックアップの日付を一致させます。例: タスクスケジューラを毎日バックアップするように設定した場合、管理バックアップの バックアップ頻度 設定を 1 に設定します。

  8. 「プログラムの開始」のデフォルト設定のままにして、次に進みます。

  9. 「参照」をクリックして、保存したスクリプトを見つけます。次に進みます。

  10. 「完了」をクリックします。
    テストしたい場合は、スクリプトを右クリックして「PowerShellで実行」を選択します。

注意: 「指定されたファイルが見つかりません」というエラーが発生した場合は、PowerShellに表示されているようにパスワードを入力すると、いずれにせよ見つかります。

「いいね!」 1

PSスクリプトを保存し(2行目にユーザー名とIPアドレスを編集しただけです)、右クリックして「PowerShellで実行」を選択すると、何も起こりません。
一瞬画面がフラッシュします。もしすでにWin11でターミナルが開いていれば、そのターミナルが前面に来ます。それだけです。一瞬のフラッシュです。

スクリプトにユーザー名とIPアドレスを指定しましたが、SSHキーを使ってログインし、バックアップを取得するにはどうすればよいのか疑問に思っていました。

よろしくお願いします。

OPと45thj5ejのソリューションの両方を試しましたが、どちらも私には機能しません。PowerShellでスクリプトを実行すると、「/backups/」と子項目が見つかりません。また、OPがユーザー名とパスワードを入力するように言っている場所を見つけることができません。VPSでDiscourseを実行しています。