├── .github ├── dependabot.yml └── workflows │ └── publish.yml ├── .mergify.yml ├── License.md ├── Makefile ├── README.md ├── bin └── nixos-shell ├── default.nix ├── examples ├── empty-disks.nix ├── vm-efi.nix ├── vm-forward.nix ├── vm-graphics.nix ├── vm-mounts.nix ├── vm-resources.nix └── vm.nix ├── flake.lock ├── flake.nix ├── renovate.json └── share ├── modules ├── nixos-shell-config.nix └── nixos-shell.nix └── nixos-shell.nix /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: "Publish a flake to flakestry" 2 | on: 3 | push: 4 | tags: 5 | - "v?[0-9]+.[0-9]+.[0-9]+" 6 | - "v?[0-9]+.[0-9]+" 7 | workflow_dispatch: 8 | inputs: 9 | tag: 10 | description: "The existing tag to publish" 11 | type: "string" 12 | required: true 13 | jobs: 14 | publish-flake: 15 | runs-on: ubuntu-latest 16 | permissions: 17 | id-token: "write" 18 | contents: "read" 19 | steps: 20 | - uses: flakestry/flakestry-publish@main 21 | with: 22 | version: "${{ inputs.tag || github.ref_name }}" 23 | -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | queue_rules: 2 | - name: default 3 | queue_conditions: 4 | - base=master 5 | - label~=merge-queue|dependencies 6 | merge_conditions: [] 7 | merge_method: rebase 8 | 9 | pull_request_rules: 10 | - name: refactored queue action rule 11 | conditions: [] 12 | actions: 13 | queue: 14 | -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Jörg Thalheim and contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | INSTALL ?= install 2 | NIXOS_SHELL ?= $(shell nix-build --no-out-link default.nix)/bin/nixos-shell 3 | 4 | all: 5 | 6 | test: 7 | $(NIXOS_SHELL) examples/vm.nix 8 | 9 | test-resources: 10 | $(NIXOS_SHELL) examples/vm-resources.nix 11 | 12 | test-forward: 13 | $(NIXOS_SHELL) examples/vm-forward.nix 14 | 15 | test-graphics: 16 | $(NIXOS_SHELL) examples/vm-graphics.nix 17 | 18 | test-mounts: 19 | $(NIXOS_SHELL) examples/vm-mounts.nix 20 | 21 | test-efi: 22 | $(NIXOS_SHELL) examples/vm-efi.nix 23 | 24 | test-broken: 25 | ! $(NIXOS_SHELL) --flake '.#BROKEN-DO-NOT-USE-UNLESS-YOU-KNOW-WHAT-YOU-ARE-DOING' 26 | 27 | install: 28 | $(INSTALL) -D bin/nixos-shell $(DESTDIR)$(PREFIX)/bin/nixos-shell 29 | $(INSTALL) -D share/modules/nixos-shell.nix $(DESTDIR)$(PREFIX)/share/modules/nixos-shell.nix 30 | $(INSTALL) -D share/modules/nixos-shell-config.nix $(DESTDIR)$(PREFIX)/share/modules/nixos-shell-config.nix 31 | $(INSTALL) -D share/nixos-shell.nix $(DESTDIR)$(PREFIX)/share/nixos-shell.nix 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nixos-shell 2 | 3 | * Spawns a headless qemu virtual machines based on a `vm.nix` nixos module in the current working directory. 4 | * Mounts `$HOME` and the user's nix profile into the virtual machine 5 | * Provides console access in the same terminal window 6 | 7 | Example `vm.nix`: 8 | 9 | ```nix 10 | { pkgs, ... }: { 11 | boot.kernelPackages = pkgs.linuxPackages_latest; 12 | } 13 | ``` 14 | 15 | ## How to install 16 | 17 | `nixos-shell` is available in nixpkgs. 18 | 19 | ## Start a virtual machine 20 | 21 | To start a vm use: 22 | 23 | ```console 24 | $ nixos-shell 25 | ``` 26 | 27 | In this case `nixos-shell` will read `vm.nix` in the current directory. 28 | Instead of `vm.nix`, `nixos-shell` also accepts other modules on the command line. 29 | 30 | ```console 31 | $ nixos-shell some-nix-module.nix 32 | ``` 33 | 34 | You can also start a vm from a flake's `nixosConfigurations` or `nixosModules` output using the `--flake` flag. 35 | 36 | ```console 37 | $ nixos-shell --flake github:Mic92/nixos-shell#vm-forward 38 | ``` 39 | 40 | This will run the `vm-forward` example. 41 | 42 | > Note: `nixos-shell` must be able to extend the specified system configuration with [certain modules](share/modules). 43 | > 44 | > If your version of `nixpkgs` provides the `extendModules` function on system configurations, `nixos-shell` will use it to inject the required modules; no additional work on your part is needed. 45 | > 46 | > If your version of `nixpkgs` **does not** provide `extendModules`, you must make your system configurations overridable with `lib.makeOverridable` to use them with `nixos-shell`: 47 | >```nix 48 | >{ 49 | > nixosConfigurations = let 50 | > lib = nixpkgs.lib; 51 | > in { 52 | > vm = lib.makeOverridable lib.nixosSystem { 53 | > # ... 54 | > }; 55 | > }; 56 | >} 57 | >``` 58 | > Specifying a non-overridable system configuration will cause `nixos-shell` to abort with a non-zero exit status. 59 | 60 | When using the `--flake` flag, if no attribute is given, `nixos-shell` tries the following flake output attributes: 61 | - `packages..nixosConfigurations.` 62 | - `nixosConfigurations.` 63 | - `nixosModules.` 64 | 65 | If an attribute _name_ is given, `nixos-shell` tries the following flake output attributes: 66 | - `packages..nixosConfigurations.` 67 | - `nixosConfigurations.` 68 | - `nixosModules.` 69 | 70 | 71 | ## Terminating the virtual machine 72 | 73 | Type `Ctrl-a x` to exit the virtual machine. 74 | 75 | You can also run the `poweroff` command in the virtual machine console: 76 | 77 | ```console 78 | $vm> poweroff 79 | ``` 80 | 81 | Or switch to qemu console with `Ctrl-a c` and type: 82 | 83 | ```console 84 | (qemu) quit 85 | ``` 86 | 87 | ## Port forwarding 88 | 89 | To forward ports from the virtual machine to the host, use the 90 | `virtualisation.forwardPorts` NixOS option. 91 | See `examples/vm-forward.nix` where the ssh server running on port 22 in the 92 | virtual machine is made accessible through port 2222 on the host. 93 | 94 | The same can be also achieved by using the `QEMU_NET_OPTS` environment variable. 95 | 96 | ```console 97 | $ QEMU_NET_OPTS="hostfwd=tcp::2222-:22" nixos-shell 98 | ``` 99 | 100 | ### SSH login 101 | 102 | Your keys are used to enable passwordless login for the root user. 103 | At the moment only `~/.ssh/id_rsa.pub`, `~/.ssh/id_ecdsa.pub` and `~/.ssh/id_ed25519.pub` are 104 | added automatically. Use `users.users.root.openssh.authorizedKeys.keyFiles` to add more. 105 | 106 | *Note: sshd is not started by default. It can be enabled by setting 107 | `services.openssh.enable = true`.* 108 | 109 | ## Bridge Network 110 | 111 | QEMU is started with user mode network by default. To use bridge network instead, 112 | set `virtualisation.qemu.networkingOptions` to something like 113 | `[ "-nic bridge,br=br0,model=virtio-net-pci,mac=11:11:11:11:11:11,helper=/run/wrappers/bin/qemu-bridge-helper" ]`. `/run/wrappers/bin/qemu-bridge-helper` is a NixOS specific 114 | path for qemu-bridge-helper on other Linux distributions it will be different. 115 | QEMU needs to be installed on the host to get `qemu-bridge-helper` with setuid bit 116 | set - otherwise you will need to start VM as root. On NixOS this can be achieved using 117 | `virtualisation.libvirtd.enable = true;` 118 | 119 | 120 | ## RAM 121 | 122 | By default qemu will allow at most 500MB of RAM, this can be increased using `virtualisation.memorySize` (size in megabyte). 123 | 124 | ```nix 125 | { virtualisation.memorySize = 1024; } 126 | ``` 127 | 128 | ## CPUs 129 | 130 | To increase the CPU count use `virtualisation.cores` (defaults to 1): 131 | 132 | ```nix 133 | { virtualisation.cores = 2; } 134 | ``` 135 | 136 | ## Hard drive 137 | 138 | To increase the size of the virtual hard drive, i. e. to 20 GB (see [virtualisation] options at bottom, defaults to 512M): 139 | 140 | ```nix 141 | { virtualisation.diskSize = 20 * 1024; } 142 | ``` 143 | 144 | Notice that for this option to become effective you may also need to delete previous block device files created by qemu (`nixos.qcow2`). 145 | 146 | Notice that changes in the nix store are written to an overlayfs backed by tmpfs rather than the block device 147 | that is configured by `virtualisation.diskSize`. This tmpfs can be disabled however by using: 148 | 149 | ```nix 150 | { virtualisation.writableStoreUseTmpfs = false; } 151 | ``` 152 | 153 | This option is recommend if you plan to use nixos-shell as a remote builder. 154 | 155 | ## Graphics/Xserver 156 | 157 | To use graphical applications, add the `virtualisation.graphics` NixOS option (see `examples/vm-graphics.nix`). 158 | 159 | ## Firewall 160 | 161 | By default for user's convenience `nixos-shell` does not enable a firewall. 162 | This can be overridden by: 163 | 164 | ```nix 165 | { networking.firewall.enable = true; } 166 | ``` 167 | 168 | ## Mounting physical disks 169 | 170 | There does not exists any explicit options right now but 171 | one can use either the `$QEMU_OPTS` environment variable 172 | or set `virtualisation.qemu.options` to pass the right qemu 173 | command line flags: 174 | 175 | ```nix 176 | { 177 | # /dev/sdc also needs to be read-writable by the user executing nixos-shell 178 | virtualisation.qemu.options = [ "-hdc" "/dev/sdc" ]; 179 | } 180 | ``` 181 | 182 | 183 | ## Boot with efi 184 | 185 | ``` nix 186 | { virtualisation.qemu.options = [ "-bios" "${pkgs.OVMF.fd}/FV/OVMF.fd" ]; } 187 | ``` 188 | 189 | ## Shared folders 190 | 191 | To mount anywhere inside the virtual machine, use the `nixos-shell.mounts.extraMounts` option. 192 | 193 | ```nix 194 | { 195 | nixos-shell.mounts.extraMounts = { 196 | # simple USB stick sharing 197 | "/media" = /media; 198 | 199 | # override options for each mount 200 | "/var/www" = { 201 | target = ./src; 202 | cache = "none"; 203 | }; 204 | }; 205 | } 206 | ``` 207 | 208 | You can further configure the default mount settings: 209 | 210 | ```nix 211 | { 212 | nixos-shell.mounts = { 213 | mountHome = false; 214 | mountNixProfile = false; 215 | cache = "none"; # default is "loose" 216 | }; 217 | } 218 | ``` 219 | 220 | Available cache modes are documented in the [9p kernel module]. 221 | 222 | ## Disable KVM 223 | 224 | In many cloud environments KVM is not available and therefore nixos-shell will fail with: 225 | `CPU model 'host' requires KVM`. 226 | In newer versions of nixpkgs this has been fixed by falling back to [emulation](https://github.com/NixOS/nixpkgs/pull/95956). 227 | In older version one can set the `virtualisation.qemu.options` or set the environment variable `QEMU_OPTS`: 228 | 229 | ```bash 230 | export QEMU_OPTS="-cpu max" 231 | nixos-shell 232 | ``` 233 | 234 | A full list of supported qemu cpus can be obtained by running `qemu-kvm -cpu help`. 235 | 236 | ## Channels/NIX_PATH 237 | 238 | By default VMs will have a NIX_PATH configured for nix channels but no channel are downloaded yet. 239 | To avoid having to download a nix-channel every time the VM is reset, you can use the following nixos configuration: 240 | 241 | ```nix 242 | {...}: { 243 | nix.nixPath = [ 244 | "nixpkgs=${pkgs.path}" 245 | ]; 246 | } 247 | ``` 248 | 249 | This will add the nixpkgs that is used for the VM in the NIX_PATH of login shell. 250 | 251 | ## Embedding nixos-shell in your own nixos-configuration 252 | 253 | Instead of using the cli, it's also possible to include the `nixos-shell` NixOS module in your own NixOS configuration. 254 | 255 | Add this to your `flake.nix`: 256 | 257 | ```nix 258 | { 259 | inputs.nixos-shell.url = "github:Mic92/nixos-shell"; 260 | } 261 | ``` 262 | 263 | And this to your nixos configuration defined in your flake: 264 | 265 | ```nix 266 | { 267 | imports = [ inputs.nixos-shell.nixosModules.nixos-shell ]; 268 | } 269 | ``` 270 | 271 | Afterwards you can start your nixos configuration with nixos-shell with one of the two following variants: 272 | 273 | For the pure version (doesn't set SHELL or mount /home): 274 | 275 | ``` 276 | nix run .#nixosConfigurations..config.system.build.nixos-shell 277 | ``` 278 | 279 | Or for a version closer to `nixos-shell`: 280 | 281 | ``` 282 | nix run --impure .#nixosConfigurations..config.system.build.nixos-shell 283 | ``` 284 | 285 | ## Running different architectures / operating systems i.e. Linux on MacOS 286 | 287 | It's possible to specify a different architecture using `--guest-system`. 288 | This requires your host system to have a either a remote builder 289 | (i.e. [darwin-builder](https://github.com/NixOS/nixpkgs/blob/master/doc/packages/darwin-builder.section.md) on macOS) 290 | or beeing able to run builds in emulation 291 | for the guest system (`boot.binfmt.emulatedSystems` on NixOS.). 292 | 293 | Here is an example for macOS (arm) that will run an aarch64-linux vm: 294 | 295 | ``` 296 | $ nixos-shell --guest-system aarch64-linux examples/vm.nix 297 | ``` 298 | 299 | ## More configuration 300 | 301 | Have a look at the [virtualisation] options NixOS provides. 302 | 303 | [virtualisation]: https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/virtualisation/qemu-vm.nix 304 | [9p kernel module]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/plain/Documentation/filesystems/9p.rst 305 | -------------------------------------------------------------------------------- /bin/nixos-shell: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | script_dir="$(dirname "$(readlink -f "$0")")" 6 | flake_uri= 7 | flake_attr= 8 | build_only= 9 | out_link= 10 | 11 | usage() { 12 | echo "USAGE: $0 [--builders builders] [--flake uri] [--fallback] [-I path] 13 | [--impure] [--keep-going | -k] [--keep-failed | -K] 14 | [--no-net] [--option name value] [--repair] 15 | [--refresh] [--show-trace] [--verbose | -v | -vv | -vvv | -vvvv | -vvvvv] 16 | [--build-only] [--] [nixos-config] 17 | [--out-link link] 18 | 19 | vm.nix" 1>&2 20 | } 21 | 22 | nixos_config= 23 | while [[ $# -gt 0 ]]; do 24 | i="$1"; shift 1 25 | case "$i" in 26 | --) 27 | shift 28 | break 29 | ;; 30 | --help|-h) 31 | usage 32 | exit 33 | ;; 34 | --flake) 35 | flake="$(echo "$1" | awk -F '#' '{ print $1; }')" 36 | 37 | flake_uri="$(nix flake metadata --extra-experimental-features "nix-command flakes" --json -- "$flake" | jq -r .url)" 38 | flake_attr="$(echo "$1" | awk -F'#' '{ print $2; }')" 39 | shift 40 | ;; 41 | -I|--builders) 42 | j="$1"; shift 1 43 | extraBuildFlags+=("$i" "$j") 44 | ;; 45 | --show-trace|--keep-failed|-K|--keep-going|-k|--verbose|-v|-vv|-vvv|-vvvv|-vvvvv|--fallback|--repair|-L|--refresh|--no-net|--impure) 46 | extraBuildFlags+=("$i") 47 | ;; 48 | --guest-system) 49 | j="$1"; shift 1 50 | extraBuildFlags+=("--argstr" "guestSystem" "$j") 51 | ;; 52 | --host-system) 53 | j="$1"; shift 1 54 | extraBuildFlags+=("--argstr" "hostSystem" "$j") 55 | ;; 56 | --option) 57 | j="$1"; shift 1 58 | k="$1"; shift 1 59 | extraBuildFlags+=("$i" "$j" "$k") 60 | ;; 61 | --build-only) 62 | build_only=1 63 | ;; 64 | --out-link) 65 | out_link="$1"; 66 | shift 1 67 | ;; 68 | *) 69 | if [[ -n "$nixos_config" ]]; then 70 | usage 71 | exit 1 72 | fi 73 | nixos_config="$i" 74 | ;; 75 | esac 76 | done 77 | nixos_config=${nixos_config:-vm.nix} 78 | 79 | unset NIXOS_CONFIG 80 | 81 | if [[ -z "$flake_uri" ]]; then 82 | extraBuildFlags+=( 83 | --extra-experimental-features "nix-command" 84 | -I "nixos-config=$nixos_config" 85 | ) 86 | else 87 | extraBuildFlags+=( 88 | --extra-experimental-features "nix-command flakes" 89 | --argstr flakeStr "$flake" 90 | --argstr flakeUri "$flake_uri" 91 | --argstr flakeAttr "${flake_attr:-"vm"}" 92 | ) 93 | fi 94 | 95 | if [[ -n "$out_link" ]]; then 96 | extraBuildFlags+=(--out-link "$out_link") 97 | else 98 | TEMP_DIR=$(mktemp -d) 99 | trap 'rm -rf "$TEMP_DIR"' EXIT 100 | extraBuildFlags+=(--out-link "$TEMP_DIR/result") 101 | fi 102 | 103 | nix build config.system.build.vm \ 104 | --file "${script_dir}/../share/nixos-shell.nix" \ 105 | --keep-going \ 106 | "${extraBuildFlags[@]}" 107 | 108 | # NixOS VMs use the machine name in the executable path. Rather than try to 109 | # figure it out here, we'll just use a wildcard 110 | runScript=$(echo "$TEMP_DIR"/result/bin/run-*-vm) 111 | 112 | if [[ -n "$build_only" ]]; then 113 | realpath "$runScript" 114 | else 115 | "$runScript" 116 | fi 117 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs ? import {}, 3 | }: 4 | 5 | with pkgs; 6 | stdenv.mkDerivation { 7 | name = "nixos-shell"; 8 | src = builtins.filterSource 9 | (path: type: baseNameOf path != "nixos.qcow2" && 10 | baseNameOf path != ".git" && 11 | baseNameOf path != ".direnv" && 12 | baseNameOf path != "result" 13 | ) ./.; 14 | nativeBuildInputs = [ makeWrapper ]; 15 | preConfigure = '' 16 | export PREFIX=$out 17 | ''; 18 | postInstall = '' 19 | wrapProgram $out/bin/nixos-shell \ 20 | --prefix PATH : ${lib.makeBinPath [ jq coreutils gawk ]} 21 | ''; 22 | } 23 | -------------------------------------------------------------------------------- /examples/empty-disks.nix: -------------------------------------------------------------------------------- 1 | { 2 | # This is useful to test installations and partitions. 3 | # Those will show up as /dev/vdb and /dev/vdc. 4 | virtualisation.emptyDiskImages = [ 4096 4096 ]; 5 | } 6 | -------------------------------------------------------------------------------- /examples/vm-efi.nix: -------------------------------------------------------------------------------- 1 | { config, lib, pkgs, ... }: 2 | { 3 | virtualisation.qemu.options = [ "-bios" "${pkgs.OVMF.fd}/FV/OVMF.fd" ]; 4 | } 5 | -------------------------------------------------------------------------------- /examples/vm-forward.nix: -------------------------------------------------------------------------------- 1 | { pkgs, ... }: { 2 | services.openssh.enable = true; 3 | virtualisation = { 4 | forwardPorts = [ 5 | { from = "host"; host.port = 2222; guest.port = 22; } 6 | ]; 7 | }; 8 | 9 | } 10 | -------------------------------------------------------------------------------- /examples/vm-graphics.nix: -------------------------------------------------------------------------------- 1 | { 2 | services.xserver.enable = true; 3 | virtualisation.graphics = true; 4 | } 5 | -------------------------------------------------------------------------------- /examples/vm-mounts.nix: -------------------------------------------------------------------------------- 1 | { 2 | nixos-shell.mounts.extraMounts = { 3 | "/mnt/examples" = ./.; 4 | 5 | "/mnt/nixos-shell" = { 6 | target = ./..; 7 | cache = "none"; 8 | }; 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /examples/vm-resources.nix: -------------------------------------------------------------------------------- 1 | { 2 | virtualisation = { 3 | cores = 2; 4 | memorySize = 1024; 5 | }; 6 | } 7 | -------------------------------------------------------------------------------- /examples/vm.nix: -------------------------------------------------------------------------------- 1 | { pkgs, ... }: { 2 | boot.kernelPackages = pkgs.linuxPackages_latest; 3 | services.openssh.enable = true; 4 | documentation.enable = false; 5 | } 6 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1747179050, 6 | "narHash": "sha256-qhFMmDkeJX9KJwr5H32f1r7Prs7XbQWtO0h3V0a0rFY=", 7 | "owner": "NixOS", 8 | "repo": "nixpkgs", 9 | "rev": "adaa24fbf46737f3f1b5497bf64bae750f82942e", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "id": "nixpkgs", 14 | "ref": "nixos-unstable", 15 | "type": "indirect" 16 | } 17 | }, 18 | "root": { 19 | "inputs": { 20 | "nixpkgs": "nixpkgs" 21 | } 22 | } 23 | }, 24 | "root": "root", 25 | "version": 7 26 | } 27 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Spawns lightweight nixos vms in a shell"; 3 | 4 | inputs = { 5 | nixpkgs.url = "nixpkgs/nixos-unstable"; 6 | }; 7 | 8 | outputs = inp: 9 | let 10 | lib = inp.nixpkgs.lib; 11 | 12 | inherit (lib) mapAttrs' removeSuffix makeOverridable nixosSystem mapAttrs; 13 | 14 | vms = mapAttrs' (file: _: { 15 | name = removeSuffix ".nix" file; 16 | value = import (./examples + "/${file}"); 17 | }) (builtins.readDir ./examples); 18 | 19 | mkSystem = pkgs: config: makeOverridable nixosSystem { 20 | system = "x86_64-linux"; 21 | modules = [ config inp.self.nixosModules.nixos-shell ]; 22 | }; 23 | 24 | supportedSystems = [ 25 | "x86_64-linux" 26 | "aarch64-linux" 27 | ]; 28 | in 29 | { 30 | nixosConfigurations = 31 | let 32 | configs = mapAttrs (_name: config: mkSystem inp.nixpkgs config) vms; 33 | in 34 | configs 35 | // 36 | { 37 | # Used for testing that nixos-shell exits nonzero when provided a 38 | # non-extensible config 39 | BROKEN-DO-NOT-USE-UNLESS-YOU-KNOW-WHAT-YOU-ARE-DOING = 40 | removeAttrs configs.vm [ "extendModules" "override" ]; 41 | }; 42 | 43 | nixosModules.nixos-shell.imports = [ ./share/modules/nixos-shell.nix ]; 44 | } 45 | 46 | // 47 | 48 | lib.foldl' lib.recursiveUpdate {} (lib.forEach supportedSystems (system: { 49 | 50 | packages."${system}" = { 51 | nixos-shell = import ./default.nix { 52 | pkgs = inp.nixpkgs.legacyPackages."${system}"; 53 | }; 54 | 55 | default = inp.self.packages."${system}".nixos-shell; 56 | }; 57 | 58 | })); 59 | } 60 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json" 3 | } 4 | -------------------------------------------------------------------------------- /share/modules/nixos-shell-config.nix: -------------------------------------------------------------------------------- 1 | { lib, options, config, pkgs, ... }: 2 | 3 | let 4 | cfg = config.nixos-shell; 5 | home = builtins.getEnv "HOME"; 6 | mkVMDefault = lib.mkOverride 900; 7 | in { 8 | config = 9 | let 10 | user = builtins.getEnv "USER"; 11 | shell' = builtins.baseNameOf (builtins.getEnv "SHELL"); 12 | 13 | # fish seems to do funky stuff: https://github.com/Mic92/nixos-shell/issues/42 14 | shell = if shell' == "fish" then "bash" else shell'; 15 | # Enable the module of the user's shell for some sensible defaults. 16 | maybeSetShell = lib.optional (options.programs ? ${shell}.enable && shell != "bash") { 17 | programs.${shell}.enable = mkVMDefault true; 18 | }; 19 | in 20 | lib.mkMerge (maybeSetShell ++ [ 21 | (lib.mkIf (pkgs ? ${shell}) { 22 | users.extraUsers.root.shell = mkVMDefault pkgs.${shell}; 23 | }) 24 | 25 | ( 26 | lib.mkIf (home != "" && cfg.mounts.mountHome) { 27 | users.extraUsers.root.home = lib.mkVMOverride home; 28 | } 29 | ) 30 | 31 | # Allow passwordless ssh login with the user's key if it exists. 32 | ( 33 | let 34 | keys = map (key: "${builtins.getEnv "HOME"}/.ssh/${key}") 35 | [ "id_rsa.pub" "id_ecdsa.pub" "id_ed25519.pub" ]; 36 | in 37 | { 38 | users.users.root.openssh.authorizedKeys.keyFiles = lib.filter builtins.pathExists keys; 39 | } 40 | ) 41 | 42 | { 43 | # Allow the user to login as root without password. 44 | users.extraUsers.root.initialHashedPassword = ""; 45 | 46 | # see https://wiki.qemu.org/Documentation/9psetup#Performance_Considerations 47 | # == 100M 48 | # FIXME? currently 500K seems to be the limit? 49 | virtualisation.msize = mkVMDefault 104857600; 50 | 51 | services.getty.helpLine = '' 52 | If you are connect via serial console: 53 | Type Ctrl-a c to switch to the qemu console 54 | and `quit` to stop the VM. 55 | ''; 56 | services.getty.autologinUser = "root"; 57 | 58 | virtualisation = { 59 | graphics = mkVMDefault false; 60 | memorySize = mkVMDefault 700; 61 | 62 | qemu.consoles = lib.mkIf (!config.virtualisation.graphics) [ "tty0" "hvc0" ]; 63 | 64 | qemu.options = 65 | let 66 | nixProfile = "/nix/var/nix/profiles/per-user/${user}/profile/"; 67 | in 68 | lib.optionals (!config.virtualisation.graphics) [ 69 | "-serial null" 70 | "-device virtio-serial" 71 | "-chardev stdio,mux=on,id=char0,signal=off" 72 | "-mon chardev=char0,mode=readline" 73 | "-device virtconsole,chardev=char0,nr=0" 74 | ] ++ 75 | lib.optional cfg.mounts.mountHome "-virtfs local,path=${home},security_model=none,mount_tag=home" ++ 76 | lib.optional (cfg.mounts.mountNixProfile && builtins.pathExists nixProfile) "-virtfs local,path=${nixProfile},security_model=none,mount_tag=nixprofile" ++ 77 | lib.mapAttrsToList (target: mount: "-virtfs local,path=${builtins.toString mount.target},security_model=none,mount_tag=${mount.tag}") cfg.mounts.extraMounts; 78 | }; 79 | 80 | # build-vm overrides our filesystem settings in nixos-config 81 | boot.initrd.postMountCommands = 82 | (lib.optionalString cfg.mounts.mountHome '' 83 | mkdir -p $targetRoot/${lib.escapeShellArg home} 84 | mount -t 9p home $targetRoot/${lib.escapeShellArg home} -o trans=virtio,version=9p2000.L,cache=${cfg.mounts.cache},msize=${toString config.virtualisation.msize} 85 | '') + 86 | (lib.optionalString (user != "" && cfg.mounts.mountNixProfile) '' 87 | mkdir -p $targetRoot/nix/var/nix/profiles/per-user/${user}/profile/ 88 | mount -t 9p nixprofile $targetRoot/nix/var/nix/profiles/per-user/${user}/profile/ -o trans=virtio,version=9p2000.L,cache=${cfg.mounts.cache},msize=${toString config.virtualisation.msize} 89 | '') + 90 | builtins.concatStringsSep " " (lib.mapAttrsToList 91 | (target: mount: '' 92 | mkdir -p $targetRoot/${target} 93 | mount -t 9p ${mount.tag} $targetRoot/${target} -o trans=virtio,version=9p2000.L,cache=${mount.cache},msize=${toString config.virtualisation.msize} 94 | '') 95 | cfg.mounts.extraMounts); 96 | 97 | # avoid leaking incompatible host binaries into the VM 98 | system.activationScripts.shadow-nix-profile = lib.mkIf (options.virtualisation.host.pkgs.isDefined && config.virtualisation.host.pkgs.stdenv.hostPlatform != pkgs.stdenv.hostPlatform) (lib.stringAfter [ "specialfs" "users" "groups" ] '' 99 | mkdir -p ${lib.escapeShellArg home}/.nix-profile/ 100 | mount --bind ${config.system.path} ${lib.escapeShellArg home}/.nix-profile/ 101 | ''); 102 | 103 | environment = { 104 | systemPackages = with pkgs; [ 105 | xterm # for resize command 106 | ]; 107 | 108 | loginShellInit = 109 | let 110 | pwd = builtins.getEnv "PWD"; 111 | term = builtins.getEnv "TERM"; 112 | path = builtins.getEnv "PATH"; 113 | in 114 | '' 115 | # if terminal with stdout, fix terminal size 116 | if [ -t 1 ]; then eval "$(resize)"; fi 117 | 118 | ${lib.optionalString (pwd != "") "cd '${pwd}' 2>/dev/null"} 119 | ${lib.optionalString (term != "") "export TERM='${term}'"} 120 | ${lib.optionalString (path != "") "export PATH=\"${path}:$PATH\""} 121 | ''; 122 | }; 123 | 124 | networking.firewall.enable = mkVMDefault false; 125 | } 126 | ]); 127 | } 128 | -------------------------------------------------------------------------------- /share/modules/nixos-shell.nix: -------------------------------------------------------------------------------- 1 | { lib, pkgs, modulesPath, config, options, extendModules, ... }: 2 | 3 | { 4 | imports = [ 5 | "${toString modulesPath}/virtualisation/qemu-vm.nix" 6 | ]; 7 | 8 | options.nixos-shell = with lib; { 9 | mounts = let 10 | cache = mkOption { 11 | type = types.enum ["none" "loose" "fscache" "mmap"]; 12 | default = "loose"; # bad idea? Well, at least it is fast!1!! 13 | description = "9p caching policy"; 14 | }; 15 | in { 16 | mountHome = mkOption { 17 | type = types.bool; 18 | default = builtins.getEnv "HOME" != ""; 19 | description = "Whether to mount `$HOME`."; 20 | }; 21 | 22 | mountNixProfile = mkOption { 23 | type = types.bool; 24 | # if our host os does not match the guest os, binaries in our nix profile will not work 25 | default = options.virtualisation.host.pkgs.isDefined && config.virtualisation.host.pkgs.stdenv.hostPlatform != pkgs.stdenv.hostPlatform; 26 | description = "Whether to mount the user's nix profile."; 27 | }; 28 | 29 | inherit cache; 30 | 31 | extraMounts = mkOption { 32 | type = types.attrsOf (types.coercedTo 33 | types.path (target: { 34 | inherit target; 35 | }) 36 | (types.submodule ({ config, ... }: { 37 | options = { 38 | target = mkOption { 39 | type = types.path; 40 | description = lib.mdDoc "Target on the guest."; 41 | }; 42 | 43 | inherit cache; 44 | 45 | tag = mkOption { 46 | type = types.str; 47 | internal = true; 48 | }; 49 | }; 50 | 51 | config.tag = lib.mkDefault ( 52 | builtins.substring 0 31 ( # tags must be shorter than 32 bytes 53 | "a" + # tags must not begin with a digit 54 | builtins.hashString "md5" config._module.args.name 55 | ) 56 | ); 57 | })) 58 | ); 59 | default = {}; 60 | }; 61 | }; 62 | }; 63 | 64 | config = let 65 | vmSystem = extendModules { 66 | modules = [ 67 | ./nixos-shell-config.nix 68 | ]; 69 | }; 70 | in { 71 | system.build.nixos-shell = vmSystem.config.system.build.vm; 72 | }; 73 | } 74 | -------------------------------------------------------------------------------- /share/nixos-shell.nix: -------------------------------------------------------------------------------- 1 | { nixpkgs ? 2 | , guestSystem ? builtins.currentSystem 3 | , hostSystem ? builtins.currentSystem 4 | , configuration ? 5 | , flakeStr ? null # flake as named on the command line 6 | , flakeUri ? null 7 | , flakeAttr ? null 8 | }: 9 | let 10 | hasFlake = flakeUri != null; 11 | hasFlakeNixpkgs = hasFlake && flake ? inputs.nixpkgs; 12 | 13 | flake = builtins.getFlake flakeUri; 14 | 15 | nixosShellModules = [ 16 | ({lib, ...}: lib.optionalAttrs (guestSystem != hostSystem) { 17 | virtualisation.host.pkgs = if hasFlakeNixpkgs then 18 | flake.inputs.nixpkgs.legacyPackages.${hostSystem} 19 | else 20 | import nixpkgs { system = hostSystem; }; 21 | }) 22 | ./modules/nixos-shell.nix 23 | ./modules/nixos-shell-config.nix 24 | ]; 25 | 26 | nixpkgsPath = if hasFlakeNixpkgs then 27 | flake.inputs.nixpkgs 28 | else 29 | nixpkgs; 30 | 31 | mkShellSystem = config: import "${toString nixpkgsPath}/nixos/lib/eval-config.nix" { 32 | system = guestSystem; 33 | modules = [ config ] ++ nixosShellModules; 34 | }; 35 | 36 | flakeSystem = 37 | flake.outputs.packages.${guestSystem}.nixosConfigurations.${flakeAttr} or 38 | flake.outputs.nixosConfigurations.${flakeAttr} or 39 | null; 40 | 41 | flakeModule = flake.outputs.nixosModules.${flakeAttr} or null; 42 | 43 | in 44 | if flakeUri != null then 45 | if flakeSystem != null then 46 | if flakeSystem ? "extendModules" then 47 | flakeSystem.extendModules { modules = nixosShellModules; } 48 | else if flakeSystem ? "override" then 49 | flakeSystem.override (attrs: { modules = attrs.modules ++ nixosShellModules; }) 50 | else 51 | throw '' 52 | '${flakeStr}#${flakeAttr}' is missing the expected 'override' attribute. 53 | 54 | Please ensure that '${flakeStr}#${flakeAttr}' is an overridable attribute set by declaring it with 'lib.makeOverridable'. 55 | 56 | For instance: 57 | 58 | nixosConfigurations = let 59 | lib = nixpkgs.lib; 60 | in { 61 | "${flakeAttr}" = lib.makeOverridable lib.nixosSystem { 62 | # ... 63 | }; 64 | }; 65 | 66 | Alternatively, upgrade to a version of nixpkgs that provides the 'extendModules' function on NixOS system configurations. 67 | 68 | See https://github.com/Mic92/nixos-shell#start-a-virtual-machine for additional information. 69 | '' 70 | else if flakeModule != null then 71 | mkShellSystem flakeModule 72 | else 73 | throw "cannot find flake attribute '${flakeUri}#${flakeAttr}'" 74 | else 75 | mkShellSystem configuration 76 | --------------------------------------------------------------------------------