diff options
| author | Giacomo Leidi <goodoldpaul@autistici.org> | 2025-05-17 17:09:54 +0200 |
|---|---|---|
| committer | Ludovic Courtès <ludo@gnu.org> | 2025-05-18 22:55:53 +0200 |
| commit | 1220d1a84eaaa8a60a457fa378cd986ec2269c08 (patch) | |
| tree | 83fd23a403c792646b7afc0a2a9f380cd1b986c5 /gnu/services | |
| parent | 86022e994e5fcb3918f2d3d2f6f89b24c5562910 (diff) | |
home: Add home-restic-backup service.
* gnu/services/backup.scm: Drop mcron obsolete export.
(restic-backup-job-program): Generalize to restic-program.
(lower-restic-backup-job): New procedure implementing a standard way to
lower restic-backup-job records into lists.
(restic-program): Implement general way to run restic commands, for
example to initialize repositories.
(restic-backup-configuration): Reimplement
with (guix records).
(restic-backup-job-{logfile,command,requirement,modules}): Add new
procedures and add support for Guix Home environments.
(restic-backup-job->shepherd-service): Add support for Guix Home
environments.
(restic-backup-service-activation): Drop procedure as now the Shepherd
takes care of creating timers log file directories.
(restic-backup-service-type): Drop profile and activation services extensions.
* gnu/home/services/backup.scm: New file.
* gnu/local.mk: Add this.
* doc/guix.texi: Document this.
Change-Id: Ied1c0a5756b715fba176a0e42ea154246089e6be
Signed-off-by: Ludovic Courtès <ludo@gnu.org>
Diffstat (limited to 'gnu/services')
| -rw-r--r-- | gnu/services/backup.scm | 187 |
1 files changed, 125 insertions, 62 deletions
diff --git a/gnu/services/backup.scm b/gnu/services/backup.scm index 6e066bd3d66..8fdf9ce902f 100644 --- a/gnu/services/backup.scm +++ b/gnu/services/backup.scm | |||
| @@ -28,6 +28,7 @@ | |||
| 28 | #:prefix license:) | 28 | #:prefix license:) |
| 29 | #:use-module (guix modules) | 29 | #:use-module (guix modules) |
| 30 | #:use-module (guix packages) | 30 | #:use-module (guix packages) |
| 31 | #:use-module (guix records) | ||
| 31 | #:use-module (srfi srfi-1) | 32 | #:use-module (srfi srfi-1) |
| 32 | #:export (restic-backup-job | 33 | #:export (restic-backup-job |
| 33 | restic-backup-job? | 34 | restic-backup-job? |
| @@ -47,16 +48,21 @@ | |||
| 47 | restic-backup-job-verbose? | 48 | restic-backup-job-verbose? |
| 48 | restic-backup-job-extra-flags | 49 | restic-backup-job-extra-flags |
| 49 | 50 | ||
| 51 | lower-restic-backup-job | ||
| 52 | |||
| 50 | restic-backup-configuration | 53 | restic-backup-configuration |
| 51 | restic-backup-configuration? | 54 | restic-backup-configuration? |
| 52 | restic-backup-configuration-fields | ||
| 53 | restic-backup-configuration-jobs | 55 | restic-backup-configuration-jobs |
| 54 | 56 | ||
| 55 | restic-backup-job-program | 57 | restic-backup-job-program |
| 56 | restic-backup-job->mcron-job | 58 | restic-backup-job->shepherd-service |
| 57 | restic-guix | 59 | restic-guix |
| 58 | restic-guix-wrapper-package | 60 | restic-guix-wrapper-package |
| 59 | restic-backup-service-profile | 61 | restic-backup-service-profile |
| 62 | restic-program | ||
| 63 | restic-job-log-file | ||
| 64 | restic-backup-job-command | ||
| 65 | restic-backup-job-modules | ||
| 60 | restic-backup-service-type)) | 66 | restic-backup-service-type)) |
| 61 | 67 | ||
| 62 | (define (gexp-or-string? value) | 68 | (define (gexp-or-string? value) |
| @@ -75,6 +81,8 @@ | |||
| 75 | 81 | ||
| 76 | (define-maybe/no-serialization string) | 82 | (define-maybe/no-serialization string) |
| 77 | (define-maybe/no-serialization number) | 83 | (define-maybe/no-serialization number) |
| 84 | (define-maybe/no-serialization symbol) | ||
| 85 | (define-maybe/no-serialization list-of-symbols) | ||
| 78 | 86 | ||
| 79 | (define-configuration/no-serialization restic-backup-job | 87 | (define-configuration/no-serialization restic-backup-job |
| 80 | (restic | 88 | (restic |
| @@ -90,7 +98,8 @@ | |||
| 90 | (maybe-string) | 98 | (maybe-string) |
| 91 | "The file system path to the log file for this job. By default the file will | 99 | "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 | 100 | 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.") | 101 | name defined in the @code{name} field. For Guix Home services it defaults to |
| 102 | @file{$XDG_STATE_HOME/shepherd/restic-backup/@var{job-name}.log}.") | ||
| 94 | (max-duration | 103 | (max-duration |
| 95 | (maybe-number) | 104 | (maybe-number) |
| 96 | "The maximum duration in seconds that a job may last. Past | 105 | "The maximum duration in seconds that a job may last. Past |
| @@ -117,8 +126,10 @@ current job.") | |||
| 117 | evaluate to @code{calendar-event} records or to strings. Strings must contain | 126 | evaluate to @code{calendar-event} records or to strings. Strings must contain |
| 118 | Vixie cron date lines.") | 127 | Vixie cron date lines.") |
| 119 | (requirement | 128 | (requirement |
| 120 | (list-of-symbols '()) | 129 | (maybe-list-of-symbols) |
| 121 | "The list of Shepherd services that this backup job depends upon.") | 130 | "The list of Shepherd services that this backup job depends upon. When unset it |
| 131 | defaults to @code{'()}, for Guix Home. Otherwise to | ||
| 132 | @code{'(user-processes file-systems)}.") | ||
| 122 | (files | 133 | (files |
| 123 | (list-of-lowerables '()) | 134 | (list-of-lowerables '()) |
| 124 | "The list of files or directories to be backed up. It must be a list of | 135 | "The list of files or directories to be backed up. It must be a list of |
| @@ -131,15 +142,20 @@ values that can be lowered to strings.") | |||
| 131 | "A list of values that are lowered to strings. These will be passed as | 142 | "A list of values that are lowered to strings. These will be passed as |
| 132 | command-line arguments to the current job @command{restic backup} invocation.")) | 143 | command-line arguments to the current job @command{restic backup} invocation.")) |
| 133 | 144 | ||
| 134 | (define list-of-restic-backup-jobs? | 145 | ;; (for-home (restic-backup-configuration ...)) is not able to replace for-home? with #t, |
| 135 | (list-of restic-backup-job?)) | 146 | ;; pk prints #f. Once for-home will be able to work with (gnu services configuration) the |
| 147 | ;; record can be migrated back to define-configuration. | ||
| 148 | (define-record-type* <restic-backup-configuration> | ||
| 149 | restic-backup-configuration | ||
| 150 | make-restic-backup-configuration | ||
| 151 | restic-backup-configuration? | ||
| 152 | this-restic-backup-configuration | ||
| 136 | 153 | ||
| 137 | (define-configuration/no-serialization restic-backup-configuration | 154 | (jobs restic-backup-configuration-jobs (default '())) ; list of restic-backup-job |
| 138 | (jobs | 155 | (home-service? restic-backup-configuration-home-service? |
| 139 | (list-of-restic-backup-jobs '()) | 156 | (default for-home?) (innate))) |
| 140 | "The list of backup jobs for the current system.")) | ||
| 141 | 157 | ||
| 142 | (define (restic-backup-job-program config) | 158 | (define (lower-restic-backup-job config) |
| 143 | (let ((restic | 159 | (let ((restic |
| 144 | (file-append (restic-backup-job-restic config) "/bin/restic")) | 160 | (file-append (restic-backup-job-restic config) "/bin/restic")) |
| 145 | (repository | 161 | (repository |
| @@ -150,22 +166,42 @@ command-line arguments to the current job @command{restic backup} invocation.")) | |||
| 150 | (restic-backup-job-files config)) | 166 | (restic-backup-job-files config)) |
| 151 | (extra-flags | 167 | (extra-flags |
| 152 | (restic-backup-job-extra-flags config)) | 168 | (restic-backup-job-extra-flags config)) |
| 153 | (verbose | 169 | (verbose? |
| 154 | (if (restic-backup-job-verbose? config) | 170 | (if (restic-backup-job-verbose? config) |
| 155 | '("--verbose") | 171 | '("--verbose") |
| 156 | '()))) | 172 | '()))) |
| 157 | (program-file | 173 | #~(list (list #$@files) #$restic #$repository #$password-file |
| 158 | "restic-backup-job.scm" | 174 | (list #$@verbose?) (list #$@extra-flags)))) |
| 159 | #~(begin | 175 | |
| 160 | (use-modules (ice-9 popen) | 176 | (define restic-program |
| 161 | (ice-9 rdelim)) | 177 | #~(lambda (action action-args job-restic repository password-file verbose? extra-flags) |
| 162 | (setenv "RESTIC_PASSWORD" | 178 | (use-modules (ice-9 format)) |
| 163 | (with-input-from-file #$password-file read-line)) | 179 | ;; This can be extended later, i.e. to have a |
| 164 | 180 | ;; centrally defined restic package. | |
| 165 | (execlp #$restic #$restic #$@verbose | 181 | ;; See https://issues.guix.gnu.org/71639 |
| 166 | "-r" #$repository | 182 | (define restic job-restic) |
| 167 | #$@extra-flags | 183 | |
| 168 | "backup" #$@files))))) | 184 | (define command |
| 185 | `(,restic ,@verbose? | ||
| 186 | "-r" ,repository | ||
| 187 | ,@extra-flags | ||
| 188 | ,action ,@action-args)) | ||
| 189 | |||
| 190 | (setenv "RESTIC_PASSWORD_FILE" password-file) | ||
| 191 | |||
| 192 | (when (> (length verbose?) 0) | ||
| 193 | (format #t "Running~{ ~a~}~%" command)) | ||
| 194 | |||
| 195 | (apply execlp `(,restic ,@command)))) | ||
| 196 | |||
| 197 | (define (restic-backup-job-program config) | ||
| 198 | (program-file | ||
| 199 | "restic-backup" | ||
| 200 | #~(let ((restic-exec | ||
| 201 | #$restic-program) | ||
| 202 | (job #$(lower-restic-backup-job config))) | ||
| 203 | |||
| 204 | (apply restic-exec `("backup" ,@job))))) | ||
| 169 | 205 | ||
| 170 | (define (restic-guix jobs) | 206 | (define (restic-guix jobs) |
| 171 | (program-file | 207 | (program-file |
| @@ -207,55 +243,89 @@ command-line arguments to the current job @command{restic backup} invocation.")) | |||
| 207 | 243 | ||
| 208 | (main (command-line))))) | 244 | (main (command-line))))) |
| 209 | 245 | ||
| 210 | (define (restic-job-log-file job) | 246 | (define* (restic-job-log-file job #:key (home-service? #f)) |
| 211 | (let ((name (restic-backup-job-name job)) | 247 | (let ((name (restic-backup-job-name job)) |
| 212 | (log-file (restic-backup-job-log-file job))) | 248 | (log-file (restic-backup-job-log-file job))) |
| 213 | (if (maybe-value-set? log-file) | 249 | (if (maybe-value-set? log-file) |
| 214 | log-file | 250 | log-file |
| 215 | (string-append "/var/log/restic-backup/" name ".log")))) | 251 | (if home-service? |
| 252 | #~(begin | ||
| 253 | (use-modules (shepherd support)) | ||
| 254 | (string-append %user-log-dir "/restic-backup/" #$name ".log")) | ||
| 255 | (string-append "/var/log/restic-backup/" name ".log"))))) | ||
| 256 | |||
| 257 | (define* (restic-backup-job-command name files #:key (home-service? #f)) | ||
| 258 | (if home-service? | ||
| 259 | #~(list | ||
| 260 | "restic-guix" "backup" #$name) | ||
| 261 | ;; We go through bash, instead of executing | ||
| 262 | ;; restic-guix directly, because the login shell | ||
| 263 | ;; gives us the correct user environment that some | ||
| 264 | ;; backends require, such as rclone. | ||
| 265 | #~(list | ||
| 266 | (string-append #$bash-minimal "/bin/bash") | ||
| 267 | "-l" "-c" | ||
| 268 | (string-append "restic-guix backup " #$name)))) | ||
| 216 | 269 | ||
| 217 | (define (restic-backup-job->shepherd-service config) | 270 | (define* (restic-job-requirement config #:key (home-service? #f)) |
| 271 | (define maybe-requirement (restic-backup-job-requirement config)) | ||
| 272 | (if (maybe-value-set? maybe-requirement) | ||
| 273 | maybe-requirement | ||
| 274 | (if home-service? | ||
| 275 | '() | ||
| 276 | '(user-processes file-systems)))) | ||
| 277 | |||
| 278 | (define* (restic-backup-job-modules #:key (home-service? #f)) | ||
| 279 | `((shepherd service timer) | ||
| 280 | ,@(if home-service? | ||
| 281 | ;;for %user-log-dir | ||
| 282 | '((shepherd support)) | ||
| 283 | '()))) | ||
| 284 | |||
| 285 | (define* (restic-backup-job->shepherd-service config #:key (home-service? #f)) | ||
| 218 | (let ((schedule (restic-backup-job-schedule config)) | 286 | (let ((schedule (restic-backup-job-schedule config)) |
| 219 | (name (restic-backup-job-name config)) | 287 | (name (restic-backup-job-name config)) |
| 288 | (files (restic-backup-job-files config)) | ||
| 220 | (user (restic-backup-job-user config)) | 289 | (user (restic-backup-job-user config)) |
| 221 | (group (restic-backup-job-group config)) | 290 | (group (restic-backup-job-group config)) |
| 222 | (max-duration (restic-backup-job-max-duration config)) | 291 | (max-duration (restic-backup-job-max-duration config)) |
| 223 | (wait-for-termination? (restic-backup-job-wait-for-termination? config)) | 292 | (wait-for-termination? (restic-backup-job-wait-for-termination? config)) |
| 224 | (log-file (restic-job-log-file config)) | 293 | (log-file (restic-job-log-file |
| 225 | (requirement (restic-backup-job-requirement config))) | 294 | config #:home-service? home-service?)) |
| 295 | (requirement | ||
| 296 | (restic-job-requirement config #:home-service? home-service?))) | ||
| 226 | (shepherd-service (provision `(,(string->symbol name))) | 297 | (shepherd-service (provision `(,(string->symbol name))) |
| 227 | (requirement | 298 | (requirement requirement) |
| 228 | `(user-processes file-systems ,@requirement)) | ||
| 229 | (documentation | 299 | (documentation |
| 230 | "Run @code{restic} backed backups on a regular basis.") | 300 | "Run restic backed backups on a regular basis.") |
| 231 | (modules '((shepherd service timer))) | 301 | (modules (restic-backup-job-modules |
| 302 | #:home-service? home-service?)) | ||
| 232 | (start | 303 | (start |
| 233 | #~(make-timer-constructor | 304 | #~(make-timer-constructor |
| 234 | (if (string? #$schedule) | 305 | (if (string? #$schedule) |
| 235 | (cron-string->calendar-event #$schedule) | 306 | (cron-string->calendar-event #$schedule) |
| 236 | #$schedule) | 307 | #$schedule) |
| 237 | (command | 308 | (command |
| 238 | (list | 309 | #$(restic-backup-job-command |
| 239 | ;; We go through bash, instead of executing | 310 | name files #:home-service? home-service?) |
| 240 | ;; restic-guix directly, because the login shell | 311 | #$@(if home-service? '() (list #:user user)) |
| 241 | ;; gives us the correct user environment that some | 312 | #$@(if home-service? '() (list #:group group)) |
| 242 | ;; backends require, such as rclone. | 313 | #$@(if home-service? '() |
| 243 | (string-append #+bash-minimal "/bin/bash") | 314 | (list |
| 244 | "-l" "-c" | 315 | #:environment-variables |
| 245 | (string-append "restic-guix backup " #$name)) | 316 | #~(list |
| 246 | #:user #$user | 317 | (string-append |
| 247 | #:group #$group | 318 | "HOME=" (passwd:dir (getpwnam #$user))))))) |
| 248 | #:environment-variables | ||
| 249 | (list | ||
| 250 | (string-append | ||
| 251 | "HOME=" (passwd:dir (getpwnam #$user))))) | ||
| 252 | #:log-file #$log-file | 319 | #:log-file #$log-file |
| 253 | #:wait-for-termination? #$wait-for-termination? | 320 | #:wait-for-termination? #$wait-for-termination? |
| 254 | #:max-duration #$(and (maybe-value-set? max-duration) | 321 | #:max-duration #$(and (maybe-value-set? max-duration) |
| 255 | max-duration))) | 322 | max-duration))) |
| 256 | (stop | 323 | (stop |
| 257 | #~(make-timer-destructor)) | 324 | #~(make-timer-destructor)) |
| 258 | (actions (list shepherd-trigger-action))))) | 325 | (actions (list (shepherd-action |
| 326 | (inherit shepherd-trigger-action) | ||
| 327 | (documentation "Manually trigger a backup, | ||
| 328 | without waiting for the scheduled time."))))))) | ||
| 259 | 329 | ||
| 260 | (define (restic-guix-wrapper-package jobs) | 330 | (define (restic-guix-wrapper-package jobs) |
| 261 | (package | 331 | (package |
| @@ -283,26 +353,19 @@ without waiting for the scheduled job to run.") | |||
| 283 | (restic-guix-wrapper-package jobs)) | 353 | (restic-guix-wrapper-package jobs)) |
| 284 | '()))) | 354 | '()))) |
| 285 | 355 | ||
| 286 | (define (restic-backup-activation config) | ||
| 287 | #~(for-each | ||
| 288 | (lambda (log-file) | ||
| 289 | (mkdir-p (dirname log-file))) | ||
| 290 | (list #$@(map restic-job-log-file | ||
| 291 | (restic-backup-configuration-jobs config))))) | ||
| 292 | |||
| 293 | (define restic-backup-service-type | 356 | (define restic-backup-service-type |
| 294 | (service-type (name 'restic-backup) | 357 | (service-type (name 'restic-backup) |
| 295 | (extensions | 358 | (extensions |
| 296 | (list | 359 | (list |
| 297 | (service-extension activation-service-type | ||
| 298 | restic-backup-activation) | ||
| 299 | (service-extension profile-service-type | 360 | (service-extension profile-service-type |
| 300 | restic-backup-service-profile) | 361 | restic-backup-service-profile) |
| 301 | (service-extension shepherd-root-service-type | 362 | (service-extension shepherd-root-service-type |
| 302 | (lambda (config) | 363 | (match-record-lambda <restic-backup-configuration> |
| 303 | (map restic-backup-job->shepherd-service | 364 | (jobs home-service?) |
| 304 | (restic-backup-configuration-jobs | 365 | (map (lambda (job) |
| 305 | config)))))) | 366 | (restic-backup-job->shepherd-service |
| 367 | job #:home-service? home-service?)) | ||
| 368 | jobs))))) | ||
| 306 | (compose concatenate) | 369 | (compose concatenate) |
| 307 | (extend | 370 | (extend |
| 308 | (lambda (config jobs) | 371 | (lambda (config jobs) |
