2024-03-28 05:02:32 +00:00
{ lib , config , pkgs }:
let
inherit ( lib )
any
attrNames
concatMapStringsSep
concatStringsSep
elem
escapeShellArg
filter
flatten
getName
hasPrefix
hasSuffix
imap0
imap1
isAttrs
isDerivation
isFloat
isInt
isList
isPath
isString
listToAttrs
2024-07-07 07:24:03 +01:00
mapAttrs
2024-03-28 05:02:32 +00:00
nameValuePair
optionalString
removePrefix
removeSuffix
replaceStrings
stringToCharacters
types
;
inherit ( lib . strings ) toJSON normalizePath escapeC ;
in
2012-10-12 22:01:49 +01:00
2024-03-21 13:52:12 +00:00
let
utils = rec {
2012-10-12 22:01:49 +01:00
2020-08-16 13:34:26 +01:00
# Copy configuration files to avoid having the entire sources in the system closure
2024-03-28 05:02:32 +00:00
copyFile = filePath : pkgs . runCommand ( builtins . unsafeDiscardStringContext ( baseNameOf filePath ) ) { } ''
2020-08-16 13:34:26 +01:00
cp $ { filePath } $ out
'' ;
2020-07-27 01:05:21 +01:00
# Check whenever fileSystem is needed for boot. NOTE: Make sure
# pathsNeededForBoot is closed under the parent relationship, i.e. if /a/b/c
# is in the list, put /a and /a/b in as well.
2022-12-03 13:16:42 +00:00
pathsNeededForBoot = [ " / " " / n i x " " / n i x / s t o r e " " / v a r " " / v a r / l o g " " / v a r / l i b " " / v a r / l i b / n i x o s " " / e t c " " / u s r " ] ;
2020-07-27 01:05:21 +01:00
fsNeededForBoot = fs : fs . neededForBoot || elem fs . mountPoint pathsNeededForBoot ;
2016-08-23 19:01:35 +01:00
2015-11-25 19:09:09 +00:00
# Check whenever `b` depends on `a` as a fileSystem
2020-05-21 08:10:47 +01:00
fsBefore = a : b :
let
# normalisePath adds a slash at the end of the path if it didn't already
# have one.
#
# The reason slashes are added at the end of each path is to prevent `b`
# from accidentally depending on `a` in cases like
# a = { mountPoint = "/aaa"; ... }
# b = { device = "/aaaa"; ... }
# Here a.mountPoint *is* a prefix of b.device even though a.mountPoint is
# *not* a parent of b.device. If we add a slash at the end of each string,
# though, this is not a problem: "/aaa/" is not a prefix of "/aaaa/".
normalisePath = path : " ${ path } ${ optionalString ( ! ( hasSuffix " / " path ) ) " / " } " ;
2021-06-18 07:32:45 +01:00
normalise = mount : mount // { device = normalisePath ( toString mount . device ) ;
2020-05-21 08:10:47 +01:00
mountPoint = normalisePath mount . mountPoint ;
depends = map normalisePath mount . depends ;
} ;
a' = normalise a ;
b' = normalise b ;
in hasPrefix a' . mountPoint b' . device
|| hasPrefix a' . mountPoint b' . mountPoint
|| any ( hasPrefix a' . mountPoint ) b' . depends ;
2015-11-25 19:09:09 +00:00
2022-06-11 10:18:54 +01:00
# Escape a path according to the systemd rules. FIXME: slow
# The rules are described in systemd.unit(5) as follows:
# The escaping algorithm operates as follows: given a string, any "/" character is replaced by "-", and all other characters which are not ASCII alphanumerics, ":", "_" or "." are replaced by C-style "\x2d" escapes. In addition, "." is replaced with such a C-style escape when it would appear as the first character in the escaped string.
# When the input qualifies as absolute file system path, this algorithm is extended slightly: the path to the root directory "/" is encoded as single dash "-". In addition, any leading, trailing or duplicate "/" characters are removed from the string before transformation. Example: /foo//bar/baz/ becomes "foo-bar-baz".
escapeSystemdPath = s : let
replacePrefix = p : r : s : ( if ( hasPrefix p s ) then r + ( removePrefix p s ) else s ) ;
trim = s : removeSuffix " / " ( removePrefix " / " s ) ;
2024-03-28 05:02:32 +00:00
normalizedPath = normalizePath s ;
2022-06-11 10:18:54 +01:00
in
2022-12-12 01:36:03 +00:00
replaceStrings [ " / " ] [ " - " ]
2024-03-28 05:02:32 +00:00
( replacePrefix " . " ( escapeC [ " . " ] " . " )
( escapeC ( stringToCharacters " ! \" # $ % & ' ( ) * + , ; < = > = @ [ \\ ] ^ ` { | } ~ - " )
2022-06-11 10:18:54 +01:00
( if normalizedPath == " / " then normalizedPath else trim normalizedPath ) ) ) ;
2012-10-12 22:01:49 +01:00
2022-01-09 07:46:55 +00:00
# Quotes an argument for use in Exec* service lines.
# systemd accepts "-quoted strings with escape sequences, toJSON produces
# a subset of these.
# Additionally we escape % to disallow expansion of % specifiers. Any lone ;
# in the input will be turned it ";" and thus lose its special meaning.
# Every $ is escaped to $$, this makes it unnecessary to disable environment
# substitution for the directive.
escapeSystemdExecArg = arg :
let
2024-03-28 05:02:32 +00:00
s = if isPath arg then " ${ arg } "
else if isString arg then arg
else if isInt arg || isFloat arg || isDerivation arg then toString arg
2024-03-23 14:59:50 +00:00
else throw " e s c a p e S y s t e m d E x e c A r g o n l y a l l o w s s t r i n g s , p a t h s , n u m b e r s a n d d e r i v a t i o n s " ;
2022-01-09 07:46:55 +00:00
in
2024-03-28 05:02:32 +00:00
replaceStrings [ " % " " $ " ] [ " % % " " $ $ " ] ( toJSON s ) ;
2022-01-09 07:46:55 +00:00
# Quotes a list of arguments into a single string for use in a Exec*
# line.
escapeSystemdExecArgs = concatMapStringsSep " " escapeSystemdExecArg ;
2016-06-12 20:03:14 +01:00
# Returns a system path for a given shell package
toShellPath = shell :
if types . shellPackage . check shell then
" / r u n / c u r r e n t - s y s t e m / s w ${ shell . shellPath } "
2016-07-04 15:10:51 +01:00
else if types . package . check shell then
throw " ${ shell } i s n o t a s h e l l p a c k a g e "
2016-06-12 20:03:14 +01:00
else
shell ;
2019-09-06 15:38:41 +01:00
/* R e c u r s e i n t o a l i s t o r a n a t t r s e t , s e a r c h i n g f o r a t t r s n a m e d l i k e
the value of the " a t t r " parameter , and return an attrset where the
names are the corresponding jq path where the attrs were found and
the values are the values of the attrs .
Example :
recursiveGetAttrWithJqPrefix {
example = [
{
irrelevant = " n o t i n t e r e s t i n g " ;
}
{
ignored = " i g n o r e d a t t r " ;
relevant = {
secret = {
_secret = " / p a t h / t o / s e c r e t " ;
} ;
} ;
}
] ;
} " _ s e c r e t " -> { " . e x a m p l e [ 1 ] . r e l e v a n t . s e c r e t " = " / p a t h / t o / s e c r e t " ; }
* /
2024-07-07 07:24:03 +01:00
recursiveGetAttrWithJqPrefix = item : attr : mapAttrs ( _name : set : set . ${ attr } ) ( recursiveGetAttrsetWithJqPrefix item attr ) ;
/* S i m i l a r t o ` r e c u r s i v e G e t A t t r W i t h J q P r e f i x ` , b u t r e t u r n s t h e w h o l e
attribute set containing ` attr ` instead of the value of ` attr ` in
the set .
Example :
recursiveGetAttrsetWithJqPrefix {
example = [
{
irrelevant = " n o t i n t e r e s t i n g " ;
}
{
ignored = " i g n o r e d a t t r " ;
relevant = {
secret = {
_secret = " / p a t h / t o / s e c r e t " ;
quote = true ;
} ;
} ;
}
] ;
} " _ s e c r e t " -> { " . e x a m p l e [ 1 ] . r e l e v a n t . s e c r e t " = { _secret = " / p a t h / t o / s e c r e t " ; quote = true ; } ; }
* /
recursiveGetAttrsetWithJqPrefix = item : attr :
2019-09-06 15:38:41 +01:00
let
recurse = prefix : item :
if item ? ${ attr } then
2024-07-07 07:24:03 +01:00
nameValuePair prefix item
2024-01-30 10:00:08 +00:00
else if isDerivation item then [ ]
2019-09-06 15:38:41 +01:00
else if isAttrs item then
2022-09-30 16:02:24 +01:00
map ( name :
let
2022-12-12 01:36:03 +00:00
escapedName = '' " ${ replaceStrings [ '' " '' " \\ " ] [ '' \ " '' " \\ \\ " ] name } " '' ;
2022-09-30 16:02:24 +01:00
in
recurse ( prefix + " . " + escapedName ) item . ${ name } ) ( attrNames item )
2019-09-06 15:38:41 +01:00
else if isList item then
imap0 ( index : item : recurse ( prefix + " [ ${ toString index } ] " ) item ) item
else
[ ] ;
in listToAttrs ( flatten ( recurse " " item ) ) ;
/* T a k e s a n a t t r s e t a n d a f i l e p a t h a n d g e n e r a t e s a b a s h s n i p p e t t h a t
outputs a JSON file at the file path with all instances of
{ _secret = " / p a t h / t o / s e c r e t " }
in the attrset replaced with the contents of the file
" / p a t h / t o / s e c r e t " in the output JSON .
When a configuration option accepts an attrset that is finally
converted to JSON , this makes it possible to let the user define
arbitrary secret values .
Example :
If the file " / p a t h / t o / s e c r e t " contains the string
" t o p s e c r e t p a s s w o r d 1 2 3 4 " ,
genJqSecretsReplacementSnippet {
example = [
{
irrelevant = " n o t i n t e r e s t i n g " ;
}
{
ignored = " i g n o r e d a t t r " ;
relevant = {
secret = {
_secret = " / p a t h / t o / s e c r e t " ;
} ;
} ;
}
] ;
} " / p a t h / t o / o u t p u t . j s o n "
would generate a snippet that , when run , outputs the following
JSON file at " / p a t h / t o / o u t p u t . j s o n " :
{
" e x a m p l e " : [
{
" i r r e l e v a n t " : " n o t i n t e r e s t i n g "
} ,
{
" i g n o r e d " : " i g n o r e d a t t r " ,
" r e l e v a n t " : {
" s e c r e t " : " t o p s e c r e t p a s s w o r d 1 2 3 4 "
}
}
]
}
2024-07-07 07:24:03 +01:00
The attribute set { _secret = " / p a t h / t o / s e c r e t " ; } can contain extra
options , currently it accepts the ` quote = true | false ` option .
If ` quote = true ` ( default behavior ) , the content of the secret file will
be quoted as a string and embedded . Otherwise , if ` quote = false ` , the
content of the secret file will be parsed to JSON and then embedded .
Example :
If the file " / p a t h / t o / s e c r e t " contains the JSON document :
[
{ " a " : " t o p s e c r e t p a s s w o r d 1 2 3 4 " } ,
{ " b " : " t o p s e c r e t p a s s w o r d 5 6 7 8 " }
]
genJqSecretsReplacementSnippet {
example = [
{
irrelevant = " n o t i n t e r e s t i n g " ;
}
{
ignored = " i g n o r e d a t t r " ;
relevant = {
secret = {
_secret = " / p a t h / t o / s e c r e t " ;
quote = false ;
} ;
} ;
}
] ;
} " / p a t h / t o / o u t p u t . j s o n "
would generate a snippet that , when run , outputs the following
JSON file at " / p a t h / t o / o u t p u t . j s o n " :
{
" e x a m p l e " : [
{
" i r r e l e v a n t " : " n o t i n t e r e s t i n g "
} ,
{
" i g n o r e d " : " i g n o r e d a t t r " ,
" r e l e v a n t " : {
" s e c r e t " : [
{ " a " : " t o p s e c r e t p a s s w o r d 1 2 3 4 " } ,
{ " b " : " t o p s e c r e t p a s s w o r d 5 6 7 8 " }
]
}
}
]
}
2019-09-06 15:38:41 +01:00
* /
genJqSecretsReplacementSnippet = genJqSecretsReplacementSnippet' " _ s e c r e t " ;
# Like genJqSecretsReplacementSnippet, but allows the name of the
# attr which identifies the secret to be changed.
genJqSecretsReplacementSnippet' = attr : set : output :
let
2024-07-07 07:24:03 +01:00
secretsRaw = recursiveGetAttrsetWithJqPrefix set attr ;
# Set default option values
secrets = mapAttrs ( _name : set : {
quote = true ;
} // set ) secretsRaw ;
2023-09-30 13:00:13 +01:00
stringOrDefault = str : def : if str == " " then def else str ;
2019-09-06 15:38:41 +01:00
in ''
if [ [ - h ' $ { output } ' ] ] ; then
rm ' $ { output } '
fi
2022-01-31 11:44:54 +00:00
2022-02-23 11:48:51 +00:00
inherit_errexit_enabled = 0
shopt - pq inherit_errexit && inherit_errexit_enabled = 1
2022-01-31 11:44:54 +00:00
shopt - s inherit_errexit
2019-09-06 15:38:41 +01:00
''
+ concatStringsSep
" \n "
2022-01-31 11:44:54 +00:00
( imap1 ( index : name : ''
2024-07-07 07:24:03 +01:00
secret $ { toString index } = $ ( < ' $ { secrets . ${ name } . ${ attr } } ' )
2022-01-31 11:44:54 +00:00
export secret $ { toString index }
'' )
2019-09-06 15:38:41 +01:00
( attrNames secrets ) )
+ " \n "
2022-09-30 16:02:24 +01:00
+ " ${ pkgs . jq } / b i n / j q > ' ${ output } ' "
2024-03-28 05:02:32 +00:00
+ escapeShellArg ( stringOrDefault
2023-09-30 13:00:13 +01:00
( concatStringsSep
" | "
2024-07-07 07:24:03 +01:00
( imap1 ( index : name : '' ${ name } = ( $E N V . s e c r e t ${ toString index } ${ optionalString ( ! secrets . ${ name } . quote ) " | f r o m j s o n " } ) '' )
2023-09-30 13:00:13 +01:00
( attrNames secrets ) ) )
" . " )
2019-09-06 15:38:41 +01:00
+ ''
2022-09-30 16:02:24 +01:00
< < ' EOF'
2024-03-28 05:02:32 +00:00
$ { toJSON set }
2019-09-06 15:38:41 +01:00
EOF
2022-02-23 11:48:51 +00:00
( ( ! $ inherit_errexit_enabled ) ) && shopt - u inherit_errexit
2019-09-06 15:38:41 +01:00
'' ;
2021-11-20 17:34:13 +00:00
2022-04-11 09:51:54 +01:00
/* R e m o v e p a c k a g e s o f p a c k a g e s T o R e m o v e f r o m p a c k a g e s , b a s e d o n t h e i r n a m e s .
Relies on package names and has quadratic complexity so use with caution !
Type :
removePackagesByName : : [ package ] -> [ package ] -> [ package ]
Example :
removePackagesByName [ nautilus file-roller ] [ file-roller totem ]
= > [ nautilus ]
* /
removePackagesByName = packages : packagesToRemove :
let
2024-03-28 05:02:32 +00:00
namesToRemove = map getName packagesToRemove ;
2022-04-11 09:51:54 +01:00
in
2024-03-28 05:02:32 +00:00
filter ( x : ! ( elem ( getName x ) namesToRemove ) ) packages ;
2022-04-11 09:51:54 +01:00
2021-11-20 17:34:13 +00:00
systemdUtils = {
2024-03-21 13:52:12 +00:00
lib = import ./systemd-lib.nix { inherit lib config pkgs utils ; } ;
2021-11-20 17:34:13 +00:00
unitOptions = import ./systemd-unit-options.nix { inherit lib systemdUtils ; } ;
2022-04-24 19:47:28 +01:00
types = import ./systemd-types.nix { inherit lib systemdUtils pkgs ; } ;
2023-07-01 03:18:05 +01:00
network = {
units = import ./systemd-network-units.nix { inherit lib systemdUtils ; } ;
} ;
2021-11-20 17:34:13 +00:00
} ;
2024-03-21 13:52:12 +00:00
} ;
in utils