bup 0.1.0 → 0.5.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: 66fb72e2d0fbc1251274b4b505e62f695cf975aba7daa6fb6896fe7c72f2f969
4
- data.tar.gz: 38be925b9fb61f41475b495145b572e6244ce8a692b40e8af311816315cca517
3
+ metadata.gz: c9226e1ffb057342aaf45173d2ecaafc78537d19a25db5e25ae8275311e3c46f
4
+ data.tar.gz: 74a4207d1606adbca58e7f2650a01f3ae422a8c9c752832c1e684faf817fd6a8
5
5
  SHA512:
6
- metadata.gz: 0c9621fb9d2087df3767a57ce5e71f540e8316d1dc0be9a1a8eb47c3da81f9230f234de7832f2b507754764759305a3edb21131da182caeb1a40bb6e752e3615
7
- data.tar.gz: d762849552b7c575d7a366eaeea96900a1fd822ef07f4e7cf736afb737368c004e2aab9a8a34563971f9124cc348fd5bca0aa6686327ef34edc73a2b37fdffe0
6
+ metadata.gz: 6d0c35ab5eb84c7a1a68527fe49e28f1347f6c0e9cc38017aa160ff31f41046afba36674cf5ce42cf6c6f1e30b2bad0c4f62108b54734253a490c21d5e73c8b1
7
+ data.tar.gz: 835d1ec79c3c4fcd7b42d9632b4e8c649ee41cefcac949fd658d8874d50c25edd7b99768e461e230fe7d0b385997f4a8dd6772d2f4d8d4e6835d603da2f3e448
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,28 @@
1
1
  ## [Unreleased]
2
2
 
3
- ## [0.1.0] - 2021-11-10
3
+ ## [0.5.0] - 2021-11-13
4
4
 
