2023-11-20 14:09:10 +01:00
# Suspend:
# sudo systemctl suspend
# Suspend for 60 seconds:
# sudo rtcwake -m mem -s 60
2023-11-20 19:12:54 +01:00
# View service log:
# journalctl -u daily-backup-and-suspend
# Print unit file:
# cat "$(systemctl show -P FragmentPath daily-backup-and-suspend.service)"
2023-11-20 14:09:10 +01:00
2023-11-05 17:21:29 +01:00
{ config , pkgs , . . . }:
2023-11-15 13:54:37 +01:00
let
backup-source = " r o o t N a s " ;
# The "stay-awake" file is located at `${backup-source}:${stay-awake-file}`.
2024-01-02 11:42:22 +01:00
# Example: ssh rootNas 'touch yodaHedgehog.stay-awake'
2023-11-15 13:54:37 +01:00
stay-awake-file = " ${ config . networking . hostName } . s t a y - a w a k e " ;
in
2023-11-05 17:21:29 +01:00
{
2023-11-15 13:54:37 +01:00
assertions = [ {
assertion = config . services . openssh . enable ;
message = " s y s t e m d s e r v i c e d a i l y - b a c k u p - a n d - s u s p e n d r e q u i r e s S S H . " ;
} {
assertion = config . services . journalwatch . enable ;
message = " s y s t e m d s e r v i c e d a i l y - b a c k u p - a n d - s u s p e n d r e q u i r e s j o u r n a l w a t c h . " ;
} ] ;
2023-11-05 17:21:29 +01:00
2023-11-15 13:54:37 +01:00
systemd . timers . " d a i l y - b a c k u p - a n d - s u s p e n d " = {
2023-11-05 17:21:29 +01:00
wantedBy = [ " m u l t i - u s e r . t a r g e t " ] ;
timerConfig = {
OnCalendar = [
# Daily
2023-11-22 14:32:35 +01:00
" * - * - * 0 0 : 0 5 : 0 0 "
2023-11-05 17:21:29 +01:00
] ;
WakeSystem = true ;
2023-11-15 13:54:37 +01:00
} ;
} ;
systemd . services . " d a i l y - b a c k u p - a n d - s u s p e n d " = {
2023-11-21 13:01:45 +01:00
after = [ " n e t w o r k - o n l i n e . t a r g e t " ] ;
2023-11-15 13:54:37 +01:00
# Packages required for this script.
# For `ssh` and `journalwatch`, there are assertions above.
path = with pkgs ; [
2023-11-19 14:12:18 +01:00
# Provides `ssh`
openssh
2024-02-23 19:12:30 +01:00
# Provides `sync`, `readlink` (with support for parameter `-e`, required by `btrbk`)
2023-11-20 20:30:55 +01:00
coreutils
# Provides `awk`, `grep`, `sleep`, `printf`, `echo`, 'sendmail', `readlink` (without support for parameter `-e`)
2023-11-20 19:12:54 +01:00
busybox
# Provides `smtpctl`
opensmtpd
2023-11-20 20:30:55 +01:00
# Provides `btrbk`
btrbk
# Provides `sudo` required by `btrbk`.
# Alternatively we could configure `btrbk` to use the "btrfs-progs" instead of the "btrfs-progs-sudo" backend. But the `btrbk` NixOS module has no option for this.
sudo
2023-11-15 13:54:37 +01:00
] ;
# Script to execute as main process.
2023-11-05 17:21:29 +01:00
script = ''
set - eu - o pipefail
2023-11-20 20:30:55 +01:00
#printf '%s\n' 'Starting backup script.'
2023-11-15 13:54:37 +01:00
2023-11-22 14:32:35 +01:00
# Wait until ${backup-source} is reachable.
#
# This test is necessary because of the following:
# If the system wakes up at 00:05, it is not directly connected to the Internet.
# The config option `after = [ "network-online.target" ];` does not help in this regard.
# Thus, `btrbk` might fail with the following error while connecting to ${backup-source}:
# ssh: Could not resolve hostname p1st.de: Name or service not known
#
while : ; do
result = " $ ( s s h ${ backup-source } ' e c h o ${ backup-source } ' ) " && e = 0 || e = $ ?
if [ " ' ' ${ e } " = 0 ] && [ " ' ' ${ result } " = ' $ { backup-source } ' ] ; then
break
fi
printf ' % s \ n' ' Delaying backup due to SSH connectivity problems . '
sleep 1 0 s
done
2023-11-20 20:30:55 +01:00
# Pull BTRFS snapshots from $ { backup-source } .
btrbk - c /etc/btrbk/remote-backup-ssd.conf run
btrbk - c /etc/btrbk/remote-backup-hdd.conf run
2023-11-15 13:54:37 +01:00
# Don't suspend as long as `${backup-source}:${stay-awake-file}` exists.
while : ; do
2023-11-20 19:12:54 +01:00
result = " $ ( s s h ${ backup-source } ' l s ${ stay-awake-file } 2 > & 1 ' ) " || :
case " ' ' ${ result } " in
2023-11-23 12:44:23 +01:00
* ' No such file or directory' )
2023-11-15 13:54:37 +01:00
break
; ;
2023-11-23 12:44:23 +01:00
' $ { stay-awake-file } ' )
2023-11-15 13:54:37 +01:00
printf ' % s \ n' ' Delaying suspend due to $ { stay-awake-file } file . '
; ;
* )
printf ' % s \ n' ' Delaying suspend due to SSH connectivity problems . '
; ;
esac
2023-11-22 14:32:35 +01:00
sleep 6 0 s
2023-11-15 13:54:37 +01:00
done
# Wait until no BTRFS scrub service is running.
2023-11-20 19:12:54 +01:00
while : ; do
running_services = " $ ( s y s t e m c t l l i s t - u n i t s - - t y p e = s e r v i c e - - p l a i n - - q u i e t | a w k ' { p r i n t $ 1 } ' ) "
if ! printf ' % s' " ' ' ${ running_services } " | grep ' ^ btrfs-scrub' ; then
break ;
fi
2023-11-15 13:54:37 +01:00
printf ' % s \ n' ' Delaying suspend due to running BTRFS scrub service . '
sleep 6 0 s
done
# Send filtered journal by email.
systemctl start journalwatch . service || :
2023-11-20 19:12:54 +01:00
# Send notification by email.
printf ' % s \ n \ n % s' ' Subject : $ { config . networking . hostName } ' ' Finished backup . ' | sendmail - f langbein @ mail . de daniel @ systemli . org
# Let sendmail send emails.
#while :; do
# # TODO: Plain usage of `smtpctl` gives the error:
# # smtpctl: this program must be setgid smtpq
# queue="$(smtpctl show queue)"
# if [ "''${queue}" = "" ]; then
# break
# fi
# printf '%s\n' 'Delaying suspend due to non-empty smtpd email queue.'
# sleep 1s
#done
2023-11-15 13:54:37 +01:00
sleep 1 5 s
2023-11-20 20:30:55 +01:00
#printf '%s\n' 'Finished backup script.'
2024-02-23 19:12:30 +01:00
# Sync changed files to disk to reduce risk of file corruption in case of power loss.
sync
2023-11-15 13:54:37 +01:00
# Suspend to save power.
2023-11-20 19:12:54 +01:00
systemctl suspend
2023-11-05 17:21:29 +01:00
'' ;
} ;
}