162 lines
5.0 KiB
Nix
162 lines
5.0 KiB
Nix
{ 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 ];
|
|
})
|
|
];
|
|
}
|