diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c61332e3..3db8e0d6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -59,7 +59,7 @@ jobs: - uses: actions/download-artifact@v2 with: name: installer - + - name: Generate checksums run: | for x in *.tar.gz; do diff --git a/flake.nix b/flake.nix index 8a62b472..d03007fe 100644 --- a/flake.nix +++ b/flake.nix @@ -40,16 +40,16 @@ pkgs = import nixpkgs { inherit system; }; in { - checks.check-format = pkgs.runCommand "check-format" - { - buildInputs = with pkgs; [ nixpkgs-fmt ]; - } '' - nixpkgs-fmt --check ${./.} - mkdir $out # success - ''; + checks = { + check-format = pkgs.runCommand "check-format" { nativeBuildInputs = with pkgs; [ nixpkgs-fmt shfmt ]; } '' + nixpkgs-fmt --check ${./.} + shfmt -i 2 -d ${./scripts}/*.sh + mkdir $out # success + ''; + }; devShell = pkgs.mkShell { - nativeBuildInputs = with pkgs; [ nixpkgs-fmt ]; + nativeBuildInputs = with pkgs; [ nixpkgs-fmt shfmt ]; }; } ); diff --git a/modules/wsl-distro.nix b/modules/wsl-distro.nix index 4c6eda5e..b3437d71 100644 --- a/modules/wsl-distro.nix +++ b/modules/wsl-distro.nix @@ -33,7 +33,7 @@ with builtins; with lib; config = let cfg = config.wsl; - syschdemd = import ../syschdemd.nix { inherit lib pkgs config; inherit (cfg) automountPath defaultUser; defaultUserHome = config.users.users.${cfg.defaultUser}.home; }; + syschdemd = pkgs.callPackage ../scripts/syschdemd.nix { inherit (cfg) automountPath defaultUser; }; in mkIf cfg.enable { diff --git a/scripts/syschdemd.nix b/scripts/syschdemd.nix new file mode 100644 index 00000000..2a2a725f --- /dev/null +++ b/scripts/syschdemd.nix @@ -0,0 +1,51 @@ +{ runCommand +, makeWrapper +, lib +, coreutils +, daemonize +, glibc +, gnugrep +, systemd +, util-linux +, defaultUser +, automountPath +, +}: +let + mkWrappedScript = + { name + , src + , path + , ... + } @ args: + runCommand name ({ nativeBuildInputs = [ makeWrapper ]; } // args) '' + install -Dm755 ${src} $out/bin/${name} + patchShebangs $out/bin/${name} + substituteAllInPlace $out/bin/${name} + wrapProgram $out/bin/${name} --prefix PATH ':' ${lib.escapeShellArg path} + ''; + + wrapper = mkWrappedScript { + name = "nixos-wsl-systemd-wrapper"; + src = ./wrapper.sh; + path = lib.makeSearchPath "" [ + "/run/wrappers/bin" # mount + "${systemd}/lib/systemd" # systemd + ]; + }; +in +mkWrappedScript { + name = "syschdemd"; + src = ./syschdemd.sh; + path = lib.makeBinPath [ + "/run/wrappers" # mount + coreutils + daemonize + glibc # getent + gnugrep + systemd # machinectl + util-linux # nsenter + wrapper + ]; + inherit defaultUser automountPath; +} diff --git a/scripts/syschdemd.sh b/scripts/syschdemd.sh new file mode 100644 index 00000000..774c5830 --- /dev/null +++ b/scripts/syschdemd.sh @@ -0,0 +1,138 @@ +#!/usr/bin/env bash +set -euo pipefail + +[ "${NIXOS_WSL_DEBUG:-}" == "1" ] && set -x + +rundir="/run/nixos-wsl" +pidfile="$rundir/unshare.pid" + +ensure_root() { + if [ $EUID -ne 0 ]; then + echo "[ERROR] Requires root! :( Make sure the WSL default user is set to root" >&2 + exit 1 + fi +} + +activate() { + mount --bind -o ro /nix/store /nix/store + + mount --bind "@automountPath@" "@automountPath@" + mount --make-rshared "@automountPath@" + + LANG="C.UTF-8" /nix/var/nix/profiles/system/activate +} + +create_rundir() { + if [ ! -d $rundir ]; then + mkdir -p $rundir/ns + touch $rundir/ns/{pid,mount} + fi +} + +is_unshare_alive() { + [ -e $pidfile ] && [ -d "/proc/$(<$pidfile)" ] +} + +run_in_namespace() { + nsenter \ + --pid=$rundir/ns/pid \ + --mount=$rundir/ns/mount \ + -- "$@" +} + +start_systemd() { + daemonize \ + -o $rundir/stdout \ + -e $rundir/stderr \ + -l $rundir/systemd.lock \ + -p $pidfile \ + -E LOCALE_ARCHIVE=/run/current-system/sw/lib/locale/locale-archive \ + "$(command -v unshare)" \ + --fork \ + --pid=$rundir/ns/pid \ + --mount=$rundir/ns/mount \ + --mount-proc=/proc \ + nixos-wsl-systemd-wrapper + + # Wait for systemd to start + while ! (run_in_namespace systemctl is-system-running -q --wait) &>/dev/null; do + sleep 1 + + if ! is_unshare_alive; then + echo "[ERROR] systemd startup failed!" + + echo "[ERROR] stderr:" + cat $rundir/stderr + + echo "[ERROR] stdout:" + cat $rundir/stdout + + exit 1 + fi + done +} + +get_shell() { + getent passwd "$1" | cut -d: -f7 +} + +get_home() { + getent passwd "$1" | cut -d: -f6 +} + +is_in_container() { + [ "${INSIDE_NAMESPACE:-}" == "true" ] +} + +clean_wslpath() { + echo "$PATH" | tr ':' '\n' | grep -E "^@automountPath@" | tr '\n' ':' +} + +main() { + ensure_root + + if [ ! -e "/run/current-system" ]; then + activate + fi + + if [ ! -e "$rundir" ]; then + create_rundir + fi + + if ! is_in_container && ! is_unshare_alive; then + start_systemd + fi + + if [ $# -gt 0 ]; then + # wsl seems to prefix with "-c" + shift + command="$*" + else + command=$(get_shell @defaultUser@) + fi + + # If we're executed from inside the container, e.g. sudo + if is_in_container; then + exec $command + fi + + # If we are currently in /root, this is probably because the directory that WSL was started is inaccessible + # cd to the user's home to prevent a warning about permission being denied on /root + if [ "$PWD" == "/root" ]; then + cd "$(get_home @defaultUser@)" + fi + + # Pass external environment but filter variables specific to root user. + exportCmd="$(export -p | grep -vE ' (HOME|LOGNAME|SHELL|USER)=')" + + run_in_namespace \ + machinectl \ + --quiet \ + --uid=@defaultUser@ \ + --setenv=INSIDE_NAMESPACE=true \ + --setenv=WSLPATH="$(clean_wslpath)" \ + shell .host \ + /bin/sh -c "cd \"$PWD\"; $exportCmd; source /etc/set-environment; exec $command" +} + +main "$@" diff --git a/scripts/wrapper.sh b/scripts/wrapper.sh new file mode 100644 index 00000000..6f4dd5ad --- /dev/null +++ b/scripts/wrapper.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -euxo pipefail + +mount -t binfmt_misc binfmt_misc /proc/sys/fs/binfmt_misc + +exec systemd diff --git a/syschdemd.nix b/syschdemd.nix deleted file mode 100644 index 406dd861..00000000 --- a/syschdemd.nix +++ /dev/null @@ -1,28 +0,0 @@ -{ lib -, pkgs -, config -, automountPath -, defaultUser -, defaultUserHome ? "/home/${defaultUser}" -, ... -}: - -pkgs.substituteAll { - name = "syschdemd"; - src = ./syschdemd.sh; - dir = "bin"; - isExecutable = true; - - buildInputs = with pkgs; [ daemonize ]; - - inherit defaultUser defaultUserHome; - inherit (pkgs) daemonize; - inherit (config.security) wrapperDir; - fsPackagesPath = lib.makeBinPath config.system.fsPackages; - - systemdWrapper = pkgs.writeShellScript "systemd-wrapper.sh" '' - mount -t binfmt_misc binfmt_misc /proc/sys/fs/binfmt_misc || true - mount --make-rshared ${automountPath} - exec systemd - ''; -} diff --git a/syschdemd.sh b/syschdemd.sh deleted file mode 100644 index 6223cdac..00000000 --- a/syschdemd.sh +++ /dev/null @@ -1,78 +0,0 @@ -#! @shell@ - -set -e - -sw="/nix/var/nix/profiles/system/sw/bin" -systemPath=$(${sw}/readlink -f /nix/var/nix/profiles/system) - -function start_systemd { - echo "Starting systemd..." >&2 - - PATH=/run/current-system/systemd/lib/systemd:@fsPackagesPath@ \ - LOCALE_ARCHIVE=/run/current-system/sw/lib/locale/locale-archive \ - @daemonize@/bin/daemonize /run/current-system/sw/bin/unshare -fp --mount-proc @systemdWrapper@ - - # Wait until systemd has been started to prevent a race condition from occuring - while ! $sw/pgrep -xf systemd | $sw/tail -n1 >/run/systemd.pid; do - $sw/sleep 1s - done - - # Wait for systemd to start services - status=1 - while [[ $status -gt 0 ]]; do - $sw/sleep 1 - status=0 - $sw/nsenter -t $(/dev/null || - status=$? - done -} - -# Needs root to work -if [[ $EUID -ne 0 ]]; then - echo "[ERROR] Requires root! :( Make sure the WSL default user is set to root" >&2 - exit 1 -fi - -if [ ! -e "/run/current-system" ]; then - LANG="C.UTF-8" /nix/var/nix/profiles/system/activate -fi - -if [ ! -e "/run/systemd.pid" ]; then - start_systemd -fi - -userShell=$($sw/getent passwd @defaultUser@ | $sw/cut -d: -f7) -if [[ $# -gt 0 ]]; then - # wsl seems to prefix with "-c" - shift - cmd="$@" -else - cmd="$userShell" -fi - -# Pass external environment but filter variables specific to root user. -exportCmd="$(export -p | $sw/grep -vE ' (HOME|LOGNAME|SHELL|USER)='); export WSLPATH=\"$PATH\"; export INSIDE_NAMESPACE=true" - -if [[ -z "${INSIDE_NAMESPACE:-}" ]]; then - - # Test whether systemd is still alive if it was started previously - if ! [ -d "/proc/$(