1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8# TODO: test configuration when building nixexpr (use -t parameter)
9# TODO: support sqlite3 (it's deprecate?) and mysql
10
11let
12 inherit (lib)
13 concatStringsSep
14 literalExpression
15 mapAttrsToList
16 mkIf
17 mkOption
18 optional
19 optionalString
20 types
21 ;
22 libDir = "/var/lib/bacula";
23
24 yes_no = bool: if bool then "yes" else "no";
25 tls_conf =
26 tls_cfg:
27 optionalString tls_cfg.enable (
28 concatStringsSep "\n" (
29 [ "TLS Enable = yes;" ]
30 ++ optional (tls_cfg.require != null) "TLS Require = ${yes_no tls_cfg.require};"
31 ++ optional (tls_cfg.certificate != null) ''TLS Certificate = "${tls_cfg.certificate}";''
32 ++ [ ''TLS Key = "${tls_cfg.key}";'' ]
33 ++ optional (tls_cfg.verifyPeer != null) "TLS Verify Peer = ${yes_no tls_cfg.verifyPeer};"
34 ++ optional (
35 tls_cfg.allowedCN != [ ]
36 ) "TLS Allowed CN = ${concatStringsSep " " (tls_cfg.allowedCN)};"
37 ++ optional (
38 tls_cfg.caCertificateFile != null
39 ) ''TLS CA Certificate File = "${tls_cfg.caCertificateFile}";''
40 )
41 );
42
43 fd_cfg = config.services.bacula-fd;
44 fd_conf = pkgs.writeText "bacula-fd.conf" ''
45 Client {
46 Name = "${fd_cfg.name}";
47 FDPort = ${toString fd_cfg.port};
48 WorkingDirectory = ${libDir};
49 Pid Directory = /run;
50 ${fd_cfg.extraClientConfig}
51 ${tls_conf fd_cfg.tls}
52 }
53
54 ${concatStringsSep "\n" (
55 mapAttrsToList (name: value: ''
56 Director {
57 Name = "${name}";
58 Password = ${value.password};
59 Monitor = ${value.monitor};
60 ${tls_conf value.tls}
61 }
62 '') fd_cfg.director
63 )}
64
65 Messages {
66 Name = Standard;
67 syslog = all, !skipped, !restored
68 ${fd_cfg.extraMessagesConfig}
69 }
70 '';
71
72 sd_cfg = config.services.bacula-sd;
73 sd_conf = pkgs.writeText "bacula-sd.conf" ''
74 Storage {
75 Name = "${sd_cfg.name}";
76 SDPort = ${toString sd_cfg.port};
77 WorkingDirectory = ${libDir};
78 Pid Directory = /run;
79 ${sd_cfg.extraStorageConfig}
80 ${tls_conf sd_cfg.tls}
81 }
82
83 ${concatStringsSep "\n" (
84 mapAttrsToList (name: value: ''
85 Autochanger {
86 Name = "${name}";
87 Device = ${concatStringsSep ", " (map (a: "\"${a}\"") value.devices)};
88 Changer Device = ${value.changerDevice};
89 Changer Command = ${value.changerCommand};
90 ${value.extraAutochangerConfig}
91 }
92 '') sd_cfg.autochanger
93 )}
94
95 ${concatStringsSep "\n" (
96 mapAttrsToList (name: value: ''
97 Device {
98 Name = "${name}";
99 Archive Device = ${value.archiveDevice};
100 Media Type = ${value.mediaType};
101 ${value.extraDeviceConfig}
102 }
103 '') sd_cfg.device
104 )}
105
106 ${concatStringsSep "\n" (
107 mapAttrsToList (name: value: ''
108 Director {
109 Name = "${name}";
110 Password = ${value.password};
111 Monitor = ${value.monitor};
112 ${tls_conf value.tls}
113 }
114 '') sd_cfg.director
115 )}
116
117 Messages {
118 Name = Standard;
119 syslog = all, !skipped, !restored
120 ${sd_cfg.extraMessagesConfig}
121 }
122 '';
123
124 dir_cfg = config.services.bacula-dir;
125 dir_conf = pkgs.writeText "bacula-dir.conf" ''
126 Director {
127 Name = "${dir_cfg.name}";
128 Password = ${dir_cfg.password};
129 DirPort = ${toString dir_cfg.port};
130 Working Directory = ${libDir};
131 Pid Directory = /run/;
132 QueryFile = ${pkgs.bacula}/etc/query.sql;
133 ${tls_conf dir_cfg.tls}
134 ${dir_cfg.extraDirectorConfig}
135 }
136
137 Catalog {
138 Name = PostgreSQL;
139 dbname = bacula;
140 user = bacula;
141 }
142
143 Messages {
144 Name = Standard;
145 syslog = all, !skipped, !restored
146 ${dir_cfg.extraMessagesConfig}
147 }
148
149 ${dir_cfg.extraConfig}
150 '';
151
152 linkOption =
153 name: destination: "[${name}](#opt-${builtins.replaceStrings [ "<" ">" ] [ "_" "_" ] destination})";
154 tlsLink =
155 destination: submodulePath:
156 linkOption "${submodulePath}.${destination}" "${submodulePath}.${destination}";
157
158 tlsOptions =
159 submodulePath:
160 { ... }:
161 {
162 options = {
163 enable = mkOption {
164 type = types.bool;
165 default = false;
166 description = ''
167 Specifies if TLS should be enabled.
168 If this set to `false` TLS will be completely disabled, even if ${tlsLink "tls.require" submodulePath} is true.
169 '';
170 };
171 require = mkOption {
172 type = types.nullOr types.bool;
173 default = null;
174 description = ''
175 Require TLS or TLS-PSK encryption.
176 This directive is ignored unless one of ${tlsLink "tls.enable" submodulePath} is true or TLS PSK Enable is set to `yes`.
177 If TLS is not required while TLS or TLS-PSK are enabled, then the Bacula component
178 will connect with other components either with or without TLS or TLS-PSK
179
180 If ${tlsLink "tls.enable" submodulePath} or TLS-PSK is enabled and TLS is required, then the Bacula
181 component will refuse any connection request that does not use TLS.
182 '';
183 };
184 certificate = mkOption {
185 type = types.nullOr types.path;
186 default = null;
187 description = ''
188 The full path to the PEM encoded TLS certificate.
189 It will be used as either a client or server certificate,
190 depending on the connection direction.
191 This directive is required in a server context, but it may
192 not be specified in a client context if ${tlsLink "tls.verifyPeer" submodulePath} is
193 `false` in the corresponding server context.
194 '';
195 };
196 key = mkOption {
197 type = types.path;
198 description = ''
199 The path of a PEM encoded TLS private key.
200 It must correspond to the TLS certificate.
201 '';
202 };
203 verifyPeer = mkOption {
204 type = types.nullOr types.bool;
205 default = null;
206 description = ''
207 Verify peer certificate.
208 Instructs server to request and verify the client's X.509 certificate.
209 Any client certificate signed by a known-CA will be accepted.
210 Additionally, the client's X509 certificate Common Name must meet the value of the Address directive.
211 If ${tlsLink "tls.allowedCN" submodulePath} is used,
212 the client's x509 certificate Common Name must also correspond to
213 one of the CN specified in the ${tlsLink "tls.allowedCN" submodulePath} directive.
214 This directive is valid only for a server and not in client context.
215
216 Standard from Bacula is `true`.
217 '';
218 };
219 allowedCN = mkOption {
220 type = types.listOf types.str;
221 default = [ ];
222 description = ''
223 Common name attribute of allowed peer certificates.
224 This directive is valid for a server and in a client context.
225 If this directive is specified, the peer certificate will be verified against this list.
226 In the case this directive is configured on a server side, the allowed
227 CN list will not be checked if ${tlsLink "tls.verifyPeer" submodulePath} is false.
228 '';
229 };
230 caCertificateFile = mkOption {
231 type = types.nullOr types.path;
232 default = null;
233 description = ''
234 The path specifying a PEM encoded TLS CA certificate(s).
235 Multiple certificates are permitted in the file.
236 One of TLS CA Certificate File or TLS CA Certificate Dir are required in a server context, unless
237 ${tlsLink "tls.verifyPeer" submodulePath} is false, and are always required in a client context.
238 '';
239 };
240 };
241 };
242
243 directorOptions =
244 submodulePath:
245 { ... }:
246 {
247 options = {
248 password = mkOption {
249 type = types.str;
250 # TODO: required?
251 description = ''
252 Specifies the password that must be supplied for the default Bacula
253 Console to be authorized. The same password must appear in the
254 Director resource of the Console configuration file. For added
255 security, the password is never passed across the network but instead
256 a challenge response hash code created with the password. This
257 directive is required. If you have either /dev/random or bc on your
258 machine, Bacula will generate a random password during the
259 configuration process, otherwise it will be left blank and you must
260 manually supply it.
261
262 The password is plain text. It is not generated through any special
263 process but as noted above, it is better to use random text for
264 security reasons.
265 '';
266 };
267
268 monitor = mkOption {
269 type = types.enum [
270 "no"
271 "yes"
272 ];
273 default = "no";
274 example = "yes";
275 description = ''
276 If Monitor is set to `no`, this director will have
277 full access to this Storage daemon. If Monitor is set to
278 `yes`, this director will only be able to fetch the
279 current status of this Storage daemon.
280
281 Please note that if this director is being used by a Monitor, we
282 highly recommend to set this directive to yes to avoid serious
283 security problems.
284 '';
285 };
286
287 tls = mkOption {
288 type = types.submodule (tlsOptions "${submodulePath}.director.<name>");
289 description = ''
290 TLS Options for the Director in this Configuration.
291 '';
292 };
293 };
294 };
295
296 autochangerOptions =
297 { ... }:
298 {
299 options = {
300 changerDevice = mkOption {
301 type = types.str;
302 description = ''
303 The specified name-string must be the generic SCSI device name of the
304 autochanger that corresponds to the normal read/write Archive Device
305 specified in the Device resource. This generic SCSI device name
306 should be specified if you have an autochanger or if you have a
307 standard tape drive and want to use the Alert Command (see below).
308 For example, on Linux systems, for an Archive Device name of
309 `/dev/nst0`, you would specify
310 `/dev/sg0` for the Changer Device name. Depending
311 on your exact configuration, and the number of autochangers or the
312 type of autochanger, what you specify here can vary. This directive
313 is optional. See the Using AutochangersAutochangersChapter chapter of
314 this manual for more details of using this and the following
315 autochanger directives.
316 '';
317 };
318
319 changerCommand = mkOption {
320 type = types.str;
321 description = ''
322 The name-string specifies an external program to be called that will
323 automatically change volumes as required by Bacula. Normally, this
324 directive will be specified only in the AutoChanger resource, which
325 is then used for all devices. However, you may also specify the
326 different Changer Command in each Device resource. Most frequently,
327 you will specify the Bacula supplied mtx-changer script as follows:
328
329 `"/path/mtx-changer %c %o %S %a %d"`
330
331 and you will install the mtx on your system (found in the depkgs
332 release). An example of this command is in the default bacula-sd.conf
333 file. For more details on the substitution characters that may be
334 specified to configure your autochanger please see the
335 AutochangersAutochangersChapter chapter of this manual. For FreeBSD
336 users, you might want to see one of the several chio scripts in
337 examples/autochangers.
338 '';
339 default = "/etc/bacula/mtx-changer %c %o %S %a %d";
340 };
341
342 devices = mkOption {
343 description = "";
344 type = types.listOf types.str;
345 };
346
347 extraAutochangerConfig = mkOption {
348 default = "";
349 type = types.lines;
350 description = ''
351 Extra configuration to be passed in Autochanger directive.
352 '';
353 example = ''
354
355 '';
356 };
357 };
358 };
359
360 deviceOptions =
361 { ... }:
362 {
363 options = {
364 archiveDevice = mkOption {
365 # TODO: required?
366 type = types.str;
367 description = ''
368 The specified name-string gives the system file name of the storage
369 device managed by this storage daemon. This will usually be the
370 device file name of a removable storage device (tape drive), for
371 example `/dev/nst0` or
372 `/dev/rmt/0mbn`. For a DVD-writer, it will be for
373 example `/dev/hdc`. It may also be a directory name
374 if you are archiving to disk storage. In this case, you must supply
375 the full absolute path to the directory. When specifying a tape
376 device, it is preferable that the "non-rewind" variant of the device
377 file name be given.
378 '';
379 };
380
381 mediaType = mkOption {
382 # TODO: required?
383 type = types.str;
384 description = ''
385 The specified name-string names the type of media supported by this
386 device, for example, `DLT7000`. Media type names are
387 arbitrary in that you set them to anything you want, but they must be
388 known to the volume database to keep track of which storage daemons
389 can read which volumes. In general, each different storage type
390 should have a unique Media Type associated with it. The same
391 name-string must appear in the appropriate Storage resource
392 definition in the Director's configuration file.
393
394 Even though the names you assign are arbitrary (i.e. you choose the
395 name you want), you should take care in specifying them because the
396 Media Type is used to determine which storage device Bacula will
397 select during restore. Thus you should probably use the same Media
398 Type specification for all drives where the Media can be freely
399 interchanged. This is not generally an issue if you have a single
400 Storage daemon, but it is with multiple Storage daemons, especially
401 if they have incompatible media.
402
403 For example, if you specify a Media Type of `DDS-4`
404 then during the restore, Bacula will be able to choose any Storage
405 Daemon that handles `DDS-4`. If you have an
406 autochanger, you might want to name the Media Type in a way that is
407 unique to the autochanger, unless you wish to possibly use the
408 Volumes in other drives. You should also ensure to have unique Media
409 Type names if the Media is not compatible between drives. This
410 specification is required for all devices.
411
412 In addition, if you are using disk storage, each Device resource will
413 generally have a different mount point or directory. In order for
414 Bacula to select the correct Device resource, each one must have a
415 unique Media Type.
416 '';
417 };
418
419 extraDeviceConfig = mkOption {
420 default = "";
421 type = types.lines;
422 description = ''
423 Extra configuration to be passed in Device directive.
424 '';
425 example = ''
426 LabelMedia = yes
427 Random Access = no
428 AutomaticMount = no
429 RemovableMedia = no
430 MaximumOpenWait = 60
431 AlwaysOpen = no
432 '';
433 };
434 };
435 };
436
437in
438{
439 options = {
440 services.bacula-fd = {
441 enable = mkOption {
442 type = types.bool;
443 default = false;
444 description = ''
445 Whether to enable the Bacula File Daemon.
446 '';
447 };
448
449 name = mkOption {
450 default = "${config.networking.hostName}-fd";
451 defaultText = literalExpression ''"''${config.networking.hostName}-fd"'';
452 type = types.str;
453 description = ''
454 The client name that must be used by the Director when connecting.
455 Generally, it is a good idea to use a name related to the machine so
456 that error messages can be easily identified if you have multiple
457 Clients. This directive is required.
458 '';
459 };
460
461 port = mkOption {
462 default = 9102;
463 type = types.port;
464 description = ''
465 This specifies the port number on which the Client listens for
466 Director connections. It must agree with the FDPort specified in
467 the Client resource of the Director's configuration file.
468 '';
469 };
470
471 director = mkOption {
472 default = { };
473 description = ''
474 This option defines director resources in Bacula File Daemon.
475 '';
476 type = types.attrsOf (types.submodule (directorOptions "services.bacula-fd"));
477 };
478
479 tls = mkOption {
480 type = types.submodule (tlsOptions "services.bacula-fd");
481 default = { };
482 description = ''
483 TLS Options for the File Daemon.
484 Important notice: The backup won't be encrypted.
485 '';
486 };
487
488 extraClientConfig = mkOption {
489 default = "";
490 type = types.lines;
491 description = ''
492 Extra configuration to be passed in Client directive.
493 '';
494 example = ''
495 Maximum Concurrent Jobs = 20;
496 Heartbeat Interval = 30;
497 '';
498 };
499
500 extraMessagesConfig = mkOption {
501 default = "";
502 type = types.lines;
503 description = ''
504 Extra configuration to be passed in Messages directive.
505 '';
506 example = ''
507 console = all
508 '';
509 };
510 };
511
512 services.bacula-sd = {
513 enable = mkOption {
514 type = types.bool;
515 default = false;
516 description = ''
517 Whether to enable Bacula Storage Daemon.
518 '';
519 };
520
521 name = mkOption {
522 default = "${config.networking.hostName}-sd";
523 defaultText = literalExpression ''"''${config.networking.hostName}-sd"'';
524 type = types.str;
525 description = ''
526 Specifies the Name of the Storage daemon.
527 '';
528 };
529
530 port = mkOption {
531 default = 9103;
532 type = types.port;
533 description = ''
534 Specifies port number on which the Storage daemon listens for
535 Director connections.
536 '';
537 };
538
539 director = mkOption {
540 default = { };
541 description = ''
542 This option defines Director resources in Bacula Storage Daemon.
543 '';
544 type = types.attrsOf (types.submodule (directorOptions "services.bacula-sd"));
545 };
546
547 device = mkOption {
548 default = { };
549 description = ''
550 This option defines Device resources in Bacula Storage Daemon.
551 '';
552 type = types.attrsOf (types.submodule deviceOptions);
553 };
554
555 autochanger = mkOption {
556 default = { };
557 description = ''
558 This option defines Autochanger resources in Bacula Storage Daemon.
559 '';
560 type = types.attrsOf (types.submodule autochangerOptions);
561 };
562
563 extraStorageConfig = mkOption {
564 default = "";
565 type = types.lines;
566 description = ''
567 Extra configuration to be passed in Storage directive.
568 '';
569 example = ''
570 Maximum Concurrent Jobs = 20;
571 Heartbeat Interval = 30;
572 '';
573 };
574
575 extraMessagesConfig = mkOption {
576 default = "";
577 type = types.lines;
578 description = ''
579 Extra configuration to be passed in Messages directive.
580 '';
581 example = ''
582 console = all
583 '';
584 };
585 tls = mkOption {
586 type = types.submodule (tlsOptions "services.bacula-sd");
587 default = { };
588 description = ''
589 TLS Options for the Storage Daemon.
590 Important notice: The backup won't be encrypted.
591 '';
592 };
593
594 };
595
596 services.bacula-dir = {
597 enable = mkOption {
598 type = types.bool;
599 default = false;
600 description = ''
601 Whether to enable Bacula Director Daemon.
602 '';
603 };
604
605 name = mkOption {
606 default = "${config.networking.hostName}-dir";
607 defaultText = literalExpression ''"''${config.networking.hostName}-dir"'';
608 type = types.str;
609 description = ''
610 The director name used by the system administrator. This directive is
611 required.
612 '';
613 };
614
615 port = mkOption {
616 default = 9101;
617 type = types.port;
618 description = ''
619 Specify the port (a positive integer) on which the Director daemon
620 will listen for Bacula Console connections. This same port number
621 must be specified in the Director resource of the Console
622 configuration file. The default is 9101, so normally this directive
623 need not be specified. This directive should not be used if you
624 specify DirAddresses (N.B plural) directive.
625 '';
626 };
627
628 password = mkOption {
629 # TODO: required?
630 type = types.str;
631 description = ''
632 Specifies the password that must be supplied for a Director.
633 '';
634 };
635
636 extraMessagesConfig = mkOption {
637 default = "";
638 type = types.lines;
639 description = ''
640 Extra configuration to be passed in Messages directive.
641 '';
642 example = ''
643 console = all
644 '';
645 };
646
647 extraDirectorConfig = mkOption {
648 default = "";
649 type = types.lines;
650 description = ''
651 Extra configuration to be passed in Director directive.
652 '';
653 example = ''
654 Maximum Concurrent Jobs = 20;
655 Heartbeat Interval = 30;
656 '';
657 };
658
659 extraConfig = mkOption {
660 default = "";
661 type = types.lines;
662 description = ''
663 Extra configuration for Bacula Director Daemon.
664 '';
665 example = ''
666 TODO
667 '';
668 };
669
670 tls = mkOption {
671 type = types.submodule (tlsOptions "services.bacula-dir");
672 default = { };
673 description = ''
674 TLS Options for the Director.
675 Important notice: The backup won't be encrypted.
676 '';
677 };
678 };
679 };
680
681 config = mkIf (fd_cfg.enable || sd_cfg.enable || dir_cfg.enable) {
682 systemd.slices.system-bacula = {
683 description = "Bacula Backup System Slice";
684 documentation = [
685 "man:bacula(8)"
686 "https://www.bacula.org/"
687 ];
688 };
689
690 systemd.services.bacula-fd = mkIf fd_cfg.enable {
691 after = [ "network.target" ];
692 description = "Bacula File Daemon";
693 wantedBy = [ "multi-user.target" ];
694 path = [ pkgs.bacula ];
695 serviceConfig = {
696 ExecStart = "${pkgs.bacula}/sbin/bacula-fd -f -u root -g bacula -c ${fd_conf}";
697 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
698 LogsDirectory = "bacula";
699 StateDirectory = "bacula";
700 Slice = "system-bacula.slice";
701 };
702 };
703
704 systemd.services.bacula-sd = mkIf sd_cfg.enable {
705 after = [ "network.target" ];
706 description = "Bacula Storage Daemon";
707 wantedBy = [ "multi-user.target" ];
708 path = [ pkgs.bacula ];
709 serviceConfig = {
710 ExecStart = "${pkgs.bacula}/sbin/bacula-sd -f -u bacula -g bacula -c ${sd_conf}";
711 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
712 LogsDirectory = "bacula";
713 StateDirectory = "bacula";
714 Slice = "system-bacula.slice";
715 };
716 };
717
718 services.postgresql.enable = lib.mkIf dir_cfg.enable true;
719
720 systemd.services.bacula-dir = mkIf dir_cfg.enable {
721 after = [
722 "network.target"
723 "postgresql.target"
724 ];
725 description = "Bacula Director Daemon";
726 wantedBy = [ "multi-user.target" ];
727 path = [ pkgs.bacula ];
728 serviceConfig = {
729 ExecStart = "${pkgs.bacula}/sbin/bacula-dir -f -u bacula -g bacula -c ${dir_conf}";
730 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
731 LogsDirectory = "bacula";
732 StateDirectory = "bacula";
733 Slice = "system-bacula.slice";
734 };
735 preStart = ''
736 if ! test -e "${libDir}/db-created"; then
737 ${pkgs.postgresql}/bin/createuser --no-superuser --no-createdb --no-createrole bacula
738 #${pkgs.postgresql}/bin/createdb --owner bacula bacula
739
740 # populate DB
741 ${pkgs.bacula}/etc/create_bacula_database postgresql
742 ${pkgs.bacula}/etc/make_bacula_tables postgresql
743 ${pkgs.bacula}/etc/grant_bacula_privileges postgresql
744 touch "${libDir}/db-created"
745 else
746 ${pkgs.bacula}/etc/update_bacula_tables postgresql || true
747 fi
748 '';
749 };
750
751 environment.systemPackages = [ pkgs.bacula ];
752
753 users.users.bacula = {
754 group = "bacula";
755 uid = config.ids.uids.bacula;
756 home = "${libDir}";
757 createHome = true;
758 description = "Bacula Daemons user";
759 shell = "${pkgs.bash}/bin/bash";
760 };
761
762 users.groups.bacula.gid = config.ids.gids.bacula;
763 };
764}