Wiki source code of Bacula

Last modified by Sebastian Marsching on 2022/06/21 21:12

Show last authors
1 {{info}}
2 While the information compiled on this page was originally intended for Bacula, most of it should also apply to Bareos.
3
4 There is a [[separate page with information about Bareos|doc:Software.Bareos.WebHome]], and some of the information there might also apply to Bacula.
5 {{/info}}
6
7 {{toc/}}
8
9 # vchanger script
10
11 See [http://article.gmane.org/gmane.comp.bacula.user/28990](http://article.gmane.org/gmane.comp.bacula.user/28990).
12
13 I modified the script slightly to fix some problems I experienced. You can find the modified version below:
14
15 ```bash
16 #!/bin/bash
17 #
18 # Bacula interface to virtual autochanger using removable disk drives
19 #
20 # Based (somewhat) on the "disk-changer" script from bacula 1.39.26
21 #
22 # Vchanger is a Bacula autochanger script that emulates a conventional
23 # magazine-based tape library device using removable disk drives.
24 # Partitions on the removable drives are used as virtual magazines,
25 # where each "magazine" contains the same number of virtual slots. Each
26 # "slot" holds one virtual tape, where a "tape" is a regular file that
27 # Bacula treats as a "Device Type = File" volume.
28 #
29 # This script will be invoked by Bacula using the Bacula Autochanger
30 # Interface and will be passed the following arguments:
31 #
32 # vchanger "changer-device" "command" "slot" "archive-device" "drive-index"
33 # $1 $2 $3 #4 #5
34 #
35 # See the Bacula documentation for Autochanger Interface details
36 #
37 # Copyright (C) 2006 Josh Fisher
38 #
39 # Permission to use, copy, modify, distribute, and sell this software
40 # and its documentation for any purpose is hereby granted without fee,
41 # provided that the above copyright notice appears in all copies. This
42 # software is provided "as is" without express or implied warranty.
43 #
44 # This software is distributed in the hope that it will be useful,
45 # but WITHOUT ANY WARRANTY; without even the implied warranty of
46 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
47 #
48 # $Id: vchanger,v 0.7.3 2006/11/05 11:31:47 jfisher Exp $
49
50 #
51 # log whats done
52 #
53 dbgfile="/var/lib/bacula/vchanger.log"
54 # to turn on logging, uncomment the following line
55 #touch $dbgfile
56 #
57
58 #
59 # Write to a log file
60 # To log debugging info, create file /var/bacula/vchanger.log
61 # with write permission for bacula-sd user. To stop logging,
62 # delete file /var/bacula/vchanger.log
63 #
64 function debug()
65 {
66 if test -e $dbgfile; then
67 echo "`date +\"%Y%m%d-%H:%M:%S\"` $*" >> $dbgfile
68 fi
69 }
70
71 #
72 # Return length of string $1
73 #
74 function strlen ()
75 {
76 expr length $1
77 }
78
79 #
80 # Prepend zeros to $1 and return a string that is $2 characters long
81 #
82 function mklen ()
83 {
84 o1=$1
85 while [ `eval strlen ${o1}` -lt ${2} ]; do
86 o1="0${o1}"
87 done
88 echo $o1
89 }
90
91 #
92 # Initialize autochanger's state directory if not already initialized
93 #
94 function init_statedir() {
95 debug "Initializing $statedir"
96 # Create state dir if needed
97 if [ ! -d "${statedir}" ]; then
98 mkdir "${statedir}"
99 if [ $? -ne 0 ]; then
100 echo "Could not create ${statedir}"
101 exit 1
102 fi
103 fi
104 chmod 770 "${statedir}"
105 if [ $? -ne 0 ]; then
106 echo "Could not chmod ${statedir}"
107 exit 1
108 fi
109 # Create nextmag file to hold max magazine index used
110 if [ ! -f "${statedir}/nextmag" ]; then
111 echo 0 >"${statedir}/nextmag"
112 if [ $? -ne 0 ]; then
113 echo "Could not create ${statedir}/nextmag"
114 exit 1
115 fi
116 fi
117 chmod 660 "${statedir}/nextmag"
118 if [ $? -ne 0 ]; then
119 echo "Could not chmod ${statedir}/nextmag"
120 exit 1
121 fi
122 # Check nextmag value
123 nextmag=`cat "${statedir}/nextmag"`
124 if [ $? -ne 0 -o "${nextmag}" == "" -o $nextmag -lt 0 -o $nextmag -gt 99 ]; then
125 echo "${statedir}/nextmag has invalid value"
126 return 1
127 fi
128 # Create 'loaded' files for each virtual drive that hold the slot
129 # number currently loaded in that 'drive'
130 i=0
131 while [ $i -le $maxdrive ]; do
132 if [ ! -f "${statedir}/loaded${i}" ]; then
133 echo "0" 2>/dev/null >"${statedir}/loaded${i}"
134 if [ $? -ne 0 ]; then
135 echo "Could not create ${statedir}/loaded${i}"
136 exit 1
137 fi
138 chmod 660 "${statedir}/loaded${i}"
139 if [ $? -ne 0 ]; then
140 echo "Could not chmod ${statedir}/loaded${i}"
141 exit 1
142 fi
143 fi
144 i=`expr ${i} + 1`
145 done
146 }
147
148
149 #
150 # Initialize magazine if not already initialized
151 #
152 function init_magazine() {
153 debug "Initializing magazine"
154 # Get max magazine index that has been used
155 nextmag=`cat "${statedir}/nextmag"`
156 if [ $? -ne 0 -o "${nextmag}" == "" ]; then
157 echo "Failed to read ${statedir}/nextmag"
158 exit 1
159 fi
160 # Check magazine for existing index
161 if [ -f "${mountpoint}/index" ]; then
162 # retrieve existing magazine index
163 mi=`cat "${mountpoint}/index"`
164 if [ $? -ne 0 ]; then
165 echo "Failed to read ${mountpoint}/index"
166 exit 1
167 fi
168 # must be 1-99
169 if [ $mi -lt 1 -o $mi -gt 99 ]; then
170 echo "Magazine has invalid index ${mi}"
171 exit 1
172 fi
173 else
174 # new magazine, so assign it the next avail index
175 mi=`expr ${nextmag} + 1`
176 if [ $mi -lt 0 -o $mi -gt 99 ]; then
177 echo "Max magazines exceeded"
178 exit 1
179 fi
180 echo $mi 2>/dev/null >"${mountpoint}/index"
181 if [ $? -ne 0 ]; then
182 echo "Failed to write ${mountpoint}/index"
183 exit 1
184 fi
185 fi
186 # make sure max index used is up to date
187 if [ $mi -gt $nextmag ]; then
188 echo $mi 2>/dev/null >"${statedir}/nextmag"
189 if [ $? -ne 0 ]; then
190 echo "Failed to update ${statedir}/nextmag"
191 exit 1
192 fi
193 fi
194 # make magazine index 2 digits
195 magindex=`eval mklen ${mi} 2`
196 # setup slot files (ie. virtual tapes)
197 i=1
198 while [ $i -le $magslots ]; do
199 s=`eval mklen ${i} 3`
200 f="${mountpoint}/${volumenameprefix}m${magindex}s${s}"
201 if [ ! -f "${f}" ]; then
202 touch "${f}" 2>/dev/null >/dev/null
203 if [ $? -ne 0 ]; then
204 echo "Failed to create ${f}"
205 exit 1
206 fi
207 fi
208 i=`expr ${i} + 1`
209 done
210 return 0
211 }
212
213
214 #
215 # check parameter count on commandline
216 #
217 function check_parm_count() {
218 pCount=$1
219 pCountNeed=$2
220 if test $pCount -lt $pCountNeed; then
221 echo "usage: vchanger ctl-device command [slot archive-device drive-index]"
222 echo " Insufficient number of arguments arguments given."
223 if test $pCount -lt 2; then
224 echo " Mimimum usage is first two arguments ..."
225 else
226 echo " Command expected $pCountNeed arguments"
227 fi
228 exit 1
229 fi
230 }
231
232
233 # Setup arguments
234 ctl=$1
235 cmd="$2"
236 slot=$3
237 device=$4
238 drive=$5
239
240 # Setup default config values
241 magslots=10
242 maxdrive=0
243 statedir="/var/bacula/vchanger"
244 mountpoint=
245 volumenameprefix=
246
247 # Pull in conf file
248 if [ -f $ctl ]; then
249 . $ctl
250 else
251 echo "Config file ${ctl} not found"
252 exit 1
253 fi
254
255 # check for required config values
256 if [ "${mountpoint}" == "" ]; then
257 echo "Required variable 'mountpoint' not defined in ${ctl}"
258 exit 1
259 fi
260 if [ "${magslots}" == "" -o $magslots -lt 1 -o $magslots -gt 999 ]; then
261 echo "Ivalid value for 'magslots' in ${ctl}"
262 exit 1
263 fi
264 if [ "${maxdrive}" == "" -o $maxdrive -lt 0 -o $maxdrive -ge $magslots ]; then
265 echo "Invalid value for 'maxdrive' in ${ctl}"
266 exit 1
267 fi
268 if [ "${statedir}" == "" ]; then
269 echo "Invalid value for 'statedir' in ${ctl}"
270 exit 1
271 fi
272
273 # Initialize state directory for this autochanger
274 init_statedir
275
276 # Check for special cases where only 2 arguments are needed,
277 # all others are a minimum of 5
278 #
279 case $2 in
280 list)
281 check_parm_count $# 2
282 ;;
283 slots)
284 check_parm_count $# 2
285 ;;
286 *)
287 check_parm_count $# 5
288 if [ $drive -gt $maxdrive ]; then
289 echo "Drive ($drive) out of range (0-${maxdrive})"
290 exit 1
291 fi
292 if [ $slot -gt $magslots ]; then
293 echo "Slot ($slot) out of range (1-$magslots)"
294 exit 1
295 fi
296 ;;
297 esac
298
299 debug "Parms: $ctl $cmd $slot $device $drive"
300
301 case $cmd in
302 unload)
303 debug "Doing vchanger -f $ctl unload $slot $device $drive"
304 ld=`cat "${statedir}/loaded${drive}"`
305 if [ $? -ne 0 ]; then
306 echo "Failed to read ${statedir}/loaded${drive}"
307 exit 1
308 fi
309 if [ $slot -eq $ld ]; then
310 echo "0" >"${statedir}/loaded${drive}"
311 if [ $? -ne 0 ]; then
312 echo "Failed to write ${statedir}/loaded${drive}"
313 exit 1
314 fi
315 unlink "${device}" 2>/dev/null >/dev/null
316 exit 0
317 fi
318 if [ $ld -eq 0 ]; then
319 echo "Drive ${drive} Is Empty"
320 else
321 echo "Storage Element ${slot} is Already Full"
322 fi
323 exit 1
324 ;;
325
326 load)
327 debug "Doing vchanger $ctl load $slot $device $drive"
328 ld=`cat "${statedir}/loaded${drive}"`
329 if [ $? -ne 0 ]; then
330 echo "Failed to read ${statedir}/loaded${drive}"
331 exit 1
332 fi
333 if [ $ld -eq 0 ]; then
334 unlink "${device}" 2>/dev/null >/dev/null
335 # make sure slot is not loaded in another drive
336 i=0
337 while [ $i -le $maxdrive ]; do
338 if [ $i -ne $drive ]; then
339 ldi=`cat "${statedir}/loaded${i}"`
340 if [ $ldi -eq $slot ]; then
341 echo "Storage Element ${slot} Empty (loaded in drive ${i})"
342 exit 1
343 fi
344 fi
345 i=`expr ${i} + 1`
346 done
347 init_magazine
348 if [ $? -ne 0 ]; then
349 echo "Magazine Not Loaded"
350 exit 1
351 fi
352 s=`eval mklen ${slot} 3`
353 ln -s "${mountpoint}/${volumenameprefix}m${magindex}s${s}" "${device}"
354 echo $slot >"${statedir}/loaded${drive}"
355 exit 0
356 else
357 echo "Drive ${drive} Full (Storage element ${ld} loaded)"
358 exit 1
359 fi
360 ;;
361
362 list)
363 debug "Doing vchanger -f $ctl -- to list volumes"
364 init_magazine
365 if [ $? -ne 0 ]; then
366 echo "Magazine Not Loaded"
367 exit 1
368 fi
369 i=1
370 while [ $i -le $magslots ]; do
371 s=`eval mklen ${i} 3`
372 echo "${i}:${volumenameprefix}m${magindex}s${s}"
373 i=`expr ${i} + 1`
374 done
375 exit 0
376 ;;
377
378 loaded)
379 debug "Doing vchanger -f $ctl $drive -- to find what is loaded"
380 cat "${statedir}/loaded${drive}"
381 exit 0
382 ;;
383
384 slots)
385 debug "Doing vchanger -f $ctl -- to get count of slots"
386 echo $magslots
387 exit 0
388 ;;
389 esac
390 ```
391
392 # Labeling Volumes Automatically
393
394 For creating labels for new volumes automatically in an autochanger setup, you can use the `label barcodes` command in bconsole.
395
396 # Recovering an error volume
397
398 A volume with an error status is not used when looking for available volumes. In order to make it available again, one can purge it, which will clear the error status:
399
400 # Disaster Recovery for Windows Server 2012 R2 using Bacula
401
402 Disaster recovery for Windows systems has been a hot topic for years. The [Bacula manual](http://www.bacula.org/de/dev-manual/Disast_Recove_Using_Bacula.html) recommends creating a backup of the system state using "ntbackup" and (after restoring the files) using this backup for restoring the system state. However, ntbackup has not been a part of Windows since Windows Server 2008 and using the new "Windows Server Backup" (also known as "wbadmin") is not a great alternative, because its system-state backups are huge.
403
404 Bacula Systems offer a [Bare Metal Recovery plugin](http://www.baculasystems.com/products/bare-metal-restore) as part of their Bacula Enterprise solution, however many people (like me) do not want to migrate to a commercial solution for their whole backup system.
405
406 Therefore I wondered, whether there might be a better solution for having disaster recovery with Bacula. My idea was that, using VSS, Bacula itself should be capable of creating an accurate backup of the system state and the main problem is not backup but restore. For my tests, I created a virtual machine running Windows Server 2012 R2 and set it up as an Active Directory domain controller. I used this setup because disaster recovery of a domain controller was one of my main points of worry. The idea is that, if you can restore a domain controller, all other things will be simple in comparison. I made a backup of the whole C: drive of this virtual machine using Bacula (having VSS enabled). Then I shutdown the virtual machine, created a new virtual machine with the same configuration and tried to restore the backup. This mimics the situation that I completely lost a system and now have to restore it on a new computer having the same hardware components. In my case having the same hardware is a pretty good assumption, because I like to run Windows Servers in VMs anyway.
407
408 The following guide lists the steps that I used for restoring the system. Although I tested it with Windows Server 2012 R2, I think the same steps will apply for disaster recovery of a Windows Server 2008, Windows Server 2008 R2 or Windows Server 2012 system.
409
410 **Important Note:** The steps described here worked for me in a particular situation. For example I restored from a full backup (without any differential or incremental backups in between). If you want to use differential or incremental backups, I think that at least you will want to enable the `Accurate` option on the job. For a different system configuration, these steps might not work. So **you should not rely** on the steps described here for disaster recovery, but instead test whether these steps work for your situation. It is much more comfortable to test a restore when you know that the original system is fine than when the restore just has to work. I can sleep better at night when i know that I tested restore procedures for critical systems.
411
412 ## Step-by-Step Guide
413
414 We have to build a Windows 7 x64 image using [WinBuilder](http://winbuilder.net/) / [multiPE](http://reboot.pro/files/file/61-multipe) (x86 might also work). The German computer magazine [PC-Welt](http://www.pcwelt.de/) provides a [nice download bundle](http://www.pcwelt.de/downloads/PC-WELT_Multi-PE-Windows-Rettungssystem-7053993.html) that contains everything needed (apart from the Windows system itself of course). It is important to include the Visual C++ runtime (with 32-bit compatibility support). This option named "Force x86 SideBySide For 64-Bit Operation Systems" can be found in WinBuilder under "VC++ 2008". A PE-disk based on Windows XP might not work, because Windows XP did not support Junction Points (although the support for them has been in NTFS for decades), so Bacula might not be able to restore them. If we have a NIC or disk controller that is not natively supported by Windows, we have to include the corresponding drivers in the PE disk because otherwise we will not be able to restore the backup later.
415
416 First we have to boot from the operating-system installation-disk and install the OS the same way we would for a new system. However, to save some time, we can use the "Server Core" installation because we only do this to create partitions and to restore the boot loader. We stop the installation process at the time of the first reboot.
417
418 Now we boot from the PE-disk created with WinBuilder. We assign the C: drive letter to the new system partition on the hard-disk (using Disk Management), so that Bacula will restore the files to the right partition.
419
420 We format the C: drive, so that the files that were just created by the installer are deleted.
421
422 Now we have to download and install the Bacula File Daemon. It makes sense to use the same version of the Bacula FD that was used to create the Backup. For a 64-bit PE-disk we use the 64-bit version of Bacula. We use an installation path that for sure does not exist in the image to be restored (e.g. `C:\BaculaJustForRestore`). If we used the default path, we would get a collission with the Bacula files that are part of the restore. We remove the checkmarks on the "Install as service" and "Start after install" options because we will take care of starting Bacula manually once we have adjusted the configuration.
423
424 After the installation has finished, we open the `bacula-fd.conf` file and make sure that the configuration is correct: Basically, we want the same settings here that we had on the original system. In particular, the client name, director name and password should match. We also have to make sure that the paths for the working and PID directories point to the correct (non-standard) directory.
425
426 Finally, we have to ensure that the IP address of the system is set correctly. If the IP address does not match the address in the Bacula director configuration, we can either change the director configuration or manually configure the IP address of the system.
427
428 Now we start the Bacula FD by opening a command prompt, changing to the installation directory and running `bacula-fd.exe` with the right command-line parameters. Example:
429
430 ```bat
431 c:
432 cd BaculaJustForRestore
433 bacula-fd /run -c C:\BaculaJustForRestore\bacula-fd.conf
434 ```
435
436 Now we are ready to start the restore in the Bacula console. We have to select the whole C: drive (but no other drives - we can restore them later). In the restore options we set an empty prefix ("/") for the target location ("Where:"), so that the files are restored to their original location on the C: drive.
437
438 When the restore has completed, we shut the computer down and remove the PE-disk. That’s it. The next time we boot the computer the restored system should boot. After that, we can delete the temporary Bacula installation and restore the files on other disks (if there are any).
439
440 If the computer does not boot the system in the last step, the first thing I would try would be to boot from OS installation disk and use `startrep.exe` in repair mode. However, this is not a part of this guide, because for me it worked right away.
441
442 # Making a system state backup using Windows Server Backup
443
444 If you want to make a system state backup using Windows Server Backup each time that you make a backup with Bacula, you might find the following script useful, that you can add as a `run before job` script.
445
446 ```bat
447 @echo off
448 if "%1" == "" (
449 echo Backup job level has to be passed as first parameter.
450 exit /b 1
451 )
452 if /i %1 == full (
453 rem wbadmin delete systemstatebackup -backupTarget:p: -keepVersions:0 -quiet
454 if exist p:\WindowsImageBackup rd /s /q p:\WindowsImageBackup
455 wbadmin start systemstatebackup -backupTarget:p: -quiet
456 ) else (
457 echo Backup Level is %1, skipping system state backup.
458 )
459 ```