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 06:24:03 +00: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 21:01:49 +00:00
2024-03-21 13:52:12 +00:00
let
utils = rec {
2024-12-10 19:26:33 +00:00
2020-08-16 12:34:26 +00: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 12:34:26 +00:00
cp $ { filePath } $ out
'' ;
2024-12-10 19:26:33 +00:00
2020-07-27 00:05:21 +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.
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 00:05:21 +00:00
fsNeededForBoot = fs : fs . neededForBoot || elem fs . mountPoint pathsNeededForBoot ;
2024-12-10 19:26:33 +00:00
2015-11-25 19:09:09 +00:00
# Check whenever `b` depends on `a` as a fileSystem
2020-05-21 07:10:47 +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 ) ) " / " } " ;
2021-06-18 06:32:45 +00:00
normalise =
mount :
mount
// {
device = normalisePath ( toString mount . device ) ;
2020-05-21 07:10:47 +00:00
mountPoint = normalisePath mount . mountPoint ;
depends = map normalisePath mount . depends ;
} ;
2024-12-10 19:26:33 +00:00
2020-05-21 07:10:47 +00:00
a' = normalise a ;
b' = normalise b ;
2024-12-10 19:26:33 +00:00
2020-05-21 07:10:47 +00:00
in
hasPrefix a' . mountPoint b' . device
|| hasPrefix a' . mountPoint b' . mountPoint
|| any ( hasPrefix a' . mountPoint ) b' . depends ;
2024-12-10 19:26:33 +00:00
2022-06-11 09:18:54 +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-03-28 05:02:32 +00:00
normalizedPath = normalizePath s ;
2022-06-11 09:18:54 +00: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 09:18:54 +00:00
if normalizedPath == " / " then normalizedPath else trim normalizedPath
2024-12-10 19:26:33 +00:00
)
)
2022-06-11 09:18:54 +00:00
) ;
2024-12-10 19:26:33 +00: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 ;
2024-12-10 19:26:33 +00:00
2016-06-12 19:03:14 +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 } "
2016-07-04 14:10:51 +00: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 19:03:14 +00:00
else
shell ;
2024-12-10 19:26:33 +00:00
2019-09-06 14:38:41 +00:00
/*
Recurse into a list or an attrset , searching for attrs named like
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 .
2024-12-10 19:26:33 +00:00
2019-09-06 14:38:41 +00:00
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 06:24:03 +00:00
recursiveGetAttrWithJqPrefix =
item : attr : mapAttrs ( _name : set : set . ${ attr } ) ( recursiveGetAttrsetWithJqPrefix item attr ) ;
2024-12-10 19:26:33 +00:00
2024-07-07 06:24:03 +00:00
/*
Similar to ` recursiveGetAttrWithJqPrefix ` , but returns the whole
attribute set containing ` attr ` instead of the value of ` attr ` in
the set .
2024-12-10 19:26:33 +00:00
2024-07-07 06:24:03 +00:00
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 ;
2024-12-10 19:26:33 +00:00
} ;
2024-07-07 06:24:03 +00:00
} ;
}
] ;
} " _ 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 14:38:41 +00:00
let
recurse =
prefix : item :
if item ? ${ attr } then
2024-07-07 06:24:03 +00:00
nameValuePair prefix item
2024-01-30 10:00:08 +00:00
else if isDerivation item then
[ ]
2019-09-06 14:38:41 +00:00
else if isAttrs item then
2022-09-30 15:02:24 +00:00
map (
name :
let
2022-12-12 01:36:03 +00:00
escapedName = '' " ${ replaceStrings [ '' " '' " \\ " ] [ '' \ " '' " \\ \\ " ] name } " '' ;
2022-09-30 15:02:24 +00:00
in
recurse ( prefix + " . " + escapedName ) item . ${ name }
) ( attrNames item )
2019-09-06 14:38:41 +00:00
else if isList item then
imap0 ( index : item : recurse ( prefix + " [ ${ toString index } ] " ) item ) item
else
[ ] ;
in
listToAttrs ( flatten ( recurse " " item ) ) ;
2024-12-10 19:26:33 +00:00
2019-09-06 14:38:41 +00:00
/*
Takes an attrset and a file path and generates a bash snippet that
outputs a JSON file at the file path with all instances of
2024-12-10 19:26:33 +00:00
2019-09-06 14:38:41 +00:00
{ _secret = " / p a t h / t o / s e c r e t " }
2024-12-10 19:26:33 +00:00
2019-09-06 14:38:41 +00:00
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 .
2024-12-10 19:26:33 +00:00
2019-09-06 14:38:41 +00:00
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 .
2024-12-10 19:26:33 +00:00
2019-09-06 14:38:41 +00:00
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 " ,
2024-12-10 19:26:33 +00:00
2019-09-06 14:38:41 +00:00
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 " ;
} ;
2024-12-10 19:26:33 +00:00
} ;
2019-09-06 14:38:41 +00:00
}
] ;
} " / p a t h / t o / o u t p u t . j s o n "
2024-12-10 19:26:33 +00:00
2019-09-06 14:38:41 +00:00
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 " :
2024-12-10 19:26:33 +00:00
2019-09-06 14:38:41 +00:00
{
" 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-12-10 19:26:33 +00:00
}
2019-09-06 14:38:41 +00:00
]
}
2024-12-10 19:26:33 +00:00
2024-07-07 06:24:03 +00: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 .
2024-12-10 19:26:33 +00:00
2024-07-07 06:24:03 +00:00
If ` quote = true ` ( default behavior ) , the content of the secret file w ill
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 .
2024-12-10 19:26:33 +00:00
2024-07-07 06:24:03 +00:00
Example :
If the file " / p a t h / t o / s e c r e t " contains the JSON document :
2024-12-10 19:26:33 +00:00
2024-07-07 06:24:03 +00:00
[
{ " 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 " }
]
2024-12-10 19:26:33 +00:00
2024-07-07 06:24:03 +00:00
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 ;
2024-12-10 19:26:33 +00:00
} ;
2024-07-07 06:24:03 +00:00
} ;
}
] ;
} " / p a t h / t o / o u t p u t . j s o n "
2024-12-10 19:26:33 +00:00
2024-07-07 06:24:03 +00:00
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 " :
2024-12-10 19:26:33 +00:00
2024-07-07 06:24:03 +00:00
{
" 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 " }
2024-12-10 19:26:33 +00:00
]
}
}
2024-07-07 06:24:03 +00:00
]
}
2019-09-06 14:38:41 +00:00
* /
genJqSecretsReplacementSnippet = genJqSecretsReplacementSnippet' " _ s e c r e t " ;
2024-12-10 19:26:33 +00:00
2019-09-06 14:38:41 +00:00
# Like genJqSecretsReplacementSnippet, but allows the name of the
# attr which identifies the secret to be changed.
genJqSecretsReplacementSnippet' =
attr : set : output :
let
2024-07-07 06:24:03 +00:00
secretsRaw = recursiveGetAttrsetWithJqPrefix set attr ;
# Set default option values
secrets = mapAttrs (
_name : set :
{
quote = true ;
}
// set
) secretsRaw ;
2023-09-30 12:00:13 +00:00
stringOrDefault = str : def : if str == " " then def else str ;
2019-09-06 14:38:41 +00:00
in
''
if [ [ - h ' $ { output } ' ] ] ; then
rm ' $ { output } '
fi
2024-12-10 19:26:33 +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 14:38:41 +00:00
''
+ concatStringsSep " \n " (
2022-01-31 11:44:54 +00:00
imap1 ( index : name : ''
2024-07-07 06:24:03 +00:00
secret $ { toString index } = $ ( < ' $ { secrets . ${ name } . ${ attr } } ' )
2022-01-31 11:44:54 +00:00
export secret $ { toString index }
2019-09-06 14:38:41 +00:00
'' ) ( a t t r N a m e s s e c r e t s )
2024-12-10 19:26:33 +00:00
)
2019-09-06 14:38:41 +00:00
+ " \n "
2022-09-30 15:02:24 +00:00
+ " ${ pkgs . jq } / b i n / j q > ' ${ output } ' "
2024-03-28 05:02:32 +00:00
+ escapeShellArg (
2023-09-30 12:00:13 +00:00
stringOrDefault ( concatStringsSep " | " (
2024-07-07 06:24:03 +00: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 12:00:13 +00:00
) ( attrNames secrets )
) ) " . "
2024-12-10 19:26:33 +00:00
)
2019-09-06 14:38:41 +00:00
+ ''
2022-09-30 15:02:24 +00:00
< < ' EOF'
2024-03-28 05:02:32 +00:00
$ { toJSON set }
2019-09-06 14:38:41 +00:00
EOF
2022-02-23 11:48:51 +00:00
( ( ! $ inherit_errexit_enabled ) ) && shopt - u inherit_errexit
2019-09-06 14:38:41 +00:00
'' ;
2024-12-10 19:26:33 +00:00
2022-04-11 08:51:54 +00:00
/*
Remove packages of packagesToRemove from packages , based on their names .
Relies on package names and has quadratic complexity so use with caution !
2024-12-10 19:26:33 +00:00
2022-04-11 08:51:54 +00:00
Type :
removePackagesByName : : [ package ] -> [ package ] -> [ package ]
2024-12-10 19:26:33 +00:00
2022-04-11 08:51:54 +00:00
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 08:51:54 +00:00
in
2024-03-28 05:02:32 +00:00
filter ( x : ! ( elem ( getName x ) namesToRemove ) ) packages ;
2022-04-11 08:51:54 +00:00
2024-12-01 17:27:19 +00:00
/*
Returns false if a package with the same name as the ` package ` is present in ` packagesToDisable ` .
2024-12-10 19:26:33 +00:00
2024-12-01 17:27:19 +00:00
Type :
disablePackageByName : : package -> [ package ] -> bool
2024-12-10 19:26:33 +00:00
2024-12-01 17:27:19 +00:00
Example :
disablePackageByName file-roller [ file-roller totem ]
= > false
2024-12-10 19:26:33 +00:00
2024-12-01 17:27:19 +00:00
Example :
disablePackageByName nautilus [ file-roller totem ]
= > true
* /
disablePackageByName =
package : packagesToDisable :
let
namesToDisable = map getName packagesToDisable ;
in
! elem ( getName package ) namesToDisable ;
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 18:47:28 +00:00
types = import ./systemd-types.nix { inherit lib systemdUtils pkgs ; } ;
2023-07-01 02:18:05 +00:00
network = {
units = import ./systemd-network-units.nix { inherit lib systemdUtils ; } ;
2024-12-10 19:26:33 +00:00
} ;
2023-07-01 02:18:05 +00:00
} ;
2021-11-20 17:34:13 +00:00
} ;
2024-03-21 13:52:12 +00:00
in
utils