groovyでのサーバファイルバックアップ

2018年3月15日

ownCloudサーバ側でファイルを追加するに書いたように、ownCloudを導入した目的の一つは、サーバ内にあるプロジェクトファイルやmysqlデータベースを自動でバックアップすることである。

つまり、夜間バックアップジョブを走らせ、何らかの変更があれば、そのバックアップをownCloudの特定のディレクトリに書き込んでおく(occ file:scanを忘れずに)。あとは、昼間の間にゆっくりローカルPC(複数)にそのバックアップを取得すればよい。ここでのお題としては、以下。

  • ある特定のディレクトリ以下をtazにまとめてバックアップする。ただし、変更があった場合のみ。
  • あるmysqlデータベースをmysqldumpでダンプし、gzに圧縮してバックアップする。ただし、変更があった場合のみ。

groovyの選択

最初はシェルスクリプトで行おうとも思ったのだが、そもそもif文さえろくに使ったことの無い状態なので、groovyを使ってみることにした。gradleは使っているが、生のgroovyは初めてである。

Javaはインストール済であり、CentOS-6.9へのgroovyのインストールは割愛する。検索すればいくらでも見つかる。

cronでの実行

最終的にはcronで動作させるのだが、groovyの実行にはJAVA_HOMEの設定が必要とのことで、以下のようにした。いろいろ方法はあるようだが、これが最も簡単に思われる。

※sdkmanでgroovyをインストールした場合には、.bashrcの記述のようにsdkmanの初期化をしないといけない。以下はrootでsdkmanをインストールした場合の例。

#!/bin/sh
export JAVA_HOME=/usr/java/default
export SDKMAN_DIR="/root/.sdkman"
[[ -s "/root/.sdkman/bin/sdkman-init.sh" ]] && source "/root/.sdkman/bin/sdkman-init.sh"
/opt/backups/mysqlBackup.groovy
/opt/backups/backupDirs.groovy
sudo -u apache php /usr/share/owncloud/occ file:scan username

最後の行はownCloudに対してファイルが変更されたことを通知する。

mysqlのバックアップ

mysqlのバックアップとしては、単純にmysqldumpを使用してバックアップを取り、以前バックアップしたものと異なるかどうかを調べるしかないように思える。ただし、ここでハマったのは、mysqldumpのフラグを使用しなければならないこと。

  • –skip-dump-date

をつけないと、ダンプ日時が入ってしまうため、必ず「異なってしまう」。また、ダンプ後のファイルをgzipで圧縮しているのだが、(どういう理由かわからないのだが)全く同じファイルをgzip圧縮してもcmpで比較すると異なってしまう。これは、cmpではなくzcmpを使用することで回避できた。

コードは以下のようなものである。

#!/usr/bin/env groovy
DBPASS='dbpass'
DBBACK = '/tmp/owncloud-backup.sql.gz'
USERNAME = 'username'
DSTDIR = "/var/lib/owncloud/data/${USERNAME}/files/Backups/mysql"
DEBUG = true

def mayBackupDb(dbName) {
  ['/bin/sh', '-c', "mysqldump --skip-dump-date -u root -p${DBPASS} ${dbName} | gzip - > ${DBBACK}"].execute().waitFor()
  dstFile = "${DSTDIR}/${dbName}.sql.gz"
  int ret = "zcmp ${dstFile} ${DBBACK}".execute().waitFor()
  if (ret == 0) {
    if (DEBUG) println "${dbName} not changed"
    return;
  }
  if (DEBUG) println "${dbName} new backup"
  "cp ${DBBACK} ${dstFile}".execute().waitFor()
  "chown apache. ${dstFile}".execute().waitFor()
}

def allDatabases() {
  new File(DSTDIR).mkdirs()
  def p = ['/bin/bash', '-c', "echo 'show databases' | mysql -uroot -p${DBPASS}"].execute().text
  p.split(/\n/).each{ 
    if (['Database', 'mysql', 'information_schema'].contains(it)) return
    mayBackupDb(it)
  }
}

allDatabases()

ディレクトリのバックアップ

例えば、/var/git、/var/svnには複数のリポジトリがあり、/var/www/sitesには複数のサイトの、主にwordpress環境が格納されている。これらをそれぞれtazの形にしてバックアップする。

ここで行っていることはmysqlの場合とは少々異なる。ディレクトリの中身をtazでバックアップすることはもちろんだが、同時に「ls -lR」によって、そのディレクトリの構造すべてをリストしておく。中身を比較するのではなく、このリストを比較すれば十分だから、その分少しは高速になるだろう。

#!/usr/bin/env groovy
USERNAME = 'username'
TMPLIST = '/tmp/owncloud-backup.list'
DEBUG = true

/**
 * dstParent: like /foo/bar/Backups/git
 * srcParent: like /var/git
 * dirName: like product.git
 */
def mayBackupDir(dstParent, srcParent, dirName) {

  // src directory(directory only)
  // like /var/git/product.git
  String srcDir = "${srcParent}/${dirName}";
  if (!new File(srcDir).isDirectory()) return

  if (DEBUG) print "Checking ${srcDir} ... ";

  // list of srcDirectory in dst folder
  String dstList = "${dstParent}/${dirName}.list";

  // make temporary dir list
  String lslr = "ls -lR ${srcDir}".execute().text;
  new File(TMPLIST).write(lslr);

  // compare temporary dir list to dstList
  int ret = "cmp ${TMPLIST} ${dstList}".execute().waitFor();
  if (ret == 0) {
    if (DEBUG) println "Not Changed"
    return
  }

  // do backup
  if (DEBUG) println "Backing Up"
  "cp ${TMPLIST} ${dstList}".execute().waitFor();
  "chown apache. ${dstList}".execute().waitFor();
  String dstTaz = "${dstParent}/${dirName}.taz";
  "tar zcf ${dstTaz} -C ${srcParent} ${dirName}".execute().waitFor()
  "chown apache. ${dstTaz}".execute().waitFor();
}

/**
 * checking srcParent's subfolder & backup it to dstParent's subfolder which is the same name.
 */
def doForTheDirs(dstParent, srcParent) {
  new File(dstParent).mkdirs();
  new File(srcParent).list().each{
    mayBackupDir(dstParent, srcParent, it)
  }
}

// backup these directories
doForTheDirs("/var/lib/owncloud/data/${USERNAME}/files/Backups/git", "/var/git");
doForTheDirs("/var/lib/owncloud/data/${USERNAME}/files/Backups/svn", "/var/svn");
doForTheDirs("/var/lib/owncloud/data/${USERNAME}/files/Backups/sites", "/var/www/sites");