Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Perlless Activation #270727

Merged
merged 12 commits into from
Jan 22, 2024
1 change: 1 addition & 0 deletions nixos/doc/manual/configuration/profiles.chapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ profiles/graphical.section.md
profiles/hardened.section.md
profiles/headless.section.md
profiles/installation-device.section.md
profiles/perlless.section.md
profiles/minimal.section.md
profiles/qemu-guest.section.md
```
11 changes: 11 additions & 0 deletions nixos/doc/manual/configuration/profiles/perlless.section.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Perlless {#sec-perlless}

::: {.warning}
If you enable this profile, you will NOT be able to switch to a new
nikstur marked this conversation as resolved.
Show resolved Hide resolved
configuration and thus you will not be able to rebuild your system with
nixos-rebuild!
:::

Render your system completely perlless (i.e. without the perl interpreter). This
includes a mechanism so that your build fails if it contains a Nix store path
that references the string "perl".
15 changes: 15 additions & 0 deletions nixos/doc/manual/configuration/user-mgmt.chapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,18 @@ A user can be deleted using `userdel`:
The flag `-r` deletes the user's home directory. Accounts can be
modified using `usermod`. Unix groups can be managed using `groupadd`,
`groupmod` and `groupdel`.

## Create users and groups with `systemd-sysusers` {#sec-systemd-sysusers}

::: {.note}
This is experimental.
:::

Instead of using a custom perl script to create users and groups, you can use
systemd-sysusers:

```nix
systemd.sysusers.enable = true;
```

The primary benefit of this is to remove a dependency on perl.
36 changes: 36 additions & 0 deletions nixos/doc/manual/development/etc-overlay.section.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# `/etc` via overlay filesystem {#sec-etc-overlay}

::: {.note}
This is experimental and requires a kernel version >= 6.6 because it uses
new overlay features and relies on the new mount API.
:::

Instead of using a custom perl script to activate `/etc`, you activate it via an
overlay filesystem:

```nix
system.etc.overlay.enable = true;
```

Using an overlay has two benefits:

1. it removes a dependency on perl
2. it makes activation faster (up to a few seconds)

By default, the `/etc` overlay is mounted writable (i.e. there is a writable
upper layer). However, you can also mount `/etc` immutably (i.e. read-only) by
setting:

```nix
system.etc.overlay.mutable = false;
```

The overlay is atomically replaced during system switch. However, files that
have been modified will NOT be overwritten. This is the biggest change compared
to the perl-based system.
nikstur marked this conversation as resolved.
Show resolved Hide resolved

If you manually make changes to `/etc` on your system and then switch to a new
configuration where `system.etc.overlay.mutable = false;`, you will not be able
to see the previously made changes in `/etc` anymore. However the changes are
not completely gone, they are still in the upperdir of the previous overlay in
`/.rw-etc/upper`.
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,5 @@ explained in the next sections.
unit-handling.section.md
activation-script.section.md
non-switchable-systems.section.md
etc-overlay.section.md
```
16 changes: 16 additions & 0 deletions nixos/doc/manual/release-notes/rl-2405.section.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,22 @@ In addition to numerous new and upgraded packages, this release has the followin

- Julia environments can now be built with arbitrary packages from the ecosystem using the `.withPackages` function. For example: `julia.withPackages ["Plots"]`.

- A new option `systemd.sysusers.enable` was added. If enabled, users and
groups are created with systemd-sysusers instead of with a custom perl script.

- A new option `system.etc.overlay.enable` was added. If enabled, `/etc` is
mounted via an overlayfs instead of being created by a custom perl script.

- It is now possible to have a completely perlless system (i.e. a system
without perl). Previously, the NixOS activation depended on two perl scripts
which can now be replaced via an opt-in mechanism. To make your system
perlless, you can use the new perlless profile:
```
{ modulesPath, ... }: {
imports = [ "${modulesPath}/profiles/perlless.nix" ];
}
```

## New Services {#sec-release-24.05-new-services}

<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
Expand Down
10 changes: 6 additions & 4 deletions nixos/modules/config/users-groups.nix
Original file line number Diff line number Diff line change
Expand Up @@ -685,7 +685,7 @@ in {
shadow.gid = ids.gids.shadow;
};

