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

Support arbitrary keys in system.activationScripts #664

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion modules/fonts/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,6 @@ in
done
fi
'';

system.activationScripts.fonts.onlyOnRebuild = true;
};
}
2 changes: 1 addition & 1 deletion modules/homebrew.nix
Original file line number Diff line number Diff line change
Expand Up @@ -768,7 +768,7 @@ in

environment.variables = mkIf cfg.enable cfg.global.homebrewEnvironmentVariables;

system.activationScripts.homebrew.text = mkIf cfg.enable ''
system.userActivationScripts.homebrew.text = mkIf cfg.enable ''
# Homebrew Bundle
echo >&2 "Homebrew bundle..."
if [ -f "${cfg.brewPrefix}/brew" ]; then
Expand Down
1 change: 1 addition & 0 deletions modules/networking/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ in

${setNetworkServices}
'';
system.activationScripts.networking.onlyOnRebuild = true;

};
}
7 changes: 7 additions & 0 deletions modules/nix/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -782,13 +782,20 @@ in
system.activationScripts.nix-daemon.text = mkIf cfg.useDaemon ''
if ! diff /etc/nix/nix.conf /run/current-system/etc/nix/nix.conf &> /dev/null; then
echo "reloading nix-daemon..." >&2
pid=$(launchctl kickstart -p system/org.nixos.nix-daemon)
launchctl kill HUP system/org.nixos.nix-daemon
while [ "$(launchctl kickstart -p system/org.nixos.nix-daemon)" = "$pid" ]; do
sleep 0.1
done
fi
while ! nix-store --store daemon -q --hash ${pkgs.stdenv.shell} &>/dev/null; do
echo "waiting for nix-daemon" >&2
launchctl kickstart system/org.nixos.nix-daemon
done
'';
system.activationScripts.nix-daemon.onlyOnRebuild = true;
# Make sure the new config file and launchd service have been installed first.
system.activationScripts.nix-daemon.deps = [ "etc" "launchd" ];

