OSDN Git Service

Support backup / restore
authorAkihiro Ono <a-ono@users.sourceforge.jp>
Fri, 18 Feb 2011 15:57:25 +0000 (00:57 +0900)
committerAkihiro Ono <a-ono@users.sourceforge.jp>
Fri, 18 Feb 2011 15:57:25 +0000 (00:57 +0900)
.gitignore
script/backup.bat [new file with mode: 0644]
script/lib/backup_restore.rb [new file with mode: 0644]
script/lib/service.rb [new file with mode: 0644]
script/restore [new file with mode: 0644]
script/restore.bat [new file with mode: 0644]
script/service.bat

index cd81bd3..994b789 100644 (file)
@@ -14,6 +14,7 @@ apache/conf/original/extra/httpd-ssl.conf
 apache/conf/original/extra/httpd-vhosts.conf
 apache/conf/conf.d/*.conf
 apache/logs
+backup
 config/service.yml
 hudson/hudson.xml
 hudson/home/*.log
diff --git a/script/backup.bat b/script/backup.bat
new file mode 100644 (file)
index 0000000..d6d011c
--- /dev/null
@@ -0,0 +1,4 @@
+@echo off\r
+setlocal\r
+call "%~dp0setenv.bat"\r
+ruby "%~dp0backup" %*
\ No newline at end of file
diff --git a/script/lib/backup_restore.rb b/script/lib/backup_restore.rb
new file mode 100644 (file)
index 0000000..7c77135
--- /dev/null
@@ -0,0 +1,186 @@
+require 'find'
+require 'service'
+
+module BackupRestore
+  INSTALL_DIR = File.expand_path(ENV["PACKAGE_HOME"])
+
+  def targets
+    unless @targets
+      @targets = %w[hudson opends redmine subversion]
+      @targets.delete("opends") unless Service.config["opends"]
+    end
+    @targets
+  end
+
+  def sync(src, dest, excludes=nil)
+    [src, dest].each {|d| 
+      raise "#{d} is not directory." unless File.directory?(d)
+    }
+
+    src = File.expand_path(src)
+    dest = File.expand_path(dest)
+    updated = {}
+
+    Dir.chdir(dest) {
+      Find.find(".") {|old_file|
+        next if old_file == "."
+        Find.prune if excludes && excludes.any? {|ex| old_file.include?(ex)}
+
+        new_file = File.join(src, old_file)
+        unless File.exists?(new_file)
+          puts "Remove " + File.expand_path(old_file)
+          FileUtils.rm_rf(old_file)
+          next
+        end
+
+        next if File.directory?(old_file)
+
+        old_st = File.stat(old_file)
+        new_st = File.stat(new_file)
+        if old_st.mtime != new_st.mtime || old_st.size != new_st.size
+          updated[old_file] = true
+          File.delete(old_file)
+        end
+      }
+    }
+
+    Dir.chdir(src) {
+      Find.find(".") {|from|
+        next if from == "."
+        Find.prune if excludes && excludes.any? {|ex| from.include?(ex)}
+
+        to = File.join(dest, from[1..-1])
+        next if File.exists?(to)
+
+        puts (updated[from] ? "Update " : "Add ") + to
+        if File.directory?(from)
+          Dir.mkdir(to)
+        else
+          st = File.stat(from)
+          FileUtils.cp(from, to)
+          File.utime(st.atime, st.mtime, to)
+        end
+      }
+    }
+  end
+
+  def system_or_raise(command)
+    raise "\"#{command}\" failed" unless system command
+  end
+
+  def exec(method, args)
+    method = method.to_sym
+    target, dir = args
+    unless target == "all" || targets.include?(target)
+      raise "invalid target #{target}"
+    end
+    dir = dir ? File.expand_path(dir) : File.join(INSTALL_DIR, "backup")
+
+    service_script = File.join(INSTALL_DIR, "script/service.bat")
+    system(service_script, "stop") if method == :restore
+
+    Dir.chdir(INSTALL_DIR) {
+      (target == "all" ? targets : [target]).each {|target|
+        puts "--- #{target} ---"
+
+        backup_dir = File.join(dir, target)
+        case method
+        when :backup
+          FileUtils.mkdir_p(backup_dir)
+        when :restore
+          unless File.directory?(backup_dir)
+            puts "Backup files not found"
+            next
+          end
+        else
+          raise "Invalid method #{method}"
+        end
+
+        
+        case target
+          when "hudson"
+            hudson_dir = "hudson/home"
+            excludes = ["/workspace", "./plugins", "./updates", "./war"]
+            if method == :backup
+              sync(hudson_dir, backup_dir, excludes)
+            else
+              sync(backup_dir, hudson_dir, excludes)
+            end
+
+          when "opends"
+            Dir.chdir("opends/bat") {
+              if method == :backup
+                FileUtils.rm_rf(backup_dir)
+                system_or_raise(%[backup --compress -a -d "#{backup_dir}"])
+              else
+                Dir.glob(backup_dir + "/*/").each {|d|
+                  system_or_raise(%[restore -d "#{d}"])
+                }
+              end
+            }
+
+          when "redmine"
+            backup_db = backup_dir + "/redmine.db"
+            files_dir = backup_dir + "/files"
+
+            if method == :backup
+              puts "Backup database"
+              system_or_raise(%[sqlite3 redmine/db/redmine.db ".backup '#{backup_db}'"])
+              Dir.mkdir(files_dir) unless File.exists?(files_dir)
+              sync("redmine/files", files_dir)
+            else
+              if File.file?(backup_db)
+                puts "Restore database"
+                system_or_raise(%[sqlite3 "#{backup_db}" ".restore redmine/db/redmine.db"])
+              end
+
+              sync(files_dir, "redmine/files") if File.directory?(files_dir)
+            end
+
+          when "subversion"
+            if method == :backup
+              Dir.chdir("subversion/repos") {
+                Dir.glob("*/").each {|svndir|
+                  puts "Backup repository #{svndir[0..-2]}"
+                  to = File.join(backup_dir, svndir)
+                  FileUtils.rm_rf(to)
+                  system_or_raise(%[svnadmin hotcopy #{svndir} "#{to}"])
+                }
+              }
+            else
+              svndirs = Dir.chdir(backup_dir) {Dir.glob("*/")}
+              svndirs.each {|svndir|
+                puts "Restore repository #{svndir[0..-2]}"
+                from = File.join(backup_dir, svndir)
+                to = "subversion/repos/" + svndir
+                FileUtils.rm_rf(to)
+                system_or_raise(%[svnadmin hotcopy #{from} #{to}])
+              }
+            end
+        end
+      }
+    }
+
+    system(service_script, "start") if method == :restore
+  end
+
+  def backup(args)
+    exec :backup, args
+  end
+
+  def restore(args)
+    exec :restore, args
+  end
+
+  def restore_usage
+    warn <<-EOT
+Usage: restore <target> [<source>]
+
+By default, restore from RedmineLE/backup directory.
+
+Targets:
+  all
+  #{targets.join("\n  ")}
+    EOT
+  end
+end
diff --git a/script/lib/service.rb b/script/lib/service.rb
new file mode 100644 (file)
index 0000000..879945b
--- /dev/null
@@ -0,0 +1,23 @@
+require 'yaml'
+require 'win32/service'
+
+class Service
+  def self.config
+    @config ||=
+      YAML.load_file(File.join(ENV["PACKAGE_HOME"], "config/service.yml"))
+  end
+
+  def self.names
+    @names ||= config.values.map {|h| h["service_name"]}
+  end
+
+  def self.exists?(name)
+    !!(Win32::Service.status(name) rescue nil)
+  end
+
+  def self.status(name)
+    s = Win32::Service.status(name) rescue nil
+    s && s.current_state
+  end
+end
+
diff --git a/script/restore b/script/restore
new file mode 100644 (file)
index 0000000..d2b2718
--- /dev/null
@@ -0,0 +1,40 @@
+#!/usr/bin/env ruby
+
+require 'backup_restore'
+require 'windows/authorization'
+
+class Restore
+  extend BackupRestore
+
+  def self.usage
+    warn <<-EOT
+Usage: restore <target> [<source>]
+
+By default, restore from RedmineLE/backup directory.
+
+Targets:
+  all
+  #{targets.join("\n  ")}
+    EOT
+  end
+end
+
+if ARGV.length < 1
+  Restore.usage
+  exit
+end
+
+Windows::Authorization.runas_admin
+
+print <<EOT
+=== Warning ===
+Current data will be overwritten by backup data.
+The service will be stopped and restarted automatically.
+
+=== Confirmation ===
+EOT
+print "Are you sure you want to continue? (y/[n]): "
+
+exit if STDIN.gets[0,1].downcase != "y"
+
+Restore.restore ARGV
diff --git a/script/restore.bat b/script/restore.bat
new file mode 100644 (file)
index 0000000..8e6c100
--- /dev/null
@@ -0,0 +1,4 @@
+@echo off\r
+setlocal\r
+call "%~dp0setenv.bat"\r
+ruby "%~dp0restore" %*
\ No newline at end of file
index d3d09c2..6d1dccf 100644 (file)
@@ -5,7 +5,7 @@ ruby -x "%~f0" %*
 goto :end\r
 \r
 #!ruby\r
-require 'yaml'\r
+require 'service'\r
 require 'win32/service'\r
 require 'windows/authorization'\r
 require 'highline/import'\r
@@ -14,8 +14,8 @@ PACKAGE_HOME = File.expand_path(ENV["PACKAGE_HOME"])
 \r
 module Command\r
   def install\r
-    service_names.each {|name|\r
-      if (Win32::Service.status(name) rescue nil)\r
+    Service.names.each {|name|\r
+      if Service.exists?(name)\r
         warn "Error: service #{name} already exists"\r
         exit 1\r
       end\r
@@ -23,7 +23,7 @@ module Command
 \r
     wrapper = File.join(PACKAGE_HOME, "script/wrapper.bat")\r
 \r
-    config.each {|key, conf|\r
+    Service.config.each {|key, conf|\r
       name = conf["service_name"]\r
       port = conf["port"]\r
 \r
@@ -61,7 +61,7 @@ module Command
 \r
   def uninstall\r
     stop(false)\r
-    service_names.each {|name|\r
+    Service.names.each {|name|\r
       next unless status(name)\r
 \r
       begin\r
@@ -75,8 +75,13 @@ module Command
   end\r
 \r
   def start(verbose=true)\r
-    service_names.each {|name|\r
-      next unless s = status(name, verbose)\r
+    Service.names.each {|name|\r
+      s = Service.status(name)\r
+      unless s\r
+        warn "service #{name} is not installed" if verbose\r
+        next\r
+      end\r
+\r
       if s == "running"\r
         warn "service #{name} is already running" if verbose\r
         next\r
@@ -84,8 +89,8 @@ module Command
 \r
       begin\r
         Win32::Service.start(name)\r
-        sleep 0.1 while status(name, false) == "start pending"\r
-        raise if status(name, false) != "running"\r
+        sleep 0.1 while Service.status(name) == "start pending"\r
+        raise if Service.status(name) != "running"\r
         warn "service #{name} started"\r
       rescue\r
         warn "Error: failed to start service #{name}"\r
@@ -95,8 +100,13 @@ module Command
   end\r
 \r
   def stop(verbose=true)\r
-    service_names.each {|name|\r
-      next unless s = status(name, verbose)\r
+    Service.names.each {|name|\r
+      s = Service.status(name)\r
+      unless s\r
+        warn "service #{name} is not installed" if verbose\r
+        next\r
+      end\r
+\r
       if s == "stopped"\r
         warn "service #{name} has already been stopped" if verbose\r
         next\r
@@ -104,7 +114,7 @@ module Command
 \r
       begin\r
         Win32::Service.stop(name)\r
-        sleep 0.1 while status(name, false) != "stopped"\r
+        sleep 0.1 while Service.status(name) != "stopped"\r
         warn "service #{name} stopped"\r
       rescue\r
         warn "Error: failed to stop service #{name}"\r
@@ -118,47 +128,32 @@ module Command
     start\r
   end\r
 \r
-  def status(name=nil, verbose=true)\r
-    if name\r
-      service = Win32::Service.status(name) rescue nil\r
-      warn "service #{name} is not installed" if verbose && service.nil?\r
-      return service && service.current_state\r
-    end\r
-\r
-    service_names.each {|name|\r
-      begin\r
-        status = Win32::Service.status(name).current_state\r
-        warn "#{name}: #{status}"\r
-      rescue\r
-        warn "#{name}: not installed"\r
-      end\r
-    }\r
-    nil\r
-  end\r
 end\r
 \r
 class Service\r
   extend Command\r
 \r
   class << self\r
-    def config\r
-      @config ||= YAML.load_file(File.join(PACKAGE_HOME, "config/service.yml"))\r
-    end\r
-\r
-    def service_names\r
-      @service_names ||= config.values.map {|h| h["service_name"]}\r
-    end\r
-\r
     def usage\r
       warn <<EOT\r
 Usage: service <command>\r
 \r
 Commands:\r
-  #{Command.instance_methods.sort.join("\n  ")}\r
+  #{Command.instance_methods.push("status").sort.join("\n  ")}\r
 EOT\r
       exit 1\r
     end\r
 \r
+    def display_status\r
+      names.each {|name|\r
+        if s = status(name)\r
+          warn "#{name}: #{s}"\r
+        else\r
+          warn "#{name}: not installed"\r
+        end\r
+      }\r
+    end\r
+\r
     def method_missing(name, *args)\r
       warn "Unknown command #{name}"\r
       usage\r
@@ -168,10 +163,12 @@ end
 \r
 (command, pause) = ARGV[0..1]\r
 Service.usage unless command\r
-if %w[install uninstall start stop restart].include?(command)\r
+if command == "status"\r
+  Service.display_status\r
+else\r
   Windows::Authorization.runas_admin\r
+  Service.send(command)\r
 end\r
-Service.send(command)\r
 \r
 ask("Press any key to exit ... ") {|q|\r
   q.character = true\r