bup 0.2.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 02ca6df7a29ad6290ab682bd214dc097c70e6a7a464bbbea22b1b4d100465ec7
4
- data.tar.gz: 7bf6a98055fed3353f131e56033c876860ec6f4e22b995c4f002ffedd5e8462b
3
+ metadata.gz: 3228f83fe11f72b1e77210809dec6e3d8a4ccbdc849fa756c97f525803e04357
4
+ data.tar.gz: ea48181ef6b1adb871c2b558a6bfb9bdb50ef364459ec0fc453837d33dc57589
5
5
  SHA512:
6
- metadata.gz: f96824b5eaa8fa4089497aa3eff4daf4abf5f041931fac622ac0163aed57d79036cced77b50a771fb5c675b6abaa1473e1018b9312c8e8d5935d12fc6255c601
7
- data.tar.gz: 31aaf6faab04f3e7806bc60b082aac8bd0177aa4ade5fb40e02189289e75fef8d401aaedfce4f4abd4454aeb6679c881af0391dc732169d6eb362a820c970c9a
6
+ metadata.gz: 106db1b2e171e056b9bf19763f269898a2b5eeea115ce30918353fab95147850d124ffb53bcb64dfd35c46f220c71019ac0de8a3276263f93fc79c39a42a53b6
7
+ data.tar.gz: 64217767bfc2cbcfe73b7c1963175481cdfa4d8318ee2e2f4f92317bccddb1690244e48666891a78b5aa1d61da8876f90a80c8495850c62a3800619b2a4983d4
data/.rubocop.yml CHANGED
@@ -1,5 +1,8 @@
1
1
  AllCops:
2
2
  TargetRubyVersion: 3.0
3
+ Exclude:
4
+ - lib/bup/tar.rb
5
+ - lib/bup/config.rb
3
6
 
4
7
  Style/StringLiterals:
5
8
  Enabled: true
data/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.6.0] - 2021-11-29
4
+
5
+ - Add pre_cmds list to allow executing command before backup, such as dumping a database.
6
+ - Add BUP_DESTINATION that holds the backup destination directory.
7
+ This may be used to put dumped database files in before a backup and clean
8
+ them up with a post_cmd.
9
+ - Refactor the tar.rb file for more simple individual function calls
10
+ and to do a little less redundant processing.
11
+ - Replaced --type=[full|incremental] with --incremental and --full options.
12
+
13
+ ## [0.5.0] - 2021-11-13
14
+
15
+ - Allow for a history size of 0.
16
+ - Add default_profile setting.
17
+
18
+ ## [0.4.0] - 2021-11-13
19
+
20
+ - Bug fix for empty post_cmds list.
21
+
22
+ ## [0.3.0] - 2021-11-13
23
+
24
+ - Communicate the backup file base name through the environment variable BUP_FILENAME.
25
+ - Rubocop fixes or ignore files.
26
+ - Add post commands, incase you want to default copy something around.
27
+
28
+ ## [0.2.0] - 2021-11-13
29
+
3
30
  - List profiles.
4
31
  - Bug fixes against empty backup directories.
5
32
  - Bug fix to print buffers using read_nonblock from popen3 streams.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- bup (0.2.0)
