Zsh on Windows via MSYS2

I have quite a long history trying to get “Linux-y” environment working on Windows (yes, I’m stuck on that platform), from enhanced cmd.exe to WSL to Git Bash.

I still think that Git Bash is the best middle ground for most people: super-easy to install (comes with Git for Windows), has very few issues and you can do almost anything you’d want on a Linux / macOS machine, e.g., rm -rf node_modules, single quotes, Bash scripting, etc.

The only limitation is that Git Bash comes with a fixed set of utilities — understandably as the project only maintains tools directly related to Git. So when you’re hunting for rsync, you're on your own. Same for zsh, and basically everything else.

I recently started using Mac besides my Windows machine and some unification is crucial for my sanity, so especially zsh became a priority. And while I was at it, I reconsidered my tool chain on Windows. In the end, I switched to MSYS2, installed Git for Windows into it but it also unlocked a world of other packages for me. This is the best setup I found so far so I’d like to share it.

Note on WSL: I love it in theory and think that it might make Windows the best development platform in the future (true Linux utilities, native Docker, etc.) but using it in practice feels quite cumbersome to me, as one simply is in a different operating system. For example, tools installed in WSL are not that easy to access from Windows, users are different, syncing system settings between machines works differently, updates are separate, etc. With MSYS2, there is just a single operating system: Windows.

MSYS2

Git for Windows builds on (is a friendly fork) of MSYS2 and the key to this whole setup is to turn it around: start from MSYS2 and install Git for Windows into it. This will allow us to then continue beyond its boundaries, with tools like rsync or make.

MSYS2 is the underlying goodness so it’s vital to understand what it is. The project has a nice (and short!) introduction on their wiki, in essence:

  • MSYS2 (usually upper case) consists of three relatively separate subsystems: msys2 , mingw32 and mingw64.
  • msys2 (sometimes called just msys) is an emulation layer — fully POSIX compatible but slow.
  • mingw subsystems provide native Windows binaries, with Linux calls rewritten at compile time to their Windows equivalents. For example, Git for Windows is a mingw64 binary (unlike msys Git which utilizes the compatibility layer and is therefore slow).
  • Each subsystem has its own shell and it’s important to be in the right one. The msys shell has a PATH starting with /usr/local/bin:/usr/bin:/bin:... while the mingw64 shell adds /mingw64/bin before it. This means that /mingw64/bin/git.exe is only available in the mingw64 shell.
  • MSYS2 comes with Pacman, a package manager ported from Arch Linux, and many packages installable by pacman -S <package>.

Install MSYS2 by downloading the 64bit version from https://www.msys2.org/, then check “Run MSYS2” and run this:

pacman -Syu # repeat if necessary
pacman -Su

When asked, close the terminal entirely and start it again via the start menu shortcut “MSYS2 MSYS”.

You now have the basic environment installed and updated. Close the terminal.

$HOME

By default, $HOME is /home/You (C:\msys64\home\You). Let's switch it to C:\Users\You as usual so that for example your standard .gitconfig works.

The best way is to use the Windows dialog; add this (use the standard Windows format, not MSYS2 path /c/...):

HOME = C:\Users\You

I also tried other ways, e.g., updating C:\msys64\etc\profile or C:\msys64\etc\bash.bashrc, but they are less universal (depend on a specific shell, loading order, etc.).

UPDATE: for openssh (and maybe other programs) to work, also update the db_home line in C:\msys64\etc\nsswitch.conf to look like this:

db_home: windows cygwin desc

ConEmu with mingw shell

Let’s switch to a better terminal app, ConEmu.

I’ll recommend one controversial thing here: to only use the mingw64 shell from now on. This is in contrast with the official wiki which recommends to run Pacman in the msys shell but:

  1. I didn’t hit any issues installing packages from the mingw shell.
  2. I did hit issues installing mingw-w64-x86_64-git-lfs from the msys shell, because it uses git as part of its installation which is not in msys’ PATH.
  3. Worrying about two shells, two ConEmu tasks, two PATH configurations, etc. is IMO not worth it until proven otherwise.

So in this section, we’ll only setup a ConEmu taks for mingw64. If you ever need the msys shell, use the “MSYS2 MSYS” shortcut in the Windows start menu.

My MSYS2::mingw ConEmu task looks like this:

set CHERE_INVOKING=1 & set MSYSTEM=MINGW64 & set MSYS2_PATH_TYPE=inherit & set “PATH=%ConEmuDrive%\msys64\mingw64\bin;%ConEmuDrive%\msys64\usr\bin;%PATH%” & %ConEmuBaseDirShort%\conemu-msys2–64.exe -new_console:p %ConEmuDrive%\msys64\usr\bin\bash.exe — login -i -new_console:C:”%ConEmuDrive%\msys64\msys2.ico”

It’s a copy of the default {Bash::Msys2-64} task with the MSYSTEM variable set (making it a mingw shell) and the PATH expanded to contain the full Windows path so that system-wide binaries like node, yarn or kubectl are accessible.

If you ever need to confirm which shell you’re in, run echo $MSYSTEM.

Essential utilities

Let’s install some basic utilities:

pacman -S man vim nano
pacman -S openssh rsync make
pacman -S zip unzip
pacman -S mingw64/mingw-w64-x86_64-jq

Notice how easy it is to install things. For example, adding rsync to Windows is historically not easy at all — you end up going through various ports, forks, etc. This is much easier.

Note about sudo: MSYS2 doesn't provide it. See this question or imachug/win-sudo.

