init-less install (#188)

* wip

* Add dockerfile

* Add readme bits

* Fix logic inversion

* Relax init detection error

* Tidying heuristics

* Fix doc tests

* Mac supports start-daemon false

* Get VM tests working

* Add instructions

* Some target_os flagging

* More target flagging

* Fix lints

* Fixup more mac-only stuff

* cfg flag examples too

* Fix planner wording

* More os specific lint fixing

* More refinement on mac and the README

* Add new CI jobs to test no-daemon

* Use nix-installer-pr to point at branch

* Tests with no-init

* init/no-daemon are linux only

* nix tests support a per-distro all attribute

* Add working podman test

* Expand docker tests

* Add contributing notes

* format

* Support both podman and docker

* Update contributing

* Add Windows WSL test script for Ubuntu

* format nix tests

* More ignores to check-spelling

* Add systemd based wsl test

* We don't have root-only darwin

* Reflect review nits

* Reenable tests

* Restore mac plan

* Flag off os specific tests

* Better cfg flagging

* Remove dead comments

* Rework readme to look better with new sections

* Correct codeblock language

* Remove some warnings
This commit is contained in:
Ana Hobden 2023-02-01 12:35:52 -08:00 committed by GitHub
parent 4d2e2c3911
commit d69f335703
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 2229 additions and 1529 deletions

1
.dockerignore Normal file
View file

@ -0,0 +1 @@
target

View file

@ -12,7 +12,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Install Nix - name: Install Nix
uses: DeterminateSystems/nix-installer-action@main uses: DeterminateSystems/nix-installer-action@start-daemon-and-init
with: with:
github-token: ${{ secrets.GITHUB_TOKEN }} github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Cache lint store (x86_64-linux) - name: Cache lint store (x86_64-linux)
@ -36,7 +36,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Install Nix - name: Install Nix
uses: DeterminateSystems/nix-installer-action@main uses: DeterminateSystems/nix-installer-action@start-daemon-and-init
with: with:
github-token: ${{ secrets.GITHUB_TOKEN }} github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Cache build store (x86_64-linux) - name: Cache build store (x86_64-linux)
@ -67,7 +67,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Install Nix - name: Install Nix
uses: DeterminateSystems/nix-installer-action@main uses: DeterminateSystems/nix-installer-action@start-daemon-and-init
with: with:
github-token: ${{ secrets.GITHUB_TOKEN }} github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Cache build store (x86_64-linux) - name: Cache build store (x86_64-linux)
@ -100,7 +100,7 @@ jobs:
cp nix-installer.sh install-root/nix-installer.sh cp nix-installer.sh install-root/nix-installer.sh
mv nix-installer install-root/nix-installer-x86_64-linux mv nix-installer install-root/nix-installer-x86_64-linux
- name: Initial install - name: Initial install
uses: DeterminateSystems/nix-installer-action@main uses: DeterminateSystems/nix-installer-action@start-daemon-and-init
with: with:
local-root: install-root/ local-root: install-root/
logger: pretty logger: pretty
@ -129,7 +129,7 @@ jobs:
exit 1 exit 1
fi fi
- name: Repeated install - name: Repeated install
uses: DeterminateSystems/nix-installer-action@main uses: DeterminateSystems/nix-installer-action@start-daemon-and-init
with: with:
local-root: install-root/ local-root: install-root/
logger: pretty logger: pretty
@ -184,6 +184,113 @@ jobs:
exit 1 exit 1
fi fi
run-x86_64-linux-no-init:
name: Run x86_64 Linux (No init)
runs-on: ubuntu-22.04
needs: [build-x86_64-linux, lints]
steps:
- uses: actions/checkout@v3
- run: sudo apt install fish zsh
- uses: actions/download-artifact@v3
with:
name: nix-installer-x86_64-linux
- name: Move & set executable
run: |
chmod +x ./nix-installer
mkdir install-root
cp nix-installer.sh install-root/nix-installer.sh
mv nix-installer install-root/nix-installer-x86_64-linux
- name: Initial install
uses: DeterminateSystems/nix-installer-action@start-daemon-and-init
with:
init: none
planner: linux-multi
local-root: install-root/
logger: pretty
log-directives: nix_installer=trace
backtrace: full
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Ensure daemon was not configured with init
run: |
if systemctl is-active nix-daemon.socket; then
echo "nix-daemon.socket was running"
exit 1
fi
if systemctl is-active nix-daemon.service; then
echo "nix-daemon.service was running"
exit 1
fi
- name: Initial uninstall (without a `nix run` first)
run: sudo -E /nix/nix-installer uninstall
env:
NIX_INSTALLER_NO_CONFIRM: true
NIX_INSTALLER_LOGGER: pretty
NIX_INSTALLER_LOG_DIRECTIVES: nix_installer=trace
RUST_BACKTRACE: full
- name: Ensure `nix` is removed
run: |
if [ -e /nix ]; then
echo "/nix exists"
exit 1
fi
- name: Repeated install
uses: DeterminateSystems/nix-installer-action@start-daemon-and-init
with:
init: none
planner: linux-multi
local-root: install-root/
logger: pretty
log-directives: nix_installer=trace
backtrace: full
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: echo $PATH
run: echo $PATH
- name: Test `nix` with `$GITHUB_PATH`
if: success() || failure()
run: |
sudo -i nix run nixpkgs#fortune
sudo -i nix profile install nixpkgs#fortune
fortune
sudo -i nix store gc
sudo -i nix run nixpkgs#fortune
- name: Test bash
run: sudo -i nix-instantiate -E 'builtins.currentTime' --eval
if: success() || failure()
shell: bash --login {0}
- name: Test sh
run: sudo -i nix-instantiate -E 'builtins.currentTime' --eval
if: success() || failure()
shell: sh -l {0}
- name: Test zsh
run: sudo -i nix-instantiate -E 'builtins.currentTime' --eval
if: success() || failure()
shell: zsh --login --interactive {0}
- name: Test fish
run: sudo -i nix-instantiate -E 'builtins.currentTime' --eval
if: success() || failure()
shell: fish --login {0}
- name: Repeated uninstall
run: sudo -E /nix/nix-installer uninstall
env:
NIX_INSTALLER_NO_CONFIRM: true
NIX_INSTALLER_LOGGER: pretty
NIX_INSTALLER_LOG_DIRECTIVES: nix_installer=trace
RUST_BACKTRACE: full
- name: Ensure `nix` is removed
run: |
if systemctl is-active nix-daemon.socket; then
echo "nix-daemon.socket was running"
exit 1
fi
if systemctl is-active nix-daemon.service; then
echo "nix-daemon.service was running"
exit 1
fi
if [ -e /nix ]; then
echo "/nix exists"
exit 1
fi
run-steam-deck: run-steam-deck:
name: Run Steam Deck (mock) name: Run Steam Deck (mock)
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
@ -207,7 +314,7 @@ jobs:
sudo chmod +x /bin/steamos-readonly sudo chmod +x /bin/steamos-readonly
sudo useradd -m deck sudo useradd -m deck
- name: Initial install - name: Initial install
uses: DeterminateSystems/nix-installer-action@main uses: DeterminateSystems/nix-installer-action@start-daemon-and-init
with: with:
local-root: install-root/ local-root: install-root/
logger: pretty logger: pretty
@ -242,7 +349,7 @@ jobs:
exit 1 exit 1
fi fi
- name: Repeated install - name: Repeated install
uses: DeterminateSystems/nix-installer-action@main uses: DeterminateSystems/nix-installer-action@start-daemon-and-init
with: with:
local-root: install-root/ local-root: install-root/
logger: pretty logger: pretty
@ -309,7 +416,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Install Nix - name: Install Nix
uses: DeterminateSystems/nix-installer-action@main uses: DeterminateSystems/nix-installer-action@start-daemon-and-init
with: with:
github-token: ${{ secrets.GITHUB_TOKEN }} github-token: ${{ secrets.GITHUB_TOKEN }}
# Runs clippy as part of the preBuild. # Runs clippy as part of the preBuild.
@ -339,7 +446,7 @@ jobs:
cp nix-installer.sh install-root/nix-installer.sh cp nix-installer.sh install-root/nix-installer.sh
mv nix-installer install-root/nix-installer-x86_64-darwin mv nix-installer install-root/nix-installer-x86_64-darwin
- name: Initial install - name: Initial install
uses: DeterminateSystems/nix-installer-action@main uses: DeterminateSystems/nix-installer-action@start-daemon-and-init
with: with:
local-root: install-root/ local-root: install-root/
logger: pretty logger: pretty
@ -356,7 +463,7 @@ jobs:
NIX_INSTALLER_LOG_DIRECTIVES: nix_installer=trace NIX_INSTALLER_LOG_DIRECTIVES: nix_installer=trace
RUST_BACKTRACE: full RUST_BACKTRACE: full
- name: Repeated install - name: Repeated install
uses: DeterminateSystems/nix-installer-action@main uses: DeterminateSystems/nix-installer-action@start-daemon-and-init
with: with:
local-root: install-root/ local-root: install-root/
logger: pretty logger: pretty
@ -397,4 +504,3 @@ jobs:
NIX_INSTALLER_LOGGER: pretty NIX_INSTALLER_LOGGER: pretty
NIX_INSTALLER_LOG_DIRECTIVES: nix_installer=trace NIX_INSTALLER_LOG_DIRECTIVES: nix_installer=trace
RUST_BACKTRACE: full RUST_BACKTRACE: full

View file

@ -156,7 +156,7 @@ It's perfectly fine if they are manual or labor intensive, as these should be a
## `qemu` VM tests ## `qemu` VM tests
In `nix/tests/vm-test` there exists some Nix derivations which we expose in the flake via `hydraJobs`. For x86_64 Linux we have some additional QEMU based tests. In `nix/tests/vm-test` there exists some Nix derivations which we expose in the flake via `hydraJobs`.
These should be visible in `nix flake show`: These should be visible in `nix flake show`:
@ -190,13 +190,13 @@ git+file:///home/ana/git/determinatesystems/nix-installer
To run all of the currently supported tests: To run all of the currently supported tests:
```bash ```bash
nix build .#hydraJobs.vm-test.all.x86_64-linux.install-default -L nix build .#hydraJobs.vm-test.all.x86_64-linux.all -L
``` ```
To run a specific distribution listed in the `nix flake show` output: To run a specific distribution listed in the `nix flake show` output:
```bash ```bash
nix build .#hydraJobs.vm-test.rhel-v7.x86_64-linux.install-default -L nix build .#hydraJobs.vm-test.rhel-v7.x86_64-linux.all -L
``` ```
For PR review, you can also test arbitrary branches or checkouts like so: For PR review, you can also test arbitrary branches or checkouts like so:
@ -238,6 +238,120 @@ installer-test-rhel-v7-install-default> Formatting './disk.qcow2', fmt=qcow2 clu
</details> </details>
## Container tests
For x86_64 Linux we have some additional container tests. In `nix/tests/container-test` there exists some Nix derivations which we expose in the flake via `hydraJobs`.
These should be visible in `nix flake show`:
```
nix flake show
warning: Git tree '/home/ana/git/determinatesystems/nix-installer' is dirty
git+file:///home/ana/git/determinatesystems/nix-installer
# ...
├───hydraJobs
│ ├───container-test
│ │ ├───all
│ │ │ └───x86_64-linux
│ │ │ ├───all: derivation 'all'
│ │ │ ├───docker: derivation 'all'
│ │ │ └───podman: derivation 'all'
│ │ ├───ubuntu-v18_04
│ │ │ └───x86_64-linux
│ │ │ ├───all: derivation 'all'
│ │ │ ├───docker: derivation 'vm-test-run-container-test-ubuntu-v18_04'
│ │ │ └───podman: derivation 'vm-test-run-container-test-ubuntu-v18_04'
│ │ ├───ubuntu-v20_04
│ │ │ └───x86_64-linux
│ │ │ ├───all: derivation 'all'
│ │ │ ├───docker: derivation 'vm-test-run-container-test-ubuntu-v20_04'
│ │ │ └───podman: derivation 'vm-test-run-container-test-ubuntu-v20_04'
│ │ └───ubuntu-v22_04
│ │ └───x86_64-linux
│ │ ├───all: derivation 'all'
│ │ ├───docker: derivation 'vm-test-run-container-test-ubuntu-v22_04'
│ │ └───podman: derivation 'vm-test-run-container-test-ubuntu-v22_04'
```
To run all of the currently supported tests:
```bash
nix build .#hydraJobs.container-test.all.x86_64-linux.all -L
```
To run a specific distribution listed in the `nix flake show` output:
```bash
nix build .#hydraJobs.container-test.ubuntu-v22_04.x86_64-linux.docker -L
```
For PR review, you can also test arbitrary branches or checkouts like so:
```bash
nix build github:determinatesystems/nix-installer/${BRANCH}#hydraJobs.container-test.ubuntu-v22_04.x86_64-linux.podman -L
```
<details>
<summary><strong>Adding a distro?</strong></summary>
Notice how `ubuntu-v20_02` has a `v20`, not just `20`? That's so the test output shows correctly, as Nix will interpret the first `-\d` (eg `-20`, `-123213`) as a version, and not show it in the output.
Using `v20` instead turns:
```
# ...
vm-test-run-container-test-ubuntu> machine # [ 23.385182] dhcpcd[670]: vethae56f366: deleting address fe80::c036:c8ff:fe04:5832
vm-test-run-container-test-ubuntu> machine # this derivation will be built:
vm-test-run-container-test-ubuntu> machine # /nix/store/9qb0l9n1gsmcyynfmndnq3qpmlvq8rln-foo.drv
vm-test-run-container-test-ubuntu> machine # [ 23.424605] dhcpcd[670]: vethae56f366: removing interface
vm-test-run-container-test-ubuntu> machine # building '/nix/store/9qb0l9n1gsmcyynfmndnq3qpmlvq8rln-foo.drv'...
vm-test-run-container-test-ubuntu> machine # [ 23.371066] systemd[1]: crun-buildah-buildah1810857047.scope: Deactivated successfully.
# ...
```
Into this:
```
# ...
vm-test-run-container-test-ubuntu-v18_04> machine # [ 23.385182] dhcpcd[670]: vethae56f366: deleting address fe80::c036:c8ff:fe04:5832
vm-test-run-container-test-ubuntu-v20_04> machine # this derivation will be built:
vm-test-run-container-test-ubuntu-v20_04> machine # /nix/store/9qb0l9n1gsmcyynfmndnq3qpmlvq8rln-foo.drv
vm-test-run-container-test-ubuntu-v18_04> machine # [ 23.424605] dhcpcd[670]: vethae56f366: removing interface
vm-test-run-container-test-ubuntu-v20_04> machine # building '/nix/store/9qb0l9n1gsmcyynfmndnq3qpmlvq8rln-foo.drv'...
vm-test-run-container-test-ubuntu-v20_04> machine # [ 23.371066] systemd[1]: crun-buildah-buildah1810857047.scope: Deactivated successfully.
# ...
```
</details>
## WSL tests
On a Windows Machine with WSL2 enabled (and updated to [support systemd](https://ubuntu.com/blog/ubuntu-wsl-enable-systemd)) you can test using WSL the scripts in `tests/windows`:
```powershell
.\tests\windows\test-wsl.ps1
.\tests\windows\test-wsl.ps1 -Systemd
```
If something breaks you may need to unregister the test WSL instance. First, look for the distro prefixed with `nix-installer-test`:
```powershell
$ wsl --list
Windows Subsystem for Linux Distributions:
Ubuntu (Default)
nix-installer-test-ubuntu-jammy
```
Then delete it:
```powershell
wsl --unregister nix-installer-test-ubuntu-jammy
```
You can also remove your `$HOME/nix-installer-wsl-tests-temp` folder whenever you wish.
## Testing the `action.yml` ## Testing the `action.yml`
The `action.yml` is used directly in the CI process, so it is automatically tested for most changes. The `action.yml` is used directly in the CI process, so it is automatically tested for most changes.

121
README.md
View file

@ -16,12 +16,11 @@ curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix
Current and planned support: Current and planned support:
* [x] Multi-user Linux (aarch64 and x86_64) with systemd init, no SELinux * [x] Multi-user Linux (aarch64 and x86_64) with systemd integration, no SELinux
* [x] Multi-user MacOS (aarch64 and x86_64) * [x] Root-only Linux (aarch64 and x86_64) with no init integration, no SELinux
+ Note: User deletion is currently unimplemented, you need to use a user with a secure token and `dscl . -delete /Users/_nixbuild*` where `*` is each user number. * [x] Multi-user MacOS (aarch64 and x86_64) with launchd integration
* [x] Valve Steam Deck * [x] SteamOS on the Valve Steam Deck
* [ ] Multi-user Linux (aarch64 and x86_64) with systemd init & SELinux * [ ] Multi-user Linux (aarch64 and x86_64) with systemd integration & SELinux
* [ ] Single-user Linux (aarch64 and x86_64)
* [ ] Others... * [ ] Others...
## Installation Differences ## Installation Differences
@ -95,16 +94,16 @@ Usage: nix-installer install linux-multi [OPTIONS]
Options: Options:
# ... # ...
--nix-build-user-count <NIX_BUILD_USER_COUNT> --nix-build-group-name <NIX_BUILD_GROUP_NAME>
Number of build users to create The Nix build group name
[env: NIX_INSTALLER_NIX_BUILD_USER_COUNT=] [env: NIX_INSTALLER_NIX_BUILD_GROUP_NAME=]
[default: 32] [default: nixbld]
--nix-build-user-id-base <NIX_BUILD_USER_ID_BASE> --nix-build-group-id <NIX_BUILD_GROUP_ID>
The Nix build user base UID (ascending) The Nix build group GID
[env: NIX_INSTALLER_NIX_BUILD_USER_ID_BASE=] [env: NIX_INSTALLER_NIX_BUILD_GROUP_ID=]
[default: 3000] [default: 3000]
# ... # ...
``` ```
@ -112,9 +111,9 @@ Options:
Planners can be configured via environment variable or command arguments: Planners can be configured via environment variable or command arguments:
```bash ```bash
$ curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | NIX_BUILD_USER_COUNT=4 sh -s -- install linux-multi --nix-build-user-id-base 4000 $ curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | NIX_BUILD_GROUP_NAME=nixbuilder sh -s -- install linux-multi --nix-build-group-id 4000
# Or... # Or...
$ NIX_BUILD_USER_COUNT=4 ./nix-installer install linux-multi --nix-build-user-id-base 4000 $ NIX_BUILD_GROUP_NAME=nixbuilder ./nix-installer install linux-multi --nix-build-group-id 4000
``` ```
@ -145,28 +144,102 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Install Nix - name: Install Nix
uses: DeterminateSystems/nix-installer-action@main uses: DeterminateSystems/nix-installer-action@main
with:
# Allow the installed Nix to make authenticated Github requests.
# If you skip this, you will likely get rate limited.
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Run `nix build` - name: Run `nix build`
run: nix build . run: nix build .
``` ```
## In a container
## Building In Docker/Podman containers or WSL instances where an init (like `systemd`) is not present, pass `--init none`.
> When `--init none` is used, only `root` or sudoers can run Nix:
>
> ```bash
> sudo -i nix run nixpkgs#hello
> ```
For Docker containers (without an init):
```dockerfile
# Dockerfile
FROM ubuntu:latest
RUN apt update -y
RUN apt install curl -y
COPY nix-installer /nix-installer
RUN /nix-installer install linux-multi --init none --no-confirm
ENV PATH="${PATH}:/nix/var/nix/profiles/default/bin"
RUN nix run nixpkgs#hello
```
Podman containers require `sandbox = false` in your `Nix.conf`.
For podman containers without an init:
```dockerfile
# Dockerfile
FROM ubuntu:latest
RUN apt update -y
RUN apt install curl -y
COPY nix-installer /nix-installer
RUN /nix-installer install linux-multi --extra-conf "sandbox = false" --init none --no-confirm
ENV PATH="${PATH}:/nix/var/nix/profiles/default/bin"
RUN nix run nixpkgs#hello
```
For Podman containers with an init:
```dockerfile
# Dockerfile
FROM ubuntu:latest
RUN apt update -y
RUN apt install curl systemd -y
COPY nix-installer /nix-installer
RUN /nix-installer install linux-multi --extra-conf "sandbox = false" --no-start-daemon --no-confirm
ENV PATH="${PATH}:/nix/var/nix/profiles/default/bin"
RUN nix run nixpkgs#hello
CMD [ "/usr/sbin/init" ]
```
## In WSL
If [systemd is enabled](https://ubuntu.com/blog/ubuntu-wsl-enable-systemd) it's possible to install Nix as normal using the command at the top of this document:
```bash
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
```
If systemd is not enabled, pass `--init none` at the end of the command:
> When `--init none` is used, only `root` or sudoers can run Nix:
>
> ```bash
> sudo -i nix run nixpkgs#hello
> ```
```bash
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install --init none
```
## Building a binary
Since you'll be using `nix-installer` to install Nix on systems without Nix, the default build is a static binary. Since you'll be using `nix-installer` to install Nix on systems without Nix, the default build is a static binary.
Build a portable binary on a system with Nix: Build a portable Linux binary on a system with Nix:
```bash ```bash
nix build -L github:determinatesystems/nix-installer#nix-installer-static nix build -L github:determinatesystems/nix-installer#nix-installer-static
``` ```
On Mac:
```bash
nix build -L github:determinatesystems/nix-installer#nix-installer
```
Then copy the `result/bin/nix-installer` to the machine you wish to run it on. Then copy the `result/bin/nix-installer` to the machine you wish to run it on.
You can also add `nix-installer` to your system without having Nix: You can also add `nix-installer` to a system without Nix via `cargo`:
```bash ```bash
RUSTFLAGS="--cfg tokio_unstable" cargo install nix-installer RUSTFLAGS="--cfg tokio_unstable" cargo install nix-installer
@ -188,7 +261,7 @@ cargo add nix-installer
> **Building a CLI?** Check out the `cli` feature flag for `clap` integration. > **Building a CLI?** Check out the `cli` feature flag for `clap` integration.
You'll also need to edit your `.cargo/config.toml` to use `tokio_unstable`: You'll also need to edit your `.cargo/config.toml` to use `tokio_unstable` as we utilize [Tokio's process groups](https://docs.rs/tokio/1.24.1/tokio/process/struct.Command.html#method.process_group), which wrap stable `std` APIs, but are unstable due to it requiring an MSRV bump:
```toml ```toml
# .cargo/config.toml # .cargo/config.toml
@ -208,3 +281,5 @@ Documentation is also available via `nix` build:
nix build github:DeterminateSystems/nix-installer#nix-installer.doc nix build github:DeterminateSystems/nix-installer#nix-installer.doc
firefox result-doc/nix-installer/index.html firefox result-doc/nix-installer/index.html
``` ```

View file

@ -182,6 +182,10 @@
inherit forSystem; inherit forSystem;
inherit (nix.hydraJobs) binaryTarball; inherit (nix.hydraJobs) binaryTarball;
}; };
container-test = import ./nix/tests/container-test {
inherit forSystem;
inherit (nix.hydraJobs) binaryTarball;
};
}; };
}; };
} }

