2024-04-21 15:54:59 +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
nameValuePair
optionalString
removePrefix
removeSuffix
replaceStrings
stringToCharacters
types
;
inherit ( lib . strings ) toJSON normalizePath escapeC ;
in
2020-04-24 23:36:52 +00:00
rec {
2020-09-25 04:45:31 +00:00
# Copy configuration files to avoid having the entire sources in the system closure
2024-04-21 15:54:59 +00:00
copyFile = filePath : pkgs . runCommand ( builtins . unsafeDiscardStringContext ( baseNameOf filePath ) ) { } ''
2020-09-25 04:45:31 +00:00
cp $ { filePath } $ out
'' ;
2020-08-20 17:08:02 +00: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.
2021-12-06 16:07:01 +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-08-20 17:08:02 +00:00
fsNeededForBoot = fs : fs . neededForBoot || elem fs . mountPoint pathsNeededForBoot ;
2020-04-24 23:36:52 +00:00
# Check whenever `b` depends on `a` as a fileSystem
2021-06-28 23:13:55 +00: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 ) ) " / " } " ;
normalise = mount : mount // { device = normalisePath ( toString mount . device ) ;
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 ;
2020-04-24 23:36:52 +00:00
2022-10-21 18:38:19 +00: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-04-21 15:54:59 +00:00
normalizedPath = normalizePath s ;
2022-10-21 18:38:19 +00:00
in
2022-12-17 10:02:37 +00:00
replaceStrings [ " / " ] [ " - " ]
2024-04-21 15:54:59 +00:00
( replacePrefix " . " ( escapeC [ " . " ] " . " )
( escapeC ( stringToCharacters " ! \" # $ % & ' ( ) * + , ; < = > = @ [ \\ ] ^ ` { | } ~ - " )
2022-10-21 18:38:19 +00:00
( if normalizedPath == " / " then normalizedPath else trim normalizedPath ) ) ) ;
2020-04-24 23:36:52 +00:00
2022-03-30 09:31:56 +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-04-21 15:54:59 +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
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-03-30 09:31:56 +00:00
in
2024-04-21 15:54:59 +00:00
replaceStrings [ " % " " $ " ] [ " % % " " $ $ " ] ( toJSON s ) ;
2022-03-30 09:31:56 +00:00
# Quotes a list of arguments into a single string for use in a Exec*
# line.
escapeSystemdExecArgs = concatMapStringsSep " " escapeSystemdExecArg ;
2020-04-24 23:36:52 +00: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 } "
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 "
else
shell ;
/* 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 " ; }
* /
recursiveGetAttrWithJqPrefix = item : attr :
let
recurse = prefix : item :
if item ? ${ attr } then
nameValuePair prefix item . ${ attr }
2024-02-07 01:22:34 +00:00
else if isDerivation item then [ ]
2020-04-24 23:36:52 +00:00
else if isAttrs item then
2022-10-21 18:38:19 +00:00
map ( name :
let
2022-12-17 10:02:37 +00:00
escapedName = '' " ${ replaceStrings [ '' " '' " \\ " ] [ '' \ " '' " \\ \\ " ] name } " '' ;
2022-10-21 18:38:19 +00:00
in
recurse ( prefix + " . " + escapedName ) item . ${ name } ) ( attrNames item )
2020-04-24 23:36:52 +00: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 "
}
}
]
}
* /
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
secrets = recursiveGetAttrWithJqPrefix set attr ;
2023-10-09 19:29:22 +00:00
stringOrDefault = str : def : if str == " " then def else str ;
2020-04-24 23:36:52 +00:00
in ''
if [ [ - h ' $ { output } ' ] ] ; then
rm ' $ { output } '
fi
2022-02-10 20:34:41 +00:00
2022-03-05 16:20:37 +00:00
inherit_errexit_enabled = 0
shopt - pq inherit_errexit && inherit_errexit_enabled = 1
2022-02-10 20:34:41 +00:00
shopt - s inherit_errexit
2020-04-24 23:36:52 +00:00
''
+ concatStringsSep
" \n "
2022-02-10 20:34:41 +00:00
( imap1 ( index : name : ''
secret $ { toString index } = $ ( < ' $ { secrets . ${ name } } ' )
export secret $ { toString index }
'' )
2020-04-24 23:36:52 +00:00
( attrNames secrets ) )
+ " \n "
2022-10-21 18:38:19 +00:00
+ " ${ pkgs . jq } / b i n / j q > ' ${ output } ' "
2024-04-21 15:54:59 +00:00
+ escapeShellArg ( stringOrDefault
2023-10-09 19:29:22 +00:00
( concatStringsSep
" | "
( imap1 ( index : name : '' ${ name } = $E N V . s e c r e t ${ toString index } '' )
( attrNames secrets ) ) )
" . " )
2020-04-24 23:36:52 +00:00
+ ''
2022-10-21 18:38:19 +00:00
< < ' EOF'
2024-04-21 15:54:59 +00:00
$ { toJSON set }
2020-04-24 23:36:52 +00:00
EOF
2022-03-05 16:20:37 +00:00
( ( ! $ inherit_errexit_enabled ) ) && shopt - u inherit_errexit
2020-04-24 23:36:52 +00:00
'' ;
2021-12-19 01:06:50 +00:00
2022-04-15 01:41:22 +00: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-04-21 15:54:59 +00:00
namesToRemove = map getName packagesToRemove ;
2022-04-15 01:41:22 +00:00
in
2024-04-21 15:54:59 +00:00
filter ( x : ! ( elem ( getName x ) namesToRemove ) ) packages ;
2022-04-15 01:41:22 +00:00
2021-12-19 01:06:50 +00:00
systemdUtils = {
lib = import ./systemd-lib.nix { inherit lib config pkgs ; } ;
unitOptions = import ./systemd-unit-options.nix { inherit lib systemdUtils ; } ;
2022-04-27 09:35:20 +00:00
types = import ./systemd-types.nix { inherit lib systemdUtils pkgs ; } ;
2023-07-15 17:15:38 +00:00
network = {
units = import ./systemd-network-units.nix { inherit lib systemdUtils ; } ;
} ;
2021-12-19 01:06:50 +00:00
} ;
2020-04-24 23:36:52 +00:00
}