diff --git a/nixos/lib/eval-config.nix b/nixos/lib/eval-config.nix
index 5e1ce69158f..4b8c7354a7e 100644
--- a/nixos/lib/eval-config.nix
+++ b/nixos/lib/eval-config.nix
@@ -8,6 +8,7 @@
, extraArgs ? {}
, modules
, check ? true
+, prefix ? []
}:
let extraArgs_ = extraArgs; pkgs_ = pkgs; system_ = system; in
@@ -17,6 +18,7 @@ rec {
# Merge the option definitions in all modules, forming the full
# system configuration.
inherit (pkgs.lib.evalModules {
+ inherit prefix;
modules = modules ++ baseModules;
args = extraArgs;
check = check && options.environment.checkConfigurationOptions.value;
@@ -48,7 +50,7 @@ rec {
let
system = if nixpkgsOptions.system != "" then nixpkgsOptions.system else system_;
nixpkgsOptions = (import ./eval-config.nix {
- inherit system extraArgs modules;
+ inherit system extraArgs modules prefix;
# For efficiency, leave out most NixOS modules; they don't
# define nixpkgs.config, so it's pointless to evaluate them.
baseModules = [ ../modules/misc/nixpkgs.nix ];
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 2189d0358da..078ea225e16 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -247,11 +247,11 @@
./system/boot/kexec.nix
./system/boot/loader/efi.nix
./system/boot/loader/generations-dir/generations-dir.nix
- ./system/boot/loader/gummiboot/gummiboot.nix
- ./system/boot/loader/raspberrypi/raspberrypi.nix
./system/boot/loader/grub/grub.nix
./system/boot/loader/grub/memtest.nix
+ ./system/boot/loader/gummiboot/gummiboot.nix
./system/boot/loader/init-script/init-script.nix
+ ./system/boot/loader/raspberrypi/raspberrypi.nix
./system/boot/luksroot.nix
./system/boot/modprobe.nix
./system/boot/shutdown.nix
@@ -276,6 +276,7 @@
./tasks/scsi-link-power-management.nix
./tasks/swraid.nix
./testing/service-runner.nix
+ ./virtualisation/containers.nix
./virtualisation/libvirtd.nix
#./virtualisation/nova.nix
./virtualisation/virtualbox-guest.nix
diff --git a/nixos/modules/services/hardware/udev.nix b/nixos/modules/services/hardware/udev.nix
index 516569c0280..52b3ad43579 100644
--- a/nixos/modules/services/hardware/udev.nix
+++ b/nixos/modules/services/hardware/udev.nix
@@ -209,7 +209,7 @@ in
###### implementation
- config = {
+ config = mkIf (!config.boot.isContainer) {
services.udev.extraRules = nixosRules;
@@ -231,9 +231,16 @@ in
boot.extraModprobeConfig = "options firmware_class path=${config.hardware.firmware}";
- system.activationScripts.clearHotplug =
+ system.activationScripts.udevd =
''
echo "" > /proc/sys/kernel/hotplug
+
+ # Regenerate the hardware database /var/lib/udev/hwdb.bin
+ # whenever systemd changes.
+ if [ ! -e /var/lib/udev/prev-systemd -o "$(readlink /var/lib/udev/prev-systemd)" != ${config.systemd.package} ]; then
+ echo "regenerating udev hardware database..."
+ ${config.systemd.package}/bin/udevadm hwdb --update && ln -sfn ${config.systemd.package} /var/lib/udev/prev-systemd
+ fi
'';
};
diff --git a/nixos/modules/system/activation/top-level.nix b/nixos/modules/system/activation/top-level.nix
index ada96131675..4146cd8394a 100644
--- a/nixos/modules/system/activation/top-level.nix
+++ b/nixos/modules/system/activation/top-level.nix
@@ -34,16 +34,24 @@ let
in ''
mkdir $out
- if [ ! -f ${kernelPath} ]; then
- echo "The bootloader cannot find the proper kernel image."
- echo "(Expecting ${kernelPath})"
- false
- fi
+ # 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
+ ln -s ${kernelPath} $out/kernel
+ ln -s ${config.system.modulesTree} $out/kernel-modules
- ln -s ${config.system.build.initialRamdisk}/initrd $out/initrd
+ echo -n "$kernelParams" > $out/kernel-params
+
+ ln -s ${config.system.build.initialRamdisk}/initrd $out/initrd
+
+ ln -s ${config.hardware.firmware} $out/firmware
+ ''}
echo "$activationScript" > $out/activate
substituteInPlace $out/activate --subst-var out
@@ -56,9 +64,7 @@ let
ln -s ${config.system.build.etc}/etc $out/etc
ln -s ${config.system.path} $out/sw
ln -s "$systemd" $out/systemd
- ln -s ${config.hardware.firmware} $out/firmware
- echo -n "$kernelParams" > $out/kernel-params
echo -n "$configurationName" > $out/configuration-name
echo -n "systemd ${toString config.systemd.package.interfaceVersion}" > $out/init-interface-version
echo -n "$nixosVersion" > $out/nixos-version
@@ -92,7 +98,6 @@ let
systemd = config.systemd.package;
inherit children;
- 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";
diff --git a/nixos/modules/system/boot/kernel.nix b/nixos/modules/system/boot/kernel.nix
index c3c38b186bd..ee2f5e9b4f6 100644
--- a/nixos/modules/system/boot/kernel.nix
+++ b/nixos/modules/system/boot/kernel.nix
@@ -145,7 +145,7 @@ in
###### implementation
- config = {
+ config = mkIf (!config.boot.isContainer) {
system.build = { inherit kernel; };
diff --git a/nixos/modules/system/boot/loader/grub/grub.nix b/nixos/modules/system/boot/loader/grub/grub.nix
index 8b3923e30a0..ef6ff71ed77 100644
--- a/nixos/modules/system/boot/loader/grub/grub.nix
+++ b/nixos/modules/system/boot/loader/grub/grub.nix
@@ -44,7 +44,7 @@ in
boot.loader.grub = {
enable = mkOption {
- default = true;
+ default = !config.boot.isContainer;
type = types.bool;
description = ''
Whether to enable the GNU GRUB boot loader.
diff --git a/nixos/modules/system/boot/modprobe.nix b/nixos/modules/system/boot/modprobe.nix
index 39928da8d19..027a7ac99d5 100644
--- a/nixos/modules/system/boot/modprobe.nix
+++ b/nixos/modules/system/boot/modprobe.nix
@@ -66,7 +66,7 @@ with pkgs.lib;
###### implementation
- config = {
+ config = mkIf (!config.boot.isContainer) {
environment.etc = singleton
{ source = pkgs.writeText "modprobe.conf"
diff --git a/nixos/modules/system/boot/stage-1.nix b/nixos/modules/system/boot/stage-1.nix
index 7f7184b1e45..8ed3aecb691 100644
--- a/nixos/modules/system/boot/stage-1.nix
+++ b/nixos/modules/system/boot/stage-1.nix
@@ -328,7 +328,7 @@ in
};
- config = {
+ config = mkIf (!config.boot.isContainer) {
assertions = singleton
{ assertion = any (fs: fs.mountPoint == "/") (attrValues config.fileSystems);
diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix
index 143f923813d..9f5a7678c85 100644
--- a/nixos/modules/system/boot/systemd.nix
+++ b/nixos/modules/system/boot/systemd.nix
@@ -604,13 +604,6 @@ in
mkdir -p /var/log/journal
chmod 0755 /var/log/journal
- # Regenerate the hardware database /var/lib/udev/hwdb.bin
- # whenever systemd changes.
- if [ ! -e /var/lib/udev/prev-systemd -o "$(readlink /var/lib/udev/prev-systemd)" != ${systemd} ]; then
- echo "regenerating udev hardware database..."
- ${systemd}/bin/udevadm hwdb --update && ln -sfn ${systemd} /var/lib/udev/prev-systemd
- fi
-
# Make all journals readable to users in the wheel and adm
# groups, in addition to those in the systemd-journal group.
# Users can always read their own journals.
diff --git a/nixos/modules/virtualisation/containers.nix b/nixos/modules/virtualisation/containers.nix
new file mode 100644
index 00000000000..bcbfaacd703
--- /dev/null
+++ b/nixos/modules/virtualisation/containers.nix
@@ -0,0 +1,137 @@
+{ config, pkgs, ... }:
+
+with pkgs.lib;
+
+{
+ options = {
+
+ boot.isContainer = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether this NixOS machine is a lightweight container running
+ in another NixOS system.
+ '';
+ };
+
+ systemd.containers = mkOption {
+ type = types.attrsOf (types.submodule (
+ { config, options, name, ... }:
+ {
+ options = {
+
+ root = mkOption {
+ type = types.path;
+ description = ''
+ The root directory of the container.
+ '';
+ };
+
+ config = mkOption {
+ description = ''
+ A specification of the desired configuration of this
+ container, as a NixOS module.
+ '';
+ };
+
+ path = mkOption {
+ type = types.path;
+ example = "/nix/var/nix/profiles/containers/webserver";
+ description = ''
+ As an alternative to specifying
+ , you can specify the path to
+ the evaluated NixOS system configuration, typically a
+ symlink to a system profile.
+ '';
+ };
+
+ };
+
+ config = mkMerge
+ [ { root = mkDefault "/var/lib/containers/${name}";
+ }
+ (mkIf options.config.isDefined {
+ path = (import ../../lib/eval-config.nix {
+ modules =
+ let extraConfig =
+ { boot.isContainer = true;
+ security.initialRootPassword = "!";
+ networking.hostName = mkDefault name;
+ };
+ in [ extraConfig config.config ];
+ prefix = [ "systemd" "containers" name ];
+ }).config.system.build.toplevel;
+ })
+ ];
+ }));
+
+ default = {};
+ example = literalExample
+ ''
+ { webserver =
+ { root = "/containers/webserver";
+ path = "/nix/var/nix/profiles/webserver";
+ };
+ database =
+ { root = "/containers/database";
+ config =
+ { config, pkgs, ... }:
+ { services.postgresql.enable = true;
+ services.postgresql.package = pkgs.postgresql92;
+ };
+ };
+ }
+ '';
+ description = ''
+ A set of NixOS system configurations to be run as lightweight
+ containers. Each container appears as a service
+ container-name
+ on the host system, allowing it to be started and stopped via
+ systemctl .
+ '';
+ };
+
+ };
+
+
+ config = {
+
+ systemd.services = mapAttrs' (name: container: nameValuePair "container-${name}"
+ { description = "Container '${name}'";
+
+ wantedBy = [ "multi-user.target" ];
+
+ unitConfig.RequiresMountsFor = [ container.root ];
+
+ preStart =
+ ''
+ mkdir -p -m 0755 ${container.root}/etc
+ if ! [ -e ${container.root}/etc/os-release ]; then
+ touch ${container.root}/etc/os-release
+ fi
+ '';
+
+ serviceConfig.ExecStart =
+ "${config.systemd.package}/bin/systemd-nspawn -M ${name} -D ${container.root} --bind-ro=/nix ${container.path}/init";
+
+ preStop =
+ ''
+ pid="$(cat /sys/fs/cgroup/systemd/machine/${name}.nspawn/system/tasks 2> /dev/null)"
+ if [ -n "$pid" ]; then
+ # Send the RTMIN+3 signal, which causes the container
+ # systemd to start halt.target.
+ echo "killing container systemd, PID = $pid"
+ kill -RTMIN+3 $pid
+ # Wait for the container to exit. We can't let systemd
+ # do this because it will send a signal to the entire
+ # cgroup.
+ for ((n = 0; n < 180; n++)); do
+ if ! kill -0 $pid 2> /dev/null; then break; fi
+ sleep 1
+ done
+ fi
+ '';
+ }) config.systemd.containers;
+
+ };
+}
\ No newline at end of file