mirror of
https://github.com/librephoenix/nixos-config
synced 2025-07-06 06:52:13 +05:30
Got my NixOS flake to run under WSL!
This commit is contained in:
parent
e877fd63bc
commit
0a71232a56
18 changed files with 1063 additions and 10 deletions
97
profiles/wsl/nixos-wsl/modules/build-tarball.nix
Executable file
97
profiles/wsl/nixos-wsl/modules/build-tarball.nix
Executable file
|
@ -0,0 +1,97 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
with builtins; with lib;
|
||||
let
|
||||
pkgs2storeContents = l: map (x: { object = x; symlink = "none"; }) l;
|
||||
|
||||
nixpkgs = lib.cleanSource pkgs.path;
|
||||
|
||||
channelSources = pkgs.runCommand "nixos-${config.system.nixos.version}"
|
||||
{ preferLocalBuild = true; }
|
||||
''
|
||||
mkdir -p $out
|
||||
cp -prd ${nixpkgs.outPath} $out/nixos
|
||||
chmod -R u+w $out/nixos
|
||||
if [ ! -e $out/nixos/nixpkgs ]; then
|
||||
ln -s . $out/nixos/nixpkgs
|
||||
fi
|
||||
echo -n ${toString config.system.nixos.revision} > $out/nixos/.git-revision
|
||||
echo -n ${toString config.system.nixos.versionSuffix} > $out/nixos/.version-suffix
|
||||
echo ${toString config.system.nixos.versionSuffix} | sed -e s/pre// > $out/nixos/svn-revision
|
||||
'';
|
||||
|
||||
preparer = pkgs.writeShellScriptBin "wsl-prepare" ''
|
||||
set -e
|
||||
|
||||
mkdir -m 0755 ./bin ./etc
|
||||
mkdir -m 1777 ./tmp
|
||||
|
||||
# WSL requires a /bin/sh - only temporary, NixOS's activate will overwrite
|
||||
ln -s ${config.users.users.root.shell} ./bin/sh
|
||||
|
||||
# WSL also requires a /bin/mount, otherwise the host fs isn't accessible
|
||||
ln -s /nix/var/nix/profiles/system/sw/bin/mount ./bin/mount
|
||||
|
||||
# Set system profile
|
||||
system=${config.system.build.toplevel}
|
||||
./$system/sw/bin/nix-store --store `pwd` --load-db < ./nix-path-registration
|
||||
rm ./nix-path-registration
|
||||
./$system/sw/bin/nix-env --store `pwd` -p ./nix/var/nix/profiles/system --set $system
|
||||
|
||||
# Set channel
|
||||
mkdir -p ./nix/var/nix/profiles/per-user/root
|
||||
./$system/sw/bin/nix-env --store `pwd` -p ./nix/var/nix/profiles/per-user/root/channels --set ${channelSources}
|
||||
mkdir -m 0700 -p ./root/.nix-defexpr
|
||||
ln -s /nix/var/nix/profiles/per-user/root/channels ./root/.nix-defexpr/channels
|
||||
|
||||
# It's now a NixOS!
|
||||
touch ./etc/NIXOS
|
||||
|
||||
# Write wsl.conf so that it is present when NixOS is started for the first time
|
||||
cp ${config.environment.etc."wsl.conf".source} ./etc/wsl.conf
|
||||
|
||||
${lib.optionalString config.wsl.tarball.includeConfig ''
|
||||
# Copy the system configuration
|
||||
mkdir -p ./etc/nixos/nixos-wsl
|
||||
cp -R ${lib.cleanSource ../.}/. ./etc/nixos/nixos-wsl
|
||||
mv ./etc/nixos/nixos-wsl/configuration.nix ./etc/nixos/configuration.nix
|
||||
# Patch the import path to avoid having a flake.nix in /etc/nixos
|
||||
sed -i 's|import \./default\.nix|import \./nixos-wsl|' ./etc/nixos/configuration.nix
|
||||
''}
|
||||
'';
|
||||
|
||||
in
|
||||
{
|
||||
|
||||
options.wsl.tarball = {
|
||||
includeConfig = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Whether or not to copy the system configuration into the tarball";
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
config = mkIf config.wsl.enable {
|
||||
# These options make no sense without the wsl-distro module anyway
|
||||
|
||||
system.build.tarball = pkgs.callPackage "${nixpkgs}/nixos/lib/make-system-tarball.nix" {
|
||||
# No contents, structure will be added by prepare script
|
||||
contents = [ ];
|
||||
|
||||
fileName = "nixos-wsl-${pkgs.hostPlatform.system}";
|
||||
|
||||
storeContents = pkgs2storeContents [
|
||||
config.system.build.toplevel
|
||||
channelSources
|
||||
preparer
|
||||
];
|
||||
|
||||
extraCommands = "${preparer}/bin/wsl-prepare";
|
||||
|
||||
# Use gzip
|
||||
compressCommand = "gzip";
|
||||
compressionExtension = ".gz";
|
||||
};
|
||||
|
||||
};
|
||||
}
|
41
profiles/wsl/nixos-wsl/modules/docker-desktop.nix
Executable file
41
profiles/wsl/nixos-wsl/modules/docker-desktop.nix
Executable file
|
@ -0,0 +1,41 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
with builtins; with lib; {
|
||||
|
||||
imports = [
|
||||
(mkRenamedOptionModule [ "wsl" "docker" ] [ "wsl" "docker-desktop" ])
|
||||
];
|
||||
|
||||
options.wsl.docker-desktop = with types; {
|
||||
enable = mkEnableOption "Docker Desktop integration";
|
||||
};
|
||||
|
||||
config =
|
||||
let
|
||||
cfg = config.wsl.docker-desktop;
|
||||
in
|
||||
mkIf (config.wsl.enable && cfg.enable) {
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
docker
|
||||
docker-compose
|
||||
];
|
||||
|
||||
systemd.services.docker-desktop-proxy = {
|
||||
description = "Docker Desktop proxy";
|
||||
script = ''
|
||||
${config.wsl.automountPath}/wsl/docker-desktop/docker-desktop-user-distro proxy --docker-desktop-root ${config.wsl.automountPath}/wsl/docker-desktop
|
||||
'';
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
Restart = "on-failure";
|
||||
RestartSec = "30s";
|
||||
};
|
||||
};
|
||||
|
||||
users.groups.docker.members = [
|
||||
config.wsl.defaultUser
|
||||
];
|
||||
|
||||
};
|
||||
|
||||
}
|
40
profiles/wsl/nixos-wsl/modules/docker-native.nix
Executable file
40
profiles/wsl/nixos-wsl/modules/docker-native.nix
Executable file
|
@ -0,0 +1,40 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
with builtins; with lib; {
|
||||
|
||||
options.wsl.docker-native = with types; {
|
||||
enable = mkEnableOption "Native Docker integration in NixOS.";
|
||||
|
||||
addToDockerGroup = mkOption {
|
||||
type = bool;
|
||||
default = config.security.sudo.wheelNeedsPassword;
|
||||
description = ''
|
||||
Wether to add the default user to the docker group.
|
||||
|
||||
This is not recommended, if you have a password, because it essentially permits unauthenticated root access.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config =
|
||||
let
|
||||
cfg = config.wsl.docker-native;
|
||||
in
|
||||
mkIf (config.wsl.enable && cfg.enable) {
|
||||
nixpkgs.overlays = [
|
||||
(self: super: {
|
||||
docker = super.docker.override { iptables = pkgs.iptables-legacy; };
|
||||
})
|
||||
];
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
docker
|
||||
docker-compose
|
||||
];
|
||||
|
||||
virtualisation.docker.enable = true;
|
||||
|
||||
users.groups.docker.members = lib.mkIf cfg.addToDockerGroup [
|
||||
config.wsl.defaultUser
|
||||
];
|
||||
};
|
||||
}
|
73
profiles/wsl/nixos-wsl/modules/installer.nix
Executable file
73
profiles/wsl/nixos-wsl/modules/installer.nix
Executable file
|
@ -0,0 +1,73 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
with builtins; with lib; {
|
||||
|
||||
config = mkIf config.wsl.enable (
|
||||
let
|
||||
mkTarball = pkgs.callPackage "${lib.cleanSource pkgs.path}/nixos/lib/make-system-tarball.nix";
|
||||
|
||||
pkgs2storeContents = map (x: { object = x; symlink = "none"; });
|
||||
|
||||
rootfs = let tarball = config.system.build.tarball; in "${tarball}/tarball/${tarball.fileName}.tar${tarball.extension}";
|
||||
|
||||
installer = pkgs.writeScript "installer.sh" ''
|
||||
#!${pkgs.busybox}/bin/sh
|
||||
BASEPATH=$PATH
|
||||
export PATH=$BASEPATH:${pkgs.busybox}/bin # Add busybox to path
|
||||
|
||||
set -e
|
||||
cd /
|
||||
|
||||
echo "Unpacking root file system..."
|
||||
${pkgs.pv}/bin/pv ${rootfs} | tar xz
|
||||
|
||||
echo "Activating nix configuration..."
|
||||
/nix/var/nix/profiles/system/activate
|
||||
PATH=$BASEPATH:/run/current-system/sw/bin # Use packages from target system
|
||||
|
||||
echo "Cleaning up installer files..."
|
||||
nix-collect-garbage
|
||||
rm /nix-path-registration
|
||||
|
||||
echo "Optimizing store..."
|
||||
nix-store --optimize
|
||||
|
||||
# Don't package the shell here, it's contained in the rootfs
|
||||
exec ${builtins.unsafeDiscardStringContext config.users.users.root.shell} "$@"
|
||||
'';
|
||||
|
||||
# Set installer.sh as the root shell
|
||||
passwd = pkgs.writeText "passwd" ''
|
||||
root:x:0:0:System administrator:/root:${installer}
|
||||
'';
|
||||
in
|
||||
{
|
||||
|
||||
system.build.installer = mkTarball {
|
||||
fileName = "nixos-wsl-installer";
|
||||
compressCommand = "gzip";
|
||||
compressionExtension = ".gz";
|
||||
extraArgs = "--hard-dereference";
|
||||
|
||||
storeContents = with pkgs; pkgs2storeContents [
|
||||
installer
|
||||
];
|
||||
|
||||
contents = [
|
||||
{ source = config.environment.etc."wsl.conf".source; target = "/etc/wsl.conf"; }
|
||||
{ source = config.environment.etc."fstab".source; target = "/etc/fstab"; }
|
||||
{ source = passwd; target = "/etc/passwd"; }
|
||||
{ source = "${pkgs.busybox}/bin/busybox"; target = "/bin/sh"; }
|
||||
{ source = "${pkgs.busybox}/bin/busybox"; target = "/bin/mount"; }
|
||||
];
|
||||
|
||||
extraCommands = pkgs.writeShellScript "prepare" ''
|
||||
export PATH=$PATH:${pkgs.coreutils}/bin
|
||||
mkdir -p bin
|
||||
ln -s /init bin/wslpath
|
||||
'';
|
||||
};
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
}
|
88
profiles/wsl/nixos-wsl/modules/interop.nix
Executable file
88
profiles/wsl/nixos-wsl/modules/interop.nix
Executable file
|
@ -0,0 +1,88 @@
|
|||
{ lib, pkgs, config, ... }:
|
||||
|
||||
with builtins; with lib;
|
||||
{
|
||||
imports = [
|
||||
(mkRenamedOptionModule [ "wsl" "compatibility" "interopPreserveArgvZero" ] [ "wsl" "interop" "preserveArgvZero" ])
|
||||
];
|
||||
|
||||
options.wsl.interop = with types; {
|
||||
register = mkOption {
|
||||
type = bool;
|
||||
default = false; # Use the existing registration by default
|
||||
description = "Explicitly register the binfmt_misc handler for Windows executables";
|
||||
};
|
||||
|
||||
includePath = mkOption {
|
||||
type = bool;
|
||||
default = true;
|
||||
description = "Include Windows PATH in WSL PATH";
|
||||
};
|
||||
|
||||
preserveArgvZero = mkOption {
|
||||
type = nullOr bool;
|
||||
default = null;
|
||||
description = ''
|
||||
Register binfmt interpreter for Windows executables with 'preserves argv[0]' flag.
|
||||
|
||||
Default (null): autodetect, at some performance cost.
|
||||
To avoid the performance cost, set this to true for WSL Preview 0.58 and up,
|
||||
or to false for any older versions, including pre-Microsoft Store and Windows 10.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config =
|
||||
let
|
||||
cfg = config.wsl.interop;
|
||||
in
|
||||
mkIf config.wsl.enable {
|
||||
|
||||
boot.binfmt.registrations = mkIf cfg.register {
|
||||
WSLInterop =
|
||||
let
|
||||
compat = cfg.preserveArgvZero;
|
||||
|
||||
# WSL Preview 0.58 and up registers the /init binfmt interp for Windows executable
|
||||
# with the "preserve argv[0]" flag, so if you run `./foo.exe`, the interp gets invoked
|
||||
# as `/init foo.exe ./foo.exe`.
|
||||
# argv[0] --^ ^-- actual path
|
||||
#
|
||||
# Older versions expect to be called without the argv[0] bit, simply as `/init ./foo.exe`.
|
||||
#
|
||||
# We detect that by running `/init /known-not-existing-path.exe` and checking the exit code:
|
||||
# the new style interp expects at least two arguments, so exits with exit code 1,
|
||||
# presumably meaning "parsing error"; the old style interp attempts to actually run
|
||||
# the executable, fails to find it, and exits with 255.
|
||||
compatWrapper = pkgs.writeShellScript "nixos-wsl-binfmt-hack" ''
|
||||
/init /nixos-wsl-does-not-exist.exe
|
||||
[ $? -eq 255 ] && shift
|
||||
exec /init "$@"
|
||||
'';
|
||||
|
||||
# use the autodetect hack if unset, otherwise call /init directly
|
||||
interpreter = if compat == null then compatWrapper else "/init";
|
||||
|
||||
# enable for the wrapper and autodetect hack
|
||||
preserveArgvZero = if compat == false then false else true;
|
||||
in
|
||||
{
|
||||
magicOrExtension = "MZ";
|
||||
fixBinary = true;
|
||||
wrapInterpreterInShell = false;
|
||||
inherit interpreter preserveArgvZero;
|
||||
};
|
||||
};
|
||||
|
||||
# Include Windows %PATH% in Linux $PATH.
|
||||
environment.extraInit = mkIf cfg.includePath ''PATH="$PATH:$WSLPATH"'';
|
||||
|
||||
warnings =
|
||||
let
|
||||
registrations = config.boot.binfmt.registrations;
|
||||
in
|
||||
optional (!(registrations ? WSLInterop) && (length (attrNames config.boot.binfmt.registrations)) != 0) "Having any binfmt registrations without re-registering WSLInterop (wsl.interop.register) will break running .exe files from WSL2";
|
||||
};
|
||||
|
||||
|
||||
}
|
139
profiles/wsl/nixos-wsl/modules/wsl-distro.nix
Executable file
139
profiles/wsl/nixos-wsl/modules/wsl-distro.nix
Executable file
|
@ -0,0 +1,139 @@
|
|||
{ lib, pkgs, config, ... }:
|
||||
|
||||
with builtins; with lib;
|
||||
{
|
||||
options.wsl = with types;
|
||||
let
|
||||
coercedToStr = coercedTo (oneOf [ bool path int ]) (toString) str;
|
||||
in
|
||||
{
|
||||
enable = mkEnableOption "support for running NixOS as a WSL distribution";
|
||||
automountPath = mkOption {
|
||||
type = str;
|
||||
default = "/mnt";
|
||||
description = "The path where windows drives are mounted (e.g. /mnt/c)";
|
||||
};
|
||||
automountOptions = mkOption {
|
||||
type = str;
|
||||
default = "metadata,uid=1000,gid=100";
|
||||
description = "Options to use when mounting windows drives";
|
||||
};
|
||||
defaultUser = mkOption {
|
||||
type = str;
|
||||
default = "nixos";
|
||||
description = "The name of the default user";
|
||||
};
|
||||
startMenuLaunchers = mkEnableOption "shortcuts for GUI applications in the windows start menu";
|
||||
wslConf = mkOption {
|
||||
type = attrsOf (attrsOf (oneOf [ str int bool ]));
|
||||
description = "Entries that are added to /etc/wsl.conf";
|
||||
};
|
||||
};
|
||||
|
||||
config =
|
||||
let
|
||||
cfg = config.wsl;
|
||||
syschdemd = import ../syschdemd.nix { inherit lib pkgs config; inherit (cfg) automountPath defaultUser; defaultUserHome = config.users.users.${cfg.defaultUser}.home; };
|
||||
in
|
||||
mkIf cfg.enable {
|
||||
|
||||
wsl.wslConf = {
|
||||
automount = {
|
||||
enabled = true;
|
||||
mountFsTab = true;
|
||||
root = "${cfg.automountPath}/";
|
||||
options = cfg.automountOptions;
|
||||
};
|
||||
network = {
|
||||
generateResolvConf = mkDefault true;
|
||||
generateHosts = mkDefault true;
|
||||
};
|
||||
};
|
||||
|
||||
# WSL is closer to a container than anything else
|
||||
boot.isContainer = true;
|
||||
|
||||
environment.noXlibs = lib.mkForce false; # override xlibs not being installed (due to isContainer) to enable the use of GUI apps
|
||||
hardware.opengl.enable = true; # Enable GPU acceleration
|
||||
|
||||
environment = {
|
||||
|
||||
etc = {
|
||||
"wsl.conf".text = generators.toINI { } cfg.wslConf;
|
||||
|
||||
# DNS settings are managed by WSL
|
||||
hosts.enable = !config.wsl.wslConf.network.generateHosts;
|
||||
"resolv.conf".enable = !config.wsl.wslConf.network.generateResolvConf;
|
||||
};
|
||||
|
||||
systemPackages = [
|
||||
(pkgs.runCommand "wslpath" { } ''
|
||||
mkdir -p $out/bin
|
||||
ln -s /init $out/bin/wslpath
|
||||
'')
|
||||
];
|
||||
};
|
||||
|
||||
networking.dhcpcd.enable = false;
|
||||
|
||||
users.users.${cfg.defaultUser} = {
|
||||
isNormalUser = true;
|
||||
uid = 1000;
|
||||
extraGroups = [ "wheel" ]; # Allow the default user to use sudo
|
||||
};
|
||||
|
||||
users.users.root = {
|
||||
shell = "${syschdemd}/bin/syschdemd";
|
||||
# Otherwise WSL fails to login as root with "initgroups failed 5"
|
||||
extraGroups = [ "root" ];
|
||||
};
|
||||
|
||||
security.sudo = {
|
||||
extraConfig = ''
|
||||
Defaults env_keep+=INSIDE_NAMESPACE
|
||||
'';
|
||||
wheelNeedsPassword = mkDefault false; # The default user will not have a password by default
|
||||
};
|
||||
|
||||
system.activationScripts = {
|
||||
copy-launchers = mkIf cfg.startMenuLaunchers (
|
||||
stringAfter [ ] ''
|
||||
for x in applications icons; do
|
||||
echo "Copying /usr/share/$x"
|
||||
mkdir -p /usr/share/$x
|
||||
${pkgs.rsync}/bin/rsync -ar --delete $systemConfig/sw/share/$x/. /usr/share/$x
|
||||
done
|
||||
''
|
||||
);
|
||||
populateBin = stringAfter [ ] ''
|
||||
echo "setting up /bin..."
|
||||
ln -sf /init /bin/wslpath
|
||||
ln -sf ${pkgs.bashInteractive}/bin/bash /bin/sh
|
||||
ln -sf ${pkgs.util-linux}/bin/mount /bin/mount
|
||||
'';
|
||||
};
|
||||
|
||||
systemd = {
|
||||
# Disable systemd units that don't make sense on WSL
|
||||
services = {
|
||||
"serial-getty@ttyS0".enable = false;
|
||||
"serial-getty@hvc0".enable = false;
|
||||
"getty@tty1".enable = false;
|
||||
"autovt@".enable = false;
|
||||
firewall.enable = false;
|
||||
systemd-resolved.enable = false;
|
||||
systemd-udevd.enable = false;
|
||||
};
|
||||
|
||||
tmpfiles.rules = [
|
||||
# Don't remove the X11 socket
|
||||
"d /tmp/.X11-unix 1777 root root"
|
||||
];
|
||||
|
||||
# Don't allow emergency mode, because we don't have a console.
|
||||
enableEmergencyMode = false;
|
||||
};
|
||||
|
||||
warnings = (optional (config.systemd.services.systemd-resolved.enable && config.wsl.wslConf.network.generateResolvConf) "systemd-resolved is enabled, but resolv.conf is managed by WSL");
|
||||
};
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue