From c92a7eeee2c32d25a1ad160a1d5aeccaa37d7488 Mon Sep 17 00:00:00 2001 From: K900 Date: Fri, 29 Sep 2023 20:51:59 +0300 Subject: [PATCH] feat: rewrite tarball generation to use proper nixos-install + nixos-enter Supersedes #243. --- .github/workflows/main.yml | 24 +++-- .github/workflows/release.yml | 11 +- .gitignore | 2 +- README.md | 61 ++++------- checks/username.nix | 4 +- configuration.nix | 32 ------ flake.nix | 24 ++++- modules/build-tarball.nix | 153 ++++++++++++---------------- modules/wsl-distro.nix | 5 - tests/README.md | 8 +- tests/basic-functionality.Tests.ps1 | 2 +- tests/docker/docker.Tests.ps1 | 1 - tests/lib/Dockerfile | 2 +- tests/lib/lib.ps1 | 13 +-- 14 files changed, 139 insertions(+), 203 deletions(-) delete mode 100644 configuration.nix diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7baaef12..39da98ef 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -66,15 +66,25 @@ jobs: echo ${{ needs.prepare.outputs.version }} > ./VERSION echo $(git rev-parse HEAD) >> ./VERSION - - name: Build installer ๐Ÿ› ๏ธ + - name: Build tarballs ๐Ÿ› ๏ธ + # We can't just nix run here because nix is not on root's PATH in the container run: | - nix build '.#nixosConfigurations.mysystem.config.system.build.installer' + nix build .#nixosConfigurations.modern.config.system.build.tarballBuilder + sudo ./result nixos-wsl.tar.gz + nix build .#nixosConfigurations.legacy.config.system.build.tarballBuilder + sudo ./result nixos-wsl-legacy.tar.gz - - name: Upload installer ๐Ÿ“ค + - name: Upload tarball ๐Ÿ“ค uses: actions/upload-artifact@v3 with: - name: installer - path: result/tarball/nixos-wsl-installer.tar.gz + name: tarball + path: nixos-wsl.tar.gz + + - name: Upload legacy tarball ๐Ÿ“ค + uses: actions/upload-artifact@v3 + with: + name: tarball-legacy + path: nixos-wsl-legacy.tar.gz checks: name: Flake Check ๐Ÿ“‹ @@ -117,10 +127,10 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Download installer ๐Ÿ“ฅ + - name: Download tarball ๐Ÿ“ฅ uses: actions/download-artifact@v3 with: - name: installer + name: tarball-legacy - name: Execute test ๐Ÿงช shell: pwsh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 72ba873b..081e65f2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,13 +14,12 @@ jobs: name: Create Release ๐Ÿ“ข runs-on: ubuntu-latest steps: - - name: Download installer ๐Ÿ“ฅ + - name: Download tarball ๐Ÿ“ฅ uses: actions/download-artifact@v3 - with: - name: installer - name: Generate checksums ๐Ÿ”‘ run: | + mv */*.tar.gz . for x in *.tar.gz; do sha256sum $x > ${x}.sha256 done @@ -29,7 +28,9 @@ jobs: uses: softprops/action-gh-release@v1 with: files: | - nixos-wsl-installer.tar.gz - nixos-wsl-installer.tar.gz.sha256 + nixos-wsl.tar.gz + nixos-wsl.tar.gz.sha256 + nixos-wsl-legacy.tar.gz + nixos-wsl-legacy.tar.gz.sha256 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index c798a58b..d6835c74 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ result result-* -nixos-wsl-installer.tar.gz +nixos-wsl*.tar.gz diff --git a/README.md b/README.md index e2dad8f0..695b858e 100644 --- a/README.md +++ b/README.md @@ -9,18 +9,23 @@ A minimal root filesystem for running NixOS on WSL. It can be used with [DistroLauncher](https://github.com/microsoft/WSL-DistroLauncher) as `install.tar.gz` or as input to `wsl --import --version 2`. +## System requirements + +NixOS-WSL is tested with the Windows Store version of WSL 2, which is now available on all supported Windows releases (both 10 and 11). +Support for older "inbox" versions is best-effort. + ## Quick start -First, [download the latest release\'s installer](https://github.com/nix-community/NixOS-WSL/releases/latest). +First, [download the latest release](https://github.com/nix-community/NixOS-WSL/releases/latest). Then open up a Terminal, PowerShell or Command Prompt and run: ```sh -wsl --import NixOS .\NixOS\ nixos-wsl-installer.tar.gz --version 2 +wsl --import NixOS .\NixOS\ nixos-wsl.tar.gz --version 2 ``` This sets up a new WSL distribution `NixOS` that is installed under -`.\NixOS`. `nixos-wsl-installer.tar.gz` is the path to the file you +`.\NixOS`. `nixos-wsl.tar.gz` is the path to the file you downloaded earlier. You might need to change this path or change to the download directory first. You can now run NixOS: @@ -29,11 +34,6 @@ You can now run NixOS: wsl -d NixOS ``` -The installer will unpack the file system and subsequently start NixOS. -A few warnings about file systems and locales will pop up. You can -safely ignore them. After systemd has started, you should be greeted -with a bash prompt inside your fresh NixOS installation. - If you want to make NixOS your default distribution, you can do so with ```sh @@ -68,57 +68,34 @@ If you have a flakes-enabled Nix, you can use the following command to build your own tarball instead of relying on a prebuilt one: ```cmd -nix build github:nix-community/NixOS-WSL#nixosConfigurations.mysystem.config.system.build.installer +sudo nix run github:nix-community/NixOS-WSL#nixosConfigurations.modern.config.system.build.tarballBuilder ``` Or, if you want to build with local changes, run inside your checkout: ```cmd -nix build .#nixosConfigurations.mysystem.config.system.build.installer +sudo nix run .#nixosConfigurations.your-hostname.config.system.build.tarballBuilder ``` Without a flakes-enabled Nix, you can build a tarball using: ```cmd -nix-build -A nixosConfigurations.mysystem.config.system.build.installer -``` +nix-build -A nixosConfigurations.mysystem.config.system.build.tarballBuilder && sudo ./result/bin/nixos-wsl-tarball-builder -The resulting installer tarball can then be found under -`./result/tarball/nixos-wsl-installer.tar.gz`. +``` -You can also build a rootfs tarball without wrapping it in the installer -by replacing `installer` with `tarball` in the above commands. The -rootfs tarball can then be found under -`./result/tarball/nixos-wsl-x86_64-linux.tar.gz`. +The resulting tarball can then be found under `nixos-wsl.tar.gz`. ## Design Getting NixOS to run under WSL requires some workarounds: -### systemd support - -WSL comes with its own (non-substitutable) init system while NixOS uses -systemd. Simply starting systemd later on does not work out of the box, -because systemd as system instance refuses to start if it is not PID 1. -This unfortunate combination is resolved in two ways: - -- the user\'s default shell is replaced by a wrapper script that acts - is init system and then drops to the actual shell -- systemd is started in its own PID namespace; therefore, it is PID 1. - The shell wrapper (see above) enters the systemd namespace before - dropping to the shell. - -### Installer - -Usually WSL distributions ship as a tarball of their root file system. -These tarballs however, can not contain any hard-links due to the way -they are unpacked by WSL, resulting in an \"Unspecified Error\". By -default some Nix-derivations will contain hard-links when they are -built. This results in system tarballs that can not be imported into -WSL. To circumvent this problem, the rootfs tarball is wrapped in that -of a minimal distribution (the installer), that is packaged without any -hard-links. When the installer system is started for the first time, it -overwrites itself with the contents of the rootfs tarball. +- instead of directly loading systemd, we use a small shim that runs the NixOS activation scripts first +- some additional binaries required by WSL's internal tooling are symlinked to FHS paths on activation + +Running on older WSL versions also requires a workaround to spawn systemd by hijacking the root shell and +spawning a container with systemd inside. This method of running things is deprecated and not recommended, +however still available as `nixos-wsl-legacy.tar.gz` or via `wsl.nativeSystemd = false`. ## License diff --git a/checks/username.nix b/checks/username.nix index 53337c76..2da4418d 100644 --- a/checks/username.nix +++ b/checks/username.nix @@ -6,9 +6,9 @@ let baseModule = { ... }: { - imports = [ ../configuration.nix ]; - + imports = [ ../modules ]; wsl.enable = true; + wsl.defaultUser = "nixos"; }; changedUsername = { lib, ... }: { wsl.defaultUser = lib.mkForce "different"; diff --git a/configuration.nix b/configuration.nix deleted file mode 100644 index 789cf59c..00000000 --- a/configuration.nix +++ /dev/null @@ -1,32 +0,0 @@ -{ pkgs, ... }: - -let - nixos-wsl = import ./default.nix; -in -{ - imports = [ - nixos-wsl.nixosModules.wsl - ]; - - wsl = { - enable = true; - wslConf.automount.root = "/mnt"; - defaultUser = "nixos"; - startMenuLaunchers = true; - - # Enable native Docker support - # docker-native.enable = true; - - # Enable integration with Docker Desktop (needs to be installed) - # docker-desktop.enable = true; - - }; - - # Enable nix flakes - nix.package = pkgs.nixFlakes; - nix.extraOptions = '' - experimental-features = nix-command flakes - ''; - - system.stateVersion = "23.05"; -} diff --git a/flake.nix b/flake.nix index 66397e88..c0f2414b 100644 --- a/flake.nix +++ b/flake.nix @@ -26,11 +26,25 @@ }; nixosModules.default = self.nixosModules.wsl; - nixosConfigurations.mysystem = nixpkgs.lib.nixosSystem { - system = "x86_64-linux"; - modules = [ - ./configuration.nix - ]; + nixosConfigurations = { + modern = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ + self.nixosModules.default + { wsl.enable = true; } + ]; + }; + + legacy = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ + self.nixosModules.default + { + wsl.enable = true; + wsl.nativeSystemd = false; + } + ]; + }; }; } // diff --git a/modules/build-tarball.nix b/modules/build-tarball.nix index d2e52312..e1b3b751 100644 --- a/modules/build-tarball.nix +++ b/modules/build-tarball.nix @@ -1,112 +1,85 @@ { config, pkgs, lib, ... }: with builtins; with lib; let - cfg = config.wsl.tarball; + cfg = config.wsl; - pkgs2storeContents = l: map (x: { object = x; symlink = "none"; }) l; + defaultConfig = pkgs.writeText "default-configuration.nix" '' + # Edit this configuration file to define what should be installed on + # your system. Help is available in the configuration.nix(5) man page, on + # https://search.nixos.org/options and in the NixOS manual (`nixos-help`). - nixpkgs = lib.cleanSource pkgs.path; + # NixOS-WSL specific options are documented on the NixOS-WSL repository: + # https://github.com/nix-community/NixOS-WSL - channelSources = pkgs.runCommand "nixos-${config.system.nixos.version}" - { preferLocalBuild = true; } - '' - mkdir -p $out - cp -prd ${nixpkgs.outPath} $out/nixos - chmod -R u+w $out/nixos - if [ ! -e $out/nixos/nixpkgs ]; then - ln -s . $out/nixos/nixpkgs - fi - echo -n ${toString config.system.nixos.revision} > $out/nixos/.git-revision - echo -n ${toString config.system.nixos.versionSuffix} > $out/nixos/.version-suffix - echo ${toString config.system.nixos.versionSuffix} | sed -e s/pre// > $out/nixos/svn-revision - ''; - - preparer = pkgs.writeShellScriptBin "wsl-prepare" ('' - set -e - - mkdir -m 0755 ./bin ./etc - mkdir -m 1777 ./tmp - - # WSL requires a /bin/sh - only temporary, NixOS's activate will overwrite - ln -s ${config.users.users.root.shell} ./bin/sh - - # WSL also requires a /bin/mount, otherwise the host fs isn't accessible - ln -s /nix/var/nix/profiles/system/sw/bin/mount ./bin/mount - - # Set system profile - system=${config.system.build.toplevel} - ./$system/sw/bin/nix-store --store `pwd` --load-db < ./nix-path-registration - rm ./nix-path-registration - ./$system/sw/bin/nix-env --store `pwd` -p ./nix/var/nix/profiles/system --set $system - - # Set channel - mkdir -p ./nix/var/nix/profiles/per-user/root - ./$system/sw/bin/nix-env --store `pwd` -p ./nix/var/nix/profiles/per-user/root/channels --set ${channelSources} - mkdir -m 0700 -p ./root/.nix-defexpr - ln -s /nix/var/nix/profiles/per-user/root/channels ./root/.nix-defexpr/channels - - # It's now a NixOS! - touch ./etc/NIXOS + { config, lib, pkgs, ... }: - # Write wsl.conf so that it is present when NixOS is started for the first time - cp ${config.environment.etc."wsl.conf".source} ./etc/wsl.conf - - '' + lib.optionalString cfg.includeConfig '' - ${if cfg.configPath == null then '' - # Copy the system configuration - mkdir -p ./etc/nixos/nixos-wsl - cp -R ${lib.cleanSource ../.}/. ./etc/nixos/nixos-wsl - mv ./etc/nixos/nixos-wsl/configuration.nix ./etc/nixos/configuration.nix - # Patch the import path to avoid having a flake.nix in /etc/nixos - sed -i 's|import \./default\.nix|import \./nixos-wsl|' ./etc/nixos/configuration.nix - '' else '' - mkdir -p ./etc/nixos - cp -R ${lib.cleanSource cfg.configPath}/. ./etc/nixos - ''} - chmod -R u+w etc/nixos - ''); + { + imports = [ + # include NixOS-WSL modules + + ]; + wsl.enable = true; + wsl.defaultUser = "nixos"; + ${cfg.extraTarballConfig} + + # This value determines the NixOS release from which the default + # settings for stateful data, like file locations and database versions + # on your system were taken. It's perfectly fine and recommended to leave + # this value at the release version of the first install of this system. + # Before changing this value read the documentation for this option + # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html). + system.stateVersion = "${config.system.nixos.release}"; # Did you read the comment? + } + ''; in { - - options.wsl.tarball = { - includeConfig = mkOption { - type = types.bool; - default = true; - description = "Whether or not to copy the system configuration into the tarball"; - }; - - configPath = mkOption { - type = types.nullOr types.path; - default = null; - description = "Path to system configuration which is copied into the tarball"; - }; + options.wsl.extraTarballConfig = mkOption { + type = types.str; + internal = true; + default = ""; }; + # These options make no sense without the wsl-distro module anyway + config = mkIf cfg.enable { + system.build.tarballBuilder = pkgs.writeShellScriptBin "nixos-wsl-tarball-builder" '' + set -euo pipefail - config = mkIf config.wsl.enable { - # These options make no sense without the wsl-distro module anyway + if ! [ $EUID -eq 0 ]; then + echo "This script must be run as root!" + exit 1 + fi - system.build.tarball = pkgs.callPackage "${nixpkgs}/nixos/lib/make-system-tarball.nix" { + out=''${1:-nixos-wsl.tar.gz} - contents = [ - { source = config.users.users.root.shell; target = "/nix/nixos-wsl/entrypoint"; } - ]; + root=$(mktemp -p "''${TMPDIR:-/tmp}" -d nixos-wsl-tarball.XXXXXXXXXX) + trap 'rm -rf "$root"' INT TERM EXIT - fileName = "nixos-wsl-${pkgs.hostPlatform.system}"; + chmod o+rx "$root" - storeContents = pkgs2storeContents [ - config.system.build.toplevel - channelSources - preparer - ]; + echo "[NixOS-WSL] Installing..." + nixos-install \ + --root "$root" \ + --no-root-passwd \ + --system ${config.system.build.toplevel} \ + --substituters "" - extraCommands = "${preparer}/bin/wsl-prepare"; + echo "[NixOS-WSL] Adding channel..." + nixos-enter --root "$root" --command 'nix-channel --add https://github.com/nix-community/NixOS-WSL/archive/refs/heads/main.tar.gz nixos-wsl' - # Use gzip - compressCommand = "gzip"; - compressionExtension = ".gz"; - }; + echo "[NixOS-WSL] Adding default config..." + install -Dm644 ${defaultConfig} "$root/etc/nixos/configuration.nix" + echo "[NixOS-WSL] Compressing..." + tar -C "$root" \ + -cz \ + --sort=name \ + --mtime='@1' \ + --owner=0 \ + --group=0 \ + --numeric-owner \ + . \ + > "$out" + ''; }; } diff --git a/modules/wsl-distro.nix b/modules/wsl-distro.nix index 0a2b88bb..f2279e3b 100644 --- a/modules/wsl-distro.nix +++ b/modules/wsl-distro.nix @@ -132,11 +132,6 @@ in config.wsl.extraBin )} ''); - # TODO: This is only needed for the docker tests, it can be removed when they are moved to something else - update-entrypoint.text = '' - mkdir -p /nix/nixos-wsl - ln -sfn ${config.users.users.root.shell} /nix/nixos-wsl/entrypoint - ''; }; # require people to use lib.mkForce to make it harder to brick their installation diff --git a/tests/README.md b/tests/README.md index d2858098..3f1e7bf1 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,7 +1,7 @@ # Tests -This directory contains tests that are executed against a built installer tarball. -The test are written using the [Pester](https://pester.dev/) testing framework +This directory contains tests that are executed against a built NixOS-WSL "legacy" tarball. +The tests are written using the [Pester](https://pester.dev/) testing framework. ## Execute Tests @@ -19,7 +19,9 @@ Running the tests requires Docker and PowerShell to be installed on your system. ### Running the Tests If you haven't already, [install Pester](https://pester.dev/docs/introduction/installation/). -The tests require a `nixos-wsl-installer.tar.gz` to be present in the current working directory or in `./result/tarball`. Refer to the top-level readme on how to build it. +The tests require a `nixos-wsl-legacy.tar.gz` to be present in the current working directory, which can be built with +`sudo nix run .#nixosConfigurations.legacy.config.system.build.tarballBuilder -- nixos-wsl-legacy.tar.gz`. + Once everything is in place, run the test by running the following in PowerShell at the root of this repo: ```powershell diff --git a/tests/basic-functionality.Tests.ps1 b/tests/basic-functionality.Tests.ps1 index d62823b6..c148c79f 100644 --- a/tests/basic-functionality.Tests.ps1 +++ b/tests/basic-functionality.Tests.ps1 @@ -7,7 +7,7 @@ Describe "Basic Functionality" { $distro = Install-Distro } - It "is possible to run a command through the installer" { + It "is possible to run a command in the container" { $distro.Launch("nixos-version") $LASTEXITCODE | Should -Be 0 } diff --git a/tests/docker/docker.Tests.ps1 b/tests/docker/docker.Tests.ps1 index fefff074..9ea46aa9 100644 --- a/tests/docker/docker.Tests.ps1 +++ b/tests/docker/docker.Tests.ps1 @@ -27,7 +27,6 @@ Describe "Docker (native)" { It "should be possible to connect to the internet from a container" { $distro.Launch("docker run --rm -it alpine wget -qO- http://www.msftconnecttest.com/connecttest.txt") | Select-Object -Last 1 | Should -BeExactly "Microsoft Connect Test" - # docker exec -it $distro.id /nix/nixos-wsl/entrypoint -c "docker run --rm -it alpine wget -qO- http://www.msftconnecttest.com/connecttest.txt" | Select-Object -Last 1 | Should -BeExactly "Microsoft Connect Test" $LASTEXITCODE | Should -Be 0 } diff --git a/tests/lib/Dockerfile b/tests/lib/Dockerfile index 1fcea668..144c9797 100644 --- a/tests/lib/Dockerfile +++ b/tests/lib/Dockerfile @@ -1,2 +1,2 @@ FROM scratch -ADD nixos-wsl-installer.tar.gz / +ADD nixos-wsl-legacy.tar.gz / diff --git a/tests/lib/lib.ps1 b/tests/lib/lib.ps1 index 58b1a424..5d4ed4af 100644 --- a/tests/lib/lib.ps1 +++ b/tests/lib/lib.ps1 @@ -26,12 +26,9 @@ class Distro { [string]FindTarball() { # Check if a fresh tarball exists in result, otherwise try one in the current directory - $tarball = "./result/tarball/nixos-wsl-installer.tar.gz" + $tarball = "./nixos-wsl-legacy.tar.gz" if (!(Test-Path $tarball)) { - $tarball = "./nixos-wsl-installer.tar.gz" - if (!(Test-Path $tarball)) { - throw "Could not find the installer tarball! Run nix build first, or place one in the current directory." - } + throw "Could not find the tarball! Run nix build first, or place one in the current directory." } Write-Host "Using tarball: $tarball" return $tarball @@ -75,7 +72,7 @@ class DockerDistro : Distro { $tarball = $this.FindTarball() if (!([DockerDistro]::imageCreated)) { - # Build docker image from the installer tarball + # Build docker image from the tarball $tmpdir = $(mktemp -d) Copy-Item $PSScriptRoot/Dockerfile $tmpdir Copy-Item $tarball $tmpdir @@ -96,7 +93,7 @@ class DockerDistro : Distro { [Array]Launch([string]$command) { Write-Host "> $command" $result = @() - docker exec -t $this.id /nix/nixos-wsl/entrypoint -c $command | Tee-Object -Variable result | Write-Host + docker exec -t $this.id /bin/sh -c $command | Tee-Object -Variable result | Write-Host return $result | Remove-Escapes } @@ -140,7 +137,7 @@ class WslDistro : Distro { [Array]Launch([string]$command) { Write-Host "> $command" $result = @() - & wsl.exe -d $this.id -e /nix/nixos-wsl/entrypoint -c $command | Tee-Object -Variable result | Write-Host + & wsl.exe -d $this.id -e /bin/sh -c $command | Tee-Object -Variable result | Write-Host return $result | Remove-Escapes }