system.activationScripts.users = {
system.activationScripts.users = if !config.systemd.sysusers.enable then {
nikstur marked this conversation as resolved.
Show resolved Hide resolved
supportsDryActivation = true;
text = ''
install -m 0700 -d /root
Expand All @@ -694,7 +694,7 @@ in {
${pkgs.perl.withPackages (p: [ p.FileSlurp p.JSON ])}/bin/perl \
-w ${./update-users-groups.pl} ${spec}
'';
};
} else ""; # keep around for backwards compatibility

system.activationScripts.update-lingering = let
lingerDir = "/var/lib/systemd/linger";
Expand All @@ -711,7 +711,9 @@ in {
'';

# Warn about user accounts with deprecated password hashing schemes
system.activationScripts.hashes = {
# This does not work when the users and groups are created by
# systemd-sysusers because the users are created too late then.
system.activationScripts.hashes = if !config.systemd.sysusers.enable then {
deps = [ "users" ];
text = ''
users=()
Expand All @@ -729,7 +731,7 @@ in {
printf ' - %s\n' "''${users[@]}"
fi
'';
};
} else ""; # keep around for backwards compatibility

# for backwards compatibility
system.activationScripts.groups = stringAfter [ "users" ] "";
Expand Down
1 change: 1 addition & 0 deletions nixos/modules/module-list.nix
Original file line number Diff line number Diff line change
Expand Up @@ -1486,6 +1486,7 @@
./system/boot/systemd/repart.nix
./system/boot/systemd/shutdown.nix
./system/boot/systemd/sysupdate.nix
./system/boot/systemd/sysusers.nix
./system/boot/systemd/tmpfiles.nix
./system/boot/systemd/user.nix
./system/boot/systemd/userdbd.nix
Expand Down
31 changes: 31 additions & 0 deletions nixos/modules/profiles/perlless.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# WARNING: If you enable this profile, you will NOT be able to switch to a new
# configuration and thus you will not be able to rebuild your system with
# nixos-rebuild!

{ lib, ... }:

{

# Disable switching to a new configuration. This is not a necessary
nikstur marked this conversation as resolved.
Show resolved Hide resolved
# limitation of a perlless system but just a current one. In the future,
# perlless switching might be possible.
system.switch.enable = lib.mkDefault false;

# Remove perl from activation
boot.initrd.systemd.enable = lib.mkDefault true;
system.etc.overlay.enable = lib.mkDefault true;
systemd.sysusers.enable = lib.mkDefault true;

# Random perl remnants
system.disableInstallerTools = lib.mkDefault true;
programs.less.lessopen = lib.mkDefault null;
programs.command-not-found.enable = lib.mkDefault false;
boot.enableContainers = lib.mkDefault false;
environment.defaultPackages = lib.mkDefault [ ];
documentation.info.enable = lib.mkDefault false;

# Check that the system does not contain a Nix store path that contains the
# string "perl".
system.forbiddenDependenciesRegex = "perl";

}
1 change: 1 addition & 0 deletions nixos/modules/services/system/dbus.nix
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ in
uid = config.ids.uids.messagebus;
description = "D-Bus system message bus daemon user";
home = homeDir;
homeMode = "0755";
group = "messagebus";
};

Expand Down
169 changes: 169 additions & 0 deletions nixos/modules/system/boot/systemd/sysusers.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
{ config, lib, pkgs, utils, ... }:

let

cfg = config.systemd.sysusers;
userCfg = config.users;

sysusersConfig = pkgs.writeTextDir "00-nixos.conf" ''
# Type Name ID GECOS Home directory Shell

# Users
${lib.concatLines (lib.mapAttrsToList
(username: opts:
let
uid = if opts.uid == null then "-" else toString opts.uid;
in
''u ${username} ${uid}:${opts.group} "${opts.description}" ${opts.home} ${utils.toShellPath opts.shell}''
)
userCfg.users)
}

# Groups
${lib.concatLines (lib.mapAttrsToList
(groupname: opts: ''g ${groupname} ${if opts.gid == null then "-" else toString opts.gid}'') userCfg.groups)
}

# Group membership
${lib.concatStrings (lib.mapAttrsToList
(groupname: opts: (lib.concatMapStrings (username: "m ${username} ${groupname}\n")) opts.members ) userCfg.groups)
}
'';

staticSysusersCredentials = pkgs.runCommand "static-sysusers-credentials" { } ''
mkdir $out; cd $out
${lib.concatLines (
(lib.mapAttrsToList
(username: opts: "echo -n '${opts.initialHashedPassword}' > 'passwd.hashed-password.${username}'")
(lib.filterAttrs (_username: opts: opts.initialHashedPassword != null) userCfg.users))
++
(lib.mapAttrsToList
(username: opts: "echo -n '${opts.initialPassword}' > 'passwd.plaintext-password.${username}'")
(lib.filterAttrs (_username: opts: opts.initialPassword != null) userCfg.users))
++
(lib.mapAttrsToList
(username: opts: "cat '${opts.hashedPasswordFile}' > 'passwd.hashed-password.${username}'")
(lib.filterAttrs (_username: opts: opts.hashedPasswordFile != null) userCfg.users))
)
}
'';

staticSysusers = pkgs.runCommand "static-sysusers"
{
nativeBuildInputs = [ pkgs.systemd ];
} ''
mkdir $out
export CREDENTIALS_DIRECTORY=${staticSysusersCredentials}
systemd-sysusers --root $out ${sysusersConfig}/00-nixos.conf
'';

in

{

options = {

# This module doesn't set it's own user options but reuses the ones from
# users-groups.nix

systemd.sysusers = {
enable = lib.mkEnableOption (lib.mdDoc "systemd-sysusers") // {
description = lib.mdDoc ''
If enabled, users are created with systemd-sysusers instead of with
the custom `update-users-groups.pl` script.

Note: This is experimental.
'';
};
};

};

config = lib.mkIf cfg.enable {

assertions = [
{
assertion = config.system.activationScripts.users == "";
message = "system.activationScripts.users has to be empty to use systemd-sysusers";
}
{
assertion = config.users.mutableUsers -> config.system.etc.overlay.enable;
message = "config.users.mutableUsers requires config.system.etc.overlay.enable.";
}
];

systemd = lib.mkMerge [
({

# Create home directories, do not create /var/empty even if that's a user's
# home.
tmpfiles.settings.home-directories = lib.mapAttrs'
(username: opts: lib.nameValuePair opts.home {
d = {
mode = opts.homeMode;
user = username;
group = opts.group;
};
})
(lib.filterAttrs (_username: opts: opts.home != "/var/empty") userCfg.users);
})

(lib.mkIf config.users.mutableUsers {
additionalUpstreamSystemUnits = [
"systemd-sysusers.service"
];

services.systemd-sysusers = {
# Enable switch-to-configuration to restart the service.
unitConfig.ConditionNeedsUpdate = [ "" ];
requiredBy = [ "sysinit-reactivation.target" ];
before = [ "sysinit-reactivation.target" ];
restartTriggers = [ "${config.environment.etc."sysusers.d".source}" ];

serviceConfig = {
LoadCredential = lib.mapAttrsToList
(username: opts: "passwd.hashed-password.${username}:${opts.hashedPasswordFile}")
(lib.filterAttrs (_username: opts: opts.hashedPasswordFile != null) userCfg.users);
SetCredential = (lib.mapAttrsToList
(username: opts: "passwd.hashed-password.${username}:${opts.initialHashedPassword}")
(lib.filterAttrs (_username: opts: opts.initialHashedPassword != null) userCfg.users))
++
(lib.mapAttrsToList
(username: opts: "passwd.plaintext-password.${username}:${opts.initialPassword}")
(lib.filterAttrs (_username: opts: opts.initialPassword != null) userCfg.users))
;
};
};
})
];

environment.etc = lib.mkMerge [
(lib.mkIf (!userCfg.mutableUsers) {
"passwd" = {
source = "${staticSysusers}/etc/passwd";
mode = "0644";
};
"group" = {
source = "${staticSysusers}/etc/group";
mode = "0644";
};
"shadow" = {
source = "${staticSysusers}/etc/shadow";
mode = "0000";
};
"gshadow" = {
source = "${staticSysusers}/etc/gshadow";
mode = "0000";
};
})

(lib.mkIf userCfg.mutableUsers {
"sysusers.d".source = sysusersConfig;
})
];

};

meta.maintainers = with lib.maintainers; [ nikstur ];

}
Loading