Skip to content

Instantly share code, notes, and snippets.

@blakeNaccarato
Last active November 5, 2022 21:43
Show Gist options
  • Save blakeNaccarato/e1a9cfeedc367640685740e21ea9c5a0 to your computer and use it in GitHub Desktop.
Save blakeNaccarato/e1a9cfeedc367640685740e21ea9c5a0 to your computer and use it in GitHub Desktop.
[20/08/25] A guide to setting up Windows Subsystem for Linux 2 (WSL2) in Windows 10 version 2004

WSL2 Setup [20/08/25]

First, execute these commands in Windows pwsh/cmd to ensure the proper features are turned on.

dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart

Then, update the WSL 2 Linux kernel.

You will want to run wsl --set-default-version 2 so that future distros are set up as SL2. WSL2 or WSL1 refers to the way in which we interact with our distros. WSL2 is the new standard, and will get progressively better as time goes on. Eventually, WSL2 will even have GPU-compute/CUDA support, and full, built-in GUI support. For now, GUI stuff will have to be "hacked" in (see GUI setup) below).

Installing the default "Ubuntu" distro from the Microsoft Store

If you already have a WSL1 distro named "Ubuntu", follow the steps for Exporting a distro first, including the final step of unregistering the distro after exporting. Find Ubuntu on the Microsoft Store and install it. The first time you run it (through the Start menu or by running ubuntu),  it will create a WSL2 distro by the name of "Ubuntu".  It will ask for a username and password. If you plan to share the distro with others, choose a generic username and password.

Exporting a distro

In the context of WSL, a "distro" is a specific image/instance of a UNIX-like OS installed in WSL. For instance, the "Ubuntu" distro was automatically created when we ran ubuntu in console after installing it from the Microsoft Store. To see which distros we have installed, run wsl --list --verbose or wsl -l -v for short. If you have Docker installed, you might see some Docker distros in addition to the Ubuntu one automatically created above. Don't worry about those.

Let's  say you want to back up the "Ubuntu" distro. We can do that in Windows pwsh/cmd as follows

wsl --export Ubuntu "C:\Users\<You>\Desktop\Ubuntu_backup.tar"

which will pack up the distro into a tarball for later unpacking. We can now safely delete/unregister the "live" distro, now that we have a frozen backup in the tarball. We can do that with wsl --unregister Ubuntu.

Importing a distro

The distro that we just exported can now be re-imported, if so desired. Or else you want to import a different distro that you have previously exported. Maybe it's your old WSL1 distro, and you want to re-import it by a different name. You could do that by running in Windows pwsh/cmd

wsl --import Ubuntu_imported "C:\Users\<You>\WSL\Ubuntu_imported" "C:\Users\<You>\Desktop\Ubuntu_backup.tar"

Let's go through this command one argument at a time.

  • The name Ubuntu_imported tells WSL that the distro to be imported will be named as such. After importing, you would be able to find it in the wsl -l -v list by that name.
  • The path ...\WSL\Ubuntu_imported is where we want to host/store the "live" distro. If everything goes smoothly, there will be a single VHDX file inside ...\WSL\Ubuntu_imported. This is a virtual hard drive that will hold the entirety of the distro's installation. Windows default distros are installed somewhere in %APPDATA%, but it's an ugly folder so I prefer importing to a special "WSL" folder.
  • The path "...\Desktop\Ubuntu_backup.tar" is the source file that we're importing from.

Now if we run wsl -l -v, we will see Ubuntu_old as one of the available distros. We can set it as default by entering wsl --set-default Ubuntu_imported. Now when we type just wsl in PowerShell, we'll enter the Ubuntu_imported distro.

By the way, if you SHIFT+Right-click on an item in Windows Explorer, a "Copy as path" option will appear in the context menu, which is great for grabbing paths on the fly.

Setting an imported distro's default user to something other than root

If a distro doesn't have user.default set in a /etc/wsl.conf file, then when it is exported and re-imported, the default user will become root. In order to fix this, create/modify /etc/wsl.conf to have the contents of wsl.conf from this Gist. Note that if you're using VSCode with the Remote - WSL extension to attach to the WSL distro, VSCode will not have root privileges and therefore cannot save changes to /etc/wsl.conf. You will have to use sudo vi /etc/wsl.conf or sudo nano /etc/wsl.conf. These are in-terminal text editors. Of the two, nano is probably more straightforward.

Now you can exit the distro (CTRL+D is a shortcut to log out), and re-open it. You should be greeted by a colorful user@COMPUTER_NAME prompt instead of a drab root@COMPUTER_NAME. By the way, if you have the appropriate /etc/wsl.conf in your distro prior to exporting/re-importing, you won't have to do this every time.

Managing distros and suggested workflow

