diff --git a/checks/shfmt.nix b/checks/shfmt.nix index 3a655147..0336dd8c 100644 --- a/checks/shfmt.nix +++ b/checks/shfmt.nix @@ -3,6 +3,6 @@ , ... }: runCommand "check-shfmt" { nativeBuildInputs = [ shfmt ]; } '' - shfmt -i 2 -d ${./../scripts}/*.sh + shfmt -i 2 -d $(find ${./..} -name '*.sh') touch $out '' diff --git a/modules/default.nix b/modules/default.nix index fc3d3448..0278634b 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -5,6 +5,7 @@ ./installer.nix ./interop.nix ./recovery.nix + ./systemd ./version.nix ./wsl-conf.nix ./wsl-distro.nix diff --git a/modules/recovery.nix b/modules/recovery.nix index 5387ebce..ec29da69 100644 --- a/modules/recovery.nix +++ b/modules/recovery.nix @@ -24,7 +24,7 @@ in config = { wsl.extraBin = [ # needs to be a copy, not a symlink, to be executable from outside - { src = "${recovery}/bin/nixos-wsl-recovery"; copy = true;} + { src = "${recovery}/bin/nixos-wsl-recovery"; copy = true; } ]; }; diff --git a/modules/systemd/default.nix b/modules/systemd/default.nix new file mode 100644 index 00000000..51ce0820 --- /dev/null +++ b/modules/systemd/default.nix @@ -0,0 +1,50 @@ +{ config, pkgs, lib, ... }: +with lib; { + + imports = [ + ./native + ./syschdemd + ]; + + options.wsl = with types; { + nativeSystemd = mkOption { + type = bool; + default = false; + description = "Use native WSL systemd support"; + }; + }; + + config = + let + cfg = config.wsl; + in + mkIf (cfg.enable) { + + wsl.binShPkg = if cfg.nativeSystemd then bashWrapper else pkgs.bashInteractive; + + # systemd-oomd requires cgroup pressure info which WSL doesn't have + systemd.oomd.enable = false; + + # useful for usbip but adds a dependency on various firmwares which are combined over 300 MB big + services.udev.enable = lib.mkDefault false; + + systemd = { + # Disable systemd units that don't make sense on WSL + services = { + firewall.enable = false; + systemd-resolved.enable = lib.mkDefault false; + # systemd-timesyncd actually works in WSL and without it the clock can drift + systemd-timesyncd.unitConfig.ConditionVirtualization = ""; + }; + + # Don't allow emergency mode, because we don't have a console. + enableEmergencyMode = false; + + # Link the X11 socket into place. This is a no-op on a normal setup, + # but helps if /tmp is a tmpfs or mounted from some other location. + tmpfiles.rules = [ "L /tmp/.X11-unix - - - - ${cfg.wslConf.automount.root}/wslg/.X11-unix" ]; + }; + + }; + +} diff --git a/modules/systemd/native/default.nix b/modules/systemd/native/default.nix new file mode 100644 index 00000000..f1e31b24 --- /dev/null +++ b/modules/systemd/native/default.nix @@ -0,0 +1,37 @@ +{ config, pkgs, lib, ... }: +with lib; { + + config = + let + cfg = config.wsl; + nativeUtils = pkgs.callPackage ../scripts/native-utils { }; + in + mkIf (cfg.enable && cfg.nativeSystemd) { + wsl.wslConf = { + user.default = config.users.users.${cfg.defaultUser}.name; + boot.systemd = true; + }; + + system.activationScripts = { + shimSystemd = stringAfter [ ] '' + echo "setting up /sbin/init shim..." + mkdir -p /sbin + ln -sf ${nativeUtils}/bin/systemd-shim /sbin/init + ''; + setupLogin = lib.mkIf cfg.populateBin (stringAfter [ ] '' + echo "setting up /bin/login..." + mkdir -p /bin + ln -sf ${pkgs.shadow}/bin/login /bin/login + ''); + }; + + environment = { + # preserve $PATH from parent + variables.PATH = [ "$PATH" ]; + extraInit = '' + eval $(${nativeUtils}/bin/split-path --automount-root="${cfg.wslConf.automount.root}" ${lib.optionalString cfg.interop.includePath "--include-interop"}) + ''; + }; + }; + +} diff --git a/modules/systemd/syschdemd/default.nix b/modules/systemd/syschdemd/default.nix new file mode 100644 index 00000000..62cb51b0 --- /dev/null +++ b/modules/systemd/syschdemd/default.nix @@ -0,0 +1,37 @@ +{ config, pkgs, lib, ... }: +with lib; { + + options = { }; + + config = + let + cfg = config.wsl; + + syschdemd = pkgs.callPackage ./syschdemd.nix { + automountPath = cfg.wslConf.automount.root; + defaultUser = config.users.users.${cfg.defaultUser}; + }; + in + mkIf (cfg.enable && !cfg.nativeSystemd) { + + users.users.root.shell = "${syschdemd}/bin/syschdemd"; + security.sudo.extraConfig = '' + Defaults env_keep+=INSIDE_NAMESPACE + ''; + wsl.wslConf.user.default = "root"; + + # Start a systemd user session when starting a command through runuser + security.pam.services.runuser.startSession = true; + + # Include Windows %PATH% in Linux $PATH. + environment.extraInit = mkIf cfg.interop.includePath ''PATH="$PATH:$WSLPATH"''; + environment.systemPackages = [ + (pkgs.runCommand "wslpath" { } '' + mkdir -p $out/bin + ln -s /init $out/bin/wslpath + '') + ]; + + }; + +} diff --git a/scripts/syschdemd.nix b/modules/systemd/syschdemd/syschdemd.nix similarity index 100% rename from scripts/syschdemd.nix rename to modules/systemd/syschdemd/syschdemd.nix diff --git a/scripts/syschdemd.sh b/modules/systemd/syschdemd/syschdemd.sh similarity index 100% rename from scripts/syschdemd.sh rename to modules/systemd/syschdemd/syschdemd.sh diff --git a/scripts/wrapper.sh b/modules/systemd/syschdemd/wrapper.sh similarity index 100% rename from scripts/wrapper.sh rename to modules/systemd/syschdemd/wrapper.sh diff --git a/modules/wsl-distro.nix b/modules/wsl-distro.nix index a9eed4b8..b93a4cb3 100644 --- a/modules/wsl-distro.nix +++ b/modules/wsl-distro.nix @@ -57,200 +57,113 @@ in })); description = "Additional files to be added to /bin"; }; - nativeSystemd = mkOption { - type = bool; - default = false; - description = "Use native WSL systemd support"; - }; startMenuLaunchers = mkEnableOption "shortcuts for GUI applications in the windows start menu"; }; config = - let - syschdemd = pkgs.callPackage ../scripts/syschdemd.nix { - automountPath = cfg.wslConf.automount.root; - defaultUser = config.users.users.${cfg.defaultUser}; + mkIf cfg.enable { + # WSL uses its own kernel and boot loader + boot = { + initrd.enable = false; + kernel.enable = false; + loader.grub.enable = false; + modprobeConfig.enable = false; + }; + system.build.installBootLoader = "${pkgs.coreutils}/bin/true"; + + # WSL does not support virtual consoles + console.enable = false; + + hardware.opengl.enable = true; # Enable GPU acceleration + + environment = { + # Only set the options if the files are managed by WSL + etc = mkMerge [ + (mkIf config.wsl.wslConf.network.generateHosts { + hosts.enable = false; + }) + (mkIf config.wsl.wslConf.network.generateResolvConf { + "resolv.conf".enable = false; + }) + ]; }; - nativeUtils = pkgs.callPackage ../scripts/native-utils { }; - in - mkIf cfg.enable ( - mkMerge [ - { - # WSL uses its own kernel and boot loader - boot = { - initrd.enable = false; - kernel.enable = false; - loader.grub.enable = false; - modprobeConfig.enable = false; - }; - system.build.installBootLoader = "${pkgs.coreutils}/bin/true"; - - # WSL does not support virtual consoles - console.enable = false; - - hardware.opengl.enable = true; # Enable GPU acceleration - - environment = { - # Only set the options if the files are managed by WSL - etc = mkMerge [ - (mkIf config.wsl.wslConf.network.generateHosts { - hosts.enable = false; - }) - (mkIf config.wsl.wslConf.network.generateResolvConf { - "resolv.conf".enable = false; - }) - ]; - }; - - # dhcp is handled by windows - networking.dhcpcd.enable = false; - - users.users.${cfg.defaultUser} = { - isNormalUser = true; - uid = 1000; - extraGroups = [ "wheel" ]; # Allow the default user to use sudo - }; - - # Otherwise WSL fails to login as root with "initgroups failed 5" - users.users.root.extraGroups = [ "root" ]; - - powerManagement.enable = false; - - security.sudo.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 "setting up /usr/share/''${x}..." - targets=() - if [[ -d "$systemConfig/sw/share/$x" ]]; then - targets+=("$systemConfig/sw/share/$x/.") - fi - if [[ -d "/etc/profiles/per-user/${cfg.defaultUser}/share/$x" ]]; then - targets+=("/etc/profiles/per-user/${cfg.defaultUser}/share/$x/.") - fi - - if (( ''${#targets[@]} != 0 )); then - mkdir -p "/usr/share/$x" - ${pkgs.rsync}/bin/rsync -ar --delete-after "''${targets[@]}" "/usr/share/$x" - else - rm -rf "/usr/share/$x" - fi - done - '' - ); - populateBin = lib.mkIf cfg.populateBin (stringAfter [ ] '' - echo "setting up /bin..." - ${concatStringsSep "\n" (map - (entry: - if entry.copy - then "cp -f ${entry.src} /bin/${entry.name}" - else "ln -sf ${entry.src} /bin/${entry.name}" - ) - config.wsl.extraBin - )} - ''); - update-entrypoint.text = '' - mkdir -p /nix/nixos-wsl - ln -sfn ${config.users.users.root.shell} /nix/nixos-wsl/entrypoint - ''; - }; - - # useful for usbip but adds a dependency on various firmwares which are combined over 300 MB big - services.udev.enable = lib.mkDefault false; - - systemd = { - # Disable systemd units that don't make sense on WSL - services = { - firewall.enable = false; - systemd-resolved.enable = lib.mkDefault false; - # systemd-timesyncd actually works in WSL and without it the clock can drift - systemd-timesyncd.unitConfig.ConditionVirtualization = ""; - }; - - # Don't allow emergency mode, because we don't have a console. - enableEmergencyMode = false; - - # Link the X11 socket into place. This is a no-op on a normal setup, - # but helps if /tmp is a tmpfs or mounted from some other location. - tmpfiles.rules = [ "L /tmp/.X11-unix - - - - ${cfg.wslConf.automount.root}/wslg/.X11-unix" ]; - }; - - # Start a systemd user session when starting a command through runuser - security.pam.services.runuser.startSession = true; + # dhcp is handled by windows + networking.dhcpcd.enable = false; - # require people to use lib.mkForce to make it harder to brick their installation - wsl = { - binShPkg = if cfg.nativeSystemd then bashWrapper else pkgs.bashInteractive; - populateBin = true; - extraBin = [ - { src = "/init"; name = "wslpath"; } - { src = "${cfg.binShPkg}/bin/sh"; name = "sh"; } - { src = "${pkgs.util-linux}/bin/mount"; } - ]; - }; + users.users.${cfg.defaultUser} = { + isNormalUser = true; + uid = 1000; + extraGroups = [ "wheel" ]; # Allow the default user to use sudo + }; - warnings = flatten [ - (optional (config.services.resolved.enable && config.wsl.wslConf.network.generateResolvConf) - "systemd-resolved is enabled, but resolv.conf is managed by WSL (wsl.wslConf.network.generateResolvConf)" - ) - (optional ((length config.networking.nameservers) > 0 && config.wsl.wslConf.network.generateResolvConf) - "custom nameservers are set (networking.nameservers), but resolv.conf is managed by WSL (wsl.wslConf.network.generateResolvConf)" + # Otherwise WSL fails to login as root with "initgroups failed 5" + users.users.root.extraGroups = [ "root" ]; + + powerManagement.enable = false; + + security.sudo.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 "setting up /usr/share/''${x}..." + targets=() + if [[ -d "$systemConfig/sw/share/$x" ]]; then + targets+=("$systemConfig/sw/share/$x/.") + fi + if [[ -d "/etc/profiles/per-user/${cfg.defaultUser}/share/$x" ]]; then + targets+=("/etc/profiles/per-user/${cfg.defaultUser}/share/$x/.") + fi + + if (( ''${#targets[@]} != 0 )); then + mkdir -p "/usr/share/$x" + ${pkgs.rsync}/bin/rsync -ar --delete-after "''${targets[@]}" "/usr/share/$x" + else + rm -rf "/usr/share/$x" + fi + done + '' + ); + populateBin = lib.mkIf cfg.populateBin (stringAfter [ ] '' + echo "setting up /bin..." + ${concatStringsSep "\n" (map + (entry: + if entry.copy + then "cp -f ${entry.src} /bin/${entry.name}" + else "ln -sf ${entry.src} /bin/${entry.name}" ) - (optional ((length config.networking.nameservers) == 0 && !config.services.resolved.enable && !config.wsl.wslConf.network.generateResolvConf) - "resolv.conf generation is turned off (wsl.wslConf.network.generateResolvConf), but no other nameservers are configured (networking.nameservers)" - ) - ]; - } - (mkIf (!cfg.nativeSystemd) { - users.users.root.shell = "${syschdemd}/bin/syschdemd"; - security.sudo.extraConfig = '' - Defaults env_keep+=INSIDE_NAMESPACE - ''; - wsl.wslConf.user.default = "root"; - - # Include Windows %PATH% in Linux $PATH. - environment.extraInit = mkIf cfg.interop.includePath ''PATH="$PATH:$WSLPATH"''; - environment.systemPackages = [ - (pkgs.runCommand "wslpath" { } '' - mkdir -p $out/bin - ln -s /init $out/bin/wslpath - '') - ]; - }) - (mkIf cfg.nativeSystemd { - wsl.wslConf = { - user.default = config.users.users.${cfg.defaultUser}.name; - boot.systemd = true; - }; + config.wsl.extraBin + )} + ''); + # TODO: This is only needed for the docker tests, it can be removed when they are moved to something else + update-entrypoint.text = '' + mkdir -p /nix/nixos-wsl + ln -sfn ${config.users.users.root.shell} /nix/nixos-wsl/entrypoint + ''; + }; - system.activationScripts = { - shimSystemd = stringAfter [ ] '' - echo "setting up /sbin/init shim..." - mkdir -p /sbin - ln -sf ${nativeUtils}/bin/systemd-shim /sbin/init - ''; - setupLogin = lib.mkIf cfg.populateBin (stringAfter [ ] '' - echo "setting up /bin/login..." - mkdir -p /bin - ln -sf ${pkgs.shadow}/bin/login /bin/login - ''); - }; + # require people to use lib.mkForce to make it harder to brick their installation + wsl = { + populateBin = true; + extraBin = [ + { src = "/init"; name = "wslpath"; } + { src = "${cfg.binShPkg}/bin/sh"; name = "sh"; } + { src = "${pkgs.util-linux}/bin/mount"; } + ]; + }; - environment = { - # preserve $PATH from parent - variables.PATH = [ "$PATH" ]; - extraInit = '' - eval $(${nativeUtils}/bin/split-path --automount-root="${cfg.wslConf.automount.root}" ${lib.optionalString cfg.interop.includePath "--include-interop"}) - ''; - }; - }) - # this option doesn't exist on older NixOS, so hack. - (lib.optionalAttrs (builtins.hasAttr "oomd" options.systemd) { - # systemd-oomd requires cgroup pressure info which WSL doesn't have - systemd.oomd.enable = false; - }) - ]); + warnings = flatten [ + (optional (config.services.resolved.enable && config.wsl.wslConf.network.generateResolvConf) + "systemd-resolved is enabled, but resolv.conf is managed by WSL (wsl.wslConf.network.generateResolvConf)" + ) + (optional ((length config.networking.nameservers) > 0 && config.wsl.wslConf.network.generateResolvConf) + "custom nameservers are set (networking.nameservers), but resolv.conf is managed by WSL (wsl.wslConf.network.generateResolvConf)" + ) + (optional ((length config.networking.nameservers) == 0 && !config.services.resolved.enable && !config.wsl.wslConf.network.generateResolvConf) + "resolv.conf generation is turned off (wsl.wslConf.network.generateResolvConf), but no other nameservers are configured (networking.nameservers)" + ) + ]; + }; }