Power ShellでRedmineをバックアップする。


概要

チケット管理システム「Redimine」のデータをバックアップするPower Shellスクリプトを作成します。
データの圧縮には、フリーの圧縮ソフト「7-zip」を使います。

実装する機能

  1. 圧縮保管
    Redmineのバックアップに必要な以下のデータを圧縮して保管します。

    • 画像ファイルが入っている「files」フォルダ
    • その他の全てのデータが格納されているMySQLのDBデータ
  2. 世代管理
    3世代まで保持します。4世代以上過去のデータは削除します。

  3. ミラーリング
    外付けHDDとミラーリングし、データを2箇所で保持します。

実装方法

■ 画像フォルダのバックアップ

Power ShellのCopy-Item関数でバックアップします。

PowerShell
Copy-Item -Path (画像フォルダ) -Destination (バックアップ先) -Recurse

「-Recurse」を指定すると、フォルダ階層が深くても、再帰的にコピーしてくれます。

■ DBデータのバックアップ

MySQLに付属しているコマンド「mysqldump.exe」を使います。

コンソール
mysqldump --defaults-file=(オプションファイル) --result-file=(バックアップデータ出力先) --log-error=(エラーログ出力先) DB

引数で指定しているオプションファイルには、ログイン情報を指定します。

mysqldump-options.ini
[mysqldump]
user=xxx
password=xxx
host=xx.xx.xx.xx
port=xxx

■ バックアップデータの圧縮

フリーの圧縮解凍ソフト「7-zip」に付属しているコマンドラインツールを使って圧縮します。1
圧縮形式は7zを使用します。

コンソール
7z a -sdel (圧縮先の書庫) (圧縮元の画像フォルダ) (圧縮元のダンプファイル)

引数の意味は以下です。

  • a : addの略。ファイルを圧縮先の書庫に入れる。
  • -sdel : 圧縮処理の後、圧縮元ファイルを削除する。

■ バックアップデータのローテート

ファイル作成日付でソートし、作成日付の新しい3ファイルを残して、古いデータを削除します。

PowerShell
Get-ChildItem (バックアップフォルダ) |
Sort-Object CreationTime -Descending |
Select-Object -Skip 3 |
foreach{Remove-Item -Path $_.FullName}

■ バックアップデータのミラーリング

Windowsの付属コマンド「RoboCopy」を使います。

コンソール
ROBOCOPY (コピー元のバックアップフォルダ) (コピー先のミラーフォルダ)/MIR

引数の「/MIR」はミラーリングするという意味です。
「/MIR」を指定すると、コピー元にだけあるファイルはコピー先に作成され、コピー元にないファイルは、コピー先から削除されます。

プログラム全体の構成

PowerShellで上記の処理を順番に呼び出します。
処理に必要なパスなどの情報は、設定ファイルに記述し、実行時に読み込む事にします。
処理に必要なフォルダは、初回実行時に作成します。

フォルダ構成

以下にフォルダ構成の例を示します。
内蔵HDD内で一度バックアップし、外付けHDDにミラーリングする、という流れです。

フォルダ構成
【内蔵HDD】
C:
└─Bitnami
    └─redmineBackup
        ├─backup_files
        │      redmineBackup_YYYY-MMDD-HHMMSS.7z
        │      redmineBackup_YYYY-MMDD-HHMMSS.7z
        │      redmineBackup_YYYY-MMDD-HHMMSS.7z
        │
        ├─config
        │      settings.ini
        │      mysqldump-options.ini
        │
        ├─log
        │      dumpMySqlDataError.log
        │      redmineBackup.log
        │      
        ├─scripts
        │      redmine_backup.ps1
        │          
        └─work

【外付けHDD】
E:
└─Bitnami
    └─redmine_data_backup
            redmineBackup_YYYY-MMDD-HHMMSS.7z
            redmineBackup_YYYY-MMDD-HHMMSS.7z
            redmineBackup_YYYY-MMDD-HHMMSS.7z

ソースコード

PowerShell
# ---  ヘルパー関数  ---

function initializeFolders ($bkroot,$settings) {
    createFolderIfNotExists (Join-Path $bkroot work)
    createFolderIfNotExists (Join-Path $bkroot log)
    createFolderIfNotExists (Join-Path $bkroot backup_files)
    createFolderIfNotExists $settings.mir
}

function createFolderIfNotExists($path){
    if(-not (Test-Path $path)){
        New-Item $path -ItemType Directory
    }
}

function deleteFileIfExists($path){
    if(Test-Path $path){
        Remove-Item $path
    }
}