View file

@ -18,8 +18,8 @@ in
runtimeInputs = with pkgs; [ git codespell ]; runtimeInputs = with pkgs; [ git codespell ];
text = '' text = ''
codespell \ codespell \
--ignore-words-list ba,sur,crate,pullrequest,pullrequests,ser \ --ignore-words-list ba,sur,crate,pullrequest,pullrequests,ser,distroname \
--skip target \ --skip target,.git \
. .
''; '';
}); });

View file

@ -0,0 +1,100 @@
# Largely derived from https://github.com/NixOS/nix/blob/14f7dae3e4eb0c34192d0077383a7f2a2d630129/tests/installer/default.nix
{ forSystem, binaryTarball }:
let
images = {
# Found via https://hub.docker.com/_/ubuntu/ under "How is the rootfs build?"
# Jammy
"ubuntu-v22_04" = {
tarball = import <nix/fetchurl.nix> {
url = "https://launchpad.net/~cloud-images-release-managers/+livefs/ubuntu/jammy/ubuntu-oci/+build/408115/+files/livecd.ubuntu-oci.rootfs.tar.gz";
hash = "sha256-BirwSM4c+ZV1upU0yV3qa+BW9AvpBUxvZuPTeI9mA8M=";
};
tester = ./default/Dockerfile;
system = "x86_64-linux";
};
# focal
"ubuntu-v20_04" = {
tarball = import <nix/fetchurl.nix> {
url = "https://launchpad.net/~cloud-images-release-managers/+livefs/ubuntu/focal/ubuntu-oci/+build/408120/+files/livecd.ubuntu-oci.rootfs.tar.gz";
hash = "sha256-iTJR+DeC5lT4PMqT/xFAFwmlC/qvslDFccDrVFLt/a8=";
};
tester = ./default/Dockerfile;
system = "x86_64-linux";
};
# bionic
"ubuntu-v18_04" = {
tarball = import <nix/fetchurl.nix> {
url = "https://launchpad.net/~cloud-images-release-managers/+livefs/ubuntu/bionic/ubuntu-oci/+build/408103/+files/livecd.ubuntu-oci.rootfs.tar.gz";
hash = "sha256-gi48yl5laoKLoVCDIORsseOM6DI58FNpAjSVe7OOs7I=";
};
tester = ./default/Dockerfile;
system = "x86_64-linux";
};
};
makeTest = containerTool: imageName:
let image = images.${imageName}; in
with (forSystem image.system ({ system, pkgs, lib, ... }: pkgs));
testers.nixosTest
{
name = "container-test-${imageName}";
nodes = {
machine =
{ config, pkgs, ... }: {
virtualisation.${containerTool}.enable = true;
};
};
testScript = ''
machine.start()
machine.copy_from_host("${image.tarball}", "/image")
machine.succeed("mkdir -p /test")
machine.copy_from_host("${image.tester}", "/test/Dockerfile")
machine.copy_from_host("${nix-installer-static}", "/test/nix-installer")
machine.copy_from_host("${binaryTarball.${system}}", "/test/binary-tarball")
machine.succeed("${containerTool} import /image default")
machine.succeed("${containerTool} build -t test /test")
'';
};
container-tests = builtins.mapAttrs
(imageName: image: (with (forSystem "x86_64-linux" ({ system, pkgs, ... }: pkgs)); {
${image.system} = rec {
docker = makeTest "docker" imageName;
podman = makeTest "podman" imageName;
all = pkgs.releaseTools.aggregate {
name = "all";
constituents = [
docker
podman
];
};
};
}))
images;
in
container-tests // {
all."x86_64-linux" = rec {
all = (with (forSystem "x86_64-linux" ({ system, pkgs, ... }: pkgs)); pkgs.releaseTools.aggregate {
name = "all";
constituents = [
docker
podman
];
});
docker = (with (forSystem "x86_64-linux" ({ system, pkgs, ... }: pkgs)); pkgs.releaseTools.aggregate {
name = "all";
constituents = pkgs.lib.mapAttrsToList (name: value: value."x86_64-linux".docker) container-tests;
});
podman = (with (forSystem "x86_64-linux" ({ system, pkgs, ... }: pkgs)); pkgs.releaseTools.aggregate {
name = "all";
constituents = pkgs.lib.mapAttrsToList (name: value: value."x86_64-linux".podman) container-tests;
});
};
}

