summaryrefslogtreecommitdiff
path: root/modules/wsl-distro.nix
blob: f5b94ce16f1f4f1b42688a79a8815f5c7f1ddfa0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
{ lib, pkgs, config, options, ... }:

with lib; {

  options.wsl = with types; {
    enable = mkEnableOption "support for running NixOS as a WSL distribution";
    nativeSystemd = mkOption {
      type = bool;
      default = false;
      description = "Use native WSL systemd support";
    };
    defaultUser = mkOption {
      type = str;
      default = "nixos";
      description = "The name of the default user";
    };
    startMenuLaunchers = mkEnableOption "shortcuts for GUI applications in the windows start menu";
  };

  config =
    let
      cfg = config.wsl;

      syschdemd = pkgs.callPackage ../scripts/syschdemd.nix {
        automountPath = cfg.wslConf.automount.root;
        defaultUser = config.users.users.${cfg.defaultUser};
      };

      shim = pkgs.callPackage ../scripts/native-systemd-shim/shim.nix { };

      bashWrapper = pkgs.runCommand "nixos-wsl-bash-wrapper" { nativeBuildInputs = [ pkgs.makeWrapper ]; } ''
        makeWrapper ${pkgs.bashInteractive}/bin/sh $out/bin/sh --prefix PATH ':' ${lib.makeBinPath [pkgs.systemd pkgs.gnugrep]}
      '';

      bash = if cfg.nativeSystemd then bashWrapper else pkgs.bashInteractive;
    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;
          };

          # 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}..."
                  if [[ -d $systemConfig/sw/share/$x ]]; then
                    mkdir -p /usr/share/$x
                    ${pkgs.rsync}/bin/rsync -ar --delete $systemConfig/sw/share/$x/. /usr/share/$x
                  else
                    rm -rf /usr/share/$x
                  fi
                done
              ''
            );
            populateBin = stringAfter [ ] ''
              echo "setting up /bin..."
              ln -sf /init /bin/wslpath
              ln -sf ${bash}/bin/sh /bin/sh
              ln -sf ${pkgs.util-linux}/bin/mount /bin/mount
            '';
            update-entrypoint.text = ''
              mkdir -p /nix/nixos-wsl
              ln -sfn ${config.users.users.root.shell} /nix/nixos-wsl/entrypoint
            '';
          };

          # no udev devices can be attached
          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;
              # system clock cannot be changed
              systemd-timesyncd.enable = false;
            };

            # 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;

          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)"
            )
          ];
        }
        (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"'';
        })
        (mkIf 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 ${shim}/bin/nixos-wsl-native-systemd-shim /sbin/init
            '';
            setupLogin = 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 = ''
              export WSLPATH=$(echo "$PATH" | tr ':' '\0' | command grep -az "^${cfg.wslConf.automount.root}" | tr '\0' ':')
              ${if cfg.interop.includePath then "" else ''
                export PATH=$(echo "$PATH" | tr ':' '\0' | command grep -avz "^${cfg.wslConf.automount.root}" | tr '\0' ':')
              ''}
            '';
          };
        })
        # 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;
        })
      ]);
}