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>
This commit is contained in:
Giacomo Leidi 2025-01-19 23:04:00 +01:00 committed by Ludovic Courtès
parent 44d12f9663
commit 35c6ae6e58
No known key found for this signature in database
GPG key ID: 090B11993D9AEBB5
2 changed files with 131 additions and 30 deletions

View file

@ -111,7 +111,7 @@ Copyright @copyright{} 2022 (@*
Copyright @copyright{} 2022 John Kehayias@* Copyright @copyright{} 2022 John Kehayias@*
Copyright @copyright{} 20222023 Bruno Victal@* Copyright @copyright{} 20222023 Bruno Victal@*
Copyright @copyright{} 2022 Ivan Vilata-i-Balaguer@* Copyright @copyright{} 2022 Ivan Vilata-i-Balaguer@*
Copyright @copyright{} 2023-2024 Giacomo Leidi@* Copyright @copyright{} 2023-2025 Giacomo Leidi@*
Copyright @copyright{} 2022 Antero Mejr@* Copyright @copyright{} 2022 Antero Mejr@*
Copyright @copyright{} 2023 Karl Hallsby@* Copyright @copyright{} 2023 Karl Hallsby@*
Copyright @copyright{} 2023 Nathaniel Nicandro@* Copyright @copyright{} 2023 Nathaniel Nicandro@*
@ -42710,19 +42710,16 @@ following configuration:
"/etc/guix/signing-key.sec")))))))))) "/etc/guix/signing-key.sec"))))))))))
@end lisp @end lisp
Each @code{restic-backup-job} translates to an mcron job which sets the Each @code{restic-backup-job} translates to a Shepherd timer which sets the
@env{RESTIC_PASSWORD} environment variable by reading the first line of @env{RESTIC_PASSWORD} environment variable by reading the first line of
@code{password-file} and runs @command{restic backup}, creating backups @code{password-file} and runs @command{restic backup}, creating backups
using rclone of all the files listed in the @code{files} field. using rclone of all the files listed in the @code{files} field.
The @code{restic-backup-service-type} installs as well @code{restic-guix} The @code{restic-backup-service-type} provides the ability to instantaneously
to the system profile, a @code{restic} utility wrapper that allows for easier trigger a backup with the @code{trigger} Shepherd action:
interaction with the Guix configured backup jobs. For example the following
could be used to instantaneusly trigger a backup for the above shown
configuration, without waiting for the scheduled job:
@example @example
restic-guix backup remote-ftp sudo herd trigger remote-ftp
@end example @end example
@c %start of fragment @c %start of fragment
@ -42753,6 +42750,23 @@ The restic package to be used for the current job.
@item @code{user} (default: @code{"root"}) (type: string) @item @code{user} (default: @code{"root"}) (type: string)
The user used for running the current job. The user used for running the current job.
@item @code{group} (default: @code{"root"}) (type: string)
The group used for running the current job.
@item @code{log-file} (type: maybe-string)
The file system path to the log file for this job. By default the file will
have be @file{/var/log/restic-backup/@var{job-name}.log}, where @var{job-name} is the
name defined in the @code{name} field.
@item @code{max-duration} (type: maybe-number)
The maximum duration in seconds that a job may last. Past
@code{max-duration} seconds, the job is forcefully terminated.
@item @code{wait-for-termination?} (default: @code{#f}) (type: boolean)
Wait until the job has finished before considering executing it again;
otherwise, perform it strictly on every occurrence of event, at the risk of
having multiple instances running concurrently.
@item @code{repository} (type: string) @item @code{repository} (type: string)
The restic repository target of this job. The restic repository target of this job.
@ -42765,9 +42779,12 @@ that will be used to set the @env{RESTIC_PASSWORD} environment variable
for the current job. for the current job.
@item @code{schedule} (type: gexp-or-string) @item @code{schedule} (type: gexp-or-string)
A string or a gexp that will be passed as time specification in the A string or a gexp representing the frequency of the backup. Gexp must
mcron job specification (@pxref{Syntax, mcron job specifications,, evaluate to @code{calendar-event} records or to strings. Strings must contain
mcron,GNU@tie{}mcron}). Vixie cron date lines.
@item @code{requirement} (default: @code{'()}) (type: list-of-symbols)
The list of Shepherd services that this backup job depends upon.
@item @code{files} (default: @code{'()}) (type: list-of-lowerables) @item @code{files} (default: @code{'()}) (type: list-of-lowerables)
The list of files or directories to be backed up. It must be a list of The list of files or directories to be backed up. It must be a list of

View file

@ -1,5 +1,5 @@
;;; GNU Guix --- Functional package management for GNU ;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2024 Giacomo Leidi <goodoldpaul@autistici.org> ;;; Copyright © 2024, 2025 Giacomo Leidi <goodoldpaul@autistici.org>
;;; ;;;
;;; This file is part of GNU Guix. ;;; This file is part of GNU Guix.
;;; ;;;
@ -18,9 +18,10 @@
(define-module (gnu services backup) (define-module (gnu services backup)
#:use-module (gnu packages backup) #:use-module (gnu packages backup)
#:use-module (gnu packages bash)
#:use-module (gnu services) #:use-module (gnu services)
#:use-module (gnu services configuration) #:use-module (gnu services configuration)
#:use-module (gnu services mcron) #:use-module (gnu services shepherd)
#:use-module (guix build-system copy) #:use-module (guix build-system copy)
#:use-module (guix gexp) #:use-module (guix gexp)
#:use-module ((guix licenses) #:use-module ((guix licenses)
@ -33,11 +34,16 @@
restic-backup-job-fields restic-backup-job-fields
restic-backup-job-restic restic-backup-job-restic
restic-backup-job-user restic-backup-job-user
restic-backup-job-group
restic-backup-job-log-file
restic-backup-job-max-duration
restic-backup-job-wait-for-termination?
restic-backup-job-name restic-backup-job-name
restic-backup-job-repository restic-backup-job-repository
restic-backup-job-password-file restic-backup-job-password-file
restic-backup-job-schedule restic-backup-job-schedule
restic-backup-job-files restic-backup-job-files
restic-backup-job-requirement
restic-backup-job-verbose? restic-backup-job-verbose?
restic-backup-job-extra-flags restic-backup-job-extra-flags
@ -64,6 +70,12 @@
(define list-of-lowerables? (define list-of-lowerables?
(list-of lowerable?)) (list-of lowerable?))
(define list-of-symbols?
(list-of symbol?))
(define-maybe/no-serialization string)
(define-maybe/no-serialization number)
(define-configuration/no-serialization restic-backup-job (define-configuration/no-serialization restic-backup-job
(restic (restic
(package restic) (package restic)
@ -71,6 +83,23 @@
(user (user
(string "root") (string "root")
"The user used for running the current job.") "The user used for running the current job.")
(group
(string "root")
"The group used for running the current job.")
(log-file
(maybe-string)
"The file system path to the log file for this job. By default the file will
have be @file{/var/log/restic-backup/@var{job-name}.log}, where @var{job-name} is the
name defined in the @code{name} field.")
(max-duration
(maybe-number)
"The maximum duration in seconds that a job may last. Past
@code{max-duration} seconds, the job is forcefully terminated.")
(wait-for-termination?
(boolean #f)
"Wait until the job has finished before considering executing it again;
otherwise, perform it strictly on every occurrence of event, at the risk of
having multiple instances running concurrently.")
(name (name
(string) (string)
"A string denoting a name for this job.") "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
current job.") current job.")
(schedule (schedule
(gexp-or-string) (gexp-or-string)
"A string or a gexp that will be passed as time specification in the mcron "A string or a gexp representing the frequency of the backup. Gexp must
job specification (@pxref{Syntax, mcron job specifications,, mcron, evaluate to @code{calendar-event} records or to strings. Strings must contain
GNU@tie{}mcron}).") Vixie cron date lines.")
(requirement
(list-of-symbols '())
"The list of Shepherd services that this backup job depends upon.")
(files (files
(list-of-lowerables '()) (list-of-lowerables '())
"The list of files or directories to be backed up. It must be a list of "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."))
(main (command-line))))) (main (command-line)))))
(define (restic-backup-job->mcron-job config) (define (restic-job-log-file job)
(let ((user (let ((name (restic-backup-job-name job))
(restic-backup-job-user config)) (log-file (restic-backup-job-log-file job)))
(schedule (if (maybe-value-set? log-file)
(restic-backup-job-schedule config)) log-file
(name (string-append "/var/log/restic-backup/" name ".log"))))
(restic-backup-job-name config)))
#~(job #$schedule (define (restic-backup-job->shepherd-service config)
#$(string-append "restic-guix backup " name) (let ((schedule (restic-backup-job-schedule config))
#:user #$user))) (name (restic-backup-job-name config))
(user (restic-backup-job-user config))
(group (restic-backup-job-group config))
(max-duration (restic-backup-job-max-duration config))
(wait-for-termination? (restic-backup-job-wait-for-termination? config))
(log-file (restic-job-log-file config))
(requirement (restic-backup-job-requirement config)))
(shepherd-service (provision `(,(string->symbol name)))
(requirement
`(user-processes file-systems ,@requirement))
(documentation
"Run @code{restic} backed backups on a regular basis.")
(modules '((shepherd service timer)))
(start
#~(make-timer-constructor
(if (string? #$schedule)
(cron-string->calendar-event #$schedule)
#$schedule)
(command
(list
;; We go through bash, instead of executing
;; restic-guix directly, because the login shell
;; gives us the correct user environment that some
;; backends require, such as rclone.
(string-append #+bash-minimal "/bin/bash")
"-l" "-c"
(string-append "restic-guix backup " #$name))
#:user #$user
#:group #$group
#:environment-variables
(list
(string-append
"HOME=" (passwd:dir (getpwnam #$user)))))
#:log-file #$log-file
#:wait-for-termination? #$wait-for-termination?
#:max-duration #$(and (maybe-value-set? max-duration)
max-duration)))
(stop
#~(make-timer-destructor))
(actions (list (shepherd-action
(name 'trigger)
(documentation "Manually trigger a backup,
without waiting for the scheduled time.")
(procedure #~trigger-timer)))))))
(define (restic-guix-wrapper-package jobs) (define (restic-guix-wrapper-package jobs)
(package (package
@ -212,15 +287,24 @@ without waiting for the scheduled job to run.")
(restic-guix-wrapper-package jobs)) (restic-guix-wrapper-package jobs))
'()))) '())))
(define (restic-backup-activation config)
#~(for-each
(lambda (log-file)
(mkdir-p (dirname log-file)))
(list #$@(map restic-job-log-file
(restic-backup-configuration-jobs config)))))
(define restic-backup-service-type (define restic-backup-service-type
(service-type (name 'restic-backup) (service-type (name 'restic-backup)
(extensions (extensions
(list (list
(service-extension activation-service-type
restic-backup-activation)
(service-extension profile-service-type (service-extension profile-service-type
restic-backup-service-profile) restic-backup-service-profile)
(service-extension mcron-service-type (service-extension shepherd-root-service-type
(lambda (config) (lambda (config)
(map restic-backup-job->mcron-job (map restic-backup-job->shepherd-service
(restic-backup-configuration-jobs (restic-backup-configuration-jobs
config)))))) config))))))
(compose concatenate) (compose concatenate)
@ -232,5 +316,5 @@ without waiting for the scheduled job to run.")
jobs))))) jobs)))))
(default-value (restic-backup-configuration)) (default-value (restic-backup-configuration))
(description (description
"This service configures @code{mcron} jobs for running backups "This service configures Shepherd timers for running backups
with @code{restic}."))) with restic.")))