View file

@ -0,0 +1,8 @@
FROM default
COPY nix-installer /nix-installer
RUN chmod +x /nix-installer
COPY binary-tarball /binary-tarball
RUN mv /binary-tarball/nix-*.tar.xz nix.tar.xz
RUN /nix-installer/bin/nix-installer install linux-multi --nix-package-url file:///nix.tar.xz --init none --extra-conf "sandbox = false" --channel --no-confirm -vvv
ENV PATH="${PATH}:/nix/var/nix/profiles/default/bin"
RUN nix-build --no-substitute -E 'derivation { name = "foo"; system = "x86_64-linux"; builder = "/bin/sh"; args = ["-c" "echo foobar > $out"]; }'

View file

@ -5,10 +5,61 @@ let
installScripts = { installScripts = {
install-default = { install-default = {
script = '' install = ''
NIX_PATH=$(readlink -f nix.tar.xz) NIX_PATH=$(readlink -f nix.tar.xz)
RUST_BACKTRACE="full" ./nix-installer install --logger pretty --log-directive nix_installer=trace --channel --nix-package-url "file://$NIX_PATH" --no-confirm RUST_BACKTRACE="full" ./nix-installer install --logger pretty --log-directive nix_installer=trace --channel --nix-package-url "file://$NIX_PATH" --no-confirm
''; '';
check = ''
set -ex
nix-env --version
nix --extra-experimental-features nix-command store ping
out=$(nix-build --no-substitute -E 'derivation { name = "foo"; system = "x86_64-linux"; builder = "/bin/sh"; args = ["-c" "echo foobar > $out"]; }')
[[ $(cat $out) = foobar ]]
'';
};
install-no-start-daemon = {
install = ''
NIX_PATH=$(readlink -f nix.tar.xz)
RUST_BACKTRACE="full" ./nix-installer install linux-multi --no-start-daemon --logger pretty --log-directive nix_installer=trace --channel --nix-package-url "file://$NIX_PATH" --no-confirm
'';
check = ''
set -ex
if systemctl is-active nix-daemon.socket; then
echo "nix-daemon.socket was running, should not be"
exit 1
fi
if systemctl is-active nix-daemon.service; then
echo "nix-daemon.service was running, should not be"
exit 1
fi
sudo systemctl start nix-daemon.socket
nix-env --version
nix --extra-experimental-features nix-command store ping
out=$(nix-build --no-substitute -E 'derivation { name = "foo"; system = "x86_64-linux"; builder = "/bin/sh"; args = ["-c" "echo foobar > $out"]; }')
[[ $(cat $out) = foobar ]]
'';
};
install-daemonless = {
install = ''
NIX_PATH=$(readlink -f nix.tar.xz)
RUST_BACKTRACE="full" ./nix-installer install linux-multi --init none --logger pretty --log-directive nix_installer=trace --channel --nix-package-url "file://$NIX_PATH" --no-confirm
'';
check = ''
set -ex
sudo -i nix-env --version
sudo -i nix --extra-experimental-features nix-command store ping
echo 'derivation { name = "foo"; system = "x86_64-linux"; builder = "/bin/sh"; args = ["-c" "echo foobar > $out"]; }' | sudo tee -a /drv
out=$(sudo -i nix-build --no-substitute /drv)
[[ $(cat $out) = foobar ]]
'';
}; };
}; };
@ -114,7 +165,8 @@ let
buildInputs = [ qemu_kvm openssh ]; buildInputs = [ qemu_kvm openssh ];
image = image.image; image = image.image;
postBoot = image.postBoot or ""; postBoot = image.postBoot or "";
installScript = installScripts.${testName}.script; installScript = installScripts.${testName}.install;
checkScript = installScripts.${testName}.check;
installer = nix-installer-static; installer = nix-installer-static;
binaryTarball = binaryTarball.${system}; binaryTarball = binaryTarball.${system};
} }
@ -182,15 +234,7 @@ let
$ssh "set -eux; $installScript" $ssh "set -eux; $installScript"
echo "Testing Nix installation..." echo "Testing Nix installation..."
$ssh <<EOF $ssh "set -eux; $checkScript"
set -ex
nix-env --version
nix --extra-experimental-features nix-command store ping
out=\$(nix-build --no-substitute -E 'derivation { name = "foo"; system = "x86_64-linux"; builder = "/bin/sh"; args = ["-c" "echo foobar > \$out"]; }')
[[ \$(cat \$out) = foobar ]]
EOF
echo "Done!" echo "Done!"
touch $out touch $out
@ -198,20 +242,47 @@ let
vm-tests = builtins.mapAttrs vm-tests = builtins.mapAttrs
(imageName: image: (imageName: image:
{ rec {
${image.system} = builtins.mapAttrs ${image.system} = (builtins.mapAttrs
(testName: test: (testName: test:
makeTest imageName testName makeTest imageName testName
) )
installScripts; installScripts) // {
all = (with (forSystem "x86_64-linux" ({ system, pkgs, ... }: pkgs)); pkgs.releaseTools.aggregate {
name = "all";
constituents = (
pkgs.lib.mapAttrsToList
(testName: test:
makeTest imageName testName
)
installScripts
);
});
};
} }
) )
images; images;
in in
vm-tests // { vm-tests // rec {
all."x86_64-linux".install-default = (with (forSystem "x86_64-linux" ({ system, pkgs, ... }: pkgs)); pkgs.releaseTools.aggregate { all."x86_64-linux".install-default = (with (forSystem "x86_64-linux" ({ system, pkgs, ... }: pkgs)); pkgs.releaseTools.aggregate {
name = "all"; name = "all";
constituents = pkgs.lib.mapAttrsToList (name: value: value."x86_64-linux".install-default) vm-tests; constituents = pkgs.lib.mapAttrsToList (name: value: value."x86_64-linux".install-default) vm-tests;
}); });
all."x86_64-linux".install-no-start-daemon = (with (forSystem "x86_64-linux" ({ system, pkgs, ... }: pkgs)); pkgs.releaseTools.aggregate {
name = "all";
constituents = pkgs.lib.mapAttrsToList (name: value: value."x86_64-linux".install-default) vm-tests;
});
all."x86_64-linux".install-daemonless = (with (forSystem "x86_64-linux" ({ system, pkgs, ... }: pkgs)); pkgs.releaseTools.aggregate {
name = "all";
constituents = pkgs.lib.mapAttrsToList (name: value: value."x86_64-linux".install-daemonless) vm-tests;
});
all."x86_64-linux".all = (with (forSystem "x86_64-linux" ({ system, pkgs, ... }: pkgs)); pkgs.releaseTools.aggregate {
name = "all";
constituents = [
all."x86_64-linux".install-default
all."x86_64-linux".install-no-start-daemon
all."x86_64-linux".install-daemonless
];
});
} }

View file

