Loading nixos/modules/system/activation/apply/apply.sh 0 → 100644 +146 −0 Original line number Diff line number Diff line #!@bash@ # This is the NixOS apply script, typically located at # # ${config.system.build.toplevel}/bin/apply # # This script is responsible for managing the profile link and calling the # appropriate scripts for its subcommands, such as switch, boot, and test. set -euo pipefail toplevel=@toplevel@ subcommand= installBootloader= specialisation= profile=/nix/var/nix/profiles/system log() { echo "$@" >&2 } die() { log "NixOS apply error: $*" exit 1 } parse_args() { while [[ $# -gt 0 ]]; do case "$1" in switch|boot|test|dry-activate) subcommand="$1" ;; --install-bootloader) installBootloader=1 ;; --profile) if [[ $# -lt 2 ]]; then die "missing argument for --profile" fi profile="$2" shift ;; # --rollback is not an `apply` responsibility, and it should be # implemented by the caller of `apply` instead. --specialisation) if [[ $# -lt 2 ]]; then die "missing argument for --specialisation" fi specialisation="$2" shift ;; *) if [[ -n "$subcommand" ]]; then die "unexpected argument or flag: $1" else die "unexpected subcommand or flag: $1" fi ;; esac shift done if [ -z "$subcommand" ]; then die "no subcommand specified" fi } main() { local cmd activity case "$subcommand" in boot|switch) nix-env -p "$profile" --set "$toplevel" ;; esac # Using systemd-run here to protect against PTY failures/network # disconnections during rebuild. # See: https://github.com/NixOS/nixpkgs/issues/39118 cmd=( "systemd-run" "-E" "LOCALE_ARCHIVE" # Will be set to new value early in switch-to-configuration script, but interpreter starts out with old value "-E" "NIXOS_INSTALL_BOOTLOADER=$installBootloader" "--collect" "--no-ask-password" "--pipe" "--quiet" "--same-dir" "--service-type=exec" "--unit=nixos-rebuild-switch-to-configuration" "--wait" ) # Check if we have a working systemd-run. In chroot environments we may have # a non-working systemd, so we fallback to not using systemd-run. if ! "${cmd[@]}" true; then log "Skipping systemd-run to switch configuration since it is not working in target host." cmd=( "env" "-i" "LOCALE_ARCHIVE=${LOCALE_ARCHIVE:-}" "NIXOS_INSTALL_BOOTLOADER=$installBootloader" ) fi if [[ -z "$specialisation" ]]; then cmd+=("$toplevel/bin/switch-to-configuration") else cmd+=("$toplevel/specialisation/$specialisation/bin/switch-to-configuration") if ! [[ -f "${cmd[-1]}" ]]; then log "error: specialisation not found: $specialisation" exit 1 fi fi if ! "${cmd[@]}" "$subcommand"; then case "$subcommand" in switch) activity="switching to the new configuration" ;; boot) activity="switching the boot entry to the new configuration" ;; test) activity="switching to the new configuration (in test mode)" ;; dry-activate) activity="switching to the new configuration (in dry-activate mode)" ;; *) # Should never happen activity="running $subcommand" ;; esac log "warning: error(s) occurred while $activity" exit 1 fi } if ! type test_run_tests &>/dev/null; then # We're not loaded into the test.sh, so we run main. parse_args "$@" main fi nixos/modules/system/activation/apply/checks.nix 0 → 100644 +51 −0 Original line number Diff line number Diff line # Run: # nix-build -A nixosTests.apply # # These are not all tests. See also nixosTests. { lib, stdenvNoCC, testers, ... }: let fileset = lib.fileset.unions [ ./test.sh ./apply.sh ]; in { unitTests = stdenvNoCC.mkDerivation { name = "nixos-apply-unit-tests"; src = lib.fileset.toSource { root = ./.; inherit fileset; }; dontBuild = true; checkPhase = '' ./test.sh ''; installPhase = '' touch $out ''; }; shellcheck = (testers.shellcheck { src = lib.fileset.toSource { # This makes the error messages include the full path root = ../../../../..; inherit fileset; }; }).overrideAttrs { postUnpack = '' for f in $(find . -type f); do substituteInPlace $f --replace @bash@ /usr/bin/bash done ''; }; } nixos/modules/system/activation/apply/test.sh 0 → 100755 +176 −0 Original line number Diff line number Diff line #!/usr/bin/env bash # shellcheck disable=SC2317 disable=SC2031 # False positives: # SC2317: Unreachable code: TEST_* # SC2031: <variable> was modified in a subshell. That change might be lost. # We have a lot of that, and that's expected. # This is a unit test script for the NixOS apply script. # It can be run quickly with the following command: # # ./test.sh # # Alternatively, run the following to run all tests and checks # # TODO # set -euo pipefail # set -x apply="${BASH_SOURCE[0]%/*}/apply.sh" # source_apply() { run_parse_args() { bash -c "source $apply;"' parse_args "$@"' -- "$@" } TEST_parse_args_none() { if errout="$(run_parse_args 2>&1)"; then test_fail "apply without arguments should fail" elif [[ $? -ne 1 ]]; then test_fail "apply without arguments should exit with code 1" fi grep -F "no subcommand specified" <<<"$errout" >/dev/null } TEST_parse_args_switch() { ( # shellcheck source=nixos/modules/system/activation/apply/apply.sh source "$apply"; parse_args switch; [[ $subcommand == switch ]] [[ $specialisation == "" ]] [[ $profile == "" ]] ) } TEST_parse_args_boot() { ( # shellcheck source=nixos/modules/system/activation/apply/apply.sh source "$apply"; parse_args boot; [[ $subcommand == boot ]] [[ $specialisation == "" ]] [[ $profile == "" ]] ) } TEST_parse_args_test() { ( # shellcheck source=nixos/modules/system/activation/apply/apply.sh source "$apply"; parse_args test; [[ $subcommand == test ]] [[ $specialisation == "" ]] [[ $profile == "" ]] ) } TEST_parse_args_dry_activate() { ( # shellcheck source=nixos/modules/system/activation/apply/apply.sh source "$apply"; parse_args dry-activate; [[ $subcommand == dry-activate ]] [[ $specialisation == "" ]] [[ $profile == "" ]] ) } TEST_parse_args_unknown() { if errout="$(run_parse_args foo 2>&1)"; then test_fail "apply with unknown subcommand should fail" fi grep -F "unexpected argument or flag: foo" <<<"$errout" >/dev/null } TEST_parse_args_switch_specialisation_no_arg() { if errout="$(run_parse_args switch --specialisation 2>&1)"; then test_fail "apply with --specialisation without argument should fail" fi grep -F "missing argument for --specialisation" <<<"$errout" >/dev/null } TEST_parse_args_switch_specialisation() { ( # shellcheck source=nixos/modules/system/activation/apply/apply.sh source "$apply"; parse_args switch --specialisation low-power; [[ $subcommand == switch ]] [[ $specialisation == low-power ]] [[ $profile == "" ]] ) } TEST_parse_args_switch_profile() { ( # shellcheck source=nixos/modules/system/activation/apply/apply.sh source "$apply"; parse_args switch --profile /nix/var/nix/profiles/system; [[ $subcommand == switch ]] [[ $specialisation == "" ]] [[ $profile == /nix/var/nix/profiles/system ]] ) } # Support code test_fail() { echo "TEST FAILURE: $*" >&2 exit 1 } test_print_trace() { local frame=${1:0} local caller # shellcheck disable=SC2207 disable=SC2086 while caller=( $(caller $frame) ); do echo " in ${caller[1]} at ${caller[2]}:${caller[0]}" frame=$((frame+1)); done } test_on_err() { echo "ERROR running: ${BASH_COMMAND}" >&2 test_print_trace 1 >&2 } test_init() { trap 'test_on_err' ERR } test_find() { declare -F | grep -o 'TEST_.*' | sort } test_run_tests() { local status=0 for test in $(test_find); do set +e ( set -eEuo pipefail trap 'test_on_err' ERR $test ) r=$? set -e if [[ $r == 0 ]]; then echo "ok: $test" else echo "TEST FAIL: $test"; status=1; fi done if [[ $status == 0 ]]; then echo "All good" else echo echo "TEST SUITE FAILED" fi exit $status } # Main test_init test_run_tests nixos/modules/system/activation/switchable-system.nix +25 −2 Original line number Diff line number Diff line Loading @@ -40,7 +40,30 @@ in }; }; options.system.apply.enable = lib.mkOption { type = lib.types.bool; default = config.system.switch.enable; internal = true; description = '' Whether to include the `bin/apply` script. Disabling puts `nixos-rebuild` in a legacy mode that won't be maintained and removes cheap and useful functionality. It's also slower over ssh. This should only be used for testing the `nixos-rebuild` command, to pretend that the configuration is an old NixOS. ''; }; config = lib.mkMerge [ (lib.mkIf config.system.apply.enable { system.activatableSystemBuilderCommands = '' mkdir -p $out/bin substitute ${./apply/apply.sh} $out/bin/apply \ --subst-var-by bash ${lib.getExe pkgs.bash} \ --subst-var-by toplevel ''${!toplevelVar} chmod +x $out/bin/apply ''; }) (lib.mkIf (config.system.switch.enable && !config.system.switch.enableNg) { warnings = [ '' Loading @@ -54,7 +77,7 @@ in ]; system.activatableSystemBuilderCommands = '' mkdir $out/bin mkdir -p $out/bin substitute ${./switch-to-configuration.pl} $out/bin/switch-to-configuration \ --subst-var out \ --subst-var-by toplevel ''${!toplevelVar} \ Loading Loading @@ -86,7 +109,7 @@ in ( source ${pkgs.buildPackages.makeWrapper}/nix-support/setup-hook mkdir $out/bin mkdir -p $out/bin ln -sf ${lib.getExe pkgs.switch-to-configuration-ng} $out/bin/switch-to-configuration wrapProgram $out/bin/switch-to-configuration \ --set OUT $out \ Loading nixos/tests/all-tests.nix +1 −0 Original line number Diff line number Diff line Loading @@ -129,6 +129,7 @@ in { apfs = runTest ./apfs.nix; appliance-repart-image = runTest ./appliance-repart-image.nix; appliance-repart-image-verity-store = runTest ./appliance-repart-image-verity-store.nix; apply = pkgs.callPackage ../modules/system/activation/apply/checks.nix { }; apparmor = handleTest ./apparmor.nix {}; archi = handleTest ./archi.nix {}; aria2 = handleTest ./aria2.nix {}; Loading Loading
nixos/modules/system/activation/apply/apply.sh 0 → 100644 +146 −0 Original line number Diff line number Diff line #!@bash@ # This is the NixOS apply script, typically located at # # ${config.system.build.toplevel}/bin/apply # # This script is responsible for managing the profile link and calling the # appropriate scripts for its subcommands, such as switch, boot, and test. set -euo pipefail toplevel=@toplevel@ subcommand= installBootloader= specialisation= profile=/nix/var/nix/profiles/system log() { echo "$@" >&2 } die() { log "NixOS apply error: $*" exit 1 } parse_args() { while [[ $# -gt 0 ]]; do case "$1" in switch|boot|test|dry-activate) subcommand="$1" ;; --install-bootloader) installBootloader=1 ;; --profile) if [[ $# -lt 2 ]]; then die "missing argument for --profile" fi profile="$2" shift ;; # --rollback is not an `apply` responsibility, and it should be # implemented by the caller of `apply` instead. --specialisation) if [[ $# -lt 2 ]]; then die "missing argument for --specialisation" fi specialisation="$2" shift ;; *) if [[ -n "$subcommand" ]]; then die "unexpected argument or flag: $1" else die "unexpected subcommand or flag: $1" fi ;; esac shift done if [ -z "$subcommand" ]; then die "no subcommand specified" fi } main() { local cmd activity case "$subcommand" in boot|switch) nix-env -p "$profile" --set "$toplevel" ;; esac # Using systemd-run here to protect against PTY failures/network # disconnections during rebuild. # See: https://github.com/NixOS/nixpkgs/issues/39118 cmd=( "systemd-run" "-E" "LOCALE_ARCHIVE" # Will be set to new value early in switch-to-configuration script, but interpreter starts out with old value "-E" "NIXOS_INSTALL_BOOTLOADER=$installBootloader" "--collect" "--no-ask-password" "--pipe" "--quiet" "--same-dir" "--service-type=exec" "--unit=nixos-rebuild-switch-to-configuration" "--wait" ) # Check if we have a working systemd-run. In chroot environments we may have # a non-working systemd, so we fallback to not using systemd-run. if ! "${cmd[@]}" true; then log "Skipping systemd-run to switch configuration since it is not working in target host." cmd=( "env" "-i" "LOCALE_ARCHIVE=${LOCALE_ARCHIVE:-}" "NIXOS_INSTALL_BOOTLOADER=$installBootloader" ) fi if [[ -z "$specialisation" ]]; then cmd+=("$toplevel/bin/switch-to-configuration") else cmd+=("$toplevel/specialisation/$specialisation/bin/switch-to-configuration") if ! [[ -f "${cmd[-1]}" ]]; then log "error: specialisation not found: $specialisation" exit 1 fi fi if ! "${cmd[@]}" "$subcommand"; then case "$subcommand" in switch) activity="switching to the new configuration" ;; boot) activity="switching the boot entry to the new configuration" ;; test) activity="switching to the new configuration (in test mode)" ;; dry-activate) activity="switching to the new configuration (in dry-activate mode)" ;; *) # Should never happen activity="running $subcommand" ;; esac log "warning: error(s) occurred while $activity" exit 1 fi } if ! type test_run_tests &>/dev/null; then # We're not loaded into the test.sh, so we run main. parse_args "$@" main fi
nixos/modules/system/activation/apply/checks.nix 0 → 100644 +51 −0 Original line number Diff line number Diff line # Run: # nix-build -A nixosTests.apply # # These are not all tests. See also nixosTests. { lib, stdenvNoCC, testers, ... }: let fileset = lib.fileset.unions [ ./test.sh ./apply.sh ]; in { unitTests = stdenvNoCC.mkDerivation { name = "nixos-apply-unit-tests"; src = lib.fileset.toSource { root = ./.; inherit fileset; }; dontBuild = true; checkPhase = '' ./test.sh ''; installPhase = '' touch $out ''; }; shellcheck = (testers.shellcheck { src = lib.fileset.toSource { # This makes the error messages include the full path root = ../../../../..; inherit fileset; }; }).overrideAttrs { postUnpack = '' for f in $(find . -type f); do substituteInPlace $f --replace @bash@ /usr/bin/bash done ''; }; }
nixos/modules/system/activation/apply/test.sh 0 → 100755 +176 −0 Original line number Diff line number Diff line #!/usr/bin/env bash # shellcheck disable=SC2317 disable=SC2031 # False positives: # SC2317: Unreachable code: TEST_* # SC2031: <variable> was modified in a subshell. That change might be lost. # We have a lot of that, and that's expected. # This is a unit test script for the NixOS apply script. # It can be run quickly with the following command: # # ./test.sh # # Alternatively, run the following to run all tests and checks # # TODO # set -euo pipefail # set -x apply="${BASH_SOURCE[0]%/*}/apply.sh" # source_apply() { run_parse_args() { bash -c "source $apply;"' parse_args "$@"' -- "$@" } TEST_parse_args_none() { if errout="$(run_parse_args 2>&1)"; then test_fail "apply without arguments should fail" elif [[ $? -ne 1 ]]; then test_fail "apply without arguments should exit with code 1" fi grep -F "no subcommand specified" <<<"$errout" >/dev/null } TEST_parse_args_switch() { ( # shellcheck source=nixos/modules/system/activation/apply/apply.sh source "$apply"; parse_args switch; [[ $subcommand == switch ]] [[ $specialisation == "" ]] [[ $profile == "" ]] ) } TEST_parse_args_boot() { ( # shellcheck source=nixos/modules/system/activation/apply/apply.sh source "$apply"; parse_args boot; [[ $subcommand == boot ]] [[ $specialisation == "" ]] [[ $profile == "" ]] ) } TEST_parse_args_test() { ( # shellcheck source=nixos/modules/system/activation/apply/apply.sh source "$apply"; parse_args test; [[ $subcommand == test ]] [[ $specialisation == "" ]] [[ $profile == "" ]] ) } TEST_parse_args_dry_activate() { ( # shellcheck source=nixos/modules/system/activation/apply/apply.sh source "$apply"; parse_args dry-activate; [[ $subcommand == dry-activate ]] [[ $specialisation == "" ]] [[ $profile == "" ]] ) } TEST_parse_args_unknown() { if errout="$(run_parse_args foo 2>&1)"; then test_fail "apply with unknown subcommand should fail" fi grep -F "unexpected argument or flag: foo" <<<"$errout" >/dev/null } TEST_parse_args_switch_specialisation_no_arg() { if errout="$(run_parse_args switch --specialisation 2>&1)"; then test_fail "apply with --specialisation without argument should fail" fi grep -F "missing argument for --specialisation" <<<"$errout" >/dev/null } TEST_parse_args_switch_specialisation() { ( # shellcheck source=nixos/modules/system/activation/apply/apply.sh source "$apply"; parse_args switch --specialisation low-power; [[ $subcommand == switch ]] [[ $specialisation == low-power ]] [[ $profile == "" ]] ) } TEST_parse_args_switch_profile() { ( # shellcheck source=nixos/modules/system/activation/apply/apply.sh source "$apply"; parse_args switch --profile /nix/var/nix/profiles/system; [[ $subcommand == switch ]] [[ $specialisation == "" ]] [[ $profile == /nix/var/nix/profiles/system ]] ) } # Support code test_fail() { echo "TEST FAILURE: $*" >&2 exit 1 } test_print_trace() { local frame=${1:0} local caller # shellcheck disable=SC2207 disable=SC2086 while caller=( $(caller $frame) ); do echo " in ${caller[1]} at ${caller[2]}:${caller[0]}" frame=$((frame+1)); done } test_on_err() { echo "ERROR running: ${BASH_COMMAND}" >&2 test_print_trace 1 >&2 } test_init() { trap 'test_on_err' ERR } test_find() { declare -F | grep -o 'TEST_.*' | sort } test_run_tests() { local status=0 for test in $(test_find); do set +e ( set -eEuo pipefail trap 'test_on_err' ERR $test ) r=$? set -e if [[ $r == 0 ]]; then echo "ok: $test" else echo "TEST FAIL: $test"; status=1; fi done if [[ $status == 0 ]]; then echo "All good" else echo echo "TEST SUITE FAILED" fi exit $status } # Main test_init test_run_tests
nixos/modules/system/activation/switchable-system.nix +25 −2 Original line number Diff line number Diff line Loading @@ -40,7 +40,30 @@ in }; }; options.system.apply.enable = lib.mkOption { type = lib.types.bool; default = config.system.switch.enable; internal = true; description = '' Whether to include the `bin/apply` script. Disabling puts `nixos-rebuild` in a legacy mode that won't be maintained and removes cheap and useful functionality. It's also slower over ssh. This should only be used for testing the `nixos-rebuild` command, to pretend that the configuration is an old NixOS. ''; }; config = lib.mkMerge [ (lib.mkIf config.system.apply.enable { system.activatableSystemBuilderCommands = '' mkdir -p $out/bin substitute ${./apply/apply.sh} $out/bin/apply \ --subst-var-by bash ${lib.getExe pkgs.bash} \ --subst-var-by toplevel ''${!toplevelVar} chmod +x $out/bin/apply ''; }) (lib.mkIf (config.system.switch.enable && !config.system.switch.enableNg) { warnings = [ '' Loading @@ -54,7 +77,7 @@ in ]; system.activatableSystemBuilderCommands = '' mkdir $out/bin mkdir -p $out/bin substitute ${./switch-to-configuration.pl} $out/bin/switch-to-configuration \ --subst-var out \ --subst-var-by toplevel ''${!toplevelVar} \ Loading Loading @@ -86,7 +109,7 @@ in ( source ${pkgs.buildPackages.makeWrapper}/nix-support/setup-hook mkdir $out/bin mkdir -p $out/bin ln -sf ${lib.getExe pkgs.switch-to-configuration-ng} $out/bin/switch-to-configuration wrapProgram $out/bin/switch-to-configuration \ --set OUT $out \ Loading
nixos/tests/all-tests.nix +1 −0 Original line number Diff line number Diff line Loading @@ -129,6 +129,7 @@ in { apfs = runTest ./apfs.nix; appliance-repart-image = runTest ./appliance-repart-image.nix; appliance-repart-image-verity-store = runTest ./appliance-repart-image-verity-store.nix; apply = pkgs.callPackage ../modules/system/activation/apply/checks.nix { }; apparmor = handleTest ./apparmor.nix {}; archi = handleTest ./archi.nix {}; aria2 = handleTest ./aria2.nix {}; Loading