4
+ bup (0.5.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -1,43 +1,66 @@
1
1
  # Bup
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/bup`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ Bup is a simple backup driver script that uses `tar` to backup files.
4
+ The idea is to simplify creating backups, full and incremental, and leave
5
+ restoration as a project for the user to untar the file where their contents
6
+ is needed.
4
7
 
5
- TODO: Delete this and the text above, and describe your gem
6
-
7
- ## Installation
8
-
9
- Add this line to your application's Gemfile:
10
-
11
- ```ruby
12
- gem 'bup'
13
- ```
14
-
15
- And then execute:
16
-
17
- $ bundle install
18
-
19
- Or install it yourself as:
20
-
21
- $ gem install bup
8
+ Features like backup profiles, file dating, history rotation,
9
+ incremental backups, and post-processing scripting is provided.
22
10
 
23
11
  ## Usage
24
12
 
25
- TODO: Write usage instructions here
26
-
27
- ## Development
28
-
29
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
-
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
13
+ Create a file ~/.buprc with the default contents...
14
+
15
+ ```yaml
16
+ ---
17
+ default_profile: default
18
+ profiles:
19
+ default:
20
+ description: Simple backup of critical files.
21
+ include:
22
+ - "$HOME"
23
+ - /$BUP_DESTINATION/db.dump
24
+ exclude:
25
+ - "$HOME"
26
+ - "$HOME/backups"
27
+ lastrun: '2021-11-14T03:06:45 +0000'
28
+ destination: "$HOME/backups"
29
+ history: 2
30
+ tarcmd:
31
+ - tar
32
+ - cJvf
33
+ - "${BUP_FILENAME}.tar.xz"
34
+ pre_cmds:
35
+ - - dumpdatabase
36
+ - /$BUP_DESTINATION/db.dump
37
+ post_cmds:
38
+ - - ls
39
+ - $BUP_FILENAME.tar.xz
40
+ - - rm
41
+ - /$BUP_DESTINATION/db.dump
42
+ ```
32
43
 
33
- ## Contributing
44
+ This defines a single profile, "default". You can list
45
+ defined profiles by running `bup -l`. You run the default backup profile
46
+ simply by running `bup`. Other profiles must be selected on the command
47
+ line using `bup -p <profile name>`.
34
48
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/bup. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/bup/blob/main/CODE_OF_CONDUCT.md).
49
+ The default profile, as defined above, excludes both the `$HOME` and
50
+ `$HOME/backups` directories. Remove `$HOME` from the excludes list to actually
51
+ backup your entire home directory.
36
52
 
37
- ## License
53
+ ## Incremental Backups
38
54
 
39
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
55
+ By calling `bup -t incremental` an incremental backup will be run.
56
+ This runs `tar` with the `--newer` option included with the time
57
+ specified in the profile's `lastrun` setting. `Tar` will then exclude
58
+ files older than that time.
40
59
 
41
- ## Code of Conduct
60
+ ## Backup History
42
61
 
43
- Everyone interacting in the Bup project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/bup/blob/main/CODE_OF_CONDUCT.md).
62
+ Before a new backup is run, previous backups are removed. If you run many
63
+ uncompleted backups, you will eventually remove all good backups from your
64
+ backup history. This behavior is desired because backups should nearly always
65
+ succeed and we want to ensure that disk pressure is kep lower than higher.
66
+ Higher disk pressure might cause a backup to fail.
data/buprc CHANGED
@@ -1,15 +1,21 @@
1
1
  ---
2
+ default_profile: default
2
3
  profiles:
3
4
  default:
4
- description: "Simple backup of critical files."
5
+ description: Simple backup of critical files.
5
6
  include:
6
7
  - "$HOME"
7
8
  exclude:
8
9
  - "$HOME"
9
10
  - "$HOME/backups"
10
- lastrun: '2021-11-11T18:45:33 +0000'
11
+ lastrun: '2021-11-14T03:06:45 +0000'
11
12
  destination: "$HOME/backups"
12
13
  history: 2
13
14
  tarcmd:
14
15
  - sudo
15
16
  - tar
17
+ - cJvf
18
+ - "${BUP_FILENAME}.tar.xz"
19
+ post_cmds:
20
+ - - ls
21
+ - $BUP_FILENAME.tar.xz
data/exe/bup CHANGED
@@ -6,6 +6,7 @@ require "optparse"
6
6
 
7
7
  configfile = File.join(ENV["HOME"], ".buprc")
8
8
  config = Bup::Config.new
9
+ profile = nil
9
10
 
10
11
  OptParse.new do |opt|
11
12
  opt.banner = "#{$PROGRAM_NAME} #{Bup::VERSION}"
@@ -15,21 +16,32 @@ OptParse.new do |opt|
15
16
  end
16
17
 
17
18
  opt.on("-p", "--profile=profile", String, "Profile name.") do |p|
18
- config.runtime["profile"] = p
19
+ profile = p
19
20
  end
20
21
 
21
- opt.on("-t", "--type=type", String, "Type of backup.") do |t|
22
- config.runtime["type"] = t == "incremental" ? t : "full"
22
+ opt.on("-i", "--incremental", String, "Set the type of backup to incremental.") do
23
+ config.runtime["type"] = "incremental"
24
+ end
25
+
26
+ opt.on("-f", "--full", String, "Set the type of backup to full.") do
27
+ config.runtime["type"] = "full"
23
28
  end
24
29
 
25
30
  opt.on("-l", "--list", "List profiles and exit.") do
26
- config.runtime['action'] = 'list'
31
+ config.runtime["action"] = "list"
27
32
  end
28
33
  end.parse!
29
34
 
30
35
  config.load(configfile) if File.exist?(configfile)
31
36
 
32
- case config.runtime["action"]
37
+ if profile
38
+ config.runtime["profile"] = profile
39
+ elsif config.config["default_profile"]
40
+ config.runtime["profile"] = config.config["default_profile"]
41
+ end
42
+
43
+
44
+ case config.runtime["action"]
33
45
  when "list"
34
46
  config.config["profiles"].each do |key, profile|
35
47
  puts "#{key} - #{profile["description"] || ""}"
@@ -38,4 +50,4 @@ else
38
50
  tar = Bup::Tar.new(config)
39
51
  tar.call
40
52
  config.save(configfile)
41
- end
53
+ end
data/lib/bup/config.rb CHANGED
@@ -5,12 +5,13 @@ require "yaml"
5
5
  require "date"
6
6
 
7
7
  module Bup
8
+ # Configuration management.
8
9
  class Config
9
- @@timeformat = "%Y-%m-%dT%H:%M:%S %z"
10
10
 
11
11
  attr_accessor :config, :runtime
12
12
 
13
13
  def initialize
14
+ @timeformat = "%Y-%m-%dT%H:%M:%S %z"
14
15
  @runtime = {
15
16
  "profile" => "default",
16
17
  "type" => "full"
@@ -45,14 +46,14 @@ module Bup
45
46
 
46
47
  # Return the last run time of the backup or nil if there is none.
47
48
  def lastrun(name)
48
- DateTime.strptime(profile(name)["lastrun"] || "", @@timeformat)
49
+ DateTime.strptime(profile(name)["lastrun"] || "", @timeformat)
49
50
  rescue Date::Error
50
51
  nil
51
52
  end
52
53
 
53
54
  # Return the last run time of the backup or nil if there is none.
54
55
  def set_lastrun(name, date)
55
- profile(name)["lastrun"] = date.strftime(@@timeformat)
56
+ profile(name)["lastrun"] = date.strftime(@timeformat)
56
57
  end
57
58
 
58
59
  def profile(name)
data/lib/bup/tar.rb CHANGED
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "English"
4
- require "English"
5
3
  require "tempfile"
6
4
  require "open3"
7
5
  require "date"
@@ -13,9 +11,9 @@ module Bup
13
11
  end
14
12
 
15
13
  def expand_string(str)
16
- while str =~ /\${?([a-zA-Z0-9]+)}?/
17
- all = $LAST_MATCH_INFO[0]
18
- nm = $LAST_MATCH_INFO[1]
14
+ while str =~ /\${?([a-zA-Z0-9_]+)}?/
15
+ all = $~[0]
16
+ nm = $~[1]
19
17
  str = str.sub(all, ENV[nm] || "")
20
18
  end
21
19
 
@@ -34,100 +32,130 @@ module Bup
34
32
  end
35
33
 
36
34
  # Prepare the destination directory.
37
- def prepare_destination(profile_name)
38
- destination = expand_string(@config.profile(profile_name)["destination"] || ".")
39
- suffix = ".tar.xz"
35
+ def prepare_destination(profile_name, profile)
36
+ destination = profile["destination"] || "."
37
+
40
38
  type = @config.runtime["type"] || "full"
41
39
 
42
40
  FileUtils.mkdir_p(destination) unless File.exist?(destination)
43
41
 
44
42
  filename_base = "#{profile_name}-#{type}"
45
43
  filename = File.join(destination,
46
- "#{filename_base}-#{DateTime.now.new_offset(0).strftime("%Y%m%d%H%M%S")}#{suffix}")
44
+ "#{filename_base}-#{DateTime.now.new_offset(0).strftime("%Y%m%d%H%M%S")}")
47
45
 
48
46
  history = @config.profile(profile_name)["history"] || 0
49
47
 
50
- if type == "full" && history.positive?
48
+ if type == "full"
49
+ if history.positive?
50
+ prune_history(profile_name, destination, filename_base, history)
51
+ elsif history == 0
52
+ clear_history(profile_name, destination)
53
+ end
54
+ end
51
55
 
52
- oldest_kept_file = nil
56
+ ENV["BUP_DESTINATION"] = destination
57
+ ENV["BUP_FILENAME"] = filename
58
+ filename
59
+ end
53
60
 
54
- # Keep some full backups and remove the others.
55
- # We capture the oldest full backup and get rid of preceeding incrementals.
56
- Dir["#{destination}/#{filename_base}*"].sort.reverse.each_with_index do |fullbackup, idx|
57
- if idx < history
58
- oldest_kept_file = fullbackup
59
- else
60
- File.delete(fullbackup)
61
- end
62
- end
61
+ def clear_history(profile_name, destination)
62
+ Dir["#{destination}/#{profile_name}*"].each do |backup|
63
+ File.delete(backup)
64
+ end
65
+ end
63
66
 
64
- # Remove all incremental files that are older than the oldest kept full backup.
65
- if oldest_kept_file
66
- remove_before = File.stat(oldest_kept_file).ctime
67
- Dir["#{destination}/#{profile_name}*"].each do |backupfile|
68
- File.delete(backupfile) if File.stat(backupfile).ctime < remove_before
69
- end
67
+ def prune_history(profile_name, destination, filename_base, history)
68
+ oldest_kept_file = nil
69
+
70
+ # Keep some full backups and remove the others.
71
+ # We capture the oldest full backup and get rid of preceeding incrementals.
72
+ Dir["#{destination}/#{filename_base}*"].sort.reverse.each_with_index do |fullbackup, idx|
73
+ if idx < history
74
+ oldest_kept_file = fullbackup
75
+ else
76
+ File.delete(fullbackup)
70
77
  end
71
78
  end
72
79
 
73
- filename
80
+ # Remove all incremental files that are older than the oldest kept full backup.
81
+ if oldest_kept_file
82
+ remove_before = File.stat(oldest_kept_file).ctime
83
+ Dir["#{destination}/#{profile_name}*"].each do |backupfile|
84
+ File.delete(backupfile) if File.stat(backupfile).ctime < remove_before
85
+ end
86
+ end
74
87
  end
75
88
 
89
+ # Run tar.
76
90
  def call(profile_name = nil)
77
91
  profile_name ||= @config.runtime["profile"]
78
92
  profile = @config.config["profiles"][profile_name]
79
93
 
80
94
  raise "Missing profile #{profile_name}." unless profile
81
95
 
82
- args = @config.profile(profile_name)["tarcmd"].dup || ["tar"]
96
+ args = profile["tarcmd"].dup || ["tar", "cJvf", "${BUP_FILENAME}.tar.xz"]
83
97
 
84
- args << "cJvf"
85
-
86
- filename = prepare_destination(profile_name)
87
- args << filename
98
+ prepare_destination(profile_name, profile)
88
99
 
89
100
  if @config.runtime["type"] == "incremental"
90
101
  lastrun = @config.lastrun(profile_name)
91
102
  args += ["--newer", lastrun.strftime("%Y-%m-%d %H:%M:%S %z")] if lastrun
92
103
  end
93
104
 
105
+ run_pre_cmds profile
106
+
94
107
  @config.update_lastrun(profile_name)
95
108
 
96
109
  tf = build_exclude_file(profile["exclude"] || [])
97
110
 
98
- args += ["--exclude-from", tf.path]
99
- args += (@config.profile(profile_name)["include"] || ["."]).map do |str|
100
- expand_string(str)
111
+ # Build and run tar command.
112
+ begin
113
+ args += ["--exclude-from", tf.path]
114
+ args += (profile["include"] || ["."])
115
+ args = args.map do |str|
116
+ expand_string(str)
117
+ end
118
+
119
+ # Run tar.
120
+ run_cmd(*args)
121
+ ensure
122
+ tf.unlink
101
123
  end
102
124
 
103
- begin
104
- Open3.popen3(*args) do |stdin, stdout, stderr, wait_thr|
105
- stdin.close
106
- while wait_thr.status
107
- r, w, e = IO.select([stdout, stderr])
108
- r.each do |stream|
109
- begin
110
- print stream.read_nonblock(1024)
111
- rescue EOFError
112
- end
113
- end
114
- end
125
+ run_post_cmds profile
126
+ end
115
127
 
116
- wait_thr.join
117
- begin
118
- print stdout.read_nonblock(1024)
119
- rescue EOFError
120
- end
128
+ def run_pre_cmds(profile)
129
+ # Before we update the last_run, execute any pre_cmds.
130
+ (profile["pre_cmds"] || []).each do |args|
131
+ run_cmd(*args.map { |c| expand_string(c) })
132
+ end
133
+ end
121
134
 
122
- begin
123
- print stderr.read_nonblock(1024)
124
- rescue EOFError
125
- end
135
+ def run_post_cmds(profile)
136
+ (profile["post_cmds"] || []).each do |args|
137
+ run_cmd(*args.map { |c| expand_string(c) })
138
+ end
139
+ end
126
140
 
127
- STDOUT.flush
141
+ # Exec and run a command in a standard way.
142
+ def run_cmd(*args)
143
+ Open3.popen3(*args) do |stdin, stdout, stderr, wait_thr|
144
+ stdin.close
145
+ x = nil
146
+ while wait_thr.status
147
+ r, _w, _e = IO.select([stdout, stderr])
148
+ r.each do |stream|
149
+ print x if (x = stream.read_nonblock(1024, exception: false))
150
+ end
128
151
  end
129
- ensure
130
- tf.unlink
152
+
153
+ wait_thr.join
154
+ print x if (x = stdout.read_nonblock(1024, exception: false))
155
+
156
+ print x if (x = stderr.read_nonblock(1024, exception: false))
157
+
158
+ $stdout.flush
131
159
  end
132
160
  end
133
161
  end
data/lib/bup/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bup
4
- VERSION = "0.2.0"
4
+ VERSION = "0.6.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bup
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Baskinger
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-11-14 00:00:00.000000000 Z
11
+ date: 2021-11-29 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Backup tool driver that connects tar, gnupg, and other tools to accomplish
14
14
  backups.