# Legacy configuration conversion.
nix.settings = mkMerge [
Expand Down
1 change: 1 addition & 0 deletions modules/security/pam.nix
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,6 @@ in
echo >&2 "setting up pam..."
${mkSudoTouchIdAuthScript cfg.enableSudoTouchIdAuth}
'';
system.activationScripts.pam.onlyOnRebuild = true;
};
}
20 changes: 1 addition & 19 deletions modules/services/activate-system/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,7 @@ in
config = mkIf cfg.enable {

launchd.daemons.activate-system = {
script = ''
set -e
set -o pipefail
export PATH="${pkgs.gnugrep}/bin:${pkgs.coreutils}/bin:@out@/sw/bin:/usr/bin:/bin:/usr/sbin:/sbin"

systemConfig=$(cat ${config.system.profile}/systemConfig)

# Make this configuration the current configuration.
# The readlink is there to ensure that when $systemConfig = /system
# (which is a symlink to the store), /run/current-system is still
# used as a garbage collection root.
ln -sfn $(cat ${config.system.profile}/systemConfig) /run/current-system

# Prevent the current configuration from being garbage-collected.
ln -sfn /run/current-system /nix/var/nix/gcroots/current-system

${config.system.activationScripts.etc.text}
${config.system.activationScripts.keyboard.text}
'';
script = config.system.activationScripts.startupScript;
serviceConfig.RunAtLoad = true;
serviceConfig.KeepAlive.SuccessfulExit = false;
};
Expand Down
251 changes: 178 additions & 73 deletions modules/system/activation-scripts.nix
Original file line number Diff line number Diff line change
Expand Up @@ -8,119 +8,224 @@ let

cfg = config.system;

script = import ../lib/write-text.nix {
inherit lib;
mkTextDerivation = name: text: pkgs.writeScript "activate-${name}" text;
};

in

{
options = {

system.activationScripts = mkOption {
internal = true;
type = types.attrsOf (types.submodule script);
default = {};
description = ''
A set of shell script fragments that are executed when a NixOS
system configuration is activated. Examples are updating
/etc, creating accounts, and so on. Since these are executed
every time you boot the system or run
<command>nixos-rebuild</command>, it's important that they are
idempotent and fast.
'';
};

};

config = {

system.activationScripts.script.text = ''
#! ${stdenv.shell}
set -e
set -o pipefail
export PATH="${pkgs.gnugrep}/bin:${pkgs.coreutils}/bin:@out@/sw/bin:/usr/bin:/bin:/usr/sbin:/sbin"

systemConfig=@out@
addAttributeName = mapAttrs (a: v: v // optionalAttrs (v.text != "") {
text = ''
#### Activation script snippet ${a}:
_localstatus=0
${v.text}

if (( _localstatus > 0 )); then
printf "Activation script snippet '%s' failed (%s)\n" "${a}" "$_localstatus"
fi
'';
});

path = with pkgs; map getBin
[ gnugrep
coreutils
];

massageOrder = set: let
names = attrNames (removeAttrs set [ "postActivation" ]);
in mapAttrs (k: v: v // {
deps =
v.deps ++
(if k == "postActivation" then names
else optional (k != "preActivation" && set ? preActivation) "preActivation"
);
} ) set;

activationScriptBody = set: let
set' = mapAttrs (_: v: if isString v then (noDepEntry v) else v) set;
withHeadlines = addAttributeName (massageOrder set');
in
textClosureMap id (withHeadlines) (attrNames withHeadlines);

activationScript = set: system:
''
#!${pkgs.runtimeShell}

systemConfig='@out@'

export PATH=/empty
for i in ${toString path}; do
PATH=$PATH:$i/bin:$i/sbin
done

PATH=$PATH:@out@/sw/bin:/usr/bin:/bin:/usr/sbin:/sbin

_status=0
trap "_status=1" ERR
trap "_status=1 _localstatus=\$?" ERR

# Ensure a consistent umask.
umask 0022

${cfg.activationScripts.preActivation.text}

${cfg.activationScripts.extraActivation.text}
${cfg.activationScripts.groups.text}
${cfg.activationScripts.users.text}
${cfg.activationScripts.applications.text}
${cfg.activationScripts.pam.text}
${cfg.activationScripts.patches.text}
${cfg.activationScripts.etc.text}
${cfg.activationScripts.defaults.text}
${cfg.activationScripts.launchd.text}
${cfg.activationScripts.nix-daemon.text}
${cfg.activationScripts.time.text}
${cfg.activationScripts.networking.text}
${cfg.activationScripts.keyboard.text}
${cfg.activationScripts.fonts.text}

${cfg.activationScripts.postActivation.text}

'' + optionalString (system) ''
# Ensure /run exists.
if [ ! -e /run ]; then
ln -sfn private/var/run /run
fi

'' + ''
${activationScriptBody set}
'' + optionalString (system) ''
# Make this configuration the current configuration.
# The readlink is there to ensure that when $systemConfig = /system
# (which is a symlink to the store), /run/current-system is still
# used as a garbage collection root.
ln -sfn "$(readlink -f "$systemConfig")" /run/current-system

# Prevent the current configuration from being garbage-collected.
mkdir -p /nix/var/nix/gcroots
ln -sfn /run/current-system /nix/var/nix/gcroots/current-system

'' + ''
exit $_status
'';

system.activationScripts.userScript.text = ''
#! ${stdenv.shell}
set -e
set -o pipefail
export PATH="${pkgs.gnugrep}/bin:${pkgs.coreutils}/bin:@out@/sw/bin:/usr/bin:/bin"
startupScript = set: let
set' = filterAttrs (_: v: isString v || (!v.onlyOnRebuild)) set;
in ''
systemConfig=$(cat ${config.system.profile}/systemConfig)

export PATH=/empty
for i in ${toString path}; do
PATH=$PATH:$i/bin:$i/sbin
done

systemConfig=@out@
PATH=$PATH:$systemConfig/sw/bin:/usr/bin:/bin:/usr/sbin:/sbin

_status=0
trap "_status=1" ERR
trap "_status=1 _localstatus=\$?" ERR

# Ensure a consistent umask.
umask 0022

${cfg.activationScripts.preUserActivation.text}
# Make this configuration the current configuration.
# The readlink is there to ensure that when $systemConfig = /system
# (which is a symlink to the store), /run/current-system is still
# used as a garbage collection root.
ln -sfn $(cat ${config.system.profile}/systemConfig) /run/current-system

${cfg.activationScripts.checks.text}
${cfg.activationScripts.extraUserActivation.text}
${cfg.activationScripts.userDefaults.text}
${cfg.activationScripts.userLaunchd.text}
${cfg.activationScripts.homebrew.text}
# Prevent the current configuration from being garbage-collected.
ln -sfn /run/current-system /nix/var/nix/gcroots/current-system

${cfg.activationScripts.postUserActivation.text}
${activationScriptBody set'}

exit $_status
'';

scriptType = withOnlyOnRebuild:
let scriptOptions =
{ deps = mkOption
{ type = types.listOf types.str;
default = [ ];
description = "List of dependencies. The script will run after these.";
};
text = mkOption
{ type = types.lines;
default = "";
description = "The content of the script.";
};
} // optionalAttrs withOnlyOnRebuild {
onlyOnRebuild = mkOption
{ type = types.bool;
default = false;
description = ''
Whether this activation script should only be run as part of
<command>darwin-rebuild</command>. By default, activation scripts
are run at activation time and on every boot.
'';
};
};
in with types; either str (submodule { options = scriptOptions; });


script = import ../lib/write-text.nix {
inherit lib;
mkTextDerivation = name: text: pkgs.writeScript "activate-${name}" text;
};

in

{
options = {

system.activationScripts = mkOption {
type = types.attrsOf (scriptType true);
default = {};
description = ''
A set of shell script fragments that are executed when a nix-darwin
system configuration is activated. Examples are updating
/etc, creating accounts, and so on. Since these are executed
every time you boot the system or run
<command>darwin-rebuild</command>, it's important that they are
idempotent and fast.
'';
apply = set: let
set' = removeAttrs set [
# Skip over renamed activation scripts
"extraUserActivation"
"preUserActivation"
"postUserActivation"
];
in set // {
script = activationScript set' true;
startupScript = startupScript set';
};
};

system.userActivationScripts = mkOption {
default = {};

example = literalExpression ''
{ plasmaSetup = {
text = '''
''${pkgs.libsForQt5.kservice}/bin/kbuildsycoca5"
''';
deps = [];
};
}
'';

description = ''
A set of shell script fragments that are executed by a launchd launch
agent when a nix-darwin system configuration is activated. Examples are
rebuilding the .desktop file cache for showing applications in the menu.
Since these are executed every time you run
<command>darwin-rebuild</command>, it's important that they are
idempotent and fast.
'';

type = with types; attrsOf (scriptType false);

apply = set: set // {
script = activationScript set false;
};
};
};

