2020-04-24 23:36:52 +00:00
{ config , lib , pkgs , utils , . . . }:
with lib ;
with utils ;
let
addCheckDesc = desc : elemType : check : types . addCheck elemType check
// { description = " ${ elemType . description } ( w i t h c h e c k : ${ desc } ) " ; } ;
2021-02-05 17:12:51 +00:00
isNonEmpty = s : ( builtins . match " [ \t \n ] * " s ) == null ;
nonEmptyStr = addCheckDesc " n o n - e m p t y " types . str isNonEmpty ;
2020-04-24 23:36:52 +00:00
fileSystems' = toposort fsBefore ( attrValues config . fileSystems ) ;
fileSystems = if fileSystems' ? result
then # use topologically sorted fileSystems everywhere
fileSystems' . result
else # the assertion below will catch this,
# but we fall back to the original order
# anyway so that other modules could check
# their assertions too
( attrValues config . fileSystems ) ;
specialFSTypes = [ " p r o c " " s y s f s " " t m p f s " " r a m f s " " d e v t m p f s " " d e v p t s " ] ;
2021-06-28 23:13:55 +00:00
nonEmptyWithoutTrailingSlash = addCheckDesc " n o n - e m p t y w i t h o u t t r a i l i n g s l a s h " types . str
( s : isNonEmpty s && ( builtins . match " . + / " s ) == null ) ;
2020-04-24 23:36:52 +00:00
coreFileSystemOpts = { name , config , . . . }: {
options = {
mountPoint = mkOption {
example = " / m n t / u s b " ;
2021-06-28 23:13:55 +00:00
type = nonEmptyWithoutTrailingSlash ;
2020-04-24 23:36:52 +00:00
description = " L o c a t i o n o f t h e m o u n t e d t h e f i l e s y s t e m . " ;
} ;
device = mkOption {
default = null ;
example = " / d e v / s d a " ;
type = types . nullOr nonEmptyStr ;
description = " L o c a t i o n o f t h e d e v i c e . " ;
} ;
fsType = mkOption {
default = " a u t o " ;
example = " e x t 3 " ;
type = nonEmptyStr ;
description = " T y p e o f t h e f i l e s y s t e m . " ;
} ;
options = mkOption {
default = [ " d e f a u l t s " ] ;
example = [ " d a t a = j o u r n a l " ] ;
description = " O p t i o n s u s e d t o m o u n t t h e f i l e s y s t e m . " ;
type = types . listOf nonEmptyStr ;
} ;
2021-06-28 23:13:55 +00:00
depends = mkOption {
default = [ ] ;
example = [ " / p e r s i s t " ] ;
type = types . listOf nonEmptyWithoutTrailingSlash ;
description = ''
List of paths that should be mounted before this one . This filesystem's
<option> device < /option > and <option> mountPoint < /option > are always
checked and do not need to be included explicitly . If a path is added
to this list , any other filesystem whose mount point is a parent of
the path will be mounted before this filesystem . The paths do not need
to actually be the <option> mountPoint < /option > of some other filesystem .
'' ;
} ;
2020-04-24 23:36:52 +00:00
} ;
config = {
mountPoint = mkDefault name ;
device = mkIf ( elem config . fsType specialFSTypes ) ( mkDefault config . fsType ) ;
} ;
} ;
fileSystemOpts = { config , . . . }: {
options = {
label = mkOption {
default = null ;
example = " r o o t - p a r t i t i o n " ;
type = types . nullOr nonEmptyStr ;
description = " L a b e l o f t h e d e v i c e ( i f a n y ) . " ;
} ;
autoFormat = mkOption {
default = false ;
type = types . bool ;
description = ''
If the device does not currently contain a filesystem ( as
determined by <command> blkid < /command > , then automatically
format it with the filesystem type specified in
<option> fsType < /option > . Use with caution .
'' ;
} ;
formatOptions = mkOption {
default = " " ;
type = types . str ;
description = ''
If <option> autoFormat < /option > option is set specifies
extra options passed to mkfs .
'' ;
} ;
autoResize = mkOption {
default = false ;
type = types . bool ;
description = ''
If set , the filesystem is grown to its maximum size before
being mounted . ( This is typically the size of the containing
partition . ) This is currently only supported for ext2/3/4
filesystems that are mounted during early boot .
'' ;
} ;
noCheck = mkOption {
default = false ;
type = types . bool ;
description = " D i s a b l e r u n n i n g f s c k o n t h i s f i l e s y s t e m . " ;
} ;
} ;
config = let
defaultFormatOptions =
# -F needed to allow bare block device without partitions
if ( builtins . substring 0 3 config . fsType ) == " e x t " then " - F "
# -q needed for non-interactive operations
else if config . fsType == " j f s " then " - q "
# (same here)
else if config . fsType == " r e i s e r f s " then " - q "
else null ;
in {
options = mkIf config . autoResize [ " x - n i x o s . a u t o r e s i z e " ] ;
formatOptions = mkIf ( defaultFormatOptions != null ) ( mkDefault defaultFormatOptions ) ;
} ;
} ;
# Makes sequence of `specialMount device mountPoint options fsType` commands.
# `systemMount` should be defined in the sourcing script.
makeSpecialMounts = mounts :
pkgs . writeText " m o u n t s . s h " ( concatMapStringsSep " \n " ( mount : ''
specialMount " ${ mount . device } " " ${ mount . mountPoint } " " ${ concatStringsSep " , " mount . options } " " ${ mount . fsType } "
'' ) m o u n t s ) ;
in
{
###### interface
options = {
fileSystems = mkOption {
default = { } ;
2021-10-06 13:57:05 +00:00
example = literalExpression ''
2020-04-24 23:36:52 +00:00
{
" / " . device = " / d e v / h d a 1 " ;
" / d a t a " = {
device = " / d e v / h d a 2 " ;
fsType = " e x t 3 " ;
options = [ " d a t a = j o u r n a l " ] ;
} ;
" / b i g d i s k " . label = " b i g d i s k " ;
}
'' ;
2020-09-25 04:45:31 +00:00
type = types . attrsOf ( types . submodule [ coreFileSystemOpts fileSystemOpts ] ) ;
2020-04-24 23:36:52 +00:00
description = ''
The file systems to be mounted . It must include an entry for
the root directory ( <literal> mountPoint = " / " < /literal > ) . Each
entry in the list is an attribute set with the following fields :
<literal> mountPoint < /literal > , <literal> device < /literal > ,
<literal> fsType < /literal > ( a file system type recognised by
<command> mount < /command > ; defaults to
<literal> " a u t o " < /literal > ) , and <literal> options < /literal >
( the mount options passed to <command> mount < /command > using the
<option> - o < /option > flag ; defaults to <literal> [ " d e f a u l t s " ] < /literal > ) .
Instead of specifying <literal> device < /literal > , you can also
specify a volume label ( <literal> label < /literal > ) for file
systems that support it , such as ext2/ext3 ( see <command> mke2fs
- L < /command > ) .
'' ;
} ;
system . fsPackages = mkOption {
internal = true ;
default = [ ] ;
description = " P a c k a g e s s u p p l y i n g f i l e s y s t e m m o u n t e r s a n d c h e c k e r s . " ;
} ;
boot . supportedFilesystems = mkOption {
default = [ ] ;
example = [ " b t r f s " ] ;
type = types . listOf types . str ;
description = " N a m e s o f s u p p o r t e d f i l e s y s t e m t y p e s . " ;
} ;
boot . specialFileSystems = mkOption {
default = { } ;
2020-09-25 04:45:31 +00:00
type = types . attrsOf ( types . submodule coreFileSystemOpts ) ;
2020-04-24 23:36:52 +00:00
internal = true ;
description = ''
Special filesystems that are mounted very early during boot .
'' ;
} ;
} ;
###### implementation
config = {
assertions = let
ls = sep : concatMapStringsSep sep ( x : x . mountPoint ) ;
notAutoResizable = fs : fs . autoResize && ! ( hasPrefix " e x t " fs . fsType || fs . fsType == " f 2 f s " ) ;
in [
{ assertion = ! ( fileSystems' ? cycle ) ;
message = " T h e ‘ f i l e S y s t e m s ’ o p t i o n c a n ' t b e t o p o l o g i c a l l y s o r t e d : m o u n t p o i n t d e p e n d e n c y p a t h ${ ls " - > " fileSystems' . cycle } l o o p s t o ${ ls " , " fileSystems' . loops } " ;
}
{ assertion = ! ( any notAutoResizable fileSystems ) ;
message = let
fs = head ( filter notAutoResizable fileSystems ) ;
in
" M o u n t p o i n t ' ${ fs . mountPoint } ' : ' a u t o R e s i z e = t r u e ' i s n o t s u p p o r t e d f o r ' f s T y p e = \" ${ fs . fsType } \" ' : ${ if fs . fsType == " a u t o " then " f s T y p e h a s t o b e e x p l i c i t l y s e t a n d " else " " } o n l y t h e e x t f i l e s y s t e m s a n d f 2 f s s u p p o r t i t . " ;
}
] ;
# Export for use in other modules
system . build . fileSystems = fileSystems ;
system . build . earlyMountScript = makeSpecialMounts ( toposort fsBefore ( attrValues config . boot . specialFileSystems ) ) . result ;
boot . supportedFilesystems = map ( fs : fs . fsType ) fileSystems ;
# Add the mount helpers to the system path so that `mount' can find them.
system . fsPackages = [ pkgs . dosfstools ] ;
environment . systemPackages = with pkgs ; [ fuse3 fuse ] ++ config . system . fsPackages ;
environment . etc . fstab . text =
let
fsToSkipCheck = [ " n o n e " " b i n d f s " " b t r f s " " z f s " " t m p f s " " n f s " " v b o x s f " " g l u s t e r f s " ] ;
skipCheck = fs : fs . noCheck || fs . device == " n o n e " || builtins . elem fs . fsType fsToSkipCheck ;
# https://wiki.archlinux.org/index.php/fstab#Filepath_spaces
escape = string : builtins . replaceStrings [ " " " \t " ] [ " \\ 0 4 0 " " \\ 0 1 1 " ] string ;
2021-06-28 23:13:55 +00:00
swapOptions = sw : concatStringsSep " , " (
2021-07-24 12:14:16 +00:00
sw . options
2021-06-28 23:13:55 +00:00
++ optional ( sw . priority != null ) " p r i = ${ toString sw . priority } "
++ optional ( sw . discardPolicy != null ) " d i s c a r d ${ optionalString ( sw . discardPolicy != " b o t h " ) " = ${ toString sw . discardPolicy } " } "
) ;
2020-04-24 23:36:52 +00:00
in ''
# This is a generated file. Do not edit!
#
# To make changes, edit the fileSystems and swapDevices NixOS options
# in your /etc/nixos/configuration.nix file.
2021-08-27 14:25:00 +00:00
#
# <file system> <mount point> <type> <options> <dump> <pass>
2020-04-24 23:36:52 +00:00
# Filesystems.
$ { concatMapStrings ( fs :
( if fs . device != null then escape fs . device
else if fs . label != null then " / d e v / d i s k / b y - l a b e l / ${ escape fs . label } "
else throw " N o d e v i c e s p e c i f i e d f o r m o u n t p o i n t ‘ ${ fs . mountPoint } ’ . " )
+ " " + escape fs . mountPoint
+ " " + fs . fsType
+ " " + builtins . concatStringsSep " , " fs . options
+ " 0 "
+ " " + ( if skipCheck fs then " 0 " else
if fs . mountPoint == " / " then " 1 " else " 2 " )
+ " \n "
) fileSystems }
# Swap devices.
$ { flip concatMapStrings config . swapDevices ( sw :
2021-03-20 04:20:00 +00:00
" ${ sw . realDevice } n o n e s w a p ${ swapOptions sw } \n "
2020-04-24 23:36:52 +00:00
) }
'' ;
# Provide a target that pulls in all filesystems.
systemd . targets . fs =
{ description = " A l l F i l e S y s t e m s " ;
wants = [ " l o c a l - f s . t a r g e t " " r e m o t e - f s . t a r g e t " ] ;
} ;
systemd . services =
2021-05-20 23:08:51 +00:00
# Emit systemd services to format requested filesystems.
let
2020-04-24 23:36:52 +00:00
formatDevice = fs :
let
mountPoint' = " ${ escapeSystemdPath fs . mountPoint } . m o u n t " ;
device' = escapeSystemdPath fs . device ;
device'' = " ${ device' } . d e v i c e " ;
in nameValuePair " m k f s - ${ device' } "
{ description = " I n i t i a l i s a t i o n o f F i l e s y s t e m ${ fs . device } " ;
wantedBy = [ mountPoint' ] ;
before = [ mountPoint' " s y s t e m d - f s c k @ ${ device' } . s e r v i c e " ] ;
requires = [ device'' ] ;
after = [ device'' ] ;
2020-11-24 20:58:05 +00:00
path = [ pkgs . util-linux ] ++ config . system . fsPackages ;
2020-04-24 23:36:52 +00:00
script =
''
if ! [ - e " ${ fs . device } " ] ; then exit 1 ; fi
# FIXME: this is scary. The test could be more robust.
type = $ ( blkid - p - s TYPE - o value " ${ fs . device } " || true )
if [ - z " $ t y p e " ] ; then
echo " c r e a t i n g ${ fs . fsType } f i l e s y s t e m o n ${ fs . device } . . . "
mkfs . ${ fs . fsType } $ { fs . formatOptions } " ${ fs . device } "
fi
'' ;
unitConfig . RequiresMountsFor = [ " ${ dirOf fs . device } " ] ;
unitConfig . DefaultDependencies = false ; # needed to prevent a cycle
serviceConfig . Type = " o n e s h o t " ;
} ;
2021-05-20 23:08:51 +00:00
in listToAttrs ( map formatDevice ( filter ( fs : fs . autoFormat ) fileSystems ) ) // {
# Mount /sys/fs/pstore for evacuating panic logs and crashdumps from persistent storage onto the disk using systemd-pstore.
# This cannot be done with the other special filesystems because the pstore module (which creates the mount point) is not loaded then.
" m o u n t - p s t o r e " = {
serviceConfig = {
Type = " o n e s h o t " ;
2021-08-05 21:33:18 +00:00
# skip on kernels without the pstore module
ExecCondition = " ${ pkgs . kmod } / b i n / m o d p r o b e - b p s t o r e " ;
ExecStart = pkgs . writeShellScript " m o u n t - p s t o r e . s h " ''
2021-05-20 23:08:51 +00:00
set - eu
2021-08-05 21:33:18 +00:00
# if the pstore module is builtin it will have mounted the persistent store automatically. it may also be already mounted for other reasons.
$ { pkgs . util-linux } /bin/mountpoint - q /sys/fs/pstore || $ { pkgs . util-linux } /bin/mount - t pstore - o nosuid , noexec , nodev pstore /sys/fs/pstore
# wait up to 1.5 seconds for the backend to be registered and the files to appear. a systemd path unit cannot detect this happening; and succeeding after a restart would not start dependent units.
TRIES = 15
while [ " $ ( c a t / s y s / m o d u l e / p s t o r e / p a r a m e t e r s / b a c k e n d ) " = " ( n u l l ) " ] ; do
if ( ( $ TRIES ) ) ; then
sleep 0 .1
TRIES = $ ( ( TRIES-1 ) )
else
echo " P e r s i s t e n t S t o r a g e b a c k e n d w a s n o t r e g i s t e r e d i n t i m e . " > & 2
break
fi
2021-05-20 23:08:51 +00:00
done
'' ;
RemainAfterExit = true ;
} ;
unitConfig = {
ConditionVirtualization = " ! c o n t a i n e r " ;
DefaultDependencies = false ; # needed to prevent a cycle
} ;
before = [ " s y s t e m d - p s t o r e . s e r v i c e " ] ;
wantedBy = [ " s y s t e m d - p s t o r e . s e r v i c e " ] ;
} ;
} ;
2020-04-24 23:36:52 +00:00
systemd . tmpfiles . rules = [
" d / r u n / k e y s 0 7 5 0 r o o t ${ toString config . ids . gids . keys } "
" z / r u n / k e y s 0 7 5 0 r o o t ${ toString config . ids . gids . keys } "
] ;
# Sync mount options with systemd's src/core/mount-setup.c: mount_table.
boot . specialFileSystems = {
" / p r o c " = { fsType = " p r o c " ; options = [ " n o s u i d " " n o e x e c " " n o d e v " ] ; } ;
" / r u n " = { fsType = " t m p f s " ; options = [ " n o s u i d " " n o d e v " " s t r i c t a t i m e " " m o d e = 7 5 5 " " s i z e = ${ config . boot . runSize } " ] ; } ;
" / d e v " = { fsType = " d e v t m p f s " ; options = [ " n o s u i d " " s t r i c t a t i m e " " m o d e = 7 5 5 " " s i z e = ${ config . boot . devSize } " ] ; } ;
" / d e v / s h m " = { fsType = " t m p f s " ; options = [ " n o s u i d " " n o d e v " " s t r i c t a t i m e " " m o d e = 1 7 7 7 " " s i z e = ${ config . boot . devShmSize } " ] ; } ;
" / d e v / p t s " = { fsType = " d e v p t s " ; options = [ " n o s u i d " " n o e x e c " " m o d e = 6 2 0 " " p t m x m o d e = 0 6 6 6 " " g i d = ${ toString config . ids . gids . tty } " ] ; } ;
# To hold secrets that shouldn't be written to disk
" / r u n / k e y s " = { fsType = " r a m f s " ; options = [ " n o s u i d " " n o d e v " " m o d e = 7 5 0 " ] ; } ;
} // optionalAttrs ( ! config . boot . isContainer ) {
# systemd-nspawn populates /sys by itself, and remounting it causes all
# kinds of weird issues (most noticeably, waiting for host disk device
# nodes).
" / s y s " = { fsType = " s y s f s " ; options = [ " n o s u i d " " n o e x e c " " n o d e v " ] ; } ;
} ;
} ;
}