1
1
Fork 1
mirror of https://github.com/oddlama/nix-config.git synced 2025-10-11 07:10:39 +02:00

feat(guests): derive stable machine-id for containers; always start sshd in containers

This commit is contained in:
oddlama 2023-12-19 01:15:51 +01:00
parent 054103a004
commit eafe3b673c
No known key found for this signature in database
GPG key ID: 14EFE510775FE39A
6 changed files with 74 additions and 43 deletions

View file

@ -109,7 +109,9 @@ in {
environment.persistence."/persist" = { environment.persistence."/persist" = {
hideMounts = true; hideMounts = true;
files = [ files = [
"/etc/machine-id" # For ephemeral nixos-containers we cannot link the /etc/machine-id file,
# because it will be generated based on a stable container uuid.
(lib.mkIf (!config.boot.isContainer) "/etc/machine-id")
"/etc/ssh/ssh_host_ed25519_key" "/etc/ssh/ssh_host_ed25519_key"
"/etc/ssh/ssh_host_ed25519_key.pub" "/etc/ssh/ssh_host_ed25519_key.pub"
]; ];

View file

@ -1,6 +1,9 @@
{lib, ...}: { {lib, ...}: {
services.openssh = { services.openssh = {
enable = true; enable = true;
# In containers, this is true by default, but we don't want that
# because we rely on ssh key generation for agenix
startWhenNeeded = lib.mkForce false;
authorizedKeysFiles = lib.mkForce ["/etc/ssh/authorized_keys.d/%u"]; authorizedKeysFiles = lib.mkForce ["/etc/ssh/authorized_keys.d/%u"];
settings = { settings = {
PasswordAuthentication = false; PasswordAuthentication = false;

View file

@ -12,6 +12,7 @@ in {
systemd.network.networks = { systemd.network.networks = {
"10-${guestCfg.networking.mainLinkName}" = { "10-${guestCfg.networking.mainLinkName}" = {
matchConfig.Name = guestCfg.networking.mainLinkName;
DHCP = "yes"; DHCP = "yes";
dhcpV4Config.UseDNS = false; dhcpV4Config.UseDNS = false;
dhcpV6Config.UseDNS = false; dhcpV6Config.UseDNS = false;

View file

@ -17,10 +17,13 @@ guestName: guestCfg: {
initialLinkName = "mv-${(substring 0 12 (builtins.hashString "sha256" guestName))}"; initialLinkName = "mv-${(substring 0 12 (builtins.hashString "sha256" guestName))}";
in { in {
autoStart = guestCfg.autostart;
macvlans = ["${guestCfg.container.macvlan}:${initialLinkName}"];
ephemeral = true; ephemeral = true;
privateNetwork = true; privateNetwork = true;
autoStart = guestCfg.autostart;
macvlans = ["${guestCfg.container.macvlan}:${initialLinkName}"];
extraFlags = [
"--uuid=${builtins.substring 0 32 (builtins.hashString "sha256" guestName)}"
];
bindMounts = flip mapAttrs' guestCfg.zfs ( bindMounts = flip mapAttrs' guestCfg.zfs (
_: zfsCfg: _: zfsCfg:
nameValuePair zfsCfg.guestMountpoint { nameValuePair zfsCfg.guestMountpoint {
@ -64,7 +67,7 @@ in {
# Rename the network interface to our liking # Rename the network interface to our liking
systemd.network.links = { systemd.network.links = {
"01-${guestCfg.networking.mainLinkName}" = { "01-${guestCfg.networking.mainLinkName}" = {
matchConfig.OriginalName = initialLinkName; matchConfig.Name = initialLinkName;
linkConfig.Name = guestCfg.networking.mainLinkName; linkConfig.Name = guestCfg.networking.mainLinkName;
}; };
}; };

View file

@ -9,23 +9,30 @@
inherit inherit
(lib) (lib)
attrValues attrValues
any attrsToList
disko disko
escapeShellArg escapeShellArg
makeBinPath
mapAttrsToList
mkMerge
mergeToplevelConfigs
flip flip
groupBy
listToAttrs
makeBinPath
mapAttrs
mapAttrsToList
mergeToplevelConfigs
mkIf mkIf
mkMerge
mkOption mkOption
types types
; ;
backends = ["microvm" "container"];
nodeName = config.node.name; nodeName = config.node.name;
guestsByBackend =
lib.genAttrs backends (_: {})
// mapAttrs (_: listToAttrs) (groupBy (x: x.value.backend) (attrsToList config.guests));
# Configuration required on the host for a specific guest # Configuration required on the host for a specific guest
defineGuest = guestName: guestCfg: { defineGuest = _guestName: guestCfg: {
# Add the required datasets to the disko configuration of the machine # Add the required datasets to the disko configuration of the machine
disko.devices.zpool = mkMerge (flip map (attrValues guestCfg.zfs) (zfsCfg: { disko.devices.zpool = mkMerge (flip map (attrValues guestCfg.zfs) (zfsCfg: {
${zfsCfg.pool}.datasets.${zfsCfg.dataset} = ${zfsCfg.pool}.datasets.${zfsCfg.dataset} =
@ -56,33 +63,43 @@
fi fi
''; '';
}; };
# Ensure that the zfs dataset has the correct permissions when mounted
"zfs-chown-${utils.escapeSystemdPath zfsCfg.hostMountpoint}" = {
after = [fsMountUnit];
unitConfig.DefaultDependencies = "no";
serviceConfig.Type = "oneshot";
script = ''
chmod 700 ${escapeShellArg zfsCfg.hostMountpoint}
'';
};
"microvm@${guestName}" = mkIf (guestCfg.backend == "microvm") {
requires = [fsMountUnit "zfs-chown-${utils.escapeSystemdPath zfsCfg.hostMountpoint}.service"];
after = [fsMountUnit "zfs-chown-${utils.escapeSystemdPath zfsCfg.hostMountpoint}.service"];
};
"container@${guestName}" = mkIf (guestCfg.backend == "container") {
requires = [fsMountUnit "zfs-chown-${utils.escapeSystemdPath zfsCfg.hostMountpoint}.service"];
after = [fsMountUnit "zfs-chown-${utils.escapeSystemdPath zfsCfg.hostMountpoint}.service"];
};
})); }));
};
microvm.vms.${guestName} = defineMicrovm = guestName: guestCfg: {
mkIf (guestCfg.backend == "microvm") (import ./microvm.nix guestName guestCfg attrs); # Ensure that the zfs dataset exists before it is mounted.
systemd.services."microvm@${guestName}" = let
fsMountUnits =
map
(x: "${utils.escapeSystemdPath x.hostMountpoint}.mount")
(attrValues guestCfg.zfs);
in {
requires = fsMountUnits;
after = fsMountUnits;
};
containers.${guestName} = microvm.vms.${guestName} = import ./microvm.nix guestName guestCfg attrs;
mkIf (guestCfg.backend == "container") (import ./container.nix guestName guestCfg attrs); };
defineContainer = guestName: guestCfg: {
# Ensure that the zfs dataset exists before it is mounted.
systemd.services."container@${guestName}" = let
fsMountUnits =
map
(x: "${utils.escapeSystemdPath x.hostMountpoint}.mount")
(attrValues guestCfg.zfs);
in {
requires = fsMountUnits;
after = fsMountUnits;
# Don't use the notify service type. Using exec will always consider containers
# started immediately and donesn't wait until the container is fully booted.
# Containers should behave like independent machines, and issues inside the container
# will unnecessarily lock up the service on the host otherwise.
# This causes issues on system activation.
serviceConfig.Type = lib.mkForce "exec";
};
containers.${guestName} = import ./container.nix guestName guestCfg attrs;
}; };
in { in {
imports = [ imports = [
@ -90,10 +107,7 @@ in {
inputs.microvm.nixosModules.host inputs.microvm.nixosModules.host
# This is opt-out, so we can't put this into the mkIf below # This is opt-out, so we can't put this into the mkIf below
{ {
microvm.host.enable = microvm.host.enable = guestsByBackend.microvm != {};
any
(guestCfg: guestCfg.backend == "microvm")
(attrValues config.guests);
} }
]; ];
@ -132,7 +146,7 @@ in {
}; };
backend = mkOption { backend = mkOption {
type = types.enum ["microvm" "container"]; type = types.enum backends;
description = '' description = ''
Determines how the guest will be hosted. You can currently choose Determines how the guest will be hosted. You can currently choose
between microvm based deployment, or nixos containers. between microvm based deployment, or nixos containers.
@ -216,7 +230,16 @@ in {
})); }));
}; };
config = config = mkIf (config.guests != {}) (
mkIf (config.guests != {}) mkMerge [
(mergeToplevelConfigs ["containers" "disko" "microvm" "systemd"] (mapAttrsToList defineGuest config.guests)); {
systemd.tmpfiles.rules = [
"d /guests 0700 root root -"
];
}
(mergeToplevelConfigs ["disko" "systemd"] (mapAttrsToList defineGuest config.guests))
(mergeToplevelConfigs ["containers" "systemd"] (mapAttrsToList defineContainer guestsByBackend.container))
(mergeToplevelConfigs ["microvm" "systemd"] (mapAttrsToList defineMicrovm guestsByBackend.microvm))
]
);
} }

View file

@ -1 +0,0 @@
PTlU+qtfddz0ZfcHcfZmSxZ4Abe8UCpWV2FBJQswzBk=