bup 0.1.0 → 0.5.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: 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.