Skip to content

Liber Tertius · Chapter 1 · Getting grim

Install on Windows

You work inside the distro and look at the result in a Windows browser. One command starts it. Windows keeps only what the distro cannot own — the certificate authority its browsers trust — and everything else lives on the Linux side.

≈20 min 6 steps Windows 10 or 11 with WSL2 Docker Desktop with WSL integration on A GitHub account with access to grimoiry
Source

grim runs inside a WSL2 distro. That is not a variant of installing on Windows, it is how it is installed on Windows: Docker reaches files on the Windows disk through a proxy that makes a Laravel project unusable, and inside the distro it does not.

Before you start

Know which side does what. Windows holds the browser and the certificate authority that browser trusts. The distro holds grim, your projects and every command you type after today. Docker Desktop serves both.

Inside the distro, Docker has to answer, which means WSL integration is switched on for this distro in Docker Desktop. The installer brings grim's own PHP (one self-contained PHP 8.5 binary in ~/.grim/runtime, not on PATH), along with Node and mkcert; the distro needs no PHP and no Composer.

$ docker info --format '{{.ServerVersion}}'

The ritual, in order

  1. Run the installer in PowerShell

    In PowerShell, not in the distro. The installer first shows a checklist of the parts you can leave out, MariaDB, Node.js + pnpm and mkcert, all ticked (arrows move, space unticks, Enter goes on). The answer travels with the install into the distro, which does not ask again. Under irm | iex there is no way to pass options, so the environment answers instead: $env:GRIM_SKIP_MARIADB = '1', and likewise GRIM_SKIP_NODE and GRIM_SKIP_MKCERT. Any of them set skips the checklist.

    GRIM_TARGET answers the other question, Windows or WSL2, in advance, so the run is unattended until the distro asks for something.

    $ $env:GRIM_TARGET = 'wsl'
    $ irm https://grim.grimoiry.com/install.ps1 | iex

    The installer then does two things and stops touching Windows.

    It installs mkcert and puts its authority in the Windows trust store. This step cannot move to the distro: certificates are minted inside WSL2, but whether Chrome, Edge and Firefox warn about them is decided by the Windows trust store alone.

    Then it hands over to the Linux installer inside the distro, which notices WSL2 and borrows that authority instead of making one of its own.

    Sharing mkcert CA with Windows host: /mnt/c/Users/anna/AppData/Local/mkcert
    …
    Found GitHub token from a Windows-side grim install at:
      /mnt/c/Users/anna/.grim/config.json
    
    Reuse it? [Y/n]

    Sharing mkcert CA with Windows host is the line this recipe exists for. With no token on the Windows side the installer asks for a classic GitHub token instead; paste it and it is stored in the distro.

    Two prompts appear along the way and both wait for you: a sudo password, when the authority goes into the distro's trust store, and the token question above. The run ends by calling grim auth:login, which logs Docker into ghcr.io and removes the credsStore entry that breaks image pulls inside WSL2. It also hands the token to git, so cloning a private repository needs nothing further.

  2. When there is no distro yet

    wsl -l -q listing only docker-desktop and docker-desktop-data means there is nothing to install into — those two belong to Docker Desktop and are recreated when it resets. The installer says so and installs Ubuntu for you. Ubuntu opens its own window and asks for a UNIX username and password; finish that, then run the installer again and choose 1.

  3. Open the distro

    $ wsl -d Ubuntu
    $ echo $CAROOT
    /mnt/c/Users/anna/AppData/Local/mkcert
    $ grim auth:whoami

    An empty CAROOT in a fresh shell means your shell reads none of ~/.profile, ~/.bashrc and ~/.zshrc. Copy the marked lines from one of them into the profile it does read.

  4. Stop WSL2 from rewriting the hosts file

    grim adds each project's names to the distro's /etc/hosts. WSL2 generates that file again at every restart and the entries are gone. Switch the generation off once per distro, then restart WSL.

    $ printf '[network]\ngenerateHosts = false\n' | sudo tee -a /etc/wsl.conf
    $ wsl.exe --shutdown

    The shutdown closes every distro and this terminal with them. When /etc/wsl.conf already has a [network] section, put the line there by hand instead. Open the distro again before the next step, and wait until Docker Desktop reports the engine running.

  5. Install the database

    The Linux installer does not install a database server. It prints the packages and one GRANT statement and leaves them to you. grim creates each project's database as root without a password, so do this before the first project.

    $ sudo apt install mariadb-server && sudo systemctl start mariadb
    $ sudo mysql -u root -e "CREATE USER IF NOT EXISTS 'root'@'%' IDENTIFIED BY ''; GRANT ALL ON *.* TO 'root'@'%' WITH GRANT OPTION; FLUSH PRIVILEGES;"
  6. Ask the doctor

    $ grim doctor
    …
      Docker daemon    ✓ running
      MySQL/MariaDB    ✓ 10.11.6 (root@127.0.0.1, no password)
    …
    WSL2 browser path
      mkcert CAROOT     ✓ /mnt/c/Users/anna/AppData/Local/mkcert
      Windows CA        ✓ /mnt/c/Users/anna/AppData/Local/mkcert/rootCA.pem
      certutil          ✓ /usr/bin/certutil
      Browser           ✓ Snap Firefox not on PATH (use Windows browser for *.test)
    
    All checks passed!

    A - on the MySQL/MariaDB line does not fail the run, and it still matters: with a - there, the first project gets no database.

    grim setup — the shared Docker network, the authority in the distro's trust store, the single Traefik every project routes through — is not a step here. grim up does all of it on first use.

