From 06d46d47bb07774ec8ed1a0161308b3bfd58e110 Mon Sep 17 00:00:00 2001 From: "Hongli Lai (Phusion)" Date: Wed, 30 Mar 2011 16:37:19 +0200 Subject: [PATCH] Write code. --- .gitignore | 2 + backups/.gitignore | 0 bin/phusion-backup | 187 +++++++++++++++++++++++++++ resources/default-files.txt | 32 +++++ resources/default-install-script.txt | 5 + 5 files changed, 226 insertions(+) create mode 100644 .gitignore create mode 100644 backups/.gitignore create mode 100755 bin/phusion-backup create mode 100644 resources/default-files.txt create mode 100644 resources/default-install-script.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e5a72e8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.DS_Store +backups diff --git a/backups/.gitignore b/backups/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/bin/phusion-backup b/bin/phusion-backup new file mode 100755 index 0000000..41771d6 --- /dev/null +++ b/bin/phusion-backup @@ -0,0 +1,187 @@ +#!/usr/bin/env ruby +module PhusionBackup + +module TUI +protected + DEFAULT_TERMINAL_COLORS = "\e[0m\e[37m\e[40m" + + def stdout + @stdout || STDOUT + end + + def print(text) + stdout.write(substitute_color_tags(text)) + stdout.flush + end + + def puts(text = nil) + if text + stdout.puts(substitute_color_tags(text)) + else + stdout.puts + end + stdout.flush + end + + def puts_error(text) + STDERR.puts(substitute_color_tags("#{text}")) + STDERR.flush + end + + def substitute_color_tags(data) + data = data.gsub(%r{(.*?)}m, "\e[1m\\1#{DEFAULT_TERMINAL_COLORS}") + data.gsub!(%r{(.*?)}m, "\e[1m\e[31m\\1#{DEFAULT_TERMINAL_COLORS}") + data.gsub!(%r{(.*?)}m, "\e[1m\e[32m\\1#{DEFAULT_TERMINAL_COLORS}") + data.gsub!(%r{(.*?)}m, "\e[1m\e[33m\\1#{DEFAULT_TERMINAL_COLORS}") + data.gsub!(%r{(.*?)}m, "\e[33m\e[44m\e[1m\\1#{DEFAULT_TERMINAL_COLORS}") + return data + end +end + +class Server < Struct.new(:hostname, :dir) +end + +class Core + ROOT = File.expand_path(File.dirname(__FILE__) + "/..") + + include TUI + + def initialize(backupdirs) + @backupdirs = backupdirs + end + + def servers + results = [] + @backupdirs.each do |dir| + Dir.foreach(dir) do |hostname| + next if hostname =~ /^\./ + fullname = "#{dir}/#{hostname}" + if File.directory?(fullname) + results << Server.new(hostname, fullname) + end + end + end + return results + end + + def find_server(hostname_or_dir) + return servers.find { |s| s.hostname == hostname_or_dir || s.dir == hostname_or_dir } + end + + def run_backup(server) + if !File.exist?("#{server.dir}/files.txt") + puts "No `files.txt` found in this backup directory." + puts + puts "Each backup directory must have such a file, in which you " + + "specify which files must be backed up. You can create " + + "a default one with:" + puts + puts " phusion-backup --generate #{server.dir}" + exit 1 + end + + filename = create_globbing_filelist(server) + begin + sh!('rdiff-backup', + '-v6', + '--exclude-sockets', + '--include-globbing-filelist', filename, + "root@#{server.hostname}::/", + "#{server.dir}/data") + ensure + File.unlink(filename) + end + end + +private + class CommandError < StandardError + end + + def sh(*args) + puts "# #{args.join(' ')}" + result = system(*args) + if result + return true + elsif $?.signaled? && $?.termsig == Signal.list["INT"] + raise Interrupt + else + return false + end + end + + def sh!(*args) + if !sh(*args) + puts_error "*** Command failed: #{args.join(' ')}" + raise CommandError + end + end + + def create_globbing_filelist(server) + filename = "/tmp/phusion-backup-#{Process.pid}.txt" + File.open(filename, 'a') do |target| + File.open("#{server.dir}/files.txt", 'r') do |source| + while !source.eof? + line = source.readline.sub(/^#.*/, '') + target.puts(line) + end + end + target.puts "- **" + end + return filename + end +end + +class App + include TUI + + def initialize(argv) + @argv = argv.dup + @stdout = STDOUT + end + + def run + core = Core.new(["#{Core::ROOT}/backups"]) + + if @argv.empty? + if core.servers.empty? + puts_error "There are no servers to backup. Please read README for usage." + else + puts_error "Please specify the server you want to backup:" + puts + core.servers.each do |server| + puts " phusion-backup #{server.hostname}" + end + puts + puts "For full usage please see `phusion-backup --help` and the README." + end + return 1 + end + + servers = [] + @argv.each do |name| + server = core.find_server(name) + if !server + puts_error "This program doesn't know anything about the server '#{name}'." + puts "If you've misspelled the name, please correct it." + puts "If you want to see a list of available servers that this programs knows about, run phusion-backup --list." + puts "For general, please refer to the README." + exit 1 + end + servers << server + end + + servers.each do |server| + puts "Backing up #{server.hostname} - #{server.dir}" + core.run_backup(server) + puts + end + + return 0 + rescue Core::CommandError + return 1 + end +end + +end + +exit(PhusionBackup::App.new(ARGV).run) \ No newline at end of file diff --git a/resources/default-files.txt b/resources/default-files.txt new file mode 100644 index 0000000..2523218 --- /dev/null +++ b/resources/default-files.txt @@ -0,0 +1,32 @@ +# Standard system stuff. +/etc/skel +/etc/profile +/etc/bash.bashrc +/etc/hostname +/etc/nanorc +/etc/screenrc +/etc/mailname +/etc/rc.local +/var/spool/cron + +# Daemon tools. +- /etc/service/*/supervise +/etc/service + +# Backup all home directories, excluding non-critical things like caches. +/root +- /home/*/.gem +- /home/*/.bundle +- /home/*/.*/bundle +/home + +# Backup all Capistrano-deployed web applications, excluding non-critical things like caches. +- /u/*/*/shared/cached-copy +- /u/*/*/shared/bundle +- /u/*/*/releases/*/.git +- /u/*/*/releases/*/vendor/bundle +- /u/*/**/*.log +/u + +# Phusion stuff. +/etc/init.d/firewall diff --git a/resources/default-install-script.txt b/resources/default-install-script.txt new file mode 100644 index 0000000..15df16a --- /dev/null +++ b/resources/default-install-script.txt @@ -0,0 +1,5 @@ +# Standard, essential tools +apt-get install bash-completion screen + +# Compiler toolchain +apt-get install build-essential gdb