Modular, context-aware and aspect-oriented dendritic Nix configurations. Discussions: https://oeiuwq.zulipchat.com/join/nqp26cd4kngon6mo3ncgnuap/ den.oeiuwq.com
configurations den dendritic nix aspect oriented

feat(template): MicroVM example integration (#240)

authored by oeiuwq.com and committed by

GitHub 9dd55dc1 a63a4c3b

+456 -30
+9 -25
.github/workflows/test.yml
··· 75 75 EOF 76 76 git add templates/noflake/modules/ci-runtime.nix 77 77 - run: cd templates/noflake && nix-build -A flake.nixosConfigurations.igloo.config.system.build.toplevel 78 - minimal: 79 - needs: [approved] 80 - name: minimal 81 - runs-on: ubuntu-latest 82 - steps: 83 - - uses: wimpysworld/nothing-but-nix@main 84 - - uses: cachix/install-nix-action@v31 85 - - uses: DeterminateSystems/magic-nix-cache-action@v13 86 - - uses: actions/checkout@v4 87 - - run: | 88 - cat <<-EOF > templates/minimal/modules/ci-runtime.nix 89 - { 90 - _module.args.CI = true; 91 - } 92 - EOF 93 - git add templates/minimal/modules/ci-runtime.nix 94 - - run: nix flake check -L ./templates/minimal --override-input den github:$GITHUB_REPOSITORY/$GITHUB_SHA 95 - bogus: 78 + template: 96 79 needs: [approved] 97 80 strategy: 98 81 matrix: 99 - os: [ubuntu-latest, macos-latest] 100 - name: Bogus ${{matrix.os}} 82 + template: [bogus, minimal, microvm] 83 + os: [ubuntu-latest] 84 + name: Check template ${{matrix.template}} ${{matrix.os}} 101 85 runs-on: ${{matrix.os}} 102 86 steps: 103 87 - uses: wimpysworld/nothing-but-nix@main ··· 105 89 - uses: DeterminateSystems/magic-nix-cache-action@v13 106 90 - uses: actions/checkout@v4 107 91 - run: | 108 - cat <<-EOF > templates/bogus/modules/ci-runtime.nix 92 + cat <<-EOF > templates/${{matrix.template}}/modules/ci-runtime.nix 109 93 { 110 94 _module.args.CI = true; 111 95 } 112 96 EOF 113 - git add templates/bogus/modules/ci-runtime.nix 114 - - run: nix flake check -L ./templates/bogus --override-input den github:$GITHUB_REPOSITORY/$GITHUB_SHA 115 - template: 97 + git add templates/${{matrix.template}}/modules/ci-runtime.nix 98 + - run: nix flake check -L ./templates/${{matrix.template}} --override-input den github:$GITHUB_REPOSITORY/$GITHUB_SHA 99 + flake-file-template: 116 100 needs: [approved] 117 101 strategy: 118 102 # max-parallel: 1 119 103 matrix: 120 - os: [ubuntu-latest, macos-latest] 104 + os: [ubuntu-latest] 121 105 template: [default, example] 122 106 name: Check template ${{matrix.template}} ${{matrix.os}} 123 107 runs-on: ${{matrix.os}}
+8 -1
modules/config.nix
··· 9 9 builder: cfg: 10 10 let 11 11 items = map builtins.attrValues (builtins.attrValues cfg); 12 - buildItem = item: lib.setAttrByPath item.intoAttr (builder item); 12 + buildItem = 13 + item: 14 + if 15 + item.intoAttr == [ ] # no output requested 16 + then 17 + { } 18 + else 19 + lib.setAttrByPath item.intoAttr (builder item); 13 20 built = map buildItem (lib.flatten items); 14 21 in 15 22 built;
+5 -1
nix/ctx-apply.nix
··· 26 26 ] 27 27 ++ lib.concatLists ( 28 28 lib.mapAttrsToList ( 29 - name: into: lib.concatMap (transformAll self den.ctx.${name}) (into ctx) 29 + name: into: 30 + if !builtins.hasAttr name den.ctx then 31 + [ ] 32 + else 33 + lib.concatMap (transformAll self den.ctx.${name}) (into ctx) 30 34 ) self.into 31 35 ); 32 36
+2
nix/default.nix
··· 17 17 noflake.description = "Den without flake"; 18 18 example.path = ../templates/example; 19 19 example.description = "Example"; 20 + microvm.path = ../templates/microvm; 21 + microvm.description = "MicroVM example"; 20 22 ci.path = ../templates/ci; 21 23 ci.description = "Feature Tests"; 22 24 bogus.path = ../templates/bogus;
+2
templates/microvm/.gitignore
··· 1 + *.img 2 + *.socket
+26
templates/microvm/README.md
··· 1 + # MicroVM in Den examples 2 + 3 + There are two ways to run VMs: 4 + 5 + ## NixOS configuration MicroVM runner as package. 6 + 7 + - Den example: [runnable-example.nix](./modules/runnable-example.nix) 8 + - Den support: [microvm-runners.nix](./modules/microvm-runners.nix) 9 + 10 + ```console 11 + nix run .#runnable-microvm 12 + ``` 13 + 14 + See https://microvm-nix.github.io/microvm.nix/packages.html 15 + 16 + ## MicroVM guests as part of a Host. 17 + 18 + - Den example: [guests-example.nix](./modules/guests-example.nix) 19 + - Den support: [microvm-integration.nix](./modules/microvm-integration.nix) 20 + 21 + ```console 22 + nixos-rebuild build --flake .#server 23 + ``` 24 + 25 + See https://microvm-nix.github.io/microvm.nix/host.html\ 26 + https://microvm-nix.github.io/microvm.nix/declarative.html
+113
templates/microvm/flake.lock
··· 1 + { 2 + "nodes": { 3 + "den": { 4 + "locked": { 5 + "lastModified": 1772891845, 6 + "narHash": "sha256-sh0T1cJt45S8FM1RS0goajFfbQ/oop8XfIU8iG6Ww1k=", 7 + "owner": "vic", 8 + "repo": "den", 9 + "rev": "a63a4c3bb5ea2674cd8b941635e63afbc5a4bc9f", 10 + "type": "github" 11 + }, 12 + "original": { 13 + "owner": "vic", 14 + "repo": "den", 15 + "type": "github" 16 + } 17 + }, 18 + "flake-aspects": { 19 + "locked": { 20 + "lastModified": 1772749648, 21 + "narHash": "sha256-6QtwbL/R0RhJrHIDc1SdJD0YYfVXG9yteiFkfNl0Rbg=", 22 + "owner": "vic", 23 + "repo": "flake-aspects", 24 + "rev": "ccc25fc1e06b8957e15e6d0c0a0c51e9d7a96b37", 25 + "type": "github" 26 + }, 27 + "original": { 28 + "owner": "vic", 29 + "repo": "flake-aspects", 30 + "type": "github" 31 + } 32 + }, 33 + "import-tree": { 34 + "locked": { 35 + "lastModified": 1772344373, 36 + "narHash": "sha256-OQQ1MhB9t1J71b2wxRRTdH/Qd8UGG0p+dGspfCf5U1c=", 37 + "owner": "vic", 38 + "repo": "import-tree", 39 + "rev": "10fda59eee7d7970ec443b925f32a1bc7526648c", 40 + "type": "github" 41 + }, 42 + "original": { 43 + "owner": "vic", 44 + "repo": "import-tree", 45 + "type": "github" 46 + } 47 + }, 48 + "microvm": { 49 + "inputs": { 50 + "nixpkgs": [ 51 + "nixpkgs" 52 + ], 53 + "spectrum": "spectrum" 54 + }, 55 + "locked": { 56 + "lastModified": 1772922713, 57 + "narHash": "sha256-+dn2D7gNrrld3q/AapoZZ6HL8xnBS/pcV4Gye1Nfsg0=", 58 + "owner": "microvm-nix", 59 + "repo": "microvm.nix", 60 + "rev": "6207a74a1ec31d3aa628cb98eb75795a10f49dea", 61 + "type": "github" 62 + }, 63 + "original": { 64 + "owner": "microvm-nix", 65 + "repo": "microvm.nix", 66 + "type": "github" 67 + } 68 + }, 69 + "nixpkgs": { 70 + "locked": { 71 + "lastModified": 1772736753, 72 + "narHash": "sha256-au/m3+EuBLoSzWUCb64a/MZq6QUtOV8oC0D9tY2scPQ=", 73 + "owner": "nixos", 74 + "repo": "nixpkgs", 75 + "rev": "917fec990948658ef1ccd07cef2a1ef060786846", 76 + "type": "github" 77 + }, 78 + "original": { 79 + "owner": "nixos", 80 + "ref": "nixpkgs-unstable", 81 + "repo": "nixpkgs", 82 + "type": "github" 83 + } 84 + }, 85 + "root": { 86 + "inputs": { 87 + "den": "den", 88 + "flake-aspects": "flake-aspects", 89 + "import-tree": "import-tree", 90 + "microvm": "microvm", 91 + "nixpkgs": "nixpkgs" 92 + } 93 + }, 94 + "spectrum": { 95 + "flake": false, 96 + "locked": { 97 + "lastModified": 1759482047, 98 + "narHash": "sha256-H1wiXRQHxxPyMMlP39ce3ROKCwI5/tUn36P8x6dFiiQ=", 99 + "ref": "refs/heads/main", 100 + "rev": "c5d5786d3dc938af0b279c542d1e43bce381b4b9", 101 + "revCount": 996, 102 + "type": "git", 103 + "url": "https://spectrum-os.org/git/spectrum" 104 + }, 105 + "original": { 106 + "type": "git", 107 + "url": "https://spectrum-os.org/git/spectrum" 108 + } 109 + } 110 + }, 111 + "root": "root", 112 + "version": 7 113 + }
+27
templates/microvm/flake.nix
··· 1 + { 2 + # Den adapted from default microvm template. 3 + description = "NixOS in MicroVMs with Den"; 4 + 5 + nixConfig = { 6 + extra-substituters = [ "https://microvm.cachix.org" ]; 7 + extra-trusted-public-keys = [ "microvm.cachix.org-1:oXnBc6hRE3eX5rSYdRyMYXnfzcCxC7yKPTbZXALsqys=" ]; 8 + }; 9 + 10 + inputs.den.url = "github:vic/den"; 11 + inputs.flake-aspects.url = "github:vic/flake-aspects"; 12 + inputs.import-tree.url = "github:vic/import-tree"; 13 + 14 + inputs.nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; 15 + 16 + inputs.microvm = { 17 + url = "github:microvm-nix/microvm.nix"; 18 + inputs.nixpkgs.follows = "nixpkgs"; 19 + }; 20 + 21 + outputs = 22 + inputs: 23 + (inputs.nixpkgs.lib.evalModules { 24 + modules = [ (inputs.import-tree ./modules) ]; 25 + specialArgs = { inherit inputs; }; 26 + }).config.flake; 27 + }
+21
templates/microvm/modules/den.nix
··· 1 + { den, inputs, ... }: 2 + { 3 + imports = [ inputs.den.flakeModule ]; 4 + 5 + # 6 + # There are two ways to run VMS: 7 + # 8 + # - NixOS configuration MicroVM runner as package. 9 + # Den example: ./runnable-example.nix (by ./microvm-runners.nix) 10 + # See https://microvm-nix.github.io/microvm.nix/packages.html 11 + # 12 + # 13 + # - MicroVM guests as part of a Host. 14 + # Den example: ./guests-example.nix (by ./microvm-integration.nix) 15 + # See https://microvm-nix.github.io/microvm.nix/host.html 16 + # https://microvm-nix.github.io/microvm.nix/declarative.html 17 + # 18 + 19 + # automatically set hostname on all hosts. 20 + den.ctx.host.includes = [ den._.hostname ]; 21 + }
+38
templates/microvm/modules/guests-example.nix
··· 1 + { den, lib, ... }: 2 + { 3 + 4 + den.hosts.x86_64-linux.server.microvm.guests = [ 5 + den.hosts.x86_64-linux.guest-microvm 6 + ]; 7 + 8 + den.hosts.x86_64-linux.guest-microvm = { 9 + intoAttr = [ ]; # dont produce Guest nixosConfiguration at flake output 10 + }; 11 + 12 + den.aspects.no-boot.nixos = { 13 + boot.loader.grub.enable = false; 14 + fileSystems."/".device = "/dev/null"; 15 + }; 16 + 17 + den.aspects.server = { 18 + # USER TODO: remove this on real bootable server 19 + includes = [ den.aspects.no-boot ]; 20 + 21 + # NOTE: no microvm class exist for Host, only for Guests 22 + nixos.microvm.host.startupTimeout = 300; 23 + }; 24 + 25 + den.aspects.guest-microvm = { 26 + # resolved with: `(den.ctx.host = { host = guest-microvm }).resolve { class = "nixos" }` 27 + # resulting nixos-module is set at server: microvm.vms.<name>.config 28 + nixos = 29 + { pkgs, ... }: 30 + { 31 + environment.systemPackages = [ pkgs.cowsay ]; 32 + }; 33 + 34 + # microvm class is for Guests!, forwarded into server: nixos.microvm.vms.<name> 35 + microvm.autostart = true; 36 + }; 37 + 38 + }
+116
templates/microvm/modules/microvm-integration.nix
··· 1 + { 2 + den, 3 + lib, 4 + config, 5 + inputs, 6 + ... 7 + }: 8 + let 9 + # extends den.schema.host with MicroVM specific options 10 + extendHostSchema = 11 + { host, ... }: 12 + { 13 + options.microvm.module = lib.mkOption { 14 + description = "MicroVM microvm.nix module"; 15 + type = lib.types.deferredModule; 16 + default = inputs.microvm."${host.class}Modules".microvm; 17 + }; 18 + 19 + options.microvm.hostModule = lib.mkOption { 20 + description = "MicroVM host.nix module"; 21 + type = lib.types.deferredModule; 22 + default = inputs.microvm."${host.class}Modules".host; 23 + }; 24 + 25 + # Declarative Guest VMs built with Host. 26 + options.microvm.guests = lib.mkOption { 27 + type = lib.types.listOf lib.types.raw; 28 + default = [ ]; 29 + description = '' 30 + Guest MicroVMs. 31 + Value is a list of Den hosts: [ den.hosts.x86_64-linux.foo-microvm ] 32 + 33 + When non empty, Host imports <microvm>/host.nix module 34 + and starts our Den microvm-host context pipeline. 35 + 36 + See: https://microvm-nix.github.io/microvm.nix/host.html 37 + https://microvm-nix.github.io/microvm.nix/declarative.html 38 + ''; 39 + }; 40 + 41 + options.microvm.sharedNixStore = lib.mkEnableOption "Auto share nix store from host"; 42 + config.microvm.sharedNixStore = lib.mkDefault true; 43 + }; 44 + 45 + # transition a NixOS host into a MicroVM host (only if it has guest microvms) 46 + ctx.host.into.microvm-host = { host }: lib.optional (host.microvm.guests != [ ]) { inherit host; }; 47 + 48 + # aspect configuring a MicroVM host. imports the microvm host.nix module. 49 + ctx.microvm-host.provides.microvm-host = 50 + { host }: 51 + { 52 + ${host.class}.imports = [ host.microvm.hostModule ]; 53 + }; 54 + 55 + # transition from microvm host into each microvm guest 56 + ctx.microvm-host.into.microvm-guest = { host }: map (vm: { inherit host vm; }) host.microvm.guests; 57 + 58 + # aspect configuring a guest vm at the host level (Declarative in MicroVM parlance) 59 + # See: https://microvm-nix.github.io/microvm.nix/declarative.html 60 + ctx.microvm-host.provides.microvm-guest = 61 + { host, vm }: 62 + { 63 + includes = 64 + let 65 + sharedNixStore = lib.optionalAttrs host.microvm.sharedNixStore { 66 + ${host.class}.microvm.vms.${vm.name}.config.microvm.shares = [ 67 + { 68 + source = "/nix/store"; 69 + mountPoint = "/nix/.ro-store"; 70 + tag = "ro-store"; 71 + proto = "virtiofs"; 72 + } 73 + ]; 74 + }; 75 + 76 + # forwards guest nixos configuration into host: microvm.vms.<vm-name>.config 77 + osFwd = den.provides.forward { 78 + each = lib.singleton true; 79 + fromClass = _: vm.class; 80 + intoClass = _: host.class; 81 + intoPath = _: [ 82 + "microvm" 83 + "vms" 84 + vm.name 85 + "config" 86 + ]; 87 + # calling host-pipeline ensure all Den features supported on guest 88 + fromAspect = _: den.ctx.host { host = vm; }; 89 + }; 90 + 91 + # forwards guest microvm class into host: microvm.vms.<vm-name> 92 + microvmClass = den.provides.forward { 93 + each = lib.singleton true; 94 + fromClass = _: "microvm"; 95 + intoClass = _: host.class; 96 + intoPath = _: [ 97 + "microvm" 98 + "vms" 99 + vm.name 100 + ]; 101 + fromAspect = _: den.aspects.${vm.aspect}; 102 + }; 103 + 104 + in 105 + [ 106 + sharedNixStore 107 + osFwd 108 + microvmClass 109 + ]; 110 + }; 111 + 112 + in 113 + { 114 + den.ctx = ctx; 115 + den.schema.host.imports = [ extendHostSchema ]; 116 + }
+32
templates/microvm/modules/microvm-runners.nix
··· 1 + # for each host exposes microvm declaredRunner (if exists) as package output of this flake. 2 + # feel free to remove or adapt. 3 + { 4 + den, 5 + lib, 6 + config, 7 + ... 8 + }: 9 + let 10 + # omit if you are using flake-parts. create a packages output for us. 11 + packagesModule.options.flake.packages = lib.mkOption { }; 12 + 13 + microvmRunners = lib.pipe den.hosts [ 14 + lib.attrValues 15 + (lib.concatMap lib.attrValues) 16 + (map ( 17 + host: 18 + let 19 + osConf = lib.attrByPath host.intoAttr null config.flake; 20 + vmRunner = osConf.config.microvm.declaredRunner or null; 21 + package = lib.optionalAttrs (vmRunner != null) { 22 + ${host.system}.${host.name} = vmRunner; 23 + }; 24 + in 25 + package 26 + )) 27 + ]; 28 + in 29 + { 30 + imports = [ packagesModule ]; 31 + flake.packages = lib.mkMerge microvmRunners; 32 + }
+54
templates/microvm/modules/runnable-example.nix
··· 1 + # Copied from MicroVM flake template. 2 + # 3 + # This is just a normal NixOS configuration that happes to include microvm.nix module. 4 + # No special Den classes nor context pipeline here. It's all just a single NixOS conf. 5 + # Den support: ./microvm-runners.nix 6 + # 7 + { 8 + inputs, 9 + den, 10 + lib, 11 + ... 12 + }: 13 + { 14 + 15 + den.hosts.x86_64-linux.runnable-microvm = { 16 + intoAttr = [ 17 + "microvms" 18 + "runnable-microvm" 19 + ]; # example not intended to be used from nixosConfigurations 20 + }; 21 + 22 + den.aspects.runnable-microvm = { 23 + nixos = { 24 + imports = [ inputs.microvm.nixosModules.microvm ]; 25 + users.users.root.password = ""; 26 + 27 + # There's not much need to have a forwarding microvm class for runnable vms 28 + microvm = { 29 + volumes = [ 30 + { 31 + mountPoint = "/var"; 32 + image = "var.img"; 33 + size = 256; 34 + } 35 + ]; 36 + shares = [ 37 + { 38 + # use proto = "virtiofs" for MicroVMs that are started by systemd 39 + proto = "9p"; 40 + tag = "ro-store"; 41 + # a host's /nix/store will be picked up so that no 42 + # squashfs/erofs will be built for it. 43 + source = "/nix/store"; 44 + mountPoint = "/nix/.ro-store"; 45 + } 46 + ]; 47 + 48 + # "qemu" has 9p built-in! 49 + hypervisor = "qemu"; 50 + socket = "control.socket"; 51 + }; 52 + }; 53 + }; 54 + }
+3 -3
templates/minimal/flake.lock
··· 2 2 "nodes": { 3 3 "den": { 4 4 "locked": { 5 - "lastModified": 1772737374, 6 - "narHash": "sha256-b1nHLbA42NXPSPC6lpP2K/9RVwO+z91AVV44yRycO5c=", 5 + "lastModified": 1772891845, 6 + "narHash": "sha256-sh0T1cJt45S8FM1RS0goajFfbQ/oop8XfIU8iG6Ww1k=", 7 7 "owner": "vic", 8 8 "repo": "den", 9 - "rev": "57aef3045c87527e94f1f139a818f8851e024a33", 9 + "rev": "a63a4c3bb5ea2674cd8b941635e63afbc5a4bc9f", 10 10 "type": "github" 11 11 }, 12 12 "original": {