Git for Windows

If you had Git for Windows installed previously, uninstall it or just remove C:\Program Files\Git\... from PATH.

Now let’s install GfW to MSYS2. Most of the instructions come from this wiki page but I had to customize it a bit (it seems that the wiki page sets up the full SDK environment).

First, edit C:\msys64\etc\pacman.conf and add this:

[git-for-windows]
Server = https://wingit.blob.core.windows.net/x86-64

It needs to be the first repository, above [mingw32], so that packages from it are installed first.

Authorize a signing key:

curl -L https://raw.githubusercontent.com/git-for-windows/build-extra/master/git-for-windows-keyring/git-for-windows.gpg |
pacman-key --add - &&
pacman-key --lsign-key 1A9F3986

Now update the installation with the new repository in place:

pacman -Syu

This will guide you through installing a newer msys2 runtime. You will have to exit the terminal entirely and run the update command again, as before. Repeat pacman -Syu until there are no more things to update.

Now install Git for Windows:

pacboy is used here. It is a small wrapper that saves some typing, for example, pacboy sync git:x is equivalent to pacman -S mingw-w64-x86_64-git (the ":x" means we want the x64 version).
pacboy sync git:x git-credential-manager:x git-lfs:x git-doc-html:x git-doc-man:x

Verify that everything is working:

$ git --version
git version 2.18.0.windows.1
$ git config --list --show-origin
# ... verifies that your ~/.gitconfig is read

You should be able to pull from GitHub, credential helper should store your HTTPS password, all should be working as expected.

Update Windows path

To make Git and other tools like cp or rm -rf available also in cmd.exe and other shells, add this to your PATH in this order (I recommend system PATH which is loaded first):

C:\msys64\mingw64\bin
C:\msys64\usr\bin

Verify:

Microsoft Windows [Version 10.0.17134.112]
(c) 2018 Microsoft Corporation. All rights reserved.
C:\Users\Borek>git --version
git version 2.18.0.windows.1
C:\Users\Borek>ls -la
...

Zsh

Finally, zsh! Let’s install it:

pacman -S zsh

ConEmu task is similar to what we created before, just with zsh.exe instead of bash.exe. This is my {MSYS2:zsh} task:

set CHERE_INVOKING=1 & set MSYSTEM=MINGW64 & set MSYS2_PATH_TYPE=inherit & set “PATH=%ConEmuDrive%\msys64\mingw64\bin;%ConEmuDrive%\msys64\usr\bin;%PATH%” & %ConEmuBaseDirShort%\conemu-msys2–64.exe -new_console:p %ConEmuDrive%\msys64\usr\bin\zsh.exe — login -i -new_console:C:”%ConEmuDrive%\msys64\msys2.ico”

This article has an example of a ConEmu task that builds on mintty but I think that conemu-msys2-64.exe is the preferred way, see here.

At this point, zsh is technically working but still needs some love.

Oh My Zsh

While omz feels a bit bloated to me (for example, enabling its git plugin registers about a million aliases), it is also easy to appreciate its value once you don’t have it. So I like to enable omz to get all the useful stuff it has in its lib folder but don’t enable any plugins or themes.

My preferred way to install omz and other things is via Antigen. As a start, add this to your .zshrc:

Actually, my ~/.zshrc just sources $HOME/GDrive/Settings/zsh/.zshrc so that it’s synced between computers. I'll probably switch to a versioned dotfiles approach soon.
# Use the path where you installed Antigen
source "${funcsourcetrace[1]%/*}/antigen.zsh"
# Load Oh My Zsh
antigen use oh-my-zsh
# Example of how to add other useful things
antigen bundle zsh-users/zsh-completions
antigen apply

Some other essential things for me in .zshrc are:

# Make /c/... autocompletion work, see Alexpux/MSYS2-packages#38
zstyle ':completion:*' fake-files /: '/:c'
# Convenient path navigation, e.g., `cd vp`
setopt CDABLE_VARS
vp="/c/Dev/VersionPress/versionpress"
temp="/c/Dev/temp"
# VSCode as an editor
if [[ -n $SSH_CONNECTION ]]; then
export EDITOR='vim'
else
export EDITOR='code-insiders --wait'
fi

Prompt

The grand finale!

There are million zsh prompts out there but as expected, none is quite perfect. I want a couple of key things from a prompt:

  1. It should be simple, like Pure.
  2. It must be asynchronous so that querying Git info doesn’t block the actual work.

The second point is relatively hard to fulfill on Windows as most async prompts (incl. Pure) depend on zsh-async which depends on zsh/zpty which doesn't work in MSYS2 (until some fallback is implemented, see mafredri/zsh-async#26).

The only async prompt I found working on Windows is agkozak/agkozak-zsh-theme (awesome work!). I didn’t like some specifics about it, e.g., Git info being in the right prompt, so I have my own small fork of it (no prompt I saw thus far is truly customizable without forking; makes me sad). In my .zshrc:

antigen theme borekb/agkozak-zsh-theme@prompt-customization

It looks like this:

What you cannot see in a static image is how the Git info is loaded in the background; it is really really cool.


Ok, that’s it, we now have a fully working environment with MSYS2, zsh, Oh My Zsh, Git for Windows and Pacman where adding new packages is as simple as pacman -S <something>. This is the best setup I found so far, if you have any tips please let me know in the comments below.

And big thanks to all the people involved in MSYS2 & Git for Windows projects, you make the life of a developer on Windows bearable!