function getBackUpFileName($filenameExtension){
    return "redmineBackup_" + (Get-Date -Format "yyyy-MMdd-HHmmss") + "." + $filenameExtension
}

function log($message, $bkroot){
    Write-Output $message
    Write-Output ((Get-Date -Format "yyyy-MM-dd HH:mm:ss") + "  " + $message) | Out-File (Join-Path $bkRoot log\redmineBackup.log) -Encoding utf8 -Append
}

# ---  初期化  ---

# プログラムのルートフォルダを取得します。
$bkRoot = Split-Path $MyInvocation.MyCommand.Path -Parent | Split-Path -Parent

# アクセスするリソースのパスをINIファイルから読み込みます。
$s = Get-Content (Join-Path $bkRoot config\settings.ini) | ConvertFrom-StringData

# 処理対象となるフォルダを作成します。
initializeFolders $bkRoot $s

# 古いログファイルがあれば削除します。
deleteFileIfExists (Join-Path $bkRoot log\redmineBackup.log)

# ---  バックアップ  ---

log "添付ファイルが格納されているfilesフォルダをworkフォルダにコピーします。" $bkRoot
Copy-Item -Path $s.files -Destination (Join-Path $bkRoot work) -Recurse

log "mySqlに格納されているデータをworkフォルダに取得します。" $bkRoot
Start-Process -NoNewWindow `
              -FilePath $s.mysqldump `
              -ArgumentList ("--defaults-file=" + (Join-Path $bkRoot config\mysqldump-options.ini)) , `
                            ("--result-file=" + (Join-Path $bkRoot work\databaseBackUp.sql)), `
                            ("--log-error=" + (Join-Path $bkRoot log\dumpMySqlDataError.log)), `
                            $s.dbname `
              -Wait

log "取得したバックアップデータを圧縮します。" $bkRoot
$backUpFile = getBackUpFileName "7z"
Start-Process -NoNewWindow `
              -FilePath $s._7zip `
              -ArgumentList a, `
                            -sdel, `
                            (Join-Path $bkRoot work | Join-Path -ChildPath $backUpFile), `
                            (Join-Path $bkRoot work\files), `
                            (Join-Path $bkRoot work\databaseBackUp.sql) `
              -Wait


log "バックアップデータを保管用フォルダに移動します。" $bkRoot
Move-Item -Path (Join-Path $bkRoot work | Join-Path -ChildPath $backUpFile) -Destination (Join-Path $bkRoot backup_files)

log "4世代以前のバックアップファイルを削除します。" $bkRoot
Get-ChildItem (Join-Path $bkRoot backup_files) |
Sort-Object CreationTime -Descending |
Select-Object -Skip 3 |
foreach{Remove-Item -Path $_.FullName}


# ---  ミラーリング  ---


log "バックアップファイルを外付けHDDとミラーリングします。" $bkRoot
if((Join-Path $bkRoot backup_files | Get-ChildItem | Measure-Object).Count -ne 0){ # 万が一、コピー元が空で同期してしまうと、コピー先のファイルが全部消えるので。
    ROBOCOPY (Join-Path $bkRoot backup_files) $s.mir /MIR
}

設定ファイル

settings.ini
\# redmineの画像データ「files」フォルダのパス
files=X:\\Bitnami\\xxx\\apps\\redmine\\htdocs\\files

\# MySQLからデータをダンプするツール「mysqldump」
mysqldump=X:\\xxx\\xxx\\bin\\mysqldump.exe

\# 「mysqldump」コマンドを用いてダンプするデータベース・インスタンス
dbname=xxx

\# 7-zipのコマンドラインツール「7z」
_7zip=X:\\xxx\\7-Zip\\7z.exe

\# ミラーリングする外部ストレージのフォルダパス
mir=Y:\\xxx\\xxx

環境

Power Shell 5.1
7-zip 16.04
Bitnami Redmine Stack 3.3.2-2
   ・Redmine 3.3.2
   ・MySQL 5.6.35
windows 10 Home

github

ここで作成したプログラムは、以下に保存されています。
https://github.com/nogitsune413/redmineBackup

注釈


  1. PowerShellにも標準で圧縮用の関数「Compress-Archive」が入っているのですが、Compress-Archive関数のバグを報告するWebサイトの記事をいくつか見かけたので、今回は7-zipを使って実装しました。 簡単なプログラムですので、標準関数であるCompress-Archiveを使いたい方は、Power Shell内に書かれた、圧縮処理のコードを書き換えてください。

    【バグを報告しているサイト】
    powershellのcompress-archiveコマンドで作成したzipに潜むちょっとした罠
    これで解消!「KB2704299」でCompress-Archiveの文字化け対処