bup 0.2.0 → 0.6.0

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.
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.