2020-04-24 23:36:52 +00:00
{ config , lib , pkgs , utils , . . . }:
#
# TODO: zfs tunables
with utils ;
with lib ;
let
cfgZfs = config . boot . zfs ;
2021-08-25 08:27:29 +00:00
cfgExpandOnBoot = config . services . zfs . expandOnBoot ;
2020-04-24 23:36:52 +00:00
cfgSnapshots = config . services . zfs . autoSnapshot ;
cfgSnapFlags = cfgSnapshots . flags ;
cfgScrub = config . services . zfs . autoScrub ;
cfgTrim = config . services . zfs . trim ;
cfgZED = config . services . zfs . zed ;
inInitrd = any ( fs : fs == " z f s " ) config . boot . initrd . supportedFilesystems ;
inSystem = any ( fs : fs == " z f s " ) config . boot . supportedFilesystems ;
autosnapPkg = pkgs . zfstools . override {
2021-02-05 17:12:51 +00:00
zfs = cfgZfs . package ;
2020-04-24 23:36:52 +00:00
} ;
zfsAutoSnap = " ${ autosnapPkg } / b i n / z f s - a u t o - s n a p s h o t " ;
datasetToPool = x : elemAt ( splitString " / " x ) 0 ;
fsToPool = fs : datasetToPool fs . device ;
zfsFilesystems = filter ( x : x . fsType == " z f s " ) config . system . build . fileSystems ;
allPools = unique ( ( map fsToPool zfsFilesystems ) ++ cfgZfs . extraPools ) ;
rootPools = unique ( map fsToPool ( filter fsNeededForBoot zfsFilesystems ) ) ;
dataPools = unique ( filter ( pool : ! ( elem pool rootPools ) ) allPools ) ;
snapshotNames = [ " f r e q u e n t " " h o u r l y " " d a i l y " " w e e k l y " " m o n t h l y " ] ;
# When importing ZFS pools, there's one difficulty: These scripts may run
# before the backing devices (physical HDDs, etc.) of the pool have been
# scanned and initialized.
#
# An attempted import with all devices missing will just fail, and can be
# retried, but an import where e.g. two out of three disks in a three-way
# mirror are missing, will succeed. This is a problem: When the missing disks
# are later discovered, they won't be automatically set online, rendering the
# pool redundancy-less (and far slower) until such time as the system reboots.
#
# The solution is the below. poolReady checks the status of an un-imported
# pool, to see if *every* device is available -- in which case the pool will be
# in state ONLINE, as opposed to DEGRADED, FAULTED or MISSING.
#
# The import scripts then loop over this, waiting until the pool is ready or a
# sufficient amount of time has passed that we can assume it won't be. In the
# latter case it makes one last attempt at importing, allowing the system to
# (eventually) boot even with a degraded pool.
importLib = { zpoolCmd , awkCmd , cfgZfs }: ''
poolReady ( ) {
pool = " $ 1 "
state = " $ ( " $ { zpoolCmd } " i m p o r t 2 > / d e v / n u l l | " $ { awkCmd } " " /pool : $ pool / { found = 1 } ; /state : / { if ( found == 1 ) { print \ $ 2 ; exit } } ; END { if ( found == 0 ) { print \ " M I S S I N G \" } } " ) "
if [ [ " $ s t a t e " = " O N L I N E " ] ] ; then
return 0
else
echo " P o o l $ p o o l i n s t a t e $ s t a t e , w a i t i n g "
return 1
fi
}
poolImported ( ) {
pool = " $ 1 "
" ${ zpoolCmd } " list " $ p o o l " > /dev/null 2 > /dev/null
}
poolImport ( ) {
pool = " $ 1 "
" ${ zpoolCmd } " import - d " ${ cfgZfs . devNodes } " - N $ ZFS_FORCE " $ p o o l "
}
'' ;
zedConf = generators . toKeyValue {
mkKeyValue = generators . mkKeyValueDefault {
mkValueString = v :
if isInt v then toString v
else if isString v then " \" ${ v } \" "
else if true == v then " 1 "
else if false == v then " 0 "
else if isList v then " \" " + ( concatStringsSep " " v ) + " \" "
else err " t h i s v a l u e i s " ( toString v ) ;
} " = " ;
} cfgZED . settings ;
in
{
imports = [
( mkRemovedOptionModule [ " b o o t " " z f s " " e n a b l e L e g a c y C r y p t o " ] " T h e c o r r e s p o n d i n g p a c k a g e w a s r e m o v e d f r o m n i x p k g s . " )
] ;
###### interface
options = {
boot . zfs = {
2021-02-05 17:12:51 +00:00
package = mkOption {
readOnly = true ;
type = types . package ;
default = if config . boot . zfs . enableUnstable then pkgs . zfsUnstable else pkgs . zfs ;
2021-10-06 13:57:05 +00:00
defaultText = literalExpression " i f c o n f i g . b o o t . z f s . e n a b l e U n s t a b l e t h e n p k g s . z f s U n s t a b l e e l s e p k g s . z f s " ;
2021-02-05 17:12:51 +00:00
description = " C o n f i g u r e d Z F S u s e r l a n d t o o l s p a c k a g e . " ;
} ;
enabled = mkOption {
readOnly = true ;
type = types . bool ;
default = inInitrd || inSystem ;
description = " T r u e i f Z F S f i l e s y s t e m s u p p o r t i s e n a b l e d " ;
} ;
2020-04-24 23:36:52 +00:00
enableUnstable = mkOption {
type = types . bool ;
default = false ;
description = ''
Use the unstable zfs package . This might be an option , if the latest
kernel is not yet supported by a published release of ZFS . Enabling
this option will install a development version of ZFS on Linux . The
version will have already passed an extensive test suite , but it is
more likely to hit an undiscovered bug compared to running a released
version of ZFS on Linux .
'' ;
} ;
extraPools = mkOption {
type = types . listOf types . str ;
default = [ ] ;
example = [ " t a n k " " d a t a " ] ;
description = ''
Name or GUID of extra ZFS pools that you wish to import during boot .
Usually this is not necessary . Instead , you should set the mountpoint property
of ZFS filesystems to <literal> legacy < /literal > and add the ZFS filesystems to
NixOS's <option> fileSystems < /option > option , which makes NixOS automatically
import the associated pool .
However , in some cases ( e . g . if you have many filesystems ) it may be preferable
to exclusively use ZFS commands to manage filesystems . If so , since NixOS/systemd
will not be managing those filesystems , you will need to specify the ZFS pool here
so that NixOS automatically imports it on every boot .
'' ;
} ;
devNodes = mkOption {
type = types . path ;
default = " / d e v / d i s k / b y - i d " ;
description = ''
Name of directory from which to import ZFS devices .
This should be a path under /dev containing stable names for all devices needed , as
import may fail if device nodes are renamed concurrently with a device failing .
'' ;
} ;
forceImportRoot = mkOption {
type = types . bool ;
default = true ;
description = ''
Forcibly import the ZFS root pool ( s ) during early boot .
This is enabled by default for backwards compatibility purposes , but it is highly
recommended to disable this option , as it bypasses some of the safeguards ZFS uses
to protect your ZFS pools .
If you set this option to <literal> false < /literal > and NixOS subsequently fails to
boot because it cannot import the root pool , you should boot with the
<literal> zfs_force = 1 < /literal > option as a kernel parameter ( e . g . by manually
editing the kernel params in grub during boot ) . You should only need to do this
once .
'' ;
} ;
forceImportAll = mkOption {
type = types . bool ;
2020-11-12 09:05:59 +00:00
default = false ;
2020-04-24 23:36:52 +00:00
description = ''
Forcibly import all ZFS pool ( s ) .
If you set this option to <literal> false < /literal > and NixOS subsequently fails to
import your non-root ZFS pool ( s ) , you should manually import each pool with
" z p o o l i m p o r t - f & l t ; p o o l - n a m e & g t ; " , and then reboot . You should only need to do
this once .
'' ;
} ;
requestEncryptionCredentials = mkOption {
2020-08-20 17:08:02 +00:00
type = types . either types . bool ( types . listOf types . str ) ;
2020-04-24 23:36:52 +00:00
default = true ;
2020-08-20 17:08:02 +00:00
example = [ " t a n k " " d a t a " ] ;
2020-04-24 23:36:52 +00:00
description = ''
2020-08-20 17:08:02 +00:00
If true on import encryption keys or passwords for all encrypted datasets
are requested . To only decrypt selected datasets supply a list of dataset
names instead . For root pools the encryption key can be supplied via both
an interactive prompt ( keylocation = prompt ) and from a file ( keylocation = file:// ) .
2020-04-24 23:36:52 +00:00
'' ;
} ;
} ;
services . zfs . autoSnapshot = {
enable = mkOption {
default = false ;
type = types . bool ;
description = ''
Enable the ( OpenSolaris-compatible ) ZFS auto-snapshotting service .
Note that you must set the <literal> com.sun:auto-snapshot < /literal >
property to <literal> true < /literal > on all datasets which you wish
to auto-snapshot .
You can override a child dataset to use , or not use auto-snapshotting
by setting its flag with the given interval :
<literal> zfs set com.sun:auto-snapshot:weekly=false DATASET < /literal >
'' ;
} ;
flags = mkOption {
default = " - k - p " ;
example = " - k - p - - u t c " ;
type = types . str ;
description = ''
Flags to pass to the zfs-auto-snapshot command .
Run <literal> zfs-auto-snapshot < /literal > ( without any arguments ) to
see available flags .
If it's not too inconvenient for snapshots to have timestamps in UTC ,
it is suggested that you append <literal> - - utc < /literal > to the list
of default options ( see example ) .
Otherwise , snapshot names can cause name conflicts or apparent time
reversals due to daylight savings , timezone or other date/time changes .
'' ;
} ;
frequent = mkOption {
default = 4 ;
type = types . int ;
description = ''
Number of frequent ( 1 5 - minute ) auto-snapshots that you wish to keep .
'' ;
} ;
hourly = mkOption {
default = 24 ;
type = types . int ;
description = ''
Number of hourly auto-snapshots that you wish to keep .
'' ;
} ;
daily = mkOption {
default = 7 ;
type = types . int ;
description = ''
Number of daily auto-snapshots that you wish to keep .
'' ;
} ;
weekly = mkOption {
default = 4 ;
type = types . int ;
description = ''
Number of weekly auto-snapshots that you wish to keep .
'' ;
} ;
monthly = mkOption {
default = 12 ;
type = types . int ;
description = ''
Number of monthly auto-snapshots that you wish to keep .
'' ;
} ;
} ;
services . zfs . trim = {
enable = mkOption {
description = " W h e t h e r t o e n a b l e p e r i o d i c T R I M o n a l l Z F S p o o l s . " ;
default = true ;
example = false ;
type = types . bool ;
} ;
interval = mkOption {
default = " w e e k l y " ;
type = types . str ;
example = " d a i l y " ;
description = ''
How often we run trim . For most desktop and server systems
a sufficient trimming frequency is once a week .
The format is described in
<citerefentry> <refentrytitle> systemd . time < /refentrytitle >
<manvolnum> 7 < /manvolnum > < /citerefentry > .
'' ;
} ;
} ;
services . zfs . autoScrub = {
2021-04-05 15:23:46 +00:00
enable = mkEnableOption " p e r i o d i c s c r u b b i n g o f Z F S p o o l s " ;
2020-04-24 23:36:52 +00:00
interval = mkOption {
default = " S u n , 0 2 : 0 0 " ;
type = types . str ;
example = " d a i l y " ;
description = ''
Systemd calendar expression when to scrub ZFS pools . See
<citerefentry> <refentrytitle> systemd . time < /refentrytitle >
<manvolnum> 7 < /manvolnum > < /citerefentry > .
'' ;
} ;
pools = mkOption {
default = [ ] ;
type = types . listOf types . str ;
example = [ " t a n k " ] ;
description = ''
List of ZFS pools to periodically scrub . If empty , all pools
will be scrubbed .
'' ;
} ;
} ;
2021-08-25 08:27:29 +00:00
services . zfs . expandOnBoot = mkOption {
type = types . either ( types . enum [ " d i s a b l e d " " a l l " ] ) ( types . listOf types . str ) ;
default = " d i s a b l e d " ;
example = [ " t a n k " " d o z e r " ] ;
description = ''
After importing , expand each device in the specified pools .
Set the value to the plain string " a l l " to expand all pools on boot :
services . zfs . expandOnBoot = " a l l " ;
or set the value to a list of pools to expand the disks of specific pools :
services . zfs . expandOnBoot = [ " t a n k " " d o z e r " ] ;
'' ;
} ;
2021-02-13 14:23:35 +00:00
services . zfs . zed = {
enableMail = mkEnableOption " Z E D ' s a b i l i t y t o s e n d e m a i l s " // {
default = cfgZfs . package . enableMail ;
} ;
2020-04-24 23:36:52 +00:00
2021-02-13 14:23:35 +00:00
settings = mkOption {
type = with types ; attrsOf ( oneOf [ str int bool ( listOf str ) ] ) ;
2021-10-06 13:57:05 +00:00
example = literalExpression ''
2021-02-13 14:23:35 +00:00
{
ZED_DEBUG_LOG = " / t m p / z e d . d e b u g . l o g " ;
2020-04-24 23:36:52 +00:00
2021-02-13 14:23:35 +00:00
ZED_EMAIL_ADDR = [ " r o o t " ] ;
ZED_EMAIL_PROG = " m a i l " ;
ZED_EMAIL_OPTS = " - s ' @ S U B J E C T @ ' @ A D D R E S S @ " ;
2020-04-24 23:36:52 +00:00
2021-02-13 14:23:35 +00:00
ZED_NOTIFY_INTERVAL_SECS = 3600 ;
ZED_NOTIFY_VERBOSE = false ;
ZED_USE_ENCLOSURE_LEDS = true ;
ZED_SCRUB_AFTER_RESILVER = false ;
}
'' ;
description = ''
ZFS Event Daemon /etc/zfs/zed.d/zed.rc content
See
<citerefentry> <refentrytitle> zed < /refentrytitle > <manvolnum> 8 < /manvolnum > < /citerefentry >
for details on ZED and the scripts in /etc/zfs/zed.d to find the possible variables
'' ;
} ;
2020-04-24 23:36:52 +00:00
} ;
} ;
###### implementation
config = mkMerge [
2021-02-05 17:12:51 +00:00
( mkIf cfgZfs . enabled {
2020-04-24 23:36:52 +00:00
assertions = [
2021-02-13 14:23:35 +00:00
{
assertion = cfgZED . enableMail -> cfgZfs . package . enableMail ;
message = ''
To allow ZED to send emails , ZFS needs to be configured to enable
this . To do so , one must override the ` zfs ` package and set
` enableMail ` to true .
'' ;
}
2020-04-24 23:36:52 +00:00
{
assertion = config . networking . hostId != null ;
message = " Z F S r e q u i r e s n e t w o r k i n g . h o s t I d t o b e s e t " ;
}
{
assertion = ! cfgZfs . forceImportAll || cfgZfs . forceImportRoot ;
message = " I f y o u e n a b l e b o o t . z f s . f o r c e I m p o r t A l l , y o u m u s t a l s o e n a b l e b o o t . z f s . f o r c e I m p o r t R o o t " ;
}
] ;
boot = {
kernelModules = [ " z f s " ] ;
2021-02-05 17:12:51 +00:00
extraModulePackages = [
( if config . boot . zfs . enableUnstable then
config . boot . kernelPackages . zfsUnstable
else
config . boot . kernelPackages . zfs )
] ;
2020-04-24 23:36:52 +00:00
} ;
boot . initrd = mkIf inInitrd {
kernelModules = [ " z f s " ] ++ optional ( ! cfgZfs . enableUnstable ) " s p l " ;
extraUtilsCommands =
''
2021-02-05 17:12:51 +00:00
copy_bin_and_libs $ { cfgZfs . package } /sbin/zfs
copy_bin_and_libs $ { cfgZfs . package } /sbin/zdb
copy_bin_and_libs $ { cfgZfs . package } /sbin/zpool
2020-04-24 23:36:52 +00:00
'' ;
extraUtilsCommandsTest = mkIf inInitrd
''
$ out/bin/zfs - - help > /dev/null 2 > & 1
$ out/bin/zpool - - help > /dev/null 2 > & 1
'' ;
postDeviceCommands = concatStringsSep " \n " ( [ ''
ZFS_FORCE = " ${ optionalString cfgZfs . forceImportRoot " - f " } "
for o in $ ( cat /proc/cmdline ) ; do
case $ o in
zfs_force | zfs_force = 1 )
ZFS_FORCE = " - f "
; ;
esac
done
'' ] + + [ ( i m p o r t L i b {
# See comments at importLib definition.
zpoolCmd = " z p o o l " ;
awkCmd = " a w k " ;
inherit cfgZfs ;
} ) ] ++ ( map ( pool : ''
echo - n " i m p o r t i n g r o o t Z F S p o o l \" ${ pool } \" . . . "
# Loop across the import until it succeeds, because the devices needed may not be discovered yet.
if ! poolImported " ${ pool } " ; then
for trial in ` seq 1 60 ` ; do
poolReady " ${ pool } " > /dev/null && msg = " $ ( p o o l I m p o r t " $ { pool } " 2 > & 1 ) " && break
sleep 1
echo - n .
done
echo
if [ [ - n " $ m s g " ] ] ; then
echo " $ m s g " ;
fi
poolImported " ${ pool } " || poolImport " ${ pool } " # Try one last time, e.g. to import a degraded pool.
fi
2020-08-20 17:08:02 +00:00
$ { if isBool cfgZfs . requestEncryptionCredentials
then optionalString cfgZfs . requestEncryptionCredentials ''
zfs load-key - a
''
else concatMapStrings ( fs : ''
zfs load-key $ { fs }
'' ) c f g Z f s . r e q u e s t E n c r y p t i o n C r e d e n t i a l s }
2020-04-24 23:36:52 +00:00
'' ) r o o t P o o l s ) ) ;
} ;
2021-04-18 02:13:31 +00:00
# TODO FIXME See https://github.com/NixOS/nixpkgs/pull/99386#issuecomment-798813567. To not break people's bootloader and as probably not everybody would read release notes that thoroughly add inSystem.
boot . loader . grub = mkIf ( inInitrd || inSystem ) {
2020-04-24 23:36:52 +00:00
zfsSupport = true ;
} ;
services . zfs . zed . settings = {
2021-02-13 14:23:35 +00:00
ZED_EMAIL_PROG = mkIf cfgZED . enableMail ( mkDefault " ${ pkgs . mailutils } / b i n / m a i l " ) ;
2020-05-29 06:06:01 +00:00
PATH = lib . makeBinPath [
2021-02-05 17:12:51 +00:00
cfgZfs . package
2020-05-29 06:06:01 +00:00
pkgs . coreutils
pkgs . curl
pkgs . gawk
pkgs . gnugrep
pkgs . gnused
pkgs . nettools
2020-11-24 20:58:05 +00:00
pkgs . util-linux
2020-05-29 06:06:01 +00:00
] ;
2020-04-24 23:36:52 +00:00
} ;
environment . etc = genAttrs
( map
( file : " z f s / z e d . d / ${ file } " )
[
" a l l - s y s l o g . s h "
" p o o l _ i m p o r t - l e d . s h "
" r e s i l v e r _ f i n i s h - s t a r t - s c r u b . s h "
" s t a t e c h a n g e - l e d . s h "
" v d e v _ a t t a c h - l e d . s h "
" z e d - f u n c t i o n s . s h "
" d a t a - n o t i f y . s h "
" r e s i l v e r _ f i n i s h - n o t i f y . s h "
" s c r u b _ f i n i s h - n o t i f y . s h "
" s t a t e c h a n g e - n o t i f y . s h "
" v d e v _ c l e a r - l e d . s h "
]
)
2021-02-05 17:12:51 +00:00
( file : { source = " ${ cfgZfs . package } / e t c / ${ file } " ; } )
2020-04-24 23:36:52 +00:00
// {
" z f s / z e d . d / z e d . r c " . text = zedConf ;
2021-02-05 17:12:51 +00:00
" z f s / z p o o l . d " . source = " ${ cfgZfs . package } / e t c / z f s / z p o o l . d / " ;
2020-04-24 23:36:52 +00:00
} ;
2021-02-05 17:12:51 +00:00
system . fsPackages = [ cfgZfs . package ] ; # XXX: needed? zfs doesn't have (need) a fsck
environment . systemPackages = [ cfgZfs . package ]
2020-04-24 23:36:52 +00:00
++ optional cfgSnapshots . enable autosnapPkg ; # so the user can run the command to see flags
2021-02-05 17:12:51 +00:00
services . udev . packages = [ cfgZfs . package ] ; # to hook zvol naming, etc.
systemd . packages = [ cfgZfs . package ] ;
2020-04-24 23:36:52 +00:00
systemd . services = let
getPoolFilesystems = pool :
filter ( x : x . fsType == " z f s " && ( fsToPool x ) == pool ) config . system . build . fileSystems ;
getPoolMounts = pool :
let
mountPoint = fs : escapeSystemdPath fs . mountPoint ;
in
map ( x : " ${ mountPoint x } . m o u n t " ) ( getPoolFilesystems pool ) ;
createImportService = pool :
nameValuePair " z f s - i m p o r t - ${ pool } " {
description = " I m p o r t Z F S p o o l \" ${ pool } \" " ;
# we need systemd-udev-settle until https://github.com/zfsonlinux/zfs/pull/4943 is merged
requires = [ " s y s t e m d - u d e v - s e t t l e . s e r v i c e " ] ;
2020-07-18 16:06:22 +00:00
after = [
" s y s t e m d - u d e v - s e t t l e . s e r v i c e "
" s y s t e m d - m o d u l e s - l o a d . s e r v i c e "
" s y s t e m d - a s k - p a s s w o r d - c o n s o l e . s e r v i c e "
] ;
2020-04-24 23:36:52 +00:00
wantedBy = ( getPoolMounts pool ) ++ [ " l o c a l - f s . t a r g e t " ] ;
before = ( getPoolMounts pool ) ++ [ " l o c a l - f s . t a r g e t " ] ;
unitConfig = {
DefaultDependencies = " n o " ;
} ;
serviceConfig = {
Type = " o n e s h o t " ;
RemainAfterExit = true ;
} ;
2020-11-12 09:05:59 +00:00
environment . ZFS_FORCE = optionalString cfgZfs . forceImportAll " - f " ;
2020-04-24 23:36:52 +00:00
script = ( importLib {
# See comments at importLib definition.
2021-02-05 17:12:51 +00:00
zpoolCmd = " ${ cfgZfs . package } / s b i n / z p o o l " ;
awkCmd = " ${ pkgs . gawk } / b i n / a w k " ;
2020-04-24 23:36:52 +00:00
inherit cfgZfs ;
} ) + ''
poolImported " ${ pool } " && exit
echo - n " i m p o r t i n g Z F S p o o l \" ${ pool } \" . . . "
# Loop across the import until it succeeds, because the devices needed may not be discovered yet.
for trial in ` seq 1 60 ` ; do
poolReady " ${ pool } " && poolImport " ${ pool } " && break
sleep 1
done
poolImported " ${ pool } " || poolImport " ${ pool } " # Try one last time, e.g. to import a degraded pool.
if poolImported " ${ pool } " ; then
2020-08-20 17:08:02 +00:00
$ { optionalString ( if isBool cfgZfs . requestEncryptionCredentials
then cfgZfs . requestEncryptionCredentials
else cfgZfs . requestEncryptionCredentials != [ ] ) ''
2021-02-05 17:12:51 +00:00
$ { cfgZfs . package } /sbin/zfs list - rHo name , keylocation $ { pool } | while IFS = $ ' \ t' read ds kl ; do
2020-08-20 17:08:02 +00:00
( $ { optionalString ( ! isBool cfgZfs . requestEncryptionCredentials ) ''
if ! echo ' $ { concatStringsSep " \n " cfgZfs . requestEncryptionCredentials } ' | grep - qFx " $ d s " ; then
continue
fi
'' }
case " $ k l " in
2020-07-18 16:06:22 +00:00
none )
; ;
prompt )
2021-02-05 17:12:51 +00:00
$ { config . systemd . package } /bin/systemd-ask-password " E n t e r k e y f o r $ d s : " | $ { cfgZfs . package } /sbin/zfs load-key " $ d s "
2020-07-18 16:06:22 +00:00
; ;
* )
2021-02-05 17:12:51 +00:00
$ { cfgZfs . package } /sbin/zfs load-key " $ d s "
2020-07-18 16:06:22 +00:00
; ;
esac ) < /dev/null # To protect while read ds kl in case anything reads stdin
done
'' }
2020-04-24 23:36:52 +00:00
echo " S u c c e s s f u l l y i m p o r t e d ${ pool } "
else
exit 1
fi
'' ;
} ;
# This forces a sync of any ZFS pools prior to poweroff, even if they're set
# to sync=disabled.
createSyncService = pool :
nameValuePair " z f s - s y n c - ${ pool } " {
description = " S y n c Z F S p o o l \" ${ pool } \" " ;
wantedBy = [ " s h u t d o w n . t a r g e t " ] ;
unitConfig = {
DefaultDependencies = false ;
} ;
serviceConfig = {
Type = " o n e s h o t " ;
RemainAfterExit = true ;
} ;
script = ''
2021-02-05 17:12:51 +00:00
$ { cfgZfs . package } /sbin/zfs set nixos:shutdown-time= " $ ( d a t e ) " " ${ pool } "
2020-04-24 23:36:52 +00:00
'' ;
} ;
2021-08-25 08:27:29 +00:00
2020-04-24 23:36:52 +00:00
createZfsService = serv :
nameValuePair serv {
after = [ " s y s t e m d - m o d u l e s - l o a d . s e r v i c e " ] ;
wantedBy = [ " z f s . t a r g e t " ] ;
} ;
in listToAttrs ( map createImportService dataPools ++
map createSyncService allPools ++
map createZfsService [ " z f s - m o u n t " " z f s - s h a r e " " z f s - z e d " ] ) ;
systemd . targets . zfs-import =
let
services = map ( pool : " z f s - i m p o r t - ${ pool } . s e r v i c e " ) dataPools ;
in
{
requires = services ;
after = services ;
wantedBy = [ " z f s . t a r g e t " ] ;
} ;
systemd . targets . zfs . wantedBy = [ " m u l t i - u s e r . t a r g e t " ] ;
} )
2021-08-25 08:27:29 +00:00
( mkIf ( cfgZfs . enabled && cfgExpandOnBoot != " d i s a b l e d " ) {
systemd . services . " z p o o l - e x p a n d @ " = {
description = " E x p a n d Z F S p o o l s " ;
after = [ " z f s . t a r g e t " ] ;
serviceConfig = {
Type = " o n e s h o t " ;
RemainAfterExit = true ;
} ;
scriptArgs = " % i " ;
path = [ pkgs . gawk cfgZfs . package ] ;
# ZFS has no way of enumerating just devices in a pool in a way
# that 'zpool online -e' supports. Thus, we've implemented a
# bit of a strange approach of highlighting just devices.
# See: https://github.com/openzfs/zfs/issues/12505
script = let
# This UUID has been chosen at random and is to provide a
# collision-proof, predictable token to search for
magicIdentifier = " N I X O S - Z F S - Z P O O L - D E V I C E - I D E N T I F I E R - 3 7 1 0 8 b e c - a f f 6 - 4 b 5 8 - 9 e 5 e - 5 3 c 7 c 9 7 6 6 f 0 5 " ;
zpoolScripts = pkgs . writeShellScriptBin " d e v i c e - h i g h l i g h t e r " ''
echo " ${ magicIdentifier } "
'' ;
in ''
pool = $ 1
echo " E x p a n d i n g a l l d e v i c e s f o r $ p o o l . "
# Put our device-highlighter script it to the PATH
export ZPOOL_SCRIPTS_PATH = $ { zpoolScripts } /bin
# Enable running our precisely specified zpool script as root
export ZPOOL_SCRIPTS_AS_ROOT = 1
devices ( ) (
zpool status - c device-highlighter " $ p o o l " \
| awk ' ( $ 2 == " O N L I N E " && $ 6 == " ${ magicIdentifier } " ) { print $ 1 ; } '
)
for device in $ ( devices ) ; do
echo " A t t e m p t i n g t o e x p a n d $ d e v i c e o f $ p o o l . . . "
if ! zpool online - e " $ p o o l " " $ d e v i c e " ; then
echo " F a i l e d t o e x p a n d ' $ d e v i c e ' o f ' $ p o o l ' . "
fi
done
'' ;
} ;
systemd . services . " z p o o l - e x p a n d - p o o l s " =
let
# Create a string, to be interpolated in a bash script
# which enumerates all of the pools to expand.
# If the `pools` option is `true`, we want to dynamically
# expand every pool. Otherwise we want to enumerate
# just the specifically provided list of pools.
poolListProvider = if cfgExpandOnBoot == " a l l "
then " $ ( z p o o l l i s t - H | a w k ' { p r i n t $ 1 } ' ) "
else lib . escapeShellArgs cfgExpandOnBoot ;
in
{
description = " E x p a n d s p e c i f i e d Z F S p o o l s " ;
wantedBy = [ " d e f a u l t . t a r g e t " ] ;
after = [ " z f s . t a r g e t " ] ;
serviceConfig = {
Type = " o n e s h o t " ;
RemainAfterExit = true ;
} ;
path = [ pkgs . gawk cfgZfs . package ] ;
script = ''
for pool in $ { poolListProvider } ; do
systemctl start - - no-block " z p o o l - e x p a n d @ $ p o o l "
done
'' ;
} ;
} )
2021-02-05 17:12:51 +00:00
( mkIf ( cfgZfs . enabled && cfgSnapshots . enable ) {
2020-04-24 23:36:52 +00:00
systemd . services = let
descr = name : if name == " f r e q u e n t " then " 1 5 m i n s "
else if name == " h o u r l y " then " h o u r "
else if name == " d a i l y " then " d a y "
else if name == " w e e k l y " then " w e e k "
else if name == " m o n t h l y " then " m o n t h "
else throw " u n k n o w n s n a p s h o t n a m e " ;
numSnapshots = name : builtins . getAttr name cfgSnapshots ;
in builtins . listToAttrs ( map ( snapName :
{
name = " z f s - s n a p s h o t - ${ snapName } " ;
value = {
description = " Z F S a u t o - s n a p s h o t t i n g e v e r y ${ descr snapName } " ;
after = [ " z f s - i m p o r t . t a r g e t " ] ;
serviceConfig = {
Type = " o n e s h o t " ;
ExecStart = " ${ zfsAutoSnap } ${ cfgSnapFlags } ${ snapName } ${ toString ( numSnapshots snapName ) } " ;
} ;
restartIfChanged = false ;
} ;
} ) snapshotNames ) ;
systemd . timers = let
timer = name : if name == " f r e q u e n t " then " * : 0 , 1 5 , 3 0 , 4 5 " else name ;
in builtins . listToAttrs ( map ( snapName :
{
name = " z f s - s n a p s h o t - ${ snapName } " ;
value = {
wantedBy = [ " t i m e r s . t a r g e t " ] ;
timerConfig = {
OnCalendar = timer snapName ;
Persistent = " y e s " ;
} ;
} ;
} ) snapshotNames ) ;
} )
2021-02-05 17:12:51 +00:00
( mkIf ( cfgZfs . enabled && cfgScrub . enable ) {
2020-04-24 23:36:52 +00:00
systemd . services . zfs-scrub = {
description = " Z F S p o o l s s c r u b b i n g " ;
after = [ " z f s - i m p o r t . t a r g e t " ] ;
serviceConfig = {
Type = " o n e s h o t " ;
} ;
script = ''
2021-02-05 17:12:51 +00:00
$ { cfgZfs . package } /bin/zpool scrub $ {
2020-04-24 23:36:52 +00:00
if cfgScrub . pools != [ ] then
( concatStringsSep " " cfgScrub . pools )
else
2021-02-05 17:12:51 +00:00
" $ ( ${ cfgZfs . package } / b i n / z p o o l l i s t - H - o n a m e ) "
2020-04-24 23:36:52 +00:00
}
'' ;
} ;
systemd . timers . zfs-scrub = {
wantedBy = [ " t i m e r s . t a r g e t " ] ;
after = [ " m u l t i - u s e r . t a r g e t " ] ; # Apparently scrubbing before boot is complete hangs the system? #53583
timerConfig = {
OnCalendar = cfgScrub . interval ;
Persistent = " y e s " ;
} ;
} ;
} )
2021-02-05 17:12:51 +00:00
( mkIf ( cfgZfs . enabled && cfgTrim . enable ) {
2020-04-24 23:36:52 +00:00
systemd . services . zpool-trim = {
description = " Z F S p o o l s t r i m " ;
after = [ " z f s - i m p o r t . t a r g e t " ] ;
2021-02-05 17:12:51 +00:00
path = [ cfgZfs . package ] ;
2020-04-24 23:36:52 +00:00
startAt = cfgTrim . interval ;
# By default we ignore errors returned by the trim command, in case:
# - HDDs are mixed with SSDs
# - There is a SSDs in a pool that is currently trimmed.
# - There are only HDDs and we would set the system in a degraded state
2021-02-05 17:12:51 +00:00
serviceConfig . ExecStart = " ${ pkgs . runtimeShell } - c ' f o r p o o l i n $ ( z p o o l l i s t - H - o n a m e ) ; d o z p o o l t r i m $ p o o l ; d o n e | | t r u e ' " ;
2020-04-24 23:36:52 +00:00
} ;
2021-01-17 00:15:33 +00:00
systemd . timers . zpool-trim . timerConfig . Persistent = " y e s " ;
2020-04-24 23:36:52 +00:00
} )
] ;
}