diff options
| author | Giacomo Leidi <goodoldpaul@autistici.org> | 2025-01-19 23:04:00 +0100 |
|---|---|---|
| committer | Ludovic Courtès <ludo@gnu.org> | 2025-01-25 00:04:27 +0100 |
| commit | 35c6ae6e58f1cfd397602d12c4f4e70d37f0eb90 (patch) | |
| tree | fd6e79d4783af33baf9e78c6ae9b3a36df8961ec | |
| parent | 44d12f9663ca363134636588279ef70decd1d551 (diff) | |
services: restic-backup: Implement as a Shepherd timer.
This patch implements restic backup with Shepherd services. It is
supposed not to break any existing setup.
* gnu/services/backup.scm (restic-backup-job): Add Shepherd
configuration options;
(restic-backup-job->mcron-job): Replace with...;
(restic-job-log-file): New procedure;
(restic-backup-job->shepherd-service): New procedure;
(restic-backup-activation): New procedure;
(restic-backup-service-type): Replace mcron with Shepherd extension and add
activation extension hook.
* doc/guix.texi: Document it.
Change-Id: I66de3b6a1cb6177f9e4ee0c2acf3013ecbcdd338
Signed-off-by: Ludovic Courtès <ludo@gnu.org>
| -rw-r--r-- | doc/guix.texi | 39 | ||||
| -rw-r--r-- | gnu/services/backup.scm | 122 |
2 files changed, 131 insertions, 30 deletions
diff --git a/doc/guix.texi b/doc/guix.texi index b1463387f3a..9a53bdcd374 100644 --- a/doc/guix.texi +++ b/doc/guix.texi | |||
| @@ -111,7 +111,7 @@ Copyright @copyright{} 2022 (@* | |||
| 111 | Copyright @copyright{} 2022 John Kehayias@* | 111 | Copyright @copyright{} 2022 John Kehayias@* |
| 112 | Copyright @copyright{} 2022–2023 Bruno Victal@* | 112 | Copyright @copyright{} 2022–2023 Bruno Victal@* |
| 113 | Copyright @copyright{} 2022 Ivan Vilata-i-Balaguer@* | 113 | Copyright @copyright{} 2022 Ivan Vilata-i-Balaguer@* |
| 114 | Copyright @copyright{} 2023-2024 Giacomo Leidi@* | 114 | Copyright @copyright{} 2023-2025 Giacomo Leidi@* |
| 115 | Copyright @copyright{} 2022 Antero Mejr@* | 115 | Copyright @copyright{} 2022 Antero Mejr@* |
| 116 | Copyright @copyright{} 2023 Karl Hallsby@* | 116 | Copyright @copyright{} 2023 Karl Hallsby@* |
| 117 | Copyright @copyright{} 2023 Nathaniel Nicandro@* | 117 | Copyright @copyright{} 2023 Nathaniel Nicandro@* |
| @@ -42710,19 +42710,16 @@ following configuration: | |||
| 42710 | "/etc/guix/signing-key.sec")))))))))) | 42710 | "/etc/guix/signing-key.sec")))))))))) |
| 42711 | @end lisp | 42711 | @end lisp |
| 42712 | 42712 | ||
| 42713 | Each @code{restic-backup-job} translates to an mcron job which sets the | 42713 | Each @code{restic-backup-job} translates to a Shepherd timer which sets the |
| 42714 | @env{RESTIC_PASSWORD} environment variable by reading the first line of | 42714 | @env{RESTIC_PASSWORD} environment variable by reading the first line of |
| 42715 | @code{password-file} and runs @command{restic backup}, creating backups | 42715 | @code{password-file} and runs @command{restic backup}, creating backups |
| 42716 | using rclone of all the files listed in the @code{files} field. | 42716 | using rclone of all the files listed in the @code{files} field. |
| 42717 | 42717 | ||
| 42718 | The @code{restic-backup-service-type} installs as well @code{restic-guix} | 42718 | The @code{restic-backup-service-type} provides the ability to instantaneously |
| 42719 | to the system profile, a @code{restic} utility wrapper that allows for easier | 42719 | trigger a backup with the @code{trigger} Shepherd action: |
| 42720 | interaction with the Guix configured backup jobs. For example the following | ||
| 42721 | could be used to instantaneusly trigger a backup for the above shown | ||
| 42722 | configuration, without waiting for the scheduled job: | ||
| 42723 | 42720 | ||
| 42724 | @example | 42721 | @example |
| 42725 | restic-guix backup remote-ftp | 42722 | sudo herd trigger remote-ftp |
| 42726 | @end example | 42723 | @end example |
| 42727 | 42724 | ||
| 42728 | @c %start of fragment | 42725 | @c %start of fragment |
| @@ -42753,6 +42750,23 @@ The restic package to be used for the current job. | |||
| 42753 | @item @code{user} (default: @code{"root"}) (type: string) | 42750 | @item @code{user} (default: @code{"root"}) (type: string) |
| 42754 | The user used for running the current job. | 42751 | The user used for running the current job. |
| 42755 | 42752 | ||
| 42753 | @item @code{group} (default: @code{"root"}) (type: string) | ||
| 42754 | The group used for running the current job. | ||
| 42755 | |||
| 42756 | @item @code{log-file} (type: maybe-string) | ||
| 42757 | The file system path to the log file for this job. By default the file will | ||
| 42758 | have be @file{/var/log/restic-backup/@var{job-name}.log}, where @var{job-name} is the | ||
| 42759 | name defined in the @code{name} field. | ||
| 42760 | |||
| 42761 | @item @code{max-duration} (type: maybe-number) | ||
| 42762 | The maximum duration in seconds that a job may last. Past | ||
| 42763 | @code{max-duration} seconds, the job is forcefully terminated. | ||
| 42764 | |||
| 42765 | @item @code{wait-for-termination?} (default: @code{#f}) (type: boolean) | ||
| 42766 | Wait until the job has finished before considering executing it again; | ||
| 42767 | otherwise, perform it strictly on every occurrence of event, at the risk of | ||
| 42768 | having multiple instances running concurrently. | ||
| 42769 | |||
| 42756 | @item @code{repository} (type: string) | 42770 | @item @code{repository} (type: string) |
| 42757 | The restic repository target of this job. | 42771 | The restic repository target of this job. |
| 42758 | 42772 | ||
| @@ -42765,9 +42779,12 @@ that will be used to set the @env{RESTIC_PASSWORD} environment variable | |||
| 42765 | for the current job. | 42779 | for the current job. |
| 42766 | 42780 | ||
| 42767 | @item @code{schedule} (type: gexp-or-string) | 42781 | @item @code{schedule} (type: gexp-or-string) |
| 42768 | A string or a gexp that will be passed as time specification in the | 42782 | A string or a gexp representing the frequency of the backup. Gexp must |
| 42769 | mcron job specification (@pxref{Syntax, mcron job specifications,, | 42783 | evaluate to @code{calendar-event} records or to strings. Strings must contain |
| 42770 | mcron,GNU@tie{}mcron}). | 42784 | Vixie cron date lines. |
| 42785 | |||
| 42786 | @item @code{requirement} (default: @code{'()}) (type: list-of-symbols) | ||
| 42787 | The list of Shepherd services that this backup job depends upon. | ||
| 42771 | 42788 | ||
| 42772 | @item @code{files} (default: @code{'()}) (type: list-of-lowerables) | 42789 | @item @code{files} (default: @code{'()}) (type: list-of-lowerables) |
| 42773 | The list of files or directories to be backed up. It must be a list of | 42790 | The list of files or directories to be backed up. It must be a list of |
diff --git a/gnu/services/backup.scm b/gnu/services/backup.scm index 555e9fc9590..99a79ff5fbe 100644 --- a/gnu/services/backup.scm +++ b/gnu/services/backup.scm | |||
| @@ -1,5 +1,5 @@ | |||
| 1 | ;;; GNU Guix --- Functional package management for GNU | 1 | ;;; GNU Guix --- Functional package management for GNU |
| 2 | ;;; Copyright © 2024 Giacomo Leidi <goodoldpaul@autistici.org> | 2 | ;;; Copyright © 2024, 2025 Giacomo Leidi <goodoldpaul@autistici.org> |
| 3 | ;;; | 3 | ;;; |
| 4 | ;;; This file is part of GNU Guix. | 4 | ;;; This file is part of GNU Guix. |
| 5 | ;;; | 5 | ;;; |
| @@ -18,9 +18,10 @@ | |||
| 18 | 18 | ||
| 19 | (define-module (gnu services backup) | 19 | (define-module (gnu services backup) |
| 20 | #:use-module (gnu packages backup) | 20 | #:use-module (gnu packages backup) |
| 21 | #:use-module (gnu packages bash) | ||
| 21 | #:use-module (gnu services) | 22 | #:use-module (gnu services) |
| 22 | #:use-module (gnu services configuration) | 23 | #:use-module (gnu services configuration) |
| 23 | #:use-module (gnu services mcron) | 24 | #:use-module (gnu services shepherd) |
| 24 | #:use-module (guix build-system copy) | 25 | #:use-module (guix build-system copy) |
| 25 | #:use-module (guix gexp) | 26 | #:use-module (guix gexp) |
| 26 | #:use-module ((guix licenses) | 27 | #:use-module ((guix licenses) |
| @@ -33,11 +34,16 @@ | |||
| 33 | restic-backup-job-fields | 34 | restic-backup-job-fields |
| 34 | restic-backup-job-restic | 35 | restic-backup-job-restic |
| 35 | restic-backup-job-user | 36 | restic-backup-job-user |
| 37 | restic-backup-job-group | ||
| 38 | restic-backup-job-log-file | ||
| 39 | restic-backup-job-max-duration | ||
| 40 | restic-backup-job-wait-for-termination? | ||
| 36 | restic-backup-job-name | 41 | restic-backup-job-name |
| 37 | restic-backup-job-repository | 42 | restic-backup-job-repository |
| 38 | restic-backup-job-password-file | 43 | restic-backup-job-password-file |
| 39 | restic-backup-job-schedule | 44 | restic-backup-job-schedule |
| 40 | restic-backup-job-files | 45 | restic-backup-job-files |
| 46 | restic-backup-job-requirement | ||
| 41 | restic-backup-job-verbose? | 47 | restic-backup-job-verbose? |
| 42 | restic-backup-job-extra-flags | 48 | restic-backup-job-extra-flags |
| 43 | 49 | ||
| @@ -64,6 +70,12 @@ | |||
| 64 | (define list-of-lowerables? | 70 | (define list-of-lowerables? |
| 65 | (list-of lowerable?)) | 71 | (list-of lowerable?)) |
| 66 | 72 | ||
| 73 | (define list-of-symbols? | ||
| 74 | (list-of symbol?)) | ||
| 75 | |||
| 76 | (define-maybe/no-serialization string) | ||
| 77 | (define-maybe/no-serialization number) | ||
| 78 | |||
| 67 | (define-configuration/no-serialization restic-backup-job | 79 | (define-configuration/no-serialization restic-backup-job |
| 68 | (restic | 80 | (restic |
| 69 | (package restic) | 81 | (package restic) |
| @@ -71,6 +83,23 @@ | |||
| 71 | (user | 83 | (user |
| 72 | (string "root") | 84 | (string "root") |
| 73 | "The user used for running the current job.") | 85 | "The user used for running the current job.") |
| 86 | (group | ||
| 87 | (string "root") | ||
| 88 | "The group used for running the current job.") | ||
| 89 | (log-file | ||
| 90 | (maybe-string) | ||
| 91 | "The file system path to the log file for this job. By default the file will | ||
| 92 | have be @file{/var/log/restic-backup/@var{job-name}.log}, where @var{job-name} is the | ||
| 93 | name defined in the @code{name} field.") | ||
| 94 | (max-duration | ||
| 95 | (maybe-number) | ||
| 96 | "The maximum duration in seconds that a job may last. Past | ||
| 97 | @code{max-duration} seconds, the job is forcefully terminated.") | ||
| 98 | (wait-for-termination? | ||
| 99 | (boolean #f) | ||
| 100 | "Wait until the job has finished before considering executing it again; | ||
| 101 | otherwise, perform it strictly on every occurrence of event, at the risk of | ||
| 102 | having multiple instances running concurrently.") | ||
| 74 | (name | 103 | (name |
| 75 | (string) | 104 | (string) |
| 76 | "A string denoting a name for this job.") | 105 | "A string denoting a name for this job.") |
| @@ -84,9 +113,12 @@ will be used to set the @code{RESTIC_PASSWORD} environment variable for the | |||
| 84 | current job.") | 113 | current job.") |
| 85 | (schedule | 114 | (schedule |
| 86 | (gexp-or-string) | 115 | (gexp-or-string) |
| 87 | "A string or a gexp that will be passed as time specification in the mcron | 116 | "A string or a gexp representing the frequency of the backup. Gexp must |
| 88 | job specification (@pxref{Syntax, mcron job specifications,, mcron, | 117 | evaluate to @code{calendar-event} records or to strings. Strings must contain |
| 89 | GNU@tie{}mcron}).") | 118 | Vixie cron date lines.") |
| 119 | (requirement | ||
| 120 | (list-of-symbols '()) | ||
| 121 | "The list of Shepherd services that this backup job depends upon.") | ||
| 90 | (files | 122 | (files |
| 91 | (list-of-lowerables '()) | 123 | (list-of-lowerables '()) |
| 92 | "The list of files or directories to be backed up. It must be a list of | 124 | "The list of files or directories to be backed up. It must be a list of |
| @@ -175,16 +207,59 @@ command-line arguments to the current job @command{restic backup} invokation.")) | |||
| 175 | 207 | ||
| 176 | (main (command-line))))) | 208 | (main (command-line))))) |
| 177 | 209 | ||
| 178 | (define (restic-backup-job->mcron-job config) | 210 | (define (restic-job-log-file job) |
| 179 | (let ((user | 211 | (let ((name (restic-backup-job-name job)) |
| 180 | (restic-backup-job-user config)) | 212 | (log-file (restic-backup-job-log-file job))) |
| 181 | (schedule | 213 | (if (maybe-value-set? log-file) |
| 182 | (restic-backup-job-schedule config)) | 214 | log-file |
| 183 | (name | 215 | (string-append "/var/log/restic-backup/" name ".log")))) |
| 184 | (restic-backup-job-name config))) | 216 | |
| 185 | #~(job #$schedule | 217 | (define (restic-backup-job->shepherd-service config) |
| 186 | #$(string-append "restic-guix backup " name) | 218 | (let ((schedule (restic-backup-job-schedule config)) |
| 187 | #:user #$user))) | 219 | (name (restic-backup-job-name config)) |
| 220 | (user (restic-backup-job-user config)) | ||
| 221 | (group (restic-backup-job-group config)) | ||
| 222 | (max-duration (restic-backup-job-max-duration config)) | ||
| 223 | (wait-for-termination? (restic-backup-job-wait-for-termination? config)) | ||
| 224 | (log-file (restic-job-log-file config)) | ||
| 225 | (requirement (restic-backup-job-requirement config))) | ||
| 226 | (shepherd-service (provision `(,(string->symbol name))) | ||
| 227 | (requirement | ||
| 228 | `(user-processes file-systems ,@requirement)) | ||
| 229 | (documentation | ||
| 230 | "Run @code{restic} backed backups on a regular basis.") | ||
| 231 | (modules '((shepherd service timer))) | ||
| 232 | (start | ||
| 233 | #~(make-timer-constructor | ||
| 234 | (if (string? #$schedule) | ||
| 235 | (cron-string->calendar-event #$schedule) | ||
| 236 | #$schedule) | ||
| 237 | (command | ||
| 238 | (list | ||
| 239 | ;; We go through bash, instead of executing | ||
| 240 | ;; restic-guix directly, because the login shell | ||
| 241 | ;; gives us the correct user environment that some | ||
| 242 | ;; backends require, such as rclone. | ||
| 243 | (string-append #+bash-minimal "/bin/bash") | ||
| 244 | "-l" "-c" | ||
| 245 | (string-append "restic-guix backup " #$name)) | ||
| 246 | #:user #$user | ||
| 247 | #:group #$group | ||
| 248 | #:environment-variables | ||
| 249 | (list | ||
| 250 | (string-append | ||
| 251 | "HOME=" (passwd:dir (getpwnam #$user))))) | ||
| 252 | #:log-file #$log-file | ||
| 253 | #:wait-for-termination? #$wait-for-termination? | ||
| 254 | #:max-duration #$(and (maybe-value-set? max-duration) | ||
| 255 | max-duration))) | ||
| 256 | (stop | ||
| 257 | #~(make-timer-destructor)) | ||
| 258 | (actions (list (shepherd-action | ||
| 259 | (name 'trigger) | ||
| 260 | (documentation "Manually trigger a backup, | ||
| 261 | without waiting for the scheduled time.") | ||
| 262 | (procedure #~trigger-timer))))))) | ||
| 188 | 263 | ||
| 189 | (define (restic-guix-wrapper-package jobs) | 264 | (define (restic-guix-wrapper-package jobs) |
| 190 | (package | 265 | (package |
| @@ -212,15 +287,24 @@ without waiting for the scheduled job to run.") | |||
| 212 | (restic-guix-wrapper-package jobs)) | 287 | (restic-guix-wrapper-package jobs)) |
| 213 | '()))) | 288 | '()))) |
| 214 | 289 | ||
| 290 | (define (restic-backup-activation config) | ||
| 291 | #~(for-each | ||
| 292 | (lambda (log-file) | ||
| 293 | (mkdir-p (dirname log-file))) | ||
| 294 | (list #$@(map restic-job-log-file | ||
| 295 | (restic-backup-configuration-jobs config))))) | ||
| 296 | |||
| 215 | (define restic-backup-service-type | 297 | (define restic-backup-service-type |
| 216 | (service-type (name 'restic-backup) | 298 | (service-type (name 'restic-backup) |
| 217 | (extensions | 299 | (extensions |
| 218 | (list | 300 | (list |
| 301 | (service-extension activation-service-type | ||
| 302 | restic-backup-activation) | ||
| 219 | (service-extension profile-service-type | 303 | (service-extension profile-service-type |
| 220 | restic-backup-service-profile) | 304 | restic-backup-service-profile) |
| 221 | (service-extension mcron-service-type | 305 | (service-extension shepherd-root-service-type |
| 222 | (lambda (config) | 306 | (lambda (config) |
| 223 | (map restic-backup-job->mcron-job | 307 | (map restic-backup-job->shepherd-service |
| 224 | (restic-backup-configuration-jobs | 308 | (restic-backup-configuration-jobs |
| 225 | config)))))) | 309 | config)))))) |
| 226 | (compose concatenate) | 310 | (compose concatenate) |
| @@ -232,5 +316,5 @@ without waiting for the scheduled job to run.") | |||
| 232 | jobs))))) | 316 | jobs))))) |
| 233 | (default-value (restic-backup-configuration)) | 317 | (default-value (restic-backup-configuration)) |
| 234 | (description | 318 | (description |
| 235 | "This service configures @code{mcron} jobs for running backups | 319 | "This service configures Shepherd timers for running backups |
| 236 | with @code{restic}."))) | 320 | with restic."))) |
