バックアップスクリプト
サーバーを人為的ミスにより壊したときに、バックアップの必要性を感じたので、スクリプトを作りました。よろしかったらどうぞ。以下のプログラムを dar-backup.js と保存して、ご利用ください。実行するには、darとRhinoとjre(Java実行環境)が必要です。
(使用される際は、プログラムの内容を理解して使ってね)
2007/10/28追記、darを使うより、rdiff-backupを使った方がいいことを知りました。
/* 本ソースコードのライセンスはBSDライセンスです。 darを使って、バックアップを行う、Rhino(JavaScript)のプログラムです。 darを使って、特定のフォルダのバックアップを行うので、 rsyncなどで、別のメディアやサーバーにコピーしてください。 人為的ミスから守るためにも、RAID以外に、毎時および毎日のバックアップは必須です。 (Javaのフォルダ)/bin/java -jar (Rhinoのフォルダ)/js.jar dar-backup.js hourly などで実行できます。hourly の部分をdailyにすると、毎日のバックアップになります。 このスクリプト自体はcronで呼び出してください。 hourly は 48〜96時間分のデータを保存します。 午前0時に、フルバックアップを行い、毎時、差分バックアップを行います。 daily は 2〜3週間分のデータを保存します。 毎週日曜日に、フルバックアップを行い、毎日、差分バックアップを行います。 バックアップを復元するには、以下の手順をとります。 dar --extract フルバックアップのファイル名(末尾の.1.darは不要) dar --extract 差分バックアップのファイル名(末尾の.1.darは不要) */ /** バックアップ対象となるフォルダ(スラッシュで終えてください)*/ var BACKUP_TARGET = "/var/backup/" /** バックアップデータの保存先フォルダ(スラッシュで終えてください)*/ var BACKUP_TO = "/root/backup/" /** 最大何世代分保存するか。もし3ならば、48〜96時間、または、2〜3週間 */ var MAX_SAVE_GENERATION = 3; /** darのパス */ var DAR_EXEC = "/usr/bin/dar" //var DAR_EXEC = "/Program Files/dar64-2.3.6-i386-windows/dar.exe"; /** Windowsならtrue、UNIXならfalse */ var IS_CYGWIN = false; /** ログのファイル名 */ var LOG_FILE = "/var/log/dar-backup.log"; /** Sendmailのパス */ var SENDMAIL_PATH = "/usr/sbin/sendmail"; //var SENDMAIL_PATH = "/bin/sendmail.exe"; /** エラーメールの From */ var MAIL_FROM = "root@localhost.jp"; /** エラーメールの送り先 */ var MAIL_TO = "hoge@me.jp"; /** 1日分のミリセカンド */ var ONE_DAY_MS = 24 * 60 * 60 * 1000; importPackage(java.lang) importPackage(java.io) main(arguments) /** メインとなる処理 */ function main(arguments) { // 第1引数はバックアップの種別 var backupType = arguments[0]; // バックアップ先フォルダ var folderName = null; var diffFileName = null; if(backupType == "hourly") { // 毎時バックアップの場合 folderName = getToday(); diffFileName = getHour(); } else if(backupType == "daily") { // 毎日バックアップの場合 folderName = getThisSunday(); diffFileName = getToday(); } if(folderName == null) return; // バックアップ元のフォルダの存在確認 if(!new File(BACKUP_TARGET).exists()) { log("ERROR", "Backup target is not exists : " + BACKUP_TARGET); System.exit(3); } // バックアップ先のフォルダ作成 var folderStr = BACKUP_TO + backupType + "/" + folderName + "/"; new File(folderStr).mkdirs(); // darを実行 execDar(folderStr, diffFileName); // 古いフォルダを削除する delOldFolders(backupType); } /** darを実行 */ function execDar(folderStr, diffFileName) { // フルバックアップがあるかどうか調べ、なければ、フルバックアップ var fullBackup = new File(folderStr + "full.1.dar"); var backupFile = null, result = 0; if(fullBackup.exists()) { // 差分バックアップ backupFile = toCygwinStyle(folderStr + diffFileName); result = runProcess([ DAR_EXEC, "--create", backupFile, "--ref", toCygwinStyle(folderStr + "full"), "--fs-root", toCygwinStyle(BACKUP_TARGET), "--gzip", "--noconf", "-Q", "--no-warn", "--slice", "2G"]); } else { // フルバックアップ backupFile = toCygwinStyle(folderStr + "full"); result = runProcess([ DAR_EXEC, "--create", backupFile, "--fs-root", toCygwinStyle(BACKUP_TARGET), "--gzip", "--noconf", "-Q", "--no-warn", "--slice", "2G"]); } if(result != 0) { log("ERROR", "Failed to create backup file (" + result + ") : " + backupFile); System.exit(1); } // 正しく書き込めたかテストする(ディスクが物理的に破損しているとここでエラーになります) result = runProcess([ DAR_EXEC, "--test", backupFile, "--noconf", "-Q"]); if(result != 0) { log("ERROR", "Failed at backup file read test (" + result + ") : " + backupFile); System.exit(2); } // バックアップ成功をログに出力 log("INFO", "Backup success : " + backupFile); } /** ログの出力 */ function log(category, message) { // ログファイルに書き出し var logStr = getMillisecond() + " " + category + " - " + message; var out = new PrintWriter(new FileWriter(LOG_FILE, true)); out.println(logStr); out.close(); // category != "INFO" ならメールする。 if(category != "INFO") { if(new File(SENDMAIL_PATH).exists()) { // メールの文面 var mail = "From: " + MAIL_FROM + "\n" + "To: " + MAIL_TO + "\n" + "Subject: Error at dar-backup.js\n" + "\n" + logStr + "\n"; // sendmailにて送信 var process = Runtime.getRuntime().exec([SENDMAIL_PATH, "-t"]); var outStream = new OutputStreamWriter(process.getOutputStream()); outStream.write(mail); outStream.close(); var inStream = process.getInputStream(); while(inStream.read() != -1); inStream.close(); process.waitFor(); } } } /** 古いフォルダを削除する */ function delOldFolders(backupType) { var listFolder = new File(BACKUP_TO + backupType); var list = listFolder.listFiles(); for(var i = 0; i < list.length; i++) { // 年-月-日 の形式にマッチするかどうか? var delFolder = list[i]; var result = delFolder.getName().match("(\\d+)-(\\d+)-(\\d+)"); if(result == null || result.length != 4) continue; // Dateオブジェクトに変換 var folderDate = new Date(result[1], result[2] - 1, result[3]); // 削除するかどうか判定し、削除実行 var diff = new Date().getTime() - folderDate.getTime(); if(backupType == "hourly") { // 毎時 if(diff >= MAX_SAVE_GENERATION * ONE_DAY_MS) { // deleteが予約語につき、特殊なメソッド呼び出し。delFolder.delete()相当。 delFolder["delete"](); } } else if(backupType == "daily") { // 毎日 if(diff >= MAX_SAVE_GENERATION * 7 * ONE_DAY_MS) { // deleteが予約語につき、特殊なメソッド呼び出し。delFolder.delete()相当。 delFolder["delete"](); } } } } /** Cygwinの場合、頭に /cygdrive/c をつける */ function toCygwinStyle(str) { if(IS_CYGWIN) { return "/cygdrive/c" + str; } else { return str; } } /** 外部プロセスの実行 */ function runProcess(args) { var process = Runtime.getRuntime().exec(args); var inStream = process.getInputStream(); while(inStream.read() != -1); inStream.close(); return process.waitFor(); } /** ミリセカンド単位の日付 */ function getMillisecond() { var d = new Date(); return d.getFullYear() + "-" + (d.getMonth() + 1) + "-" + d.getDate() + " " + d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds() + "," + d.getMilliseconds(); } /** 今何時? */ function getHour() { var d = new Date(); return d.getFullYear() + "-" + (d.getMonth() + 1) + "-" + d.getDate() + "-" + d.getHours(); } /** 今日の日付を返す */ function getToday() { var d = new Date(); return d.getFullYear() + "-" + (d.getMonth() + 1) + "-" + d.getDate(); } /** 今週の日曜日の日付を返す */ function getThisSunday() { var d = toSunday(new Date()); return d.getFullYear() + "-" + (d.getMonth() + 1) + "-" + d.getDate(); } /** 日曜日に変換 */ function toSunday(d) { d.setDate(d.getDate() - d.getDay()); return d; }