nixos/activation: avoid build failure with large activation script

Two improvements:
* use `writeShellApplication` (which uses `passAsFile` instead of
  passing the activation script as an env var into the derivation.
  We disable shellcheck and the bash options that this builder
  usually adds to avoid issues with out-of-tree activation scripts.
* use `sed` instead of `substituteInPlace`, since the substitute
  functions load the file content into a shell variable

This avoids issues when the activation is too long to fit in a shell variable.
Before this commit, a very large activation script, would cause build
failures because of different limits on the file content size, e.g.
```
➜ nix build -f . nixosTests.restartByActivationScript.nodes.machine.system.build.toplevel -vL
this derivation will be built:
  /nix/store/xw6anpfjyamnycjg58cmj01sz3ididyl-nixos-system-machine-test.drv
building '/nix/store/xw6anpfjyamnycjg58cmj01sz3ididyl-nixos-system-machine-test.drv'...
nixos-system-machine-test> error: executing '/nix/store/rlq03x4cwf8zn73hxaxnx0zn5q9kifls-bash-5.3p3/bin/bash': Argument list too long
error: builder for '/nix/store/xw6anpfjyamnycjg58cmj01sz3ididyl-nixos-system-machine-test.drv' failed with exit code 1;
       last 1 log lines:
       > error: executing '/nix/store/rlq03x4cwf8zn73hxaxnx0zn5q9kifls-bash-5.3p3/bin/bash': Argument list too long
       For full logs, run:
        nix log /nix/store/xw6anpfjyamnycjg58cmj01sz3ididyl-nixos-system-machine-test.drv
```
This commit is contained in:
r-vdp 2025-12-26 18:30:49 +02:00
commit 9bfd0d688d
No known key found for this signature in database
3 changed files with 50 additions and 35 deletions

View file

@ -11,12 +11,6 @@ let
mkOption
types
;
systemBuilderArgs = {
activationScript = config.system.activationScripts.script;
dryActivationScript = config.system.dryActivationScript;
};
in
{
options = {
@ -52,36 +46,51 @@ in
'';
};
};
config = {
system.activatableSystemBuilderCommands = ''
echo "$activationScript" > $out/activate
echo "$dryActivationScript" > $out/dry-activate
substituteInPlace $out/activate --subst-var-by out ''${!toplevelVar}
substituteInPlace $out/dry-activate --subst-var-by out ''${!toplevelVar}
chmod u+x $out/activate $out/dry-activate
unset activationScript dryActivationScript
'';
config =
let
activationScript = lib.getExe (
pkgs.writeShellApplication {
name = "activate";
text = config.system.activationScripts.script;
checkPhase = "";
bashOptions = [ ];
}
);
dryActivationScript = lib.getExe (
pkgs.writeShellApplication {
name = "dry-activate";
text = config.system.dryActivationScript;
checkPhase = "";
bashOptions = [ ];
}
);
in
{
system.activatableSystemBuilderCommands =
# We use sed here instead of substitute(InPlace), because the substitute
# functions load the content of the file into a bash variable, which fails
# for very large activation scripts.
# bash
''
cp ${activationScript} $out/activate
cp ${dryActivationScript} $out/dry-activate
${lib.getExe pkgs.gnused} --in-place --expression "s|@out@|''${!toplevelVar}|g" $out/activate $out/dry-activate
'';
system.systemBuilderCommands = lib.mkIf config.system.activatable config.system.activatableSystemBuilderCommands;
system.systemBuilderArgs = lib.mkIf config.system.activatable (
systemBuilderArgs
// {
system.systemBuilderCommands = lib.mkIf config.system.activatable config.system.activatableSystemBuilderCommands;
system.systemBuilderArgs = lib.mkIf config.system.activatable {
toplevelVar = "out";
}
);
};
system.build.separateActivationScript =
pkgs.runCommand "separate-activation-script"
(
systemBuilderArgs
// {
system.build.separateActivationScript =
pkgs.runCommand "separate-activation-script"
{
toplevelVar = "toplevel";
toplevel = config.system.build.toplevel;
}
)
''
mkdir $out
${config.system.activatableSystemBuilderCommands}
'';
};
''
mkdir $out
${config.system.activatableSystemBuilderCommands}
'';
};
}

View file

@ -50,8 +50,6 @@ let
) withHeadlines;
in
''
#!${pkgs.runtimeShell}
source ${./lib/lib.sh}
systemConfig='@out@'

View file

@ -6,7 +6,7 @@
};
nodes.machine =
{ pkgs, ... }:
{ pkgs, lib, ... }:
{
imports = [ ../modules/profiles/minimal.nix ];
@ -46,6 +46,14 @@
fi
'';
};
# Make sure we don't crash on long activation scripts
specialisation.longscript.configuration = {
system.activationScripts.long = {
supportsDryActivation = true;
text = lib.concatStringsSep "\n" (lib.genList (i: ''# line number ${toString i}'') 1000000);
};
};
};
testScript = # python