dreamback 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rbenv-version ADDED
@@ -0,0 +1 @@
1
+ 1.8.7-p352
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in dreamback.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Paul R Alexander
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,34 @@
1
+ # Dreamback
2
+
3
+ Dreamback is the easiest way to automate your backups on dreamhost. Dreamhost does not guarantee their backups of your users (though they've saved me with backups before), so it's best to run backups yourself.
4
+
5
+ Using Dreamback is easy:
6
+
7
+ 1. Create a user on dreamhost to schedule your backups
8
+ 2. Log in with your new user
9
+ 3. `gem install dreamback`
10
+ 4. `dreamback`
11
+ 5. Answer the questions to setup your automated backup
12
+
13
+ ## Installation
14
+
15
+ Add this line to your application's Gemfile:
16
+
17
+ gem 'dreamback'
18
+
19
+ And then execute:
20
+
21
+ $ bundle
22
+
23
+ Or install it yourself as:
24
+
25
+ $ gem install dreamback
26
+
27
+ ## Usage
28
+
29
+ Run `dreamback` to configure automated backups.
30
+ Run `dreamback backup` to immediately run a backup.
31
+
32
+ ## Contributing
33
+
34
+ I'm happy to take pull or feature requests. Use the Github issue tracker if you have suggestions or need help.
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
data/bin/dreamback ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'dreamback'
data/dreamback.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/dreamback/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Paul R Alexander"]
6
+ gem.email = ["palexander@stanford.edu"]
7
+ gem.description = %q{The easiest, cheapest way to back up your dreamhost accounts}
8
+ gem.summary = %q{Dreamback is the easiest way to automate your backups on dreamhost. Dreamhost does not guarantee their backups of your users (though they've saved me with backups before), so it's best to run backups yourself.}
9
+ gem.homepage = "https://github.com/palexander/dreamback"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "dreamback"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Dreamback::VERSION
17
+
18
+ gem.add_dependency("json")
19
+ gem.add_dependency("net-sftp")
20
+ gem.add_dependency("cronedit")
21
+ gem.add_dependency("commander")
22
+
23
+ gem.executables = %w(dreamback)
24
+ end
@@ -0,0 +1,99 @@
1
+ require 'net/sftp'
2
+
3
+ module Dreamback
4
+ class Backup
5
+ BACKUP_FOLDER = "./dreamback"
6
+
7
+ # Manage the backup process
8
+ def self.start
9
+ # rotate the backup folders
10
+ backup_to_link = rotate_backups
11
+ # rsync backup of dreamhost user accounts
12
+ rsync_backup(backup_to_link)
13
+ # mysql backup
14
+ # TODO: Add mysql backups
15
+ end
16
+
17
+ # Rotate out the backups that are past our limit
18
+ # @returns [String] name of the most recent folder to link against
19
+ def self.rotate_backups
20
+ backup_to_link = ""
21
+ Net::SFTP.start(Dreamback.settings[:backup_server], Dreamback.settings[:backup_server_user]) do |sftp|
22
+ # Create the backup folder if it doesn't exist
23
+ backup_folder_exists = false
24
+ begin
25
+ sftp.stat!(BACKUP_FOLDER) do |response|
26
+ backup_folder_exists = response.ok?
27
+ end
28
+ rescue Net::SFTP::StatusException => e
29
+ backup_folder_exists = false if e.code == 2
30
+ end
31
+
32
+ # Get a list of all backup directories
33
+ backup_folders = []
34
+ if backup_folder_exists
35
+ sftp.dir.foreach(BACKUP_FOLDER) do |entry|
36
+ # Names should be like "dreamback.20120520", so we can sort on the folder name
37
+ backup_folders << [ entry.name, entry.name.split(".")[1].to_i ] if entry.name.include?("dreamback")
38
+ backup_folders.sort! {|a,b| b <=> a}
39
+ end
40
+ end
41
+
42
+ # Get the newest folder for linking
43
+ backup_to_link = backup_folders.first[0]
44
+
45
+ # Delete any folders older than our limit
46
+ # Subtract one to account for the folder we're about to create
47
+ # Normally we remove a folder so that our count is one less than the "days to keep"
48
+ # However, if today's folder already exists then there's no need
49
+ offset = backup_folders.include?("dreamback.#{Time.now.strftime("%Y%m%d")}") ? 0 : 1
50
+ if backup_folders.length >= Dreamback.settings[:days_to_keep] - offset
51
+ folders_to_delete = backup_folders.slice(Dreamback.settings[:days_to_keep] - offset, backup_folders.length)
52
+ folders_to_delete.map! {|f| f[0]}
53
+ rsync_delete(folders_to_delete)
54
+ end
55
+ end
56
+ backup_to_link
57
+ end
58
+
59
+ # This uses a hack where we sync an empty directory to remove files
60
+ # We do this because sftp has no recursive delete method
61
+ def self.rsync_delete(directories)
62
+ empty_dir_path = File.expand_path("../.dreamback_empty_dir", __FILE__)
63
+ empty_dir = Dir.mkdir(empty_dir_path) unless File.exists?(empty_dir_path)
64
+ begin
65
+ directories.each do |dir|
66
+ `rsync --delete -a #{empty_dir_path}/ #{Dreamback.settings[:backup_server_user]}@#{Dreamback.settings[:backup_server]}:#{BACKUP_FOLDER}/#{dir}`
67
+ Net::SFTP.start(Dreamback.settings[:backup_server], Dreamback.settings[:backup_server_user]) do |sftp|
68
+ sftp.rmdir!("#{BACKUP_FOLDER}/#{dir}")
69
+ end
70
+ end
71
+ ensure
72
+ Dir.delete(empty_dir_path)
73
+ end
74
+ end
75
+
76
+ # Sync to the backup server using link-dest to save space
77
+ # @param [String] name of the most recent backup folder prior to starting this run to link against
78
+ def self.rsync_backup(link_dir)
79
+ backup_server_user = Dreamback.settings[:backup_server_user]
80
+ backup_server = Dreamback.settings[:backup_server]
81
+ today = Time.now.strftime("%Y%m%d")
82
+ Dreamback.settings[:dreamhost_users].each do |dreamhost|
83
+ # User that we're going to back up
84
+ user = dreamhost[:user]
85
+ server = dreamhost[:server]
86
+ # rsync won't do remote<->remote syncing, so we stage everything here first
87
+ tmp_dir = File.expand_path("~/.dreamback_tmp")
88
+ Dir.mkdir(tmp_dir) unless File.exists?(tmp_dir)
89
+ `rsync -e ssh -av --keep-dirlinks --copy-links #{user}@#{server}:~/ #{tmp_dir}/#{user}@#{server}`
90
+ # Now we can sync local to remote
91
+ # Only use link-dest if a previous folder to link to exists
92
+ link_dest = link_dir.nil? ? "" : "--link-dest=~#{BACKUP_FOLDER.gsub(".", "")}/#{link_dir}"
93
+ `rsync -e ssh -av --delete --copy-links --keep-dirlinks #{link_dest} #{tmp_dir}/ #{backup_server_user}@#{backup_server}:#{BACKUP_FOLDER}/dreamback.#{today}`
94
+ # Remove the staging directory
95
+ `rm -rf #{tmp_dir}`
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,22 @@
1
+ program :version, Dreamback::VERSION
2
+ program :description, 'Automated backup for dreamhost accounts'
3
+
4
+ default_command :setup
5
+
6
+ command :setup do |c|
7
+ c.syntax = 'dreamback start [options]'
8
+ c.summary = 'This will guide you through setting up Dreamback'
9
+ c.action do |args, options|
10
+ Dreamback::Initializer.setup
11
+ end
12
+ end
13
+
14
+ command :backup do |c|
15
+ c.syntax = 'dreamback backup'
16
+ c.summary = 'Run a backup process immediately. This command can also be added to a cron job.'
17
+ c.action do |args, options|
18
+ Dreamback::Initializer.setup
19
+ Dreamback::Backup.start
20
+ end
21
+ end
22
+
@@ -0,0 +1,13 @@
1
+ bin
2
+ cgi-bin
3
+ include
4
+ jabber
5
+ php5
6
+ php5dist
7
+ php5source
8
+ share
9
+ lib
10
+ logs
11
+ mail
12
+ Maildir
13
+ tmp
@@ -0,0 +1,184 @@
1
+ require 'json'
2
+ require 'net/sftp'
3
+ require 'cronedit'
4
+
5
+ module Dreamback
6
+ @settings
7
+
8
+ def self.settings
9
+ @settings
10
+ end
11
+
12
+ def self.settings=(settings)
13
+ @settings=settings
14
+ end
15
+
16
+ class Initializer
17
+ SETTINGS_LOCATION = File.expand_path("~/.dreamback")
18
+
19
+ DEFAULT_SETTINGS = {
20
+ :backup_server => "",
21
+ :backup_server_user => "",
22
+ :dreamhost_users => [ {:user => "", :server => ""} ]
23
+ }
24
+
25
+ @settings = {}
26
+
27
+ # Walks a user through the initial setup process
28
+ def self.setup
29
+ # Check to see if settings exist
30
+ load_settings(SETTINGS_LOCATION)
31
+
32
+ # Ask user questions if no settings file
33
+ if @settings.nil? || @settings.empty?
34
+ create_new_settings
35
+ save_settings(SETTINGS_LOCATION)
36
+ else
37
+ say(bold("You have already setup Dreamback. Please run \"dreamback backup\" to start a backup."))
38
+ end
39
+
40
+ # Create ssh keys if they don't exist
41
+ ssh_keys_exist = File.exists?(File.expand_path("~/.ssh/id_dsa"))
42
+ create_ssh_keys unless ssh_keys_exist
43
+
44
+ # Copy ssh keys to backup server
45
+ unless @settings[:copied_backup_server_ssh_keys]
46
+ say(bold("Copying the ssh key to your backup server, type in your password if prompted for #{@settings[:backup_server_user]}@#{@settings[:backup_server]}"))
47
+ overwrite_keys = agree(bold("WARNING: ") + "This will overwrite existing ssh keys on your backup user account. Proceed? [y/n]: ")
48
+ if overwrite_keys
49
+ sftp_password = ask("Password for #{@settings[:backup_server_user]}@#{@settings[:backup_server]}: ") { |q| q.echo = "*" }
50
+ sftp_ssh_key_upload(sftp_password)
51
+ else
52
+ say("You will need to add the ssh key yourself to automate backups")
53
+ end
54
+ @settings[:copied_backup_server_ssh_keys] = true
55
+ end
56
+
57
+ # Copy ssh keys to dreamhost servers
58
+ unless @settings[:copied_dreamhost_users_ssh_keys]
59
+ say(bold("Copying the ssh key to the dreamhost accounts you want to back up"))
60
+ @settings[:dreamhost_users].each do |dreamhost|
61
+ say(bold("Type in password if prompted for #{dreamhost[:user]}@#{dreamhost[:server]}"))
62
+ `ssh-copy-id -i #{dreamhost[:user]}@#{dreamhost[:server]}`
63
+ end
64
+ @settings[:copied_dreamhost_users_ssh_keys] = true
65
+ end
66
+
67
+ # Setup a cron job if the user would like to
68
+ unless @settings[:cron_setup_completed]
69
+ setup_cron = agree(bold("Would you like to add a cron job to automatically run the backup? [y/n]: "))
70
+ if setup_cron
71
+ crontab_email = ask("Dreamhost requires an email address to send crontab output to, please provide one: ") { |q| q.validate = /\b[A-Za-z0-9._%-\+]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}\b/ }
72
+ ct = File.open(File.expand_path("~/.dreamback_crontab"), "w+")
73
+ ct << "MAILTO=#{crontab_email}\n"
74
+ ct << "0 1 * * * dreamback backup"
75
+ ct.close
76
+ `crontab #{ct.path}`
77
+ File.delete(ct.path)
78
+ say("Cron job added. Backups will run at 1:00am Pacific every day.")
79
+ end
80
+ @settings[:cron_setup_completed] = true
81
+ end
82
+
83
+ save_settings(SETTINGS_LOCATION)
84
+
85
+ # Set global settings
86
+ Dreamback.settings = @settings
87
+ end
88
+
89
+ private
90
+
91
+ # Dreamhost doesn't allow ssh shell access, so use sftp to write to the authorized_key file
92
+ # @param [String] password for backup user
93
+ def self.sftp_ssh_key_upload(sftp_password)
94
+ Net::SFTP.start(@settings[:backup_server], @settings[:backup_server_user], :password => sftp_password) do |sftp|
95
+ # Create the .ssh folder if it doesn't exist
96
+ ssh_folder_exists = false
97
+ begin
98
+ sftp.stat!(".ssh") do |response|
99
+ ssh_folder_exists = response.ok?
100
+ end
101
+ rescue Net::SFTP::StatusException => e
102
+ ssh_folder_exists = false if e.code == 2
103
+ end
104
+
105
+ unless ssh_folder_exists
106
+ sftp.mkdir!(".ssh")
107
+ end
108
+
109
+ sftp.file.open(".ssh/authorized_keys", "w+") do |f|
110
+ f.write File.open(File.expand_path("~/.ssh/id_dsa.pub"), "r").read
111
+ end
112
+ end
113
+ end
114
+
115
+ # Ask the user for settings
116
+ def self.create_new_settings
117
+ settings = {}
118
+ say("#{bold("Server Where We Should Store Your Backup")}\nYour dreamhost backup-specific account will work best, but any POSIX server with rsync should work\n<%= color('Note:', BOLD)%> dreamhost does not allow you to store non-webhosted data except your BACKUP-SPECIFIC account")
119
+ settings[:backup_server] = ask("Server name: ")
120
+ settings[:backup_server_user] = ask(bold("Username for the backup server: "))
121
+ settings[:dreamhost_users] = []
122
+ another_user = true
123
+ while another_user
124
+ dreamhost = {}
125
+ dreamhost[:user] = ask(bold("Dreamhost user to back up: "))
126
+ dreamhost[:server] = ask(bold("Server where the dreamhost user is located: "))
127
+ settings[:dreamhost_users] << dreamhost
128
+ another_user = agree(bold("Add another dreamhost account? [y/n]"))
129
+ end
130
+ settings[:days_to_keep] = ask(bold("How many days of backups do you want to keep [1-30]? "), Integer) {|q| q.in = 1..30}
131
+ @settings = settings
132
+ end
133
+
134
+ # Create ssh keys for user when they don't exist
135
+ def self.create_ssh_keys
136
+ ssh_key_location = File.expand_path("~/.ssh/id_dsa")
137
+ say(bold("You are missing a DSA ssh key for this user at #{ssh_key_location}, we will create one now"))
138
+ say("More on creating ssh keys here: http://en.wikipedia.org/wiki/ssh-keygen")
139
+ `ssh-keygen -t dsa`
140
+ success = File.exists?(ssh_key_location)
141
+ if success
142
+ say(bold("Key created successfully"))
143
+ else
144
+ try_again = agree(bold("It looks like the ssh key creation failed, try again? [y/n]"))
145
+ if try_again
146
+ create_ssh_keys
147
+ else
148
+ say("Please try running the setup process again")
149
+ exit
150
+ end
151
+ end
152
+ end
153
+
154
+ # Load settings from a JSON file
155
+ # @param [String] path where settings are located
156
+ def self.load_settings(settings_path)
157
+ settings_file = File.open(settings_path, "r") if File.exists?(settings_path)
158
+ @settings = JSON.parse(settings_file.read, :symbolize_names => true) unless settings_file.nil?
159
+ settings_file.close unless settings_file.nil?
160
+ @settings
161
+ end
162
+
163
+ # Store settings from a JSON file
164
+ # @param [String] path where settings are located
165
+ def self.save_settings(settings_path)
166
+ settings = @settings ||= ""
167
+ settings_file = File.open(settings_path, "w+")
168
+ settings_file << JSON.pretty_generate(settings)
169
+ settings_file.close
170
+ true
171
+ end
172
+
173
+ # Setter used primarily in testing
174
+ def self.settings=(settings)
175
+ @settings = settings
176
+ end
177
+
178
+ def self.bold(text)
179
+ "<%= color('\n#{text.gsub("'", "\\'")}', BOLD) %>"
180
+ end
181
+
182
+ end
183
+
184
+ end
@@ -0,0 +1,3 @@
1
+ module Dreamback
2
+ VERSION = "0.0.1"
3
+ end
data/lib/dreamback.rb ADDED
@@ -0,0 +1,18 @@
1
+ # Use this file with a regular ruby setup (IE not from command line or bin/dreamback)
2
+
3
+ begin
4
+ # Try loading without rubygems
5
+ require 'commander/import'
6
+ require 'dreamback/version'
7
+ require 'dreamback/initializer'
8
+ require 'dreamback/backup'
9
+ require 'dreamback/base'
10
+ rescue LoadError
11
+ # If that fails, then load with it
12
+ require 'rubygems'
13
+ require 'commander/import'
14
+ require File.expand_path('../dreamback/version.rb', __FILE__)
15
+ require File.expand_path('../dreamback/initializer.rb', __FILE__)
16
+ require File.expand_path('../dreamback/backup.rb', __FILE__)
17
+ require File.expand_path('../dreamback/base.rb', __FILE__)
18
+ end
@@ -0,0 +1,58 @@
1
+ require "rubygems"
2
+ require "test/unit"
3
+ require File.expand_path('../../lib/dreamback/initializer', __FILE__)
4
+ require "json"
5
+
6
+ # Used for testing private methods
7
+ class Class
8
+ def publicize_methods
9
+ saved_private_instance_methods = self.private_instance_methods
10
+ self.class_eval { public *saved_private_instance_methods }
11
+ yield
12
+ self.class_eval { private *saved_private_instance_methods }
13
+ end
14
+ end
15
+
16
+ class DreambackTest < Test::Unit::TestCase
17
+
18
+ def test_settings_save
19
+ settings = {:testing_settings_save => "worked"}
20
+ settings_file = File.open("./test_settings.json", "w+")
21
+ Dreamback::Initializer.publicize_methods do
22
+ Dreamback::Initializer.settings = settings
23
+ Dreamback::Initializer.save_settings(settings_file.path)
24
+ end
25
+ settings_new = JSON.parse(settings_file.read, :symbolize_names => true)
26
+ assert_equal settings, settings_new
27
+ settings_file.close
28
+ File.delete(settings_file.path)
29
+ end
30
+
31
+ def test_settings_load
32
+ file = File.open(File.expand_path('../mock/settings.json', __FILE__), "r")
33
+ settings_file = Dreamback::Initializer.load_settings(file.path)
34
+ settings_test =<<-EOS
35
+ {
36
+ "backup_server_user": "blah",
37
+ "dreamhost_users": [
38
+ {
39
+ "user": "u",
40
+ "server": "d1.dev"
41
+ },
42
+ {
43
+ "user": "u2",
44
+ "server": "d2.dev"
45
+ },
46
+ {
47
+ "user": "u3",
48
+ "server": "d3.dev"
49
+ }
50
+ ],
51
+ "days_to_keep": 7,
52
+ "backup_server": "backup.dev"
53
+ }
54
+ EOS
55
+ assert_equal settings_file, JSON.parse(settings_test, :symbolize_names => true)
56
+ end
57
+
58
+ end
@@ -0,0 +1,19 @@
1
+ {
2
+ "backup_server_user": "blah",
3
+ "dreamhost_users": [
4
+ {
5
+ "user": "u",
6
+ "server": "d1.dev"
7
+ },
8
+ {
9
+ "user": "u2",
10
+ "server": "d2.dev"
11
+ },
12
+ {
13
+ "user": "u3",
14
+ "server": "d3.dev"
15
+ }
16
+ ],
17
+ "days_to_keep": 7,
18
+ "backup_server": "backup.dev"
19
+ }
metadata ADDED
@@ -0,0 +1,139 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dreamback
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Paul R Alexander
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-05-22 00:00:00 -07:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: json
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: net-sftp
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :runtime
48
+ version_requirements: *id002
49
+ - !ruby/object:Gem::Dependency
50
+ name: cronedit
51
+ prerelease: false
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ hash: 3
58
+ segments:
59
+ - 0
60
+ version: "0"
61
+ type: :runtime
62
+ version_requirements: *id003
63
+ - !ruby/object:Gem::Dependency
64
+ name: commander
65
+ prerelease: false
66
+ requirement: &id004 !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ hash: 3
72
+ segments:
73
+ - 0
74
+ version: "0"
75
+ type: :runtime
76
+ version_requirements: *id004
77
+ description: The easiest, cheapest way to back up your dreamhost accounts
78
+ email:
79
+ - palexander@stanford.edu
80
+ executables:
81
+ - dreamback
82
+ extensions: []
83
+
84
+ extra_rdoc_files: []
85
+
86
+ files:
87
+ - .gitignore
88
+ - .rbenv-version
89
+ - Gemfile
90
+ - LICENSE
91
+ - README.md
92
+ - Rakefile
93
+ - bin/dreamback
94
+ - dreamback.gemspec
95
+ - lib/dreamback.rb
96
+ - lib/dreamback/backup.rb
97
+ - lib/dreamback/base.rb
98
+ - lib/dreamback/exclusions.txt
99
+ - lib/dreamback/initializer.rb
100
+ - lib/dreamback/version.rb
101
+ - test/dreamback_test.rb
102
+ - test/mock/settings.json
103
+ has_rdoc: true
104
+ homepage: https://github.com/palexander/dreamback
105
+ licenses: []
106
+
107
+ post_install_message:
108
+ rdoc_options: []
109
+
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ hash: 3
118
+ segments:
119
+ - 0
120
+ version: "0"
121
+ required_rubygems_version: !ruby/object:Gem::Requirement
122
+ none: false
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ hash: 3
127
+ segments:
128
+ - 0
129
+ version: "0"
130
+ requirements: []
131
+
132
+ rubyforge_project:
133
+ rubygems_version: 1.6.2
134
+ signing_key:
135
+ specification_version: 3
136
+ summary: Dreamback is the easiest way to automate your backups on dreamhost. Dreamhost does not guarantee their backups of your users (though they've saved me with backups before), so it's best to run backups yourself.
137
+ test_files:
138
+ - test/dreamback_test.rb
139
+ - test/mock/settings.json