Wiki source code of Bacula
Version 2.1 by Sebastian Marsching on 2022/03/27 13:53
Hide last authors
author | version | line-number | content |
---|---|---|---|
![]() |
1.1 | 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. | ||
![]() |
2.1 | 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: |
![]() |
1.1 | 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 | ``` |