···11-{ config, lib, pkgs, utils, ... }:
11+{
22+ config,
33+ lib,
44+ pkgs,
55+ utils,
66+ ...
77+}:
28let
39 # Type for a valid systemd unit option. Needed for correctly passing "timerConfig" to "systemd.timers"
410 inherit (utils.systemdUtils.unitOptions) unitOption;
···814 description = ''
915 Periodic backups to create with Restic.
1016 '';
1111- type = lib.types.attrsOf (lib.types.submodule ({ name, ... }: {
1212- options = {
1313- passwordFile = lib.mkOption {
1414- type = lib.types.str;
1515- description = ''
1616- Read the repository password from a file.
1717- '';
1818- example = "/etc/nixos/restic-password";
1919- };
1717+ type = lib.types.attrsOf (
1818+ lib.types.submodule (
1919+ { name, ... }:
2020+ {
2121+ options = {
2222+ passwordFile = lib.mkOption {
2323+ type = lib.types.str;
2424+ description = ''
2525+ Read the repository password from a file.
2626+ '';
2727+ example = "/etc/nixos/restic-password";
2828+ };
20292121- environmentFile = lib.mkOption {
2222- type = with lib.types; nullOr str;
2323- default = null;
2424- description = ''
2525- file containing the credentials to access the repository, in the
2626- format of an EnvironmentFile as described by {manpage}`systemd.exec(5)`
2727- '';
2828- };
3030+ environmentFile = lib.mkOption {
3131+ type = with lib.types; nullOr str;
3232+ default = null;
3333+ description = ''
3434+ file containing the credentials to access the repository, in the
3535+ format of an EnvironmentFile as described by {manpage}`systemd.exec(5)`
3636+ '';
3737+ };
29383030- rcloneOptions = lib.mkOption {
3131- type = with lib.types; nullOr (attrsOf (oneOf [ str bool ]));
3232- default = null;
3333- description = ''
3434- Options to pass to rclone to control its behavior.
3535- See <https://rclone.org/docs/#options> for
3636- available options. When specifying option names, strip the
3737- leading `--`. To set a flag such as
3838- `--drive-use-trash`, which does not take a value,
3939- set the value to the Boolean `true`.
4040- '';
4141- example = {
4242- bwlimit = "10M";
4343- drive-use-trash = "true";
4444- };
4545- };
3939+ rcloneOptions = lib.mkOption {
4040+ type =
4141+ with lib.types;
4242+ nullOr (
4343+ attrsOf (oneOf [
4444+ str
4545+ bool
4646+ ])
4747+ );
4848+ default = null;
4949+ description = ''
5050+ Options to pass to rclone to control its behavior.
5151+ See <https://rclone.org/docs/#options> for
5252+ available options. When specifying option names, strip the
5353+ leading `--`. To set a flag such as
5454+ `--drive-use-trash`, which does not take a value,
5555+ set the value to the Boolean `true`.
5656+ '';
5757+ example = {
5858+ bwlimit = "10M";
5959+ drive-use-trash = "true";
6060+ };
6161+ };
46624747- rcloneConfig = lib.mkOption {
4848- type = with lib.types; nullOr (attrsOf (oneOf [ str bool ]));
4949- default = null;
5050- description = ''
5151- Configuration for the rclone remote being used for backup.
5252- See the remote's specific options under rclone's docs at
5353- <https://rclone.org/docs/>. When specifying
5454- option names, use the "config" name specified in the docs.
5555- For example, to set `--b2-hard-delete` for a B2
5656- remote, use `hard_delete = true` in the
5757- attribute set.
5858- Warning: Secrets set in here will be world-readable in the Nix
5959- store! Consider using the `rcloneConfigFile`
6060- option instead to specify secret values separately. Note that
6161- options set here will override those set in the config file.
6262- '';
6363- example = {
6464- type = "b2";
6565- account = "xxx";
6666- key = "xxx";
6767- hard_delete = true;
6868- };
6969- };
6363+ rcloneConfig = lib.mkOption {
6464+ type =
6565+ with lib.types;
6666+ nullOr (
6767+ attrsOf (oneOf [
6868+ str
6969+ bool
7070+ ])
7171+ );
7272+ default = null;
7373+ description = ''
7474+ Configuration for the rclone remote being used for backup.
7575+ See the remote's specific options under rclone's docs at
7676+ <https://rclone.org/docs/>. When specifying
7777+ option names, use the "config" name specified in the docs.
7878+ For example, to set `--b2-hard-delete` for a B2
7979+ remote, use `hard_delete = true` in the
8080+ attribute set.
8181+ Warning: Secrets set in here will be world-readable in the Nix
8282+ store! Consider using the `rcloneConfigFile`
8383+ option instead to specify secret values separately. Note that
8484+ options set here will override those set in the config file.
8585+ '';
8686+ example = {
8787+ type = "b2";
8888+ account = "xxx";
8989+ key = "xxx";
9090+ hard_delete = true;
9191+ };
9292+ };
70937171- rcloneConfigFile = lib.mkOption {
7272- type = with lib.types; nullOr path;
7373- default = null;
7474- description = ''
7575- Path to the file containing rclone configuration. This file
7676- must contain configuration for the remote specified in this backup
7777- set and also must be readable by root. Options set in
7878- `rcloneConfig` will override those set in this
7979- file.
8080- '';
8181- };
9494+ rcloneConfigFile = lib.mkOption {
9595+ type = with lib.types; nullOr path;
9696+ default = null;
9797+ description = ''
9898+ Path to the file containing rclone configuration. This file
9999+ must contain configuration for the remote specified in this backup
100100+ set and also must be readable by root. Options set in
101101+ `rcloneConfig` will override those set in this
102102+ file.
103103+ '';
104104+ };
821058383- inhibitsSleep = lib.mkOption {
8484- default = false;
8585- type = lib.types.bool;
8686- example = true;
8787- description = ''
8888- Prevents the system from sleeping while backing up.
8989- '';
9090- };
106106+ inhibitsSleep = lib.mkOption {
107107+ default = false;
108108+ type = lib.types.bool;
109109+ example = true;
110110+ description = ''
111111+ Prevents the system from sleeping while backing up.
112112+ '';
113113+ };
911149292- repository = lib.mkOption {
9393- type = with lib.types; nullOr str;
9494- default = null;
9595- description = ''
9696- repository to backup to.
9797- '';
9898- example = "sftp:backup@192.168.1.100:/backups/${name}";
9999- };
115115+ repository = lib.mkOption {
116116+ type = with lib.types; nullOr str;
117117+ default = null;
118118+ description = ''
119119+ repository to backup to.
120120+ '';
121121+ example = "sftp:backup@192.168.1.100:/backups/${name}";
122122+ };
100123101101- repositoryFile = lib.mkOption {
102102- type = with lib.types; nullOr path;
103103- default = null;
104104- description = ''
105105- Path to the file containing the repository location to backup to.
106106- '';
107107- };
124124+ repositoryFile = lib.mkOption {
125125+ type = with lib.types; nullOr path;
126126+ default = null;
127127+ description = ''
128128+ Path to the file containing the repository location to backup to.
129129+ '';
130130+ };
108131109109- paths = lib.mkOption {
110110- # This is nullable for legacy reasons only. We should consider making it a pure listOf
111111- # after some time has passed since this comment was added.
112112- type = lib.types.nullOr (lib.types.listOf lib.types.str);
113113- default = [ ];
114114- description = ''
115115- Which paths to backup, in addition to ones specified via
116116- `dynamicFilesFrom`. If null or an empty array and
117117- `dynamicFilesFrom` is also null, no backup command will be run.
118118- This can be used to create a prune-only job.
119119- '';
120120- example = [
121121- "/var/lib/postgresql"
122122- "/home/user/backup"
123123- ];
124124- };
132132+ paths = lib.mkOption {
133133+ # This is nullable for legacy reasons only. We should consider making it a pure listOf
134134+ # after some time has passed since this comment was added.
135135+ type = lib.types.nullOr (lib.types.listOf lib.types.str);
136136+ default = [ ];
137137+ description = ''
138138+ Which paths to backup, in addition to ones specified via
139139+ `dynamicFilesFrom`. If null or an empty array and
140140+ `dynamicFilesFrom` is also null, no backup command will be run.
141141+ This can be used to create a prune-only job.
142142+ '';
143143+ example = [
144144+ "/var/lib/postgresql"
145145+ "/home/user/backup"
146146+ ];
147147+ };
125148126126- exclude = lib.mkOption {
127127- type = lib.types.listOf lib.types.str;
128128- default = [ ];
129129- description = ''
130130- Patterns to exclude when backing up. See
131131- https://restic.readthedocs.io/en/latest/040_backup.html#excluding-files for
132132- details on syntax.
133133- '';
134134- example = [
135135- "/var/cache"
136136- "/home/*/.cache"
137137- ".git"
138138- ];
139139- };
149149+ exclude = lib.mkOption {
150150+ type = lib.types.listOf lib.types.str;
151151+ default = [ ];
152152+ description = ''
153153+ Patterns to exclude when backing up. See
154154+ https://restic.readthedocs.io/en/latest/040_backup.html#excluding-files for
155155+ details on syntax.
156156+ '';
157157+ example = [
158158+ "/var/cache"
159159+ "/home/*/.cache"
160160+ ".git"
161161+ ];
162162+ };
140163141141- timerConfig = lib.mkOption {
142142- type = lib.types.nullOr (lib.types.attrsOf unitOption);
143143- default = {
144144- OnCalendar = "daily";
145145- Persistent = true;
146146- };
147147- description = ''
148148- When to run the backup. See {manpage}`systemd.timer(5)` for
149149- details. If null no timer is created and the backup will only
150150- run when explicitly started.
151151- '';
152152- example = {
153153- OnCalendar = "00:05";
154154- RandomizedDelaySec = "5h";
155155- Persistent = true;
156156- };
157157- };
164164+ timerConfig = lib.mkOption {
165165+ type = lib.types.nullOr (lib.types.attrsOf unitOption);
166166+ default = {
167167+ OnCalendar = "daily";
168168+ Persistent = true;
169169+ };
170170+ description = ''
171171+ When to run the backup. See {manpage}`systemd.timer(5)` for
172172+ details. If null no timer is created and the backup will only
173173+ run when explicitly started.
174174+ '';
175175+ example = {
176176+ OnCalendar = "00:05";
177177+ RandomizedDelaySec = "5h";
178178+ Persistent = true;
179179+ };
180180+ };
158181159159- user = lib.mkOption {
160160- type = lib.types.str;
161161- default = "root";
162162- description = ''
163163- As which user the backup should run.
164164- '';
165165- example = "postgresql";
166166- };
182182+ user = lib.mkOption {
183183+ type = lib.types.str;
184184+ default = "root";
185185+ description = ''
186186+ As which user the backup should run.
187187+ '';
188188+ example = "postgresql";
189189+ };
167190168168- extraBackupArgs = lib.mkOption {
169169- type = lib.types.listOf lib.types.str;
170170- default = [ ];
171171- description = ''
172172- Extra arguments passed to restic backup.
173173- '';
174174- example = [
175175- "--exclude-file=/etc/nixos/restic-ignore"
176176- ];
177177- };
191191+ extraBackupArgs = lib.mkOption {
192192+ type = lib.types.listOf lib.types.str;
193193+ default = [ ];
194194+ description = ''
195195+ Extra arguments passed to restic backup.
196196+ '';
197197+ example = [
198198+ "--exclude-file=/etc/nixos/restic-ignore"
199199+ ];
200200+ };
178201179179- extraOptions = lib.mkOption {
180180- type = lib.types.listOf lib.types.str;
181181- default = [ ];
182182- description = ''
183183- Extra extended options to be passed to the restic --option flag.
184184- '';
185185- example = [
186186- "sftp.command='ssh backup@192.168.1.100 -i /home/user/.ssh/id_rsa -s sftp'"
187187- ];
188188- };
202202+ extraOptions = lib.mkOption {
203203+ type = lib.types.listOf lib.types.str;
204204+ default = [ ];
205205+ description = ''
206206+ Extra extended options to be passed to the restic --option flag.
207207+ '';
208208+ example = [
209209+ "sftp.command='ssh backup@192.168.1.100 -i /home/user/.ssh/id_rsa -s sftp'"
210210+ ];
211211+ };
189212190190- initialize = lib.mkOption {
191191- type = lib.types.bool;
192192- default = false;
193193- description = ''
194194- Create the repository if it doesn't exist.
195195- '';
196196- };
213213+ initialize = lib.mkOption {
214214+ type = lib.types.bool;
215215+ default = false;
216216+ description = ''
217217+ Create the repository if it doesn't exist.
218218+ '';
219219+ };
220220+221221+ pruneOpts = lib.mkOption {
222222+ type = lib.types.listOf lib.types.str;
223223+ default = [ ];
224224+ description = ''
225225+ A list of options (--keep-\* et al.) for 'restic forget
226226+ --prune', to automatically prune old snapshots. The
227227+ 'forget' command is run *after* the 'backup' command, so
228228+ keep that in mind when constructing the --keep-\* options.
229229+ '';
230230+ example = [
231231+ "--keep-daily 7"
232232+ "--keep-weekly 5"
233233+ "--keep-monthly 12"
234234+ "--keep-yearly 75"
235235+ ];
236236+ };
197237198198- pruneOpts = lib.mkOption {
199199- type = lib.types.listOf lib.types.str;
200200- default = [ ];
201201- description = ''
202202- A list of options (--keep-\* et al.) for 'restic forget
203203- --prune', to automatically prune old snapshots. The
204204- 'forget' command is run *after* the 'backup' command, so
205205- keep that in mind when constructing the --keep-\* options.
206206- '';
207207- example = [
208208- "--keep-daily 7"
209209- "--keep-weekly 5"
210210- "--keep-monthly 12"
211211- "--keep-yearly 75"
212212- ];
213213- };
238238+ runCheck = lib.mkOption {
239239+ type = lib.types.bool;
240240+ default = (builtins.length config.services.restic.backups.${name}.checkOpts > 0);
241241+ defaultText = lib.literalExpression ''builtins.length config.services.backups.${name}.checkOpts > 0'';
242242+ description = "Whether to run the `check` command with the provided `checkOpts` options.";
243243+ example = true;
244244+ };
214245215215- runCheck = lib.mkOption {
216216- type = lib.types.bool;
217217- default = (builtins.length config.services.restic.backups.${name}.checkOpts > 0);
218218- defaultText = lib.literalExpression ''builtins.length config.services.backups.${name}.checkOpts > 0'';
219219- description = "Whether to run the `check` command with the provided `checkOpts` options.";
220220- example = true;
221221- };
246246+ checkOpts = lib.mkOption {
247247+ type = lib.types.listOf lib.types.str;
248248+ default = [ ];
249249+ description = ''
250250+ A list of options for 'restic check'.
251251+ '';
252252+ example = [
253253+ "--with-cache"
254254+ ];
255255+ };
222256223223- checkOpts = lib.mkOption {
224224- type = lib.types.listOf lib.types.str;
225225- default = [ ];
226226- description = ''
227227- A list of options for 'restic check'.
228228- '';
229229- example = [
230230- "--with-cache"
231231- ];
232232- };
257257+ dynamicFilesFrom = lib.mkOption {
258258+ type = with lib.types; nullOr str;
259259+ default = null;
260260+ description = ''
261261+ A script that produces a list of files to back up. The
262262+ results of this command are given to the '--files-from'
263263+ option. The result is merged with paths specified via `paths`.
264264+ '';
265265+ example = "find /home/matt/git -type d -name .git";
266266+ };
233267234234- dynamicFilesFrom = lib.mkOption {
235235- type = with lib.types; nullOr str;
236236- default = null;
237237- description = ''
238238- A script that produces a list of files to back up. The
239239- results of this command are given to the '--files-from'
240240- option. The result is merged with paths specified via `paths`.
241241- '';
242242- example = "find /home/matt/git -type d -name .git";
243243- };
268268+ backupPrepareCommand = lib.mkOption {
269269+ type = with lib.types; nullOr str;
270270+ default = null;
271271+ description = ''
272272+ A script that must run before starting the backup process.
273273+ '';
274274+ };
244275245245- backupPrepareCommand = lib.mkOption {
246246- type = with lib.types; nullOr str;
247247- default = null;
248248- description = ''
249249- A script that must run before starting the backup process.
250250- '';
251251- };
276276+ backupCleanupCommand = lib.mkOption {
277277+ type = with lib.types; nullOr str;
278278+ default = null;
279279+ description = ''
280280+ A script that must run after finishing the backup process.
281281+ '';
282282+ };
252283253253- backupCleanupCommand = lib.mkOption {
254254- type = with lib.types; nullOr str;
255255- default = null;
256256- description = ''
257257- A script that must run after finishing the backup process.
258258- '';
259259- };
284284+ package = lib.mkPackageOption pkgs "restic" { };
260285261261- package = lib.mkPackageOption pkgs "restic" { };
286286+ createWrapper = lib.mkOption {
287287+ type = lib.types.bool;
288288+ default = true;
289289+ description = ''
290290+ Whether to generate and add a script to the system path, that has the same environment variables set
291291+ as the systemd service. This can be used to e.g. mount snapshots or perform other opterations, without
292292+ having to manually specify most options.
293293+ '';
294294+ };
262295263263- createWrapper = lib.mkOption {
264264- type = lib.types.bool;
265265- default = true;
266266- description = ''
267267- Whether to generate and add a script to the system path, that has the same environment variables set
268268- as the systemd service. This can be used to e.g. mount snapshots or perform other opterations, without
269269- having to manually specify most options.
270270- '';
271271- };
272272- };
273273- }));
296296+ progressFps = lib.mkOption {
297297+ type = with lib.types; nullOr numbers.nonnegative;
298298+ default = null;
299299+ description = ''
300300+ Controls the frequency of progress reporting.
301301+ '';
302302+ example = 0.1;
303303+ };
304304+ };
305305+ }
306306+ )
307307+ );
274308 default = { };
275309 example = {
276310 localbackup = {
···300334 assertion = (v.repository == null) != (v.repositoryFile == null);
301335 message = "services.restic.backups.${n}: exactly one of repository or repositoryFile should be set";
302336 }) config.services.restic.backups;
303303- systemd.services =
304304- lib.mapAttrs'
305305- (name: backup:
306306- let
307307- extraOptions = lib.concatMapStrings (arg: " -o ${arg}") backup.extraOptions;
308308- inhibitCmd = lib.concatStringsSep " " [
309309- "${pkgs.systemd}/bin/systemd-inhibit"
310310- "--mode='block'"
311311- "--who='restic'"
312312- "--what='sleep'"
313313- "--why=${lib.escapeShellArg "Scheduled backup ${name}"} "
314314- ];
315315- resticCmd = "${lib.optionalString backup.inhibitsSleep inhibitCmd}${backup.package}/bin/restic${extraOptions}";
316316- excludeFlags = lib.optional (backup.exclude != []) "--exclude-file=${pkgs.writeText "exclude-patterns" (lib.concatStringsSep "\n" backup.exclude)}";
317317- filesFromTmpFile = "/run/restic-backups-${name}/includes";
318318- doBackup = (backup.dynamicFilesFrom != null) || (backup.paths != null && backup.paths != []);
319319- pruneCmd = lib.optionals (builtins.length backup.pruneOpts > 0) [
320320- (resticCmd + " forget --prune " + (lib.concatStringsSep " " backup.pruneOpts))
321321- ];
322322- checkCmd = lib.optionals backup.runCheck [
323323- (resticCmd + " check " + (lib.concatStringsSep " " backup.checkOpts))
324324- ];
325325- # Helper functions for rclone remotes
326326- rcloneRemoteName = builtins.elemAt (lib.splitString ":" backup.repository) 1;
327327- rcloneAttrToOpt = v: "RCLONE_" + lib.toUpper (builtins.replaceStrings [ "-" ] [ "_" ] v);
328328- rcloneAttrToConf = v: "RCLONE_CONFIG_" + lib.toUpper (rcloneRemoteName + "_" + v);
329329- toRcloneVal = v: if lib.isBool v then lib.boolToString v else v;
330330- in
331331- lib.nameValuePair "restic-backups-${name}" ({
332332- environment = {
337337+ systemd.services = lib.mapAttrs' (
338338+ name: backup:
339339+ let
340340+ extraOptions = lib.concatMapStrings (arg: " -o ${arg}") backup.extraOptions;
341341+ inhibitCmd = lib.concatStringsSep " " [
342342+ "${pkgs.systemd}/bin/systemd-inhibit"
343343+ "--mode='block'"
344344+ "--who='restic'"
345345+ "--what='sleep'"
346346+ "--why=${lib.escapeShellArg "Scheduled backup ${name}"} "
347347+ ];
348348+ resticCmd = "${lib.optionalString backup.inhibitsSleep inhibitCmd}${backup.package}/bin/restic${extraOptions}";
349349+ excludeFlags = lib.optional (
350350+ backup.exclude != [ ]
351351+ ) "--exclude-file=${pkgs.writeText "exclude-patterns" (lib.concatStringsSep "\n" backup.exclude)}";
352352+ filesFromTmpFile = "/run/restic-backups-${name}/includes";
353353+ doBackup = (backup.dynamicFilesFrom != null) || (backup.paths != null && backup.paths != [ ]);
354354+ pruneCmd = lib.optionals (builtins.length backup.pruneOpts > 0) [
355355+ (resticCmd + " forget --prune " + (lib.concatStringsSep " " backup.pruneOpts))
356356+ ];
357357+ checkCmd = lib.optionals backup.runCheck [
358358+ (resticCmd + " check " + (lib.concatStringsSep " " backup.checkOpts))
359359+ ];
360360+ # Helper functions for rclone remotes
361361+ rcloneRemoteName = builtins.elemAt (lib.splitString ":" backup.repository) 1;
362362+ rcloneAttrToOpt = v: "RCLONE_" + lib.toUpper (builtins.replaceStrings [ "-" ] [ "_" ] v);
363363+ rcloneAttrToConf = v: "RCLONE_CONFIG_" + lib.toUpper (rcloneRemoteName + "_" + v);
364364+ toRcloneVal = v: if lib.isBool v then lib.boolToString v else v;
365365+ in
366366+ lib.nameValuePair "restic-backups-${name}" (
367367+ {
368368+ environment =
369369+ {
333370 # not %C, because that wouldn't work in the wrapper script
334371 RESTIC_CACHE_DIR = "/var/cache/restic-backups-${name}";
335372 RESTIC_PASSWORD_FILE = backup.passwordFile;
336373 RESTIC_REPOSITORY = backup.repository;
337374 RESTIC_REPOSITORY_FILE = backup.repositoryFile;
338338- } // lib.optionalAttrs (backup.rcloneOptions != null) (lib.mapAttrs'
339339- (name: value:
340340- lib.nameValuePair (rcloneAttrToOpt name) (toRcloneVal value)
341341- )
342342- backup.rcloneOptions) // lib.optionalAttrs (backup.rcloneConfigFile != null) {
375375+ }
376376+ // lib.optionalAttrs (backup.rcloneOptions != null) (
377377+ lib.mapAttrs' (
378378+ name: value: lib.nameValuePair (rcloneAttrToOpt name) (toRcloneVal value)
379379+ ) backup.rcloneOptions
380380+ )
381381+ // lib.optionalAttrs (backup.rcloneConfigFile != null) {
343382 RCLONE_CONFIG = backup.rcloneConfigFile;
344344- } // lib.optionalAttrs (backup.rcloneConfig != null) (lib.mapAttrs'
345345- (name: value:
346346- lib.nameValuePair (rcloneAttrToConf name) (toRcloneVal value)
347347- )
348348- backup.rcloneConfig);
349349- path = [ config.programs.ssh.package ];
350350- restartIfChanged = false;
351351- wants = [ "network-online.target" ];
352352- after = [ "network-online.target" ];
353353- serviceConfig = {
383383+ }
384384+ // lib.optionalAttrs (backup.rcloneConfig != null) (
385385+ lib.mapAttrs' (
386386+ name: value: lib.nameValuePair (rcloneAttrToConf name) (toRcloneVal value)
387387+ ) backup.rcloneConfig
388388+ )
389389+ // lib.optionalAttrs (backup.progressFps != null) {
390390+ RESTIC_PROGRESS_FPS = toString backup.progressFps;
391391+ };
392392+ path = [ config.programs.ssh.package ];
393393+ restartIfChanged = false;
394394+ wants = [ "network-online.target" ];
395395+ after = [ "network-online.target" ];
396396+ serviceConfig =
397397+ {
354398 Type = "oneshot";
355355- ExecStart = (lib.optionals doBackup [ "${resticCmd} backup ${lib.concatStringsSep " " (backup.extraBackupArgs ++ excludeFlags)} --files-from=${filesFromTmpFile}" ])
356356- ++ pruneCmd ++ checkCmd;
399399+ ExecStart =
400400+ (lib.optionals doBackup [
401401+ "${resticCmd} backup ${
402402+ lib.concatStringsSep " " (backup.extraBackupArgs ++ excludeFlags)
403403+ } --files-from=${filesFromTmpFile}"
404404+ ])
405405+ ++ pruneCmd
406406+ ++ checkCmd;
357407 User = backup.user;
358408 RuntimeDirectory = "restic-backups-${name}";
359409 CacheDirectory = "restic-backups-${name}";
360410 CacheDirectoryMode = "0700";
361411 PrivateTmp = true;
362362- } // lib.optionalAttrs (backup.environmentFile != null) {
412412+ }
413413+ // lib.optionalAttrs (backup.environmentFile != null) {
363414 EnvironmentFile = backup.environmentFile;
364415 };
365365- } // lib.optionalAttrs (backup.initialize || doBackup || backup.backupPrepareCommand != null) {
366366- preStart = ''
367367- ${lib.optionalString (backup.backupPrepareCommand != null) ''
368368- ${pkgs.writeScript "backupPrepareCommand" backup.backupPrepareCommand}
369369- ''}
370370- ${lib.optionalString (backup.initialize) ''
371371- ${resticCmd} cat config > /dev/null || ${resticCmd} init
372372- ''}
373373- ${lib.optionalString (backup.paths != null && backup.paths != []) ''
374374- cat ${pkgs.writeText "staticPaths" (lib.concatLines backup.paths)} >> ${filesFromTmpFile}
375375- ''}
376376- ${lib.optionalString (backup.dynamicFilesFrom != null) ''
377377- ${pkgs.writeScript "dynamicFilesFromScript" backup.dynamicFilesFrom} >> ${filesFromTmpFile}
378378- ''}
379379- '';
380380- } // lib.optionalAttrs (doBackup || backup.backupCleanupCommand != null) {
381381- postStop = ''
382382- ${lib.optionalString (backup.backupCleanupCommand != null) ''
383383- ${pkgs.writeScript "backupCleanupCommand" backup.backupCleanupCommand}
384384- ''}
385385- ${lib.optionalString doBackup ''
386386- rm ${filesFromTmpFile}
387387- ''}
388388- '';
389389- })
390390- )
391391- config.services.restic.backups;
392392- systemd.timers =
393393- lib.mapAttrs'
394394- (name: backup: lib.nameValuePair "restic-backups-${name}" {
395395- wantedBy = [ "timers.target" ];
396396- timerConfig = backup.timerConfig;
397397- })
398398- (lib.filterAttrs (_: backup: backup.timerConfig != null) config.services.restic.backups);
416416+ }
417417+ // lib.optionalAttrs (backup.initialize || doBackup || backup.backupPrepareCommand != null) {
418418+ preStart = ''
419419+ ${lib.optionalString (backup.backupPrepareCommand != null) ''
420420+ ${pkgs.writeScript "backupPrepareCommand" backup.backupPrepareCommand}
421421+ ''}
422422+ ${lib.optionalString (backup.initialize) ''
423423+ ${resticCmd} cat config > /dev/null || ${resticCmd} init
424424+ ''}
425425+ ${lib.optionalString (backup.paths != null && backup.paths != [ ]) ''
426426+ cat ${pkgs.writeText "staticPaths" (lib.concatLines backup.paths)} >> ${filesFromTmpFile}
427427+ ''}
428428+ ${lib.optionalString (backup.dynamicFilesFrom != null) ''
429429+ ${pkgs.writeScript "dynamicFilesFromScript" backup.dynamicFilesFrom} >> ${filesFromTmpFile}
430430+ ''}
431431+ '';
432432+ }
433433+ // lib.optionalAttrs (doBackup || backup.backupCleanupCommand != null) {
434434+ postStop = ''
435435+ ${lib.optionalString (backup.backupCleanupCommand != null) ''
436436+ ${pkgs.writeScript "backupCleanupCommand" backup.backupCleanupCommand}
437437+ ''}
438438+ ${lib.optionalString doBackup ''
439439+ rm ${filesFromTmpFile}
440440+ ''}
441441+ '';
442442+ }
443443+ )
444444+ ) config.services.restic.backups;
445445+ systemd.timers = lib.mapAttrs' (
446446+ name: backup:
447447+ lib.nameValuePair "restic-backups-${name}" {
448448+ wantedBy = [ "timers.target" ];
449449+ timerConfig = backup.timerConfig;
450450+ }
451451+ ) (lib.filterAttrs (_: backup: backup.timerConfig != null) config.services.restic.backups);
399452400453 # generate wrapper scripts, as described in the createWrapper option
401401- environment.systemPackages = lib.mapAttrsToList (name: backup: let
402402- extraOptions = lib.concatMapStrings (arg: " -o ${arg}") backup.extraOptions;
403403- resticCmd = "${backup.package}/bin/restic${extraOptions}";
404404- in pkgs.writeShellScriptBin "restic-${name}" ''
405405- set -a # automatically export variables
406406- ${lib.optionalString (backup.environmentFile != null) "source ${backup.environmentFile}"}
407407- # set same environment variables as the systemd service
408408- ${lib.pipe config.systemd.services."restic-backups-${name}".environment [
409409- (lib.filterAttrs (n: v: v != null && n != "PATH"))
410410- (lib.mapAttrsToList (n: v: "${n}=${v}"))
411411- (lib.concatStringsSep "\n")
412412- ]}
413413- PATH=${config.systemd.services."restic-backups-${name}".environment.PATH}:$PATH
454454+ environment.systemPackages = lib.mapAttrsToList (
455455+ name: backup:
456456+ let
457457+ extraOptions = lib.concatMapStrings (arg: " -o ${arg}") backup.extraOptions;
458458+ resticCmd = "${backup.package}/bin/restic${extraOptions}";
459459+ in
460460+ pkgs.writeShellScriptBin "restic-${name}" ''
461461+ set -a # automatically export variables
462462+ ${lib.optionalString (backup.environmentFile != null) "source ${backup.environmentFile}"}
463463+ # set same environment variables as the systemd service
464464+ ${lib.pipe config.systemd.services."restic-backups-${name}".environment [
465465+ (lib.filterAttrs (n: v: v != null && n != "PATH"))
466466+ (lib.mapAttrsToList (n: v: "${n}=${v}"))
467467+ (lib.concatStringsSep "\n")
468468+ ]}
469469+ PATH=${config.systemd.services."restic-backups-${name}".environment.PATH}:$PATH
414470415415- exec ${resticCmd} "$@"
416416- '') (lib.filterAttrs (_: v: v.createWrapper) config.services.restic.backups);
471471+ exec ${resticCmd} "$@"
472472+ ''
473473+ ) (lib.filterAttrs (_: v: v.createWrapper) config.services.restic.backups);
417474 };
418475}
+13-1
nixos/modules/services/web-apps/immich.nix
···2020 NoNewPrivileges = true;
2121 PrivateUsers = true;
2222 PrivateTmp = true;
2323- PrivateDevices = true;
2323+ PrivateDevices = cfg.accelerationDevices == [ ];
2424+ DeviceAllow = mkIf (cfg.accelerationDevices != null) cfg.accelerationDevices;
2425 PrivateMounts = true;
2526 ProtectClock = true;
2627 ProtectControlGroups = true;
···159160 Extra configuration environment variables. Refer to the [documentation](https://immich.app/docs/install/environment-variables) for options tagged with 'machine-learning'.
160161 '';
161162 };
163163+ };
164164+165165+ accelerationDevices = mkOption {
166166+ type = types.nullOr (types.listOf types.str);
167167+ default = [ ];
168168+ example = [ "/dev/dri/renderD128" ];
169169+ description = ''
170170+ A list of device paths to hardware acceleration devices that immich should
171171+ have access to. This is useful when transcoding media files.
172172+ The special value `[ ]` will disallow all devices using `PrivateDevices`. `null` will give access to all devices.
173173+ '';
162174 };
163175164176 database = {