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 +4 -4
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +25 -2
- data/Gemfile.lock +1 -1
- data/README.md +48 -31
- data/buprc +9 -2
- data/exe/bup +21 -4
- data/lib/bup/config.rb +4 -3
- data/lib/bup/tar.rb +63 -40
- data/lib/bup/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c9226e1ffb057342aaf45173d2ecaafc78537d19a25db5e25ae8275311e3c46f
|
4
|
+
data.tar.gz: 74a4207d1606adbca58e7f2650a01f3ae422a8c9c752832c1e684faf817fd6a8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6d0c35ab5eb84c7a1a68527fe49e28f1347f6c0e9cc38017aa160ff31f41046afba36674cf5ce42cf6c6f1e30b2bad0c4f62108b54734253a490c21d5e73c8b1
|
7
|
+
data.tar.gz: 835d1ec79c3c4fcd7b42d9632b4e8c649ee41cefcac949fd658d8874d50c25edd7b99768e461e230fe7d0b385997f4a8dd6772d2f4d8d4e6835d603da2f3e448
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,28 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
-
## [0.
|
3
|
+
## [0.5.0] - 2021-11-13
|
4
4
|
|
5
|
-
-
|
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
data/README.md
CHANGED
@@ -1,43 +1,60 @@
|
|
1
1
|
# Bup
|
2
2
|
|
3
|
-
|
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
|
-
|
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
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
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
|
-
|
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
|
-
##
|
47
|
+
## Incremental Backups
|
38
48
|
|
39
|
-
|
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
|
-
##
|
54
|
+
## Backup History
|
42
55
|
|
43
|
-
|
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-
|
11
|
+
lastrun: '2021-11-14T03:06:45 +0000'
|
10
12
|
destination: "$HOME/backups"
|
11
|
-
history:
|
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
|
-
|
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
|
-
|
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.
|
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"] || "",
|
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(
|
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-
|
17
|
-
all =
|
18
|
-
nm =
|
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")}
|
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"
|
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
|
-
|
55
|
+
filename
|
56
|
+
end
|
53
57
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
-
|
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
|
-
|
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"] || ["."])
|
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
|
-
|
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
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.
|
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
|
+
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.
|