bup 0.2.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|