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