Implement initial containers module
This commit is contained in:
161
nixos/modules/containers.nix
Normal file
161
nixos/modules/containers.nix
Normal file
@@ -0,0 +1,161 @@
|
||||
{ lib, options, config, systems, ... }:
|
||||
let
|
||||
inherit (builtins) attrNames attrValues mapAttrs;
|
||||
inherit (lib) concatMapStringsSep filterAttrs mkDefault mkIf mkMerge mkAliasDefinitions mkVMOverride mkAfter;
|
||||
inherit (lib.my) mkOpt';
|
||||
|
||||
cfg = config.my.containers;
|
||||
|
||||
devVMKeyPath = "/run/dev.key";
|
||||
|
||||
containerOpts = with lib.types; { name, ... }: {
|
||||
options = {
|
||||
system = mkOpt' unspecified systems."${name}".configuration.config.my.buildAs.container
|
||||
"Top-level system configuration.";
|
||||
opts = mkOpt' lib.my.naiveModule { } "Options to pass to `containers.*name*`.";
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
options.my.containers = with lib.types; {
|
||||
networking = {
|
||||
bridgeName = mkOpt' str "containers" "Name of host bridge.";
|
||||
hostAddresses = mkOpt' (either str (listOf str)) "172.16.137.1/24" "Addresses for the host bridge.";
|
||||
};
|
||||
persistDir = mkOpt' str "/persist/containers" "Where to store container persistence data.";
|
||||
instances = mkOpt' (attrsOf (submodule containerOpts)) { } "Individual containers.";
|
||||
};
|
||||
|
||||
config = mkMerge [
|
||||
(mkIf (cfg.instances != { }) {
|
||||
assertions = [
|
||||
{
|
||||
assertion = config.systemd.network.enable;
|
||||
message = "Containers currently require systemd-networkd!";
|
||||
}
|
||||
];
|
||||
|
||||
my.firewall.trustedInterfaces = [ cfg.networking.bridgeName ];
|
||||
|
||||
systemd = {
|
||||
network = {
|
||||
netdevs."25-container-bridge".netdevConfig = {
|
||||
Name = cfg.networking.bridgeName;
|
||||
Kind = "bridge";
|
||||
};
|
||||
# Based on the pre-installed 80-container-vz
|
||||
networks."80-container-vb" = {
|
||||
matchConfig = {
|
||||
Name = "vb-*";
|
||||
Driver = "veth";
|
||||
};
|
||||
networkConfig = {
|
||||
# systemd LLDP doesn't work on bridge interfaces
|
||||
LLDP = true;
|
||||
EmitLLDP = "customer-bridge";
|
||||
# Although nspawn will set the veth's master, systemd will clear it (systemd 250 adds a `KeepMaster`
|
||||
# to avoid this)
|
||||
Bridge = cfg.networking.bridgeName;
|
||||
};
|
||||
};
|
||||
networks."80-containers-bridge" = {
|
||||
matchConfig = {
|
||||
Name = cfg.networking.bridgeName;
|
||||
Driver = "bridge";
|
||||
};
|
||||
networkConfig = {
|
||||
Address = cfg.networking.hostAddresses;
|
||||
DHCPServer = true;
|
||||
# TODO: Configuration for routed IPv6 (and maybe IPv4)
|
||||
IPMasquerade = "both";
|
||||
IPv6SendRA = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
tmpfiles.rules = map (n: "d ${cfg.persistDir}/${n} 0755 root root") (attrNames cfg.instances);
|
||||
};
|
||||
|
||||
containers = mapAttrs (n: c: mkMerge [
|
||||
{
|
||||
path = "/nix/var/nix/profiles/per-container/${n}";
|
||||
ephemeral = true;
|
||||
autoStart = mkDefault true;
|
||||
bindMounts = {
|
||||
"/persist" = {
|
||||
hostPath = "${cfg.persistDir}/${n}";
|
||||
isReadOnly = false;
|
||||
};
|
||||
};
|
||||
|
||||
privateNetwork = true;
|
||||
hostBridge = cfg.networking.bridgeName;
|
||||
additionalCapabilities = [ "CAP_NET_ADMIN" ];
|
||||
}
|
||||
c.opts
|
||||
|
||||
(mkIf config.my.build.isDevVM {
|
||||
path = mkVMOverride c.system;
|
||||
bindMounts."${devVMKeyPath}" = {
|
||||
hostPath = config.my.secrets.vmKeyPath;
|
||||
isReadOnly = true;
|
||||
};
|
||||
})
|
||||
]) cfg.instances;
|
||||
})
|
||||
|
||||
# Inside container
|
||||
(mkIf config.boot.isContainer {
|
||||
assertions = [
|
||||
{
|
||||
assertion = config.systemd.network.enable;
|
||||
message = "Containers currently require systemd-networkd!";
|
||||
}
|
||||
];
|
||||
|
||||
my = {
|
||||
tmproot.enable = true;
|
||||
};
|
||||
|
||||
system.activationScripts = {
|
||||
# impermanence will throw a fit and bail the whole activation script if this already exists (the container
|
||||
# start script pre-creates it for some reason)
|
||||
clearMachineId.text = "rm -f /etc/machine-id";
|
||||
createPersistentStorageDirs.deps = [ "clearMachineId" ];
|
||||
|
||||
# Ordinarily I think the Nix daemon does this but ofc it doesn't in the container
|
||||
createNixPerUserDirs = {
|
||||
text =
|
||||
let
|
||||
users = attrValues (filterAttrs (_: u: u.isNormalUser) config.users.users);
|
||||
in
|
||||
concatMapStringsSep "\n"
|
||||
(u: ''install -d -o ${u.name} -g ${u.group} /nix/var/nix/{profiles,gcroots}/per-user/"${u.name}"'') users;
|
||||
deps = [ "users" "groups" ];
|
||||
};
|
||||
};
|
||||
|
||||
networking = {
|
||||
useHostResolvConf = false;
|
||||
};
|
||||
# Based on the pre-installed 80-container-host0
|
||||
systemd.network.networks."80-container-eth0" = {
|
||||
matchConfig = {
|
||||
Name = "eth0";
|
||||
Virtualization = "container";
|
||||
};
|
||||
networkConfig = {
|
||||
DHCP = "yes";
|
||||
LLDP = true;
|
||||
EmitLLDP = "customer-bridge";
|
||||
};
|
||||
dhcpConfig = {
|
||||
UseTimezone = true;
|
||||
};
|
||||
};
|
||||
|
||||
# If the host is a dev VM
|
||||
age.identityPaths = [ devVMKeyPath ];
|
||||
})
|
||||
];
|
||||
}
|
Reference in New Issue
Block a user