5
- - Initial release
5
+ - Allow for a history size of 0.
6
+ - Add default_profile setting.
7
+
8
+ ## [0.4.0] - 2021-11-13
9
+
10
+ - Bug fix for empty post_cmds list.
11
+
12
+ ## [0.3.0] - 2021-11-13
13
+
14
+ - Communicate the backup file base name through the environment variable BUP_FILENAME.
15
+ - Rubocop fixes or ignore files.
16
+ - Add post commands, incase you want to default copy something around.
17
+
18
+ ## [0.2.0] - 2021-11-13
19
+
20
+ - List profiles.
21
+ - Bug fixes against empty backup directories.
22
+ - Bug fix to print buffers using read_nonblock from popen3 streams.
23
+
24
+ ## [0.1.0] - 2021-11-11
25
+
26
+ - Backup profiles.
27
+ - Backup history limits.
28
+ - Tar command specification.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- bup (0.1.0)
4
+ bup (0.5.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -1,43 +1,60 @@
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
+ exclude:
24
+ - "$HOME"
25
+ - "$HOME/backups"
26
+ lastrun: '2021-11-14T03:06:45 +0000'
27
+ destination: "$HOME/backups"
28
+ history: 2
29
+ tarcmd:
30
+ - tar
31
+ - cJvf
32
+ - "${BUP_FILENAME}.tar.xz"
33
+ post_cmds:
34
+ - - ls
35
+ - $BUP_FILENAME.tar.xz
36
+ ```
32
37
 
33
- ## Contributing
38
+ This defines a single profile, "default". You can list
39
+ defined profiles by running `bup -l`. You run the default backup profile
40
+ simply by running `bup`. Other profiles must be selected on the command
41
+ line using `bup -p <profile name>`.
34
42
 
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).
43
+ The default profile, as defined above, excludes both the `$HOME` and
44
+ `$HOME/backups` directories. Remove `$HOME` from the excludes list to actually
45
+ backup your entire home directory.
36
46
 
37
- ## License
47
+ ## Incremental Backups
38
48
 
39
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
49
+ By calling `bup -t incremental` an incremental backup will be run.
50
+ This runs `tar` with the `--newer` option included with the time
51
+ specified in the profile's `lastrun` setting. `Tar` will then exclude
52
+ files older than that time.
40
53
 
41
- ## Code of Conduct
54
+ ## Backup History
42
55
 
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).
56
+ Before a new backup is run, previous backups are removed. If you run many
57
+ uncompleted backups, you will eventually remove all good backups from your
58
+ backup history. This behavior is desired because backups should nearly always
59
+ succeed and we want to ensure that disk pressure is kep lower than higher.
60
+ Higher disk pressure might cause a backup to fail.
data/buprc CHANGED
@@ -1,14 +1,21 @@
1
1
  ---
2
+ default_profile: default
2
3
  profiles:
3
4
  default:
5
+ description: Simple backup of critical files.
4
6
  include:
5
7
  - "$HOME"
6
8
  exclude:
7
9
  - "$HOME"
8
10
  - "$HOME/backups"
9
- lastrun: '2021-11-11T14:18:27 +0000'
11
+ lastrun: '2021-11-14T03:06:45 +0000'
10
12
  destination: "$HOME/backups"
11
- history: 10
13
+ history: 2
12
14
  tarcmd:
13
15
  - sudo
14
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,18 +16,34 @@ 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
22
  opt.on("-t", "--type=type", String, "Type of backup.") do |t|
22
23
  config.runtime["type"] = t == "incremental" ? t : "full"
23
24
  end
25
+
26
+ opt.on("-l", "--list", "List profiles and exit.") do
27
+ config.runtime["action"] = "list"
28
+ end
24
29
  end.parse!
25
30
 
26
31
  config.load(configfile) if File.exist?(configfile)
27
32
 
28
- tar = Bup::Tar.new(config)
33
+ if profile
34
+ config.runtime["profile"] = profile
35
+ elsif config.config["default_profile"]
36
+ config.runtime["profile"] = config.config["default_profile"]
37
+ end
29
38
 
30
- tar.call
31
39
 
32
- config.save(configfile)
40
+ case config.runtime["action"]
41
+ when "list"
42
+ config.config["profiles"].each do |key, profile|
43
+ puts "#{key} - #{profile["description"] || ""}"
44
+ end
45
+ else
46
+ tar = Bup::Tar.new(config)
47
+ tar.call
48
+ config.save(configfile)
49
+ 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
 
@@ -36,53 +34,65 @@ module Bup
36
34
  # Prepare the destination directory.
37
35
  def prepare_destination(profile_name)
38
36
  destination = expand_string(@config.profile(profile_name)["destination"] || ".")
39
- suffix = ".tar.xz"
40
37
  type = @config.runtime["type"] || "full"
41
38
 
42
39
  FileUtils.mkdir_p(destination) unless File.exist?(destination)
43
40
 
44
41
  filename_base = "#{profile_name}-#{type}"
45
42
  filename = File.join(destination,
46
- "#{filename_base}-#{DateTime.now.new_offset(0).strftime("%Y%m%d%H%M%S")}#{suffix}")
43
+ "#{filename_base}-#{DateTime.now.new_offset(0).strftime("%Y%m%d%H%M%S")}")
47
44
 
48
45
  history = @config.profile(profile_name)["history"] || 0
49
46
 
50
- if type == "full" && history.positive?
47
+ if type == "full"
48
+ if history.positive?
49
+ prune_history(profile_name, destination, filename_base, history)
50
+ elsif history == 0
51
+ clear_history(profile_name, destination)
52
+ end
53
+ end
51
54
 
52
- oldest_kept_file = nil
55
+ filename
56
+ end
53
57
 
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
58
+ def clear_history(profile_name, destination)
59
+ Dir["#{destination}/#{profile_name}*"].each do |backup|
60
+ File.delete(backup)
61
+ end
62
+ end
63
+
64
+ def prune_history(profile_name, destination, filename_base, history)
65
+ oldest_kept_file = nil
66
+
67
+ # Keep some full backups and remove the others.
68
+ # We capture the oldest full backup and get rid of preceeding incrementals.
69
+ Dir["#{destination}/#{filename_base}*"].sort.reverse.each_with_index do |fullbackup, idx|
70
+ if idx < history
71
+ oldest_kept_file = fullbackup
72
+ else
73
+ File.delete(fullbackup)
62
74
  end
75
+ end
63
76
 
64
- # Remove all incremental files that are older than the oldest kept full backup.
77
+ # Remove all incremental files that are older than the oldest kept full backup.
78
+ if oldest_kept_file
65
79
  remove_before = File.stat(oldest_kept_file).ctime
66
80
  Dir["#{destination}/#{profile_name}*"].each do |backupfile|
67
81
  File.delete(backupfile) if File.stat(backupfile).ctime < remove_before
68
82
  end
69
83
  end
70
-
71
- filename
72
84
  end
73
85
 
86
+ # Run tar.
74
87
  def call(profile_name = nil)
75
88
  profile_name ||= @config.runtime["profile"]
76
89
  profile = @config.config["profiles"][profile_name]
77
90
 
78
91
  raise "Missing profile #{profile_name}." unless profile
79
92
 
80
- args = @config.profile(profile_name)["tarcmd"].dup || ["tar"]
81
-
82
- args << "cJvf"
93
+ args = @config.profile(profile_name)["tarcmd"].dup || ["tar", "cJvf", "${BUP_FILENAME}.tar.xz"]
83
94
 
84
- filename = prepare_destination(profile_name)
85
- args << filename
95
+ ENV["BUP_FILENAME"] = prepare_destination(profile_name)
86
96
 
87
97
  if @config.runtime["type"] == "incremental"
88
98
  lastrun = @config.lastrun(profile_name)
@@ -94,28 +104,41 @@ module Bup
94
104
  tf = build_exclude_file(profile["exclude"] || [])
95
105
 
96
106
  args += ["--exclude-from", tf.path]
97
- args += (@config.profile(profile_name)["include"] || ["."]).map do |str|
107
+ args += (@config.profile(profile_name)["include"] || ["."])
108
+ args = args.map do |str|
98
109
  expand_string(str)
99
110
  end
100
111
 
101
112
  begin
102
- Open3.popen3(*args) do |stdin, stdout, stderr, wait_thr|
103
- stdin.close
104
-
105
- while wait_thr.status
106
- r, w, e = IO.select([stdout, stderr])
107
- r.each do |stream|
108
- print stream.read
109
- end
110
- end
111
-
112
- wait_thr.join
113
- print stdout.read
114
- print stderr.read
115
- end
113
+ run_cmd(*args)
116
114
  ensure
117
115
  tf.unlink
118
116
  end
117
+
118
+ (@config.profile(profile_name)["post_cmds"] || []).each do |args|
119
+ run_cmd(*args.map { |c| expand_string(c) })
120
+ end
121
+ end
122
+
123
+ # Exec and run a command in a standard way.
124
+ def run_cmd(*args)
125
+ Open3.popen3(*args) do |stdin, stdout, stderr, wait_thr|
126
+ stdin.close
127
+ x = nil
128
+ while wait_thr.status
129
+ r, _w, _e = IO.select([stdout, stderr])
130
+ r.each do |stream|
131
+ print x if (x = stream.read_nonblock(1024, exception: false))
132
+ end
133
+ end
134
+
135
+ wait_thr.join
136
+ print x if (x = stdout.read_nonblock(1024, exception: false))
137
+
138
+ print x if (x = stderr.read_nonblock(1024, exception: false))
139
+
140
+ $stdout.flush
141
+ end
119
142
  end
120
143
  end
121
144
  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.1.0"
4
+ VERSION = "0.5.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.1.0
4
+ version: 0.5.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-11 00:00:00.000000000 Z
11
+ date: 2021-11-14 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.