What you have now

Where What
Windows The mkcert authority in the trust store, and nothing else to maintain
Distro ~/.local/bin/grim, ~/.grim/config.json, ~/.grim/traefik/, CAROOT and a git credential for github.com
Docker Desktop The traefik-public network and one Traefik on ports 80 and 443, its dashboard on 127.0.0.1:8099

Keep projects under ~, never under /mnt/c. Both paths are reachable from the distro and only one of them works: /mnt/c is the Windows disk, which Docker reaches through a proxy that charges for every stat(). On one Laravel codebase, the same commands from each:

Project on /mnt/c Project under ~
php artisan --version 34–48 s 1.2 s
du -sh vendor 148 s 0.9 s
First byte of a request 10–27 s 0.15 s

Windows can still open those files when you need them — \\wsl$\Ubuntu\home\you\ in Explorer, and editors with a WSL mode work on them directly.

No project yet. That is the next recipe, Create a project. Its one Windows-specific moment is a UAC prompt on the desktop, when the project's names go into the Windows hosts file. If you miss it, grim hosts:windows asks again.

When it does not work

“Failed to download the PHP runtime”

The installer could not download grim's PHP (no network to dl.static-php.dev). Run it again once the distro reaches the internet. does not match its pin instead means the file that came down is not the one grim's release pinned; nothing was installed, and the macOS recipe says what to do.

“The Linux installer reported success but grim is not on PATH”

It stopped partway. The distro's log holds the last step it reached, and the installer leaves itself in /tmp, so a second attempt needs no download and shows everything on screen.

$ wsl -d Ubuntu -- bash -lc "tail -40 ~/.grim/install.log"
$ wsl -d Ubuntu -- bash /tmp/grim-install.sh

“No Windows mkcert CA found at /mnt/c/Users/*/AppData/Local/mkcert/.”

The distro made an authority of its own, and Windows browsers reject every certificate signed by it. Run mkcert -install in PowerShell, then run the distro installer again. Doctor shows the same state as Windows CA ✗.

A clone asks for a password and then refuses it

GitHub stopped accepting account passwords for git in 2021, so the account password can never work here. The installer writes the token into ~/.git-credentials to avoid this; when the prompt appears anyway, that file has no github.com line. Before touching git, check that the token reaches the repository at all — 404 is an access problem on the account, not a git one.

$ curl -s -o /dev/null -w "%{http_code}\n" -H "Authorization: Bearer $TOKEN" https://api.github.com/repos/ORG/REPO

WSL2 cannot be used on this machine

Some machines cannot run it: virtualization switched off in firmware or by policy, a VDI without nested virtualization, an account that may not install a distro. Setting GRIM_TARGET to windows installs everything on the Windows side instead, and the installer provisions grim's own PHP, MariaDB, Node and mkcert there. Expect the numbers in the table above, the wrong way round, on every project you keep on C:.

grim is not found in PowerShell

Nothing is wrong. In this setup grim lives in the distro, and the projects it works on are paths inside the distro. Open the distro and work there.

“error getting credentials” when an image is pulled

The distro's ~/.docker/config.json names "credsStore": "desktop" again, and that helper fails inside WSL2. Run grim auth:login again. It removes the entry and logs in without it.

Firefox in the distro still warns about the certificate

Snap Firefox detected on PATH in doctor explains it. A Snap browser keeps a sandboxed trust store the authority cannot be installed into. Use the Windows browser for .test pages.