Skip to content

Commit 9a81009

Browse files
authored
Merge pull request #46 from welteki/flake-support
Flake support
2 parents da5d521 + 62b655c commit 9a81009

File tree

9 files changed

+302
-182
lines changed

9 files changed

+302
-182
lines changed

Makefile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,6 @@ test-efi:
2323

2424
install:
2525
$(INSTALL) -D bin/nixos-shell $(DESTDIR)$(PREFIX)/bin/nixos-shell
26-
$(INSTALL) -D share/nixos-shell/nixos-shell.nix $(DESTDIR)$(PREFIX)/share/nixos-shell/nixos-shell.nix
26+
$(INSTALL) -D share/modules/nixos-shell.nix $(DESTDIR)$(PREFIX)/share/modules/nixos-shell.nix
27+
$(INSTALL) -D share/modules/nixos-shell-config.nix $(DESTDIR)$(PREFIX)/share/modules/nixos-shell-config.nix
28+
$(INSTALL) -D share/nixos-shell.nix $(DESTDIR)$(PREFIX)/share/nixos-shell.nix

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,27 @@ Instead of `vm.nix`, `nixos-shell` also accepts other modules on the command lin
3131
$ nixos-shell some-nix-module.nix
3232
```
3333

34+
You can also start a vm from a flake's `nixosConfigurations` 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: system configurations have to be made overridable with `lib.makeOverridable` to use them with `nixos-shell`
43+
>```nix
44+
>{
45+
> nixosConfigurations = let
46+
> lib = nixpkgs.lib;
47+
> in {
48+
> vm = lib.makeOverridable lib.nixosSystem {
49+
> # ...
50+
> };
51+
> };
52+
>}
53+
>```
54+
3455
## Terminating the virtual machine
3556
3657
Type `Ctrl-a x` to exit the virtual machine.

bin/nixos-shell

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
set -euo pipefail
44

55
script_dir="$(dirname "$(readlink -f "$0")")"
6+
flake_uri=
7+
flake_attr=
68

79
usage() {
8-
echo "USAGE: $0 [--builders builders] [--fallback] [-I path]
10+
echo "USAGE: $0 [--builders builders] [--flake uri] [--fallback] [-I path]
911
[--impure] [--keep-going | -k] [--keep-failed | -K]
1012
[--no-net] [--option name value] [--repair]
1113
[--refresh] [--show-trace] [--verbose | -v | -vv | -vvv | -vvvv | -vvvvv]
@@ -24,6 +26,13 @@ while [[ $# -gt 0 ]]; do
2426
usage
2527
exit
2628
;;
29+
--flake)
30+
flake="$(echo "$1" | awk -F '#' '{ print $1; }')"
31+
32+
flake_uri="$(nix flake metadata --extra-experimental-features "nix-command flakes" --json -- "$flake" | jq -r .url)"
33+
flake_attr="$(echo "$1" | awk -F'#' '{ print $2; }')"
34+
shift
35+
;;
2736
-I|--builders)
2837
j="$1"; shift 1
2938
extraBuildFlags+=("$i" "$j")
@@ -42,7 +51,6 @@ while [[ $# -gt 0 ]]; do
4251
esac
4352
done
4453

45-
export QEMU_NIXOS_CONFIG="$(readlink -f "$nixos_config")"
4654
unset NIXOS_CONFIG
4755

4856
tempdir=$(mktemp -d)
@@ -51,8 +59,19 @@ cleanup() {
5159
}
5260
trap cleanup EXIT SIGINT SIGQUIT ERR
5361

54-
nix-build '<nixpkgs/nixos>' -A vm -k \
55-
-I "nixos-config=${script_dir}/../share/nixos-shell/nixos-shell.nix" \
62+
if [[ -z "$flake_uri" ]]; then
63+
extraBuildFlags+=(
64+
-I "nixos-config=$nixos_config"
65+
)
66+
else
67+
extraBuildFlags+=(
68+
--extra-experimental-features "flakes"
69+
--argstr flakeUri "$flake_uri"
70+
--argstr flakeAttr "${flake_attr:-"vm"}"
71+
)
72+
fi
73+
74+
nix-build "${script_dir}/../share/nixos-shell.nix" -A "config.system.build.vm" -k \
5675
-o "${tempdir}/result" \
5776
"${extraBuildFlags[@]}"
5877
"$@"

default.nix

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,12 @@ stdenv.mkDerivation {
1111
baseNameOf path != ".direnv" &&
1212
baseNameOf path != "result"
1313
) ./.;
14-
buildInputs = [ bash ];
14+
nativeBuildInputs = [ makeWrapper ];
1515
preConfigure = ''
1616
export PREFIX=$out
1717
'';
18+
postInstall = ''
19+
wrapProgram $out/bin/nixos-shell \
20+
--prefix PATH : ${lib.makeBinPath [ jq coreutils gawk ]}
21+
'';
1822
}

flake.nix

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,29 @@
88
outputs = inp:
99
let
1010
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+
1124
supportedSystems = [ "x86_64-linux" ];
1225
in
26+
{
27+
nixosConfigurations = mapAttrs (_name: config: mkSystem inp.nixpkgs config) vms;
28+
29+
nixosModules.nixos-shell = import ./share/modules/nixos-shell.nix;
30+
}
31+
32+
//
33+
1334
lib.foldl' lib.recursiveUpdate {} (lib.forEach supportedSystems (system: rec {
1435

1536
packages."${system}".nixos-shell = import ./default.nix {
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
{ lib, options, config, pkgs, ... }:
2+
3+
let
4+
cfg = config.nixos-shell;
5+
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+
in
16+
lib.mkMerge [
17+
# Enable the module of the user's shell for some sensible defaults.
18+
(lib.mkIf (options.programs ? ${shell}.enable && shell != "bash") {
19+
programs.${shell}.enable = mkVMDefault true;
20+
})
21+
22+
(lib.mkIf (pkgs ? ${shell}) {
23+
users.extraUsers.root.shell = mkVMDefault pkgs.${shell};
24+
})
25+
26+
(
27+
let
28+
home = builtins.getEnv "HOME";
29+
in
30+
lib.mkIf (home != "" && cfg.mounts.mountHome) {
31+
users.extraUsers.root.home = lib.mkVMOverride home;
32+
}
33+
)
34+
35+
# Allow passwordless ssh login with the user's key if it exists.
36+
(
37+
let
38+
keys = map (key: "${builtins.getEnv "HOME"}/.ssh/${key}")
39+
[ "id_rsa.pub" "id_ecdsa.pub" "id_ed25519.pub" ];
40+
in
41+
{
42+
users.users.root.openssh.authorizedKeys.keyFiles = lib.filter builtins.pathExists keys;
43+
}
44+
)
45+
46+
{
47+
# Allow the user to login as root without password.
48+
users.extraUsers.root.initialHashedPassword = "";
49+
50+
# see https://wiki.qemu.org/Documentation/9psetup#Performance_Considerations
51+
# == 100M
52+
# FIXME? currently 500K seems to be the limit?
53+
virtualisation.msize = mkVMDefault 104857600;
54+
55+
services =
56+
let
57+
service = if lib.versionAtLeast (lib.versions.majorMinor lib.version) "20.09" then "getty" else "mingetty";
58+
in
59+
{
60+
${service}.helpLine = ''
61+
Log in as "root" with an empty password.
62+
If you are connect via serial console:
63+
Type Ctrl-a c to switch to the qemu console
64+
and `quit` to stop the VM.
65+
'';
66+
};
67+
68+
virtualisation = {
69+
graphics = mkVMDefault false;
70+
memorySize = mkVMDefault 700;
71+
72+
qemu.consoles = lib.mkIf (!config.virtualisation.graphics) [ "tty0" "hvc0" ];
73+
74+
qemu.options =
75+
let
76+
nixProfile = "/nix/var/nix/profiles/per-user/${user}/profile/";
77+
in
78+
lib.optionals (!config.virtualisation.graphics) [
79+
"-serial null"
80+
"-device virtio-serial"
81+
"-chardev stdio,mux=on,id=char0,signal=off"
82+
"-mon chardev=char0,mode=readline"
83+
"-device virtconsole,chardev=char0,nr=0"
84+
] ++
85+
lib.optional cfg.mounts.mountHome "-virtfs local,path=/home,security_model=none,mount_tag=home" ++
86+
lib.optional (cfg.mounts.mountNixProfile && builtins.pathExists nixProfile) "-virtfs local,path=${nixProfile},security_model=none,mount_tag=nixprofile" ++
87+
lib.mapAttrsToList (target: mount: "-virtfs local,path=${builtins.toString mount.target},security_model=none,mount_tag=${mount.tag}") cfg.mounts.extraMounts;
88+
};
89+
90+
# build-vm overrides our filesystem settings in nixos-config
91+
boot.initrd.postMountCommands =
92+
(lib.optionalString cfg.mounts.mountHome ''
93+
mkdir -p $targetRoot/home/
94+
mount -t 9p home $targetRoot/home/ -o trans=virtio,version=9p2000.L,cache=${cfg.mounts.cache},msize=${toString config.virtualisation.msize}
95+
'') +
96+
(lib.optionalString (user != "" && cfg.mounts.mountNixProfile) ''
97+
mkdir -p $targetRoot/nix/var/nix/profiles/per-user/${user}/profile/
98+
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}
99+
'') +
100+
builtins.concatStringsSep " " (lib.mapAttrsToList
101+
(target: mount: ''
102+
mkdir -p $targetRoot/${target}
103+
mount -t 9p ${mount.tag} $targetRoot/${target} -o trans=virtio,version=9p2000.L,cache=${mount.cache},msize=${toString config.virtualisation.msize}
104+
'')
105+
cfg.mounts.extraMounts);
106+
107+
environment = {
108+
systemPackages = with pkgs; [
109+
xterm # for resize command
110+
];
111+
112+
loginShellInit =
113+
let
114+
pwd = builtins.getEnv "PWD";
115+
term = builtins.getEnv "TERM";
116+
path = builtins.getEnv "PATH";
117+
in
118+
''
119+
# fix terminal size
120+
eval "$(resize)"
121+
122+
${lib.optionalString (pwd != "") "cd '${pwd}' 2>/dev/null"}
123+
${lib.optionalString (term != "") "export TERM='${term}'"}
124+
${lib.optionalString (path != "") "export PATH=\"${path}:$PATH\""}
125+
'';
126+
};
127+
128+
networking.firewall.enable = mkVMDefault false;
129+
}
130+
];
131+
}

share/modules/nixos-shell.nix

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
{ lib, options, pkgs, modulesPath, ... }:
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 = true;
19+
description = "Whether to mount <filename>/home</filename>.";
20+
};
21+
22+
mountNixProfile = mkOption {
23+
type = types.bool;
24+
default = true;
25+
description = "Whether to mount the user's nix profile.";
26+
};
27+
28+
inherit cache;
29+
30+
extraMounts = mkOption {
31+
type = types.attrsOf (types.coercedTo
32+
types.path (target: {
33+
inherit target;
34+
})
35+
(types.submodule ({ config, ... }: {
36+
options = {
37+
target = mkOption {
38+
type = types.path;
39+
description = "Target on the guest.";
40+
};
41+
42+
inherit cache;
43+
44+
tag = mkOption {
45+
type = types.str;
46+
internal = true;
47+
};
48+
};
49+
50+
config.tag = lib.mkDefault (
51+
builtins.substring 0 31 ( # tags must be shorter than 32 bytes
52+
"a" + # tags must not begin with a digit
53+
builtins.hashString "md5" config._module.args.name
54+
)
55+
);
56+
}))
57+
);
58+
default = {};
59+
};
60+
};
61+
};
62+
}

share/nixos-shell.nix

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{ nixpkgs ? <nixpkgs>
2+
, system ? builtins.currentSystem
3+
, configuration ? <nixos-config>
4+
5+
, flakeUri ? null
6+
, flakeAttr ? null
7+
}:
8+
let
9+
nixos-shell = import ./modules/nixos-shell.nix;
10+
nixos-shell-config = import ./modules/nixos-shell-config.nix;
11+
12+
flake = builtins.getFlake flakeUri;
13+
flakeSystem = flake.outputs.packages."${system}".nixosConfigurations."${flakeAttr}" or flake.outputs.nixosConfigurations."${flakeAttr}";
14+
in
15+
if flakeUri != null then
16+
flakeSystem.override
17+
(attrs: {
18+
modules =
19+
let
20+
nixosShellModules =
21+
if flakeSystem ? options.nixos-shell then
22+
[ nixos-shell-config ]
23+
else
24+
[ nixos-shell nixos-shell-config ];
25+
in
26+
attrs.modules ++ nixosShellModules;
27+
})
28+
else
29+
import "${toString nixpkgs}/nixos/lib/eval-config.nix" {
30+
inherit system;
31+
modules = [
32+
configuration
33+
nixos-shell
34+
nixos-shell-config
35+
];
36+
}

0 commit comments

Comments
 (0)