···
# The container's init script, a small wrapper around the regular
# NixOS stage-2 init script.
+
renderExtraVeth = (name: cfg:
+
echo "Bringing ${name} up"
+
ip link set dev ${name} up
+
${optionalString (cfg . "localAddress" or null != null) ''
+
echo "Setting ip for ${name}"
+
ip addr add ${cfg . "localAddress"} dev ${name}
+
${optionalString (cfg . "localAddress6" or null != null) ''
+
echo "Setting ip6 for ${name}"
+
ip -6 addr add ${cfg . "localAddress6"} dev ${name}
+
${optionalString (cfg . "hostAddress" or null != null) ''
+
echo "Setting route to host for ${name}"
+
ip route add ${cfg . "hostAddress"} dev ${name}
+
${optionalString (cfg . "hostAddress6" or null != null) ''
+
echo "Setting route6 to host for ${name}"
+
ip -6 route add ${cfg . "hostAddress6"} dev ${name}
+
pkgs.writeScript "container-init"
+
#! ${pkgs.stdenv.shell} -e
+
# Initialise the container side of the veth pair.
+
if [ "$PRIVATE_NETWORK" = 1 ]; then
+
ip link set host0 name eth0
+
ip link set dev eth0 up
+
if [ -n "$LOCAL_ADDRESS" ]; then
+
ip addr add $LOCAL_ADDRESS dev eth0
+
if [ -n "$LOCAL_ADDRESS6" ]; then
+
ip -6 addr add $LOCAL_ADDRESS6 dev eth0
+
if [ -n "$HOST_ADDRESS" ]; then
+
ip route add $HOST_ADDRESS dev eth0
+
ip route add default via $HOST_ADDRESS
+
if [ -n "$HOST_ADDRESS6" ]; then
+
ip -6 route add $HOST_ADDRESS6 dev eth0
+
ip -6 route add default via $HOST_ADDRESS6
+
${concatStringsSep "\n" (mapAttrsToList renderExtraVeth cfg . "extraVeths" or {})}
+
# Start the regular stage 1 script.
+
nspawnExtraVethArgs = (name: cfg: "--network-veth-extra=${name}");
+
mkdir -p -m 0755 "$root/etc" "$root/var/lib"
+
mkdir -p -m 0700 "$root/var/lib/private" "$root/root" /run/containers
+
if ! [ -e "$root/etc/os-release" ]; then
+
touch "$root/etc/os-release"
+
if ! [ -e "$root/etc/machine-id" ]; then
+
touch "$root/etc/machine-id"
+
"/nix/var/nix/profiles/per-container/$INSTANCE" \
+
"/nix/var/nix/gcroots/per-container/$INSTANCE"
+
cp --remove-destination /etc/resolv.conf "$root/etc/resolv.conf"
if [ "$PRIVATE_NETWORK" = 1 ]; then
+
extraFlags+=" --network-veth"
+
if [ -n "$HOST_BRIDGE" ]; then
+
extraFlags+=" --network-bridge=$HOST_BRIDGE"
+
${if cfg . "extraVeths" or null != null then
+
''extraFlags+=" ${concatStringsSep " " (mapAttrsToList nspawnExtraVethArgs cfg . "extraVeths" or {})}"''
+
''# No extra veth pairs to create''
+
for iface in $INTERFACES; do
+
extraFlags+=" --network-interface=$iface"
+
for iface in $MACVLANS; do
+
extraFlags+=" --network-macvlan=$iface"
+
# If the host is 64-bit and the container is 32-bit, add a
+
${optionalString (config.nixpkgs.system == "x86_64-linux") ''
+
if [ "$(< ''${SYSTEM_PATH:-/nix/var/nix/profiles/per-container/$INSTANCE/system}/system)" = i686-linux ]; then
+
extraFlags+=" --personality=x86"
+
# Run systemd-nspawn without startup notification (we'll
+
# wait for the container systemd to signal readiness).
+
exec ${config.systemd.package}/bin/systemd-nspawn \
+
-M "$INSTANCE" -D "$root" $extraFlags \
+
--bind-ro=/nix/var/nix/db \
+
--bind-ro=/nix/var/nix/daemon-socket \
+
--bind="/nix/var/nix/profiles/per-container/$INSTANCE:/nix/var/nix/profiles" \
+
--bind="/nix/var/nix/gcroots/per-container/$INSTANCE:/nix/var/nix/gcroots" \
+
--setenv PRIVATE_NETWORK="$PRIVATE_NETWORK" \
+
--setenv HOST_BRIDGE="$HOST_BRIDGE" \
+
--setenv HOST_ADDRESS="$HOST_ADDRESS" \
+
--setenv LOCAL_ADDRESS="$LOCAL_ADDRESS" \
+
--setenv HOST_ADDRESS6="$HOST_ADDRESS6" \
+
--setenv LOCAL_ADDRESS6="$LOCAL_ADDRESS6" \
+
--setenv PATH="$PATH" \
+
${containerInit cfg} "''${SYSTEM_PATH:-/nix/var/nix/profiles/system}/init"
+
# Clean up existing machined registration and interfaces.
+
machinectl terminate "$INSTANCE" 2> /dev/null || true
+
if [ "$PRIVATE_NETWORK" = 1 ]; then
+
ip link del dev "ve-$INSTANCE" 2> /dev/null || true
+
ip link del dev "vb-$INSTANCE" 2> /dev/null || true
+
${concatStringsSep "\n" (
+
mapAttrsToList (name: cfg:
+
''ip link del dev ${name} 2> /dev/null || true ''
+
) cfg . "extraVeths" or {}
+
postStartScript = (cfg:
+
ipcall = (cfg: ipcmd: variable: attribute:
+
if cfg . attribute or null == null then
+
if [ -n "${variable}" ]; then
+
${ipcmd} add ${variable} dev $ifaceHost
+
''${ipcmd} add ${cfg . attribute} dev $ifaceHost''
+
renderExtraVeth = (name: cfg:
+
if cfg . "hostBridge" or null != null then
+
# Add ${name} to bridge ${cfg.hostBridge}
+
ip link set dev ${name} master ${cfg.hostBridge} up
+
# Set IPs and routes for ${name}
+
${optionalString (cfg . "hostAddress" or null != null) ''
+
ip addr add ${cfg . "hostAddress"} dev ${name}
+
${optionalString (cfg . "hostAddress6" or null != null) ''
+
ip -6 addr add ${cfg . "hostAddress6"} dev ${name}
+
${optionalString (cfg . "localAddress" or null != null) ''
+
ip route add ${cfg . "localAddress"} dev ${name}
+
${optionalString (cfg . "localAddress6" or null != null) ''
+
ip -6 route add ${cfg . "localAddress6"} dev ${name}
+
if [ "$PRIVATE_NETWORK" = 1 ]; then
+
if [ -z "$HOST_BRIDGE" ]; then
+
ip link set dev $ifaceHost up
+
${ipcall cfg "ip addr" "$HOST_ADDRESS" "hostAddress"}
+
${ipcall cfg "ip -6 addr" "$HOST_ADDRESS6" "hostAddress6"}
+
${ipcall cfg "ip route" "$LOCAL_ADDRESS" "localAddress"}
+
${ipcall cfg "ip -6 route" "$LOCAL_ADDRESS6" "localAddress6"}
+
${concatStringsSep "\n" (mapAttrsToList renderExtraVeth cfg . "extraVeths" or {})}
+
# Get the leader PID so that we can signal it in
+
# preStop. We can't use machinectl there because D-Bus
+
# might be shutting down. FIXME: in systemd 219 we can
+
# just signal systemd-nspawn to do a clean shutdown.
+
machinectl show "$INSTANCE" | sed 's/Leader=\(.*\)/\1/;t;d' > "/run/containers/$INSTANCE.pid"
system = config.nixpkgs.system;
···
mkBindFlags = bs: concatMapStrings mkBindFlag (lib.attrValues bs);
+
hostBridge = mkOption {
+
type = types.nullOr types.string;
+
Put the host-side of the veth-pair into the named bridge.
+
Only one of hostAddress* or hostBridge can be given.
+
hostAddress = mkOption {
+
type = types.nullOr types.str;
+
example = "10.231.136.1";
+
The IPv4 address assigned to the host interface.
+
(Not used when hostBridge is set.)
+
hostAddress6 = mkOption {
+
type = types.nullOr types.string;
+
The IPv6 address assigned to the host interface.
+
(Not used when hostBridge is set.)
+
localAddress = mkOption {
+
type = types.nullOr types.str;
+
example = "10.231.136.2";
+
The IPv4 address assigned to the interface in the container.
+
If a hostBridge is used, this should be given with netmask to access
+
the whole network. Otherwise the default netmask is /32 and routing is
+
set up from localAddress to hostAddress and back.
+
localAddress6 = mkOption {
+
type = types.nullOr types.string;
+
The IPv6 address assigned to the interface in the container.
+
If a hostBridge is used, this should be given with netmask to access
+
the whole network. Otherwise the default netmask is /128 and routing is
+
set up from localAddress6 to hostAddress6 and back.
···
type = types.listOf types.string;
example = [ "eth1" "eth2" ];
The list of interfaces to be moved into the container.
+
extraVeths = mkOption {
+
type = types.attrsOf types.optionSet;
+
options = networkOptions;
+
Extra veth-pairs to be created for the container
···
[ (mkIf options.config.isDefined {
···
environment.INSTANCE = "%i";
environment.root = "/var/lib/containers/%i";
+
preStart = preStartScript {};
+
script = startScript {};
+
postStart = postStartScript {};
···
[{ name = "container@"; value = unit; }]
++ (mapAttrsToList (name: cfg: nameValuePair "container@${name}" (
+
preStart = preStartScript cfg;
+
script = startScript cfg;
+
postStart = postStartScript cfg;
wantedBy = [ "multi-user.target" ];
wants = [ "network.target" ];
after = [ "network.target" ];
restartTriggers = [ cfg.path ];
···
LOCAL_ADDRESS6=${cfg.localAddress6}
+
INTERFACES="${toString cfg.interfaces}"
+
${optionalString cfg.autoStart ''
+
EXTRA_NSPAWN_FLAGS="${mkBindFlags cfg.bindMounts}"