My Windows Subsystem for Linux setup
My trusty 2011 Macbook Air had started to show its age – a couple of missing keys, the fan being on pretty much all the time – so I looked for a replacement. After some tedious investigation, I settled upon a Dell XPS 9370 from their Outlet store. Windows 10 can run Linux now, via the Windows Subsystem for Linux (WSL), so it seemed like an interesting thing to spend some time with, and if it all went sour I could always fully install Linux instead.
I’m sure some people have a reason to prefer one OS over another, but I have never really understood it, at least not since my last RISC OS machine :) They all have awful file management (as do I), and they’re all much of a muchness in every way I seem to use them; I can install most things I want to use on anything, and most of the rest is in a web browser.
I’ve had it about a month now, and I thought it worth writing up some of the things I’ve done to aid shared development between Windows and WSL.
So firstly, let us rattle off the list of apps I installed. Many were identical to what it would be on a Mac: Firefox (with Containers), Thunderbird, Slack, Steam, VSCode, Licecap, Spotify, Git, KeePassXC, Chrome, and OpenVPN. As replacements for some small Mac programs, I have installed HexChat (IRC), KeyFerret (special character input), Ditto (clipboard memory), and NVDA (screen reader).
I also newly installed LDraw because my four year old has made her own Lego dinosaur and wanted to generate some instructions :)
Setting up WSL
To install WSL and Ubuntu, I followed Microsoft’s instructions (except in more stereotypical Windows behaviour, I activated WSL by going to Control Panel → Programs and Features → Turn Windows features on or off, rather than using the command line). Ubuntu was indeed a one click install and initial set up worked fine. I’m not sure this is something I’d ever have predicted years ago!
Shell and terminal
fish is my shell of choice (saves so
many typos). If you start WSL with wsl.exe rather than bash.exe, then it uses
your default shell. I tried a few terminals and settled on Hyper with the Fira Code font, though it is
somewhat annoying (copy and paste use Ctrl-Shift, new tabs don’t keep the same
directory, etc). Thankfully I have always loved autojump, which alleviates the
latter annoyance, making project navigation trivial (
j f takes me
directly to my main FixMyStreet git checkout).
In order for WSL to maintain Linux file permissions, you need to enable
metadata on the mounted C drive.
tells you how, but not why, which can be found at
this blog entry.
My wsl.conf looks like this:
enabled = true
options = "metadata,umask=002"
mountFsTab = true
postgres installed and ran fine in WSL, but I’d like it to start
on computer start. I found the instructions at
helpful, though I made a few changes (e.g. I added a file to
Sharing ssh keys between Windows and Linux
So now I was at a point where I had a working Linux installation inside my Windows laptop. But I didn’t want to keep it totally separate, because that would make development harder. I wanted them to work together where that would be useful.
For example, KeePassXC lets me store my SSH key passphrase within it, and automatically add/remove it from the SSH agent in Windows while unlocked. I didn’t want to have to enter my passphrase within WSL when running SSH, in order to add the key to the Linux SSH agent.
Firstly, I made the
/home/matthew/.ssh directory in WSL a symlink to
so my SSH keys could be shared. I made the SSH agent run automatically on Windows (it’s not by default),
which can pipe the Windows SSH agent information through to WSL.
Everyone appears to have slightly different SSH agent script to run on login; here’s mine:
~ $ cat .config/fish/functions/fish_ssh_agent.fish
set SSH_ENV $HOME/.ssh/environment
set SSH_AGENT /mnt/c/Users/Matthew/bin/ssh-agent-wsl -c -b
function __ssh_agent_needs_starting -d "check if ssh agent is already started"
if test -f $SSH_ENV
source $SSH_ENV > /dev/null
ssh-add -l >/dev/null 2>&1
if test $status -eq 2
function __ssh_agent_start -d "start a new ssh agent"
eval $SSH_AGENT > $SSH_ENV
source $SSH_ENV > /dev/null
As you can see I run ssh-agent-wsl with
-b, as without that it started
using runaway CPU after the last terminal window was closed.
Update August 2020: I have upgraded to WSL2, and the above is now a bit simpler. I installed npiperelay in ~/winhome/.wsl/ (winhome a symlink to my Windows homedir), installed socat in Ubuntu, then changed the above function to:
set -x SSH_AUTH_SOCK $HOME/.ssh/agent.sock
ss -a | grep -q $SSH_AUTH_SOCK
if [ $status -ne 0 ]
rm -f $SSH_AUTH_SOCK
setsid socat UNIX-LISTEN:$SSH_AUTH_SOCK,fork EXEC:"$HOME/winhome/.wsl/npiperelay.exe -ei -s //./pipe/openssh-ssh-agent",nofork &
end >/dev/null 2>&1
Sharing git repos between Windows and Linux
One very frequent thing I do is work with git repositories in both Windows (using VSCode) and WSL, and so want to run git commands in both. So I needed to make sure these interoperated okay.
In Windows git, I set
core.autocrlf=false, as I don’t want git
to do any cleverness – this repo will be accessible by two different OSs at
once! I set
core.filemode to false as otherwise it gets confused
It took a while to track this down, but Windows git includes its own SSH commands,
which meant I was getting asked for my passphrase even though I had my key loaded into
the Windows SSH agent. Thankfully, recent git lets you set
core.sshCommand to the SSH you wish to use, so I set it to
ssh) and this was
The only annoyance left was symlinks. I enabled symlink creation by turning on
Developer Mode and adding my user to the correct policy group, as explained at
https://github.com/git-for-windows/git/wiki/Symbolic-Links, which meant I
core.symlinks to true. Symlinks then worked fine in
both Windows and WSL, but sadly git status would say that the symlinks were modified
in one OS or the other (ie. if I
git reset them in Windows, WSL would say they were modified, and vice-versa).
on StackOverflow, it seems this is one thing I have to live with, and as mentioned
there have marked them as
assume-unchanged, as there are only a small
number and they very rarely change.
On a Mac, you can type "open [file]" to open that file with the default program for it. To recreate the same thing, I created a tiny script in my bin directory:
For slightly more advanced behaviour, I have just discovered
wsl-open which I should probably install.
cmd.exe /C start "" $argv
For this, we use a piece of software called Cypress which helps automate it all and aid development, and wrote a script to do the set up/tear down parts of creating a test database, running a test server for Cypress to connect to, and so on. Our test suite runs on Travis with each commit, and our front end tests running there push video recordings of the running output to a service called Cypress Dashboard, so you can see the output of the latest run. For developing locally, you can do the same as Travis and run the front end tests headlessly, only seeing the output text and videos, or you can run Cypress as a GUI and see the browser running the tests, inspect the DOM at any point of a test, easily rerun a particular test, and so on.
As Linux on Windows 10 is basically ‘native’, I can call
Windows programs from inside Linux. I have Cypress installed natively on
Windows (installed node and
npm install cypress),
and so I run the test script in WSL with a special argument (unsurprisingly,
--wsl), which at the point it wants to run Cypress, calls out to it in Windows
to run there. (I did have the option of installing Cypress in Linux, setting up
a Linux window manager and running it entirely inside, but the GUI support for
Linux on Windows isn’t their core focus and I thought this way would
probably be easier.) This was a little tricky to find the precise combination
of commands that worked (by default, running Windows Cypress from Linux either
assumed you were in Linux and died because there was no Linux window manager,
or assumed you were in Windows and couldn’t find the command to run because
it tried to use Windows path names in Linux), but now that it’s done,
it’s all working smoothly.
Update August 2020: With WSL2, I still have it working, but I have to have the code checked out under /mnt/c rather than in Linux, otherwise it is incredibly slow.
I always used MacPorts on my Mac, which worked nicely; however, it is also nice to have Linux directly installed. I’d also have been fine with a full Linux installation, but don’t really have the time at present to play about with that, so the presence of WSL means I am happy to use Windows until such a time as more free time becomes available(!)