From e38f96eedb1328399c605aa78f380eb0d3bc7993 Mon Sep 17 00:00:00 2001 From: Artem Sheremet Date: Thu, 30 Apr 2026 13:12:03 +0200 Subject: [PATCH] Move linux-lxc away from linux-headless Not every linux-headless is LXC; e.g. venus --- flake.nix | 2 + modules/nixos/linux-headless.nix | 70 ----------------------------- modules/nixos/linux-lxc.nix | 75 ++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 70 deletions(-) create mode 100644 modules/nixos/linux-lxc.nix diff --git a/flake.nix b/flake.nix index bf3ff1d..70a5bf4 100644 --- a/flake.nix +++ b/flake.nix @@ -67,6 +67,7 @@ }; nixosModules = { linux-headless = import ./modules/nixos/linux-headless.nix; + linux-lxc = import ./modules/nixos/linux-lxc.nix; }; homeConfigurations."artem@deimos" = home-manager.lib.homeManagerConfiguration { @@ -116,6 +117,7 @@ }; modules = [ self.nixosModules.linux-headless + self.nixosModules.linux-lxc inputs.fw_nix.nixosModules.nix-gc inputs.fw_nix.nixosModules.nix-settings inputs.fw_nix.nixosModules.tools diff --git a/modules/nixos/linux-headless.nix b/modules/nixos/linux-headless.nix index a186996..37506d9 100644 --- a/modules/nixos/linux-headless.nix +++ b/modules/nixos/linux-headless.nix @@ -1,70 +1,8 @@ { - modulesPath, pkgs, ... }: { - imports = [ - "${modulesPath}/virtualisation/lxc-container.nix" - ]; - # Disable legacy channel behavior that lxc-container brings in via installer/cd-dvd/channel.nix. - system.installer.channel.enable = false; - - # Impermanence setup: - # 1. There's no initrd/stage 1 in LXC container; /sbin/init is invoked after - # LXC finishes setting up special and user-configured filesystems. Any - # options in boot.initrd, as well as neededForBoot fileSystems won't be - # respected. - # 2. Non-boot fileSystems (aka systemd) mount too late for systemd or nixos - # persistence to be instantiated, so we have to create this script below. - # 3. The expectation from host is to mount /home and /nix. Root filesystem - # will also be a disk, as that's Incus requirement; the host should clean - # it up periodically using: "incus rebuild --empty ". - # 4. Since rootfs will be empty after rebuild, you have to point LXC at the - # current init (instead of /sbin/init), by adding to the "config:" section - # in "incus config edit ": - # raw.lxc: lxc.init.cmd = /nix/var/nix/profiles/system/init - system.activationScripts.persistence = { - deps = [ "specialfs" ]; - text = '' - persist() { - local item="$1" - local constructor="''${item%%:*}" - local target="''${item#*:}" - - mkdir -p "$(dirname "$target")" - $constructor "$target" - - if ! mountpoint -q "$target"; then - local source="/home/persistent/$target" - - mkdir -p "$(dirname "$source")" - $constructor "$source" - - mount --bind "$source" "$target" - fi - } - - for item in \ - "mkdir -p:/var/lib/nixos" \ - "mkdir -p:/var/lib/systemd" \ - "touch:/etc/machine-id" \ - "touch:/etc/ssh/ssh_host_ed25519_key" \ - ; do - persist "$item" - done - - chmod 0600 /etc/ssh/ssh_host_ed25519_key - - # lxc-container.nix installBootloader/installInitScript will attempt to - # symlink /sbin/init, so we have to create the parent directory. - mkdir -p /sbin - ''; - }; - system.activationScripts.users.deps = [ "persistence" ]; - # This is supposed to persist machine-id, but fails. - systemd.services.systemd-machine-id-commit.enable = false; - # Create /etc/zshrc that loads the nix-darwin environment. programs.zsh.enable = true; security.sudo.wheelNeedsPassword = false; @@ -79,13 +17,5 @@ '') ]; - # unprivileged LXCs can't set net.ipv4.ping_group_range - security.wrappers.ping = { - owner = "root"; - group = "root"; - capabilities = "cap_net_raw+p"; - source = "${pkgs.iputils.out}/bin/ping"; - }; - system.stateVersion = "25.11"; # Never change this. } diff --git a/modules/nixos/linux-lxc.nix b/modules/nixos/linux-lxc.nix new file mode 100644 index 0000000..1967da6 --- /dev/null +++ b/modules/nixos/linux-lxc.nix @@ -0,0 +1,75 @@ +{ + modulesPath, + pkgs, + ... +}: +{ + imports = [ + "${modulesPath}/virtualisation/lxc-container.nix" + ]; + # Disable legacy channel behavior that lxc-container brings in via installer/cd-dvd/channel.nix. + system.installer.channel.enable = false; + + # Impermanence setup: + # 1. There's no initrd/stage 1 in LXC container; /sbin/init is invoked after + # LXC finishes setting up special and user-configured filesystems. Any + # options in boot.initrd, as well as neededForBoot fileSystems won't be + # respected. + # 2. Non-boot fileSystems (aka systemd) mount too late for systemd or nixos + # persistence to be instantiated, so we have to create this script below. + # 3. The expectation from host is to mount /home and /nix. Root filesystem + # will also be a disk, as that's Incus requirement; the host should clean + # it up periodically using: "incus rebuild --empty ". + # 4. Since rootfs will be empty after rebuild, you have to point LXC at the + # current init (instead of /sbin/init), by adding to the "config:" section + # in "incus config edit ": + # raw.lxc: lxc.init.cmd = /nix/var/nix/profiles/system/init + system.activationScripts.persistence = { + deps = [ "specialfs" ]; + text = '' + persist() { + local item="$1" + local constructor="''${item%%:*}" + local target="''${item#*:}" + + mkdir -p "$(dirname "$target")" + $constructor "$target" + + if ! mountpoint -q "$target"; then + local source="/home/persistent/$target" + + mkdir -p "$(dirname "$source")" + $constructor "$source" + + mount --bind "$source" "$target" + fi + } + + for item in \ + "mkdir -p:/var/lib/nixos" \ + "mkdir -p:/var/lib/systemd" \ + "touch:/etc/machine-id" \ + "touch:/etc/ssh/ssh_host_ed25519_key" \ + ; do + persist "$item" + done + + chmod 0600 /etc/ssh/ssh_host_ed25519_key + + # lxc-container.nix installBootloader/installInitScript will attempt to + # symlink /sbin/init, so we have to create the parent directory. + mkdir -p /sbin + ''; + }; + system.activationScripts.users.deps = [ "persistence" ]; + # This is supposed to persist machine-id, but fails. + systemd.services.systemd-machine-id-commit.enable = false; + + # unprivileged LXCs can't set net.ipv4.ping_group_range + security.wrappers.ping = { + owner = "root"; + group = "root"; + capabilities = "cap_net_raw+p"; + source = "${pkgs.iputils.out}/bin/ping"; + }; +}