config = {
# Extra activation scripts, that can be customized by users
# don't use this unless you know what you are doing.
# It's better to define new named activation scripts with lib.stringAfter
# specifying the exact ordering constraint.
system.activationScripts.extraActivation.text = mkDefault "";
system.activationScripts.preActivation.text = mkDefault "";
system.activationScripts.postActivation.text = mkDefault "";

# Preserve existing behavior
system.activationScripts.extraActivation.onlyOnRebuild = true;
system.activationScripts.preActivation.onlyOnRebuild = true;
system.activationScripts.postActivation.onlyOnRebuild = true;

# Support legacy *UserActivation keys.
system.activationScripts.extraUserActivation.text = mkDefault "";
system.activationScripts.preUserActivation.text = mkDefault "";
system.activationScripts.postUserActivation.text = mkDefault "";

system.userActivationScripts.extraActivation = mkDefault config.system.activationScripts.extraUserActivation.text;
system.userActivationScripts.preActivation.text = mkDefault config.system.activationScripts.preUserActivation.text;
system.userActivationScripts.postActivation = mkDefault config.system.activationScripts.postUserActivation.text;
# Manually generate warnings because mkRenamedOptionModule can't be used with attrset keys.
warnings = map (name:
mkIf (config.system.activationScripts.${name + "UserActivation"}.text != "") "Obsolete option `system.activationScripts.${name}UserActivation` is used. It was renamed to `system.userActivationScripts.${name}Activation`."
) [ "extra" "pre" "post" ];
};
}
1 change: 1 addition & 0 deletions modules/system/applications.nix
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ in
echo "warning: /Applications/Nix Apps is not owned by nix-darwin, skipping App linking..." >&2
fi
'';
system.activationScripts.applications.onlyOnRebuild = true;

};
}
2 changes: 1 addition & 1 deletion modules/system/checks.nix
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ in
(mkIf cfg.verifyNixPath nixPath)
];

system.activationScripts.checks.text = ''
system.userActivationScripts.checks.text = ''
${cfg.text}

if test ''${checkActivation:-0} -eq 1; then
Expand Down
Loading
Loading