Now that you know how to import, export, and set default distros, you should determine a workflow that suits you. For me, I've found it useful to wsl --export at "checkpoints" in setting up a distro. For example, I have saved off a barebones Ubuntu install with just Python 3.7  installed. I will probably maintain separate distros for OpenFOAM, OpenCV, and LaTeX. You must choose a distro's name when you wsl --import it, but you can always change that name down the road by doing a sequence like the following:

wsl --export Distro_with_bad_name "C:\...\Distro.tar"
wsl --unregister Distro_with_bad_name
wsl --import Distro_with_good_name "C:\...\Distro_with_good_name" "C:\...\Distro.tar"
wsl --set-default Distro_with_good_name

If you've already configured /etc/wsl.conf, you will even be automatically logged in once you spin up the new distro. You can imagine a workflow that involves a couple project-specific distros. You could even send the tarball of a distro to someone to have them be able to run the code you've written. It's kind of like Docker on a budget.

GUI setup

Install VcXSrv. This will run an X Window System on the Windows side, which will be "listening" for events coming from Ubuntu. Then create/modify C:\Program Files\VcXsrv\config.xlaunch to have the contents of config.xlaunch from this Gist.

Then you will append the contents of .bashrc from this Gist to your own ~/.bashrc in your Ubuntu distro. Recall, the commands in this file run every time you open an interactive shell/terminal in your Ubuntu install.

Let's say you're working on Python code in ~/projects/homework_1/, and your Python code has UI elements, like matplotlib or tkinter/Qt buttons and prompts. You can trigger the GUI stuff in ~/.bashrc to run by creating a file named .env in your project directory, and putting USE_X=true (exactly like that, no spaces between equal sign) in the file. It will look like .env from the Gist. Now when an interactive terminal window opens in the project folder, the following block in your .bashrc will run, triggering the function defined earlier and getting the X Window System up and running.

# Start the X Window System if $USE_X is `true`
# Note that you can't do `if $USE_X` or any simpler variation, due to bash quirks
if [ "$USE_X" = true ]; then
    x_start
fi

Maintaining backup of system images [21/02/19]

If you predominantly use apt to install/remove packages, then you can just filter the file /var/log/apt/history.log by lines containing apt install and apt remove to determine the packages installed in a given system image on top of the default packages that came with the distro.

# ... THE REST OF YOUR `~/.bashrc` GOES HERE ... #
#! Modifications to the default `~/.bashrc`
#* Import dotenv aka .env environment variables if it is found
if [ -f ./.env ] ; then
set -o allexport # enable shell option to export all created variables
source ./.env # now this will export all vars instead of just one
set +o allexport # disable shell option to export all created variables
fi
#* Run X Window System if `$USE_X` is `true`. Must install VcXsrv on Windows
# Uncomment this line if there is significant GUI lag
# export LIBGL_ALWAYS_INDIRECT=1
# Functions
x_is_running () {
tasklist.exe | grep -q "vcxsrv.exe"
}
x_start () {
# Necessary for now. Windows `localhost` is not known and must be pulled from ipconfig
export WSL_IP=$(ipconfig.exe | grep "WSL" -n | awk -F ":" '{print $1+4}')
export DISPLAY=$(ipconfig.exe | awk -v a=$WSL_IP '{if (NR==a) print $NF":0.0"}' | tr -d "\r")
# Start VcXsrv if it's not already started
export VCXSRV_PATH="/mnt/c/Program Files/VcXsrv"
if ! x_is_running; then
"$VCXSRV_PATH"/xlaunch.exe -run "$VCXSRV_PATH"/config.xlaunch
fi
}
x_stop () {
taskkill.exe /F /PID $(tasklist.exe | grep "vcxsrv.exe" | awk '{print $2}')
unset DISPLAY
}
x_restart() {
vcxsrv_stop
vcxsrv_start
}
# Start the X Window System if $USE_X is `true`
# Note that you can't do `if $USE_X` or any simpler variation, due to bash quirks
if [ "$USE_X" = true ]; then
x_start
fi
USE_X=true
<?xml version="1.0" encoding="UTF-8"?>
<XLaunch WindowMode="MultiWindow"
ClientMode="NoClient"
LocalClient="False"
Display="0"
LocalProgram="xcalc"
RemoteProgram="xterm"
RemotePassword=""
PrivateKey=""
RemoteHost=""
RemoteUser=""
XDMCPHost=""
XDMCPBroadcast="False"
XDMCPIndirect="False"
Clipboard="True"
ClipboardPrimary="True"
ExtraParams=""
Wgl="True"
DisableAC="False"
XDMCPTerminate="False"/>
# Log in as 'user' instead of 'root'.
[user]
default=user
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment