├── .gitignore ├── templates └── microvm-interactive │ ├── configuration.nix │ ├── flake.lock │ └── flake.nix ├── flake.lock ├── flake.nix ├── README.md └── bundlers └── runtimeReport └── default.nix /.gitignore: -------------------------------------------------------------------------------- 1 | *result* 2 | *.img 3 | -------------------------------------------------------------------------------- /templates/microvm-interactive/configuration.nix: -------------------------------------------------------------------------------- 1 | { 2 | # services.nginx.enable = true; 3 | } 4 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1669465383, 6 | "narHash": "sha256-fVbG427suESAEb8/P47O/zD/G9BSeFxLh94IUzgOchs=", 7 | "owner": "nixos", 8 | "repo": "nixpkgs", 9 | "rev": "899e7caf59d1954882a8e2dff45ccc0387c186f6", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "nixos", 14 | "ref": "nixos-22.11", 15 | "repo": "nixpkgs", 16 | "type": "github" 17 | } 18 | }, 19 | "root": { 20 | "inputs": { 21 | "nixpkgs": "nixpkgs" 22 | } 23 | } 24 | }, 25 | "root": "root", 26 | "version": 7 27 | } 28 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-22.11"; 3 | outputs = { self, nixpkgs }: 4 | let 5 | supportedSystems = [ "x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin" ]; 6 | forSystems = systems: f: 7 | nixpkgs.lib.genAttrs systems 8 | (system: f system nixpkgs.legacyPackages.${system}); 9 | forAllSystems = forSystems supportedSystems; 10 | in 11 | { 12 | templates = { 13 | microvm-interactive = { 14 | path = ./templates/microvm-interactive; 15 | description = "A microvm that can demonstrate NixOS interactively"; 16 | }; 17 | }; 18 | bundlers = (forAllSystems (system: pkgs: { 19 | runtimeReport = drv: import ./bundlers/runtimeReport { inherit drv pkgs; }; 20 | })); 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nix-demos 2 | 3 | Sometimes you want to show people the power of Nix. 4 | 5 | ## MicroVMs 6 | 7 | To run the MicroVM example from this repo, there are two approaches: 8 | 9 | ### Directly, with no modifications 10 | 11 | `nix run github:nix-how/nix-demos?dir=templates/microvm-interactive#microvm` 12 | 13 | ### Clone the template and start modifying, then run 14 | 15 | Create a directory to start working in, then: 16 | 17 | 1. `nix flake init --template github:nix-how/nix-demos#microvm-interactive` 18 | 2. optionally edit the `flake.nix` or `configuration.nix` to change how the microvm behaves 19 | 2. `nix run .#microvm` 20 | 21 | ## runtimeReport 22 | 23 | Based on [Recovering Nix derivation attributes of runtime 24 | dependencies](https://www.nmattia.com/posts/2019-10-08-runtime-dependencies.html) 25 | by Nicolas Mattia, you can generate a runtime dependency report, which includes 26 | recursive license information, if you run it as follows: 27 | 28 | `nix bundle --bundler github:nix-how/nix-demos#runtimeReport nixpkgs#hello` 29 | 30 | This will produce a symlink to the `/nix/store` by the name and version of the 31 | package, with the following output: 32 | 33 | ``` 34 | ❯ nix bundle --bundler .#runtimeReport nixpkgs#hello 35 | ❯ cat hello-2.12.1-report 36 | --------------------------------- 37 | | OFFICIAL REPORT | 38 | | requested by: the lawyers | 39 | | written by: yours truly | 40 | | TOP SECRET - TOP SECRET | 41 | --------------------------------- 42 | 43 | runtime dependencies of hello-2.12.1: 44 | - libidn2-2.3.2 (lgpl3Plus, gpl2Plus, gpl3Plus) maintained by Franz Pletz 45 | - hello-2.12.1 (gpl3Plus) maintained by Eelco Dolstra 46 | - glibc-2.35-163 (lgpl2Plus) maintained by Eelco Dolstra, Maximilian Bosch 47 | - libunistring-1.0 (lgpl3Plus) maintained by 48 | ``` 49 | 50 | This is similar to an SBOM, but in no particular format such as SPDX or 51 | CycloneDX. This could be expanded upon. 52 | 53 | -------------------------------------------------------------------------------- /templates/microvm-interactive/flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1726560853, 9 | "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "microvm": { 22 | "inputs": { 23 | "flake-utils": "flake-utils", 24 | "nixpkgs": [ 25 | "nixpkgs" 26 | ], 27 | "spectrum": "spectrum" 28 | }, 29 | "locked": { 30 | "lastModified": 1730296510, 31 | "narHash": "sha256-ZiQPP6LE+SLKbS0UYNeDax08whSXlhZeniDzzLQGVEw=", 32 | "owner": "astro", 33 | "repo": "microvm.nix", 34 | "rev": "6ff444ef6cfd5d2effb6c4f50da2ca6f20f2677f", 35 | "type": "github" 36 | }, 37 | "original": { 38 | "owner": "astro", 39 | "repo": "microvm.nix", 40 | "type": "github" 41 | } 42 | }, 43 | "nixpkgs": { 44 | "locked": { 45 | "lastModified": 1730137625, 46 | "narHash": "sha256-9z8oOgFZiaguj+bbi3k4QhAD6JabWrnv7fscC/mt0KE=", 47 | "owner": "nixos", 48 | "repo": "nixpkgs", 49 | "rev": "64b80bfb316b57cdb8919a9110ef63393d74382a", 50 | "type": "github" 51 | }, 52 | "original": { 53 | "owner": "nixos", 54 | "ref": "nixos-24.05", 55 | "repo": "nixpkgs", 56 | "type": "github" 57 | } 58 | }, 59 | "root": { 60 | "inputs": { 61 | "microvm": "microvm", 62 | "nixpkgs": "nixpkgs" 63 | } 64 | }, 65 | "spectrum": { 66 | "flake": false, 67 | "locked": { 68 | "lastModified": 1729945407, 69 | "narHash": "sha256-iGNMamNOAnVTETnIVqDWd6fl74J8fLEi1ejdZiNjEtY=", 70 | "ref": "refs/heads/main", 71 | "rev": "f1d94ee7029af18637dbd5fdf4749621533693fa", 72 | "revCount": 764, 73 | "type": "git", 74 | "url": "https://spectrum-os.org/git/spectrum" 75 | }, 76 | "original": { 77 | "type": "git", 78 | "url": "https://spectrum-os.org/git/spectrum" 79 | } 80 | }, 81 | "systems": { 82 | "locked": { 83 | "lastModified": 1681028828, 84 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 85 | "owner": "nix-systems", 86 | "repo": "default", 87 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 88 | "type": "github" 89 | }, 90 | "original": { 91 | "owner": "nix-systems", 92 | "repo": "default", 93 | "type": "github" 94 | } 95 | } 96 | }, 97 | "root": "root", 98 | "version": 7 99 | } 100 | -------------------------------------------------------------------------------- /templates/microvm-interactive/flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "NixOS in MicroVMs"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:nixos/nixpkgs/nixos-24.05"; 6 | microvm = { 7 | url = "github:astro/microvm.nix"; 8 | inputs.nixpkgs.follows = "nixpkgs"; 9 | }; 10 | }; 11 | 12 | nixConfig = { 13 | extra-substituters = [ "https://microvm.cachix.org" "https://poetry2nix.cachix.org" "https://matthewcroughan.cachix.org" ]; 14 | extra-trusted-public-keys = [ "microvm.cachix.org-1:oXnBc6hRE3eX5rSYdRyMYXnfzcCxC7yKPTbZXALsqys=" "poetry2nix.cachix.org-1:2EWcWDlH12X9H76hfi5KlVtHgOtLa1Xeb7KjTjaV/R8=" "matthewcroughan.cachix.org-1:fON2C9BdzJlp1qPan4t5AF0xlnx8sB0ghZf8VDo7+e8=" ]; 15 | }; 16 | 17 | outputs = { self, nixpkgs, microvm }: 18 | let 19 | system = "x86_64-linux"; 20 | in { 21 | defaultPackage.${system} = self.packages.${system}.microvm; 22 | 23 | packages.${system}.microvm = 24 | let 25 | inherit (self.nixosConfigurations.microvm) config; 26 | hypervisor = "qemu"; 27 | in config.microvm.runner.${hypervisor}; 28 | 29 | nixosConfigurations.microvm = nixpkgs.lib.nixosSystem { 30 | inherit system; 31 | modules = [ 32 | ./configuration.nix 33 | microvm.nixosModules.microvm 34 | ({ config, pkgs, ... }: { 35 | systemd.services.initialConfig = { 36 | description = "Copy configuration into microvm"; 37 | wantedBy = [ "multi-user.target" ]; 38 | unitConfig.ConditionPathExists = "!/root/flake.nix"; 39 | serviceConfig.Type = "oneshot"; 40 | script = '' 41 | mkdir -p /etc/nixos 42 | cp --no-preserve=mode ${./flake.nix} /etc/nixos/flake.nix 43 | cp --no-preserve=mode ${./flake.lock} /etc/nixos/flake.lock 44 | cp --no-preserve=mode ${./configuration.nix} /etc/nixos/configuration.nix 45 | ''; 46 | }; 47 | networking.hostName = "microvm"; 48 | users.users.root.password = ""; 49 | nix = { 50 | nixPath = [ "nixpkgs=${nixpkgs}" ]; 51 | extraOptions = '' 52 | experimental-features = nix-command flakes 53 | accept-flake-config = true 54 | ''; 55 | }; 56 | environment.systemPackages = with pkgs; [ magic-wormhole bore-cli ]; 57 | services.logind.extraConfig = "RuntimeDirectorySize=2G"; 58 | services.mingetty.autologinUser = "root"; 59 | microvm = { 60 | optimize.enable = false; 61 | vcpu = 4; 62 | interfaces = [ { 63 | type = "user"; 64 | id = "microvm-a1"; 65 | mac = "02:00:00:00:00:01"; 66 | } ]; 67 | balloonMem = 4096; 68 | volumes = [ 69 | { 70 | mountPoint = "/"; 71 | image = "rootfs.img"; 72 | size = 10240; 73 | } 74 | { 75 | image = "nix-store-overlay.img"; 76 | mountPoint = config.microvm.writableStoreOverlay; 77 | size = 10240; 78 | } 79 | ]; 80 | writableStoreOverlay = "/nix/.rw-store"; 81 | shares = [ { 82 | # use "virtiofs" for MicroVMs that are started by systemd 83 | proto = "9p"; 84 | tag = "ro-store"; 85 | # a host's /nix/store will be picked up so that the 86 | # size of the /dev/vda can be reduced. 87 | source = "/nix/store"; 88 | mountPoint = "/nix/.ro-store"; 89 | } ]; 90 | socket = "control.socket"; 91 | # relevant for delarative MicroVM management 92 | hypervisor = "qemu"; 93 | }; 94 | }) 95 | ]; 96 | }; 97 | }; 98 | } 99 | -------------------------------------------------------------------------------- /bundlers/runtimeReport/default.nix: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2021 Nicolas Mattia 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | { drv, pkgs }: 24 | with { 25 | inherit (pkgs) 26 | closureInfo 27 | runCommandNoCC 28 | writeText 29 | jq 30 | ; 31 | inherit (pkgs.lib) 32 | concatLists 33 | concatMap 34 | concatStringsSep 35 | filter 36 | isAttrs 37 | isDerivation 38 | isList 39 | isString 40 | mapAttrsToList 41 | ; 42 | inherit (builtins) 43 | genericClosure 44 | hasAttr 45 | toJSON 46 | typeOf 47 | unsafeDiscardStringContext 48 | ; 49 | }; 50 | 51 | let 52 | 53 | # Create a "runtime report" of the runtime dependencies of `drv`. A "runtime 54 | # report" is made up of smaller "dependency reports". A "dependency report" is 55 | # a string describing the dependency, made from the dependency's derivation 56 | # attributes. Here we use `mkReport` to make the report of any particular 57 | # dependency. 58 | # 59 | # NOTE: we use the following terms: 60 | # 61 | # * "buildtime" to mean basically any derivation involved in the build of 62 | # `drv`. 63 | # * "buildtime-only" for the "buildtime" dependencies that _are not_ 64 | # referenced anymore by `drv`'s store entry. 65 | # * "runtime" for the rest. 66 | # 67 | # The "runtime report" is created in two steps: 68 | # 69 | # * Generate reports for all the _buildtime_ dependencies with 70 | # `buildtimeReports`. 71 | # * Filter out the reports for buildtime-only dependencies. 72 | # 73 | # Most of the "buildtime" reports won't even be used, because most buildtime 74 | # dependencies are buildtime-only dependencies. However Nix does not give us a 75 | # way of retrieving the derivation attributes of runtime dependencies, but we 76 | # can twist its arm to: 77 | # 78 | # * Give us the store paths of runtime dependencies (see `cinfo`). 79 | # * Give us the derivation attributes of all the buildtime dependencies (see 80 | # `buildtimeDerivations`). 81 | # 82 | # Here's the hack: `buildtimeReports` tags the reports with the (expected) 83 | # store path of the "buildtime" dependency, which we cross check against the 84 | # list of runtime store paths. If it's a match, we keep it. Otherwise, we 85 | # discard it. 86 | runtimeReport = drv: 87 | runCommandNoCC "${drv.name}-report" { buildInputs = [ jq ]; } 88 | # XXX: This is to avoid IFD 89 | '' 90 | ( 91 | echo " ---------------------------------" 92 | echo " | OFFICIAL REPORT |" 93 | echo " | requested by: the lawyers |" 94 | echo " | written by: yours truly |" 95 | echo " | TOP SECRET - TOP SECRET |" 96 | echo " ---------------------------------" 97 | echo 98 | echo "runtime dependencies of ${drv.name}:" 99 | cat ${buildtimeReports drv} |\ 100 | jq -r --slurpfile runtime ${cinfo drv} \ 101 | ' # First, we strip away (path-)duplicates. 102 | unique_by(.path) 103 | # Then we map over each build-time derivation and use `select()` 104 | # to keep only the ones that show up in $runtime 105 | 106 | | map( # this little beauty checks if "obj.path" is in "runtime" 107 | select(. as $obj | $runtime | any(.[] | . == $obj.path)) 108 | | .report) 109 | | .[]' 110 | ) > $out 111 | ''; 112 | 113 | # Creates reports for all of `drv`'s buildtime dependencies. Each element in 114 | # the list has two fields: 115 | # 116 | # * path = "/nix/store/..." 117 | # * report = "some report based on the dependency's derivation attributes" 118 | buildtimeReports = drv: writeText "${drv.name}-runtime" ( toJSON ( 119 | map (obj: 120 | # unsafe: optimization to avoid downloading unused deps 121 | { # XXX: we discard the context of the dependencies' store paths because 122 | # they're only ever used for lookup. This matters when fetching a 123 | # prebuilt final report -- there's no point downloading all of `drv`'s 124 | # buildtime dependencies. 125 | path = unsafeDiscardStringContext obj.key; 126 | report = mkReport obj.drv; 127 | } 128 | ) 129 | (buildtimeDerivations drv) # the heavy lifting is done somewhere else 130 | )); 131 | 132 | # Returns a list of all of `drv0`'s inputs, a.k.a. buildtime dependencies. 133 | # Elements in the list has two fields: 134 | # 135 | # * key: the store path of the input. 136 | # * drv: the actual derivation object. 137 | # 138 | # There are no guarantees that this will find _all_ inputs, but it works well 139 | # enough in practice. 140 | # 141 | buildtimeDerivations = drv0: 142 | let 143 | # We include all the outputs because they each have different outPaths 144 | drvOutputs = drv: 145 | # XXX: some derivations, like stdenv, don't have "outputs" 146 | if hasAttr "outputs" drv 147 | then map (output: drv.${output}) drv.outputs 148 | else [ drv ]; 149 | 150 | # Recurse into the derivation attributes to find new derivations 151 | drvDeps = attrs: 152 | mapAttrsToList (k: v: 153 | if isDerivation v then (drvOutputs v) 154 | else if isList v 155 | then concatMap drvOutputs (filter isDerivation v) 156 | else [] 157 | ) attrs; 158 | in 159 | # Walk through the whole DAG of dependencies, using the `outPath` as an 160 | # index for the elements. 161 | let wrap = drv: { key = drv.outPath ; inherit drv; }; in genericClosure 162 | { startSet = map wrap (drvOutputs drv0) ; 163 | operator = obj: map wrap 164 | ( concatLists (drvDeps obj.drv.drvAttrs) ) ; 165 | }; 166 | 167 | # make a report. Would could also output a json object and process everything 168 | # later on. 169 | mkReport = drv: 170 | let 171 | license = 172 | if hasAttr "meta" drv && hasAttr "license" drv.meta then 173 | if isList drv.meta.license then 174 | concatStringsSep ", " ( 175 | map renderLicense drv.meta.license) 176 | else renderLicense drv.meta.license 177 | else "no license"; 178 | 179 | maintainer = 180 | if hasAttr "meta" drv && hasAttr "maintainers" drv.meta 181 | then concatStringsSep ", " (map (m: m.name) drv.meta.maintainers) 182 | else "nobody"; 183 | 184 | in " - ${drv.name} (${license}) maintained by ${maintainer}"; 185 | 186 | # Basically pretty prints a license 187 | renderLicense = license: 188 | if isAttrs license then license.shortName 189 | else if isString license then license 190 | else abort "no idea how to handle license of type ${typeOf license}"; 191 | 192 | # This is a wrapper around nixpkgs' `closureInfo`. It produces a JSON file 193 | # containing a list of the store paths of `drv`'s runtime dependencies. 194 | cinfo = drv: runCommandNoCC "${drv.name}-cinfo" 195 | { buildInputs = [ jq ]; } 196 | # NOTE: we avoid IFD here as well 197 | '' 198 | cat ${closureInfo { rootPaths = [ drv ]; }}/store-paths |\ 199 | grep -v "^$" |\ 200 | jq -R -s -c 'split("\n")' |\ 201 | jq -c 'map(select( length > 0 ))' > $out 202 | ''; 203 | 204 | in runtimeReport drv 205 | --------------------------------------------------------------------------------