@ -2,7 +2,7 @@ use std::path::{Path, PathBuf};
use crate::{ use crate::{
action::{ActionError, StatefulAction}, action::{ActionError, StatefulAction},
execute_command, set_env, execute_command, set_env, ChannelValue,
}; };
use glob::glob; use glob::glob;
@ -17,12 +17,12 @@ Setup the default Nix profile with `nss-cacert` and `nix` itself.
*/ */
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct SetupDefaultProfile { pub struct SetupDefaultProfile {
channels: Vec<String>, channels: Vec<ChannelValue>,
} }
impl SetupDefaultProfile { impl SetupDefaultProfile {
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
pub async fn plan(channels: Vec<String>) -> Result<StatefulAction<Self>, ActionError> { pub async fn plan(channels: Vec<ChannelValue>) -> Result<StatefulAction<Self>, ActionError> {
Ok(Self { channels }.into()) Ok(Self { channels }.into())
} }
} }
@ -38,7 +38,12 @@ impl Action for SetupDefaultProfile {
span!( span!(
tracing::Level::DEBUG, tracing::Level::DEBUG,
"setup_default_profile", "setup_default_profile",
channels = self.channels.join(","), channels = self
.channels
.iter()
.map(|ChannelValue(channel, url)| format!("{channel}={url}"))
.collect::<Vec<_>>()
.join(","),
) )
} }
@ -206,7 +211,7 @@ impl Action for SetupDefaultProfile {
command.process_group(0); command.process_group(0);
command.arg("--update"); command.arg("--update");
for channel in channels { for channel in channels {
command.arg(channel); command.arg(channel.0.clone());
} }
command.env( command.env(
"NIX_SSL_CERT_FILE", "NIX_SSL_CERT_FILE",

View file

@ -1,7 +1,5 @@
use std::path::{Path, PathBuf}; use std::path::PathBuf;
use target_lexicon::OperatingSystem;
use tokio::fs::remove_file;
use tokio::process::Command; use tokio::process::Command;
use tracing::{span, Span}; use tracing::{span, Span};
@ -9,48 +7,54 @@ use crate::action::{ActionError, StatefulAction};
use crate::execute_command; use crate::execute_command;
use crate::action::{Action, ActionDescription}; use crate::action::{Action, ActionDescription};
use crate::settings::InitSystem;
#[cfg(target_os = "linux")]
const SERVICE_SRC: &str = "/nix/var/nix/profiles/default/lib/systemd/system/nix-daemon.service"; const SERVICE_SRC: &str = "/nix/var/nix/profiles/default/lib/systemd/system/nix-daemon.service";
#[cfg(target_os = "linux")]
const SOCKET_SRC: &str = "/nix/var/nix/profiles/default/lib/systemd/system/nix-daemon.socket"; const SOCKET_SRC: &str = "/nix/var/nix/profiles/default/lib/systemd/system/nix-daemon.socket";
#[cfg(target_os = "linux")]
const TMPFILES_SRC: &str = "/nix/var/nix/profiles/default/lib/tmpfiles.d/nix-daemon.conf"; const TMPFILES_SRC: &str = "/nix/var/nix/profiles/default/lib/tmpfiles.d/nix-daemon.conf";
#[cfg(target_os = "linux")]
const TMPFILES_DEST: &str = "/etc/tmpfiles.d/nix-daemon.conf"; const TMPFILES_DEST: &str = "/etc/tmpfiles.d/nix-daemon.conf";
#[cfg(target_os = "macos")]
const DARWIN_NIX_DAEMON_DEST: &str = "/Library/LaunchDaemons/org.nixos.nix-daemon.plist"; const DARWIN_NIX_DAEMON_DEST: &str = "/Library/LaunchDaemons/org.nixos.nix-daemon.plist";
#[cfg(target_os = "macos")]
const DARWIN_NIX_DAEMON_SOURCE: &str = const DARWIN_NIX_DAEMON_SOURCE: &str =
"/nix/var/nix/profiles/default/Library/LaunchDaemons/org.nixos.nix-daemon.plist"; "/nix/var/nix/profiles/default/Library/LaunchDaemons/org.nixos.nix-daemon.plist";
/** /**
Run systemd utilities to configure the Nix daemon Configure the init to run the Nix daemon
*/ */
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct ConfigureNixDaemonService {} pub struct ConfigureInitService {
init: InitSystem,
start_daemon: bool,
}
impl ConfigureNixDaemonService { impl ConfigureInitService {
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
pub async fn plan() -> Result<StatefulAction<Self>, ActionError> { pub async fn plan(
match OperatingSystem::host() { init: InitSystem,
OperatingSystem::MacOSX { start_daemon: bool,
major: _, ) -> Result<StatefulAction<Self>, ActionError> {
minor: _, Ok(Self { init, start_daemon }.into())
patch: _,
}
| OperatingSystem::Darwin => (),
_ => {
if !Path::new("/run/systemd/system").exists() {
return Err(ActionError::Custom(Box::new(
ConfigureNixDaemonServiceError::InitNotSupported,
)));
}
},
};
Ok(Self {}.into())
} }
} }
#[async_trait::async_trait] #[async_trait::async_trait]
#[typetag::serde(name = "configure_nix_daemon")] #[typetag::serde(name = "configure_nix_daemon")]
impl Action for ConfigureNixDaemonService { impl Action for ConfigureInitService {
fn tracing_synopsis(&self) -> String { fn tracing_synopsis(&self) -> String {
"Configure Nix daemon related settings with systemd".to_string() match self.init {
#[cfg(target_os = "linux")]
InitSystem::Systemd => "Configure Nix daemon related settings with systemd".to_string(),
#[cfg(target_os = "macos")]
InitSystem::Launchd => {
"Configure Nix daemon related settings with launchctl".to_string()
},
#[cfg(not(target_os = "macos"))]
InitSystem::None => "Leave the Nix daemon unconfigured".to_string(),
}
} }
fn tracing_span(&self) -> Span { fn tracing_span(&self) -> Span {
@ -58,44 +62,45 @@ impl Action for ConfigureNixDaemonService {
} }
fn execute_description(&self) -> Vec<ActionDescription> { fn execute_description(&self) -> Vec<ActionDescription> {
match OperatingSystem::host() { let mut vec = Vec::new();
OperatingSystem::MacOSX { match self.init {
major: _, #[cfg(target_os = "linux")]
minor: _, InitSystem::Systemd => {
patch: _, let mut explanation = vec![
}
| OperatingSystem::Darwin => vec![ActionDescription::new(
self.tracing_synopsis(),
vec![
format!("Copy `{DARWIN_NIX_DAEMON_SOURCE}` to `DARWIN_NIX_DAEMON_DEST`"),
format!("Run `launchctl load {DARWIN_NIX_DAEMON_DEST}`"),
],
)],
_ => vec![ActionDescription::new(
self.tracing_synopsis(),
vec![
"Run `systemd-tempfiles --create --prefix=/nix/var/nix`".to_string(), "Run `systemd-tempfiles --create --prefix=/nix/var/nix`".to_string(),
format!("Run `systemctl link {SERVICE_SRC}`"), format!("Run `systemctl link {SERVICE_SRC}`"),
format!("Run `systemctl link {SOCKET_SRC}`"), format!("Run `systemctl link {SOCKET_SRC}`"),
"Run `systemctl daemon-reload`".to_string(), "Run `systemctl daemon-reload`".to_string(),
format!("Run `systemctl enable --now {SOCKET_SRC}`"), ];
], if self.start_daemon {
)], explanation.push(format!("Run `systemctl enable --now {SOCKET_SRC}`"));
}
vec.push(ActionDescription::new(self.tracing_synopsis(), explanation))
},
#[cfg(target_os = "macos")]
InitSystem::Launchd => {
let mut explanation = vec![format!(
"Copy `{DARWIN_NIX_DAEMON_SOURCE}` to `DARWIN_NIX_DAEMON_DEST`"
)];
if self.start_daemon {
explanation.push(format!("Run `launchctl load {DARWIN_NIX_DAEMON_DEST}`"));
}
vec.push(ActionDescription::new(self.tracing_synopsis(), explanation))
},
#[cfg(not(target_os = "macos"))]
InitSystem::None => (),
} }
vec
} }
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
async fn execute(&mut self) -> Result<(), ActionError> { async fn execute(&mut self) -> Result<(), ActionError> {
let Self {} = self; let Self { init, start_daemon } = self;
match OperatingSystem::host() { match init {
OperatingSystem::MacOSX { #[cfg(target_os = "macos")]
major: _, InitSystem::Launchd => {
minor: _, let src = std::path::Path::new(DARWIN_NIX_DAEMON_SOURCE);
patch: _,
}
| OperatingSystem::Darwin => {
let src = Path::new(DARWIN_NIX_DAEMON_SOURCE);
tokio::fs::copy(src.clone(), DARWIN_NIX_DAEMON_DEST) tokio::fs::copy(src.clone(), DARWIN_NIX_DAEMON_DEST)
.await .await
.map_err(|e| { .map_err(|e| {
@ -115,8 +120,22 @@ impl Action for ConfigureNixDaemonService {
) )
.await .await
.map_err(ActionError::Command)?; .map_err(ActionError::Command)?;
if *start_daemon {
execute_command(
Command::new("launchctl")
.process_group(0)
.arg("kickstart")
.arg("-k")
.arg("system/org.nixos.nix-daemon")
.stdin(std::process::Stdio::null()),
)
.await
.map_err(ActionError::Command)?;
}
}, },
_ => { #[cfg(target_os = "linux")]
InitSystem::Systemd => {
tracing::trace!(src = TMPFILES_SRC, dest = TMPFILES_DEST, "Symlinking"); tracing::trace!(src = TMPFILES_SRC, dest = TMPFILES_DEST, "Symlinking");
tokio::fs::symlink(TMPFILES_SRC, TMPFILES_DEST) tokio::fs::symlink(TMPFILES_SRC, TMPFILES_DEST)
.await .await
@ -158,24 +177,30 @@ impl Action for ConfigureNixDaemonService {
.await .await
.map_err(ActionError::Command)?; .map_err(ActionError::Command)?;
execute_command( if *start_daemon {
Command::new("systemctl") execute_command(
.process_group(0) Command::new("systemctl")
.arg("daemon-reload") .process_group(0)
.stdin(std::process::Stdio::null()), .arg("daemon-reload")
) .stdin(std::process::Stdio::null()),
.await )
.map_err(ActionError::Command)?; .await
.map_err(ActionError::Command)?;
execute_command( execute_command(
Command::new("systemctl") Command::new("systemctl")
.process_group(0) .process_group(0)
.arg("enable") .arg("enable")
.arg("--now") .arg("--now")
.arg(SOCKET_SRC), .arg(SOCKET_SRC),
) )
.await .await
.map_err(ActionError::Command)?; .map_err(ActionError::Command)?;
}
},
#[cfg(not(target_os = "macos"))]
InitSystem::None => {
// Nothing here, no init system
}, },
}; };
@ -183,37 +208,36 @@ impl Action for ConfigureNixDaemonService {
} }
fn revert_description(&self) -> Vec<ActionDescription> { fn revert_description(&self) -> Vec<ActionDescription> {
match OperatingSystem::host() { match self.init {
OperatingSystem::MacOSX { #[cfg(target_os = "linux")]
major: _, InitSystem::Systemd => {
minor: _, vec![ActionDescription::new(
patch: _, "Unconfigure Nix daemon related settings with systemd".to_string(),
} vec![
| OperatingSystem::Darwin => vec![ActionDescription::new( "Run `systemctl disable {SOCKET_SRC}`".to_string(),
"Unconfigure Nix daemon related settings with launchd".to_string(), "Run `systemctl disable {SERVICE_SRC}`".to_string(),
vec!["Run `launchctl unload {DARWIN_NIX_DAEMON_DEST}`".to_string()], "Run `systemd-tempfiles --remove --prefix=/nix/var/nix`".to_string(),
)], "Run `systemctl daemon-reload`".to_string(),
_ => vec![ActionDescription::new( ],
"Unconfigure Nix daemon related settings with systemd".to_string(), )]
vec![ },
"Run `systemctl disable {SOCKET_SRC}`".to_string(), #[cfg(target_os = "macos")]
"Run `systemctl disable {SERVICE_SRC}`".to_string(), InitSystem::Launchd => {
"Run `systemd-tempfiles --remove --prefix=/nix/var/nix`".to_string(), vec![ActionDescription::new(
"Run `systemctl daemon-reload`".to_string(), "Unconfigure Nix daemon related settings with launchctl".to_string(),
], vec!["Run `launchctl unload {DARWIN_NIX_DAEMON_DEST}`".to_string()],
)], )]
},
#[cfg(not(target_os = "macos"))]
InitSystem::None => Vec::new(),
} }
} }
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
async fn revert(&mut self) -> Result<(), ActionError> { async fn revert(&mut self) -> Result<(), ActionError> {
match OperatingSystem::host() { match self.init {
OperatingSystem::MacOSX { #[cfg(target_os = "macos")]
major: _, InitSystem::Launchd => {
minor: _,
patch: _,
}
| OperatingSystem::Darwin => {
execute_command( execute_command(
Command::new("launchctl") Command::new("launchctl")
.process_group(0) .process_group(0)
@ -223,7 +247,8 @@ impl Action for ConfigureNixDaemonService {
.await .await
.map_err(ActionError::Command)?; .map_err(ActionError::Command)?;
}, },
_ => { #[cfg(target_os = "linux")]
InitSystem::Systemd => {
// We separate stop and disable (instead of using `--now`) to avoid cases where the service isn't started, but is enabled. // We separate stop and disable (instead of using `--now`) to avoid cases where the service isn't started, but is enabled.
let socket_is_active = is_active("nix-daemon.socket").await?; let socket_is_active = is_active("nix-daemon.socket").await?;
@ -285,7 +310,7 @@ impl Action for ConfigureNixDaemonService {
.await .await
.map_err(ActionError::Command)?; .map_err(ActionError::Command)?;
remove_file(TMPFILES_DEST) tokio::fs::remove_file(TMPFILES_DEST)
.await .await
.map_err(|e| ActionError::Remove(PathBuf::from(TMPFILES_DEST), e))?; .map_err(|e| ActionError::Remove(PathBuf::from(TMPFILES_DEST), e))?;
@ -298,6 +323,10 @@ impl Action for ConfigureNixDaemonService {
.await .await
.map_err(ActionError::Command)?; .map_err(ActionError::Command)?;
}, },
#[cfg(not(target_os = "macos"))]
InitSystem::None => {
// Nothing here, no init
},
}; };
Ok(()) Ok(())
@ -310,6 +339,7 @@ pub enum ConfigureNixDaemonServiceError {
InitNotSupported, InitNotSupported,
} }
#[cfg(target_os = "linux")]
async fn is_active(unit: &str) -> Result<bool, ActionError> { async fn is_active(unit: &str) -> Result<bool, ActionError> {
let output = Command::new("systemctl") let output = Command::new("systemctl")
.arg("is-active") .arg("is-active")
@ -326,6 +356,7 @@ async fn is_active(unit: &str) -> Result<bool, ActionError> {
} }
} }
#[cfg(target_os = "linux")]
async fn is_enabled(unit: &str) -> Result<bool, ActionError> { async fn is_enabled(unit: &str) -> Result<bool, ActionError> {
let output = Command::new("systemctl") let output = Command::new("systemctl")
.arg("is-enabled") .arg("is-enabled")

View file

@ -1,17 +1,12 @@
use crate::{ use crate::{
action::{ action::{
base::SetupDefaultProfile, base::SetupDefaultProfile,
common::{ common::{ConfigureShellProfile, PlaceChannelConfiguration, PlaceNixConfiguration},
ConfigureNixDaemonService, ConfigureShellProfile, PlaceChannelConfiguration,
PlaceNixConfiguration,
},
Action, ActionDescription, ActionError, StatefulAction, Action, ActionDescription, ActionError, StatefulAction,
}, },
channel_value::ChannelValue,
settings::CommonSettings, settings::CommonSettings,
}; };
use reqwest::Url;
use tracing::{span, Instrument, Span}; use tracing::{span, Instrument, Span};
/** /**
@ -23,20 +18,12 @@ pub struct ConfigureNix {
configure_shell_profile: Option<StatefulAction<ConfigureShellProfile>>, configure_shell_profile: Option<StatefulAction<ConfigureShellProfile>>,
place_channel_configuration: StatefulAction<PlaceChannelConfiguration>, place_channel_configuration: StatefulAction<PlaceChannelConfiguration>,
place_nix_configuration: StatefulAction<PlaceNixConfiguration>, place_nix_configuration: StatefulAction<PlaceNixConfiguration>,
configure_nix_daemon_service: StatefulAction<ConfigureNixDaemonService>,
} }
impl ConfigureNix { impl ConfigureNix {
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
pub async fn plan(settings: &CommonSettings) -> Result<StatefulAction<Self>, ActionError> { pub async fn plan(settings: &CommonSettings) -> Result<StatefulAction<Self>, ActionError> {
let channels: Vec<(String, Url)> = settings let setup_default_profile = SetupDefaultProfile::plan(settings.channels.clone()).await?;
.channels
.iter()
.map(|ChannelValue(channel, url)| (channel.to_string(), url.clone()))
.collect();
let setup_default_profile =
SetupDefaultProfile::plan(channels.iter().map(|(v, _k)| v.clone()).collect()).await?;
let configure_shell_profile = if settings.modify_profile { let configure_shell_profile = if settings.modify_profile {
Some(ConfigureShellProfile::plan().await?) Some(ConfigureShellProfile::plan().await?)
@ -44,20 +31,18 @@ impl ConfigureNix {
None None
}; };
let place_channel_configuration = let place_channel_configuration =
PlaceChannelConfiguration::plan(channels, settings.force).await?; PlaceChannelConfiguration::plan(settings.channels.clone(), settings.force).await?;
let place_nix_configuration = PlaceNixConfiguration::plan( let place_nix_configuration = PlaceNixConfiguration::plan(
settings.nix_build_group_name.clone(), settings.nix_build_group_name.clone(),
settings.extra_conf.clone(), settings.extra_conf.clone(),
settings.force, settings.force,
) )
.await?; .await?;
let configure_nix_daemon_service = ConfigureNixDaemonService::plan().await?;
Ok(Self { Ok(Self {
place_channel_configuration, place_channel_configuration,
place_nix_configuration, place_nix_configuration,
setup_default_profile, setup_default_profile,
configure_nix_daemon_service,
configure_shell_profile, configure_shell_profile,
} }
.into()) .into())
@ -78,14 +63,12 @@ impl Action for ConfigureNix {
fn execute_description(&self) -> Vec<ActionDescription> { fn execute_description(&self) -> Vec<ActionDescription> {
let Self { let Self {
setup_default_profile, setup_default_profile,
configure_nix_daemon_service,
place_nix_configuration, place_nix_configuration,
place_channel_configuration, place_channel_configuration,
configure_shell_profile, configure_shell_profile,
} = &self; } = &self;
let mut buf = setup_default_profile.describe_execute(); let mut buf = setup_default_profile.describe_execute();
buf.append(&mut configure_nix_daemon_service.describe_execute());
buf.append(&mut place_nix_configuration.describe_execute()); buf.append(&mut place_nix_configuration.describe_execute());
buf.append(&mut place_channel_configuration.describe_execute()); buf.append(&mut place_channel_configuration.describe_execute());
if let Some(configure_shell_profile) = configure_shell_profile { if let Some(configure_shell_profile) = configure_shell_profile {
@ -98,7 +81,6 @@ impl Action for ConfigureNix {
async fn execute(&mut self) -> Result<(), ActionError> { async fn execute(&mut self) -> Result<(), ActionError> {
let Self { let Self {
setup_default_profile, setup_default_profile,
configure_nix_daemon_service,
place_nix_configuration, place_nix_configuration,
place_channel_configuration, place_channel_configuration,
configure_shell_profile, configure_shell_profile,
@ -168,7 +150,6 @@ impl Action for ConfigureNix {
}, },
)?; )?;
}; };
configure_nix_daemon_service.try_execute().await?;
Ok(()) Ok(())
} }
@ -176,7 +157,6 @@ impl Action for ConfigureNix {
fn revert_description(&self) -> Vec<ActionDescription> { fn revert_description(&self) -> Vec<ActionDescription> {
let Self { let Self {
setup_default_profile, setup_default_profile,
configure_nix_daemon_service,
place_nix_configuration, place_nix_configuration,
place_channel_configuration, place_channel_configuration,
configure_shell_profile, configure_shell_profile,
@ -188,7 +168,6 @@ impl Action for ConfigureNix {
} }
buf.append(&mut place_channel_configuration.describe_revert()); buf.append(&mut place_channel_configuration.describe_revert());
buf.append(&mut place_nix_configuration.describe_revert()); buf.append(&mut place_nix_configuration.describe_revert());
buf.append(&mut configure_nix_daemon_service.describe_revert());
buf.append(&mut setup_default_profile.describe_revert()); buf.append(&mut setup_default_profile.describe_revert());
buf buf
@ -198,13 +177,11 @@ impl Action for ConfigureNix {
async fn revert(&mut self) -> Result<(), ActionError> { async fn revert(&mut self) -> Result<(), ActionError> {
let Self { let Self {
setup_default_profile, setup_default_profile,
configure_nix_daemon_service,
place_nix_configuration, place_nix_configuration,
place_channel_configuration, place_channel_configuration,
configure_shell_profile, configure_shell_profile,
} = self; } = self;
configure_nix_daemon_service.try_revert().await?;
if let Some(configure_shell_profile) = configure_shell_profile { if let Some(configure_shell_profile) = configure_shell_profile {
configure_shell_profile.try_revert().await?; configure_shell_profile.try_revert().await?;
} }

View file

@ -1,7 +1,7 @@
//! [`Action`](crate::action::Action)s which only call other base plugins //! [`Action`](crate::action::Action)s which only call other base plugins
pub(crate) mod configure_init_service;
pub(crate) mod configure_nix; pub(crate) mod configure_nix;
pub(crate) mod configure_nix_daemon_service;
pub(crate) mod configure_shell_profile; pub(crate) mod configure_shell_profile;
pub(crate) mod create_nix_tree; pub(crate) mod create_nix_tree;
pub(crate) mod create_users_and_groups; pub(crate) mod create_users_and_groups;
@ -9,8 +9,8 @@ pub(crate) mod place_channel_configuration;
pub(crate) mod place_nix_configuration; pub(crate) mod place_nix_configuration;
pub(crate) mod provision_nix; pub(crate) mod provision_nix;
pub use configure_init_service::{ConfigureInitService, ConfigureNixDaemonServiceError};
pub use configure_nix::ConfigureNix; pub use configure_nix::ConfigureNix;
pub use configure_nix_daemon_service::{ConfigureNixDaemonService, ConfigureNixDaemonServiceError};
pub use configure_shell_profile::ConfigureShellProfile; pub use configure_shell_profile::ConfigureShellProfile;
pub use create_nix_tree::CreateNixTree; pub use create_nix_tree::CreateNixTree;
pub use create_users_and_groups::CreateUsersAndGroups; pub use create_users_and_groups::CreateUsersAndGroups;

View file

@ -1,7 +1,7 @@
use crate::action::base::CreateFile; use crate::action::base::CreateFile;
use crate::action::ActionError; use crate::action::ActionError;
use crate::action::{Action, ActionDescription, StatefulAction}; use crate::action::{Action, ActionDescription, StatefulAction};
use reqwest::Url; use crate::ChannelValue;
use tracing::{span, Span}; use tracing::{span, Span};
/** /**
@ -9,19 +9,19 @@ Place a channel configuration containing `channels` to the `$ROOT_HOME/.nix-chan
*/ */
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct PlaceChannelConfiguration { pub struct PlaceChannelConfiguration {
channels: Vec<(String, Url)>, channels: Vec<ChannelValue>,
create_file: StatefulAction<CreateFile>, create_file: StatefulAction<CreateFile>,
} }
impl PlaceChannelConfiguration { impl PlaceChannelConfiguration {
#[tracing::instrument(level = "debug", skip_all)] #[tracing::instrument(level = "debug", skip_all)]
pub async fn plan( pub async fn plan(
channels: Vec<(String, Url)>, channels: Vec<ChannelValue>,
force: bool, force: bool,
) -> Result<StatefulAction<Self>, ActionError> { ) -> Result<StatefulAction<Self>, ActionError> {
let buf = channels let buf = channels
.iter() .iter()
.map(|(name, url)| format!("{} {}", url, name)) .map(|ChannelValue(name, url)| format!("{} {}", url, name))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("\n"); .join("\n");
let create_file = CreateFile::plan( let create_file = CreateFile::plan(
@ -62,7 +62,7 @@ impl Action for PlaceChannelConfiguration {
channels = self channels = self
.channels .channels
.iter() .iter()
.map(|(c, u)| format!("{c}={u}")) .map(|ChannelValue(c, u)| format!("{c}={u}"))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(", "), .join(", "),
) )

View file

@ -1,19 +1,13 @@
use nix::unistd::{chown, Group, User};
use uuid::Uuid; use uuid::Uuid;
use crate::{ use crate::{
action::{Action, ActionDescription, ActionError, StatefulAction}, action::{Action, ActionDescription, ActionError, StatefulAction},
execute_command, execute_command,
}; };
use rand::Rng;
use serde::Deserialize; use serde::Deserialize;
use std::{ use std::{io::SeekFrom, path::Path};
io::SeekFrom,
path::{Path, PathBuf},
str::FromStr,
};
use tokio::{ use tokio::{
fs::{remove_file, OpenOptions}, fs::OpenOptions,
io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt}, io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt},
process::Command, process::Command,
}; };

View file

@ -1,71 +0,0 @@
use tokio::process::Command;
use tracing::{span, Span};
use crate::action::{ActionError, StatefulAction};
use crate::execute_command;
use crate::action::{Action, ActionDescription};
/**
Kickstart a `launchctl` service
*/
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct KickstartLaunchctlService {
unit: String,
}
impl KickstartLaunchctlService {
#[tracing::instrument(level = "debug", skip_all)]
pub async fn plan(unit: String) -> Result<StatefulAction<Self>, ActionError> {
Ok(Self { unit }.into())
}
}
#[async_trait::async_trait]
#[typetag::serde(name = "kickstart_launchctl_service")]
impl Action for KickstartLaunchctlService {
fn tracing_synopsis(&self) -> String {
let Self { unit, .. } = self;
format!("Kickstart the launchctl unit `{unit}`")
}
fn tracing_span(&self) -> Span {
span!(
tracing::Level::DEBUG,
"kickstart_launchctl_service",
unit = %self.unit,
)
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
}
#[tracing::instrument(level = "debug", skip_all)]
async fn execute(&mut self) -> Result<(), ActionError> {
let Self { unit } = self;
execute_command(
Command::new("launchctl")
.process_group(0)
.arg("kickstart")
.arg("-k")
.arg(unit)
.stdin(std::process::Stdio::null()),
)
.await
.map_err(ActionError::Command)?;
Ok(())
}
fn revert_description(&self) -> Vec<ActionDescription> {
vec![]
}
#[tracing::instrument(level = "debug", skip_all)]
async fn revert(&mut self) -> Result<(), ActionError> {
// noop
Ok(())
}
}

View file

@ -8,7 +8,6 @@ pub(crate) mod create_nix_volume;
pub(crate) mod create_synthetic_objects; pub(crate) mod create_synthetic_objects;
pub(crate) mod enable_ownership; pub(crate) mod enable_ownership;
pub(crate) mod encrypt_apfs_volume; pub(crate) mod encrypt_apfs_volume;
pub(crate) mod kickstart_launchctl_service;
pub(crate) mod unmount_apfs_volume; pub(crate) mod unmount_apfs_volume;
pub use bootstrap_apfs_volume::{BootstrapApfsVolume, BootstrapVolumeError}; pub use bootstrap_apfs_volume::{BootstrapApfsVolume, BootstrapVolumeError};
@ -17,5 +16,4 @@ pub use create_nix_volume::{CreateNixVolume, NIX_VOLUME_MOUNTD_DEST};
pub use create_synthetic_objects::{CreateSyntheticObjects, CreateSyntheticObjectsError}; pub use create_synthetic_objects::{CreateSyntheticObjects, CreateSyntheticObjectsError};
pub use enable_ownership::{EnableOwnership, EnableOwnershipError}; pub use enable_ownership::{EnableOwnership, EnableOwnershipError};
pub use encrypt_apfs_volume::EncryptApfsVolume; pub use encrypt_apfs_volume::EncryptApfsVolume;
pub use kickstart_launchctl_service::KickstartLaunchctlService;
pub use unmount_apfs_volume::UnmountApfsVolume; pub use unmount_apfs_volume::UnmountApfsVolume;

View file

@ -32,7 +32,7 @@ action.try_revert().await.unwrap();
``` ```
A general guidance for what determines how fine-grained an [`Action`] should be is the unit of A general guidance for what determines how fine-grained an [`Action`] should be is the unit of
reversion. The [`ConfigureNixDaemonService`](common::ConfigureNixDaemonService) action is a good reversion. The [`ConfigureInitService`](common::ConfigureInitService) action is a good
example of this, it takes several steps, such as running `systemd-tmpfiles`, and calling example of this, it takes several steps, such as running `systemd-tmpfiles`, and calling
`systemctl link` on some systemd units. `systemctl link` on some systemd units.
@ -49,7 +49,7 @@ use tracing::{Span, span};
use nix_installer::{ use nix_installer::{
InstallPlan, InstallPlan,
settings::{CommonSettings, InstallSettingsError}, settings::{CommonSettings, InstallSettingsError},
planner::{Planner, PlannerError, linux::SteamDeck}, planner::{Planner, PlannerError},
action::{Action, ActionError, StatefulAction, ActionDescription}, action::{Action, ActionError, StatefulAction, ActionDescription},
}; };
@ -112,7 +112,7 @@ pub struct MyPlanner {
impl Planner for MyPlanner { impl Planner for MyPlanner {
async fn default() -> Result<Self, PlannerError> { async fn default() -> Result<Self, PlannerError> {
Ok(Self { Ok(Self {
common: CommonSettings::default()?, common: CommonSettings::default().await?,
}) })
} }

View file

@ -34,14 +34,17 @@ match plan.install(None).await {
# } # }
``` ```
Sometimes choosing a specific plan is desired: Sometimes choosing a specific planner is desired:
```rust,no_run ```rust,no_run
use std::error::Error; use std::error::Error;
use nix_installer::{InstallPlan, planner::{Planner, linux::SteamDeck}}; use nix_installer::{InstallPlan, planner::Planner};
# async fn chosen_planner_install() -> color_eyre::Result<()> { # async fn chosen_planner_install() -> color_eyre::Result<()> {
let planner = SteamDeck::default().await?; #[cfg(target_os = "linux")]
let planner = nix_installer::planner::linux::SteamDeck::default().await?;
#[cfg(target_os = "macos")]
let planner = nix_installer::planner::darwin::DarwinMulti::default().await?;
// Or call `crate::planner::BuiltinPlanner::default()` // Or call `crate::planner::BuiltinPlanner::default()`
// Match on the result to customize. // Match on the result to customize.

View file

@ -6,15 +6,15 @@ use tokio::process::Command;
use crate::{ use crate::{
action::{ action::{
common::{ConfigureNix, ProvisionNix}, common::{ConfigureInitService, ConfigureNix, ProvisionNix},
darwin::{CreateNixVolume, KickstartLaunchctlService}, darwin::CreateNixVolume,
StatefulAction, StatefulAction,
}, },
execute_command, execute_command,
os::darwin::DiskUtilOutput, os::darwin::DiskUtilOutput,
planner::{Planner, PlannerError}, planner::{Planner, PlannerError},
settings::CommonSettings,
settings::InstallSettingsError, settings::InstallSettingsError,
settings::{CommonSettings, InitSystem},
Action, BuiltinPlanner, Action, BuiltinPlanner,
}; };
@ -24,6 +24,7 @@ use crate::{
pub struct DarwinMulti { pub struct DarwinMulti {
#[cfg_attr(feature = "cli", clap(flatten))] #[cfg_attr(feature = "cli", clap(flatten))]
pub settings: CommonSettings, pub settings: CommonSettings,
/// Force encryption on the volume /// Force encryption on the volume
#[cfg_attr( #[cfg_attr(
feature = "cli", feature = "cli",
@ -76,7 +77,7 @@ async fn default_root_disk() -> Result<String, PlannerError> {
impl Planner for DarwinMulti { impl Planner for DarwinMulti {
async fn default() -> Result<Self, PlannerError> { async fn default() -> Result<Self, PlannerError> {
Ok(Self { Ok(Self {
settings: CommonSettings::default()?, settings: CommonSettings::default().await?,
root_disk: Some(default_root_disk().await?), root_disk: Some(default_root_disk().await?),
case_sensitive: false, case_sensitive: false,
encrypt: None, encrypt: None,
@ -140,7 +141,7 @@ impl Planner for DarwinMulti {
.await .await
.map_err(PlannerError::Action)? .map_err(PlannerError::Action)?
.boxed(), .boxed(),
KickstartLaunchctlService::plan("system/org.nixos.nix-daemon".into()) ConfigureInitService::plan(InitSystem::Launchd, true)
.await .await
.map_err(PlannerError::Action)? .map_err(PlannerError::Action)?
.boxed(), .boxed(),

View file

@ -1,12 +1,12 @@
use crate::{ use crate::{
action::{ action::{
base::CreateDirectory, base::CreateDirectory,
common::{ConfigureNix, ProvisionNix}, common::{ConfigureInitService, ConfigureNix, ProvisionNix},
StatefulAction, StatefulAction,
}, },
planner::{Planner, PlannerError}, planner::{Planner, PlannerError},
settings::CommonSettings, settings::CommonSettings,
settings::InstallSettingsError, settings::{InitSettings, InstallSettingsError},
Action, BuiltinPlanner, Action, BuiltinPlanner,
}; };
use std::{collections::HashMap, path::Path}; use std::{collections::HashMap, path::Path};
@ -18,6 +18,8 @@ use tokio::process::Command;
pub struct LinuxMulti { pub struct LinuxMulti {
#[cfg_attr(feature = "cli", clap(flatten))] #[cfg_attr(feature = "cli", clap(flatten))]
pub settings: CommonSettings, pub settings: CommonSettings,
#[cfg_attr(feature = "cli", clap(flatten))]
pub init: InitSettings,
} }
#[async_trait::async_trait] #[async_trait::async_trait]
@ -25,7 +27,8 @@ pub struct LinuxMulti {
impl Planner for LinuxMulti { impl Planner for LinuxMulti {
async fn default() -> Result<Self, PlannerError> { async fn default() -> Result<Self, PlannerError> {
Ok(Self { Ok(Self {
settings: CommonSettings::default()?, settings: CommonSettings::default().await?,
init: InitSettings::default().await?,
}) })
} }
@ -77,14 +80,19 @@ impl Planner for LinuxMulti {
.await .await
.map_err(PlannerError::Action)? .map_err(PlannerError::Action)?
.boxed(), .boxed(),
ConfigureInitService::plan(self.init.init, self.init.start_daemon)
.await
.map_err(PlannerError::Action)?
.boxed(),
]) ])
} }
fn settings(&self) -> Result<HashMap<String, serde_json::Value>, InstallSettingsError> { fn settings(&self) -> Result<HashMap<String, serde_json::Value>, InstallSettingsError> {
let Self { settings } = self; let Self { settings, init } = self;
let mut map = HashMap::default(); let mut map = HashMap::default();
map.extend(settings.settings()?.into_iter()); map.extend(settings.settings()?.into_iter());
map.extend(init.settings()?.into_iter());
Ok(map) Ok(map)
} }

View file

@ -64,12 +64,12 @@ use std::{collections::HashMap, path::PathBuf};
use crate::{ use crate::{
action::{ action::{
base::{CreateDirectory, CreateFile}, base::{CreateDirectory, CreateFile},
common::{ConfigureNix, ProvisionNix}, common::{ConfigureInitService, ConfigureNix, ProvisionNix},
linux::StartSystemdUnit, linux::StartSystemdUnit,
Action, StatefulAction, Action, StatefulAction,
}, },
planner::{Planner, PlannerError}, planner::{Planner, PlannerError},
settings::{CommonSettings, InstallSettingsError}, settings::{CommonSettings, InitSystem, InstallSettingsError},
BuiltinPlanner, BuiltinPlanner,
}; };
@ -95,7 +95,7 @@ impl Planner for SteamDeck {
async fn default() -> Result<Self, PlannerError> { async fn default() -> Result<Self, PlannerError> {
Ok(Self { Ok(Self {
persistence: PathBuf::from("/home/nix"), persistence: PathBuf::from("/home/nix"),
settings: CommonSettings::default()?, settings: CommonSettings::default().await?,
}) })
} }
@ -225,6 +225,11 @@ impl Planner for SteamDeck {
.await .await
.map_err(PlannerError::Action)? .map_err(PlannerError::Action)?
.boxed(), .boxed(),
// Init is required for the steam-deck archetype to make the `/nix` mount
ConfigureInitService::plan(InitSystem::Systemd, true)
.await
.map_err(PlannerError::Action)?
.boxed(),
]) ])
} }

View file

@ -1,7 +1,6 @@
/*! [`BuiltinPlanner`]s and traits to create new types which can be used to plan out an [`InstallPlan`] /*! [`BuiltinPlanner`]s and traits to create new types which can be used to plan out an [`InstallPlan`]
It's a [`Planner`]s job to construct (if possible) a valid [`InstallPlan`] for the host. Some planners, It's a [`Planner`]s job to construct (if possible) a valid [`InstallPlan`] for the host. Some planners are operating system specific, others are device specific.
like [`LinuxMulti`](linux::LinuxMulti), are operating system specific. Others, like [`SteamDeck`](linux::SteamDeck), are device specific.
[`Planner`]s contain their planner specific settings, typically alongside a [`CommonSettings`][crate::settings::CommonSettings]. [`Planner`]s contain their planner specific settings, typically alongside a [`CommonSettings`][crate::settings::CommonSettings].
@ -16,8 +15,8 @@ use std::{error::Error, collections::HashMap};
use nix_installer::{ use nix_installer::{
InstallPlan, InstallPlan,
settings::{CommonSettings, InstallSettingsError}, settings::{CommonSettings, InstallSettingsError},
planner::{Planner, PlannerError, linux::SteamDeck}, planner::{Planner, PlannerError},
action::{Action, StatefulAction, linux::StartSystemdUnit}, action::{Action, StatefulAction, base::CreateFile},
}; };
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
@ -31,7 +30,7 @@ pub struct MyPlanner {
impl Planner for MyPlanner { impl Planner for MyPlanner {
async fn default() -> Result<Self, PlannerError> { async fn default() -> Result<Self, PlannerError> {
Ok(Self { Ok(Self {
common: CommonSettings::default()?, common: CommonSettings::default().await?,
}) })
} }
@ -39,7 +38,7 @@ impl Planner for MyPlanner {
Ok(vec![ Ok(vec![
// ... // ...
StartSystemdUnit::plan("nix-daemon.socket") CreateFile::plan("/example", None, None, None, "Example".to_string(), false)
.await .await
.map_err(PlannerError::Action)?.boxed(), .map_err(PlannerError::Action)?.boxed(),
]) ])
@ -74,7 +73,9 @@ match plan.install(None).await {
``` ```
*/ */
#[cfg(target_os = "macos")]
pub mod darwin; pub mod darwin;
#[cfg(target_os = "linux")]
pub mod linux; pub mod linux;
use std::{collections::HashMap, string::FromUtf8Error}; use std::{collections::HashMap, string::FromUtf8Error};
@ -113,11 +114,14 @@ dyn_clone::clone_trait_object!(Planner);
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "cli", derive(clap::Subcommand))] #[cfg_attr(feature = "cli", derive(clap::Subcommand))]
pub enum BuiltinPlanner { pub enum BuiltinPlanner {
#[cfg(target_os = "linux")]
/// A standard Linux multi-user install /// A standard Linux multi-user install
LinuxMulti(linux::LinuxMulti), LinuxMulti(linux::LinuxMulti),
/// A standard MacOS (Darwin) multi-user install /// A standard MacOS (Darwin) multi-user install
#[cfg(target_os = "macos")]
DarwinMulti(darwin::DarwinMulti), DarwinMulti(darwin::DarwinMulti),
/// A specialized install suitable for the Valve Steam Deck console /// A specialized install suitable for the Valve Steam Deck console
#[cfg(target_os = "linux")]
SteamDeck(linux::SteamDeck), SteamDeck(linux::SteamDeck),
} }
@ -126,16 +130,20 @@ impl BuiltinPlanner {
pub async fn default() -> Result<Self, PlannerError> { pub async fn default() -> Result<Self, PlannerError> {
use target_lexicon::{Architecture, OperatingSystem}; use target_lexicon::{Architecture, OperatingSystem};
match (Architecture::host(), OperatingSystem::host()) { match (Architecture::host(), OperatingSystem::host()) {
#[cfg(target_os = "linux")]
(Architecture::X86_64, OperatingSystem::Linux) => { (Architecture::X86_64, OperatingSystem::Linux) => {
Ok(Self::LinuxMulti(linux::LinuxMulti::default().await?)) Ok(Self::LinuxMulti(linux::LinuxMulti::default().await?))
}, },
#[cfg(target_os = "linux")]
(Architecture::Aarch64(_), OperatingSystem::Linux) => { (Architecture::Aarch64(_), OperatingSystem::Linux) => {
Ok(Self::LinuxMulti(linux::LinuxMulti::default().await?)) Ok(Self::LinuxMulti(linux::LinuxMulti::default().await?))
}, },
#[cfg(target_os = "macos")]
(Architecture::X86_64, OperatingSystem::MacOSX { .. }) (Architecture::X86_64, OperatingSystem::MacOSX { .. })
| (Architecture::X86_64, OperatingSystem::Darwin) => { | (Architecture::X86_64, OperatingSystem::Darwin) => {
Ok(Self::DarwinMulti(darwin::DarwinMulti::default().await?)) Ok(Self::DarwinMulti(darwin::DarwinMulti::default().await?))
}, },
#[cfg(target_os = "macos")]
(Architecture::Aarch64(_), OperatingSystem::MacOSX { .. }) (Architecture::Aarch64(_), OperatingSystem::MacOSX { .. })
| (Architecture::Aarch64(_), OperatingSystem::Darwin) => { | (Architecture::Aarch64(_), OperatingSystem::Darwin) => {
Ok(Self::DarwinMulti(darwin::DarwinMulti::default().await?)) Ok(Self::DarwinMulti(darwin::DarwinMulti::default().await?))
@ -147,41 +155,56 @@ impl BuiltinPlanner {
pub async fn from_common_settings(settings: CommonSettings) -> Result<Self, PlannerError> { pub async fn from_common_settings(settings: CommonSettings) -> Result<Self, PlannerError> {
let mut built = Self::default().await?; let mut built = Self::default().await?;
match &mut built { match &mut built {
#[cfg(target_os = "linux")]
BuiltinPlanner::LinuxMulti(inner) => inner.settings = settings, BuiltinPlanner::LinuxMulti(inner) => inner.settings = settings,
BuiltinPlanner::DarwinMulti(inner) => inner.settings = settings, #[cfg(target_os = "linux")]
BuiltinPlanner::SteamDeck(inner) => inner.settings = settings, BuiltinPlanner::SteamDeck(inner) => inner.settings = settings,
#[cfg(target_os = "macos")]
BuiltinPlanner::DarwinMulti(inner) => inner.settings = settings,
} }
Ok(built) Ok(built)
} }
pub async fn plan(self) -> Result<InstallPlan, NixInstallerError> { pub async fn plan(self) -> Result<InstallPlan, NixInstallerError> {
match self { match self {
#[cfg(target_os = "linux")]
BuiltinPlanner::LinuxMulti(planner) => InstallPlan::plan(planner).await, BuiltinPlanner::LinuxMulti(planner) => InstallPlan::plan(planner).await,
BuiltinPlanner::DarwinMulti(planner) => InstallPlan::plan(planner).await, #[cfg(target_os = "linux")]
BuiltinPlanner::SteamDeck(planner) => InstallPlan::plan(planner).await, BuiltinPlanner::SteamDeck(planner) => InstallPlan::plan(planner).await,
#[cfg(target_os = "macos")]
BuiltinPlanner::DarwinMulti(planner) => InstallPlan::plan(planner).await,
} }
} }
pub fn boxed(self) -> Box<dyn Planner> { pub fn boxed(self) -> Box<dyn Planner> {
match self { match self {
#[cfg(target_os = "linux")]
BuiltinPlanner::LinuxMulti(i) => i.boxed(), BuiltinPlanner::LinuxMulti(i) => i.boxed(),
BuiltinPlanner::DarwinMulti(i) => i.boxed(), #[cfg(target_os = "linux")]
BuiltinPlanner::SteamDeck(i) => i.boxed(), BuiltinPlanner::SteamDeck(i) => i.boxed(),
#[cfg(target_os = "macos")]
BuiltinPlanner::DarwinMulti(i) => i.boxed(),
} }
} }
pub fn typetag_name(&self) -> &'static str { pub fn typetag_name(&self) -> &'static str {
match self { match self {
#[cfg(target_os = "linux")]
BuiltinPlanner::LinuxMulti(i) => i.typetag_name(), BuiltinPlanner::LinuxMulti(i) => i.typetag_name(),
BuiltinPlanner::DarwinMulti(i) => i.typetag_name(), #[cfg(target_os = "linux")]
BuiltinPlanner::SteamDeck(i) => i.typetag_name(), BuiltinPlanner::SteamDeck(i) => i.typetag_name(),
#[cfg(target_os = "macos")]
BuiltinPlanner::DarwinMulti(i) => i.typetag_name(),
} }
} }
pub fn settings(&self) -> Result<HashMap<String, serde_json::Value>, InstallSettingsError> { pub fn settings(&self) -> Result<HashMap<String, serde_json::Value>, InstallSettingsError> {
match self { match self {
#[cfg(target_os = "linux")]
BuiltinPlanner::LinuxMulti(i) => i.settings(), BuiltinPlanner::LinuxMulti(i) => i.settings(),
BuiltinPlanner::DarwinMulti(i) => i.settings(), #[cfg(target_os = "linux")]
BuiltinPlanner::SteamDeck(i) => i.settings(), BuiltinPlanner::SteamDeck(i) => i.settings(),
#[cfg(target_os = "macos")]
BuiltinPlanner::DarwinMulti(i) => i.settings(),
} }
} }
} }

View file

@ -21,6 +21,30 @@ pub const NIX_X64_64_DARWIN_URL: &str =
pub const NIX_AARCH64_DARWIN_URL: &str = pub const NIX_AARCH64_DARWIN_URL: &str =
"https://releases.nixos.org/nix/nix-2.12.0/nix-2.12.0-aarch64-darwin.tar.xz"; "https://releases.nixos.org/nix/nix-2.12.0/nix-2.12.0-aarch64-darwin.tar.xz";
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, Copy)]
#[cfg_attr(feature = "cli", derive(clap::ValueEnum))]
pub enum InitSystem {
#[cfg(not(target_os = "macos"))]
None,
#[cfg(target_os = "linux")]
Systemd,
#[cfg(target_os = "macos")]
Launchd,
}
impl std::fmt::Display for InitSystem {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
#[cfg(not(target_os = "macos"))]
InitSystem::None => write!(f, "none"),
#[cfg(target_os = "linux")]
InitSystem::Systemd => write!(f, "systemd"),
#[cfg(target_os = "macos")]
InitSystem::Launchd => write!(f, "launchd"),
}
}
}
/** Common settings used by all [`BuiltinPlanner`](crate::planner::BuiltinPlanner)s /** Common settings used by all [`BuiltinPlanner`](crate::planner::BuiltinPlanner)s
Settings which only apply to certain [`Planner`](crate::planner::Planner)s should be located in the planner. Settings which only apply to certain [`Planner`](crate::planner::Planner)s should be located in the planner.
@ -173,29 +197,33 @@ pub struct CommonSettings {
impl CommonSettings { impl CommonSettings {
/// The default settings for the given Architecture & Operating System /// The default settings for the given Architecture & Operating System
pub fn default() -> Result<Self, InstallSettingsError> { pub async fn default() -> Result<Self, InstallSettingsError> {
let url; let url;
let nix_build_user_prefix; let nix_build_user_prefix;
let nix_build_user_id_base; let nix_build_user_id_base;
use target_lexicon::{Architecture, OperatingSystem}; use target_lexicon::{Architecture, OperatingSystem};
match (Architecture::host(), OperatingSystem::host()) { match (Architecture::host(), OperatingSystem::host()) {
#[cfg(target_os = "linux")]
(Architecture::X86_64, OperatingSystem::Linux) => { (Architecture::X86_64, OperatingSystem::Linux) => {
url = NIX_X64_64_LINUX_URL; url = NIX_X64_64_LINUX_URL;
nix_build_user_prefix = "nixbld"; nix_build_user_prefix = "nixbld";
nix_build_user_id_base = 3000; nix_build_user_id_base = 3000;
}, },
#[cfg(target_os = "linux")]
(Architecture::Aarch64(_), OperatingSystem::Linux) => { (Architecture::Aarch64(_), OperatingSystem::Linux) => {
url = NIX_AARCH64_LINUX_URL; url = NIX_AARCH64_LINUX_URL;
nix_build_user_prefix = "nixbld"; nix_build_user_prefix = "nixbld";
nix_build_user_id_base = 3000; nix_build_user_id_base = 3000;
}, },
#[cfg(target_os = "macos")]
(Architecture::X86_64, OperatingSystem::MacOSX { .. }) (Architecture::X86_64, OperatingSystem::MacOSX { .. })
| (Architecture::X86_64, OperatingSystem::Darwin) => { | (Architecture::X86_64, OperatingSystem::Darwin) => {
url = NIX_X64_64_DARWIN_URL; url = NIX_X64_64_DARWIN_URL;
nix_build_user_prefix = "_nixbld"; nix_build_user_prefix = "_nixbld";
nix_build_user_id_base = 300; nix_build_user_id_base = 300;
}, },
#[cfg(target_os = "macos")]
(Architecture::Aarch64(_), OperatingSystem::MacOSX { .. }) (Architecture::Aarch64(_), OperatingSystem::MacOSX { .. })
| (Architecture::Aarch64(_), OperatingSystem::Darwin) => { | (Architecture::Aarch64(_), OperatingSystem::Darwin) => {
url = NIX_AARCH64_DARWIN_URL; url = NIX_AARCH64_DARWIN_URL;
@ -286,6 +314,34 @@ impl CommonSettings {
Ok(map) Ok(map)
} }
} }
#[cfg(target_os = "linux")]
async fn linux_detect_init() -> (InitSystem, bool) {
use std::process::Stdio;
let mut detected = InitSystem::None;
let mut started = false;
if std::path::Path::new("/run/systemd/system").exists() {
detected = InitSystem::Systemd;
started = if tokio::process::Command::new("systemctl")
.arg("status")
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.await
.ok()
.map(|exit| exit.success())
.unwrap_or(false)
{
true
} else {
false
}
}
// TODO: Other inits
(detected, started)
}
// Builder Pattern // Builder Pattern
impl CommonSettings { impl CommonSettings {
@ -349,6 +405,96 @@ impl CommonSettings {
} }
} }
#[serde_with::serde_as]
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
#[cfg_attr(feature = "cli", derive(clap::Parser))]
pub struct InitSettings {
/// Which init system to configure (if `--init none` Nix will be root-only)
#[cfg_attr(feature = "cli", clap(value_parser, long, env = "NIX_INSTALLER_INIT",))]
#[cfg_attr(
all(target_os = "macos", feature = "cli"),
clap(default_value_t = InitSystem::Launchd)
)]
#[cfg_attr(
all(target_os = "linux", feature = "cli"),
clap(default_value_t = InitSystem::Systemd)
)]
pub(crate) init: InitSystem,
/// Start the daemon (if not `--init none`)
#[cfg_attr(
feature = "cli",
clap(
value_parser,
long,
action(ArgAction::SetFalse),
env = "NIX_INSTALLER_START_DAEMON",
default_value_t = true,
long = "no-start-daemon"
)
)]
pub(crate) start_daemon: bool,
}
impl InitSettings {
/// The default settings for the given Architecture & Operating System
pub async fn default() -> Result<Self, InstallSettingsError> {
let init;
let start_daemon;
use target_lexicon::{Architecture, OperatingSystem};
match (Architecture::host(), OperatingSystem::host()) {
#[cfg(target_os = "linux")]
(Architecture::X86_64, OperatingSystem::Linux) => {
(init, start_daemon) = linux_detect_init().await;
},
#[cfg(target_os = "linux")]
(Architecture::Aarch64(_), OperatingSystem::Linux) => {
(init, start_daemon) = linux_detect_init().await;
},
#[cfg(target_os = "macos")]
(Architecture::X86_64, OperatingSystem::MacOSX { .. })
| (Architecture::X86_64, OperatingSystem::Darwin) => {
(init, start_daemon) = (InitSystem::Launchd, true);
},
#[cfg(target_os = "macos")]
(Architecture::Aarch64(_), OperatingSystem::MacOSX { .. })
| (Architecture::Aarch64(_), OperatingSystem::Darwin) => {
(init, start_daemon) = (InitSystem::Launchd, true);
},
_ => {
return Err(InstallSettingsError::UnsupportedArchitecture(
target_lexicon::HOST,
))
},
};
Ok(Self { init, start_daemon })
}
/// A listing of the settings, suitable for [`Planner::settings`](crate::planner::Planner::settings)
pub fn settings(&self) -> Result<HashMap<String, serde_json::Value>, InstallSettingsError> {
let Self { init, start_daemon } = self;
let mut map = HashMap::default();
map.insert("init".into(), serde_json::to_value(init)?);
map.insert("start_daemon".into(), serde_json::to_value(start_daemon)?);
Ok(map)
}
/// Which init system to configure
pub fn init(&mut self, init: InitSystem) -> &mut Self {
self.init = init;
self
}
/// Start the daemon (if one is configured)
pub fn start_daemon(&mut self, toggle: bool) -> &mut Self {
self.start_daemon = toggle;
self
}
}
/// An error originating from a [`Planner::settings`](crate::planner::Planner::settings) /// An error originating from a [`Planner::settings`](crate::planner::Planner::settings)
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum InstallSettingsError { pub enum InstallSettingsError {
@ -369,4 +515,6 @@ pub enum InstallSettingsError {
#[from] #[from]
serde_json::Error, serde_json::Error,
), ),
#[error("No supported init system found")]
InitNotSupported,
} }

View file

@ -546,7 +546,10 @@
"setup_default_profile": { "setup_default_profile": {
"action": { "action": {
"channels": [ "channels": [
"nixpkgs" [
"nixpkgs",
"https://nixos.org/channels/nixpkgs-unstable"
]
] ]
}, },
"state": "Uncompleted" "state": "Uncompleted"
@ -632,25 +635,22 @@
"user": null, "user": null,
"group": null, "group": null,
"mode": 436, "mode": 436,
"buf": "\n\nbuild-users-group = nixbld\n\nexperimental-features = nix-command flakes\n\nauto-optimise-store = true\n", "buf": "# Generated by https://github.com/DeterminateSystems/nix-installer, version 0.1.0-unreleased.\n\n\n\nbuild-users-group = nixbld\n\nexperimental-features = nix-command flakes\n\nauto-optimise-store = true\n\nbash-prompt-prefix = (nix:$name)\\040\n",
"force": false "force": false
}, },
"state": "Uncompleted" "state": "Uncompleted"
} }
}, },
"state": "Uncompleted" "state": "Uncompleted"
},
"configure_nix_daemon_service": {
"action": {},
"state": "Uncompleted"
} }
}, },
"state": "Uncompleted" "state": "Uncompleted"
}, },
{ {
"action": { "action": {
"action": "kickstart_launchctl_service", "action": "configure_nix_daemon",
"unit": "system/org.nixos.nix-daemon" "init": "Launchd",
"start_daemon": true
}, },
"state": "Uncompleted" "state": "Uncompleted"
} }

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,11 +1,15 @@
use nix_installer::InstallPlan; use nix_installer::InstallPlan;
#[cfg(target_os = "linux")]
const LINUX_MULTI: &str = include_str!("./fixtures/linux/linux-multi.json"); const LINUX_MULTI: &str = include_str!("./fixtures/linux/linux-multi.json");
#[cfg(target_os = "linux")]
const STEAM_DECK: &str = include_str!("./fixtures/linux/steam-deck.json"); const STEAM_DECK: &str = include_str!("./fixtures/linux/steam-deck.json");
#[cfg(target_os = "macos")]
const DARWIN_MULTI: &str = include_str!("./fixtures/darwin/darwin-multi.json"); const DARWIN_MULTI: &str = include_str!("./fixtures/darwin/darwin-multi.json");
// Ensure existing plans still parse // Ensure existing plans still parse
// If this breaks and you need to update the fixture, disable these tests, bump `nix_installer` to a new version, and update the plans. // If this breaks and you need to update the fixture, disable these tests, bump `nix_installer` to a new version, and update the plans.
#[cfg(target_os = "linux")]
#[test] #[test]
fn plan_compat_linux_multi() -> eyre::Result<()> { fn plan_compat_linux_multi() -> eyre::Result<()> {
let _: InstallPlan = serde_json::from_str(LINUX_MULTI)?; let _: InstallPlan = serde_json::from_str(LINUX_MULTI)?;
@ -14,6 +18,7 @@ fn plan_compat_linux_multi() -> eyre::Result<()> {
// Ensure existing plans still parse // Ensure existing plans still parse
// If this breaks and you need to update the fixture, disable these tests, bump `nix_installer` to a new version, and update the plans. // If this breaks and you need to update the fixture, disable these tests, bump `nix_installer` to a new version, and update the plans.
#[cfg(target_os = "linux")]
#[test] #[test]
fn plan_compat_steam_deck() -> eyre::Result<()> { fn plan_compat_steam_deck() -> eyre::Result<()> {
let _: InstallPlan = serde_json::from_str(STEAM_DECK)?; let _: InstallPlan = serde_json::from_str(STEAM_DECK)?;
@ -22,6 +27,7 @@ fn plan_compat_steam_deck() -> eyre::Result<()> {
// Ensure existing plans still parse // Ensure existing plans still parse
// If this breaks and you need to update the fixture, disable these tests, bump `nix_installer` to a new version, and update the plans. // If this breaks and you need to update the fixture, disable these tests, bump `nix_installer` to a new version, and update the plans.
#[cfg(target_os = "macos")]
#[test] #[test]
fn plan_compat_darwin_multi() -> eyre::Result<()> { fn plan_compat_darwin_multi() -> eyre::Result<()> {
let _: InstallPlan = serde_json::from_str(DARWIN_MULTI)?; let _: InstallPlan = serde_json::from_str(DARWIN_MULTI)?;

View file

@ -0,0 +1,75 @@
param([switch]$Systemd = $false)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
# 22.04 https://cloud-images.ubuntu.com/wsl/jammy/current/
$url = "https://cloud-images.ubuntu.com/wsl/jammy/current/ubuntu-jammy-wsl-amd64-wsl.rootfs.tar.gz"
$File = "ubuntu-jammy-wsl-amd64-wsl.rootfs.tar.gz"
$Name = "ubuntu-jammy"
$TemporaryDirectory = "$HOME/nix-installer-wsl-tests-temp"
$Image = "$TemporaryDirectory\$File"
if (!(Test-Path -Path $Image)) {
Write-Output "Fetching $File to $Image..."
New-Item $TemporaryDirectory -ItemType Directory | Out-Null
Invoke-WebRequest -Uri "https://cloud-images.ubuntu.com/wsl/jammy/current/ubuntu-jammy-wsl-amd64-wsl.rootfs.tar.gz" -OutFile $Image
} else {
Write-Output "Found existing $Image..."
}
$DistroName = "nix-installer-test-$Name"
$InstallRoot = "$TemporaryDirectory\wsl-$Name"
Write-Output "Creating WSL distribution $DistroName from $Image at $InstallRoot..."
wsl --import $DistroName $InstallRoot $Image
if ($LastExitCode -ne 0) {
exit $LastExitCode
}
Write-Output "Preparing $DistroName for nix-installer..."
wsl --distribution $DistroName bash --login -c "apt update --quiet"
if ($LastExitCode -ne 0) {
exit $LastExitCode
}
wsl --distribution $DistroName bash --login -c "apt install --quiet --yes curl build-essential"
if ($LastExitCode -ne 0) {
exit $LastExitCode
}
wsl --distribution $DistroName bash --login -c "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --quiet"
if ($LastExitCode -ne 0) {
exit $LastExitCode
}
if ($Systemd) {
$wslConf = "[boot]`nsystemd=true"
New-Item -Path "\\wsl$\$DistroName\etc\wsl.conf" -ItemType "file" -Value $wslConf
wsl --shutdown
if ($LastExitCode -ne 0) {
exit $LastExitCode
}
}
Write-Output "Building and runnings nix-installer in $DistroName..."
Copy-Item -Recurse "$PSScriptRoot\..\.." -Destination "\\wsl$\$DistroName\nix-installer"
$MaybeInitChoice = switch ($Systemd) {
$true { "" }
$false { "--init none" }
}
wsl --distribution $DistroName bash --login -c "/root/.cargo/bin/cargo run --quiet --manifest-path /nix-installer/Cargo.toml -- install linux-multi --no-confirm $MaybeInitChoice"
if ($LastExitCode -ne 0) {
exit $LastExitCode
}
Write-Output "Testing installed Nix on $DistroName..."
wsl --distribution $DistroName bash --login -c "nix run nixpkgs#hello"
if ($LastExitCode -ne 0) {
exit $LastExitCode
}
Write-Output "Unregistering $DistroName and removing $InstallRoot..."
wsl --unregister $DistroName
if ($LastExitCode -ne 0) {
exit $LastExitCode
}
Remove-Item $InstallRoot