bareos-restore 1.0.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.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/bareos-restore.rb +268 -0
  3. metadata +59 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d77f4b6e3042c4e2453c5ed4ab9842d62da720b161024298bb0c3394daf2f931
4
+ data.tar.gz: 127cc098dd8b2002d504fbaa7cb82fcf9c2f8d4970488cff8e2cbf62494b1133
5
+ SHA512:
6
+ metadata.gz: b8baad71dfec5b5107ff71141e181d22405f87203e7184810b95555d630f3c88bf45fcb3c0b52bcd47cea0156e763100da4a2ce0be878ceef33066d570db2efc
7
+ data.tar.gz: ff2989fea490a11247003dfebc343226caf305d018889bf24da73da0f04a1f91f56b640419a3eb94db68b22b7a33ba228bee828ab4b934da0f12b27105fa7355
@@ -0,0 +1,268 @@
1
+ # frozen_string_literal: true
2
+
3
+ # todo:
4
+ # - bareos-job abbrechen, wenn SIGINT kommt
5
+
6
+ # Prerequisites:
7
+ # - apt install ruby-sys-filesystem
8
+
9
+ require 'open3'
10
+ require 'json'
11
+ require 'pty'
12
+ require 'expect'
13
+ require 'time'
14
+ require 'logger'
15
+
16
+ # Bareos bconsole configuration
17
+ BCONSOLE_PATH = '/usr/sbin/bconsole'
18
+
19
+ JOBSTATUS = {
20
+ C: {
21
+ text: 'Created, not yet running',
22
+ pending: true,
23
+ running: false,
24
+ failed: false
25
+ },
26
+ R: {
27
+ text: 'Running',
28
+ pending: false,
29
+ running: true,
30
+ failed: false
31
+ },
32
+ B: {
33
+ text: 'Blocked',
34
+ pending: true,
35
+ running: true,
36
+ failed: false
37
+ },
38
+ T: {
39
+ text: 'Completed successfully',
40
+ pending: false,
41
+ running: false,
42
+ failed: false
43
+ },
44
+ E: {
45
+ text: 'Terminated with errors',
46
+ pending: false,
47
+ running: false,
48
+ failed: true
49
+ },
50
+ e: {
51
+ text: 'Non-fatal error',
52
+ pending: false,
53
+ running: false,
54
+ failed: false
55
+ },
56
+ f: {
57
+ text: 'Fatal error',
58
+ pending: false,
59
+ running: false,
60
+ failed: true
61
+ },
62
+ D: {
63
+ text: 'Verify found differences',
64
+ pending: false,
65
+ running: false,
66
+ failed: true
67
+ },
68
+ A: {
69
+ text: 'Cancelled by user',
70
+ pending: false,
71
+ running: false,
72
+ failed: true
73
+ },
74
+ I: {
75
+ text: 'Incomplete job',
76
+ pending: false,
77
+ running: false,
78
+ failed: true
79
+ },
80
+ L: {
81
+ text: 'Comitting data',
82
+ pending: false,
83
+ running: true,
84
+ failed: false
85
+ },
86
+ W: {
87
+ text: 'Terminated with warnings',
88
+ pending: false,
89
+ running: false,
90
+ failed: false
91
+ },
92
+ l: {
93
+ text: 'Doing data despooling',
94
+ pending: false,
95
+ running: true,
96
+ failed: false
97
+ },
98
+ q: {
99
+ text: 'Queued waiting for device',
100
+ pending: true,
101
+ running: false,
102
+ failed: false
103
+ },
104
+ F: {
105
+ text: 'Waiting for client',
106
+ pending: true,
107
+ running: false,
108
+ failed: false
109
+ },
110
+ S: {
111
+ text: 'Waiting for storage daemon',
112
+ pending: true,
113
+ running: true,
114
+ failed: false
115
+ },
116
+ m: {
117
+ text: 'Waiting for new media',
118
+ pending: true,
119
+ running: true,
120
+ failed: false
121
+ },
122
+ M: {
123
+ text: 'Waiting for media mount',
124
+ pending: true,
125
+ running: true,
126
+ failed: false
127
+ },
128
+ s: {
129
+ text: 'Waiting for storage resource',
130
+ pending: true,
131
+ running: true,
132
+ failed: false
133
+ },
134
+ j: {
135
+ text: 'Waiting for job resource',
136
+ pending: true,
137
+ running: true,
138
+ failed: false
139
+ },
140
+ c: {
141
+ text: 'Waiting for client resource',
142
+ pending: true,
143
+ running: true,
144
+ failed: false
145
+ },
146
+ d: {
147
+ text: 'Waiting on maximum jobs',
148
+ pending: true,
149
+ running: true,
150
+ failed: false
151
+ },
152
+ t: {
153
+ text: 'Waiting on start time',
154
+ pending: true,
155
+ running: true,
156
+ failed: false
157
+ },
158
+ p: {
159
+ text: 'Waiting on higher priority jobs',
160
+ pending: true,
161
+ running: true,
162
+ failed: false
163
+ },
164
+ i: {
165
+ text: 'Doing batch insert file records',
166
+ pending: false,
167
+ running: true,
168
+ failed: false
169
+ },
170
+ a: {
171
+ text: 'Despooling attributes',
172
+ pending: false,
173
+ running: true,
174
+ failed: false
175
+ }
176
+ }.freeze
177
+
178
+ module BareosRestore
179
+ $logger = Logger.new(STDOUT)
180
+ $logger.level = Logger::INFO
181
+ #$logger.level = Logger::DEBUG
182
+
183
+ # Function to run a command in bconsole
184
+ # Sends input commands to bconsole and captures the output
185
+ # apilevel=1 -> Text-based output
186
+ # apilevel=2 -> JSON-based output
187
+ def self.bconsole(command, apilevel: 2)
188
+ bconsole_cmd = ".api #{apilevel}\n#{command}\n"
189
+ output = nil
190
+
191
+ $logger.debug "Running #{bconsole_cmd}"
192
+
193
+ IO.popen(BCONSOLE_PATH, 'r+') do |io|
194
+ io.puts(bconsole_cmd) # Pipe the input to the command
195
+ io.close_write # Close the write stream to signal EOF
196
+ output = io.read # Read the output from the command
197
+ end
198
+
199
+ # Get the last block of { … }
200
+ output = output.scan(/(^{$.+?^})/m).last
201
+
202
+ if apilevel == 1
203
+ output
204
+ elsif apilevel == 2
205
+ # Parse from JSON and isolate the result sub-structure
206
+ JSON.parse(::Regexp.last_match(1))['result']
207
+ end
208
+ rescue JSON::ParserError => e
209
+ raise "Error parsing JSON: #{e.message}"
210
+ end
211
+
212
+ def self.latest_jobid(backup_name)
213
+ $logger.info "Getting latest job ID for backup #{backup_name}"
214
+ $logger.info "list jobs job=#{backup_name} jobstatus=T jobtype=B last"
215
+ output = bconsole "list jobs job=#{backup_name} jobstatus=T jobtype=B last"
216
+ $logger.debug output.inspect
217
+ raise "No successful backup job found." unless output['jobs'].length>0
218
+ output['jobs'].first['jobid']
219
+ end
220
+
221
+ def self.get_joblog(job_id)
222
+ $logger.info "Getting job log for job #{job_id}"
223
+ output = bconsole "list joblog jobid=#{job_id}"
224
+ textblock = []
225
+ output['joblog'].each do |entry|
226
+ textblock << "#{entry['time']} #{entry['logtext']}"
227
+ end
228
+ $logger.info "JOBLOG…\n#{textblock.join("\n")}"
229
+ end
230
+
231
+ # Function to initiate restore and wait for job completion
232
+ def self.perform_restore(job_id, restore_dir, bareos_client)
233
+ $logger.info "Gathering Full/Diff/Incr jobs to get a complete restore of Job ID #{job_id}"
234
+ output = bconsole ".bvfs_get_jobids jobid=#{job_id}"
235
+ all_jobs = output['jobids'].map { |x| x['id'] }.join(',')
236
+
237
+ $logger.info "These jobs are needed apparently: #{all_jobs}"
238
+
239
+ $logger.info 'Initiating restore'
240
+ output = bconsole "restore client=#{bareos_client} where=#{restore_dir} restorejob=RestoreFiles jobid=#{all_jobs} all done yes"
241
+
242
+ restore_job = output['run']['jobid']
243
+ $logger.info "Restore job queued. ID=#{restore_job}"
244
+
245
+ loop do
246
+ output = bconsole "list jobid=#{restore_job}"
247
+ $logger.debug output
248
+ jobstatus = JOBSTATUS[output['jobs'].first['jobstatus'].to_sym]
249
+ # puts "Status: #{jobstatus}"
250
+ # $logger.info "Status: #{jobstatus} (#{jobstatus[:text]})"
251
+
252
+ if jobstatus[:failed]
253
+ $logger.error 'Job failed.'
254
+ get_joblog(restore_job)
255
+ exit 10
256
+ elsif jobstatus[:running]
257
+ $logger.info 'Job running.'
258
+ elsif jobstatus[:pending]
259
+ $logger.info 'Job pending.'
260
+ else
261
+ $logger.info 'Job finished.'
262
+ get_joblog(restore_job)
263
+ break
264
+ end
265
+ sleep 5
266
+ end
267
+ end
268
+ end
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bareos-restore
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Christoph Haas
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2025-04-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sys-filesystem
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.4'
27
+ description: |2
28
+ Automatically restore a Bareos Backup. Useful for automated restore tests.
29
+ This simple module cannot do more than that.
30
+ email: rubygems@christoph-haas.de
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - lib/bareos-restore.rb
36
+ homepage: https://rubygems.org/gems/bareosrestore
37
+ licenses:
38
+ - MIT
39
+ metadata: {}
40
+ post_install_message:
41
+ rdoc_options: []
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ requirements: []
55
+ rubygems_version: 3.4.20
56
+ signing_key:
57
+ specification_version: 4
58
+ summary: Restore a Bareos backup
59
+ test_files: []