Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

概要

チケット管理システム「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の文字化け対処 

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away