Wiki source code of Bacula

Version 2.1 by Sebastian Marsching on 2022/03/27 13:53

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 {{/info}}
4
5 {{toc/}}
6
7 # vchanger script
8
9 See [http://article.gmane.org/gmane.comp.bacula.user/28990](http://article.gmane.org/gmane.comp.bacula.user/28990).
10
11 I modified the script slightly to fix some problems I experienced. You can find the modified version below:
12
13 ```bash
14 #!/bin/bash
15 #
16 # Bacula interface to virtual autochanger using removable disk drives
17 #
18 # Based (somewhat) on the "disk-changer" script from bacula 1.39.26
19 #
20 # Vchanger is a Bacula autochanger script that emulates a conventional
21 # magazine-based tape library device using removable disk drives.
22 # Partitions on the removable drives are used as virtual magazines,
23 # where each "magazine" contains the same number of virtual slots. Each
24 # "slot" holds one virtual tape, where a "tape" is a regular file that
25 # Bacula treats as a "Device Type = File" volume.
26 #
27 # This script will be invoked by Bacula using the Bacula Autochanger
28 # Interface and will be passed the following arguments:
29 #
30 # vchanger "changer-device" "command" "slot" "archive-device" "drive-index"
31 # $1 $2 $3 #4 #5
32 #
33 # See the Bacula documentation for Autochanger Interface details
34 #
35 # Copyright (C) 2006 Josh Fisher
36 #
37 # Permission to use, copy, modify, distribute, and sell this software
38 # and its documentation for any purpose is hereby granted without fee,
39 # provided that the above copyright notice appears in all copies. This
40 # software is provided "as is" without express or implied warranty.
41 #
42 # This software is distributed in the hope that it will be useful,
43 # but WITHOUT ANY WARRANTY; without even the implied warranty of
44 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
45 #
46 # $Id: vchanger,v 0.7.3 2006/11/05 11:31:47 jfisher Exp $
47
48 #
49 # log whats done
50 #
51 dbgfile="/var/lib/bacula/vchanger.log"
52 # to turn on logging, uncomment the following line
53 #touch $dbgfile
54 #
55
56 #
57 # Write to a log file
58 # To log debugging info, create file /var/bacula/vchanger.log
59 # with write permission for bacula-sd user. To stop logging,
60 # delete file /var/bacula/vchanger.log
61 #
62 function debug()
63 {
64 if test -e $dbgfile; then
65 echo "`date +\"%Y%m%d-%H:%M:%S\"` $*" >> $dbgfile
66 fi
67 }
68
69 #
70 # Return length of string $1
71 #
72 function strlen ()
73 {
74 expr length $1
75 }
76
77 #
78 # Prepend zeros to $1 and return a string that is $2 characters long
79 #
80 function mklen ()
81 {
82 o1=$1
83 while [ `eval strlen ${o1}` -lt ${2} ]; do
84 o1="0${o1}"
85 done
86 echo $o1
87 }
88
89 #
90 # Initialize autochanger's state directory if not already initialized
91 #
92 function init_statedir() {
93 debug "Initializing $statedir"
94 # Create state dir if needed
95 if [ ! -d "${statedir}" ]; then
96 mkdir "${statedir}"
97 if [ $? -ne 0 ]; then
98 echo "Could not create ${statedir}"
99 exit 1
100 fi
101 fi
102 chmod 770 "${statedir}"
103 if [ $? -ne 0 ]; then
104 echo "Could not chmod ${statedir}"
105 exit 1
106 fi
107 # Create nextmag file to hold max magazine index used
108 if [ ! -f "${statedir}/nextmag" ]; then
109 echo 0 >"${statedir}/nextmag"
110 if [ $? -ne 0 ]; then
111 echo "Could not create ${statedir}/nextmag"
112 exit 1
113 fi
114 fi
115 chmod 660 "${statedir}/nextmag"
116 if [ $? -ne 0 ]; then
117 echo "Could not chmod ${statedir}/nextmag"
118 exit 1
119 fi
120 # Check nextmag value
121 nextmag=`cat "${statedir}/nextmag"`
122 if [ $? -ne 0 -o "${nextmag}" == "" -o $nextmag -lt 0 -o $nextmag -gt 99 ]; then
123 echo "${statedir}/nextmag has invalid value"
124 return 1
125 fi
126 # Create 'loaded' files for each virtual drive that hold the slot
127 # number currently loaded in that 'drive'
128 i=0
129 while [ $i -le $maxdrive ]; do
130 if [ ! -f "${statedir}/loaded${i}" ]; then
131 echo "0" 2>/dev/null >"${statedir}/loaded${i}"
132 if [ $? -ne 0 ]; then
133 echo "Could not create ${statedir}/loaded${i}"
134 exit 1
135 fi
136 chmod 660 "${statedir}/loaded${i}"
137 if [ $? -ne 0 ]; then
138 echo "Could not chmod ${statedir}/loaded${i}"
139 exit 1
140 fi
141 fi
142 i=`expr ${i} + 1`
143 done
144 }
145
146
147 #
148 # Initialize magazine if not already initialized
149 #
150 function init_magazine() {
151 debug "Initializing magazine"
152 # Get max magazine index that has been used
153 nextmag=`cat "${statedir}/nextmag"`
154 if [ $? -ne 0 -o "${nextmag}" == "" ]; then
155 echo "Failed to read ${statedir}/nextmag"
156 exit 1
157 fi
158 # Check magazine for existing index
159 if [ -f "${mountpoint}/index" ]; then
160 # retrieve existing magazine index
161 mi=`cat "${mountpoint}/index"`
162 if [ $? -ne 0 ]; then
163 echo "Failed to read ${mountpoint}/index"
164 exit 1
165 fi
166 # must be 1-99
167 if [ $mi -lt 1 -o $mi -gt 99 ]; then
168 echo "Magazine has invalid index ${mi}"
169 exit 1
170 fi
171 else
172 # new magazine, so assign it the next avail index
173 mi=`expr ${nextmag} + 1`
174 if [ $mi -lt 0 -o $mi -gt 99 ]; then
175 echo "Max magazines exceeded"
176 exit 1
177 fi
178 echo $mi 2>/dev/null >"${mountpoint}/index"
179 if [ $? -ne 0 ]; then
180 echo "Failed to write ${mountpoint}/index"
181 exit 1
182 fi
183 fi
184 # make sure max index used is up to date
185 if [ $mi -gt $nextmag ]; then
186 echo $mi 2>/dev/null >"${statedir}/nextmag"
187 if [ $? -ne 0 ]; then
188 echo "Failed to update ${statedir}/nextmag"
189 exit 1
190 fi
191 fi
192 # make magazine index 2 digits
193 magindex=`eval mklen ${mi} 2`
194 # setup slot files (ie. virtual tapes)
195 i=1
196 while [ $i -le $magslots ]; do
197 s=`eval mklen ${i} 3`
198 f="${mountpoint}/${volumenameprefix}m${magindex}s${s}"
199 if [ ! -f "${f}" ]; then
200 touch "${f}" 2>/dev/null >/dev/null
201 if [ $? -ne 0 ]; then
202 echo "Failed to create ${f}"
203 exit 1
204 fi
205 fi
206 i=`expr ${i} + 1`
207 done
208 return 0
209 }
210
211
212 #
213 # check parameter count on commandline
214 #
215 function check_parm_count() {
216 pCount=$1
217 pCountNeed=$2
218 if test $pCount -lt $pCountNeed; then
219 echo "usage: vchanger ctl-device command [slot archive-device drive-index]"
220 echo " Insufficient number of arguments arguments given."
221 if test $pCount -lt 2; then
222 echo " Mimimum usage is first two arguments ..."
223 else
224 echo " Command expected $pCountNeed arguments"
225 fi
226 exit 1
227 fi
228 }
229
230
231 # Setup arguments
232 ctl=$1
233 cmd="$2"
234 slot=$3
235 device=$4
236 drive=$5
237
238 # Setup default config values
239 magslots=10
240 maxdrive=0
241 statedir="/var/bacula/vchanger"
242 mountpoint=
243 volumenameprefix=
244
245 # Pull in conf file
246 if [ -f $ctl ]; then
247 . $ctl
248 else
249 echo "Config file ${ctl} not found"
250 exit 1
251 fi
252
253 # check for required config values
254 if [ "${mountpoint}" == "" ]; then
255 echo "Required variable 'mountpoint' not defined in ${ctl}"
256 exit 1
257 fi
258 if [ "${magslots}" == "" -o $magslots -lt 1 -o $magslots -gt 999 ]; then
259 echo "Ivalid value for 'magslots' in ${ctl}"
260 exit 1
261 fi
262 if [ "${maxdrive}" == "" -o $maxdrive -lt 0 -o $maxdrive -ge $magslots ]; then
263 echo "Invalid value for 'maxdrive' in ${ctl}"
264 exit 1
265 fi
266 if [ "${statedir}" == "" ]; then
267 echo "Invalid value for 'statedir' in ${ctl}"
268 exit 1
269 fi
270
271 # Initialize state directory for this autochanger
272 init_statedir
273
274 # Check for special cases where only 2 arguments are needed,
275 # all others are a minimum of 5
276 #
277 case $2 in
278 list)
279 check_parm_count $# 2
280 ;;
281 slots)
282 check_parm_count $# 2
283 ;;
284 *)
285 check_parm_count $# 5
286 if [ $drive -gt $maxdrive ]; then
287 echo "Drive ($drive) out of range (0-${maxdrive})"
288 exit 1
289 fi
290 if [ $slot -gt $magslots ]; then
291 echo "Slot ($slot) out of range (1-$magslots)"
292 exit 1
293 fi
294 ;;
295 esac
296
297 debug "Parms: $ctl $cmd $slot $device $drive"
298
299 case $cmd in
300 unload)
301 debug "Doing vchanger -f $ctl unload $slot $device $drive"
302 ld=`cat "${statedir}/loaded${drive}"`
303 if [ $? -ne 0 ]; then
304 echo "Failed to read ${statedir}/loaded${drive}"
305 exit 1
306 fi
307 if [ $slot -eq $ld ]; then
308 echo "0" >"${statedir}/loaded${drive}"
309 if [ $? -ne 0 ]; then
310 echo "Failed to write ${statedir}/loaded${drive}"
311 exit 1
312 fi
313 unlink "${device}" 2>/dev/null >/dev/null
314 exit 0
315 fi
316 if [ $ld -eq 0 ]; then
317 echo "Drive ${drive} Is Empty"
318 else
319 echo "Storage Element ${slot} is Already Full"
320 fi
321 exit 1
322 ;;
323
324 load)
325 debug "Doing vchanger $ctl load $slot $device $drive"
326 ld=`cat "${statedir}/loaded${drive}"`
327 if [ $? -ne 0 ]; then
328 echo "Failed to read ${statedir}/loaded${drive}"
329 exit 1
330 fi
331 if [ $ld -eq 0 ]; then
332 unlink "${device}" 2>/dev/null >/dev/null
333 # make sure slot is not loaded in another drive
334 i=0
335 while [ $i -le $maxdrive ]; do
336 if [ $i -ne $drive ]; then
337 ldi=`cat "${statedir}/loaded${i}"`
338 if [ $ldi -eq $slot ]; then
339 echo "Storage Element ${slot} Empty (loaded in drive ${i})"
340 exit 1
341 fi
342 fi
343 i=`expr ${i} + 1`
344 done
345 init_magazine
346 if [ $? -ne 0 ]; then
347 echo "Magazine Not Loaded"
348 exit 1
349 fi
350 s=`eval mklen ${slot} 3`
351 ln -s "${mountpoint}/${volumenameprefix}m${magindex}s${s}" "${device}"
352 echo $slot >"${statedir}/loaded${drive}"
353 exit 0
354 else
355 echo "Drive ${drive} Full (Storage element ${ld} loaded)"
356 exit 1
357 fi
358 ;;
359
360 list)
361 debug "Doing vchanger -f $ctl -- to list volumes"
362 init_magazine
363 if [ $? -ne 0 ]; then
364 echo "Magazine Not Loaded"
365 exit 1
366 fi
367 i=1
368 while [ $i -le $magslots ]; do
369 s=`eval mklen ${i} 3`
370 echo "${i}:${volumenameprefix}m${magindex}s${s}"
371 i=`expr ${i} + 1`
372 done
373 exit 0
374 ;;
375
376 loaded)
377 debug "Doing vchanger -f $ctl $drive -- to find what is loaded"
378 cat "${statedir}/loaded${drive}"
379 exit 0
380 ;;
381
382 slots)
383 debug "Doing vchanger -f $ctl -- to get count of slots"
384 echo $magslots
385 exit 0
386 ;;
387 esac
388 ```
389
390 # Labeling Volumes Automatically
391
392 For creating labels for new volumes automatically in an autochanger setup, you can use the `label barcodes` command in bconsole.
393
394 # Recovering an error volume
395
396 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:
397
398 # Disaster Recovery for Windows Server 2012 R2 using Bacula
399
400 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.
401
402 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.
403
404 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.
405
406 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.
407
408 **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.
409
410 ## Step-by-Step Guide
411
412 1. 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.
413 1. 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.
414 1. 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.
415 1. We format the C: drive, so that the files that were just created by the installer are deleted.
416 1. 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.
417 1. 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.
418 1. 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.
419 1. 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:`c:
420 cd BaculaJustForRestore
421 bacula-fd /run -c C:\BaculaJustForRestore\bacula-fd.conf`
422 1. 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.
423 1. 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).
424
425 If the computer does not boot the system in step 10, 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.
426
427 # Making a system state backup using Windows Server Backup
428
429 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.
430
431 ```bat
432 @echo off
433 if "%1" == "" (
434 echo Backup job level has to be passed as first parameter.
435 exit /b 1
436 )
437 if /i %1 == full (
438 rem wbadmin delete systemstatebackup -backupTarget:p: -keepVersions:0 -quiet
439 if exist p:\WindowsImageBackup rd /s /q p:\WindowsImageBackup
440 wbadmin start systemstatebackup -backupTarget:p: -quiet
441 ) else (
442 echo Backup Level is %1, skipping system state backup.
443 )
444 ```