diff options
author | Mx Kookie <kookie@spacekookie.de> | 2020-10-31 19:35:09 +0100 |
---|---|---|
committer | Mx Kookie <kookie@spacekookie.de> | 2020-10-31 19:35:09 +0100 |
commit | c4625b175f8200f643fd6e11010932ea44c78433 (patch) | |
tree | bce3f89888c8ac3991fa5569a878a9eab6801ccc /infra/libkookie/nixpkgs/nixos/modules/system/activation | |
parent | 49f735974dd103039ddc4cb576bb76555164a9e7 (diff) | |
parent | d661aa56a8843e991261510c1bb28fdc2f6975ae (diff) |
Add 'infra/libkookie/' from commit 'd661aa56a8843e991261510c1bb28fdc2f6975ae'
git-subtree-dir: infra/libkookie
git-subtree-mainline: 49f735974dd103039ddc4cb576bb76555164a9e7
git-subtree-split: d661aa56a8843e991261510c1bb28fdc2f6975ae
Diffstat (limited to 'infra/libkookie/nixpkgs/nixos/modules/system/activation')
4 files changed, 1023 insertions, 0 deletions
diff --git a/infra/libkookie/nixpkgs/nixos/modules/system/activation/activation-script.nix b/infra/libkookie/nixpkgs/nixos/modules/system/activation/activation-script.nix new file mode 100644 index 000000000000..ddfd1af4a319 --- /dev/null +++ b/infra/libkookie/nixpkgs/nixos/modules/system/activation/activation-script.nix @@ -0,0 +1,229 @@ +# generate the script used to activate the configuration. +{ config, lib, pkgs, ... }: + +with lib; + +let + + addAttributeName = mapAttrs (a: v: v // { + 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 + [ coreutils + gnugrep + findutils + getent + stdenv.cc.libc # nscd in update-users-groups.pl + shadow + nettools # needed for hostname + utillinux # needed for mount and mountpoint + ]; + +in + +{ + + ###### interface + + options = { + + system.activationScripts = mkOption { + default = {}; + + example = literalExample '' + { stdio = { + text = ''' + # Needed by some programs. + ln -sfn /proc/self/fd /dev/fd + ln -sfn /proc/self/fd/0 /dev/stdin + ln -sfn /proc/self/fd/1 /dev/stdout + ln -sfn /proc/self/fd/2 /dev/stderr + '''; + deps = []; + }; + } + ''; + + 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. + ''; + + type = types.attrsOf types.unspecified; # FIXME + + apply = set: { + script = + '' + #! ${pkgs.runtimeShell} + + systemConfig=@out@ + + export PATH=/empty + for i in ${toString path}; do + PATH=$PATH:$i/bin:$i/sbin + done + + _status=0 + trap "_status=1 _localstatus=\$?" ERR + + # Ensure a consistent umask. + umask 0022 + + ${ + let + set' = mapAttrs (n: v: if isString v then noDepEntry v else v) set; + withHeadlines = addAttributeName set'; + in textClosureMap id (withHeadlines) (attrNames withHeadlines) + } + + # 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. + ln -sfn /run/current-system /nix/var/nix/gcroots/current-system + + exit $_status + ''; + }; + }; + + system.userActivationScripts = mkOption { + default = {}; + + example = literalExample '' + { plasmaSetup = { + text = ''' + ${pkgs.libsForQt5.kservice}/bin/kbuildsycoca5" + '''; + deps = []; + }; + } + ''; + + description = '' + A set of shell script fragments that are executed by a systemd user + service when a NixOS 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>nixos-rebuild</command>, it's important that they are + idempotent and fast. + ''; + + type = types.attrsOf types.unspecified; + + apply = set: { + script = '' + unset PATH + for i in ${toString path}; do + PATH=$PATH:$i/bin:$i/sbin + done + + _status=0 + trap "_status=1 _localstatus=\$?" ERR + + ${ + let + set' = mapAttrs (n: v: if isString v then noDepEntry v else v) set; + withHeadlines = addAttributeName set'; + in textClosureMap id (withHeadlines) (attrNames withHeadlines) + } + + exit $_status + ''; + }; + + }; + + environment.usrbinenv = mkOption { + default = "${pkgs.coreutils}/bin/env"; + example = literalExample '' + "''${pkgs.busybox}/bin/env" + ''; + type = types.nullOr types.path; + visible = false; + description = '' + The env(1) executable that is linked system-wide to + <literal>/usr/bin/env</literal>. + ''; + }; + }; + + + ###### implementation + + config = { + + system.activationScripts.stdio = ""; # obsolete + + system.activationScripts.var = + '' + # Various log/runtime directories. + + mkdir -m 1777 -p /var/tmp + + # Empty, immutable home directory of many system accounts. + mkdir -p /var/empty + # Make sure it's really empty + ${pkgs.e2fsprogs}/bin/chattr -f -i /var/empty || true + find /var/empty -mindepth 1 -delete + chmod 0555 /var/empty + chown root:root /var/empty + ${pkgs.e2fsprogs}/bin/chattr -f +i /var/empty || true + ''; + + system.activationScripts.usrbinenv = if config.environment.usrbinenv != null + then '' + mkdir -m 0755 -p /usr/bin + ln -sfn ${config.environment.usrbinenv} /usr/bin/.env.tmp + mv /usr/bin/.env.tmp /usr/bin/env # atomically replace /usr/bin/env + '' + else '' + rm -f /usr/bin/env + rmdir --ignore-fail-on-non-empty /usr/bin /usr + ''; + + system.activationScripts.specialfs = + '' + specialMount() { + local device="$1" + local mountPoint="$2" + local options="$3" + local fsType="$4" + + if mountpoint -q "$mountPoint"; then + local options="remount,$options" + else + mkdir -m 0755 -p "$mountPoint" + fi + mount -t "$fsType" -o "$options" "$device" "$mountPoint" + } + source ${config.system.build.earlyMountScript} + ''; + + systemd.user = { + services.nixos-activation = { + description = "Run user-specific NixOS activation"; + script = config.system.userActivationScripts.script; + unitConfig.ConditionUser = "!@system"; + serviceConfig.Type = "oneshot"; + }; + }; + }; + +} diff --git a/infra/libkookie/nixpkgs/nixos/modules/system/activation/no-clone.nix b/infra/libkookie/nixpkgs/nixos/modules/system/activation/no-clone.nix new file mode 100644 index 000000000000..912420347dc0 --- /dev/null +++ b/infra/libkookie/nixpkgs/nixos/modules/system/activation/no-clone.nix @@ -0,0 +1,8 @@ +{ lib, ... }: + +with lib; + +{ + boot.loader.grub.device = mkOverride 0 "nodev"; + specialisation = mkOverride 0 {}; +} diff --git a/infra/libkookie/nixpkgs/nixos/modules/system/activation/switch-to-configuration.pl b/infra/libkookie/nixpkgs/nixos/modules/system/activation/switch-to-configuration.pl new file mode 100644 index 000000000000..b82d69b3bb85 --- /dev/null +++ b/infra/libkookie/nixpkgs/nixos/modules/system/activation/switch-to-configuration.pl @@ -0,0 +1,489 @@ +#! @perl@ + +use strict; +use warnings; +use File::Basename; +use File::Slurp; +use Net::DBus; +use Sys::Syslog qw(:standard :macros); +use Cwd 'abs_path'; + +my $out = "@out@"; + +# FIXME: maybe we should use /proc/1/exe to get the current systemd. +my $curSystemd = abs_path("/run/current-system/sw/bin"); + +# To be robust against interruption, record what units need to be started etc. +my $startListFile = "/run/systemd/start-list"; +my $restartListFile = "/run/systemd/restart-list"; +my $reloadListFile = "/run/systemd/reload-list"; + +my $action = shift @ARGV; + +if ("@localeArchive@" ne "") { + $ENV{LOCALE_ARCHIVE} = "@localeArchive@"; +} + +if (!defined $action || ($action ne "switch" && $action ne "boot" && $action ne "test" && $action ne "dry-activate")) { + print STDERR <<EOF; +Usage: $0 [switch|boot|test] + +switch: make the configuration the boot default and activate now +boot: make the configuration the boot default +test: activate the configuration, but don\'t make it the boot default +dry-activate: show what would be done if this configuration were activated +EOF + exit 1; +} + +# This is a NixOS installation if it has /etc/NIXOS or a proper +# /etc/os-release. +die "This is not a NixOS installation!\n" unless + -f "/etc/NIXOS" || (read_file("/etc/os-release", err_mode => 'quiet') // "") =~ /ID=nixos/s; + +openlog("nixos", "", LOG_USER); + +# Install or update the bootloader. +if ($action eq "switch" || $action eq "boot") { + system("@installBootLoader@ $out") == 0 or exit 1; +} + +# Just in case the new configuration hangs the system, do a sync now. +system("@coreutils@/bin/sync", "-f", "/nix/store") unless ($ENV{"NIXOS_NO_SYNC"} // "") eq "1"; + +exit 0 if $action eq "boot"; + +# Check if we can activate the new configuration. +my $oldVersion = read_file("/run/current-system/init-interface-version", err_mode => 'quiet') // ""; +my $newVersion = read_file("$out/init-interface-version"); + +if ($newVersion ne $oldVersion) { + print STDERR <<EOF; +Warning: the new NixOS configuration has an ‘init’ that is +incompatible with the current configuration. The new configuration +won\'t take effect until you reboot the system. +EOF + exit 100; +} + +# Ignore SIGHUP so that we're not killed if we're running on (say) +# virtual console 1 and we restart the "tty1" unit. +$SIG{PIPE} = "IGNORE"; + +sub getActiveUnits { + my $mgr = Net::DBus->system->get_service("org.freedesktop.systemd1")->get_object("/org/freedesktop/systemd1"); + my $units = $mgr->ListUnitsByPatterns([], []); + my $res = {}; + for my $item (@$units) { + my ($id, $description, $load_state, $active_state, $sub_state, + $following, $unit_path, $job_id, $job_type, $job_path) = @$item; + next unless $following eq ''; + next if $job_id == 0 and $active_state eq 'inactive'; + $res->{$id} = { load => $load_state, state => $active_state, substate => $sub_state }; + } + return $res; +} + +sub parseFstab { + my ($filename) = @_; + my ($fss, $swaps); + foreach my $line (read_file($filename, err_mode => 'quiet')) { + chomp $line; + $line =~ s/^\s*#.*//; + next if $line =~ /^\s*$/; + my @xs = split / /, $line; + if ($xs[2] eq "swap") { + $swaps->{$xs[0]} = { options => $xs[3] // "" }; + } else { + $fss->{$xs[1]} = { device => $xs[0], fsType => $xs[2], options => $xs[3] // "" }; + } + } + return ($fss, $swaps); +} + +sub parseUnit { + my ($filename) = @_; + my $info = {}; + parseKeyValues($info, read_file($filename)) if -f $filename; + parseKeyValues($info, read_file("${filename}.d/overrides.conf")) if -f "${filename}.d/overrides.conf"; + return $info; +} + +sub parseKeyValues { + my $info = shift; + foreach my $line (@_) { + # FIXME: not quite correct. + $line =~ /^([^=]+)=(.*)$/ or next; + $info->{$1} = $2; + } +} + +sub boolIsTrue { + my ($s) = @_; + return $s eq "yes" || $s eq "true"; +} + +sub recordUnit { + my ($fn, $unit) = @_; + write_file($fn, { append => 1 }, "$unit\n") if $action ne "dry-activate"; +} + +# As a fingerprint for determining whether a unit has changed, we use +# its absolute path. If it has an override file, we append *its* +# absolute path as well. +sub fingerprintUnit { + my ($s) = @_; + return abs_path($s) . (-f "${s}.d/overrides.conf" ? " " . abs_path "${s}.d/overrides.conf" : ""); +} + +# Figure out what units need to be stopped, started, restarted or reloaded. +my (%unitsToStop, %unitsToSkip, %unitsToStart, %unitsToRestart, %unitsToReload); + +my %unitsToFilter; # units not shown + +$unitsToStart{$_} = 1 foreach + split('\n', read_file($startListFile, err_mode => 'quiet') // ""); + +$unitsToRestart{$_} = 1 foreach + split('\n', read_file($restartListFile, err_mode => 'quiet') // ""); + +$unitsToReload{$_} = 1 foreach + split '\n', read_file($reloadListFile, err_mode => 'quiet') // ""; + +my $activePrev = getActiveUnits; +while (my ($unit, $state) = each %{$activePrev}) { + my $baseUnit = $unit; + + my $prevUnitFile = "/etc/systemd/system/$baseUnit"; + my $newUnitFile = "$out/etc/systemd/system/$baseUnit"; + + # Detect template instances. + if (!-e $prevUnitFile && !-e $newUnitFile && $unit =~ /^(.*)@[^\.]*\.(.*)$/) { + $baseUnit = "$1\@.$2"; + $prevUnitFile = "/etc/systemd/system/$baseUnit"; + $newUnitFile = "$out/etc/systemd/system/$baseUnit"; + } + + my $baseName = $baseUnit; + $baseName =~ s/\.[a-z]*$//; + + if (-e $prevUnitFile && ($state->{state} eq "active" || $state->{state} eq "activating")) { + if (! -e $newUnitFile || abs_path($newUnitFile) eq "/dev/null") { + my $unitInfo = parseUnit($prevUnitFile); + $unitsToStop{$unit} = 1 if boolIsTrue($unitInfo->{'X-StopOnRemoval'} // "yes"); + } + + elsif ($unit =~ /\.target$/) { + my $unitInfo = parseUnit($newUnitFile); + + # Cause all active target units to be restarted below. + # This should start most changed units we stop here as + # well as any new dependencies (including new mounts and + # swap devices). FIXME: the suspend target is sometimes + # active after the system has resumed, which probably + # should not be the case. Just ignore it. + if ($unit ne "suspend.target" && $unit ne "hibernate.target" && $unit ne "hybrid-sleep.target") { + unless (boolIsTrue($unitInfo->{'RefuseManualStart'} // "no") || boolIsTrue($unitInfo->{'X-OnlyManualStart'} // "no")) { + $unitsToStart{$unit} = 1; + recordUnit($startListFile, $unit); + # Don't spam the user with target units that always get started. + $unitsToFilter{$unit} = 1; + } + } + + # Stop targets that have X-StopOnReconfiguration set. + # This is necessary to respect dependency orderings + # involving targets: if unit X starts after target Y and + # target Y starts after unit Z, then if X and Z have both + # changed, then X should be restarted after Z. However, + # if target Y is in the "active" state, X and Z will be + # restarted at the same time because X's dependency on Y + # is already satisfied. Thus, we need to stop Y first. + # Stopping a target generally has no effect on other units + # (unless there is a PartOf dependency), so this is just a + # bookkeeping thing to get systemd to do the right thing. + if (boolIsTrue($unitInfo->{'X-StopOnReconfiguration'} // "no")) { + $unitsToStop{$unit} = 1; + } + } + + elsif (fingerprintUnit($prevUnitFile) ne fingerprintUnit($newUnitFile)) { + if ($unit eq "sysinit.target" || $unit eq "basic.target" || $unit eq "multi-user.target" || $unit eq "graphical.target") { + # Do nothing. These cannot be restarted directly. + } elsif ($unit =~ /\.mount$/) { + # Reload the changed mount unit to force a remount. + $unitsToReload{$unit} = 1; + recordUnit($reloadListFile, $unit); + } elsif ($unit =~ /\.socket$/ || $unit =~ /\.path$/ || $unit =~ /\.slice$/) { + # FIXME: do something? + } else { + my $unitInfo = parseUnit($newUnitFile); + if (boolIsTrue($unitInfo->{'X-ReloadIfChanged'} // "no")) { + $unitsToReload{$unit} = 1; + recordUnit($reloadListFile, $unit); + } + elsif (!boolIsTrue($unitInfo->{'X-RestartIfChanged'} // "yes") || boolIsTrue($unitInfo->{'RefuseManualStop'} // "no") || boolIsTrue($unitInfo->{'X-OnlyManualStart'} // "no")) { + $unitsToSkip{$unit} = 1; + } else { + if (!boolIsTrue($unitInfo->{'X-StopIfChanged'} // "yes")) { + # This unit should be restarted instead of + # stopped and started. + $unitsToRestart{$unit} = 1; + recordUnit($restartListFile, $unit); + } else { + # If this unit is socket-activated, then stop the + # socket unit(s) as well, and restart the + # socket(s) instead of the service. + my $socketActivated = 0; + if ($unit =~ /\.service$/) { + my @sockets = split / /, ($unitInfo->{Sockets} // ""); + if (scalar @sockets == 0) { + @sockets = ("$baseName.socket"); + } + foreach my $socket (@sockets) { + if (defined $activePrev->{$socket}) { + $unitsToStop{$socket} = 1; + $unitsToStart{$socket} = 1; + recordUnit($startListFile, $socket); + $socketActivated = 1; + } + } + } + + # If the unit is not socket-activated, record + # that this unit needs to be started below. + # We write this to a file to ensure that the + # service gets restarted if we're interrupted. + if (!$socketActivated) { + $unitsToStart{$unit} = 1; + recordUnit($startListFile, $unit); + } + + $unitsToStop{$unit} = 1; + } + } + } + } + } +} + +sub pathToUnitName { + my ($path) = @_; + # Use current version of systemctl binary before daemon is reexeced. + open my $cmd, "-|", "$curSystemd/systemd-escape", "--suffix=mount", "-p", $path + or die "Unable to escape $path!\n"; + my $escaped = join "", <$cmd>; + chomp $escaped; + close $cmd or die; + return $escaped; +} + +sub unique { + my %seen; + my @res; + foreach my $name (@_) { + next if $seen{$name}; + $seen{$name} = 1; + push @res, $name; + } + return @res; +} + +# Compare the previous and new fstab to figure out which filesystems +# need a remount or need to be unmounted. New filesystems are mounted +# automatically by starting local-fs.target. FIXME: might be nicer if +# we generated units for all mounts; then we could unify this with the +# unit checking code above. +my ($prevFss, $prevSwaps) = parseFstab "/etc/fstab"; +my ($newFss, $newSwaps) = parseFstab "$out/etc/fstab"; +foreach my $mountPoint (keys %$prevFss) { + my $prev = $prevFss->{$mountPoint}; + my $new = $newFss->{$mountPoint}; + my $unit = pathToUnitName($mountPoint); + if (!defined $new) { + # Filesystem entry disappeared, so unmount it. + $unitsToStop{$unit} = 1; + } elsif ($prev->{fsType} ne $new->{fsType} || $prev->{device} ne $new->{device}) { + # Filesystem type or device changed, so unmount and mount it. + $unitsToStop{$unit} = 1; + $unitsToStart{$unit} = 1; + recordUnit($startListFile, $unit); + } elsif ($prev->{options} ne $new->{options}) { + # Mount options changes, so remount it. + $unitsToReload{$unit} = 1; + recordUnit($reloadListFile, $unit); + } +} + +# Also handles swap devices. +foreach my $device (keys %$prevSwaps) { + my $prev = $prevSwaps->{$device}; + my $new = $newSwaps->{$device}; + if (!defined $new) { + # Swap entry disappeared, so turn it off. Can't use + # "systemctl stop" here because systemd has lots of alias + # units that prevent a stop from actually calling + # "swapoff". + print STDERR "stopping swap device: $device\n"; + system("@utillinux@/sbin/swapoff", $device); + } + # FIXME: update swap options (i.e. its priority). +} + + +# Should we have systemd re-exec itself? +my $prevSystemd = abs_path("/proc/1/exe") // "/unknown"; +my $newSystemd = abs_path("@systemd@/lib/systemd/systemd") or die; +my $restartSystemd = $prevSystemd ne $newSystemd; + + +sub filterUnits { + my ($units) = @_; + my @res; + foreach my $unit (sort(keys %{$units})) { + push @res, $unit if !defined $unitsToFilter{$unit}; + } + return @res; +} + +my @unitsToStopFiltered = filterUnits(\%unitsToStop); +my @unitsToStartFiltered = filterUnits(\%unitsToStart); + + +# Show dry-run actions. +if ($action eq "dry-activate") { + print STDERR "would stop the following units: ", join(", ", @unitsToStopFiltered), "\n" + if scalar @unitsToStopFiltered > 0; + print STDERR "would NOT stop the following changed units: ", join(", ", sort(keys %unitsToSkip)), "\n" + if scalar(keys %unitsToSkip) > 0; + print STDERR "would restart systemd\n" if $restartSystemd; + print STDERR "would restart the following units: ", join(", ", sort(keys %unitsToRestart)), "\n" + if scalar(keys %unitsToRestart) > 0; + print STDERR "would start the following units: ", join(", ", @unitsToStartFiltered), "\n" + if scalar @unitsToStartFiltered; + print STDERR "would reload the following units: ", join(", ", sort(keys %unitsToReload)), "\n" + if scalar(keys %unitsToReload) > 0; + exit 0; +} + + +syslog(LOG_NOTICE, "switching to system configuration $out"); + +if (scalar (keys %unitsToStop) > 0) { + print STDERR "stopping the following units: ", join(", ", @unitsToStopFiltered), "\n" + if scalar @unitsToStopFiltered; + # Use current version of systemctl binary before daemon is reexeced. + system("$curSystemd/systemctl", "stop", "--", sort(keys %unitsToStop)); # FIXME: ignore errors? +} + +print STDERR "NOT restarting the following changed units: ", join(", ", sort(keys %unitsToSkip)), "\n" + if scalar(keys %unitsToSkip) > 0; + +# Activate the new configuration (i.e., update /etc, make accounts, +# and so on). +my $res = 0; +print STDERR "activating the configuration...\n"; +system("$out/activate", "$out") == 0 or $res = 2; + +# Restart systemd if necessary. Note that this is done using the +# current version of systemd, just in case the new one has trouble +# communicating with the running pid 1. +if ($restartSystemd) { + print STDERR "restarting systemd...\n"; + system("$curSystemd/systemctl", "daemon-reexec") == 0 or $res = 2; +} + +# Forget about previously failed services. +system("@systemd@/bin/systemctl", "reset-failed"); + +# Make systemd reload its units. +system("@systemd@/bin/systemctl", "daemon-reload") == 0 or $res = 3; + +# Reload user units +open my $listActiveUsers, '-|', '@systemd@/bin/loginctl', 'list-users', '--no-legend'; +while (my $f = <$listActiveUsers>) { + next unless $f =~ /^\s*(?<uid>\d+)\s+(?<user>\S+)/; + my ($uid, $name) = ($+{uid}, $+{user}); + print STDERR "reloading user units for $name...\n"; + + system("@su@", "-s", "@shell@", "-l", $name, "-c", + "export XDG_RUNTIME_DIR=/run/user/$uid; " . + "$curSystemd/systemctl --user daemon-reexec; " . + "@systemd@/bin/systemctl --user start nixos-activation.service"); +} + +close $listActiveUsers; + +# Set the new tmpfiles +print STDERR "setting up tmpfiles\n"; +system("@systemd@/bin/systemd-tmpfiles", "--create", "--remove", "--exclude-prefix=/dev") == 0 or $res = 3; + +# Reload units that need it. This includes remounting changed mount +# units. +if (scalar(keys %unitsToReload) > 0) { + print STDERR "reloading the following units: ", join(", ", sort(keys %unitsToReload)), "\n"; + system("@systemd@/bin/systemctl", "reload", "--", sort(keys %unitsToReload)) == 0 or $res = 4; + unlink($reloadListFile); +} + +# Restart changed services (those that have to be restarted rather +# than stopped and started). +if (scalar(keys %unitsToRestart) > 0) { + print STDERR "restarting the following units: ", join(", ", sort(keys %unitsToRestart)), "\n"; + system("@systemd@/bin/systemctl", "restart", "--", sort(keys %unitsToRestart)) == 0 or $res = 4; + unlink($restartListFile); +} + +# Start all active targets, as well as changed units we stopped above. +# The latter is necessary because some may not be dependencies of the +# targets (i.e., they were manually started). FIXME: detect units +# that are symlinks to other units. We shouldn't start both at the +# same time because we'll get a "Failed to add path to set" error from +# systemd. +print STDERR "starting the following units: ", join(", ", @unitsToStartFiltered), "\n" + if scalar @unitsToStartFiltered; +system("@systemd@/bin/systemctl", "start", "--", sort(keys %unitsToStart)) == 0 or $res = 4; +unlink($startListFile); + + +# Print failed and new units. +my (@failed, @new, @restarting); +my $activeNew = getActiveUnits; +while (my ($unit, $state) = each %{$activeNew}) { + if ($state->{state} eq "failed") { + push @failed, $unit; + } + elsif ($state->{state} eq "auto-restart") { + # A unit in auto-restart state is a failure *if* it previously failed to start + my $lines = `@systemd@/bin/systemctl show '$unit'`; + my $info = {}; + parseKeyValues($info, split("\n", $lines)); + + if ($info->{ExecMainStatus} ne '0') { + push @failed, $unit; + } + } + elsif ($state->{state} ne "failed" && !defined $activePrev->{$unit}) { + push @new, $unit; + } +} + +print STDERR "the following new units were started: ", join(", ", sort(@new)), "\n" + if scalar @new > 0; + +if (scalar @failed > 0) { + print STDERR "warning: the following units failed: ", join(", ", sort(@failed)), "\n"; + foreach my $unit (@failed) { + print STDERR "\n"; + system("COLUMNS=1000 @systemd@/bin/systemctl status --no-pager '$unit' >&2"); + } + $res = 4; +} + +if ($res == 0) { + syslog(LOG_NOTICE, "finished switching to system configuration $out"); +} else { + syslog(LOG_ERR, "switching to system configuration $out failed (status $res)"); +} + +exit $res; diff --git a/infra/libkookie/nixpkgs/nixos/modules/system/activation/top-level.nix b/infra/libkookie/nixpkgs/nixos/modules/system/activation/top-level.nix new file mode 100644 index 000000000000..2724d9f9cb6f --- /dev/null +++ b/infra/libkookie/nixpkgs/nixos/modules/system/activation/top-level.nix @@ -0,0 +1,297 @@ +{ config, lib, pkgs, modules, baseModules, ... }: + +with lib; + +let + + + # This attribute is responsible for creating boot entries for + # child configuration. They are only (directly) accessible + # when the parent configuration is boot default. For example, + # you can provide an easy way to boot the same configuration + # as you use, but with another kernel + # !!! fix this + children = mapAttrs (childName: childConfig: + (import ../../../lib/eval-config.nix { + inherit baseModules; + system = config.nixpkgs.initialSystem; + modules = + (optionals childConfig.inheritParentConfig modules) + ++ [ ./no-clone.nix ] + ++ [ childConfig.configuration ]; + }).config.system.build.toplevel + ) config.specialisation; + + systemBuilder = + let + kernelPath = "${config.boot.kernelPackages.kernel}/" + + "${config.system.boot.loader.kernelFile}"; + initrdPath = "${config.system.build.initialRamdisk}/" + + "${config.system.boot.loader.initrdFile}"; + in '' + mkdir $out + + # Containers don't have their own kernel or initrd. They boot + # directly into stage 2. + ${optionalString (!config.boot.isContainer) '' + if [ ! -f ${kernelPath} ]; then + echo "The bootloader cannot find the proper kernel image." + echo "(Expecting ${kernelPath})" + false + fi + + ln -s ${kernelPath} $out/kernel + ln -s ${config.system.modulesTree} $out/kernel-modules + ${optionalString (config.hardware.deviceTree.package != null) '' + ln -s ${config.hardware.deviceTree.package} $out/dtbs + ''} + + echo -n "$kernelParams" > $out/kernel-params + + ln -s ${initrdPath} $out/initrd + + ln -s ${config.system.build.initialRamdiskSecretAppender}/bin/append-initrd-secrets $out + + ln -s ${config.hardware.firmware}/lib/firmware $out/firmware + ''} + + echo "$activationScript" > $out/activate + substituteInPlace $out/activate --subst-var out + chmod u+x $out/activate + unset activationScript + + cp ${config.system.build.bootStage2} $out/init + substituteInPlace $out/init --subst-var-by systemConfig $out + + ln -s ${config.system.build.etc}/etc $out/etc + ln -s ${config.system.path} $out/sw + ln -s "$systemd" $out/systemd + + echo -n "$configurationName" > $out/configuration-name + echo -n "systemd ${toString config.systemd.package.interfaceVersion}" > $out/init-interface-version + echo -n "$nixosLabel" > $out/nixos-version + echo -n "${config.boot.kernelPackages.stdenv.hostPlatform.system}" > $out/system + + mkdir $out/specialisation + ${concatStringsSep "\n" + (mapAttrsToList (name: path: "ln -s ${path} $out/specialisation/${name}") children)} + + mkdir $out/bin + export localeArchive="${config.i18n.glibcLocales}/lib/locale/locale-archive" + substituteAll ${./switch-to-configuration.pl} $out/bin/switch-to-configuration + chmod +x $out/bin/switch-to-configuration + + echo -n "${toString config.system.extraDependencies}" > $out/extra-dependencies + + ${config.system.extraSystemBuilderCmds} + ''; + + # Putting it all together. This builds a store path containing + # symlinks to the various parts of the built configuration (the + # kernel, systemd units, init scripts, etc.) as well as a script + # `switch-to-configuration' that activates the configuration and + # makes it bootable. + baseSystem = pkgs.stdenvNoCC.mkDerivation { + name = "nixos-system-${config.system.name}-${config.system.nixos.label}"; + preferLocalBuild = true; + allowSubstitutes = false; + buildCommand = systemBuilder; + + inherit (pkgs) utillinux coreutils; + systemd = config.systemd.package; + shell = "${pkgs.bash}/bin/sh"; + su = "${pkgs.shadow.su}/bin/su"; + + kernelParams = config.boot.kernelParams; + installBootLoader = + config.system.build.installBootLoader + or "echo 'Warning: do not know how to make this configuration bootable; please enable a boot loader.' 1>&2; true"; + activationScript = config.system.activationScripts.script; + nixosLabel = config.system.nixos.label; + + configurationName = config.boot.loader.grub.configurationName; + + # Needed by switch-to-configuration. + + perl = "${pkgs.perl}/bin/perl " + (concatMapStringsSep " " (lib: "-I${lib}/${pkgs.perl.libPrefix}") (with pkgs.perlPackages; [ FileSlurp NetDBus XMLParser XMLTwig ])); + }; + + # Handle assertions and warnings + + failedAssertions = map (x: x.message) (filter (x: !x.assertion) config.assertions); + + baseSystemAssertWarn = if failedAssertions != [] + then throw "\nFailed assertions:\n${concatStringsSep "\n" (map (x: "- ${x}") failedAssertions)}" + else showWarnings config.warnings baseSystem; + + # Replace runtime dependencies + system = fold ({ oldDependency, newDependency }: drv: + pkgs.replaceDependency { inherit oldDependency newDependency drv; } + ) baseSystemAssertWarn config.system.replaceRuntimeDependencies; + +in + +{ + imports = [ + (mkRemovedOptionModule [ "nesting" "clone" ] "Use `specialisation.«name» = { inheritParentConfig = true; configuration = { ... }; }` instead.") + (mkRemovedOptionModule [ "nesting" "children" ] "Use `specialisation.«name».configuration = { ... }` instead.") + ]; + + options = { + + system.build = mkOption { + internal = true; + default = {}; + type = types.attrs; + description = '' + Attribute set of derivations used to setup the system. + ''; + }; + + specialisation = mkOption { + default = {}; + example = lib.literalExample "{ fewJobsManyCores.configuration = { nix.buildCores = 0; nix.maxJobs = 1; }; }"; + description = '' + Additional configurations to build. If + <literal>inheritParentConfig</literal> is true, the system + will be based on the overall system configuration. + + To switch to a specialised configuration + (e.g. <literal>fewJobsManyCores</literal>) at runtime, run: + + <screen> + <prompt># </prompt>sudo /run/current-system/specialisation/fewJobsManyCores/bin/switch-to-configuration test + </screen> + ''; + type = types.attrsOf (types.submodule ( + { ... }: { + options.inheritParentConfig = mkOption { + type = types.bool; + default = true; + description = "Include the entire system's configuration. Set to false to make a completely differently configured system."; + }; + + options.configuration = mkOption { + default = {}; + description = "Arbitrary NixOS configuration options."; + }; + }) + ); + }; + + system.boot.loader.id = mkOption { + internal = true; + default = ""; + description = '' + Id string of the used bootloader. + ''; + }; + + system.boot.loader.kernelFile = mkOption { + internal = true; + default = pkgs.stdenv.hostPlatform.platform.kernelTarget; + type = types.str; + description = '' + Name of the kernel file to be passed to the bootloader. + ''; + }; + + system.boot.loader.initrdFile = mkOption { + internal = true; + default = "initrd"; + type = types.str; + description = '' + Name of the initrd file to be passed to the bootloader. + ''; + }; + + system.copySystemConfiguration = mkOption { + type = types.bool; + default = false; + description = '' + If enabled, copies the NixOS configuration file + (usually <filename>/etc/nixos/configuration.nix</filename>) + and links it from the resulting system + (getting to <filename>/run/current-system/configuration.nix</filename>). + Note that only this single file is copied, even if it imports others. + ''; + }; + + system.extraSystemBuilderCmds = mkOption { + type = types.lines; + internal = true; + default = ""; + description = '' + This code will be added to the builder creating the system store path. + ''; + }; + + system.extraDependencies = mkOption { + type = types.listOf types.package; + default = []; + description = '' + A list of packages that should be included in the system + closure but not otherwise made available to users. This is + primarily used by the installation tests. + ''; + }; + + system.replaceRuntimeDependencies = mkOption { + default = []; + example = lib.literalExample "[ ({ original = pkgs.openssl; replacement = pkgs.callPackage /path/to/openssl { }; }) ]"; + type = types.listOf (types.submodule ( + { ... }: { + options.original = mkOption { + type = types.package; + description = "The original package to override."; + }; + + options.replacement = mkOption { + type = types.package; + description = "The replacement package."; + }; + }) + ); + apply = map ({ original, replacement, ... }: { + oldDependency = original; + newDependency = replacement; + }); + description = '' + List of packages to override without doing a full rebuild. + The original derivation and replacement derivation must have the same + name length, and ideally should have close-to-identical directory layout. + ''; + }; + + system.name = mkOption { + type = types.str; + default = + if config.networking.hostName == "" + then "unnamed" + else config.networking.hostName; + defaultText = '''networking.hostName' if non empty else "unnamed"''; + description = '' + The name of the system used in the <option>system.build.toplevel</option> derivation. + </para><para> + That derivation has the following name: + <literal>"nixos-system-''${config.system.name}-''${config.system.nixos.label}"</literal> + ''; + }; + + }; + + + config = { + + system.extraSystemBuilderCmds = + optionalString + config.system.copySystemConfiguration + ''ln -s '${import ../../../lib/from-env.nix "NIXOS_CONFIG" <nixos-config>}' \ + "$out/configuration.nix" + ''; + + system.build.toplevel = system; + + }; + +} |