0
0
Fork 0
mirror of https://github.com/NixOS/nixpkgs.git synced 2025-07-19 00:20:32 +03:00
nixpkgs/nixos/modules/system/etc/etc.nix
Silvan Mosberger d9d87c5196 treewide: format all inactive Nix files
After final improvements to the official formatter implementation,
this commit now performs the first treewide reformat of Nix files using it.
This is part of the implementation of RFC 166.

Only "inactive" files are reformatted, meaning only files that
aren't being touched by any PR with activity in the past 2 months.
This is to avoid conflicts for PRs that might soon be merged.
Later we can do a full treewide reformat to get the rest,
which should not cause as many conflicts.

A CI check has already been running for some time to ensure that new and
already-formatted files are formatted, so the files being reformatted here
should also stay formatted.

This commit was automatically created and can be verified using

    nix-build https://github.com/infinisil/treewide-nixpkgs-reformat-script/archive/a08b3a4d199c6124ac5b36a889d9099b4383463f.tar.gz \
      --argstr baseRev 0128fbb0a5
    result/bin/apply-formatting $NIXPKGS_PATH
2024-12-10 20:29:24 +01:00

381 lines
12 KiB
Nix

# Management of static files in /etc.
{
config,
lib,
pkgs,
...
}:
let
etc' = lib.filter (f: f.enable) (lib.attrValues config.environment.etc);
etc =
pkgs.runCommandLocal "etc"
{
# This is needed for the systemd module
passthru.targets = map (x: x.target) etc';
} # sh
''
set -euo pipefail
makeEtcEntry() {
src="$1"
target="$2"
mode="$3"
user="$4"
group="$5"
if [[ "$src" = *'*'* ]]; then
# If the source name contains '*', perform globbing.
mkdir -p "$out/etc/$target"
for fn in $src; do
ln -s "$fn" "$out/etc/$target/"
done
else
mkdir -p "$out/etc/$(dirname "$target")"
if ! [ -e "$out/etc/$target" ]; then
ln -s "$src" "$out/etc/$target"
else
echo "duplicate entry $target -> $src"
if [ "$(readlink "$out/etc/$target")" != "$src" ]; then
echo "mismatched duplicate entry $(readlink "$out/etc/$target") <-> $src"
ret=1
continue
fi
fi
if [ "$mode" != symlink ]; then
echo "$mode" > "$out/etc/$target.mode"
echo "$user" > "$out/etc/$target.uid"
echo "$group" > "$out/etc/$target.gid"
fi
fi
}
mkdir -p "$out/etc"
${lib.concatMapStringsSep "\n" (
etcEntry:
lib.escapeShellArgs [
"makeEtcEntry"
# Force local source paths to be added to the store
"${etcEntry.source}"
etcEntry.target
etcEntry.mode
etcEntry.user
etcEntry.group
]
) etc'}
'';
etcHardlinks = lib.filter (f: f.mode != "symlink" && f.mode != "direct-symlink") etc';
in
{
imports = [ ../build.nix ];
###### interface
options = {
system.etc.overlay = {
enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Mount `/etc` as an overlayfs instead of generating it via a perl script.
Note: This is currently experimental. Only enable this option if you're
confident that you can recover your system if it breaks.
'';
};
mutable = lib.mkOption {
type = lib.types.bool;
default = true;
description = ''
Whether to mount `/etc` mutably (i.e. read-write) or immutably (i.e. read-only).
If this is false, only the immutable lowerdir is mounted. If it is
true, a writable upperdir is mounted on top.
'';
};
};
environment.etc = lib.mkOption {
default = { };
example = lib.literalExpression ''
{ example-configuration-file =
{ source = "/nix/store/.../etc/dir/file.conf.example";
mode = "0440";
};
"default/useradd".text = "GROUP=100 ...";
}
'';
description = ''
Set of files that have to be linked in {file}`/etc`.
'';
type =
with lib.types;
attrsOf (
submodule (
{
name,
config,
options,
...
}:
{
options = {
enable = lib.mkOption {
type = lib.types.bool;
default = true;
description = ''
Whether this /etc file should be generated. This
option allows specific /etc files to be disabled.
'';
};
target = lib.mkOption {
type = lib.types.str;
description = ''
Name of symlink (relative to
{file}`/etc`). Defaults to the attribute
name.
'';
};
text = lib.mkOption {
default = null;
type = lib.types.nullOr lib.types.lines;
description = "Text of the file.";
};
source = lib.mkOption {
type = lib.types.path;
description = "Path of the source file.";
};
mode = lib.mkOption {
type = lib.types.str;
default = "symlink";
example = "0600";
description = ''
If set to something else than `symlink`,
the file is copied instead of symlinked, with the given
file mode.
'';
};
uid = lib.mkOption {
default = 0;
type = lib.types.int;
description = ''
UID of created file. Only takes effect when the file is
copied (that is, the mode is not 'symlink').
'';
};
gid = lib.mkOption {
default = 0;
type = lib.types.int;
description = ''
GID of created file. Only takes effect when the file is
copied (that is, the mode is not 'symlink').
'';
};
user = lib.mkOption {
default = "+${toString config.uid}";
type = lib.types.str;
description = ''
User name of created file.
Only takes effect when the file is copied (that is, the mode is not 'symlink').
Changing this option takes precedence over `uid`.
'';
};
group = lib.mkOption {
default = "+${toString config.gid}";
type = lib.types.str;
description = ''
Group name of created file.
Only takes effect when the file is copied (that is, the mode is not 'symlink').
Changing this option takes precedence over `gid`.
'';
};
};
config = {
target = lib.mkDefault name;
source = lib.mkIf (config.text != null) (
let
name' = "etc-" + lib.replaceStrings [ "/" ] [ "-" ] name;
in
lib.mkDerivedConfig options.text (pkgs.writeText name')
);
};
}
)
);
};
};
###### implementation
config = {
system.build.etc = etc;
system.build.etcActivationCommands =
let
etcOverlayOptions = lib.concatStringsSep "," (
[
"relatime"
"redirect_dir=on"
"metacopy=on"
]
++ lib.optionals config.system.etc.overlay.mutable [
"upperdir=/.rw-etc/upper"
"workdir=/.rw-etc/work"
]
);
in
if config.system.etc.overlay.enable then
''
# This script atomically remounts /etc when switching configuration. On a (re-)boot
# this should not run because /etc is mounted via a systemd mount unit
# instead. To a large extent this mimics what composefs does. Because
# it's relatively simple, however, we avoid the composefs dependency.
# Since this script is not idempotent, it should not run when etc hasn't
# changed.
if [[ ! $IN_NIXOS_SYSTEMD_STAGE1 ]] && [[ "${config.system.build.etc}/etc" != "$(readlink -f /run/current-system/etc)" ]]; then
echo "remounting /etc..."
tmpMetadataMount=$(mktemp --directory -t nixos-etc-metadata.XXXXXXXXXX)
mount --type erofs ${config.system.build.etcMetadataImage} $tmpMetadataMount
# Mount the new /etc overlay to a temporary private mount.
# This needs the indirection via a private bind mount because you
# cannot move shared mounts.
tmpEtcMount=$(mktemp --directory -t nixos-etc.XXXXXXXXXX)
mount --bind --make-private $tmpEtcMount $tmpEtcMount
mount --type overlay overlay \
--options lowerdir=$tmpMetadataMount::${config.system.build.etcBasedir},${etcOverlayOptions} \
$tmpEtcMount
# Before moving the new /etc overlay under the old /etc, we have to
# move mounts on top of /etc to the new /etc mountpoint.
findmnt /etc --submounts --list --noheading --kernel --output TARGET | while read -r mountPoint; do
if [[ "$mountPoint" = "/etc" ]]; then
continue
fi
tmpMountPoint="$tmpEtcMount/''${mountPoint:5}"
${
if config.system.etc.overlay.mutable then
''
if [[ -f "$mountPoint" ]]; then
touch "$tmpMountPoint"
elif [[ -d "$mountPoint" ]]; then
mkdir -p "$tmpMountPoint"
fi
''
else
''
if [[ ! -e "$tmpMountPoint" ]]; then
echo "Skipping undeclared mountpoint in environment.etc: $mountPoint"
continue
fi
''
}
mount --bind "$mountPoint" "$tmpMountPoint"
done
# Move the new temporary /etc mount underneath the current /etc mount.
#
# This should eventually use util-linux to perform this move beneath,
# however, this functionality is not yet in util-linux. See this
# tracking issue: https://github.com/util-linux/util-linux/issues/2604
${pkgs.move-mount-beneath}/bin/move-mount --move --beneath $tmpEtcMount /etc
# Unmount the top /etc mount to atomically reveal the new mount.
umount --lazy --recursive /etc
# Unmount the temporary mount
umount --lazy "$tmpEtcMount"
rmdir "$tmpEtcMount"
# Unmount old metadata mounts
# For some reason, `findmnt /tmp --submounts` does not show the nested
# mounts. So we'll just find all mounts of type erofs and filter on the
# name of the mountpoint.
findmnt --type erofs --list --kernel --output TARGET | while read -r mountPoint; do
if [[ "$mountPoint" =~ ^/tmp/nixos-etc-metadata\..{10}$ &&
"$mountPoint" != "$tmpMetadataMount" ]]; then
umount --lazy $mountPoint
rmdir "$mountPoint"
fi
done
fi
''
else
''
# Set up the statically computed bits of /etc.
echo "setting up /etc..."
${pkgs.perl.withPackages (p: [ p.FileSlurp ])}/bin/perl ${./setup-etc.pl} ${etc}/etc
'';
system.build.etcBasedir = pkgs.runCommandLocal "etc-lowerdir" { } ''
set -euo pipefail
makeEtcEntry() {
src="$1"
target="$2"
mkdir -p "$out/$(dirname "$target")"
cp "$src" "$out/$target"
}
mkdir -p "$out"
${lib.concatMapStringsSep "\n" (
etcEntry:
lib.escapeShellArgs [
"makeEtcEntry"
# Force local source paths to be added to the store
"${etcEntry.source}"
etcEntry.target
]
) etcHardlinks}
'';
system.build.etcMetadataImage =
let
etcJson = pkgs.writeText "etc-json" (builtins.toJSON etc');
etcDump = pkgs.runCommand "etc-dump" { } ''
${lib.getExe pkgs.buildPackages.python3} ${./build-composefs-dump.py} ${etcJson} > $out
'';
in
pkgs.runCommand "etc-metadata.erofs"
{
nativeBuildInputs = with pkgs.buildPackages; [
composefs
erofs-utils
];
}
''
mkcomposefs --from-file ${etcDump} $out
fsck.erofs $out
'';
};
}