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 +4 -4
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +27 -0
- data/Gemfile.lock +1 -1
- data/README.md +54 -31
- data/buprc +8 -2
- data/exe/bup +18 -6
- data/lib/bup/config.rb +4 -3
- data/lib/bup/tar.rb +87 -59
- 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: 3228f83fe11f72b1e77210809dec6e3d8a4ccbdc849fa756c97f525803e04357
|
4
|
+
data.tar.gz: ea48181ef6b1adb871c2b558a6bfb9bdb50ef364459ec0fc453837d33dc57589
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 106db1b2e171e056b9bf19763f269898a2b5eeea115ce30918353fab95147850d124ffb53bcb64dfd35c46f220c71019ac0de8a3276263f93fc79c39a42a53b6
|
7
|
+
data.tar.gz: 64217767bfc2cbcfe73b7c1963175481cdfa4d8318ee2e2f4f92317bccddb1690244e48666891a78b5aa1d61da8876f90a80c8495850c62a3800619b2a4983d4
|
data/.rubocop.yml
CHANGED
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
data/README.md
CHANGED
@@ -1,43 +1,66 @@
|
|
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
|
+
- /$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
|
-
|
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
|
-
|
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
|
-
##
|
53
|
+
## Incremental Backups
|
38
54
|
|
39
|
-
|
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
|
-
##
|
60
|
+
## Backup History
|
42
61
|
|
43
|
-
|
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:
|
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-
|
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
|
-
|
19
|
+
profile = p
|
19
20
|
end
|
20
21
|
|
21
|
-
opt.on("-
|
22
|
-
config.runtime["type"] =
|
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[
|
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
|
-
|
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"] || "",
|
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
|
|
@@ -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 =
|
39
|
-
|
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")}
|
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"
|
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
|
-
|
56
|
+
ENV["BUP_DESTINATION"] = destination
|
57
|
+
ENV["BUP_FILENAME"] = filename
|
58
|
+
filename
|
59
|
+
end
|
53
60
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
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
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|
-
|
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 =
|
96
|
+
args = profile["tarcmd"].dup || ["tar", "cJvf", "${BUP_FILENAME}.tar.xz"]
|
83
97
|
|
84
|
-
|
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
|
-
|
99
|
-
|
100
|
-
|
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
|
-
|
104
|
-
|
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
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
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
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
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
|
-
|
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
|
-
|
130
|
-
|
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
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.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-
|
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.
|