# nixos-config - My current NixOS configuration # # Copyright (C) 2025 Jörg Thalheim and contributors # SPDX-License-Identifier: MIT # # This file is part of my nixos-config. # # You should have received a copy of the License along with this program. # If not, see . # This file contains the code that is used to setup the VM shell. {cfg}: { lib, config, pkgs, modulesPath, ... }: let mkVMDefault = lib.mkOverride 970; env_file = "/tmp/shared/.env_variables"; in { imports = [ (modulesPath + "/profiles/qemu-guest.nix") (modulesPath + "/virtualisation/qemu-vm.nix") ]; config = { assertions = [ { assertion = builtins.elem cfg.user_name (lib.mapAttrsToList (name: value: name) config.users.users); message = '' Your user ${cfg.user_name} is not a recorded user in `config.users.users`. Auto-login will not work. ''; } ]; # Lock root login. users.users.root.hashedPassword = ""; # Remove unneeded clutter. system.switch.enable = false; virtualisation = { # See https://wiki.qemu.org/Documentation/9psetup#Performance_Considerations. # It is effectively a balance between ram and IO speed. msize = let kib = x: x * 1024; in mkVMDefault (kib 512); graphics = mkVMDefault false; memorySize = mkVMDefault 1700; # in MB cores = mkVMDefault 16; # Do not persist this VM. diskImage = mkVMDefault null; fileSystems = lib.mapAttrs' (_: mount: { name = mount.target; value = { device = mount.tag; fsType = "9p"; options = [ "trans=virtio" "version=9p2000.L" "cache=${mount.cache_mode}" "msize=${toString config.virtualisation.msize}" ] ++ lib.optionals mount.readOnly ["ro"]; }; }) cfg.mounts; qemu = { consoles = lib.mkIf (!config.virtualisation.graphics) ["tty0" "hvc0"]; options = let mkMount = options: "-virtfs " + (builtins.concatStringsSep "," options); in lib.optionals (!config.virtualisation.graphics) [ "-serial null" "-device virtio-serial" "-chardev stdio,mux=on,id=char0,signal=off" "-mon chardev=char0,mode=readline" "-device virtconsole,chardev=char0,nr=0" ] ++ (lib.mapAttrsToList (hostPath: mount: mkMount [ "local" "path=${builtins.toString hostPath}" "security_model=none" "readonly=on" "mount_tag=${mount.tag}" ]) cfg.mounts); }; }; services = { getty.helpLine = '' If you are connect via serial console: Type Ctrl-a c to switch to the qemu console and `quit` to stop the VM. ''; getty.autologinUser = cfg.user_name; }; system.activationScripts = { mountAdditionalPaths = # bash '' PATH="${pkgs.jq}/bin:${pkgs.util-linux}/bin:$PATH" export PATH max_index="$(jq '.mount | keys | length' --raw-output ${env_file})" index=0 mount --mkdir --type=tmpfs none "/.rw" --options=rw,relatime,mode=0755 while [ "$index" -lt "$max_index" ]; do what="$(jq --argjson index "$index" '.mount | keys | map(.)[$index]' --raw-output ${env_file})" where="$(jq --argjson index "$index" '.mount | map(.)[$index]' --raw-output ${env_file})" mkdir "/.rw/$what" mount --mkdir "$what" "/.ro/$what" \ --type=9p \ --options=ro,trans=virtio,version=9p2000.L,msize=${toString config.virtualisation.msize},x-systemd.requires=modprobe@9pnet_virtio.service mkdir "/.rw/work-$what" mount --mkdir --type=overlay overlay \ --options=rw,relatime,lowerdir="/.ro/$what",upperdir="/.rw/$what",workdir="/.rw/work-$what",uuid=on \ "/.ov/$what" index="$((index + 1))" done ''; }; systemd.services.mountAdditionalPaths = { after = ["local-fs.target"]; wantedBy = ["multi-user.target"]; path = [pkgs.jq]; script = # bash '' max_index="$(jq '.mount | keys | length' --raw-output ${env_file})" index=0 while [ "$index" -lt "$max_index" ]; do what="$(jq --argjson index "$index" '.mount | keys | map(.)[$index]' --raw-output ${env_file})" where="$(jq --argjson index "$index" '.mount | map(.)[$index]' --raw-output ${env_file})" systemd-mount --type none "/.ov/$what" "$where" --options=bind # HACK(@bpeetz): Nearly all of the paths are in $HOME anyways. So simply avoid # the permission issue. # Ideally, we would pass the original owners along with the mount. <2025-05-17> chown --recursive soispha:users "/home/soispha" index="$((index + 1))" done ''; }; environment = { systemPackages = [ pkgs.jq ]; sessionVariables = { IN_NIXOS_SHELL = "true"; }; loginShellInit = # bash '' cd "$(jq '.pwd' --raw-output ${env_file})" export TERM="$(jq '.term' --raw-output ${env_file})" export PATH="$(jq '.path' --raw-output ${env_file}):$PATH" ''; }; networking.firewall.enable = mkVMDefault true; }; }