diff --git a/.cargo/config.toml b/.cargo/config.toml index c19c93004..5deba453d 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,2 +1,2 @@ [build] -rustflags=["--cfg", "tokio_unstable"] \ No newline at end of file +rustflags=["--cfg", "tokio_unstable"] diff --git a/.dockerignore b/.dockerignore index 1de565933..eb5a316cb 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1 +1 @@ -target \ No newline at end of file +target diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..bc8007392 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,42 @@ +root = true + +# Unix-style newlines with a newline ending every file, utf-8 charset +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 + +# Rust +[*.rs] +indent_style = space + +# Misc +[*.{yaml,yml,nix,json,sh,service,socket,toml,te}] +insert_final_newline = true +indent_style = space +indent_size = 2 + +[*.md] +insert_final_newline = true +indent_style = space + +[*.plist] +indent_style = tab + +[*.ps1] +indent_style = space +indent_size = 4 + +[Cargo.lock] +indent_style = space +indent_size = 1 + +# selinux +[*.pp] +charset = unset +end_of_line = unset +indent_size = unset +indent_style = unset +insert_final_newline = unset +trim_trailing_whitespace = unset diff --git a/.envrc b/.envrc index 8392d159f..3550a30f2 100644 --- a/.envrc +++ b/.envrc @@ -1 +1 @@ -use flake \ No newline at end of file +use flake diff --git a/.github/workflows/build-aarch64-darwin.yml b/.github/workflows/build-aarch64-darwin.yml index b6e85524b..45df246ad 100644 --- a/.github/workflows/build-aarch64-darwin.yml +++ b/.github/workflows/build-aarch64-darwin.yml @@ -21,9 +21,11 @@ jobs: id-token: "write" contents: "read" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install Nix uses: DeterminateSystems/nix-installer-action@main + with: + determinate: false - uses: DeterminateSystems/magic-nix-cache-action@main with: use-flakehub: false diff --git a/.github/workflows/build-aarch64-linux.yml b/.github/workflows/build-aarch64-linux.yml index 43f356c70..dd82b3cd5 100644 --- a/.github/workflows/build-aarch64-linux.yml +++ b/.github/workflows/build-aarch64-linux.yml @@ -10,7 +10,7 @@ on: runs-on: type: string required: true - default: namespace-profile-default-arm64 + default: UbuntuLatest32Cores128GArm jobs: build-aarch64-linux: @@ -21,9 +21,11 @@ jobs: id-token: "write" contents: "read" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install Nix uses: DeterminateSystems/nix-installer-action@main + with: + determinate: false - uses: DeterminateSystems/magic-nix-cache-action@main with: use-flakehub: false diff --git a/.github/workflows/build-i686-linux.yml b/.github/workflows/build-i686-linux.yml deleted file mode 100644 index bc7e5a6e1..000000000 --- a/.github/workflows/build-i686-linux.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Build i686 Linux (static) - -on: - workflow_call: - inputs: - cache-key: - type: string - required: false - default: i686-linux-artifacts-${{ github.sha }} - runs-on: - type: string - required: true - default: UbuntuLatest32Cores128G - -jobs: - build-i686-linux: - name: Build i686 Linux (static) - runs-on: ${{ inputs.runs-on }} - concurrency: ${{ inputs.cache-key }} - permissions: - id-token: "write" - contents: "read" - steps: - - uses: actions/checkout@v3 - - name: Install Nix - uses: DeterminateSystems/nix-installer-action@main - - uses: DeterminateSystems/magic-nix-cache-action@main - with: - use-flakehub: false - use-gha-cache: false - - name: Build the installer - run: | - nix build .#packages.i686-linux.nix-installer-static -L - cp result/bin/nix-installer . - - name: Create GitHub cache from build artifacts - uses: actions/cache/save@v3 - with: - path: nix-installer - key: ${{ inputs.cache-key }} diff --git a/.github/workflows/build-x86_64-darwin.yml b/.github/workflows/build-x86_64-darwin.yml index 20911c31e..42acf3645 100644 --- a/.github/workflows/build-x86_64-darwin.yml +++ b/.github/workflows/build-x86_64-darwin.yml @@ -21,9 +21,11 @@ jobs: id-token: "write" contents: "read" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install Nix uses: DeterminateSystems/nix-installer-action@main + with: + determinate: false - uses: DeterminateSystems/magic-nix-cache-action@main with: use-flakehub: false diff --git a/.github/workflows/build-x86_64-linux.yml b/.github/workflows/build-x86_64-linux.yml index 2a74f043b..93a267e40 100644 --- a/.github/workflows/build-x86_64-linux.yml +++ b/.github/workflows/build-x86_64-linux.yml @@ -21,9 +21,11 @@ jobs: id-token: "write" contents: "read" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install Nix uses: DeterminateSystems/nix-installer-action@main + with: + determinate: false - uses: DeterminateSystems/magic-nix-cache-action@main with: use-flakehub: false diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6e20692e3..c015a25f7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,7 @@ name: CI on: + workflow_dispatch: pull_request: push: @@ -19,6 +20,15 @@ jobs: with: runs-on: macos-latest + # TODO: need runner; GH's supposedly coming late this year + # build-aarch64-linux: + # uses: ./.github/workflows/build-aarch64-linux.yml + + build-aarch64-darwin: + uses: ./.github/workflows/build-aarch64-darwin.yml + with: + runs-on: macos-latest + lints: name: Lints runs-on: ubuntu-latest @@ -26,7 +36,7 @@ jobs: id-token: "write" contents: "read" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Check Nixpkgs input uses: DeterminateSystems/flake-checker-action@main with: @@ -34,6 +44,8 @@ jobs: check-outdated: false # PRs shouldn't fail because main's nixpkgs is out of date - name: Install Nix uses: DeterminateSystems/nix-installer-action@main + with: + determinate: false - uses: DeterminateSystems/magic-nix-cache-action@main with: use-flakehub: false @@ -48,17 +60,22 @@ jobs: run: nix develop --command check-nixpkgs-fmt - name: Check EditorConfig conformance run: nix develop --command check-editorconfig + - name: Shell check for nix-installer.sh + run: nix develop --command shellcheck ./nix-installer.sh run-x86_64-linux: - name: Run x86_64 Linux + name: Run x86_64 Linux${{ matrix.determinate && ' (--determinate)' || ''}} runs-on: ubuntu-latest needs: [lints, build-x86_64-linux] + strategy: + matrix: + determinate: [true, false] permissions: id-token: "write" contents: "read" steps: - - uses: actions/checkout@v3 - - name: Restore Github cache of Buildkite artifacts + - uses: actions/checkout@v4 + - name: Restore Github cache artifacts uses: actions/cache/restore@v3 with: path: nix-installer @@ -73,11 +90,20 @@ jobs: - name: Initial install uses: DeterminateSystems/nix-installer-action@main with: + backtrace: full + determinate: ${{ matrix.determinate }} local-root: install-root/ - logger: pretty log-directives: nix_installer=debug - backtrace: full - github-token: ${{ secrets.GITHUB_TOKEN }} + logger: pretty + - name: "Validate dnixd is ${{ matrix.determinate && 'installed' || 'uninstalled' }}" + run: | + if test -x /usr/local/bin/determinate-nixd; then + echo "determinate-nixd is present" + ${{ matrix.determinate }} + else + echo "determinate-nixd is not present" + ${{ !matrix.determinate }} + fi - name: Initial uninstall (without a `nix run` first) run: sudo -E /nix/nix-installer uninstall env: @@ -102,11 +128,11 @@ jobs: - name: Repeated install uses: DeterminateSystems/nix-installer-action@main with: + backtrace: full + determinate: ${{ matrix.determinate }} local-root: install-root/ - logger: pretty log-directives: nix_installer=debug - backtrace: full - github-token: ${{ secrets.GITHUB_TOKEN }} + logger: pretty - name: echo $PATH run: echo $PATH - name: Test `nix` with `$GITHUB_PATH` @@ -156,15 +182,18 @@ jobs: fi run-x86_64-linux-no-init: - name: Run x86_64 Linux (No init) + name: Run x86_64 Linux (No init${{ matrix.determinate && ', --determinate' || ''}}) runs-on: ubuntu-latest needs: [lints, build-x86_64-linux] + strategy: + matrix: + determinate: [true, false] permissions: id-token: "write" contents: "read" steps: - - uses: actions/checkout@v3 - - name: Restore Github cache of Buildkite artifacts + - uses: actions/checkout@v4 + - name: Restore Github cache artifacts uses: actions/cache/restore@v3 with: path: nix-installer @@ -179,13 +208,22 @@ jobs: - name: Initial install uses: DeterminateSystems/nix-installer-action@main with: + backtrace: full + determinate: ${{ matrix.determinate }} init: none - planner: linux local-root: install-root/ - logger: pretty log-directives: nix_installer=debug - backtrace: full - github-token: ${{ secrets.GITHUB_TOKEN }} + logger: pretty + planner: linux + - name: "Validate dnixd is ${{ matrix.determinate && 'installed' || 'uninstalled' }}" + run: | + if test -x /usr/local/bin/determinate-nixd; then + echo "determinate-nixd is present" + ${{ matrix.determinate }} + else + echo "determinate-nixd is not present" + ${{ !matrix.determinate }} + fi - name: Ensure daemon was not configured with init run: | if systemctl is-active nix-daemon.socket; then @@ -212,13 +250,13 @@ jobs: - name: Repeated install uses: DeterminateSystems/nix-installer-action@main with: + backtrace: full + determinate: ${{ matrix.determinate }} init: none - planner: linux local-root: install-root/ - logger: pretty log-directives: nix_installer=debug - backtrace: full - github-token: ${{ secrets.GITHUB_TOKEN }} + logger: pretty + planner: linux - name: echo $PATH run: echo $PATH - name: Test `nix` with `$GITHUB_PATH` @@ -268,15 +306,18 @@ jobs: fi run-x86_64-darwin: - name: Run x86_64 Darwin + name: Run x86_64 Darwin${{ matrix.determinate && ' (--determinate)' || ''}} runs-on: macos-13 needs: [lints, build-x86_64-darwin] + strategy: + matrix: + determinate: [true, false] permissions: id-token: "write" contents: "read" steps: - - uses: actions/checkout@v3 - - name: Restore Github cache of Buildkite artifacts + - uses: actions/checkout@v4 + - name: Restore Github cache artifacts uses: actions/cache/restore@v3 with: path: nix-installer @@ -291,13 +332,20 @@ jobs: - name: Initial install uses: DeterminateSystems/nix-installer-action@main with: + backtrace: full + determinate: ${{ matrix.determinate }} local-root: install-root/ - logger: pretty log-directives: nix_installer=debug - backtrace: full - github-token: ${{ secrets.GITHUB_TOKEN }} - extra-conf: | - trusted-users = root runner + logger: pretty + - name: "Validate dnixd is ${{ matrix.determinate && 'installed' || 'uninstalled' }}" + run: | + if test -x /usr/local/bin/determinate-nixd; then + echo "determinate-nixd is present" + ${{ matrix.determinate }} + else + echo "determinate-nixd is not present" + ${{ !matrix.determinate }} + fi - name: Initial uninstall (without a `nix run` first) run: sudo -E /nix/nix-installer uninstall env: @@ -308,12 +356,11 @@ jobs: - name: Repeated install uses: DeterminateSystems/nix-installer-action@main with: + backtrace: full + determinate: ${{ matrix.determinate }} local-root: install-root/ - logger: pretty log-directives: nix_installer=debug - backtrace: full - github-token: ${{ secrets.GITHUB_TOKEN }} - extra-conf: trusted-users = root runner + logger: pretty - name: echo $PATH run: echo $PATH - name: Test `nix` with `$GITHUB_PATH` @@ -347,3 +394,254 @@ jobs: NIX_INSTALLER_LOGGER: pretty NIX_INSTALLER_LOG_DIRECTIVES: nix_installer=debug RUST_BACKTRACE: full + + # TODO: need runner + # run-aarch64-linux: + # name: Run aarch64 Linux${{ matrix.determinate && ' (--determinate)' || ''}} + # runs-on: namespace-profile-default-arm64 + # needs: [lints, build-aarch64-linux] + # strategy: + # matrix: + # determinate: [true, false] + # permissions: + # id-token: "write" + # contents: "read" + # steps: + # - uses: actions/checkout@v4 + # - name: Restore Github cache artifacts + # uses: actions/cache/restore@v3 + # with: + # path: nix-installer + # key: aarch64-linux-artifacts-${{ github.sha }} + # - name: Move & set executable + # run: | + # mkdir install-root + # cp nix-installer.sh install-root/nix-installer.sh + # mv ./nix-installer install-root/nix-installer-aarch64-linux + # chmod +x install-root/nix-installer-aarch64-linux install-root/nix-installer.sh + # - run: sudo apt install -y fish zsh + # - name: Initial install + # uses: DeterminateSystems/nix-installer-action@main + # with: + # backtrace: full + # determinate: ${{ matrix.determinate }} + # local-root: install-root/ + # log-directives: nix_installer=debug + # logger: pretty + # - name: "Validate dnixd is ${{ matrix.determinate && 'installed' || 'uninstalled' }}" + # run: | + # if test -x /usr/local/bin/determinate-nixd; then + # echo "determinate-nixd is present" + # ${{ matrix.determinate }} + # else + # echo "determinate-nixd is not present" + # ${{ !matrix.determinate }} + # 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=debug + # RUST_BACKTRACE: full + # - name: Ensure `nix` is removed + # run: | + # if systemctl is-active nix-daemon.socket; then + # echo "nix-daemon.socket was still running" + # exit 1 + # fi + # if systemctl is-active nix-daemon.service; then + # echo "nix-daemon.service was still running" + # exit 1 + # fi + # if [ -e /nix ]; then + # echo "/nix exists" + # exit 1 + # fi + # - name: Repeated install + # uses: DeterminateSystems/nix-installer-action@main + # with: + # backtrace: full + # determinate: ${{ matrix.determinate }} + # local-root: install-root/ + # log-directives: nix_installer=debug + # logger: pretty + # - name: echo $PATH + # run: echo $PATH + # - name: Test `nix` with `$GITHUB_PATH` + # if: success() || failure() + # run: | + # nix run nixpkgs#hello + # nix profile install nixpkgs#hello + # hello + # nix store gc + # nix run nixpkgs#hello + # - name: Test bash + # run: nix-instantiate -E 'builtins.currentTime' --eval + # if: success() || failure() + # shell: bash --login {0} + # - name: Test sh + # run: nix-instantiate -E 'builtins.currentTime' --eval + # if: success() || failure() + # shell: sh -l {0} + # - name: Test zsh + # run: nix-instantiate -E 'builtins.currentTime' --eval + # if: success() || failure() + # shell: zsh --login --interactive {0} + # - name: Test fish + # run: 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=debug + # RUST_BACKTRACE: full + # - name: Ensure `nix` is removed + # run: | + # if systemctl is-active nix-daemon.socket; then + # echo "nix-daemon.socket was still running" + # exit 1 + # fi + # if systemctl is-active nix-daemon.service; then + # echo "nix-daemon.service was still running" + # exit 1 + # fi + # if [ -e /nix ]; then + # echo "/nix exists" + # exit 1 + # fi + + run-aarch64-darwin: + name: Run aarch64 Darwin${{ matrix.determinate && ' (--determinate)' || ''}} + runs-on: macos-latest + needs: [lints, build-aarch64-darwin] + strategy: + matrix: + determinate: [true, false] + permissions: + id-token: "write" + contents: "read" + steps: + - uses: actions/checkout@v4 + - name: Restore Github cache artifacts + uses: actions/cache/restore@v3 + with: + path: nix-installer + key: aarch64-darwin-artifacts-${{ github.sha }} + - name: Move & set executable + run: | + mkdir install-root + cp nix-installer.sh install-root/nix-installer.sh + mv ./nix-installer install-root/nix-installer-aarch64-darwin + chmod +x install-root/nix-installer-aarch64-darwin install-root/nix-installer.sh + - run: brew install fish coreutils + - name: Initial install + uses: DeterminateSystems/nix-installer-action@main + with: + backtrace: full + determinate: ${{ matrix.determinate }} + local-root: install-root/ + log-directives: nix_installer=debug + logger: pretty + - name: "Validate dnixd is ${{ matrix.determinate && 'installed' || 'uninstalled' }}" + run: | + if test -x /usr/local/bin/determinate-nixd; then + echo "determinate-nixd is present" + ${{ matrix.determinate }} + else + echo "determinate-nixd is not present" + ${{ !matrix.determinate }} + 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=debug + RUST_BACKTRACE: full + - name: Repeated install + uses: DeterminateSystems/nix-installer-action@main + with: + backtrace: full + determinate: ${{ matrix.determinate }} + local-root: install-root/ + log-directives: nix_installer=debug + logger: pretty + - name: echo $PATH + run: echo $PATH + - name: Test `nix` with `$GITHUB_PATH` + if: success() || failure() + run: | + nix run nixpkgs#hello + nix profile install nixpkgs#hello + hello + nix store gc + nix run nixpkgs#hello + # NOTE(cole-h): GHA pushed a weird image that breaks this test for whatever reason, so ignore + # the failure for now + - name: Test bash + run: nix-instantiate -E 'builtins.currentTime' --eval || true + if: success() || failure() + shell: bash --login {0} + - name: Test sh + run: nix-instantiate -E 'builtins.currentTime' --eval + if: success() || failure() + shell: sh -l {0} + - name: Test zsh + run: nix-instantiate -E 'builtins.currentTime' --eval + if: success() || failure() + shell: zsh --login --interactive {0} + - name: Test fish + run: 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=debug + RUST_BACKTRACE: full + + run-x86_64-linux-release-checks: + name: Run x86_64 Linux release checks + runs-on: ubuntu-latest + needs: [lints, build-x86_64-linux] + if: contains(github.ref, 'release-') || contains(github.head_ref, 'release-') + permissions: + id-token: "write" + contents: "read" + steps: + - uses: actions/checkout@v4 + - name: Restore Github cache artifacts + uses: actions/cache/restore@v3 + with: + path: nix-installer + key: x86_64-linux-artifacts-${{ github.sha }} + - name: Move & set executable + run: | + mkdir install-root + cp nix-installer.sh install-root/nix-installer.sh + mv ./nix-installer install-root/nix-installer-x86_64-linux + chmod +x install-root/nix-installer-x86_64-linux install-root/nix-installer.sh + - name: Initial install + uses: DeterminateSystems/nix-installer-action@main + with: + backtrace: full + determinate: true + local-root: install-root/ + log-directives: nix_installer=debug + logger: pretty + - uses: DeterminateSystems/magic-nix-cache-action@main + with: + use-gha-cache: false + + - run: nix flake check -L + - run: | + nix build \ + -L --tarball-ttl 0 --keep-going \ + .#hydraJobs.container-test.all.x86_64-linux.all \ + .#hydraJobs.vm-test.all.x86_64-linux.all diff --git a/.github/workflows/release-branches.yml b/.github/workflows/release-branches.yml index d6ecb24ab..176ecf5e8 100644 --- a/.github/workflows/release-branches.yml +++ b/.github/workflows/release-branches.yml @@ -8,7 +8,7 @@ on: branches: # NOTE: make sure any branches here are also valid directory names, # otherwise creating the directory and uploading to s3 will fail - - 'main' + - "main" permissions: id-token: "write" @@ -19,10 +19,6 @@ jobs: uses: ./.github/workflows/build-x86_64-linux.yml with: cache-key: release-x86_64-linux-artifacts-${{ github.sha }} - build-i686-linux: - uses: ./.github/workflows/build-i686-linux.yml - with: - cache-key: release-i686-linux-artifacts-${{ github.sha }} build-aarch64-linux: uses: ./.github/workflows/build-aarch64-linux.yml with: @@ -42,13 +38,12 @@ jobs: id-token: write # In order to request a JWT for AWS auth needs: - build-x86_64-linux - - build-i686-linux - build-aarch64-linux - build-x86_64-darwin - build-aarch64-darwin steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Create artifacts directory run: mkdir -p ./artifacts @@ -60,14 +55,6 @@ jobs: - name: Move artifact to artifacts directory run: mv ./nix-installer ./artifacts/nix-installer-x86_64-linux - - name: Fetch cached i686-linux binary - uses: actions/cache/restore@v3 - with: - path: nix-installer - key: release-i686-linux-artifacts-${{ github.sha }} - - name: Move artifact to artifacts directory - run: mv ./nix-installer ./artifacts/nix-installer-i686-linux - - name: Fetch cached aarch64-linux binary uses: actions/cache/restore@v3 with: diff --git a/.github/workflows/release-prs.yml b/.github/workflows/release-prs.yml index 895a06a0f..83c1b7ba8 100644 --- a/.github/workflows/release-prs.yml +++ b/.github/workflows/release-prs.yml @@ -29,19 +29,6 @@ jobs: uses: ./.github/workflows/build-x86_64-linux.yml with: cache-key: release-x86_64-linux-artifacts-${{ github.sha }} - build-i686-linux: - # Only intra-repo PRs are allowed to have PR artifacts uploaded - # We only want to trigger once the upload once in the case the upload label is added, not when any label is added - if: | - always() && !failure() && !cancelled() - && github.event.pull_request.head.repo.full_name == 'DeterminateSystems/nix-installer' - && ( - (github.event.action == 'labeled' && github.event.label.name == 'upload to s3') - || (github.event.action != 'labeled' && contains(github.event.pull_request.labels.*.name, 'upload to s3')) - ) - uses: ./.github/workflows/build-i686-linux.yml - with: - cache-key: release-i686-linux-artifacts-${{ github.sha }} build-aarch64-linux: # Only intra-repo PRs are allowed to have PR artifacts uploaded # We only want to trigger once the upload once in the case the upload label is added, not when any label is added @@ -95,13 +82,12 @@ jobs: runs-on: ubuntu-latest needs: - build-x86_64-linux - - build-i686-linux - build-aarch64-linux - build-x86_64-darwin - build-aarch64-darwin steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Create artifacts directory run: mkdir -p ./artifacts @@ -113,14 +99,6 @@ jobs: - name: Move artifact to artifacts directory run: mv ./nix-installer ./artifacts/nix-installer-x86_64-linux - - name: Fetch cached i686-linux binary - uses: actions/cache/restore@v3 - with: - path: nix-installer - key: release-i686-linux-artifacts-${{ github.sha }} - - name: Move artifact to artifacts directory - run: mv ./nix-installer ./artifacts/nix-installer-i686-linux - - name: Fetch cached aarch64-linux binary uses: actions/cache/restore@v3 with: diff --git a/.github/workflows/release-tags.yml b/.github/workflows/release-tags.yml index f42550519..ae348bd30 100644 --- a/.github/workflows/release-tags.yml +++ b/.github/workflows/release-tags.yml @@ -17,10 +17,6 @@ jobs: uses: ./.github/workflows/build-x86_64-linux.yml with: cache-key: release-x86_64-linux-artifacts-${{ github.sha }} - build-i686-linux: - uses: ./.github/workflows/build-i686-linux.yml - with: - cache-key: release-i686-linux-artifacts-${{ github.sha }} build-aarch64-linux: uses: ./.github/workflows/build-aarch64-linux.yml with: @@ -38,13 +34,12 @@ jobs: runs-on: ubuntu-latest needs: - build-x86_64-linux - - build-i686-linux - build-aarch64-linux - build-x86_64-darwin - build-aarch64-darwin steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Create artifacts directory run: mkdir -p ./artifacts @@ -56,14 +51,6 @@ jobs: - name: Move artifact to artifacts directory run: mv ./nix-installer ./artifacts/nix-installer-x86_64-linux - - name: Fetch cached i686-linux binary - uses: actions/cache/restore@v3 - with: - path: nix-installer - key: release-i686-linux-artifacts-${{ github.sha }} - - name: Move artifact to artifacts directory - run: mv ./nix-installer ./artifacts/nix-installer-i686-linux - - name: Fetch cached aarch64-linux binary uses: actions/cache/restore@v3 with: diff --git a/.github/workflows/update.yml b/.github/workflows/update.yml index 0b52e0d08..dcd4cc102 100644 --- a/.github/workflows/update.yml +++ b/.github/workflows/update.yml @@ -12,9 +12,11 @@ jobs: contents: "read" steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Nix uses: DeterminateSystems/nix-installer-action@main + with: + determinate: false - name: Enable Magic Nix Cache uses: DeterminateSystems/magic-nix-cache-action@main with: diff --git a/.gitignore b/.gitignore index bbcb97819..58d542ed0 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,4 @@ .ci-store .direnv result* -src/action/linux/selinux/nix.mod \ No newline at end of file +src/action/linux/selinux/nix.mod diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bfbb99d74..c6c25e2e8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,7 +15,6 @@ contribution to this repo is appropriate. Some snippets or workflows for development. - ## Direnv support While `nix develop` should work perfectly fine for development, contributors may prefer to enable [`direnv`](https://direnv.net/) or [`nix-direnv`](https://github.com/nix-community/nix-direnv) support. @@ -28,9 +27,8 @@ direnv allow If using an editor, it may be preferable to adopt an addon to enter the environment: -* [`vim`](https://github.com/direnv/direnv.vim) -* [VSCode](https://marketplace.visualstudio.com/items?itemName=mkhl.direnv) - +- [`vim`](https://github.com/direnv/direnv.vim) +- [VSCode](https://marketplace.visualstudio.com/items?itemName=mkhl.direnv) ## Testing Installs @@ -46,11 +44,10 @@ When running such interactive tests, consider creating a snapshot of the VM righ In general, it's a good idea to test on the closest you can get to the desired target environment. For example, when testing the Steam Deck planner it's a good idea to run that test in a Steam Deck VM as described in detail in the planner. -
Adding a planner for specific hardware? -Please include an full guide on how to create the best known virtual testing environment for that device. +Please include an full guide on how to create the best known virtual testing environment for that device. **A link is not sufficient, it may break.** Please provide a full summary of steps to take, link to any original source and give them credit if it is appropriate. @@ -114,7 +111,7 @@ nix build github:NixOS/experimental-nix-installer/${BRANCH}#hydraJobs.vm-test.ub
Adding a distro? -Notice how `rhel-v7` has a `v7`, not just `7`? That's so the test output shows correctly, as Nix will interpret the first `-\d` (eg `-7`, `-123213`) as a version, and not show it in the output. +Notice how `rhel-v7` has a `v7`, not just `7`? That's so the test output shows correctly, as Nix will interpret the first `-\d` (eg `-7`, `-123213`) as a version, and not show it in the output. Using `v7` instead turns: @@ -146,7 +143,6 @@ installer-test-rhel-v7-install-default> Formatting './disk.qcow2', fmt=qcow2 clu ## 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`: @@ -182,7 +178,6 @@ git+file:///home/ana/git/determinatesystems/nix-installer To run all of the currently supported tests: - ```bash nix build .#hydraJobs.container-test.all.x86_64-linux.all -L -j 4 ``` @@ -204,7 +199,7 @@ nix build github:NixOS/experimental-nix-installer/${BRANCH}#hydraJobs.container-
Adding a distro? -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. +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: @@ -260,39 +255,32 @@ wsl --unregister nix-installer-test-ubuntu-jammy You can also remove your `$HOME/nix-installer-wsl-tests-temp` folder whenever you wish. - # Releases - This package uses [Semantic Versioning](https://semver.org/). When determining the version number for a new release refer to Semantic Versioning for guidance. You can use the `check-semver` command alias from within the development environment to validate your changes don't break semver. To cut a release: -* Ensure the `flake.lock`, `Cargo.lock`, and Rust dependencies are up-to-date with the following: - + `nix flake update --commit-lock-file` - + `cargo outdated --ignore-external-rel --aggressive` - + `cargo update --aggressive` - + Make a PR for for this and let it get merged separately -* Create a release branch from `main` (`git checkout -b release-v0.0.1`) -* Remove the `-unreleased` from the `version` field in `Cargo.toml`, `flake.nix`, and the fixture JSON files - + Release PRs should not contain any tangible code changes which require review -* Ensure the VM / container tests still pass with the following: - + `nix flake check -L` - + `nix build .#hydraJobs.container-test.all.x86_64-linux.all -L -j 6` - + `nix build .#hydraJobs.vm-test.all.x86_64-linux.all -L -j 6` -* Push the branch, create a PR ("Release v0.0.1") -* Once the PR tests pass and it has been reviewed, merge it -* `git pull` on the `main` branch -* Tag the release (`git tag v0.0.1`) -* Push the tag (`git push origin v0.0.1`) -* The CI should produce artifacts via Buildkite and create a "Draft" release containing them on GitHub - + This will take a bit, use this time to draft a changelog -* Review the draft release, test the artifacts in a VM -* Create a changelog following the format of last release -* Undraft the release -* Once you are certain the release is good, `cargo publish` it - + **Warning:** While you can re-release Github releases, it is not possible to do the same on `crates.io` -* Create a PR bumping the version up one minor in the `Cargo.toml`, `flake.nix`, and fixture JSON files, adding `-unreleased` at the end (`v0.0.2-unreleased`) +- Create a release branch from `main` (`git checkout -b release-v0.0.1`) + - Release PRs should not contain any installer-related changes which require review +- Ensure the `flake.lock`, `Cargo.lock`, and Rust dependencies are up-to-date with the following: + - `nix flake update --commit-lock-file` + - `cargo update --aggressive` + - `cargo outdated --ignore-external-rel --aggressive` +- Ensure the VM / container tests still pass with the following: + - NOTE: At time of writing, these are run in CI on release branches + - `nix flake check -L` + - `nix build .#hydraJobs.container-test.all.x86_64-linux.all -L -j 6` + - `nix build .#hydraJobs.vm-test.all.x86_64-linux.all -L -j 6` +- Push the branch, create a PR ("Release v0.0.1") +- Once the PR tests pass and it has been reviewed, merge it +- Checkout the `main` branch and `git pull` +- Prepare a draft release that creates the new tag on publish + - Create a changelog following the format of the last release +- Undraft the release +- CI will produce artifacts and upload them to the release +- Once you are certain the release is good, `cargo publish` it + - **Warning:** While you can re-release Github releases, it is not possible to do the same on `crates.io` # Who maintains `nix-installer` and why? diff --git a/Cargo.lock b/Cargo.lock index 945e25e12..605d640fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,9 +43,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.14" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", @@ -58,33 +58,33 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.3" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -92,9 +92,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.80" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", @@ -103,9 +103,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.2.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" @@ -122,12 +122,6 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - [[package]] name = "base64" version = "0.22.1" @@ -136,15 +130,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "bumpalo" @@ -170,18 +158,21 @@ dependencies = [ [[package]] name = "bytes" -version = "1.6.0" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" dependencies = [ "serde", ] [[package]] name = "cc" -version = "1.0.96" +version = "1.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "065a29261d53ba54260972629f9ca6bffa69bac13cd1fed61420f7fa68b9f8bd" +checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938" +dependencies = [ + "shlex", +] [[package]] name = "cfg-if" @@ -191,9 +182,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cfg_aliases" -version = "0.1.1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" @@ -205,14 +196,14 @@ dependencies = [ "iana-time-zone", "num-traits", "serde", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] name = "clap" -version = "4.5.4" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3" dependencies = [ "clap_builder", "clap_derive", @@ -220,23 +211,23 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.2" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b" dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim 0.11.1", + "strsim", ] [[package]] name = "clap_derive" -version = "4.5.4" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", "syn", @@ -244,9 +235,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "color-eyre" @@ -278,9 +269,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "core-foundation" @@ -294,15 +285,15 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "darling" -version = "0.20.8" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ "darling_core", "darling_macro", @@ -310,23 +301,23 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.8" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", - "strsim 0.10.0", + "strsim", "syn", ] [[package]] name = "darling_macro" -version = "0.20.8" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", @@ -352,16 +343,6 @@ dependencies = [ "dirs-sys", ] -[[package]] -name = "dirs-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - [[package]] name = "dirs-sys" version = "0.4.1" @@ -374,17 +355,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - [[package]] name = "dyn-clone" version = "1.0.17" @@ -393,17 +363,17 @@ checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" [[package]] name = "either" -version = "1.11.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "enum-as-inner" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" dependencies = [ - "heck 0.4.1", + "heck", "proc-macro2", "quote", "syn", @@ -417,18 +387,19 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "erased-serde" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b73807008a3c7f171cc40312f37d95ef0396e048b5848d775f54b1a4dd4a0d3" +checksum = "24e2389d65ab4fab27dc2a5de7b191e1f6617d1f1c8855c0dc569c94a4cbb18d" dependencies = [ "serde", + "typeid", ] [[package]] name = "errno" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", @@ -446,20 +417,20 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "filetime" -version = "0.2.23" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.4.1", - "windows-sys 0.52.0", + "libredox", + "windows-sys 0.59.0", ] [[package]] @@ -546,9 +517,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", @@ -579,12 +550,6 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - [[package]] name = "heck" version = "0.5.0" @@ -597,6 +562,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + [[package]] name = "hex" version = "0.4.3" @@ -618,29 +589,29 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ - "bytes 1.6.0", + "bytes 1.7.2", "fnv", "itoa", ] [[package]] name = "http-body" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ - "bytes 1.6.0", + "bytes 1.7.2", "http", ] [[package]] name = "http-body-util" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ - "bytes 1.6.0", - "futures-core", + "bytes 1.7.2", + "futures-util", "http", "http-body", "pin-project-lite", @@ -648,17 +619,17 @@ dependencies = [ [[package]] name = "httparse" -version = "1.8.0" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "hyper" -version = "1.3.1" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" dependencies = [ - "bytes 1.6.0", + "bytes 1.7.2", "futures-channel", "futures-util", "http", @@ -673,15 +644,16 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.26.0" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http", "hyper", "hyper-util", "rustls", + "rustls-native-certs", "rustls-pki-types", "tokio", "tokio-rustls", @@ -690,11 +662,11 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.3" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" dependencies = [ - "bytes 1.6.0", + "bytes 1.7.2", "futures-channel", "futures-util", "http", @@ -703,16 +675,15 @@ dependencies = [ "pin-project-lite", "socket2", "tokio", - "tower", "tower-service", "tracing", ] [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -766,9 +737,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.6" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", "hashbrown 0.14.5", @@ -792,17 +763,17 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" [[package]] name = "is-terminal" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" dependencies = [ - "hermit-abi", + "hermit-abi 0.4.0", "libc", "windows-sys 0.52.0", ] @@ -815,9 +786,9 @@ checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" [[package]] name = "is_terminal_polyfill" -version = "1.70.0" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itoa" @@ -827,24 +798,24 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.154" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "libredox" @@ -852,21 +823,16 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.5.0", + "bitflags", "libc", + "redox_syscall", ] -[[package]] -name = "line-wrap" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd1bc4d24ad230d21fb898d1116b1801d7adfc449d42026475862ab48b11e70e" - [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lock_api" @@ -880,9 +846,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "lzma-sys" @@ -906,9 +872,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "mime" @@ -918,31 +884,32 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.11" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ + "hermit-abi 0.3.9", "libc", "wasi", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "nix" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.5.0", + "bitflags", "cfg-if", "cfg_aliases", "libc", @@ -954,29 +921,30 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "383d96c6f2c44fc706e7a523743434465d62db109b7c8364b642f35853475d67" dependencies = [ - "indexmap 2.2.6", + "indexmap 2.5.0", "serde", "thiserror", ] [[package]] name = "nix-installer" -version = "0.19.0" +version = "0.27.0" dependencies = [ "async-trait", - "bytes 1.6.0", + "bytes 1.7.2", "clap", "color-eyre", "dirs", "dyn-clone", "eyre", "glob", - "indexmap 2.2.6", + "indexmap 2.5.0", "is_ci", "nix", "nix-config-parser", + "once_cell", "os-release", - "owo-colors 4.0.0", + "owo-colors 4.1.0", "plist", "rand", "reqwest", @@ -1021,23 +989,13 @@ checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "object" version = "0.32.2" @@ -1049,9 +1007,12 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" +dependencies = [ + "portable-atomic", +] [[package]] name = "openssl-probe" @@ -1088,18 +1049,19 @@ checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" [[package]] name = "owo-colors" -version = "4.0.0" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f" +checksum = "fb37767f6569cd834a413442455e0f066d0d522de8630436e2a1761d9726ba56" dependencies = [ - "supports-color", + "supports-color 2.1.0", + "supports-color 3.0.1", ] [[package]] name = "parking_lot" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -1113,9 +1075,9 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.1", + "redox_syscall", "smallvec", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -1124,26 +1086,6 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" -[[package]] -name = "pin-project" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "pin-project-lite" version = "0.2.14" @@ -1158,24 +1100,29 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "plist" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9d34169e64b3c7a80c8621a48adaf44e0cf62c78a9b25dd9dd35f1881a17cf9" +checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" dependencies = [ - "base64 0.21.7", - "indexmap 2.2.6", - "line-wrap", + "base64", + "indexmap 2.5.0", "quick-xml", "serde", "time", ] +[[package]] +name = "portable-atomic" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" + [[package]] name = "powerfmt" version = "0.2.0" @@ -1184,33 +1131,84 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "proc-macro2" -version = "1.0.81" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "quick-xml" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" +checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" dependencies = [ "memchr", ] +[[package]] +name = "quinn" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" +dependencies = [ + "bytes 1.7.2", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" +dependencies = [ + "bytes 1.7.2", + "rand", + "ring", + "rustc-hash", + "rustls", + "slab", + "thiserror", + "tinyvec", + "tracing", +] + +[[package]] +name = "quinn-udp" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b" +dependencies = [ + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.59.0", +] + [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -1247,27 +1245,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.5.1" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ - "bitflags 2.5.0", + "bitflags", ] [[package]] name = "redox_users" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom", "libredox", @@ -1276,14 +1265,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.4" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.6", - "regex-syntax 0.8.3", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", ] [[package]] @@ -1297,13 +1286,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.3", + "regex-syntax 0.8.5", ] [[package]] @@ -1314,18 +1303,18 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.4" +version = "0.12.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" +checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" dependencies = [ - "base64 0.22.1", - "bytes 1.6.0", + "base64", + "bytes 1.7.2", "futures-core", "futures-util", "http", @@ -1341,6 +1330,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "quinn", "rustls", "rustls-native-certs", "rustls-pemfile", @@ -1359,7 +1349,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "winreg", + "windows-registry", ] [[package]] @@ -1379,17 +1369,23 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc-hash" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ - "bitflags 2.5.0", + "bitflags", "errno", "libc", "linux-raw-sys", @@ -1398,11 +1394,11 @@ dependencies = [ [[package]] name = "rustls" -version = "0.22.4" +version = "0.23.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" dependencies = [ - "log", + "once_cell", "ring", "rustls-pki-types", "rustls-webpki", @@ -1412,9 +1408,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a" dependencies = [ "openssl-probe", "rustls-pemfile", @@ -1425,25 +1421,24 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64 0.22.1", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.5.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beb461507cee2c2ff151784c52762cf4d9ff6a61f3e80968600ed24fa837fa54" +checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" [[package]] name = "rustls-webpki" -version = "0.102.3" +version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3bce581c0dd41bce533ce695a1437fa16a7ab5ac3ccfa99fe1a620a7885eabf" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ "ring", "rustls-pki-types", @@ -1452,15 +1447,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "ryu" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "same-file" @@ -1473,11 +1468,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1488,11 +1483,11 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "2.10.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 1.3.2", + "bitflags", "core-foundation", "core-foundation-sys", "libc", @@ -1501,9 +1496,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.10.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" +checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" dependencies = [ "core-foundation-sys", "libc", @@ -1511,27 +1506,27 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" dependencies = [ "serde", ] [[package]] name = "serde" -version = "1.0.200" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.200" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", @@ -1540,11 +1535,12 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.116" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -1563,15 +1559,15 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.8.1" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20" +checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" dependencies = [ - "base64 0.22.1", + "base64", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.2.6", + "indexmap 2.5.0", "serde", "serde_derive", "serde_json", @@ -1581,9 +1577,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.8.1" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2" +checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350" dependencies = [ "darling", "proc-macro2", @@ -1600,6 +1596,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -1640,12 +1642,6 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - [[package]] name = "strsim" version = "0.11.1" @@ -1654,20 +1650,20 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" -version = "0.26.2" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" -version = "0.26.2" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ - "heck 0.4.1", + "heck", "proc-macro2", "quote", "rustversion", @@ -1676,9 +1672,9 @@ dependencies = [ [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "supports-color" @@ -1690,11 +1686,20 @@ dependencies = [ "is_ci", ] +[[package]] +name = "supports-color" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8775305acf21c96926c900ad056abeef436701108518cf890020387236ac5a77" +dependencies = [ + "is_ci", +] + [[package]] name = "syn" -version = "2.0.60" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -1703,17 +1708,20 @@ dependencies = [ [[package]] name = "sync_wrapper" -version = "0.1.2" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] [[package]] name = "sysctl" -version = "0.5.5" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7dddc5f0fee506baf8b9fdb989e242f17e4b11c61dfbb0635b705217199eea" +checksum = "01198a2debb237c62b6826ec7081082d951f46dbb64b0e8c7649a452230d1dfc" dependencies = [ - "bitflags 2.5.0", + "bitflags", "byteorder", "enum-as-inner", "libc", @@ -1723,9 +1731,9 @@ dependencies = [ [[package]] name = "tar" -version = "0.4.40" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb" +checksum = "4ff6c40d3aedb5e06b57c6f669ad17ab063dd1e63d977c6a88e7f4dfa4f04020" dependencies = [ "filetime", "libc", @@ -1734,47 +1742,47 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.14" +version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" -version = "3.10.1" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" dependencies = [ "cfg-if", "fastrand", + "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "term" -version = "0.7.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +checksum = "4df4175de05129f31b80458c6df371a15e7fc3fd367272e6bf938e5c351c7ea0" dependencies = [ - "dirs-next", - "rustversion", - "winapi", + "home", + "windows-sys 0.52.0", ] [[package]] name = "thiserror" -version = "1.0.59" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.59" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", @@ -1824,9 +1832,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -1839,22 +1847,21 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.37.0" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", - "bytes 1.6.0", + "bytes 1.7.2", "libc", "mio", - "num_cpus", "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", "tracing", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1870,9 +1877,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", @@ -1881,9 +1888,9 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.25.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ "rustls", "rustls-pki-types", @@ -1892,9 +1899,9 @@ dependencies = [ [[package]] name = "tokio-socks" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51165dfa029d2a65969413a6cc96f354b86b464498702f174a4efa13608fd8c0" +checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f" dependencies = [ "either", "futures-util", @@ -1904,45 +1911,22 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ - "bytes 1.6.0", + "bytes 1.7.2", "futures-core", "futures-sink", "pin-project-lite", "tokio", - "tracing", -] - -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "pin-project", - "pin-project-lite", - "tokio", - "tower-layer", - "tower-service", - "tracing", ] -[[package]] -name = "tower-layer" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" - [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" @@ -1950,7 +1934,6 @@ version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -2022,11 +2005,17 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "typeid" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e13db2e0ccd5e14a544e8a246ba2312cd25223f616442d7f2cb0e3db614236e" + [[package]] name = "typetag" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "661d18414ec032a49ece2d56eee03636e43c4e8d577047ab334c0ba892e29aaf" +checksum = "52ba3b6e86ffe0054b2c44f2d86407388b933b16cb0a70eea3929420db1d9bbe" dependencies = [ "erased-serde", "inventory", @@ -2037,9 +2026,9 @@ dependencies = [ [[package]] name = "typetag-impl" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac73887f47b9312552aa90ef477927ff014d63d1920ca8037c6c1951eab64bb1" +checksum = "70b20a22c42c8f1cd23ce5e34f165d4d37038f5b663ad20fb6adbdf029172483" dependencies = [ "proc-macro2", "quote", @@ -2054,15 +2043,15 @@ checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] @@ -2075,9 +2064,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", @@ -2087,15 +2076,15 @@ dependencies = [ [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" dependencies = [ "serde", ] @@ -2133,19 +2122,20 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", @@ -2158,9 +2148,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" dependencies = [ "cfg-if", "js-sys", @@ -2170,9 +2160,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2180,9 +2170,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", @@ -2193,15 +2183,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "wasm-streams" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +checksum = "4e072d4e72f700fb3443d8fe94a39315df013eef1104903cdb0a2abd322bbecd" dependencies = [ "futures-util", "js-sys", @@ -2212,9 +2202,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" dependencies = [ "js-sys", "wasm-bindgen", @@ -2222,9 +2212,9 @@ dependencies = [ [[package]] name = "which" -version = "6.0.1" +version = "6.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8211e4f58a2b2805adfbefbc07bab82958fc91e3836339b1ab7ae32465dce0d7" +checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f" dependencies = [ "either", "home", @@ -2250,11 +2240,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2269,7 +2259,37 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", ] [[package]] @@ -2287,7 +2307,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -2307,18 +2336,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -2329,9 +2358,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -2341,9 +2370,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -2353,15 +2382,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -2371,9 +2400,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -2383,9 +2412,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -2395,9 +2424,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -2407,19 +2436,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" - -[[package]] -name = "winreg" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winsafe" @@ -2449,8 +2468,29 @@ dependencies = [ "tokio-io", ] +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml index 1123b3498..3304b9820 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "nix-installer" description = "Experimental Nix Installer" -version = "0.19.0" +version = "0.27.0" edition = "2021" resolver = "2" license = "LGPL-2.1" @@ -10,6 +10,7 @@ repository = "https://github.com/NixOS/experimental-nix-installer" [features] default = ["cli", "nix-community"] nix-community = [] +determinate-nix = [] cli = ["eyre", "color-eyre", "clap", "tracing-subscriber", "tracing-error"] diagnostics = ["is_ci"] @@ -24,37 +25,38 @@ clap = { version = "4", features = ["std", "color", "usage", "help", "error-cont color-eyre = { version = "0.6.2", default-features = false, features = [ "track-caller", "issue-url", "tracing-error", "capture-spantrace", "color-spantrace" ], optional = true } eyre = { version = "0.6.8", default-features = false, features = [ "track-caller" ], optional = true } glob = { version = "0.3.0", default-features = false } -nix = { version = "0.28.0", default-features = false, features = ["user", "fs", "process", "term"] } +nix = { version = "0.29.0", default-features = false, features = ["user", "fs", "process", "term"] } owo-colors = { version = "4.0.0", default-features = false, features = [ "supports-colors" ] } reqwest = { version = "0.12.4", default-features = false, features = ["rustls-tls-native-roots", "stream", "socks"] } -serde = { version = "1.0.200", default-features = false, features = [ "std", "derive" ] } -serde_json = { version = "1.0.116", default-features = false, features = [ "std" ] } +serde = { version = "1.0.203", default-features = false, features = [ "std", "derive" ] } +serde_json = { version = "1.0.120", default-features = false, features = [ "std" ] } serde_with = { version = "3", default-features = false, features = [ "std", "macros" ] } tar = { version = "0.4.38", default-features = false, features = [ "xattr" ] } target-lexicon = { version = "0.12.4", default-features = false, features = [ "std" ] } -thiserror = { version = "1.0.59", default-features = false } +thiserror = { version = "1.0.61", default-features = false } tokio = { version = "1.21.0", default-features = false, features = ["time", "io-std", "process", "fs", "signal", "tracing", "rt-multi-thread", "macros", "io-util", "parking_lot" ] } tracing = { version = "0.1.36", default-features = false, features = [ "std", "attributes" ] } tracing-error = { version = "0.2.0", default-features = false, optional = true, features = ["traced-error"] } tracing-subscriber = { version = "0.3.15", default-features = false, features = [ "std", "registry", "fmt", "json", "ansi", "env-filter" ], optional = true } url = { version = "2.3.1", default-features = false, features = ["serde"] } xz2 = { version = "0.1.7", default-features = false, features = ["static", "tokio"] } -plist = { version = "1.3.1", default-features = false, features = [ "serde" ]} +plist = { version = "1.7.0", default-features = false, features = [ "serde" ]} dirs = { version = "5.0.0", default-features = false } -typetag = { version = "0.2.3", default-features = false } +typetag = { version = "0.2.17", default-features = false } dyn-clone = { version = "1.0.9", default-features = false } rand = { version = "0.8.5", default-features = false, features = [ "std", "std_rng" ] } -semver = { version = "1.0.14", default-features = false, features = ["serde", "std"] } -term = { version = "0.7.0", default-features = false } +semver = { version = "1.0.23", default-features = false, features = ["serde", "std"] } +term = { version = "1.0.0", default-features = false } uuid = { version = "1.2.2", features = ["serde"] } os-release = { version = "0.1.0", default-features = false } is_ci = { version = "1.1.1", default-features = false, optional = true } strum = { version = "0.26.1", features = ["derive"] } nix-config-parser = { version = "0.2", features = ["serde"] } which = "6.0.0" -sysctl = "0.5.4" +sysctl = "0.6.0" walkdir = "2.3.3" indexmap = { version = "2.0.2", features = ["serde"] } +once_cell = "1.19.0" [dev-dependencies] eyre = { version = "0.6.8", default-features = false, features = [ "track-caller" ] } @@ -64,3 +66,12 @@ tempfile = "3.3.0" strip = true # Automatically strip symbols from the binary. opt-level = "z" # Optimize for size. lto = true + +[package.metadata.docs.rs] +# NOTE(cole-h): We embed the Nix closure by reading from this environment variable, but this is not +# available in docs.rs's build. It doesn't influence docs, so it's fine to set it to a bogus (but +# existing-file) value. +cargo-args = [ + "--config", "env.NIX_INSTALLER_TARBALL_PATH=\"../Cargo.toml\"", + "--config", "env.DETERMINATE_NIXD_BINARY_PATH=\"../Cargo.toml\"" +] diff --git a/LICENSE b/LICENSE index 5ab7695ab..602bfc946 100644 --- a/LICENSE +++ b/LICENSE @@ -146,7 +146,7 @@ such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. - + 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an diff --git a/README.md b/README.md index 8025628fe..9aaa51edd 100644 --- a/README.md +++ b/README.md @@ -11,122 +11,124 @@ If you used the **Determinate Nix Installer**, report issues at https://github.c --- [![Crates.io](https://img.shields.io/crates/v/nix-installer)](https://crates.io/crates/nix-installer) -[![Docs.rs](https://img.shields.io/docsrs/nix-installer)](https://docs.rs/nix-installer/latest/nix_installer/) +[![Docs.rs](https://img.shields.io/docsrs/nix-installer)](https://docs.rs/nix-installer/latest/nix_installer) -A fast, friendly, and reliable tool to help you use [Nix] with Flakes everywhere. +**Determinate Nix Installer** is a fast, friendly, and reliable way to install and manage [Nix] everywhere, including macOS, Linux, Windows Subsystem for Linux (WSL), SELinux, the Valve Steam Deck, and more. +It installs Nix with [flakes] enabled by default, it offers support for seamlessly [uninstalling Nix](#uninstalling), it enables Nix to survive [macOS upgrades][macos-upgrades], and [much more](#features). +This one-liner is the quickest way to get started on any supported system: -```bash -curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install +```shell +curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | \ + sh -s -- install ``` -The `nix-installer` has successfully completed over 2,000,000 installs in a number of environments, including [Github Actions](#as-a-github-action) and [GitLab](#on-gitlab): +> [!TIP] +> To install [Determinate] using the installer, see the instructions [below](#install-determinate). -| Platform | Multi User | `root` only | Maturity | -|------------------------------|:------------------:|:-----------:|:-----------------:| -| Linux (x86_64 & aarch64) | ✓ (via [systemd]) | ✓ | Stable | -| MacOS (x86_64 & aarch64) | ✓ | | Stable (See note) | -| Valve Steam Deck (SteamOS) | ✓ | | Stable | -| WSL2 (x86_64 & aarch64) | ✓ (via [systemd]) | ✓ | Stable | -| Podman Linux Containers | ✓ (via [systemd]) | ✓ | Stable | -| Docker Containers | | ✓ | Stable | -| Linux (i686) | ✓ (via [systemd]) | ✓ | Unstable | +Determinate Nix Installer has successfully completed over **7 million** installs in a number of environments, including [Github Actions](#as-a-github-action) and [GitLab](#on-gitlab): -> **Note** -> On **MacOS only**, removing users and/or groups may fail if there are no users who are logged in graphically. +| Platform | Multi user? | `root` only | Maturity | +| -------------------------------------------------------------------- | :---------------: | :---------: | :---------------: | +| Linux (`x86_64` and `aarch64`) | ✓ (via [systemd]) | ✓ | Stable | +| MacOS (`x86_64` and `aarch64`) | ✓ | | Stable (see note) | +| [Valve Steam Deck][steam-deck] (SteamOS) | ✓ | | Stable | +| [Windows Subsystem for Linux][wsl] 2 (WSL2) (`x86_64` and `aarch64`) | ✓ (via [systemd]) | ✓ | Stable | +| [Podman] Linux containers | ✓ (via [systemd]) | ✓ | Stable | +| [Docker] containers | | ✓ | Stable | +## Install Nix -## Usage +You can install Nix with the default [planner](#planners) and options by running this script: -Install Nix with the default planner and options: - -```bash -curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install +```shell +curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | \ + sh -s -- install ``` -Or, to download a platform specific Installer binary yourself: +To download a platform-specific installer binary yourself: -```bash -$ curl -sL -o nix-installer https://install.determinate.systems/nix/nix-installer-x86_64-linux -$ chmod +x nix-installer -$ ./nix-installer +```shell +curl -sL -o nix-installer https://install.determinate.systems/nix/nix-installer-x86_64-linux +chmod +x nix-installer +./nix-installer ``` -`nix-installer` installs Nix by following a *plan* made by a *planner*. Review the available planners: - -```bash -$ ./nix-installer install --help -Execute an install (possibly using an existing plan) +This would install Nix on an `x86_64-linux` system but you can replace that with the system of your choice. -To pass custom options, select a planner, for example `nix-installer install linux-multi --help` +### Install Determinate -Usage: nix-installer install [OPTIONS] [PLAN] - nix-installer install +If you're on macOS (but not [nix-darwin]) or Linux (but not [NixOS]), you can install [Determinate] using Determinate Nix Installer by adding the `--determinate` flag: -Commands: - linux - A planner for Linux installs - steam-deck - A planner suitable for the Valve Steam Deck running SteamOS - help - Print this message or the help of the given subcommand(s) -# ... +```shell +curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | \ + sh -s -- install --determinate ``` -Planners have their own options and defaults, sharing most of them in common: +> [!TIP] +> If you use [nix-darwin] or [NixOS], we recommend installing Determinate using modules provided by the [`determinate` flake][determinate-flake]. + +Determinate is: -```bash -$ ./nix-installer install linux --help -A planner for Linux installs +- [**Determinate Nix**][det-nix], [Determinate Systems][detsys]' validated and secure downstream Nix distribution for enterprises. +- [**FlakeHub**][flakehub], a platform for publishing and discovering [Nix flakes][flakes] that provides features like [semantic versioning][semver] (SemVer) for flakes, [private flakes][private-flakes], and [FlakeHub Cache][cache]. -Usage: nix-installer install linux [OPTIONS] +### Planners -Options: -# ... - --nix-build-group-name - The Nix build group name +Determinate Nix Installer installs Nix by following a _plan_ made by a _planner_. +To review the available planners: - [env: NIX_INSTALLER_NIX_BUILD_GROUP_NAME=] - [default: nixbld] +```shell +/nix/nix-installer install --help +``` - --nix-build-group-id - The Nix build group GID +Planners have their own options and defaults, sharing most of them in common. +To see the options for Linux, for example: - [env: NIX_INSTALLER_NIX_BUILD_GROUP_ID=] - [default: 3000] -# ... +```shell +/nix/nix-installer install linux --help ``` -Planners can be configured via environment variable or command arguments: +You can configure planners using environment variables or command arguments: + +```shell +curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | \ + NIX_BUILD_GROUP_NAME=nixbuilder sh -s -- install --nix-build-group-id 4000 + +# Alternatively: -```bash -$ 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... -$ NIX_BUILD_GROUP_NAME=nixbuilder ./nix-installer install linux-multi --nix-build-group-id 4000 +NIX_BUILD_GROUP_NAME=nixbuilder ./nix-installer install --nix-build-group-id 4000 ``` +See [Installer settings](#installer-settings) below for a full list of options. + +### Troubleshooting + +Having problems with the installer? +Consult our [troubleshooting guide](./docs/troubleshooting.md) to see if your problem is covered. + ### Upgrading Nix You can upgrade Nix to [our currently recommended version of Nix][recommended-nix] by running: -``` +```shell sudo -i nix upgrade-nix ``` -Alternatively, you can [uninstall](#uninstalling) and [reinstall](#usage) with a different version of the `nix-installer`. +Alternatively, you can [uninstall](#uninstalling) and [reinstall](#install-nix) with a different version of Determinate Nix Installer. ### Uninstalling -You can remove a `nix-installer`-installed Nix by running +You can remove Nix installed by Determinate Nix Installer by running: -```bash +```shell /nix/nix-installer uninstall ``` - ### As a Github Action -You can use the [`nix-installer-action`](https://github.com/DeterminateSystems/nix-installer-action) Github Action like so: +You can install Nix on [GitHub Actions][actions] using [`nix-installer-action`][nix-installer-action]. +Here's an example configuration: ```yaml on: @@ -135,60 +137,62 @@ on: branches: [main] jobs: - lints: + build: name: Build runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 - - name: Install Nix - uses: DeterminateSystems/nix-installer-action@main - - name: Run `nix build` - run: nix build . + - uses: actions/checkout@v4 + - name: Install Nix + uses: DeterminateSystems/nix-installer-action@main + - name: Run `nix build` + run: nix build . ``` ### On GitLab -GitLab CI runners are typically Docker based and run as the `root` user. This means `systemd` is not present, so the `--init none` option needs to be passed to the Linux planner. +[GitLab CI][gitlab-ci] runners are typically [Docker] based and run as the `root` user. +This means that `systemd` is not present, so you need to pass the `--init none` option to the Linux planner. -On the default [GitLab.com](https://gitlab.com/) runners, `nix` can be installed and used like so: +On the default [GitLab] runners, you can install Nix using this configuration: ```yaml test: script: - - curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install linux --no-confirm --init none - - . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh - - nix run nixpkgs#hello - - nix profile install nixpkgs#hello - - hello + - curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install linux --no-confirm --init none + - . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh + - nix run nixpkgs#hello + - nix profile install nixpkgs#hello + - hello ``` If you are using different runners, the above example may need to be adjusted. ### Without systemd (Linux only) -> **Warning** +> [!WARNING] > When `--init none` is used, _only_ `root` or users who can elevate to `root` privileges can run Nix: > -> ```bash +> ```shell > sudo -i nix run nixpkgs#hello > ``` If you don't use [systemd], you can still install Nix by explicitly specifying the `linux` plan and `--init none`: -```bash -curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install linux --init none +```shell +curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | \ + sh -s -- install linux --init none ``` ### In a container -In Docker/Podman containers or WSL2 instances where an init (like `systemd`) is not present, pass `--init none`. +In [Docker]/[Podman] containers or [WSL2][wsl] instances where an init (like `systemd`) is not present, pass `--init none`. For containers (without an init): -> **Warning** +> [!WARNING] > When `--init none` is used, _only_ `root` or users who can elevate to `root` privileges can run Nix: > -> ```bash +> ```shell > sudo -i nix run nixpkgs#hello > ``` @@ -205,7 +209,7 @@ ENV PATH="${PATH}:/nix/var/nix/profiles/default/bin" RUN nix run nixpkgs#hello ``` -```bash +```shell docker build -t ubuntu-with-nix . docker run --rm -ti ubuntu-with-nix docker rmi ubuntu-with-nix @@ -215,7 +219,7 @@ podman run --rm -ti ubuntu-with-nix podman rmi ubuntu-with-nix ``` -For containers with a systemd init: +For containers with a [systemd] init: ```dockerfile # Dockerfile @@ -231,7 +235,7 @@ RUN nix run nixpkgs#hello CMD [ "/bin/systemd" ] ``` -```bash +```shell podman build -t ubuntu-systemd-with-nix . IMAGE=$(podman create ubuntu-systemd-with-nix) CONTAINER=$(podman start $IMAGE) @@ -240,15 +244,16 @@ podman rm -f $CONTAINER podman rmi $IMAGE ``` -On some container tools, such as `docker`, `sandbox = false` can be omitted. Omitting it will negatively impact compatibility with container tools like `podman`. +With some container tools, such as [Docker], you can omit `sandbox = false`. +Omitting this will negatively impact compatibility with container tools like [Podman]. ### In WSL2 -We **strongly recommend** [enabling systemd](https://ubuntu.com/blog/ubuntu-wsl-enable-systemd), then installing Nix as normal: - +We **strongly recommend** first [enabling systemd][enabling-systemd] and then installing Nix as normal: -```bash -curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install +```shell +curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | \ + sh -s -- install ``` If [WSLg][wslg] is enabled, you can do things like open a Linux Firefox from Windows on Powershell: @@ -263,50 +268,75 @@ To use some OpenGL applications, you can use [`nixGL`][nixgl] (note that some ap wsl nix run --impure github:guibou/nixGL nix run nixpkgs#obs-studio ``` - If enabling systemd is not an option, pass `--init none` at the end of the command: -> **Warning** +> [!WARNING] > When `--init none` is used, _only_ `root` or users who can elevate to `root` privileges can run Nix: > -> ```bash +> ```shell > sudo -i nix run nixpkgs#hello > ``` - -```bash -curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install linux --init none +```shell +curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | \ + sh -s -- install linux --init none ``` ### Skip confirmation If you'd like to bypass the confirmation step, you can apply the `--no-confirm` flag: -```bash -curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install --no-confirm +```shell +curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | \ + sh -s -- install --no-confirm ``` This is especially useful when using the installer in non-interactive scripts. +## Features + +Existing Nix installation scripts do a good job but they are difficult to maintain. + +Subtle differences in the shell implementations and tool used in the scripts make it difficult to make meaningful changes to the installer. + +Determinate Nix installer has numerous advantages over these options: + +- It installs Nix with [flakes] enabled by default +- It enables Nix to survive macOS upgrades +- It keeps an installation _receipt_ for easy [uninstallation](#uninstalling) +- It uses [planners](#planners) to create appropriate install plans for complicated targets—plans that you can review prior to installation +- It enables you to perform a best-effort reversion in the facing of a failed install +- It improves installation performance by maximizing parallel operations +- It supports an expanded test suite including "curing" cases (compatibility with Nix already on the system) +- It supports SELinux and OSTree-based distributions without asking users to make compromises +- It operates as a single, static binary with external dependencies such as [OpenSSL], only calling existing system tools (like `useradd`) when necessary +- As a macOS remote build target, it ensures that Nix is present on the `PATH` + +## Nix community involvement + +It has been wonderful to collaborate with other participants in the [Nix Installer Working Group][wg] and members of the broader community. +The working group maintains a [foundation-owned fork of the installer][forked-installer]. ## Quirks -While `nix-installer` tries to provide a comprehensive and unquirky experience, there are unfortunately some issues which may require manual intervention or operator choices. +While Determinate Nix Installer tries to provide a comprehensive and unquirky experience, there are unfortunately some issues that may require manual intervention or operator choices. -### Using MacOS after removing `nix` while `nix-darwin` was still installed, network requests fail +### Using MacOS after removing Nix while nix-darwin was still installed, network requests fail -If `nix` was previously uninstalled without uninstalling `nix-darwin` first, users may experience errors similar to this: +If Nix was previously uninstalled without uninstalling [nix-darwin] first, you may experience errors similar to this: + +```shell +nix shell nixpkgs#curl -```bash -$ nix shell nixpkgs#curl error: unable to download 'https://cache.nixos.org/g8bqlgmpa4yg601w561qy2n576i6g0vh.narinfo': Problem with the SSL CA cert (path? access rights?) (77) ``` This occurs because `nix-darwin` provisions an `org.nixos.activate-system` service which remains after Nix is uninstalled. The `org.nixos.activate-system` service in this state interacts with the newly installed Nix and changes the SSL certificates it uses to be a broken symlink. -```bash -$ ls -lah /etc/ssl/certs +```shell +ls -lah /etc/ssl/certs + total 0 drwxr-xr-x 3 root wheel 96B Oct 17 08:26 . drwxr-xr-x 6 root wheel 192B Sep 16 06:28 .. @@ -317,24 +347,24 @@ The problem is compounded by the matter that the [`nix-darwin` uninstaller](http It's possible to resolve this situation by removing the `org.nixos.activate-system` service and the `ca-certificates`: -```bash -$ sudo rm /Library/LaunchDaemons/org.nixos.activate-system.plist -$ sudo launchctl bootout system/org.nixos.activate-system -$ /nix/nix-installer uninstall -$ sudo rm /etc/ssl/certs/ca-certificates.crt +```shell +sudo rm /Library/LaunchDaemons/org.nixos.activate-system.plist +sudo launchctl bootout system/org.nixos.activate-system +/nix/nix-installer uninstall +sudo rm /etc/ssl/certs/ca-certificates.crt ``` -Then run the `nix-installer` again, and it should work. +Run the installer again and it should work. -Up-to-date versions of the `nix-installer` will refuse to uninstall until `nix-darwin` is uninstalled first, helping mitigate this problem. +Up-to-date versions of the installer will refuse to uninstall until [nix-darwin] is uninstalled first, helping to mitigate this problem. ## 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 the installer to install Nix on systems without Nix, the default build is a static binary. -Build a portable Linux binary on a system with Nix: +To build a portable Linux binary on a system with Nix: -```bash +```shell # to build a local copy nix build -L ".#nix-installer-static" # to build the remote main development branch @@ -344,9 +374,9 @@ export NIX_INSTALLER_TAG="v0.6.0" nix build -L "github:NixOS/experimental-nix-installer/$NIX_INSTALLER_TAG#nix-installer-static" ``` -On Mac: +On macOS: -```bash +```shell # to build a local copy nix build -L ".#nix-installer" # to build the remote main development branch @@ -356,11 +386,10 @@ export NIX_INSTALLER_TAG="v0.6.0" nix build -L "github:NixOS/experimental-nix-installer/$NIX_INSTALLER_TAG#nix-installer" ``` -Then copy the `result/bin/nix-installer` to the machine you wish to run it on. - -You can also add `nix-installer` to a system without Nix via `cargo`, there are no system dependencies to worry about: +Then copy `result/bin/nix-installer` to the machine you wish to run it on. +You can also add the installer to a system without Nix using [cargo], as there are no system dependencies to worry about: -```bash +```shell # to build and run a local copy RUSTFLAGS="--cfg tokio_unstable" cargo run -- --help # to build the remote main development branch @@ -372,26 +401,27 @@ RUSTFLAGS="--cfg tokio_unstable" cargo install --git https://github.com/Determin nix-installer --help ``` -To make this build portable, pass ` --target x86_64-unknown-linux-musl`. +To make this build portable, pass the `--target x86_64-unknown-linux-musl` option. -> **Note** +> [!NOTE] > We currently require `--cfg 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. +## As a Rust library -## As a library - -> **Warning** -> Use as a library is still experimental. This feature is likely to be removed in the future without an advocate. If you're using this, please let us know and we can make a path to stabilization. +> [!WARNING] +> Using Determinate Nix Installer as a [Rust] library is still experimental. +> This feature is likely to be removed in the future without an advocate. +> If you're using this, please let us know and we can provide a path to stabilization. -Add `nix-installer` to your dependencies: +Add the [`nix-installer` library][lib] to your dependencies: -```bash +```shell cargo add nix-installer ``` -If you are **building a CLI**, check out the `cli` feature flag for `clap` integration. +If you're building a CLI, check out the `cli` feature flag for [`clap`][clap] integration. -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: +You'll also need to edit your `.cargo/config.toml` to use `tokio_unstable` as we utilize [Tokio's process groups][process-groups], which wrap stable `std` APIs, but are unstable due to it requiring an MSRV bump: ```toml # .cargo/config.toml @@ -404,74 +434,174 @@ The contents are embedded in the resulting binary instead of downloaded at insta Then it's possible to review the [documentation](https://docs.rs/nix-installer/latest/nix_installer/): -```bash +```shell cargo doc --open -p nix-installer ``` -Documentation is also available via `nix` build: +Documentation is also available via `nix build`: -```bash +```shell nix build github:NixOS/experimental-nix-installer#nix-installer.doc firefox result-doc/nix-installer/index.html ``` ## Accessing other versions -For users who desire version pinning, the version of `nix-installer` to use can be specified in the `curl` command: +You can pin to a specific version of Determinate Nix Installer by modifying the download URL. +Here's an example: -```bash +```shell VERSION="v0.6.0" -curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix/tag/${VERSION} | sh -s -- install +curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix/tag/${VERSION} | \ + sh -s -- install ``` -To discover which versions are available, or download the binaries for any release, check the [Github Releases](https://github.com/DeterminateSystems/nix-installer/releases). +To discover which versions are available, or download the binaries for any release, check the [Github Releases][releases]. -These releases can be downloaded and used directly: +You can download and use these releases directly. +Here's an example: -```bash +```shell VERSION="v0.6.0" ARCH="aarch64-linux" curl -sSf -L https://github.com/DeterminateSystems/nix-installer/releases/download/${VERSION}/nix-installer-${ARCH} -o nix-installer ./nix-installer install ``` -Each installer version has an [associated supported nix version](src/settings.rs) -- if you pin the installer version, you'll also indirectly pin to the associated nix version. +Each installer version has an [associated supported nix version](src/settings.rs)—if you pin the installer version, you'll also indirectly pin to the associated nix version. -You can also override the `nix` version via `--nix-package-url` or `NIX_INSTALLER_NIX_PACKAGE_URL=` but doing so is not recommended since we haven't tested that combination. -Here are some example `nix` package URLs including nix version, OS and architecture: +You can also override the Nix version using `--nix-package-url` or `NIX_INSTALLER_NIX_PACKAGE_URL=` but doing this is not recommended since we haven't tested that combination. +Here are some example Nix package URLs, including the Nix version, OS, and architecture: -* https://releases.nixos.org/nix/nix-2.18.1/nix-2.18.1-x86_64-linux.tar.xz -* https://releases.nixos.org/nix/nix-2.18.1/nix-2.18.1-aarch64-darwin.tar.xz +- https://releases.nixos.org/nix/nix-2.18.1/nix-2.18.1-x86_64-linux.tar.xz +- https://releases.nixos.org/nix/nix-2.18.1/nix-2.18.1-aarch64-darwin.tar.xz -## Installation Differences +## Installation differences -Differing from the upstream [Nix](https://github.com/NixOS/nix) installer scripts: +Differing from the upstream [Nix][upstream-nix] installer scripts: * an installation receipt (for uninstalling) is stored at `/nix/receipt.json` as well as a copy of the install binary at `/nix/nix-installer` * `ssl-cert-file` is set in `/etc/nix/nix.conf` if the `ssl-cert-file` argument is used. -## Motivations +## Installer settings -The existing upstream scripts do a good job, however they are difficult to maintain. +Determinate Nix Installer provides a variety of configuration settings, some [general](#general-settings) and some on a per-command basis. +All settings are available via flags or via `NIX_INSTALLER_*` environment variables. -Subtle differences in the shell implementations and tool used in the scripts make it difficult to make meaningful changes to the installer. +### General settings + +These settings are available for all commands. + +| Flag(s) | Description | Default (if any) | Environment variable | +| ------------------ | ------------------------------------------------------------------------- | ---------------- | ------------------------------ | +| `--log-directives` | Tracing directives delimited by comma | | `NIX_INSTALLER_LOG_DIRECTIVES` | +| `--logger` | Which logger to use (options are `compact`, `full`, `pretty`, and `json`) | `compact` | `NIX_INSTALLER_LOGGER` | +| `--verbose` | Enable debug logs, (`-vv` for trace) | `false` | `NIX_INSTALLER_VERBOSITY` | + +### Installation (`nix-installer install`) + +| Flag(s) | Description | Default (if any) | Environment variable | +| -------------------------- | -------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | -------------------------------------- | +| `--determinate` | Installs [Determinate] | `NIX_INSTALLER_DETERMINATE` | +| `--diagnostic-attribution` | Relate the install diagnostic to a specific value | | `NIX_INSTALLER_DIAGNOSTIC_ATTRIBUTION` | +| `--diagnostic-endpoint` | The URL or file path for an installation diagnostic to be sent | `https://install.determinate.systems/nix/diagnostic` | `NIX_INSTALLER_DIAGNOSTIC_ENDPOINT` | +| `--explain` | Provide an explanation of the changes the installation process will make to your system | `false` | `NIX_INSTALLER_EXPLAIN` | +| `--extra-conf` | Extra configuration lines for `/etc/nix.conf` | | `NIX_INSTALLER_EXTRA_CONF` | +| `--force` | Whether the installer should forcibly recreate files it finds existing | `false` | `NIX_INSTALLER_FORCE` | +| `--init` | Which init system to configure (if `--init none` Nix will be root-only) | `launchd` (macOS), `systemd` (Linux) | `NIX_INSTALLER_INIT` | +| `--nix-build-group-id` | The Nix build group GID | `350` (macOS), `30000` (Linux) | `NIX_INSTALLER_NIX_BUILD_GROUP_ID` | +| `--nix-build-group-name` | The Nix build group name | `nixbld` | `NIX_INSTALLER_NIX_BUILD_GROUP_NAME` | +| `--nix-build-user-count` | The number of build users to create | `32` | `NIX_INSTALLER_NIX_BUILD_USER_COUNT` | +| `--nix-build-user-id-base` | The Nix build user base UID (ascending) (NOTE: the first UID will be this base + 1) | `350` (macOS), `30000` (Linux) | `NIX_INSTALLER_NIX_BUILD_USER_ID_BASE` | +| `--nix-build-user-prefix` | The Nix build user prefix (user numbers will be postfixed) | `_nixbld` (macOS), `nixbld` (Linux) | `NIX_INSTALLER_NIX_BUILD_USER_PREFIX` | +| `--nix-package-url` | The Nix package URL | | `NIX_INSTALLER_NIX_PACKAGE_URL` | +| `--no-confirm` | Run installation without requiring explicit user confirmation | `false` | `NIX_INSTALLER_NO_CONFIRM` | +| `--no-modify-profile` | Modify the user profile to automatically load Nix. | `true` | `NIX_INSTALLER_MODIFY_PROFILE` | +| `--proxy` | The proxy to use (if any); valid proxy bases are `https://$URL`, `http://$URL` and `socks5://$URL` | | `NIX_INSTALLER_PROXY` | +| `--ssl-cert-file` | An SSL cert to use (if any); used for fetching Nix and sets `ssl-cert-file` in `/etc/nix/nix.conf` | | `NIX_INSTALLER_SSL_CERT_FILE` | +| `--no-start-daemon` | Start the daemon (if not `--init none`) | `true` | `NIX_INSTALLER_START_DAEMON` | + +You can also specify a planner with the first argument: + +```shell +nix-installer install +``` -The Determinate Nix installer has numerous advantages: +Alternatively, you can use the `NIX_INSTALLER_PLAN` environment variable: -* survives macOS upgrades -* keeping an installation receipt for easy uninstallation -* offering users a chance to review an accurate, calculated install plan -* having 'planners' which can create appropriate install plans for complicated targets -* offering users with a failing install the chance to do a best-effort revert -* improving performance by maximizing parallel operations -* supporting a expanded test suite including 'curing' cases -* supporting SELinux and OSTree based distributions without asking users to make compromises -* operating as a single, static binary with external dependencies such as `openssl`, only calling existing system tools (like `useradd`) where necessary -* As a MacOS remote build target, ensures `nix` is not absent from path +```shell +NIX_INSTALLER_PLAN= nix-installer install +``` + +### Uninstalling (`nix-installer uninstall`) + +| Flag(s) | Description | Default (if any) | Environment variable | +| -------------- | --------------------------------------------------------------------------------------- | ---------------- | -------------------------- | +| `--explain` | Provide an explanation of the changes the installation process will make to your system | `false` | `NIX_INSTALLER_EXPLAIN` | +| `--no-confirm` | Run installation without requiring explicit user confirmation | `false` | `NIX_INSTALLER_NO_CONFIRM` | + +You can also specify an installation receipt as the first argument (the default is `/nix/receipt.json`): + +```shell +nix-installer uninstall /path/to/receipt.json +``` -It has been wonderful to collaborate with other participants in the Nix Installer Working Group and members of the broader community. The working group maintains a [foundation owned fork of the installer](https://github.com/nixos/experimental-nix-installer/). +### Planning (`nix-installer plan`) +| Flag(s) | Description | Default (if any) | Environment variable | +| ------------ | -------------------------------------------------- | ---------------- | ----------------------------- | +| `--out-file` | Where to write the generated plan (in JSON format) | `/dev/stdout` | `NIX_INSTALLER_PLAN_OUT_FILE` | + +### Repairing (`nix-installer repair`) + +| Flag(s) | Description | Default (if any) | Environment variable | +| -------------- | ------------------------------------------------------------- | ---------------- | -------------------------- | +| `--no-confirm` | Run installation without requiring explicit user confirmation | `false` | `NIX_INSTALLER_NO_CONFIRM` | + +### Self-test (`nix-installer self-test`) + +`nix-installer self-test` only takes [general settings](#general-settings). ## Diagnostics By default, this fork of the Determinate Nix Installer does not compile support for diagnostics. + +[actions]: https://github.com/features/actions +[cache]: https://docs.determinate.systems/flakehub/cache +[cargo]: https://doc.rust-lang.org/cargo +[clap]: https://clap.rs +[det-nix]: https://docs.determinate.systems/determinate-nix +[determinate]: https://docs.determinate.systems +[determinate-flake]: https://github.com/DeterminateSystems/determinate +[detsys]: https://determinate.systems +[docker]: https://docker.com +[diagnosticdata]: https://github.com/DeterminateSystems/nix-installer/blob/f9f927840d532b71f41670382a30cfcbea2d8a35/src/diagnostics.rs#L29-L43 +[enabling-systemd]: https://devblogs.microsoft.com/commandline/systemd-support-is-now-available-in-wsl/#how-can-you-get-systemd-on-your-machine +[flakehub]: https://flakehub.com +[flakes]: https://zero-to-nix.com/concepts/flakes +[forked-installer]: https://github.com/nixos/experimental-nix-installer +[gitlab]: https://gitlab.com +[gitlab-ci]: https://docs.gitlab.com/ee/ci +[lib]: https://docs.rs/nix-installer +[macos-upgrades]: https://determinate.systems/posts/nix-survival-mode-on-macos/ +[nix]: https://nixos.org +[nix-darwin]: https://github.com/LnL7/nix-darwin +[nix-installer-action]: https://github.com/DeterminateSystems/nix-installer-action +[nixgl]: https://github.com/guibou/nixGL +[nixos]: https://zero-to-nix.com/concepts/nixos +[openssl]: https://openssl.org +[podman]: https://podman.io +[privacy]: https://determinate.systems/policies/privacy +[private-flakes]: https://docs.determinate.systems/flakehub/private-flakes +[process-groups]: https://docs.rs/tokio/1.24.1/tokio/process/struct.Command.html#method.process_group +[recommended-nix]: https://github.com/DeterminateSystems/nix/releases/latest +[releases]: https://github.com/DeterminateSystems/nix-installer/releases +[rust]: https://rust-lang.org +[selinux]: https://selinuxproject.org +[semver]: https://docs.determinate.systems/flakehub/concepts/semver +[steam-deck]: https://store.steampowered.com/steamdeck +[systemd]: https://systemd.io +[upstream-nix]: https://github.com/NixOS/nix +[wg]: https://discourse.nixos.org/t/nix-installer-workgroup/21495 +[wsl]: https://learn.microsoft.com/en-us/windows/wsl/about +[wslg]: https://github.com/microsoft/wslg diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md new file mode 100644 index 000000000..1855ec2b4 --- /dev/null +++ b/docs/troubleshooting.md @@ -0,0 +1,40 @@ +# Troubleshooting + +- [Your system can't find Nix](#your-system-cant-find-nix) + +## Your system can't find Nix + +### Issue + +You've run the installer but when you run any Nix command, like `nix --version`, and Nix isn't found: + +```shell +$ nix --version +bash: nix: command not found +``` + +### Likely problem + +Nix isn't currently on your `PATH`. + +### Potential solutions + +1. Initialize your Nix profile: + + ```shell + . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh + ``` + + This script sets up various environment variables that Nix needs to work. + The installer does prompt you to run this command when it's finished with installation but it's easy to miss or forget. + +2. Ensure that you're not overriding your existing `PATH` somewhere. + If you have a `bash_profile`, `zshrc`, or other file that modifies your `PATH`, make sure that it _appends_ to your `PATH` rather than setting it directly. + + ```bash + # Do this ✅ + PATH=$PATH${PATH:+:}path1:path2:path3 + + # Not this ❌ + PATH=path1:path2:path3 + ``` diff --git a/flake.lock b/flake.lock index 3bd948697..df8454f1a 100644 --- a/flake.lock +++ b/flake.lock @@ -1,5 +1,70 @@ { "nodes": { + "determinate": { + "inputs": { + "determinate-nixd-aarch64-darwin": "determinate-nixd-aarch64-darwin", + "determinate-nixd-aarch64-linux": "determinate-nixd-aarch64-linux", + "determinate-nixd-x86_64-darwin": [ + "determinate", + "determinate-nixd-aarch64-darwin" + ], + "determinate-nixd-x86_64-linux": "determinate-nixd-x86_64-linux", + "nix": [ + "nix" + ], + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1729548094, + "narHash": "sha256-+jP+Zlg0prpcmBy5s7cPUa7nJr90Zm2m933aibrHBYw=", + "rev": "5babe9d6a9eb52ee001bf70ad607fd66522f781b", + "revCount": 145, + "type": "tarball", + "url": "https://api.flakehub.com/f/pinned/DeterminateSystems/determinate/0.1.145%2Brev-5babe9d6a9eb52ee001bf70ad607fd66522f781b/0192b11b-c96e-7199-ba89-8c923541fcce/source.tar.gz" + }, + "original": { + "type": "tarball", + "url": "https://flakehub.com/f/DeterminateSystems/determinate/0.1.tar.gz" + } + }, + "determinate-nixd-aarch64-darwin": { + "flake": false, + "locked": { + "narHash": "sha256-OhG8joS/uN3Kdw4h9w8F/6ZIVTFZ8J9Fb4NGn/KK5/s=", + "type": "file", + "url": "https://install.determinate.systems/determinate-nixd/rev/51ecec5a3148baef87c2015536aa12dd18e4c4ad/macOS" + }, + "original": { + "type": "file", + "url": "https://install.determinate.systems/determinate-nixd/rev/51ecec5a3148baef87c2015536aa12dd18e4c4ad/macOS" + } + }, + "determinate-nixd-aarch64-linux": { + "flake": false, + "locked": { + "narHash": "sha256-AGcHQSIdb+KEJlhJzMB4YyFxbjdLZEDDf6bv6Zi3wqM=", + "type": "file", + "url": "https://install.determinate.systems/determinate-nixd/rev/51ecec5a3148baef87c2015536aa12dd18e4c4ad/aarch64-linux" + }, + "original": { + "type": "file", + "url": "https://install.determinate.systems/determinate-nixd/rev/51ecec5a3148baef87c2015536aa12dd18e4c4ad/aarch64-linux" + } + }, + "determinate-nixd-x86_64-linux": { + "flake": false, + "locked": { + "narHash": "sha256-kU4dqHoYe3sFf4LDAUj4fyl9uGV8IHtE22+DdMeRN0s=", + "type": "file", + "url": "https://install.determinate.systems/determinate-nixd/rev/51ecec5a3148baef87c2015536aa12dd18e4c4ad/x86_64-linux" + }, + "original": { + "type": "file", + "url": "https://install.determinate.systems/determinate-nixd/rev/51ecec5a3148baef87c2015536aa12dd18e4c4ad/x86_64-linux" + } + }, "fenix": { "inputs": { "nixpkgs": [ @@ -54,18 +119,71 @@ "type": "github" } }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": [ + "nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1719994518, + "narHash": "sha256-pQMhCCHyQGRzdfAkdJ4cIWiw+JNuWsTX7f0ZYSyz0VY=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "9227223f6d922fee3c7b190b2cc238a99527bbb7", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "git-hooks-nix": { + "inputs": { + "flake-compat": [ + "nix" + ], + "gitignore": [ + "nix" + ], + "nixpkgs": [ + "nix", + "nixpkgs" + ], + "nixpkgs-stable": [ + "nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1721042469, + "narHash": "sha256-6FPUl7HVtvRHCCBQne7Ylp4p+dpP3P/OYuzjztZ4s70=", + "owner": "cachix", + "repo": "git-hooks.nix", + "rev": "f451c19376071a90d8c58ab1a953c6e9840527fd", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "git-hooks.nix", + "type": "github" + } + }, "libgit2": { "flake": false, "locked": { - "lastModified": 1714600833, - "narHash": "sha256-8pBXgbDWVpVv07zQ9sXFxssH5sLGRqjY0Gh+QlKwfg8=", + "lastModified": 1715853528, + "narHash": "sha256-J2rCxTecyLbbDdsyBWn9w7r3pbKRMkI9E7RvRgAqBdY=", "owner": "libgit2", "repo": "libgit2", - "rev": "e5e233caedaa96d6bdb35a7e42b9028b6dfcf534", + "rev": "36f7e21ad757a3dacc58cf7944329da6bc1d6e96", "type": "github" }, "original": { "owner": "libgit2", + "ref": "v1.8.1", "repo": "libgit2", "type": "github" } @@ -77,11 +195,11 @@ ] }, "locked": { - "lastModified": 1713520724, - "narHash": "sha256-CO8MmVDmqZX2FovL75pu5BvwhW+Vugc7Q6ze7Hj8heI=", + "lastModified": 1721727458, + "narHash": "sha256-r/xppY958gmZ4oTfLiHN0ZGuQ+RSTijDblVgVLFi1mw=", "owner": "nix-community", "repo": "naersk", - "rev": "c5037590290c6c7dae2e42e7da1e247e54ed2d49", + "rev": "3fb418eaf352498f6b6c30592e3beb63df42ef11", "type": "github" }, "original": { @@ -93,38 +211,57 @@ "nix": { "inputs": { "flake-compat": "flake-compat_2", + "flake-parts": "flake-parts", + "git-hooks-nix": "git-hooks-nix", "libgit2": "libgit2", "nixpkgs": "nixpkgs", + "nixpkgs-23-11": "nixpkgs-23-11", "nixpkgs-regression": "nixpkgs-regression" }, "locked": { - "lastModified": 1712161137, - "narHash": "sha256-ObaVDDPtnOeIE0t7m4OVk5G+OS6d9qYh+ktK67Fe/zE=", + "lastModified": 1727436381, + "narHash": "sha256-OwJByTdCz1t91ysBqynK+ifszkoIGEXUn6HE2t82+c8=", "owner": "NixOS", "repo": "nix", - "rev": "355cbc482f33f5b07a6bc0d72be862b1ccdb99dd", + "rev": "048cfe51c9a4ae0722440ab5337626370c82a787", "type": "github" }, "original": { "owner": "NixOS", - "ref": "2.21.2", + "ref": "2.24.9", "repo": "nix", "type": "github" } }, "nixpkgs": { "locked": { - "lastModified": 1715106579, - "narHash": "sha256-gZMgKEGiK6YrwGBiccZ1gemiUwjsZ1Zv49KYOgmX2fY=", + "lastModified": 1723688146, + "narHash": "sha256-sqLwJcHYeWLOeP/XoLwAtYjr01TISlkOfz+NG82pbdg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "c3d4ac725177c030b1e289015989da2ad9d56af0", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-24.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-23-11": { + "locked": { + "lastModified": 1717159533, + "narHash": "sha256-oamiKNfr2MS6yH64rUn99mIZjc45nGJlj9eGth/3Xuw=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "8be0d8a1ed4f96d99b09aa616e2afd47acc3da89", + "rev": "a62e6edd6d5e1fa0329b8653c801147986f8d446", "type": "github" }, "original": { "owner": "NixOS", - "ref": "release-23.11", "repo": "nixpkgs", + "rev": "a62e6edd6d5e1fa0329b8653c801147986f8d446", "type": "github" } }, @@ -162,6 +299,7 @@ }, "root": { "inputs": { + "determinate": "determinate", "fenix": "fenix", "flake-compat": "flake-compat", "naersk": "naersk", diff --git a/flake.nix b/flake.nix index 7ef53ad31..9d44b4d2a 100644 --- a/flake.nix +++ b/flake.nix @@ -15,10 +15,23 @@ }; nix = { - url = "github:NixOS/nix/2.21.2"; + url = "github:NixOS/nix/2.24.9"; # Omitting `inputs.nixpkgs.follows = "nixpkgs";` on purpose }; + determinate = { + url = "https://flakehub.com/f/DeterminateSystems/determinate/0.1.tar.gz"; + + # We set the overrides below so the flake.lock has many fewer nodes. + # + # The `determinate` input is used to access the builds of `determinate-nixd`. + # Below, we access the `packages` outputs, which download static builds of `determinate-nixd` and makes them executable. + # The way we consume the determinate flake means the `nix` and `nixpkgs` inputs are not meaningfully used. + # This means `follows` won't cause surprisingly extensive rebuilds, just trivial `chmod +x` rebuilds. + inputs.nixpkgs.follows = "nixpkgs"; + inputs.nix.follows = "nix"; + }; + flake-compat.url = "github:edolstra/flake-compat/v1.0.0"; }; @@ -28,10 +41,12 @@ , fenix , naersk , nix + , determinate , ... } @ inputs: let - supportedSystems = [ "i686-linux" "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; + supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; + systemsSupportedByDeterminateNixd = [ ]; # avoid refs to detsys nixd for now forAllSystems = f: nixpkgs.lib.genAttrs supportedSystems (system: (forSystem system f)); @@ -50,18 +65,15 @@ stable.rust-src ] ++ nixpkgs.lib.optionals (system == "x86_64-linux") [ targets.x86_64-unknown-linux-musl.stable.rust-std - ] ++ nixpkgs.lib.optionals (system == "i686-linux") [ - targets.i686-unknown-linux-musl.stable.rust-std ] ++ nixpkgs.lib.optionals (system == "aarch64-linux") [ targets.aarch64-unknown-linux-musl.stable.rust-std ]); - nixTarballForSystem = system: - let - version = inputs.nix.packages.${system}.nix.version; - in - "${inputs.nix.hydraJobs.binaryTarball.${system}}/nix-${version}-${system}.tar.xz"; + nixTarballs = forAllSystems ({ system, ... }: + inputs.nix.tarballs_direct.${system} + or "${inputs.nix.checks."${system}".binaryTarball}/nix-${inputs.nix.packages."${system}".default.version}-${system}.tar.xz"); + optionalPathToDeterminateNixd = system: if builtins.elem system systemsSupportedByDeterminateNixd then "${inputs.determinate.packages.${system}.default}/bin/determinate-nixd" else null; in { overlays.default = final: prev: @@ -73,7 +85,7 @@ }; sharedAttrs = { pname = "nix-installer"; - version = "0.19.0"; + version = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package.version; src = builtins.path { name = "nix-installer-source"; path = self; @@ -94,7 +106,8 @@ RUSTFLAGS = "--cfg tokio_unstable"; cargoTestOptions = f: f ++ [ "--all" ]; - NIX_INSTALLER_TARBALL_PATH = nixTarballForSystem final.stdenv.system; + NIX_INSTALLER_TARBALL_PATH = nixTarballs.${final.stdenv.system}; + DETERMINATE_NIXD_BINARY_PATH = optionalPathToDeterminateNixd final.stdenv.system; override = { preBuild ? "", ... }: { preBuild = preBuild + '' @@ -107,6 +120,19 @@ }; in rec { + # NOTE(cole-h): fixes build -- nixpkgs updated libsepol to 3.7 but didn't update + # checkpolicy to 3.7, checkpolicy links against libsepol, and libsepol 3.7 changed + # something in the API so checkpolicy 3.6 failed to build against libsepol 3.7 + # Can be removed once https://github.com/NixOS/nixpkgs/pull/335146 merges. + checkpolicy = prev.checkpolicy.overrideAttrs ({ ... }: rec { + version = "3.7"; + + src = final.fetchurl { + url = "https://github.com/SELinuxProject/selinux/releases/download/${version}/checkpolicy-${version}.tar.gz"; + sha256 = "sha256-/T4ZJUd9SZRtERaThmGvRMH4bw1oFGb9nwLqoGACoH8="; + }; + }); + nix-installer = naerskLib.buildPackage sharedAttrs; } // nixpkgs.lib.optionalAttrs (prev.stdenv.system == "x86_64-linux") rec { default = nix-installer-static; @@ -114,12 +140,6 @@ (sharedAttrs // { CARGO_BUILD_TARGET = "x86_64-unknown-linux-musl"; }); - } // nixpkgs.lib.optionalAttrs (prev.stdenv.system == "i686-linux") rec { - default = nix-installer-static; - nix-installer-static = naerskLib.buildPackage - (sharedAttrs // { - CARGO_BUILD_TARGET = "i686-unknown-linux-musl"; - }); } // nixpkgs.lib.optionalAttrs (prev.stdenv.system == "aarch64-linux") rec { default = nix-installer-static; nix-installer-static = naerskLib.buildPackage @@ -139,15 +159,17 @@ name = "nix-install-shell"; RUST_SRC_PATH = "${toolchain}/lib/rustlib/src/rust/library"; - NIX_INSTALLER_TARBALL_PATH = nixTarballForSystem system; + NIX_INSTALLER_TARBALL_PATH = nixTarballs.${system}; + DETERMINATE_NIXD_BINARY_PATH = optionalPathToDeterminateNixd system; nativeBuildInputs = with pkgs; [ ]; buildInputs = with pkgs; [ toolchain + shellcheck rust-analyzer cargo-outdated cacert - cargo-audit + # cargo-audit # NOTE(cole-h): build currently broken because of time dependency and Rust 1.80 cargo-watch nixpkgs-fmt check.check-rustfmt @@ -156,6 +178,7 @@ check.check-editorconfig check.check-semver check.check-clippy + editorconfig-checker ] ++ lib.optionals (pkgs.stdenv.isDarwin) (with pkgs; [ libiconv @@ -204,9 +227,6 @@ } // nixpkgs.lib.optionalAttrs (system == "x86_64-linux") { inherit (pkgs) nix-installer-static; default = pkgs.nix-installer-static; - } // nixpkgs.lib.optionalAttrs (system == "i686-linux") { - inherit (pkgs) nix-installer-static; - default = pkgs.nix-installer-static; } // nixpkgs.lib.optionalAttrs (system == "aarch64-linux") { inherit (pkgs) nix-installer-static; default = pkgs.nix-installer-static; diff --git a/nix-installer.sh b/nix-installer.sh index e22e62197..97144a5df 100755 --- a/nix-installer.sh +++ b/nix-installer.sh @@ -1,7 +1,7 @@ #!/bin/sh # shellcheck shell=dash -# If you need an offline install, or you'd prefer to run the binary directly, head to +# If you need an offline install, or you'd prefer to run the binary directly, head to # https://github.com/DeterminateSystems/nix-installer/releases then pick the version and platform # most appropriate for your deployment target. # @@ -213,10 +213,6 @@ get_architecture() { _cputype=x86_64 ;; - i686) - _cputype=i686 - ;; - *) err "unknown CPU type: $_cputype" ;; @@ -290,8 +286,10 @@ downloader() { _ciphersuites="$RETVAL" if [ -n "$_ciphersuites" ]; then if [ -n "${NIX_INSTALLER_FORCE_ALLOW_HTTP-}" ]; then + # shellcheck disable=SC2086 # ignore because $_retry could be a flag (e.g. `--retry 5`) _err=$(curl $_retry --silent --show-error --fail --location "$1" --output "$2" 2>&1) else + # shellcheck disable=SC2086 # ignore because $_retry could be a flag (e.g. `--retry 5`) _err=$(curl $_retry --proto '=https' --tlsv1.2 --ciphers "$_ciphersuites" --silent --show-error --fail --location "$1" --output "$2" 2>&1) fi _status=$? @@ -299,9 +297,11 @@ downloader() { echo "Warning: Not enforcing strong cipher suites for TLS, this is potentially less secure" if ! check_help_for "$3" curl --proto --tlsv1.2; then echo "Warning: Not enforcing TLS v1.2, this is potentially less secure" + # shellcheck disable=SC2086 # ignore because $_retry could be a flag (e.g. `--retry 5`) _err=$(curl $_retry --silent --show-error --fail --location "$1" --output "$2" 2>&1) _status=$? else + # shellcheck disable=SC2086 # ignore because $_retry could be a flag (e.g. `--retry 5`) _err=$(curl $_retry --proto '=https' --tlsv1.2 --silent --show-error --fail --location "$1" --output "$2" 2>&1) _status=$? fi @@ -410,7 +410,6 @@ check_curl_for_retry_support() { fi RETVAL="$_retry_supported" - } # Return cipher suite string specified by user, otherwise return strong TLS 1.2-1.3 cipher suites diff --git a/nix/check.nix b/nix/check.nix index 1ca091821..05eb11960 100644 --- a/nix/check.nix +++ b/nix/check.nix @@ -36,9 +36,9 @@ in # EditorConfig check-editorconfig = (writeShellApplication { name = "check-editorconfig"; - runtimeInputs = with pkgs; [ eclint ]; + runtimeInputs = with pkgs; [ editorconfig-checker ]; text = '' - eclint . + editorconfig-checker ''; }); diff --git a/nix/tests/container-test/default.nix b/nix/tests/container-test/default.nix index 4e5f59057..73fd316f4 100644 --- a/nix/tests/container-test/default.nix +++ b/nix/tests/container-test/default.nix @@ -35,6 +35,37 @@ let system = "x86_64-linux"; }; + # Found via https://hub.docker.com/_/ubuntu/ under "How is the rootfs build?" + # Jammy (--determinate) + "ubuntu-v22_04-determinate" = { + tarball = builtins.fetchurl { + url = "http://cdimage.ubuntu.com/ubuntu-base/releases/22.04/release/ubuntu-base-22.04-base-amd64.tar.gz"; + sha256 = "01sbpjb32x1z1yr9q78zrk0a6kfw5c4fxw1jqmm23g8ixryffvyz"; + }; + tester = ./default/Dockerfile.determinate; + system = "x86_64-linux"; + }; + + # focal (--determinate) + "ubuntu-v20_04-determinate" = { + tarball = builtins.fetchurl { + url = "http://cdimage.ubuntu.com/ubuntu-base/releases/20.04/release/ubuntu-base-20.04.1-base-amd64.tar.gz"; + sha256 = "0ryn38csmx41a415g9b3wk30csaxxlkgkdij9v4754pk877wpxlp"; + }; + tester = ./default/Dockerfile.determinate; + system = "x86_64-linux"; + }; + + # bionic (--determinate) + "ubuntu-v18_04-determinate" = { + tarball = builtins.fetchurl { + url = "http://cdimage.ubuntu.com/ubuntu-base/releases/18.04/release/ubuntu-base-18.04.5-base-amd64.tar.gz"; + sha256 = "1sh73pqwgyzkyssv3ngpxa2ynnkbdvjpxdw1v9ql4ghjpd3hpwlg"; + }; + tester = ./default/Dockerfile.determinate; + system = "x86_64-linux"; + }; + }; makeTest = containerTool: imageName: @@ -47,6 +78,7 @@ let machine = { config, pkgs, ... }: { virtualisation.${containerTool}.enable = true; + virtualisation.diskSize = 4 * 1024; }; }; testScript = '' diff --git a/nix/tests/container-test/default/Dockerfile b/nix/tests/container-test/default/Dockerfile index fe2ba75dd..6ee694222 100644 --- a/nix/tests/container-test/default/Dockerfile +++ b/nix/tests/container-test/default/Dockerfile @@ -3,7 +3,7 @@ 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 --logger pretty --log-directive nix_installer=debug --nix-package-url file:///nix.tar.xz --init none --extra-conf "sandbox = false" --no-confirm -vvv +RUN /nix-installer/bin/nix-installer install linux --logger pretty --log-directive nix_installer=trace --nix-package-url file:///nix.tar.xz --init none --extra-conf "sandbox = false" --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"]; }' -RUN /nix/nix-installer uninstall --no-confirm \ No newline at end of file +RUN /nix/nix-installer uninstall --no-confirm diff --git a/nix/tests/container-test/default/Dockerfile.determinate b/nix/tests/container-test/default/Dockerfile.determinate new file mode 100644 index 000000000..cb45fe1cd --- /dev/null +++ b/nix/tests/container-test/default/Dockerfile.determinate @@ -0,0 +1,9 @@ +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 --logger pretty --log-directive nix_installer=trace --nix-package-url file:///nix.tar.xz --init none --extra-conf "sandbox = false" --determinate --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"]; }' +RUN /nix/nix-installer uninstall --no-confirm diff --git a/nix/tests/vm-test/default.nix b/nix/tests/vm-test/default.nix index 63eb2a543..3f3b2316b 100644 --- a/nix/tests/vm-test/default.nix +++ b/nix/tests/vm-test/default.nix @@ -4,12 +4,16 @@ let nix-installer-install = '' NIX_PATH=$(readlink -f nix.tar.xz) - RUST_BACKTRACE="full" ./nix-installer install --nix-package-url "file://$NIX_PATH" --no-confirm --logger pretty --log-directive nix_installer=info + RUST_BACKTRACE="full" ./nix-installer install --nix-package-url "file://$NIX_PATH" --no-confirm --logger pretty --log-directive nix_installer=trace ''; nix-installer-install-quiet = '' NIX_PATH=$(readlink -f nix.tar.xz) RUST_BACKTRACE="full" ./nix-installer install --nix-package-url "file://$NIX_PATH" --no-confirm ''; + nix-installer-install-determinate = '' + NIX_PATH=$(readlink -f nix.tar.xz) + RUST_BACKTRACE="full" ./nix-installer install --nix-package-url "file://$NIX_PATH" --no-confirm --logger pretty --log-directive nix_installer=trace --determinate + ''; cure-script-multi-user = '' tar xvf nix.tar.xz ./nix-*/install --no-channel-add --yes --daemon @@ -38,25 +42,25 @@ let fi if systemctl is-failed nix-daemon.socket; then echo "nix-daemon.socket is failed" - systemctl status nix-daemon.socket + sudo journalctl -eu nix-daemon.socket exit 1 fi if !(sudo systemctl start nix-daemon.service); then echo "nix-daemon.service failed to start" - systemctl status nix-daemon.service + sudo journalctl -eu nix-daemon.service exit 1 fi if systemctl is-failed nix-daemon.service; then echo "nix-daemon.service is failed" - systemctl status nix-daemon.service + sudo journalctl -eu nix-daemon.service exit 1 fi if !(sudo systemctl stop nix-daemon.service); then echo "nix-daemon.service failed to stop" - systemctl status nix-daemon.service + sudo journalctl -eu nix-daemon.service exit 1 fi @@ -188,6 +192,24 @@ let uninstall = installCases.install-default.uninstall; uninstallCheck = installCases.install-default.uninstallCheck; }; + install-determinate = { + install = nix-installer-install-determinate; + check = '' + if ! systemctl is-active determinate-nixd.socket; then + echo "determinate-nixd.socket is not active" + sudo journalctl -eu determinate-nixd.socket + exit 1 + fi + + if ! determinate-nixd status; then + echo "determinate-nixd is not working" + sudo journalctl -eu determinate-nixd.service + exit 1 + fi + '' + installCases.install-default.check; + uninstall = installCases.install-default.uninstall; + uninstallCheck = installCases.install-default.uninstallCheck; + }; }; cureSelfCases = { cure-self-linux-working = { @@ -475,6 +497,9 @@ let rootDisk = "box.img"; upstreamScriptsWork = false; # SELinux! system = "x86_64-linux"; + skip = [ + "install-determinate" # RHEL v7 has systemd 219 (2015-02-16); determinate-nixd requires at least 227 (2015-10-07) + ]; }; "rhel-v8" = { @@ -602,12 +627,15 @@ let makeTests = name: tests: builtins.mapAttrs (imageName: image: + let + doTests = builtins.removeAttrs tests (image.skip or [ ]); + in rec { ${image.system} = (builtins.mapAttrs (testName: test: makeTest imageName testName test ) - tests) // { + doTests) // { "${name}" = (with (forSystem "x86_64-linux" ({ system, pkgs, ... }: pkgs)); pkgs.releaseTools.aggregate { name = name; constituents = ( @@ -615,7 +643,7 @@ let (testName: test: makeTest imageName testName test ) - tests + doTests ); }); }; @@ -653,7 +681,7 @@ lib.recursiveUpdate joined-tests { all."x86_64-linux" = (with (forSystem "x86_64-linux" ({ system, pkgs, ... }: pkgs)); pkgs.lib.mapAttrs (caseName: case: pkgs.releaseTools.aggregate { name = caseName; - constituents = pkgs.lib.mapAttrsToList (name: value: value."x86_64-linux"."${caseName}") joined-tests; + constituents = pkgs.lib.mapAttrsToList (name: value: value."x86_64-linux"."${caseName}" or "") joined-tests; } )) (allCases // { "cure-self" = { }; "cure-script" = { }; "install" = { }; "uninstall" = { }; "all" = { }; }); } diff --git a/rust-toolchain.toml b/rust-toolchain.toml index ffa11971d..9993e9361 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] channel = "stable" -components = [ "rustfmt" ] \ No newline at end of file +components = [ "rustfmt" ] diff --git a/src/action/base/add_user_to_group.rs b/src/action/base/add_user_to_group.rs index 2b570d7ae..55e2dbc03 100644 --- a/src/action/base/add_user_to_group.rs +++ b/src/action/base/add_user_to_group.rs @@ -14,11 +14,12 @@ use crate::action::{Action, ActionDescription, StatefulAction}; Create an operating system level user in the given group */ #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(tag = "action_name", rename = "add_user_to_group")] pub struct AddUserToGroup { - name: String, - uid: u32, - groupname: String, - gid: u32, + pub(crate) name: String, + pub(crate) uid: u32, + pub(crate) groupname: String, + pub(crate) gid: u32, } impl AddUserToGroup { @@ -183,13 +184,6 @@ impl Action for AddUserToGroup { #[tracing::instrument(level = "debug", skip_all)] async fn execute(&mut self) -> Result<(), ActionError> { - let Self { - name, - uid: _, - groupname, - gid: _, - } = self; - use target_lexicon::OperatingSystem; match OperatingSystem::host() { OperatingSystem::MacOSX { @@ -204,10 +198,10 @@ impl Action for AddUserToGroup { .args([ ".", "-append", - &format!("/Groups/{groupname}"), + &format!("/Groups/{}", self.groupname), "GroupMembership", ]) - .arg(&name) + .arg(&self.name) .stdin(std::process::Stdio::null()), ) .await @@ -217,10 +211,10 @@ impl Action for AddUserToGroup { .process_group(0) .args(["-o", "edit"]) .arg("-a") - .arg(&name) + .arg(&self.name) .arg("-t") - .arg(&name) - .arg(groupname) + .arg(&self.name) + .arg(&self.groupname) .stdin(std::process::Stdio::null()), ) .await @@ -232,7 +226,7 @@ impl Action for AddUserToGroup { Command::new("gpasswd") .process_group(0) .args(["-a"]) - .args([name, groupname]) + .args([&self.name, &self.groupname]) .stdin(std::process::Stdio::null()), ) .await @@ -241,7 +235,7 @@ impl Action for AddUserToGroup { execute_command( Command::new("addgroup") .process_group(0) - .args([name, groupname]) + .args([&self.name, &self.groupname]) .stdin(std::process::Stdio::null()), ) .await @@ -290,7 +284,7 @@ impl Action for AddUserToGroup { Command::new("/usr/bin/dscl") .process_group(0) .args([".", "-delete", &format!("/Groups/{groupname}"), "users"]) - .arg(&name) + .arg(name) .stdin(std::process::Stdio::null()), ) .await diff --git a/src/action/base/create_directory.rs b/src/action/base/create_directory.rs index ddd22086a..7d34cdde7 100644 --- a/src/action/base/create_directory.rs +++ b/src/action/base/create_directory.rs @@ -3,7 +3,8 @@ use std::path::{Path, PathBuf}; use nix::unistd::{chown, Group, User}; -use tokio::fs::{create_dir, remove_dir_all, remove_file}; +use target_lexicon::OperatingSystem; +use tokio::fs::{create_dir_all, remove_dir_all, remove_file}; use tokio::process::Command; use tracing::{span, Span}; @@ -17,6 +18,7 @@ If `force_prune_on_revert` is set, the folder will always be deleted on [`revert`](CreateDirectory::revert). */ #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(tag = "action_name", rename = "create_directory")] pub struct CreateDirectory { path: PathBuf, user: Option, @@ -182,7 +184,7 @@ impl Action for CreateDirectory { None }; - create_dir(&path) + create_dir_all(&path) .await .map_err(|e| ActionErrorKind::CreateDirectory(path.clone(), e)) .map_err(Self::error)?; @@ -296,13 +298,20 @@ async fn path_is_mountpoint(path: &Path) -> Result { None => return Err(ActionErrorKind::PathNoneString(path.to_path_buf())), }; - let mut mount_command = Command::new("mount"); - mount_command.process_group(0); + let mut mount_command = match OperatingSystem::host() { + OperatingSystem::MacOSX { .. } | OperatingSystem::Darwin => { + let mut cmd = Command::new("/sbin/mount"); + cmd.arg("-d"); // `-d` means `--dry-run` + cmd + }, + _ => { + let mut cmd = Command::new("mount"); + cmd.arg("-f"); // `-f` means `--fake` not `--force` + cmd + }, + }; - #[cfg(target_os = "macos")] - mount_command.arg("-d"); // `-d` means `--dry-run` - #[cfg(target_os = "linux")] - mount_command.arg("-f"); // `-f` means `--fake` not `--force` + mount_command.process_group(0); let output = execute_command(&mut mount_command).await?; let output_string = String::from_utf8(output.stdout).map_err(ActionErrorKind::FromUtf8)?; @@ -317,12 +326,13 @@ async fn path_is_mountpoint(path: &Path) -> Result { Some(destination_and_options) => destination_and_options, None => continue, }; - // Each line on Linux looks like `portal on /run/user/1000/doc type fuse.portal (rw,nosuid,nodev,relatime,user_id=1000,group_id=100)` - #[cfg(target_os = "linux")] - let split_token = "type"; - // Each line on MacOS looks like `/dev/disk3s6 on /System/Volumes/VM (apfs, local, noexec, journaled, noatime, nobrowse)` - #[cfg(target_os = "macos")] - let split_token = "("; + + let split_token = match OperatingSystem::host() { + // Each line on MacOS looks like `/dev/disk3s6 on /System/Volumes/VM (apfs, local, noexec, journaled, noatime, nobrowse)` + OperatingSystem::MacOSX { .. } | OperatingSystem::Darwin => "(", + // Each line on Linux looks like `portal on /run/user/1000/doc type fuse.portal (rw,nosuid,nodev,relatime,user_id=1000,group_id=100)` + _ => "type", + }; if let Some(mount_path) = destination_and_options.rsplit(split_token).last() { let trimmed = mount_path.trim(); diff --git a/src/action/base/create_file.rs b/src/action/base/create_file.rs index 18667ff2b..f5003f0f2 100644 --- a/src/action/base/create_file.rs +++ b/src/action/base/create_file.rs @@ -21,6 +21,7 @@ If `force` is set, the file will always be overwritten (and deleted) regardless of its presence prior to install. */ #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(tag = "action_name", rename = "create_file")] pub struct CreateFile { pub(crate) path: PathBuf, user: Option, @@ -177,39 +178,30 @@ impl Action for CreateFile { #[tracing::instrument(level = "debug", skip_all)] async fn execute(&mut self) -> Result<(), ActionError> { - let Self { - path, - user, - group, - mode, - buf, - force: _, - } = self; - if tracing::enabled!(tracing::Level::TRACE) { let span = tracing::Span::current(); - span.record("buf", &buf); + span.record("buf", &self.buf); } let mut options = OpenOptions::new(); options.create_new(true).write(true).read(true); - if let Some(mode) = mode { - options.mode(*mode); + if let Some(mode) = self.mode { + options.mode(mode); } let mut file = options - .open(&path) + .open(&self.path) .await - .map_err(|e| ActionErrorKind::Open(path.to_owned(), e)) + .map_err(|e| ActionErrorKind::Open(self.path.to_owned(), e)) .map_err(Self::error)?; - file.write_all(buf.as_bytes()) + file.write_all(self.buf.as_bytes()) .await - .map_err(|e| ActionErrorKind::Write(path.to_owned(), e)) + .map_err(|e| ActionErrorKind::Write(self.path.to_owned(), e)) .map_err(Self::error)?; - let gid = if let Some(group) = group { + let gid = if let Some(ref group) = self.group { Some( Group::from_name(group.as_str()) .map_err(|e| ActionErrorKind::GettingGroupId(group.clone(), e)) @@ -221,7 +213,7 @@ impl Action for CreateFile { } else { None }; - let uid = if let Some(user) = user { + let uid = if let Some(ref user) = self.user { Some( User::from_name(user.as_str()) .map_err(|e| ActionErrorKind::GettingUserId(user.clone(), e)) @@ -233,8 +225,8 @@ impl Action for CreateFile { } else { None }; - chown(path, uid, gid) - .map_err(|e| ActionErrorKind::Chown(path.clone(), e)) + chown(&self.path, uid, gid) + .map_err(|e| ActionErrorKind::Chown(self.path.clone(), e)) .map_err(Self::error)?; Ok(()) diff --git a/src/action/base/create_group.rs b/src/action/base/create_group.rs index 4b47a06aa..d54e67539 100644 --- a/src/action/base/create_group.rs +++ b/src/action/base/create_group.rs @@ -12,6 +12,7 @@ use crate::action::{Action, ActionDescription, StatefulAction}; Create an operating system level user group */ #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(tag = "action_name", rename = "create_group")] pub struct CreateGroup { name: String, gid: u32, diff --git a/src/action/base/create_or_insert_into_file.rs b/src/action/base/create_or_insert_into_file.rs index 96162a60f..5b954e75f 100644 --- a/src/action/base/create_or_insert_into_file.rs +++ b/src/action/base/create_or_insert_into_file.rs @@ -28,6 +28,7 @@ If the file exists, the provided `buf` will be inserted at its beginning or end, depending on the position field. */ #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(tag = "action_name", rename = "create_or_insert_into_file")] pub struct CreateOrInsertIntoFile { path: PathBuf, user: Option, diff --git a/src/action/base/create_or_merge_nix_config.rs b/src/action/base/create_or_merge_nix_config.rs index ec4f7a352..8ef271bfc 100644 --- a/src/action/base/create_or_merge_nix_config.rs +++ b/src/action/base/create_or_merge_nix_config.rs @@ -43,6 +43,7 @@ impl From for ActionErrorKind { /// Create or merge an existing `nix.conf` at the specified path. #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(tag = "action_name", rename = "create_or_merge_nix_config")] pub struct CreateOrMergeNixConfig { pub(crate) path: PathBuf, pending_nix_config: NixConfig, @@ -199,8 +200,7 @@ impl Action for CreateOrMergeNixConfig { if tracing::enabled!(tracing::Level::TRACE) { span.record( "pending_nix_config", - &self - .pending_nix_config + self.pending_nix_config .settings() .iter() .map(|(k, v)| format!("{k}=\"{v}\"")) diff --git a/src/action/base/create_user.rs b/src/action/base/create_user.rs index 1043876ef..04147e889 100644 --- a/src/action/base/create_user.rs +++ b/src/action/base/create_user.rs @@ -12,11 +12,12 @@ use crate::action::{Action, ActionDescription, StatefulAction}; Create an operating system level user in the given group */ #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(tag = "action_name", rename = "create_user")] pub struct CreateUser { - name: String, - uid: u32, - groupname: String, - gid: u32, + pub(crate) name: String, + pub(crate) uid: u32, + pub(crate) groupname: String, + pub(crate) gid: u32, comment: String, } @@ -28,6 +29,7 @@ impl CreateUser { groupname: String, gid: u32, comment: String, + check_completed: bool, ) -> Result, ActionError> { let this = Self { name: name.clone(), @@ -49,29 +51,31 @@ impl CreateUser { }, } - // Ensure user does not exists - if let Some(user) = User::from_name(name.as_str()) - .map_err(|e| ActionErrorKind::GettingUserId(name.clone(), e)) - .map_err(Self::error)? - { - if user.uid.as_raw() != uid { - return Err(Self::error(ActionErrorKind::UserUidMismatch( - name.clone(), - user.uid.as_raw(), - uid, - ))); - } + if check_completed { + // Ensure user does not exist + if let Some(user) = User::from_name(name.as_str()) + .map_err(|e| ActionErrorKind::GettingUserId(name.clone(), e)) + .map_err(Self::error)? + { + if user.uid.as_raw() != uid { + return Err(Self::error(ActionErrorKind::UserUidMismatch( + name.clone(), + user.uid.as_raw(), + uid, + ))); + } - if user.gid.as_raw() != gid { - return Err(Self::error(ActionErrorKind::UserGidMismatch( - name.clone(), - user.gid.as_raw(), - gid, - ))); - } + if user.gid.as_raw() != gid { + return Err(Self::error(ActionErrorKind::UserGidMismatch( + name.clone(), + user.gid.as_raw(), + gid, + ))); + } - tracing::debug!("Creating user `{}` already complete", this.name); - return Ok(StatefulAction::completed(this)); + tracing::debug!("Creating user `{}` already complete", this.name); + return Ok(StatefulAction::completed(this)); + } } Ok(StatefulAction::uncompleted(this)) @@ -121,86 +125,11 @@ impl Action for CreateUser { comment, } = self; - use OperatingSystem; match OperatingSystem::host() { - OperatingSystem::MacOSX { - major: _, - minor: _, - patch: _, - } - | OperatingSystem::Darwin => { - execute_command( - Command::new("/usr/bin/dscl") - .process_group(0) - .args([".", "-create", &format!("/Users/{name}")]) - .stdin(std::process::Stdio::null()), - ) - .await - .map_err(Self::error)?; - execute_command( - Command::new("/usr/bin/dscl") - .process_group(0) - .args([ - ".", - "-create", - &format!("/Users/{name}"), - "UniqueID", - &format!("{uid}"), - ]) - .stdin(std::process::Stdio::null()), - ) - .await - .map_err(Self::error)?; - execute_command( - Command::new("/usr/bin/dscl") - .process_group(0) - .args([ - ".", - "-create", - &format!("/Users/{name}"), - "PrimaryGroupID", - &format!("{gid}"), - ]) - .stdin(std::process::Stdio::null()), - ) - .await - .map_err(Self::error)?; - execute_command( - Command::new("/usr/bin/dscl") - .process_group(0) - .args([ - ".", - "-create", - &format!("/Users/{name}"), - "NFSHomeDirectory", - "/var/empty", - ]) - .stdin(std::process::Stdio::null()), - ) - .await - .map_err(Self::error)?; - execute_command( - Command::new("/usr/bin/dscl") - .process_group(0) - .args([ - ".", - "-create", - &format!("/Users/{name}"), - "UserShell", - "/sbin/nologin", - ]) - .stdin(std::process::Stdio::null()), - ) - .await - .map_err(Self::error)?; - execute_command( - Command::new("/usr/bin/dscl") - .process_group(0) - .args([".", "-create", &format!("/Users/{name}"), "IsHidden", "1"]) - .stdin(std::process::Stdio::null()), - ) - .await - .map_err(Self::error)?; + OperatingSystem::MacOSX { .. } | OperatingSystem::Darwin => { + create_user_macos(name, *uid, *gid) + .await + .map_err(Self::error)?; }, _ => { if which::which("useradd").is_ok() { @@ -277,43 +206,9 @@ impl Action for CreateUser { #[tracing::instrument(level = "debug", skip_all)] async fn revert(&mut self) -> Result<(), ActionError> { - use OperatingSystem; match OperatingSystem::host() { - OperatingSystem::MacOSX { - major: _, - minor: _, - patch: _, - } - | OperatingSystem::Darwin => { - // MacOS is a "Special" case - // It's only possible to delete users under certain conditions. - // Documentation on https://it.megocollector.com/macos/cant-delete-a-macos-user-with-dscl-resolution/ and http://www.aixperts.co.uk/?p=214 suggested it was a secure token - // That is correct, however it's a bit more nuanced. It appears to be that a user must be graphically logged in for some other user on the system to be deleted. - let mut command = Command::new("/usr/bin/dscl"); - command.args([".", "-delete", &format!("/Users/{}", self.name)]); - command.process_group(0); - command.stdin(std::process::Stdio::null()); - - let output = command - .output() - .await - .map_err(|e| ActionErrorKind::command(&command, e)) - .map_err(Self::error)?; - let stderr = String::from_utf8_lossy(&output.stderr); - match output.status.code() { - Some(0) => (), - Some(40) if stderr.contains("-14120") => { - // The user is on an ephemeral Mac, like detsys uses - // These Macs cannot always delete users, as sometimes there is no graphical login - tracing::warn!("Encountered an exit code 40 with -14120 error while removing user, this is likely because the initial executing user did not have a secure token, or that there was no graphical login session. To delete the user, log in graphically, then run `/usr/bin/dscl . -delete /Users/{}`", self.name); - }, - _ => { - // Something went wrong - return Err(Self::error(ActionErrorKind::command_output( - &command, output, - ))); - }, - } + OperatingSystem::MacOSX { .. } | OperatingSystem::Darwin => { + delete_user_macos(&self.name).await.map_err(Self::error)?; }, _ => { if which::which("userdel").is_ok() { @@ -343,3 +238,107 @@ impl Action for CreateUser { Ok(()) } } + +#[tracing::instrument(level = "debug", skip_all)] +async fn create_user_macos(name: &str, uid: u32, gid: u32) -> Result<(), ActionErrorKind> { + execute_command( + Command::new("/usr/bin/dscl") + .process_group(0) + .args([".", "-create", &format!("/Users/{name}")]) + .stdin(std::process::Stdio::null()), + ) + .await?; + execute_command( + Command::new("/usr/bin/dscl") + .process_group(0) + .args([ + ".", + "-create", + &format!("/Users/{name}"), + "UniqueID", + &format!("{uid}"), + ]) + .stdin(std::process::Stdio::null()), + ) + .await?; + execute_command( + Command::new("/usr/bin/dscl") + .process_group(0) + .args([ + ".", + "-create", + &format!("/Users/{name}"), + "PrimaryGroupID", + &format!("{gid}"), + ]) + .stdin(std::process::Stdio::null()), + ) + .await?; + execute_command( + Command::new("/usr/bin/dscl") + .process_group(0) + .args([ + ".", + "-create", + &format!("/Users/{name}"), + "NFSHomeDirectory", + "/var/empty", + ]) + .stdin(std::process::Stdio::null()), + ) + .await?; + execute_command( + Command::new("/usr/bin/dscl") + .process_group(0) + .args([ + ".", + "-create", + &format!("/Users/{name}"), + "UserShell", + "/sbin/nologin", + ]) + .stdin(std::process::Stdio::null()), + ) + .await?; + execute_command( + Command::new("/usr/bin/dscl") + .process_group(0) + .args([".", "-create", &format!("/Users/{name}"), "IsHidden", "1"]) + .stdin(std::process::Stdio::null()), + ) + .await?; + + Ok(()) +} + +#[tracing::instrument(level = "debug", skip_all)] +pub async fn delete_user_macos(name: &str) -> Result<(), ActionErrorKind> { + // MacOS is a "Special" case + // It's only possible to delete users under certain conditions. + // Documentation on https://it.megocollector.com/macos/cant-delete-a-macos-user-with-dscl-resolution/ and http://www.aixperts.co.uk/?p=214 suggested it was a secure token + // That is correct, however it's a bit more nuanced. It appears to be that a user must be graphically logged in for some other user on the system to be deleted. + let mut command = Command::new("/usr/bin/dscl"); + command.process_group(0); + command.args([".", "-delete", &format!("/Users/{}", name)]); + command.stdin(std::process::Stdio::null()); + + let output = command + .output() + .await + .map_err(|e| ActionErrorKind::command(&command, e))?; + let stderr = String::from_utf8_lossy(&output.stderr); + match output.status.code() { + Some(0) => (), + Some(40) if stderr.contains("-14120") => { + // The user is on an ephemeral Mac, like detsys uses + // These Macs cannot always delete users, as sometimes there is no graphical login + tracing::warn!("Encountered an exit code 40 with -14120 error while removing user, this is likely because the initial executing user did not have a secure token, or that there was no graphical login session. To delete the user, log in graphically, then run `/usr/bin/dscl . -delete /Users/{}`", name); + }, + _ => { + // Something went wrong + return Err(ActionErrorKind::command_output(&command, output)); + }, + } + + Ok(()) +} diff --git a/src/action/base/delete_user.rs b/src/action/base/delete_user.rs index 01d4ed6ca..077c6f80c 100644 --- a/src/action/base/delete_user.rs +++ b/src/action/base/delete_user.rs @@ -3,6 +3,7 @@ use target_lexicon::OperatingSystem; use tokio::process::Command; use tracing::{span, Span}; +use crate::action::base::create_user::delete_user_macos; use crate::action::{ActionError, ActionErrorKind, ActionTag}; use crate::execute_command; @@ -12,6 +13,7 @@ use crate::action::{Action, ActionDescription, StatefulAction}; Delete an operating system level user */ #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(tag = "action_name", rename = "delete_user")] pub struct DeleteUser { name: String, } @@ -72,43 +74,9 @@ impl Action for DeleteUser { #[tracing::instrument(level = "debug", skip_all)] async fn execute(&mut self) -> Result<(), ActionError> { - use OperatingSystem; match OperatingSystem::host() { - OperatingSystem::MacOSX { - major: _, - minor: _, - patch: _, - } - | OperatingSystem::Darwin => { - // MacOS is a "Special" case - // It's only possible to delete users under certain conditions. - // Documentation on https://it.megocollector.com/macos/cant-delete-a-macos-user-with-dscl-resolution/ and http://www.aixperts.co.uk/?p=214 suggested it was a secure token - // That is correct, however it's a bit more nuanced. It appears to be that a user must be graphically logged in for some other user on the system to be deleted. - let mut command = Command::new("/usr/bin/dscl"); - command.args([".", "-delete", &format!("/Users/{}", self.name)]); - command.process_group(0); - command.stdin(std::process::Stdio::null()); - - let output = command - .output() - .await - .map_err(|e| ActionErrorKind::command(&command, e)) - .map_err(Self::error)?; - let stderr = String::from_utf8_lossy(&output.stderr); - match output.status.code() { - Some(0) => (), - Some(40) if stderr.contains("-14120") => { - // The user is on an ephemeral Mac, like detsys uses - // These Macs cannot always delete users, as sometimes there is no graphical login - tracing::warn!("Encountered an exit code 40 with -14120 error while removing user, this is likely because the initial executing user did not have a secure token, or that there was no graphical login session. To delete the user, log in graphically, then run `/usr/bin/dscl . -delete /Users/{}`", self.name); - }, - _ => { - // Something went wrong - return Err(Self::error(ActionErrorKind::command_output( - &command, output, - ))); - }, - } + OperatingSystem::MacOSX { .. } | OperatingSystem::Darwin => { + delete_user_macos(&self.name).await.map_err(Self::error)?; }, _ => { if which::which("userdel").is_ok() { diff --git a/src/action/base/fetch_and_unpack_nix.rs b/src/action/base/fetch_and_unpack_nix.rs index 60a6b866a..be326d521 100644 --- a/src/action/base/fetch_and_unpack_nix.rs +++ b/src/action/base/fetch_and_unpack_nix.rs @@ -14,6 +14,7 @@ use crate::{ Fetch a URL to the given path */ #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(tag = "action_name", rename = "fetch_and_unpack_nix")] pub struct FetchAndUnpackNix { url_or_path: Option, dest: PathBuf, diff --git a/src/action/base/move_unpacked_nix.rs b/src/action/base/move_unpacked_nix.rs index 37e034ff8..c2c18828d 100644 --- a/src/action/base/move_unpacked_nix.rs +++ b/src/action/base/move_unpacked_nix.rs @@ -16,6 +16,7 @@ pub(crate) const DEST: &str = "/nix/"; Move an unpacked Nix at `src` to `/nix` */ #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(tag = "action_name", rename = "mount_unpacked_nix")] pub struct MoveUnpackedNix { unpacked_path: PathBuf, } diff --git a/src/action/base/remove_directory.rs b/src/action/base/remove_directory.rs index 32e7c80ec..d2dd73b38 100644 --- a/src/action/base/remove_directory.rs +++ b/src/action/base/remove_directory.rs @@ -9,6 +9,7 @@ use crate::action::{ActionError, StatefulAction}; /** Remove a directory, does nothing on revert. */ #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(tag = "action_name", rename = "remove_directory")] pub struct RemoveDirectory { path: PathBuf, } diff --git a/src/action/base/setup_default_profile.rs b/src/action/base/setup_default_profile.rs index f8ba75f4c..6fa5da0ad 100644 --- a/src/action/base/setup_default_profile.rs +++ b/src/action/base/setup_default_profile.rs @@ -14,6 +14,7 @@ use crate::action::{Action, ActionDescription}; Setup the default Nix profile with `nss-cacert` and `nix` itself. */ #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(tag = "action_name", rename = "setup_default_profile")] pub struct SetupDefaultProfile { unpacked_path: PathBuf, } @@ -117,6 +118,7 @@ impl Action for SetupDefaultProfile { execute_command( Command::new(nix_pkg.join("bin/nix-env")) .process_group(0) + .args(["--option", "substitute", "false"]) .arg("-i") .arg(&nix_pkg) .stdin(std::process::Stdio::null()) @@ -137,6 +139,7 @@ impl Action for SetupDefaultProfile { execute_command( Command::new(nix_pkg.join("bin/nix-env")) .process_group(0) + .args(["--option", "substitute", "false"]) .arg("-i") .arg(&nss_ca_cert_pkg) .stdin(std::process::Stdio::null()) diff --git a/src/action/common/configure_determinate_nixd_init_service/mod.rs b/src/action/common/configure_determinate_nixd_init_service/mod.rs new file mode 100644 index 000000000..003b7ad71 --- /dev/null +++ b/src/action/common/configure_determinate_nixd_init_service/mod.rs @@ -0,0 +1,258 @@ +use std::collections::HashMap; +use std::path::PathBuf; + +use serde::{Deserialize, Serialize}; +use tokio::io::AsyncWriteExt; +use tracing::{span, Span}; + +use crate::action::common::configure_init_service::{SocketFile, UnitSrc}; +use crate::action::{common::ConfigureInitService, Action, ActionDescription}; +use crate::action::{ActionError, ActionErrorKind, ActionTag, StatefulAction}; +use crate::settings::InitSystem; + +// Linux +const LINUX_NIXD_DAEMON_DEST: &str = "/etc/systemd/system/nix-daemon.service"; + +// Darwin +const DARWIN_NIXD_DAEMON_DEST: &str = "/Library/LaunchDaemons/systems.determinate.nix-daemon.plist"; +const DARWIN_NIXD_SERVICE_NAME: &str = "systems.determinate.nix-daemon"; + +/** +Configure the init to run the Nix daemon +*/ +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde( + tag = "action_name", + rename = "configure_determinate_nixd_init_service" +)] +pub struct ConfigureDeterminateNixdInitService { + init: InitSystem, + configure_init_service: StatefulAction, +} + +impl ConfigureDeterminateNixdInitService { + #[tracing::instrument(level = "debug", skip_all)] + pub async fn plan( + init: InitSystem, + start_daemon: bool, + ) -> Result, ActionError> { + let service_dest: Option = match init { + InitSystem::Launchd => Some(DARWIN_NIXD_DAEMON_DEST.into()), + InitSystem::Systemd => Some(LINUX_NIXD_DAEMON_DEST.into()), + InitSystem::None => None, + }; + let service_name: Option = match init { + InitSystem::Launchd => Some(DARWIN_NIXD_SERVICE_NAME.into()), + _ => None, + }; + + let configure_init_service = ConfigureInitService::plan( + init, + start_daemon, + None, + service_dest, + service_name, + vec![ + SocketFile { + name: "nix-daemon.socket".into(), + src: UnitSrc::Literal( + include_str!("./nix-daemon.determinate-nixd.socket").to_string(), + ), + dest: "/etc/systemd/system/nix-daemon.socket".into(), + }, + SocketFile { + name: "determinate-nixd.socket".into(), + src: UnitSrc::Literal( + include_str!("./nixd.determinate-nixd.socket").to_string(), + ), + dest: "/etc/systemd/system/determinate-nixd.socket".into(), + }, + ], + ) + .await + .map_err(Self::error)?; + + Ok(Self { + init, + configure_init_service, + } + .into()) + } +} + +#[async_trait::async_trait] +#[typetag::serde(name = "configure_determinate_nixd_init_service")] +impl Action for ConfigureDeterminateNixdInitService { + fn action_tag() -> ActionTag { + ActionTag("configure_determinate_nixd_init_service") + } + fn tracing_synopsis(&self) -> String { + "Configure the Determinate Nix daemon".to_string() + } + + fn tracing_span(&self) -> Span { + span!( + tracing::Level::DEBUG, + "configure_determinate_nixd_init_service" + ) + } + + fn execute_description(&self) -> Vec { + vec![ActionDescription::new( + self.tracing_synopsis(), + vec![self.configure_init_service.tracing_synopsis()], + )] + } + + #[tracing::instrument(level = "debug", skip_all)] + async fn execute(&mut self) -> Result<(), ActionError> { + let Self { + init, + configure_init_service, + } = self; + + if *init == InitSystem::Launchd { + let daemon_file = DARWIN_NIXD_DAEMON_DEST; + + // This is the only part that is actually different from configure_init_service, beyond variable parameters. + + let generated_plist = generate_plist(); + + let mut options = tokio::fs::OpenOptions::new(); + options.create(true).write(true).read(true); + + let mut file = options + .open(&daemon_file) + .await + .map_err(|e| Self::error(ActionErrorKind::Open(PathBuf::from(daemon_file), e)))?; + + let mut buf = Vec::new(); + plist::to_writer_xml(&mut buf, &generated_plist).map_err(Self::error)?; + file.write_all(&buf) + .await + .map_err(|e| Self::error(ActionErrorKind::Write(PathBuf::from(daemon_file), e)))?; + } else if *init == InitSystem::Systemd { + let daemon_file = PathBuf::from(LINUX_NIXD_DAEMON_DEST); + + tokio::fs::write( + &daemon_file, + include_str!("./nix-daemon.determinate-nixd.service"), + ) + .await + .map_err(|e| ActionErrorKind::Write(daemon_file.clone(), e)) + .map_err(Self::error)?; + } + + configure_init_service + .try_execute() + .await + .map_err(Self::error)?; + + Ok(()) + } + + fn revert_description(&self) -> Vec { + vec![ActionDescription::new( + "Remove the Determinate Nix daemon".to_string(), + vec![self.configure_init_service.tracing_synopsis()], + )] + } + + #[tracing::instrument(level = "debug", skip_all)] + async fn revert(&mut self) -> Result<(), ActionError> { + self.configure_init_service.try_revert().await?; + + let file_to_remove = match self.init { + InitSystem::Launchd => Some(DARWIN_NIXD_DAEMON_DEST), + InitSystem::Systemd => Some(LINUX_NIXD_DAEMON_DEST), + InitSystem::None => None, + }; + + if let Some(file_to_remove) = file_to_remove { + tracing::trace!(path = %file_to_remove, "Removing"); + tokio::fs::remove_file(file_to_remove) + .await + .map_err(|e| ActionErrorKind::Remove(file_to_remove.into(), e)) + .map_err(Self::error)?; + } + + Ok(()) + } +} + +#[non_exhaustive] +#[derive(Debug, thiserror::Error)] +pub enum ConfigureDeterminateNixDaemonServiceError {} + +#[derive(Deserialize, Clone, Debug, Serialize, PartialEq)] +#[serde(rename_all = "PascalCase")] +pub struct DeterminateNixDaemonPlist { + label: String, + program_arguments: Vec, + run_at_load: bool, + sockets: HashMap, + standard_error_path: String, + standard_out_path: String, + soft_resource_limits: ResourceLimits, + hard_resource_limits: ResourceLimits, +} + +#[derive(Deserialize, Clone, Debug, Serialize, PartialEq)] +#[serde(rename_all = "PascalCase")] +pub struct ResourceLimits { + number_of_files: usize, + number_of_processes: usize, + stack: usize, +} + +#[derive(Deserialize, Clone, Debug, Serialize, PartialEq)] +#[serde(rename_all = "PascalCase")] +pub struct Socket { + sock_family: SocketFamily, + sock_passive: bool, + sock_path_name: String, +} + +#[derive(Deserialize, Clone, Debug, Serialize, PartialEq)] +#[serde(rename_all = "PascalCase")] +enum SocketFamily { + Unix, +} + +fn generate_plist() -> DeterminateNixDaemonPlist { + DeterminateNixDaemonPlist { + run_at_load: false, + label: "systems.determinate.nix-daemon".into(), + program_arguments: vec!["/usr/local/bin/determinate-nixd".into(), "daemon".into()], + standard_error_path: "/var/log/determinate-nix-daemon.log".into(), + standard_out_path: "/var/log/determinate-nix-daemon.log".into(), + soft_resource_limits: ResourceLimits { + number_of_files: 1024 * 1024, + number_of_processes: 1024 * 1024, + stack: 64 * 1024 * 1024, + }, + hard_resource_limits: ResourceLimits { + number_of_files: 1024 * 1024, + number_of_processes: 1024 * 1024, + stack: 64 * 1024 * 1024, + }, + sockets: HashMap::from([ + ( + "determinate-nixd.socket".to_string(), + Socket { + sock_family: SocketFamily::Unix, + sock_passive: true, + sock_path_name: "/var/run/determinate-nixd.socket".into(), + }, + ), + ( + "nix-daemon.socket".to_string(), + Socket { + sock_family: SocketFamily::Unix, + sock_passive: true, + sock_path_name: "/var/run/nix-daemon.socket".into(), + }, + ), + ]), + } +} diff --git a/src/action/common/configure_determinate_nixd_init_service/nix-daemon.determinate-nixd.service b/src/action/common/configure_determinate_nixd_init_service/nix-daemon.determinate-nixd.service new file mode 100644 index 000000000..f6147f9f7 --- /dev/null +++ b/src/action/common/configure_determinate_nixd_init_service/nix-daemon.determinate-nixd.service @@ -0,0 +1,17 @@ +[Unit] +Description=Nix Daemon, with Determinate Nix superpowers. +Documentation=man:nix-daemon https://determinate.systems +RequiresMountsFor=/nix/store +RequiresMountsFor=/nix/var +RequiresMountsFor=/nix/var/nix/db +ConditionPathIsReadWrite=/nix/var/nix/daemon-socket + +[Service] +ExecStart=@/usr/local/bin/determinate-nixd determinate-nixd daemon +KillMode=process +LimitNOFILE=1048576 +LimitSTACK=64M +TasksMax=1048576 + +[Install] +WantedBy=multi-user.target diff --git a/src/action/common/configure_determinate_nixd_init_service/nix-daemon.determinate-nixd.socket b/src/action/common/configure_determinate_nixd_init_service/nix-daemon.determinate-nixd.socket new file mode 100644 index 000000000..8e91ce8b0 --- /dev/null +++ b/src/action/common/configure_determinate_nixd_init_service/nix-daemon.determinate-nixd.socket @@ -0,0 +1,14 @@ +[Unit] +Description=Determinate Nix Daemon Socket +Before=multi-user.target +RequiresMountsFor=/nix/store +RequiresMountsFor=/nix/var +RequiresMountsFor=/nix/var/nix/db +ConditionPathIsReadWrite=/nix/var/nix/daemon-socket + +[Socket] +FileDescriptorName=nix-daemon.socket +ListenStream=/nix/var/nix/daemon-socket/socket + +[Install] +WantedBy=sockets.target diff --git a/src/action/common/configure_determinate_nixd_init_service/nixd.determinate-nixd.socket b/src/action/common/configure_determinate_nixd_init_service/nixd.determinate-nixd.socket new file mode 100644 index 000000000..201a6d377 --- /dev/null +++ b/src/action/common/configure_determinate_nixd_init_service/nixd.determinate-nixd.socket @@ -0,0 +1,14 @@ +[Unit] +Description=Determinate Nixd Daemon Socket +Before=multi-user.target +RequiresMountsFor=/nix/store +RequiresMountsFor=/nix/var/determinate + +[Socket] +FileDescriptorName=determinate-nixd.socket +DirectoryMode=0755 +ListenStream=/nix/var/determinate/determinate-nixd.socket +Service=nix-daemon.service + +[Install] +WantedBy=sockets.target diff --git a/src/action/common/configure_enterprise_edition_init_service.rs b/src/action/common/configure_enterprise_edition_init_service.rs deleted file mode 100644 index ce3b87efb..000000000 --- a/src/action/common/configure_enterprise_edition_init_service.rs +++ /dev/null @@ -1,189 +0,0 @@ -/// This file is unused but is kept around to avoid merge conflicts -use std::path::PathBuf; - -#[cfg(target_os = "macos")] -use serde::{Deserialize, Serialize}; -#[cfg(target_os = "macos")] -use tokio::io::AsyncWriteExt; -use tokio::process::Command; -use tracing::{span, Span}; - -use crate::action::{ActionError, ActionErrorKind, ActionTag, StatefulAction}; -use crate::execute_command; - -use crate::action::{Action, ActionDescription}; - -#[cfg(target_os = "macos")] -const DARWIN_ENTERPRISE_EDITION_DAEMON_DEST: &str = - "/Library/LaunchDaemons/systems.determinate.nix-daemon.plist"; -/** -Configure the init to run the Nix daemon -*/ -#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] -pub struct ConfigureEnterpriseEditionInitService { - start_daemon: bool, -} - -impl ConfigureEnterpriseEditionInitService { - #[tracing::instrument(level = "debug", skip_all)] - pub async fn plan(start_daemon: bool) -> Result, ActionError> { - Ok(Self { start_daemon }.into()) - } -} - -#[async_trait::async_trait] -#[typetag::serde(name = "configure_enterprise_edition_init_service")] -impl Action for ConfigureEnterpriseEditionInitService { - fn action_tag() -> ActionTag { - ActionTag("configure_enterprise_edition_init_service") - } - fn tracing_synopsis(&self) -> String { - "Configure the Determinate Nix Enterprise Edition daemon related settings with launchctl" - .to_string() - } - - fn tracing_span(&self) -> Span { - span!( - tracing::Level::DEBUG, - "configure_enterprise_edition_init_service" - ) - } - - fn execute_description(&self) -> Vec { - let mut explanation = vec![format!("Create `{DARWIN_ENTERPRISE_EDITION_DAEMON_DEST}`")]; - if self.start_daemon { - explanation.push(format!( - "Run `launchctl load {DARWIN_ENTERPRISE_EDITION_DAEMON_DEST}`" - )); - } - - vec![ActionDescription::new(self.tracing_synopsis(), explanation)] - } - - #[tracing::instrument(level = "debug", skip_all)] - async fn execute(&mut self) -> Result<(), ActionError> { - let Self { start_daemon } = self; - - let daemon_file = DARWIN_ENTERPRISE_EDITION_DAEMON_DEST; - let domain = "system"; - let service = "systems.determinate.nix-daemon"; - - let generated_plist = generate_plist(); - - let mut options = tokio::fs::OpenOptions::new(); - options.create(true).write(true).read(true); - - let mut file = options - .open(&daemon_file) - .await - .map_err(|e| Self::error(ActionErrorKind::Open(PathBuf::from(daemon_file), e)))?; - - let mut buf = Vec::new(); - plist::to_writer_xml(&mut buf, &generated_plist).map_err(Self::error)?; - file.write_all(&buf) - .await - .map_err(|e| Self::error(ActionErrorKind::Write(PathBuf::from(daemon_file), e)))?; - - execute_command( - Command::new("launchctl") - .process_group(0) - .args(["load", "-w"]) - .arg(daemon_file) - .stdin(std::process::Stdio::null()), - ) - .await - .map_err(Self::error)?; - - let is_disabled = crate::action::macos::service_is_disabled(domain, service) - .await - .map_err(Self::error)?; - if is_disabled { - execute_command( - Command::new("launchctl") - .process_group(0) - .arg("enable") - .arg(&format!("{domain}/{service}")) - .stdin(std::process::Stdio::null()), - ) - .await - .map_err(Self::error)?; - } - - if *start_daemon { - execute_command( - Command::new("launchctl") - .process_group(0) - .arg("kickstart") - .arg("-k") - .arg(&format!("{domain}/{service}")) - .stdin(std::process::Stdio::null()), - ) - .await - .map_err(Self::error)?; - } - - Ok(()) - } - - fn revert_description(&self) -> Vec { - vec![ActionDescription::new( - "Unconfigure Nix daemon related settings with launchctl".to_string(), - vec![format!( - "Run `launchctl unload {DARWIN_ENTERPRISE_EDITION_DAEMON_DEST}`" - )], - )] - } - - #[tracing::instrument(level = "debug", skip_all)] - async fn revert(&mut self) -> Result<(), ActionError> { - execute_command( - Command::new("launchctl") - .process_group(0) - .arg("unload") - .arg(DARWIN_ENTERPRISE_EDITION_DAEMON_DEST), - ) - .await - .map_err(Self::error)?; - - Ok(()) - } -} - -#[non_exhaustive] -#[derive(Debug, thiserror::Error)] -pub enum ConfigureEnterpriseEditionNixDaemonServiceError {} - -#[cfg(target_os = "macos")] -#[derive(Deserialize, Clone, Debug, Serialize, PartialEq)] -#[serde(rename_all = "PascalCase")] -pub struct DeterminateNixDaemonPlist { - label: String, - program: String, - keep_alive: bool, - run_at_load: bool, - standard_error_path: String, - standard_out_path: String, - soft_resource_limits: ResourceLimits, -} - -#[cfg(target_os = "macos")] -#[derive(Deserialize, Clone, Debug, Serialize, PartialEq)] -#[serde(rename_all = "PascalCase")] -pub struct ResourceLimits { - number_of_files: usize, -} - -#[cfg(target_os = "macos")] -fn generate_plist() -> DeterminateNixDaemonPlist { - DeterminateNixDaemonPlist { - keep_alive: true, - run_at_load: true, - label: "systems.determinate.nix-daemon".into(), - program: "/usr/local/bin/determinate-nix-ee".into(), - standard_error_path: "/var/log/determinate-nix-daemon.log".into(), - standard_out_path: "/var/log/determinate-nix-daemon.log".into(), - soft_resource_limits: ResourceLimits { - number_of_files: 1048576, - }, - } -} diff --git a/src/action/common/configure_init_service.rs b/src/action/common/configure_init_service.rs index 3abee5436..7daba3d07 100644 --- a/src/action/common/configure_init_service.rs +++ b/src/action/common/configure_init_service.rs @@ -1,70 +1,89 @@ -#[cfg(target_os = "linux")] use std::path::Path; use std::path::PathBuf; -#[cfg(target_os = "macos")] -use serde::{Deserialize, Serialize}; use tokio::process::Command; use tracing::{span, Span}; +use crate::action::macos::DARWIN_LAUNCHD_DOMAIN; use crate::action::{ActionError, ActionErrorKind, ActionTag, StatefulAction}; use crate::execute_command; 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"; -#[cfg(target_os = "linux")] -const SERVICE_DEST: &str = "/etc/systemd/system/nix-daemon.service"; -#[cfg(target_os = "linux")] -const SOCKET_SRC: &str = "/nix/var/nix/profiles/default/lib/systemd/system/nix-daemon.socket"; -#[cfg(target_os = "linux")] -const SOCKET_DEST: &str = "/etc/systemd/system/nix-daemon.socket"; -#[cfg(target_os = "linux")] 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"; -#[cfg(target_os = "macos")] -const DARWIN_NIX_DAEMON_DEST: &str = "/Library/LaunchDaemons/org.nixos.nix-daemon.plist"; -#[cfg(target_os = "macos")] -const DARWIN_NIX_DAEMON_SOURCE: &str = - "/nix/var/nix/profiles/default/Library/LaunchDaemons/org.nixos.nix-daemon.plist"; + +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +pub struct SocketFile { + pub name: String, + pub src: UnitSrc, + pub dest: PathBuf, +} + +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +pub enum UnitSrc { + Path(PathBuf), + Literal(String), +} + /** Configure the init to run the Nix daemon */ #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(tag = "action_name", rename = "configure_init_service")] pub struct ConfigureInitService { init: InitSystem, start_daemon: bool, + // TODO(cole-h): make an enum so we can distinguish between "written out by another step" vs "actually there isn't one" + service_src: Option, + service_name: Option, + service_dest: Option, + socket_files: Vec, } impl ConfigureInitService { - #[cfg(target_os = "linux")] - async fn check_if_systemd_unit_exists(src: &str, dest: &str) -> Result<(), ActionErrorKind> { + pub(crate) async fn check_if_systemd_unit_exists( + src: &UnitSrc, + dest: &Path, + ) -> Result<(), ActionErrorKind> { // TODO: once we have a way to communicate interaction between the library and the cli, // interactively ask for permission to remove the file - let unit_src = PathBuf::from(src); // NOTE: Check if the unit file already exists... let unit_dest = PathBuf::from(dest); if unit_dest.exists() { - if unit_dest.is_symlink() { - let link_dest = tokio::fs::read_link(&unit_dest) - .await - .map_err(|e| ActionErrorKind::ReadSymlink(unit_dest.clone(), e))?; - if link_dest != unit_src { - return Err(ActionErrorKind::SymlinkExists(unit_dest)); - } - } else { - return Err(ActionErrorKind::FileExists(unit_dest)); + match src { + UnitSrc::Path(unit_src) => { + if unit_dest.is_symlink() { + let link_dest = tokio::fs::read_link(&unit_dest) + .await + .map_err(|e| ActionErrorKind::ReadSymlink(unit_dest.clone(), e))?; + if link_dest != *unit_src { + return Err(ActionErrorKind::SymlinkExists(unit_dest)); + } + } else { + return Err(ActionErrorKind::FileExists(unit_dest)); + } + }, + UnitSrc::Literal(content) => { + if unit_dest.is_symlink() { + return Err(ActionErrorKind::FileExists(unit_dest)); + } else { + let actual_content = tokio::fs::read_to_string(&unit_dest) + .await + .map_err(|e| ActionErrorKind::Read(unit_dest.clone(), e))?; + if *content != actual_content { + return Err(ActionErrorKind::DifferentContent(unit_dest)); + } + } + }, } } // NOTE: ...and if there are any overrides in the most well-known places for systemd - if Path::new(&format!("{dest}.d")).exists() { - return Err(ActionErrorKind::DirExists(PathBuf::from(format!( - "{dest}.d" - )))); + let dest_d = format!("{dest}.d", dest = dest.display()); + if Path::new(&dest_d).exists() { + return Err(ActionErrorKind::DirExists(PathBuf::from(dest_d))); } Ok(()) @@ -74,13 +93,15 @@ impl ConfigureInitService { pub async fn plan( init: InitSystem, start_daemon: bool, + service_src: Option, + service_dest: Option, + service_name: Option, + socket_files: Vec, ) -> Result, ActionError> { match init { - #[cfg(target_os = "macos")] InitSystem::Launchd => { // No plan checks, yet }, - #[cfg(target_os = "linux")] InitSystem::Systemd => { // If `no_start_daemon` is set, then we don't require a running systemd, // so we don't need to check if `/run/systemd/system` exists. @@ -96,20 +117,37 @@ impl ConfigureInitService { return Err(Self::error(ActionErrorKind::SystemdMissing)); } - Self::check_if_systemd_unit_exists(SERVICE_SRC, SERVICE_DEST) - .await - .map_err(Self::error)?; - Self::check_if_systemd_unit_exists(SOCKET_SRC, SOCKET_DEST) + if let (Some(service_src), Some(service_dest)) = + (service_src.as_ref(), service_dest.as_ref()) + { + Self::check_if_systemd_unit_exists( + &UnitSrc::Path(service_src.to_path_buf()), + service_dest, + ) .await .map_err(Self::error)?; + } + + for SocketFile { src, dest, .. } in socket_files.iter() { + Self::check_if_systemd_unit_exists(src, dest) + .await + .map_err(Self::error)?; + } }, - #[cfg(target_os = "linux")] InitSystem::None => { // Nothing here, no init system }, }; - Ok(Self { init, start_daemon }.into()) + Ok(Self { + init, + start_daemon, + service_src, + service_dest, + service_name, + socket_files, + } + .into()) } } @@ -121,13 +159,10 @@ impl Action for ConfigureInitService { } fn tracing_synopsis(&self) -> 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(), } } @@ -139,30 +174,69 @@ impl Action for ConfigureInitService { fn execute_description(&self) -> Vec { let mut vec = Vec::new(); match self.init { - #[cfg(target_os = "linux")] InitSystem::Systemd => { let mut explanation = vec![ "Run `systemd-tmpfiles --create --prefix=/nix/var/nix`".to_string(), - format!("Symlink `{SERVICE_SRC}` to `{SERVICE_DEST}`"), - format!("Symlink `{SOCKET_SRC}` to `{SOCKET_DEST}`"), - "Run `systemctl daemon-reload`".to_string(), + format!( + "Symlink `{0}` to `{1}`", + self.service_src + .as_ref() + .expect("service_src should be defined for systemd") + .display(), + self.service_dest + .as_ref() + .expect("service_src should be defined for systemd") + .display() + ), ]; + + for SocketFile { src, dest, .. } in self.socket_files.iter() { + match src { + UnitSrc::Path(src) => { + explanation.push(format!( + "Symlink `{}` to `{}`", + src.display(), + dest.display() + )); + }, + UnitSrc::Literal(_) => { + explanation.push(format!("Create `{}`", dest.display())); + }, + } + } + explanation.push("Run `systemctl daemon-reload`".to_string()); + if self.start_daemon { - explanation.push(format!("Run `systemctl enable --now {SOCKET_SRC}`")); + for SocketFile { name, .. } in self.socket_files.iter() { + explanation.push(format!("Run `systemctl enable --now {}`", name)); + } } 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}`" - )]; + let mut explanation = vec![]; + if let Some(service_src) = self.service_src.as_ref() { + explanation.push(format!( + "Copy `{0}` to `{1}`", + service_src.display(), + self.service_dest + .as_ref() + .expect("service_dest should be defined for launchd") + .display(), + )); + } + if self.start_daemon { - explanation.push(format!("Run `launchctl load {DARWIN_NIX_DAEMON_DEST}`")); + explanation.push(format!( + "Run `launchctl bootstrap {0}`", + self.service_dest + .as_ref() + .expect("service_dest should be defined for launchd") + .display(), + )); } vec.push(ActionDescription::new(self.tracing_synopsis(), explanation)) }, - #[cfg(not(target_os = "macos"))] InitSystem::None => (), } vec @@ -170,33 +244,40 @@ impl Action for ConfigureInitService { #[tracing::instrument(level = "debug", skip_all)] async fn execute(&mut self) -> Result<(), ActionError> { - let Self { init, start_daemon } = self; + let Self { + init, + start_daemon, + service_src, + service_dest, + service_name, + socket_files, + } = self; match init { - #[cfg(target_os = "macos")] InitSystem::Launchd => { - let daemon_file = DARWIN_NIX_DAEMON_DEST; - let domain = "system"; - let service = "org.nixos.nix-daemon"; - let src = std::path::Path::new(DARWIN_NIX_DAEMON_SOURCE); - - tokio::fs::copy(src, daemon_file).await.map_err(|e| { - Self::error(ActionErrorKind::Copy( - src.to_path_buf(), - PathBuf::from(daemon_file), - e, - )) - })?; + let service_dest = service_dest + .as_ref() + .expect("service_dest should be set for Launchd"); + let service = service_name + .as_ref() + .expect("service_name should be set for Launchd"); + let domain = DARWIN_LAUNCHD_DOMAIN; + + if let Some(service_src) = service_src { + tokio::fs::copy(&service_src, service_dest) + .await + .map_err(|e| { + Self::error(ActionErrorKind::Copy( + service_src.clone(), + PathBuf::from(service_dest), + e, + )) + })?; + } - execute_command( - Command::new("launchctl") - .process_group(0) - .args(["load", "-w"]) - .arg(daemon_file) - .stdin(std::process::Stdio::null()), - ) - .await - .map_err(Self::error)?; + crate::action::macos::retry_bootstrap(domain, service, service_dest) + .await + .map_err(Self::error)?; let is_disabled = crate::action::macos::service_is_disabled(domain, service) .await @@ -226,8 +307,11 @@ impl Action for ConfigureInitService { .map_err(Self::error)?; } }, - #[cfg(target_os = "linux")] InitSystem::Systemd => { + let service_dest = service_dest + .as_ref() + .expect("service_dest should be defined for systemd"); + if *start_daemon { execute_command( Command::new("systemctl") @@ -238,30 +322,37 @@ impl Action for ConfigureInitService { .await .map_err(Self::error)?; } + // The goal state is the `socket` enabled and active, the service not enabled and stopped (it activates via socket activation) - if is_enabled("nix-daemon.socket").await.map_err(Self::error)? { - disable("nix-daemon.socket", false) - .await - .map_err(Self::error)?; - } - let socket_was_active = - if is_active("nix-daemon.socket").await.map_err(Self::error)? { - stop("nix-daemon.socket").await.map_err(Self::error)?; - true - } else { - false + let mut any_socket_was_active = false; + for SocketFile { name, .. } in socket_files.iter() { + let is_active = is_active(name).await.map_err(Self::error)?; + + if is_enabled(name).await.map_err(Self::error)? { + disable(name, is_active).await.map_err(Self::error)?; + } else if is_active { + stop(name).await.map_err(Self::error)?; }; - if is_enabled("nix-daemon.service") - .await - .map_err(Self::error)? + + if is_active { + any_socket_was_active = true; + } + } + { - let now = is_active("nix-daemon.service").await.map_err(Self::error)?; - disable("nix-daemon.service", now) + let is_active = is_active("nix-daemon.service").await.map_err(Self::error)?; + + if is_enabled("nix-daemon.service") .await - .map_err(Self::error)?; - } else if is_active("nix-daemon.service").await.map_err(Self::error)? { - stop("nix-daemon.service").await.map_err(Self::error)?; - }; + .map_err(Self::error)? + { + disable("nix-daemon.service", is_active) + .await + .map_err(Self::error)?; + } else if is_active { + stop("nix-daemon.service").await.map_err(Self::error)?; + }; + } tracing::trace!(src = TMPFILES_SRC, dest = TMPFILES_DEST, "Symlinking"); if !Path::new(TMPFILES_DEST).exists() { @@ -290,49 +381,69 @@ impl Action for ConfigureInitService { // TODO: once we have a way to communicate interaction between the library and the // cli, interactively ask for permission to remove the file - Self::check_if_systemd_unit_exists(SERVICE_SRC, SERVICE_DEST) + if let Some(service_src) = service_src.as_ref() { + Self::check_if_systemd_unit_exists( + &UnitSrc::Path(service_src.to_path_buf()), + service_dest, + ) .await .map_err(Self::error)?; - if Path::new(SERVICE_DEST).exists() { - tracing::trace!(path = %SERVICE_DEST, "Removing"); - tokio::fs::remove_file(SERVICE_DEST) + if Path::new(service_dest).exists() { + tracing::trace!(path = %service_dest.display(), "Removing"); + tokio::fs::remove_file(service_dest) + .await + .map_err(|e| ActionErrorKind::Remove(service_dest.into(), e)) + .map_err(Self::error)?; + } + tracing::trace!(src = %service_src.display(), dest = %service_dest.display(), "Symlinking"); + tokio::fs::symlink(service_src, service_dest) .await - .map_err(|e| ActionErrorKind::Remove(SERVICE_DEST.into(), e)) + .map_err(|e| { + ActionErrorKind::Symlink( + service_src.clone(), + PathBuf::from(service_dest), + e, + ) + }) .map_err(Self::error)?; } - tracing::trace!(src = %SERVICE_SRC, dest = %SERVICE_DEST, "Symlinking"); - tokio::fs::symlink(SERVICE_SRC, SERVICE_DEST) - .await - .map_err(|e| { - ActionErrorKind::Symlink( - PathBuf::from(SERVICE_SRC), - PathBuf::from(SERVICE_DEST), - e, - ) - }) - .map_err(Self::error)?; - Self::check_if_systemd_unit_exists(SOCKET_SRC, SOCKET_DEST) - .await - .map_err(Self::error)?; - if Path::new(SOCKET_DEST).exists() { - tracing::trace!(path = %SOCKET_DEST, "Removing"); - tokio::fs::remove_file(SOCKET_DEST) + + for SocketFile { src, dest, .. } in socket_files.iter() { + Self::check_if_systemd_unit_exists(src, dest) .await - .map_err(|e| ActionErrorKind::Remove(SOCKET_DEST.into(), e)) .map_err(Self::error)?; - } + if Path::new(dest).exists() { + tracing::trace!(path = %dest.display(), "Removing"); + tokio::fs::remove_file(dest) + .await + .map_err(|e| ActionErrorKind::Remove(dest.into(), e)) + .map_err(Self::error)?; + } - tracing::trace!(src = %SOCKET_SRC, dest = %SOCKET_DEST, "Symlinking"); - tokio::fs::symlink(SOCKET_SRC, SOCKET_DEST) - .await - .map_err(|e| { - ActionErrorKind::Symlink( - PathBuf::from(SOCKET_SRC), - PathBuf::from(SOCKET_DEST), - e, - ) - }) - .map_err(Self::error)?; + match src { + UnitSrc::Path(src) => { + tracing::trace!(src = %src.display(), dest = %dest.display(), "Symlinking"); + tokio::fs::symlink(src, dest) + .await + .map_err(|e| { + ActionErrorKind::Symlink( + PathBuf::from(src), + PathBuf::from(dest), + e, + ) + }) + .map_err(Self::error)?; + }, + UnitSrc::Literal(content) => { + tracing::trace!(src = %content, dest = %dest.display(), "Writing"); + + tokio::fs::write(&dest, content) + .await + .map_err(|e| ActionErrorKind::Write(dest.clone(), e)) + .map_err(Self::error)?; + }, + } + } if *start_daemon { execute_command( @@ -345,13 +456,28 @@ impl Action for ConfigureInitService { .map_err(Self::error)?; } - if *start_daemon || socket_was_active { - enable(SOCKET_SRC, true).await.map_err(Self::error)?; - } else { - enable(SOCKET_SRC, false).await.map_err(Self::error)?; + for SocketFile { name, src, .. } in socket_files.iter() { + let enable_now = *start_daemon || any_socket_was_active; + + match src { + UnitSrc::Path(path) => { + // NOTE(cole-h): we have to enable by path here because older systemd's + // (e.g. on our Ubuntu 16.04 test VMs) had faulty (or too- strict) + // symlink detection, which causes the symlink chain of + // `/etc/systemd/system/nix-daemon.socket` -> + // `/nix/var/nix/profiles/default` -> `/nix/store/............/nix- + // daemon.socket` to fail with "Failed to execute operation: Too many + // levels of symbolic links" + enable(path.display().to_string().as_ref(), enable_now) + .await + .map_err(Self::error)?; + }, + UnitSrc::Literal(_) => { + enable(name, enable_now).await.map_err(Self::error)?; + }, + } } }, - #[cfg(not(target_os = "macos"))] InitSystem::None => { // Nothing here, no init system }, @@ -362,87 +488,124 @@ impl Action for ConfigureInitService { fn revert_description(&self) -> Vec { match self.init { - #[cfg(target_os = "linux")] InitSystem::Systemd => { + let mut steps = vec![]; + + for SocketFile { name, .. } in self.socket_files.iter() { + steps.push(format!("Run `systemctl disable {}`", name)); + } + + steps.push(format!( + "Run `systemctl disable {0}`", + self.service_src + .as_ref() + .expect("service_src should be defined for systemd") + .display() + )); + steps.push("Run `systemd-tempfiles --remove --prefix=/nix/var/nix`".to_string()); + steps.push("Run `systemctl daemon-reload`".to_string()); + vec![ActionDescription::new( "Unconfigure Nix daemon related settings with systemd".to_string(), - vec![ - format!("Run `systemctl disable {SOCKET_SRC}`"), - format!("Run `systemctl disable {SERVICE_SRC}`"), - "Run `systemd-tempfiles --remove --prefix=/nix/var/nix`".to_string(), - "Run `systemctl daemon-reload`".to_string(), - ], + steps, )] }, - #[cfg(target_os = "macos")] InitSystem::Launchd => { vec![ActionDescription::new( "Unconfigure Nix daemon related settings with launchctl".to_string(), - vec![format!("Run `launchctl unload {DARWIN_NIX_DAEMON_DEST}`")], + vec![format!( + "Run `launchctl bootout {DARWIN_LAUNCHD_DOMAIN}/{0}`", + self.service_name + .as_ref() + .expect("service_name should be defined for launchd"), + )], )] }, - #[cfg(not(target_os = "macos"))] InitSystem::None => Vec::new(), } } #[tracing::instrument(level = "debug", skip_all)] async fn revert(&mut self) -> Result<(), ActionError> { - #[cfg_attr(target_os = "macos", allow(unused_mut))] let mut errors = vec![]; match self.init { - #[cfg(target_os = "macos")] InitSystem::Launchd => { - execute_command( - Command::new("launchctl") - .process_group(0) - .arg("unload") - .arg(DARWIN_NIX_DAEMON_DEST), + crate::action::macos::retry_bootout( + DARWIN_LAUNCHD_DOMAIN, + self.service_name + .as_ref() + .expect("service_name should be set for launchd"), + self.service_dest + .as_ref() + .expect("service_dest should be defined for launchd"), ) .await .map_err(Self::error)?; + + // check if the daemon is down up to 99 times, with 100ms of delay between each attempt + for attempt in 1..100 { + tracing::trace!(attempt, "Checking to see if the daemon is down yet"); + if execute_command( + Command::new("launchctl").process_group(0).arg("print").arg( + [ + DARWIN_LAUNCHD_DOMAIN, + self.service_name + .as_ref() + .expect("service_name should be defined for launchd"), + ] + .join("/"), + ), + ) + .await + .is_err() + { + tracing::trace!(attempt, "Daemon is down"); + break; + } + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + } }, - #[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. // These have to fail fast. - let socket_is_active = is_active("nix-daemon.socket").await.map_err(Self::error)?; - let socket_is_enabled = - is_enabled("nix-daemon.socket").await.map_err(Self::error)?; + for SocketFile { name, .. } in self.socket_files.iter() { + let socket_is_active = is_active(name).await.map_err(Self::error)?; + let socket_is_enabled = is_enabled(name).await.map_err(Self::error)?; + + if socket_is_active { + if let Err(err) = execute_command( + Command::new("systemctl") + .process_group(0) + .args(["stop", name]) + .stdin(std::process::Stdio::null()), + ) + .await + { + errors.push(err); + } + } + + if socket_is_enabled { + if let Err(err) = execute_command( + Command::new("systemctl") + .process_group(0) + .args(["disable", name]) + .stdin(std::process::Stdio::null()), + ) + .await + { + errors.push(err); + } + } + } let service_is_active = is_active("nix-daemon.service").await.map_err(Self::error)?; let service_is_enabled = is_enabled("nix-daemon.service") .await .map_err(Self::error)?; - if socket_is_active { - if let Err(err) = execute_command( - Command::new("systemctl") - .process_group(0) - .args(["stop", "nix-daemon.socket"]) - .stdin(std::process::Stdio::null()), - ) - .await - { - errors.push(err); - } - } - - if socket_is_enabled { - if let Err(err) = execute_command( - Command::new("systemctl") - .process_group(0) - .args(["disable", "nix-daemon.socket"]) - .stdin(std::process::Stdio::null()), - ) - .await - { - errors.push(err); - } - } - if service_is_active { if let Err(err) = execute_command( Command::new("systemctl") @@ -481,6 +644,16 @@ impl Action for ConfigureInitService { errors.push(err); } + for socket in self.socket_files.iter() { + if let UnitSrc::Literal(_) = socket.src { + tracing::trace!(path = %socket.dest.display(), "Removing"); + tokio::fs::remove_file(&socket.dest) + .await + .map_err(|e| ActionErrorKind::Remove(socket.dest.to_path_buf(), e)) + .map_err(Self::error)?; + } + } + if let Err(err) = tokio::fs::remove_file(TMPFILES_DEST) .await .map_err(|e| ActionErrorKind::Remove(PathBuf::from(TMPFILES_DEST), e)) @@ -499,7 +672,6 @@ impl Action for ConfigureInitService { errors.push(err); } }, - #[cfg(not(target_os = "macos"))] InitSystem::None => { // Nothing here, no init }, @@ -527,27 +699,6 @@ pub enum ConfigureNixDaemonServiceError { InitNotSupported, } -#[cfg(target_os = "macos")] -#[derive(Deserialize, Clone, Debug, Serialize, PartialEq)] -#[serde(rename_all = "PascalCase")] -pub struct DeterminateNixDaemonPlist { - label: String, - program: String, - keep_alive: bool, - run_at_load: bool, - standard_error_path: String, - standard_out_path: String, - soft_resource_limits: ResourceLimits, -} - -#[cfg(target_os = "macos")] -#[derive(Deserialize, Clone, Debug, Serialize, PartialEq)] -#[serde(rename_all = "PascalCase")] -pub struct ResourceLimits { - number_of_files: usize, -} - -#[cfg(target_os = "linux")] async fn stop(unit: &str) -> Result<(), ActionErrorKind> { let mut command = Command::new("systemctl"); command.arg("stop"); @@ -565,7 +716,6 @@ async fn stop(unit: &str) -> Result<(), ActionErrorKind> { } } -#[cfg(target_os = "linux")] async fn enable(unit: &str, now: bool) -> Result<(), ActionErrorKind> { let mut command = Command::new("systemctl"); command.arg("enable"); @@ -579,14 +729,13 @@ async fn enable(unit: &str, now: bool) -> Result<(), ActionErrorKind> { .map_err(|e| ActionErrorKind::command(&command, e))?; match output.status.success() { true => { - tracing::trace!(%unit, %now, "Enabled unit"); + tracing::trace!(unit = %unit, %now, "Enabled unit"); Ok(()) }, false => Err(ActionErrorKind::command_output(&command, output)), } } -#[cfg(target_os = "linux")] async fn disable(unit: &str, now: bool) -> Result<(), ActionErrorKind> { let mut command = Command::new("systemctl"); command.arg("disable"); @@ -607,7 +756,6 @@ async fn disable(unit: &str, now: bool) -> Result<(), ActionErrorKind> { } } -#[cfg(target_os = "linux")] async fn is_active(unit: &str) -> Result { let mut command = Command::new("systemctl"); command.arg("is-active"); @@ -625,7 +773,6 @@ async fn is_active(unit: &str) -> Result { } } -#[cfg(target_os = "linux")] async fn is_enabled(unit: &str) -> Result { let mut command = Command::new("systemctl"); command.arg("is-enabled"); diff --git a/src/action/common/configure_nix.rs b/src/action/common/configure_nix.rs index cc2b8eae5..982f90049 100644 --- a/src/action/common/configure_nix.rs +++ b/src/action/common/configure_nix.rs @@ -19,6 +19,7 @@ use tracing::{span, Instrument, Span}; Configure Nix and start it */ #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(tag = "action_name", rename = "configure_nix")] pub struct ConfigureNix { setup_default_profile: StatefulAction, configure_shell_profile: Option>, @@ -31,6 +32,7 @@ impl ConfigureNix { pub async fn plan( shell_profile_locations: ShellProfileLocations, settings: &CommonSettings, + extra_internal_conf: Option, ) -> Result, ActionError> { let setup_default_profile = SetupDefaultProfile::plan(PathBuf::from(SCRATCH_DIR)) .await @@ -49,6 +51,7 @@ impl ConfigureNix { settings.nix_build_group_name.clone(), settings.proxy.clone(), settings.ssl_cert_file.clone(), + extra_internal_conf.clone(), settings.extra_conf.clone(), settings.force, ) diff --git a/src/action/common/configure_shell_profile.rs b/src/action/common/configure_shell_profile.rs index 70c730032..1dfb9b3c5 100644 --- a/src/action/common/configure_shell_profile.rs +++ b/src/action/common/configure_shell_profile.rs @@ -16,6 +16,7 @@ const PROFILE_NIX_FILE_FISH: &str = "/nix/var/nix/profiles/default/etc/profile.d Configure any detected shell profiles to include Nix support */ #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(tag = "action_name", rename = "configure_shell_profile")] pub struct ConfigureShellProfile { locations: ShellProfileLocations, create_directories: Vec>, diff --git a/src/action/common/configure_upstream_init_service.rs b/src/action/common/configure_upstream_init_service.rs new file mode 100644 index 000000000..10a67c5e4 --- /dev/null +++ b/src/action/common/configure_upstream_init_service.rs @@ -0,0 +1,119 @@ +use std::path::PathBuf; + +use tracing::{span, Span}; + +use crate::action::{ActionError, ActionTag, StatefulAction}; + +use crate::action::common::configure_init_service::{SocketFile, UnitSrc}; +use crate::action::{common::ConfigureInitService, Action, ActionDescription}; +use crate::settings::InitSystem; + +// Linux +const SERVICE_SRC: &str = "/nix/var/nix/profiles/default/lib/systemd/system/nix-daemon.service"; +const SERVICE_DEST: &str = "/etc/systemd/system/nix-daemon.service"; + +// Darwin +const DARWIN_NIX_DAEMON_SOURCE: &str = + "/nix/var/nix/profiles/default/Library/LaunchDaemons/org.nixos.nix-daemon.plist"; +const DARWIN_NIX_DAEMON_DEST: &str = "/Library/LaunchDaemons/org.nixos.nix-daemon.plist"; +const DARWIN_LAUNCHD_SERVICE_NAME: &str = "org.nixos.nix-daemon"; + +/** +Configure the init to run the Nix daemon +*/ +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(tag = "action_name", rename = "create_upstream_init_service")] +pub struct ConfigureUpstreamInitService { + configure_init_service: StatefulAction, +} + +impl ConfigureUpstreamInitService { + #[tracing::instrument(level = "debug", skip_all)] + pub async fn plan( + init: InitSystem, + start_daemon: bool, + ) -> Result, ActionError> { + let service_src: Option = match init { + InitSystem::Launchd => Some(DARWIN_NIX_DAEMON_SOURCE.into()), + InitSystem::Systemd => Some(SERVICE_SRC.into()), + InitSystem::None => None, + }; + let service_dest: Option = match init { + InitSystem::Launchd => Some(DARWIN_NIX_DAEMON_DEST.into()), + InitSystem::Systemd => Some(SERVICE_DEST.into()), + InitSystem::None => None, + }; + let service_name: Option = match init { + InitSystem::Launchd => Some(DARWIN_LAUNCHD_SERVICE_NAME.into()), + _ => None, + }; + + let configure_init_service = ConfigureInitService::plan( + init, + start_daemon, + service_src, + service_dest, + service_name, + vec![SocketFile { + name: "nix-daemon.socket".into(), + src: UnitSrc::Path( + "/nix/var/nix/profiles/default/lib/systemd/system/nix-daemon.socket".into(), + ), + dest: "/etc/systemd/system/nix-daemon.socket".into(), + }], + ) + .await + .map_err(Self::error)?; + + Ok(Self { + configure_init_service, + } + .into()) + } +} + +#[async_trait::async_trait] +#[typetag::serde(name = "create_upstream_init_service")] +impl Action for ConfigureUpstreamInitService { + fn action_tag() -> ActionTag { + ActionTag("create_upstream_init_service") + } + fn tracing_synopsis(&self) -> String { + "Configure upstream Nix daemon service".to_string() + } + + fn tracing_span(&self) -> Span { + span!(tracing::Level::DEBUG, "create_upstream_init_service",) + } + + fn execute_description(&self) -> Vec { + vec![ActionDescription::new( + self.tracing_synopsis(), + vec![self.configure_init_service.tracing_synopsis()], + )] + } + + #[tracing::instrument(level = "debug", skip_all)] + async fn execute(&mut self) -> Result<(), ActionError> { + self.configure_init_service + .try_execute() + .await + .map_err(Self::error)?; + + Ok(()) + } + + fn revert_description(&self) -> Vec { + vec![ActionDescription::new( + "Remove upstream Nix daemon service".to_string(), + vec![self.configure_init_service.tracing_synopsis()], + )] + } + + #[tracing::instrument(level = "debug", skip_all)] + async fn revert(&mut self) -> Result<(), ActionError> { + self.configure_init_service.try_revert().await?; + + Ok(()) + } +} diff --git a/src/action/common/create_nix_tree.rs b/src/action/common/create_nix_tree.rs index f35022e56..77991021a 100644 --- a/src/action/common/create_nix_tree.rs +++ b/src/action/common/create_nix_tree.rs @@ -25,6 +25,7 @@ const PATHS: &[&str] = &[ Create the `/nix` tree */ #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(tag = "action_name", rename = "create_nix_tree")] pub struct CreateNixTree { create_directories: Vec>, } diff --git a/src/action/common/create_users_and_groups.rs b/src/action/common/create_users_and_groups.rs index 6a10a5063..47d5d8f7b 100644 --- a/src/action/common/create_users_and_groups.rs +++ b/src/action/common/create_users_and_groups.rs @@ -8,15 +8,16 @@ use crate::{ use tracing::{span, Span}; #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(tag = "action_name", rename = "create_users_and_group")] pub struct CreateUsersAndGroups { - nix_build_user_count: u32, - nix_build_group_name: String, - nix_build_group_id: u32, - nix_build_user_prefix: String, - nix_build_user_id_base: u32, - create_group: StatefulAction, - create_users: Vec>, - add_users_to_groups: Vec>, + pub(crate) nix_build_group_name: String, + pub(crate) nix_build_group_id: u32, + pub(crate) nix_build_user_count: u32, + pub(crate) nix_build_user_prefix: String, + pub(crate) nix_build_user_id_base: u32, + pub(crate) create_group: StatefulAction, + pub(crate) create_users: Vec>, + pub(crate) add_users_to_groups: Vec>, } impl CreateUsersAndGroups { @@ -36,6 +37,7 @@ impl CreateUsersAndGroups { settings.nix_build_group_name.clone(), settings.nix_build_group_id, format!("Nix build user {index}"), + true, ) .await .map_err(Self::error)?, diff --git a/src/action/common/delete_users.rs b/src/action/common/delete_users.rs index 79ea22fe2..a644eed92 100644 --- a/src/action/common/delete_users.rs +++ b/src/action/common/delete_users.rs @@ -5,6 +5,7 @@ use crate::action::{ use tracing::{span, Span}; #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(tag = "action_name", rename = "delete_users_in_group")] pub struct DeleteUsersInGroup { group_name: String, group_id: u32, diff --git a/src/action/common/mod.rs b/src/action/common/mod.rs index 1bc7ca45e..48fc4a5b4 100644 --- a/src/action/common/mod.rs +++ b/src/action/common/mod.rs @@ -1,25 +1,27 @@ //! [`Action`](crate::action::Action)s which only call other base plugins -#[cfg(all(target_os = "macos", not(feature = "nix-community")))] -pub(crate) mod configure_enterprise_edition_init_service; +pub(crate) mod configure_determinate_nixd_init_service; pub(crate) mod configure_init_service; pub(crate) mod configure_nix; pub(crate) mod configure_shell_profile; +pub(crate) mod configure_upstream_init_service; pub(crate) mod create_nix_tree; pub(crate) mod create_users_and_groups; pub(crate) mod delete_users; pub(crate) mod place_nix_configuration; +pub(crate) mod provision_determinate_nixd; pub(crate) mod provision_nix; pub(crate) mod setup_channels; -#[cfg(all(target_os = "macos", not(feature = "nix-community")))] -pub use configure_enterprise_edition_init_service::ConfigureEnterpriseEditionInitService; +pub use configure_determinate_nixd_init_service::ConfigureDeterminateNixdInitService; pub use configure_init_service::{ConfigureInitService, ConfigureNixDaemonServiceError}; pub use configure_nix::ConfigureNix; pub use configure_shell_profile::ConfigureShellProfile; +pub use configure_upstream_init_service::ConfigureUpstreamInitService; pub use create_nix_tree::CreateNixTree; pub use create_users_and_groups::CreateUsersAndGroups; pub use delete_users::DeleteUsersInGroup; pub use place_nix_configuration::PlaceNixConfiguration; +pub use provision_determinate_nixd::ProvisionDeterminateNixd; pub use provision_nix::ProvisionNix; pub use setup_channels::SetupChannels; diff --git a/src/action/common/place_nix_configuration.rs b/src/action/common/place_nix_configuration.rs index 78843687e..525b58c94 100644 --- a/src/action/common/place_nix_configuration.rs +++ b/src/action/common/place_nix_configuration.rs @@ -10,13 +10,14 @@ use crate::parse_ssl_cert; use crate::settings::UrlOrPathOrString; use std::path::PathBuf; -const NIX_CONF_FOLDER: &str = "/etc/nix"; +pub const NIX_CONF_FOLDER: &str = "/etc/nix"; const NIX_CONF: &str = "/etc/nix/nix.conf"; /** Place the `/etc/nix.conf` file */ #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(tag = "action_name", rename = "place_nix_configuration")] pub struct PlaceNixConfiguration { create_directory: StatefulAction, create_or_merge_nix_config: StatefulAction, @@ -28,9 +29,39 @@ impl PlaceNixConfiguration { nix_build_group_name: String, proxy: Option, ssl_cert_file: Option, + extra_internal_conf: Option, extra_conf: Vec, force: bool, ) -> Result, ActionError> { + let nix_config = Self::setup_nix_config( + nix_build_group_name, + proxy, + ssl_cert_file, + extra_internal_conf, + extra_conf, + ) + .await?; + + let create_directory = CreateDirectory::plan(NIX_CONF_FOLDER, None, None, 0o0755, force) + .await + .map_err(Self::error)?; + let create_or_merge_nix_config = CreateOrMergeNixConfig::plan(NIX_CONF, nix_config) + .await + .map_err(Self::error)?; + Ok(Self { + create_directory, + create_or_merge_nix_config, + } + .into()) + } + + async fn setup_nix_config( + nix_build_group_name: String, + proxy: Option, + ssl_cert_file: Option, + extra_internal_conf: Option, + extra_conf: Vec, + ) -> Result { let mut extra_conf_text = vec![]; for extra in extra_conf { let buf = match &extra { @@ -87,15 +118,20 @@ impl PlaceNixConfiguration { let mut nix_config = nix_config_parser::NixConfig::parse_string(extra_conf, None) .map_err(CreateOrMergeNixConfigError::ParseNixConfig) .map_err(Self::error)?; + let settings = nix_config.settings_mut(); + if let Some(extra) = extra_internal_conf { + settings.extend(extra.into_settings().into_iter()); + } + settings.insert("build-users-group".to_string(), nix_build_group_name); #[cfg(not(feature = "nix-community"))] { use indexmap::map::Entry; - let experimental_features = ["nix-command", "flakes", "repl-flake"]; + let experimental_features = ["nix-command", "flakes"]; match settings.entry("experimental-features".to_string()) { Entry::Occupied(mut slot) => { let slot_mut = slot.get_mut(); @@ -115,6 +151,58 @@ impl PlaceNixConfiguration { #[cfg(not(target_os = "macos"))] settings.insert("auto-optimise-store".to_string(), "true".to_string()); + // https://github.com/NixOS/nix/pull/8047 + settings.insert("always-allow-substitutes".to_string(), "true".to_string()); + + // base, unintrusive Determinate Nix options + { + // Add FlakeHub cache to the list of possible substituters, but disabled by default. + // This allows a user to turn on FlakeHub Cache by adding it to the `extra-substituters` + // list without being a trusted user. + let extra_trusted_substituters = ["https://cache.flakehub.com"]; + match settings.entry("extra-trusted-substituters".to_string()) { + Entry::Occupied(mut slot) => { + let slot_mut = slot.get_mut(); + for extra_trusted_substituter in extra_trusted_substituters { + if !slot_mut.contains(extra_trusted_substituter) { + *slot_mut += " "; + *slot_mut += extra_trusted_substituter; + } + } + }, + Entry::Vacant(slot) => { + let _ = slot.insert(extra_trusted_substituters.join(" ")); + }, + }; + + // Add FlakeHub's cache signing keys to the allowed list, but unused unless a user + // specifies FlakeHub Cache as an `extra-substituter`. + let extra_trusted_public_keys = [ + "cache.flakehub.com-3:hJuILl5sVK4iKm86JzgdXW12Y2Hwd5G07qKtHTOcDCM=", + "cache.flakehub.com-4:Asi8qIv291s0aYLyH6IOnr5Kf6+OF14WVjkE6t3xMio=", + "cache.flakehub.com-5:zB96CRlL7tiPtzA9/WKyPkp3A2vqxqgdgyTVNGShPDU=", + "cache.flakehub.com-6:W4EGFwAGgBj3he7c5fNh9NkOXw0PUVaxygCVKeuvaqU=", + "cache.flakehub.com-7:mvxJ2DZVHn/kRxlIaxYNMuDG1OvMckZu32um1TadOR8=", + "cache.flakehub.com-8:moO+OVS0mnTjBTcOUh2kYLQEd59ExzyoW1QgQ8XAARQ=", + "cache.flakehub.com-9:wChaSeTI6TeCuV/Sg2513ZIM9i0qJaYsF+lZCXg0J6o=", + "cache.flakehub.com-10:2GqeNlIp6AKp4EF2MVbE1kBOp9iBSyo0UPR9KoR0o1Y=", + ]; + match settings.entry("extra-trusted-public-keys".to_string()) { + Entry::Occupied(mut slot) => { + let slot_mut = slot.get_mut(); + for extra_trusted_public_key in extra_trusted_public_keys { + if !slot_mut.contains(extra_trusted_public_key) { + *slot_mut += " "; + *slot_mut += extra_trusted_public_key; + } + } + }, + Entry::Vacant(slot) => { + let _ = slot.insert(extra_trusted_public_keys.join(" ")); + }, + }; + } + settings.insert( "bash-prompt-prefix".to_string(), "(nix:$name)\\040".to_string(), @@ -140,17 +228,7 @@ impl PlaceNixConfiguration { ); } - let create_directory = CreateDirectory::plan(NIX_CONF_FOLDER, None, None, 0o0755, force) - .await - .map_err(Self::error)?; - let create_or_merge_nix_config = CreateOrMergeNixConfig::plan(NIX_CONF, nix_config) - .await - .map_err(Self::error)?; - Ok(Self { - create_directory, - create_or_merge_nix_config, - } - .into()) + Ok(nix_config) } } @@ -235,3 +313,43 @@ impl Action for PlaceNixConfiguration { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn extra_trusted_no_error() -> eyre::Result<()> { + let nix_config = PlaceNixConfiguration::setup_nix_config( + String::from("foo"), + None, + None, + None, + vec![ + UrlOrPathOrString::String(String::from("extra-trusted-substituters = barfoo")), + UrlOrPathOrString::String(String::from("extra-trusted-public-keys = foobar")), + ], + ) + .await?; + + assert!( + nix_config + .settings() + .get("extra-trusted-substituters") + .unwrap() + .contains("barfoo"), + "User config and internal defaults are both respected" + ); + + assert!( + nix_config + .settings() + .get("extra-trusted-public-keys") + .unwrap() + .contains("foobar"), + "User config and internal defaults are both respected" + ); + + Ok(()) + } +} diff --git a/src/action/common/provision_determinate_nixd.rs b/src/action/common/provision_determinate_nixd.rs new file mode 100644 index 000000000..04fc31dfc --- /dev/null +++ b/src/action/common/provision_determinate_nixd.rs @@ -0,0 +1,110 @@ +use std::os::unix::fs::PermissionsExt; +use std::path::PathBuf; + +use tokio::fs::{create_dir_all, remove_file}; +use tracing::{span, Span}; + +use crate::action::{ + Action, ActionDescription, ActionError, ActionErrorKind, ActionTag, StatefulAction, +}; + +const DETERMINATE_NIXD_BINARY_PATH: &str = "/usr/local/bin/determinate-nixd"; +/** +Provision the determinate-nixd binary +*/ +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(tag = "action_name", rename = "provision_determinate_nixd")] +pub struct ProvisionDeterminateNixd { + binary_location: PathBuf, +} + +impl ProvisionDeterminateNixd { + #[tracing::instrument(level = "debug", skip_all)] + pub async fn plan() -> Result, ActionError> { + crate::settings::DETERMINATE_NIXD_BINARY + .ok_or_else(|| Self::error(ActionErrorKind::DeterminateNixUnavailable))?; + + let this = Self { + binary_location: DETERMINATE_NIXD_BINARY_PATH.into(), + }; + + Ok(StatefulAction::uncompleted(this)) + } +} + +#[async_trait::async_trait] +#[typetag::serde(name = "provision_determinate_nixd")] +impl Action for ProvisionDeterminateNixd { + fn action_tag() -> ActionTag { + ActionTag("provision_determinate_nixd") + } + fn tracing_synopsis(&self) -> String { + "Install Determinate Nixd".to_string() + } + + fn tracing_span(&self) -> Span { + span!( + tracing::Level::DEBUG, + "provision_determinate_nixd", + location = ?self.binary_location, + ) + } + + fn execute_description(&self) -> Vec { + vec![ActionDescription::new( + self.tracing_synopsis(), + vec![format!("Enable Determinate Nix superpowers")], + )] + } + + #[tracing::instrument(level = "debug", skip_all)] + async fn execute(&mut self) -> Result<(), ActionError> { + let bytes = crate::settings::DETERMINATE_NIXD_BINARY + .ok_or_else(|| Self::error(ActionErrorKind::DeterminateNixUnavailable))?; + + if self.binary_location.exists() { + remove_file(&self.binary_location) + .await + .map_err(|e| ActionErrorKind::Remove(self.binary_location.clone(), e)) + .map_err(Self::error)?; + } + + if let Some(parent) = self.binary_location.parent() { + create_dir_all(&parent) + .await + .map_err(|e| ActionErrorKind::CreateDirectory(parent.into(), e)) + .map_err(Self::error)?; + } + + tokio::fs::write(&self.binary_location, bytes) + .await + .map_err(|e| ActionErrorKind::Write(self.binary_location.clone(), e)) + .map_err(Self::error)?; + + tokio::fs::set_permissions(&self.binary_location, PermissionsExt::from_mode(0o555)) + .await + .map_err(|e| ActionErrorKind::Write(self.binary_location.clone(), e)) + .map_err(Self::error)?; + + Ok(()) + } + + fn revert_description(&self) -> Vec { + vec![ActionDescription::new( + "Remove the Determinate Nix superpowers".into(), + vec![], + )] + } + + #[tracing::instrument(level = "debug", skip_all)] + async fn revert(&mut self) -> Result<(), ActionError> { + if self.binary_location.exists() { + remove_file(&self.binary_location) + .await + .map_err(|e| ActionErrorKind::Remove(self.binary_location.clone(), e)) + .map_err(Self::error)?; + } + + Ok(()) + } +} diff --git a/src/action/common/provision_nix.rs b/src/action/common/provision_nix.rs index e05a209c7..7288a31f6 100644 --- a/src/action/common/provision_nix.rs +++ b/src/action/common/provision_nix.rs @@ -14,6 +14,7 @@ use std::path::PathBuf; Place Nix and it's requirements onto the target */ #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(tag = "action_name", rename = "provision_nix")] pub struct ProvisionNix { fetch_nix: StatefulAction, create_nix_tree: StatefulAction, diff --git a/src/action/linux/provision_selinux.rs b/src/action/linux/provision_selinux.rs index fee5c2503..756177e27 100644 --- a/src/action/linux/provision_selinux.rs +++ b/src/action/linux/provision_selinux.rs @@ -9,20 +9,30 @@ use crate::execute_command; use crate::action::{Action, ActionDescription, StatefulAction}; -const SE_LINUX_POLICY_PP_CONTENT: &[u8] = include_bytes!("selinux/nix.pp"); +pub const SELINUX_POLICY_PP_CONTENT: &[u8] = include_bytes!("selinux/nix.pp"); +pub const DETERMINATE_SELINUX_POLICY_PP_CONTENT: &[u8] = + include_bytes!("selinux/determinate-nix.pp"); /** Provision the selinux/nix.pp for SELinux compatibility */ #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(tag = "action_name", rename = "provision_selinux")] pub struct ProvisionSelinux { policy_path: PathBuf, + policy_content: Vec, } impl ProvisionSelinux { #[tracing::instrument(level = "debug", skip_all)] - pub async fn plan(policy_path: PathBuf) -> Result, ActionError> { - let this = Self { policy_path }; + pub async fn plan( + policy_path: PathBuf, + policy_content: &[u8], + ) -> Result, ActionError> { + let this = Self { + policy_path, + policy_content: policy_content.to_vec(), + }; // Note: `restorecon` requires us to not just skip this, even if everything is in place. @@ -73,7 +83,7 @@ impl Action for ProvisionSelinux { .map_err(Self::error)?; } - tokio::fs::write(&self.policy_path, SE_LINUX_POLICY_PP_CONTENT) + tokio::fs::write(&self.policy_path, &self.policy_content) .await .map_err(|e| ActionErrorKind::Write(self.policy_path.clone(), e)) .map_err(Self::error)?; diff --git a/src/action/linux/selinux/README.md b/src/action/linux/selinux/README.md index 9da2f854d..d7e3b10ef 100644 --- a/src/action/linux/selinux/README.md +++ b/src/action/linux/selinux/README.md @@ -6,4 +6,4 @@ To refresh the output `pp` file: ## Method -We use the same method and definitions as https://github.com/nix-community/nix-installers/tree/master/selinux. \ No newline at end of file +We use the same method and definitions as https://github.com/nix-community/nix-installers/tree/master/selinux. diff --git a/src/action/linux/selinux/build.sh b/src/action/linux/selinux/build.sh index b44b15f94..ef38fec84 100755 --- a/src/action/linux/selinux/build.sh +++ b/src/action/linux/selinux/build.sh @@ -1,5 +1,7 @@ -#! /usr/bin/env nix-shell -#! nix-shell -i bash ../../../../shell.nix +#!/usr/bin/env bash checkmodule -M -m -c 5 -o nix.mod nix.te -semodule_package -o nix.pp -m nix.mod -f nix.fc \ No newline at end of file +semodule_package -o nix.pp -m nix.mod -f nix.fc + +checkmodule -M -m -c 5 -o nix.mod nix.te +semodule_package -o determinate-nix.pp -m nix.mod -f determinate-nix.fc diff --git a/src/action/linux/selinux/determinate-nix.fc b/src/action/linux/selinux/determinate-nix.fc new file mode 100644 index 000000000..f1b86f4a5 --- /dev/null +++ b/src/action/linux/selinux/determinate-nix.fc @@ -0,0 +1,14 @@ +/nix/store/[^/]+/s?bin(/.*)? system_u:object_r:bin_t:s0 +/nix/store/[^/]+/lib/systemd/system(/.*)? system_u:object_r:systemd_unit_file_t:s0 +/nix/store/[^/]+/lib(/.*)? system_u:object_r:lib_t:s0 +/nix/store/[^/]+/man(/.*)? system_u:object_r:man_t:s0 +/nix/store/[^/]+/etc(/.*)? system_u:object_r:etc_t:s0 +/nix/store/[^/]+/share(/.*)? system_u:object_r:usr_t:s0 +/nix/var/nix/daemon-socket(/.*)? system_u:object_r:var_run_t:s0 +/nix/var/nix/profiles(/per-user/[^/]+)?/[^/]+ system_u:object_r:usr_t:s0 + +/usr/local/bin/determinate-nixd system_u:object_r:bin_t:s0 +/nix/var/determinate/determinate-nixd.socket system_u:object_r:var_run_t:s0 +/nix/var/determinate/intake.pipe system_u:object_r:var_run_t:s0 +/nix/var/determinate/post-build-hook.sh system_u:object_r:bin_t:s0 +/nix/var/determinate/netrc system_u:object_r:etc_t:s0 diff --git a/src/action/linux/selinux/determinate-nix.pp b/src/action/linux/selinux/determinate-nix.pp new file mode 100644 index 000000000..2fdfdf66d Binary files /dev/null and b/src/action/linux/selinux/determinate-nix.pp differ diff --git a/src/action/linux/selinux/nix.fc b/src/action/linux/selinux/nix.fc index 32cc479e3..85de520a0 100644 --- a/src/action/linux/selinux/nix.fc +++ b/src/action/linux/selinux/nix.fc @@ -5,4 +5,4 @@ /nix/store/[^/]+/etc(/.*)? system_u:object_r:etc_t:s0 /nix/store/[^/]+/share(/.*)? system_u:object_r:usr_t:s0 /nix/var/nix/daemon-socket(/.*)? system_u:object_r:var_run_t:s0 -/nix/var/nix/profiles(/per-user/[^/]+)?/[^/]+ system_u:object_r:usr_t:s0 \ No newline at end of file +/nix/var/nix/profiles(/per-user/[^/]+)?/[^/]+ system_u:object_r:usr_t:s0 diff --git a/src/action/linux/selinux/nix.pp b/src/action/linux/selinux/nix.pp index de7213666..e105d1fe0 100644 Binary files a/src/action/linux/selinux/nix.pp and b/src/action/linux/selinux/nix.pp differ diff --git a/src/action/linux/selinux/nix.te b/src/action/linux/selinux/nix.te index f8ea7b1a8..9466675ab 100644 --- a/src/action/linux/selinux/nix.te +++ b/src/action/linux/selinux/nix.te @@ -8,4 +8,9 @@ require { type etc_t; type var_run_t; type systemd_unit_file_t; -} \ No newline at end of file + type default_t; + type init_t; + class lnk_file read; +} + +allow init_t default_t:lnk_file read; diff --git a/src/action/linux/start_systemd_unit.rs b/src/action/linux/start_systemd_unit.rs index 628206e86..ac461ae95 100644 --- a/src/action/linux/start_systemd_unit.rs +++ b/src/action/linux/start_systemd_unit.rs @@ -10,6 +10,7 @@ use crate::action::{Action, ActionDescription}; Start a given systemd unit */ #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(tag = "action_name", rename = "start_systemd_unit")] pub struct StartSystemdUnit { unit: String, enable: bool, @@ -81,7 +82,7 @@ impl Action for StartSystemdUnit { .process_group(0) .arg("enable") .arg("--now") - .arg(&unit) + .arg(unit) .stdin(std::process::Stdio::null()), ) .await @@ -93,7 +94,7 @@ impl Action for StartSystemdUnit { Command::new("systemctl") .process_group(0) .arg("start") - .arg(&unit) + .arg(unit) .stdin(std::process::Stdio::null()), ) .await diff --git a/src/action/macos/bootstrap_launchctl_service.rs b/src/action/macos/bootstrap_launchctl_service.rs index b019c98fb..8882b3a21 100644 --- a/src/action/macos/bootstrap_launchctl_service.rs +++ b/src/action/macos/bootstrap_launchctl_service.rs @@ -1,4 +1,4 @@ -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use tokio::process::Command; use tracing::{span, Span}; @@ -8,14 +8,14 @@ use crate::execute_command; use crate::action::{Action, ActionDescription}; -use super::service_is_disabled; +use super::{service_is_disabled, DARWIN_LAUNCHD_DOMAIN}; /** Bootstrap and kickstart an APFS volume */ #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(tag = "action_name", rename = "bootstrap_launchctl_service")] pub struct BootstrapLaunchctlService { - domain: String, service: String, path: PathBuf, is_present: bool, @@ -24,20 +24,15 @@ pub struct BootstrapLaunchctlService { impl BootstrapLaunchctlService { #[tracing::instrument(level = "debug", skip_all)] - pub async fn plan( - domain: impl AsRef, - service: impl AsRef, - path: impl AsRef, - ) -> Result, ActionError> { - let domain = domain.as_ref().to_string(); - let service = service.as_ref().to_string(); - let path = path.as_ref().to_path_buf(); + pub async fn plan(service: &str, path: &str) -> Result, ActionError> { + let service = service.to_owned(); + let path = PathBuf::from(path); let is_present = { let mut command = Command::new("launchctl"); command.process_group(0); command.arg("print"); - command.arg(format!("{domain}/{service}")); + command.arg(format!("{DARWIN_LAUNCHD_DOMAIN}/{service}")); command.arg("-plist"); command.stdin(std::process::Stdio::null()); command.stdout(std::process::Stdio::piped()); @@ -50,14 +45,13 @@ impl BootstrapLaunchctlService { command_output.status.success() || command_output.status.code() == Some(37) }; - let is_disabled = service_is_disabled(&domain, &service) + let is_disabled = service_is_disabled(DARWIN_LAUNCHD_DOMAIN, &service) .await .map_err(Self::error)?; if is_present && !is_disabled { return Ok(StatefulAction::completed(Self { service, - domain, path, is_present, is_disabled, @@ -65,7 +59,6 @@ impl BootstrapLaunchctlService { } Ok(StatefulAction::uncompleted(Self { - domain, service, path, is_present, @@ -84,7 +77,7 @@ impl Action for BootstrapLaunchctlService { format!( "Bootstrap the `{}` service via `launchctl bootstrap {} {}`", self.service, - self.domain, + DARWIN_LAUNCHD_DOMAIN, self.path.display() ) } @@ -93,7 +86,6 @@ impl Action for BootstrapLaunchctlService { span!( tracing::Level::DEBUG, "bootstrap_launchctl_service", - domain = self.domain, path = %self.path.display(), is_disabled = self.is_disabled, is_present = self.is_present, @@ -107,7 +99,6 @@ impl Action for BootstrapLaunchctlService { #[tracing::instrument(level = "debug", skip_all)] async fn execute(&mut self) -> Result<(), ActionError> { let Self { - domain, service, path, is_present, @@ -119,7 +110,7 @@ impl Action for BootstrapLaunchctlService { Command::new("launchctl") .process_group(0) .arg("enable") - .arg(&format!("{domain}/{service}")) + .arg(&format!("{DARWIN_LAUNCHD_DOMAIN}/{service}")) .stdin(std::process::Stdio::null()), ) .await @@ -127,16 +118,9 @@ impl Action for BootstrapLaunchctlService { } if !*is_present { - execute_command( - Command::new("launchctl") - .process_group(0) - .arg("bootstrap") - .arg(&domain) - .arg(&path) - .stdin(std::process::Stdio::null()), - ) - .await - .map_err(Self::error)?; + crate::action::macos::retry_bootstrap(DARWIN_LAUNCHD_DOMAIN, service, path) + .await + .map_err(Self::error)?; } Ok(()) @@ -146,7 +130,7 @@ impl Action for BootstrapLaunchctlService { vec![ActionDescription::new( format!( "Run `launchctl bootout {} {}`", - self.domain, + DARWIN_LAUNCHD_DOMAIN, self.path.display() ), vec![], @@ -155,16 +139,9 @@ impl Action for BootstrapLaunchctlService { #[tracing::instrument(level = "debug", skip_all)] async fn revert(&mut self) -> Result<(), ActionError> { - execute_command( - Command::new("launchctl") - .process_group(0) - .arg("bootout") - .arg(&self.domain) - .arg(&self.path) - .stdin(std::process::Stdio::null()), - ) - .await - .map_err(Self::error)?; + crate::action::macos::retry_bootout(DARWIN_LAUNCHD_DOMAIN, &self.service, &self.path) + .await + .map_err(Self::error)?; Ok(()) } diff --git a/src/action/macos/configure_remote_building.rs b/src/action/macos/configure_remote_building.rs index 81cadc46d..40076ccc3 100644 --- a/src/action/macos/configure_remote_building.rs +++ b/src/action/macos/configure_remote_building.rs @@ -11,6 +11,7 @@ Configure macOS's zshenv to load the Nix environment when ForceCommand is used. This enables remote building, which requires `ssh host nix` to work. */ #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(tag = "action_name", rename = "configure_remote_building")] pub struct ConfigureRemoteBuilding { create_or_insert_into_file: Option>, } diff --git a/src/action/macos/create_apfs_volume.rs b/src/action/macos/create_apfs_volume.rs index 401ebd865..3567fadd5 100644 --- a/src/action/macos/create_apfs_volume.rs +++ b/src/action/macos/create_apfs_volume.rs @@ -11,6 +11,7 @@ use crate::action::{Action, ActionDescription}; use crate::os::darwin::{DiskUtilApfsListOutput, DiskUtilInfoOutput}; #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(tag = "action_name", rename = "create_volume")] pub struct CreateApfsVolume { disk: PathBuf, name: String, @@ -137,12 +138,12 @@ impl Action for CreateApfsVolume { let the_plist: DiskUtilInfoOutput = plist::from_reader(Cursor::new(buf)).map_err(Self::error)?; - the_plist.mount_point.is_some() + the_plist.is_mounted() }; // Unmounts the volume before attempting to remove it, avoiding 'in use' errors // https://github.com/DeterminateSystems/nix-installer/issues/647 - if !currently_mounted { + if currently_mounted { execute_command( Command::new("/usr/sbin/diskutil") .process_group(0) diff --git a/src/action/macos/create_enterprise_edition_volume.rs b/src/action/macos/create_determinate_nix_volume.rs similarity index 69% rename from src/action/macos/create_enterprise_edition_volume.rs rename to src/action/macos/create_determinate_nix_volume.rs index c1fbc4908..c01d98210 100644 --- a/src/action/macos/create_enterprise_edition_volume.rs +++ b/src/action/macos/create_determinate_nix_volume.rs @@ -1,42 +1,59 @@ /// This file is unused but is kept around to avoid merge conflicts +use std::{ + path::{Path, PathBuf}, + time::Duration, +}; + +use tokio::process::Command; +use tracing::{span, Span}; + +use super::{create_fstab_entry::CreateFstabEntry, DARWIN_LAUNCHD_DOMAIN}; +use crate::action::macos::{ + BootstrapLaunchctlService, CreateDeterminateVolumeService, KickstartLaunchctlService, +}; use crate::action::{ - base::{create_or_insert_into_file, CreateOrInsertIntoFile}, + base::{create_or_insert_into_file, CreateDirectory, CreateOrInsertIntoFile}, + common::place_nix_configuration::NIX_CONF_FOLDER, macos::{ CreateApfsVolume, CreateSyntheticObjects, EnableOwnership, EncryptApfsVolume, UnmountApfsVolume, }, Action, ActionDescription, ActionError, ActionErrorKind, ActionTag, StatefulAction, }; -use std::{ - path::{Path, PathBuf}, - time::Duration, -}; -use tokio::process::Command; -use tracing::{span, Span}; -use super::create_fstab_entry::CreateFstabEntry; +pub const VOLUME_MOUNT_SERVICE_NAME: &str = "systems.determinate.nix-store"; +pub const VOLUME_MOUNT_SERVICE_DEST: &str = + "/Library/LaunchDaemons/systems.determinate.nix-store.plist"; /// Create an APFS volume #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] -pub struct CreateEnterpriseEditionVolume { +#[serde(tag = "action_name", rename = "create_determinate_nix_volume")] +pub struct CreateDeterminateNixVolume { disk: PathBuf, name: String, case_sensitive: bool, + use_ec2_instance_store: bool, + create_directory: StatefulAction, create_or_append_synthetic_conf: StatefulAction, create_synthetic_objects: StatefulAction, unmount_volume: StatefulAction, create_volume: StatefulAction, create_fstab_entry: StatefulAction, encrypt_volume: StatefulAction, + setup_volume_daemon: StatefulAction, + bootstrap_volume: StatefulAction, + kickstart_launchctl_service: StatefulAction, enable_ownership: StatefulAction, } -impl CreateEnterpriseEditionVolume { +impl CreateDeterminateNixVolume { #[tracing::instrument(level = "debug", skip_all)] pub async fn plan( disk: impl AsRef, name: String, case_sensitive: bool, + force: bool, + use_ec2_instance_store: bool, ) -> Result, ActionError> { let disk = disk.as_ref(); let create_or_append_synthetic_conf = CreateOrInsertIntoFile::plan( @@ -50,6 +67,10 @@ impl CreateEnterpriseEditionVolume { .await .map_err(Self::error)?; + let create_directory = CreateDirectory::plan(NIX_CONF_FOLDER, None, None, 0o0755, force) + .await + .map_err(Self::error)?; + let create_synthetic_objects = CreateSyntheticObjects::plan().await.map_err(Self::error)?; let unmount_volume = UnmountApfsVolume::plan(disk, name.clone()) @@ -66,18 +87,40 @@ impl CreateEnterpriseEditionVolume { let encrypt_volume = EncryptApfsVolume::plan(true, disk, &name, &create_volume).await?; + let setup_volume_daemon = CreateDeterminateVolumeService::plan( + VOLUME_MOUNT_SERVICE_DEST, + VOLUME_MOUNT_SERVICE_NAME, + use_ec2_instance_store, + ) + .await + .map_err(Self::error)?; + + let bootstrap_volume = + BootstrapLaunchctlService::plan(VOLUME_MOUNT_SERVICE_NAME, VOLUME_MOUNT_SERVICE_DEST) + .await + .map_err(Self::error)?; + let kickstart_launchctl_service = + KickstartLaunchctlService::plan(DARWIN_LAUNCHD_DOMAIN, VOLUME_MOUNT_SERVICE_NAME) + .await + .map_err(Self::error)?; + let enable_ownership = EnableOwnership::plan("/nix").await.map_err(Self::error)?; Ok(Self { disk: disk.to_path_buf(), name, case_sensitive, + use_ec2_instance_store, + create_directory, create_or_append_synthetic_conf, create_synthetic_objects, unmount_volume, create_volume, create_fstab_entry, encrypt_volume, + setup_volume_daemon, + bootstrap_volume, + kickstart_launchctl_service, enable_ownership, } .into()) @@ -85,10 +128,10 @@ impl CreateEnterpriseEditionVolume { } #[async_trait::async_trait] -#[typetag::serde(name = "create_apfs_enterprise_volume")] -impl Action for CreateEnterpriseEditionVolume { +#[typetag::serde(name = "create_determinate_nix_volume")] +impl Action for CreateDeterminateNixVolume { fn action_tag() -> ActionTag { - ActionTag("create_enterprise_edition_volume") + ActionTag("create_determinate_nix_volume") } fn tracing_synopsis(&self) -> String { format!( @@ -101,7 +144,7 @@ impl Action for CreateEnterpriseEditionVolume { fn tracing_span(&self) -> Span { span!( tracing::Level::DEBUG, - "create_apfs_volume", + "create_determinate_nix_volume", disk = tracing::field::display(self.disk.display()), name = self.name ) @@ -109,12 +152,16 @@ impl Action for CreateEnterpriseEditionVolume { fn execute_description(&self) -> Vec { let explanation = vec![ + self.create_directory.tracing_synopsis(), self.create_or_append_synthetic_conf.tracing_synopsis(), self.create_synthetic_objects.tracing_synopsis(), self.unmount_volume.tracing_synopsis(), self.create_volume.tracing_synopsis(), self.create_fstab_entry.tracing_synopsis(), self.encrypt_volume.tracing_synopsis(), + self.setup_volume_daemon.tracing_synopsis(), + self.bootstrap_volume.tracing_synopsis(), + self.kickstart_launchctl_service.tracing_synopsis(), self.enable_ownership.tracing_synopsis(), ]; @@ -123,6 +170,10 @@ impl Action for CreateEnterpriseEditionVolume { #[tracing::instrument(level = "debug", skip_all)] async fn execute(&mut self) -> Result<(), ActionError> { + self.create_directory + .try_execute() + .await + .map_err(Self::error)?; self.create_or_append_synthetic_conf .try_execute() .await @@ -172,8 +223,8 @@ impl Action for CreateEnterpriseEditionVolume { .await .map_err(Self::error)?; - let mut command = Command::new("/usr/local/bin/determinate-nix-ee"); - command.args(["--stop-after", "mount"]); + let mut command = Command::new("/usr/local/bin/determinate-nixd"); + command.args(["init", "--stop-after", "mount"]); command.stderr(std::process::Stdio::piped()); command.stdout(std::process::Stdio::piped()); tracing::trace!(command = ?command.as_std(), "Mounting /nix"); @@ -188,29 +239,24 @@ impl Action for CreateEnterpriseEditionVolume { ))); } - let mut retry_tokens: usize = 50; - loop { - let mut command = Command::new("/usr/sbin/diskutil"); - command.args(["info", "/nix"]); - command.stderr(std::process::Stdio::null()); - command.stdout(std::process::Stdio::null()); - tracing::trace!(%retry_tokens, command = ?command.as_std(), "Checking for Nix Store mount path existence"); - let output = command - .output() - .await - .map_err(|e| ActionErrorKind::command(&command, e)) - .map_err(Self::error)?; - if output.status.success() { - break; - } else if retry_tokens == 0 { - return Err(Self::error(ActionErrorKind::command_output( - &command, output, - ))); - } else { - retry_tokens = retry_tokens.saturating_sub(1); - } - tokio::time::sleep(Duration::from_millis(100)).await; - } + crate::action::macos::wait_for_nix_store_dir() + .await + .map_err(Self::error)?; + + self.setup_volume_daemon + .try_execute() + .await + .map_err(Self::error)?; + + self.bootstrap_volume + .try_execute() + .await + .map_err(Self::error)?; + + self.kickstart_launchctl_service + .try_execute() + .await + .map_err(Self::error)?; self.enable_ownership .try_execute() @@ -222,12 +268,16 @@ impl Action for CreateEnterpriseEditionVolume { fn revert_description(&self) -> Vec { let explanation = vec![ + self.create_directory.tracing_synopsis(), self.create_or_append_synthetic_conf.tracing_synopsis(), self.create_synthetic_objects.tracing_synopsis(), self.unmount_volume.tracing_synopsis(), self.create_volume.tracing_synopsis(), self.create_fstab_entry.tracing_synopsis(), self.encrypt_volume.tracing_synopsis(), + self.setup_volume_daemon.tracing_synopsis(), + self.bootstrap_volume.tracing_synopsis(), + self.kickstart_launchctl_service.tracing_synopsis(), self.enable_ownership.tracing_synopsis(), ]; @@ -248,6 +298,15 @@ impl Action for CreateEnterpriseEditionVolume { if let Err(err) = self.enable_ownership.try_revert().await { errors.push(err) }; + if let Err(err) = self.kickstart_launchctl_service.try_revert().await { + errors.push(err) + } + if let Err(err) = self.bootstrap_volume.try_revert().await { + errors.push(err) + } + if let Err(err) = self.setup_volume_daemon.try_revert().await { + errors.push(err) + } if let Err(err) = self.encrypt_volume.try_revert().await { errors.push(err) } @@ -270,6 +329,10 @@ impl Action for CreateEnterpriseEditionVolume { errors.push(err) } + if let Err(err) = self.create_directory.try_revert().await { + errors.push(err); + } + if errors.is_empty() { Ok(()) } else if errors.len() == 1 { diff --git a/src/action/macos/create_determinate_volume_service.rs b/src/action/macos/create_determinate_volume_service.rs new file mode 100644 index 000000000..15b76028f --- /dev/null +++ b/src/action/macos/create_determinate_volume_service.rs @@ -0,0 +1,230 @@ +use serde::{Deserialize, Serialize}; +use tracing::{span, Span}; + +use std::path::{Path, PathBuf}; +use tokio::{ + fs::{remove_file, OpenOptions}, + io::AsyncWriteExt, + process::Command, +}; + +use crate::action::{ + Action, ActionDescription, ActionError, ActionErrorKind, ActionTag, StatefulAction, +}; + +use super::DARWIN_LAUNCHD_DOMAIN; + +/** Create a plist for a `launchctl` service to mount the volume + */ +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(tag = "action_name", rename = "create_determinate_volume_service")] +pub struct CreateDeterminateVolumeService { + path: PathBuf, + mount_service_label: String, + needs_bootout: bool, + use_ec2_instance_store: bool, +} + +impl CreateDeterminateVolumeService { + #[tracing::instrument(level = "debug", skip_all)] + pub async fn plan( + path: impl AsRef, + mount_service_label: impl Into, + use_ec2_instance_store: bool, + ) -> Result, ActionError> { + let path = path.as_ref().to_path_buf(); + let mount_service_label = mount_service_label.into(); + let mut this = Self { + path, + mount_service_label, + use_ec2_instance_store, + needs_bootout: false, + }; + + // If the service is currently loaded or running, we need to unload it during execute (since we will then recreate it and reload it) + // This `launchctl` command may fail if the service isn't loaded + let mut check_loaded_command = Command::new("launchctl"); + check_loaded_command.arg("print"); + check_loaded_command.arg(format!("system/{}", this.mount_service_label)); + tracing::trace!( + command = format!("{:?}", check_loaded_command.as_std()), + "Executing" + ); + let check_loaded_output = check_loaded_command + .status() + .await + .map_err(|e| ActionErrorKind::command(&check_loaded_command, e)) + .map_err(Self::error)?; + + this.needs_bootout = check_loaded_output.success(); + + if this.needs_bootout { + tracing::debug!( + "Detected loaded service `{}` which needs unload before replacing `{}`", + this.mount_service_label, + this.path.display(), + ); + } + + if this.path.exists() { + let discovered_plist: LaunchctlMountPlist = + plist::from_file(&this.path).map_err(Self::error)?; + + let expected_plist = + generate_mount_plist(&this.mount_service_label, use_ec2_instance_store) + .await + .map_err(Self::error)?; + if discovered_plist != expected_plist { + tracing::trace!( + ?discovered_plist, + ?expected_plist, + "Parsed plists not equal" + ); + return Err(Self::error( + CreateDeterminateVolumeServiceError::DifferentPlist { + expected: expected_plist, + discovered: discovered_plist, + path: this.path.clone(), + }, + )); + } + + tracing::debug!("Creating file `{}` already complete", this.path.display()); + return Ok(StatefulAction::completed(this)); + } + + Ok(StatefulAction::uncompleted(this)) + } +} + +#[async_trait::async_trait] +#[typetag::serde(name = "create_determinate_volume_service")] +impl Action for CreateDeterminateVolumeService { + fn action_tag() -> ActionTag { + ActionTag("create_determinate_volume_service") + } + fn tracing_synopsis(&self) -> String { + format!( + "{maybe_unload} a `launchctl` plist to mount the APFS volume `{path}`", + path = self.path.display(), + maybe_unload = if self.needs_bootout { + "Unload, then recreate" + } else { + "Create" + } + ) + } + + fn tracing_span(&self) -> Span { + let span = span!( + tracing::Level::DEBUG, + "create_determinate_volume_service", + path = tracing::field::display(self.path.display()), + buf = tracing::field::Empty, + ); + span + } + + fn execute_description(&self) -> Vec { + vec![ActionDescription::new(self.tracing_synopsis(), vec![])] + } + + #[tracing::instrument(level = "debug", skip_all)] + async fn execute(&mut self) -> Result<(), ActionError> { + let Self { + path, + mount_service_label, + needs_bootout, + use_ec2_instance_store, + } = self; + + if *needs_bootout { + crate::action::macos::retry_bootout(DARWIN_LAUNCHD_DOMAIN, mount_service_label, path) + .await + .map_err(Self::error)?; + } + + let generated_plist = generate_mount_plist(mount_service_label, *use_ec2_instance_store) + .await + .map_err(Self::error)?; + + let mut options = OpenOptions::new(); + options.create(true).write(true).read(true); + + let mut file = options + .open(&path) + .await + .map_err(|e| Self::error(ActionErrorKind::Open(path.to_owned(), e)))?; + + let mut buf = Vec::new(); + plist::to_writer_xml(&mut buf, &generated_plist).map_err(Self::error)?; + file.write_all(&buf) + .await + .map_err(|e| Self::error(ActionErrorKind::Write(path.to_owned(), e)))?; + + Ok(()) + } + + fn revert_description(&self) -> Vec { + vec![ActionDescription::new( + format!("Delete file `{}`", self.path.display()), + vec![format!("Delete file `{}`", self.path.display())], + )] + } + + #[tracing::instrument(level = "debug", skip_all)] + async fn revert(&mut self) -> Result<(), ActionError> { + remove_file(&self.path) + .await + .map_err(|e| Self::error(ActionErrorKind::Remove(self.path.to_owned(), e)))?; + + Ok(()) + } +} + +/// This function must be able to operate at both plan and execute time. +async fn generate_mount_plist( + mount_service_label: &str, + use_ec2_instance_store: bool, +) -> Result { + let mut arguments = vec!["/usr/local/bin/determinate-nixd".into(), "init".into()]; + if use_ec2_instance_store { + arguments.push("--keep-mounted".into()); + } + let mount_plist = LaunchctlMountPlist { + run_at_load: true, + label: mount_service_label.into(), + program_arguments: arguments, + standard_out_path: "/var/log/determinate-nix-init.log".into(), + standard_error_path: "/var/log/determinate-nix-init.log".into(), + }; + + Ok(mount_plist) +} + +#[derive(Deserialize, Clone, Debug, Serialize, PartialEq)] +#[serde(rename_all = "PascalCase")] +pub struct LaunchctlMountPlist { + run_at_load: bool, + label: String, + program_arguments: Vec, + standard_error_path: String, + standard_out_path: String, +} + +#[non_exhaustive] +#[derive(Debug, thiserror::Error)] +pub enum CreateDeterminateVolumeServiceError { + #[error("`{path}` contents differs, planned `{expected:?}`, discovered `{discovered:?}`")] + DifferentPlist { + expected: LaunchctlMountPlist, + discovered: LaunchctlMountPlist, + path: PathBuf, + }, +} + +impl From for ActionErrorKind { + fn from(val: CreateDeterminateVolumeServiceError) -> Self { + ActionErrorKind::Custom(Box::new(val)) + } +} diff --git a/src/action/macos/create_fstab_entry.rs b/src/action/macos/create_fstab_entry.rs index 606f327eb..eb61719a1 100644 --- a/src/action/macos/create_fstab_entry.rs +++ b/src/action/macos/create_fstab_entry.rs @@ -30,6 +30,7 @@ add the relevant information to `/etc/fstab`. // Initially, a `NAME` was used, however in https://github.com/DeterminateSystems/nix-installer/issues/212 // several users reported issues. Using a UUID resolved the issue for them. #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(tag = "action_name", rename = "create_fstab_entry")] pub struct CreateFstabEntry { apfs_volume_label: String, existing_entry: ExistingFstabEntry, diff --git a/src/action/macos/create_nix_hook_service.rs b/src/action/macos/create_nix_hook_service.rs index 6ae5d1d34..3a8a1a646 100644 --- a/src/action/macos/create_nix_hook_service.rs +++ b/src/action/macos/create_nix_hook_service.rs @@ -8,14 +8,16 @@ use tokio::{ process::Command, }; -use crate::{ - action::{Action, ActionDescription, ActionError, ActionErrorKind, ActionTag, StatefulAction}, - execute_command, +use crate::action::{ + Action, ActionDescription, ActionError, ActionErrorKind, ActionTag, StatefulAction, }; +use super::DARWIN_LAUNCHD_DOMAIN; + /** Create a plist for a `launchctl` service to re-add Nix to the zshrc after upgrades. */ #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(tag = "action_name", rename = "create_nix_hook_service")] pub struct CreateNixHookService { path: PathBuf, service_label: String, @@ -125,14 +127,9 @@ impl Action for CreateNixHookService { } = self; if *needs_bootout { - execute_command( - Command::new("launchctl") - .process_group(0) - .arg("bootout") - .arg(format!("system/{service_label}")), - ) - .await - .map_err(Self::error)?; + crate::action::macos::retry_bootout(DARWIN_LAUNCHD_DOMAIN, service_label, path) + .await + .map_err(Self::error)?; } let generated_plist = generate_plist(service_label).await.map_err(Self::error)?; diff --git a/src/action/macos/create_nix_volume.rs b/src/action/macos/create_nix_volume.rs index e629a39d7..b793e2ad9 100644 --- a/src/action/macos/create_nix_volume.rs +++ b/src/action/macos/create_nix_volume.rs @@ -13,12 +13,16 @@ use std::{ use tokio::process::Command; use tracing::{span, Span}; -use super::{create_fstab_entry::CreateFstabEntry, CreateVolumeService, KickstartLaunchctlService}; +use super::{ + create_fstab_entry::CreateFstabEntry, CreateVolumeService, KickstartLaunchctlService, + DARWIN_LAUNCHD_DOMAIN, +}; pub const NIX_VOLUME_MOUNTD_DEST: &str = "/Library/LaunchDaemons/org.nixos.darwin-store.plist"; /// Create an APFS volume #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(tag = "action_name", rename = "create_apfs_volume")] pub struct CreateNixVolume { disk: PathBuf, name: String, @@ -86,15 +90,12 @@ impl CreateNixVolume { .await .map_err(Self::error)?; - let bootstrap_volume = BootstrapLaunchctlService::plan( - "system", - "org.nixos.darwin-store", - NIX_VOLUME_MOUNTD_DEST, - ) - .await - .map_err(Self::error)?; + let bootstrap_volume = + BootstrapLaunchctlService::plan("org.nixos.darwin-store", NIX_VOLUME_MOUNTD_DEST) + .await + .map_err(Self::error)?; let kickstart_launchctl_service = - KickstartLaunchctlService::plan("system", "org.nixos.darwin-store") + KickstartLaunchctlService::plan(DARWIN_LAUNCHD_DOMAIN, "org.nixos.darwin-store") .await .map_err(Self::error)?; let enable_ownership = EnableOwnership::plan("/nix").await.map_err(Self::error)?; @@ -128,7 +129,7 @@ impl Action for CreateNixVolume { fn tracing_synopsis(&self) -> String { format!( "Create an{maybe_encrypted} APFS volume `{name}` for Nix on `{disk}` and add it to `/etc/fstab` mounting on `/nix`", - maybe_encrypted = if self.encrypt { " encrypted" } else { "" }, + maybe_encrypted = if self.encrypt { " encrypted" } else { "" }, name = self.name, disk = self.disk.display(), ) @@ -224,29 +225,9 @@ impl Action for CreateNixVolume { .await .map_err(Self::error)?; - let mut retry_tokens: usize = 50; - loop { - let mut command = Command::new("/usr/sbin/diskutil"); - command.args(["info", "/nix"]); - command.stderr(std::process::Stdio::null()); - command.stdout(std::process::Stdio::null()); - tracing::trace!(%retry_tokens, command = ?command.as_std(), "Checking for Nix Store mount path existence"); - let output = command - .output() - .await - .map_err(|e| ActionErrorKind::command(&command, e)) - .map_err(Self::error)?; - if output.status.success() { - break; - } else if retry_tokens == 0 { - return Err(Self::error(ActionErrorKind::command_output( - &command, output, - ))); - } else { - retry_tokens = retry_tokens.saturating_sub(1); - } - tokio::time::sleep(Duration::from_millis(100)).await; - } + crate::action::macos::wait_for_nix_store_dir() + .await + .map_err(Self::error)?; self.enable_ownership .try_execute() diff --git a/src/action/macos/create_volume_service.rs b/src/action/macos/create_volume_service.rs index 60d836c54..e4c97681f 100644 --- a/src/action/macos/create_volume_service.rs +++ b/src/action/macos/create_volume_service.rs @@ -9,7 +9,8 @@ use tokio::{ }; use crate::action::{ - Action, ActionDescription, ActionError, ActionErrorKind, ActionTag, StatefulAction, + macos::DARWIN_LAUNCHD_DOMAIN, Action, ActionDescription, ActionError, ActionErrorKind, + ActionTag, StatefulAction, }; use super::get_uuid_for_label; @@ -17,6 +18,7 @@ use super::get_uuid_for_label; /** Create a plist for a `launchctl` service to mount the given `apfs_volume_label` on the given `mount_point`. */ #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(tag = "action_name", rename = "create_volume_service")] pub struct CreateVolumeService { pub(crate) path: PathBuf, apfs_volume_label: String, @@ -184,24 +186,9 @@ impl Action for CreateVolumeService { } = self; if *needs_bootout { - let mut unload_command = Command::new("launchctl"); - unload_command.arg("bootout"); - unload_command.arg(format!("system/{mount_service_label}")); - tracing::trace!( - command = format!("{:?}", unload_command.as_std()), - "Executing" - ); - let unload_output = unload_command - .output() + crate::action::macos::retry_bootout(DARWIN_LAUNCHD_DOMAIN, mount_service_label, path) .await - .map_err(|e| ActionErrorKind::command(&unload_command, e)) .map_err(Self::error)?; - if !unload_output.status.success() { - return Err(Self::error(ActionErrorKind::command_output( - &unload_command, - unload_output, - ))); - } } let uuid = match get_uuid_for_label(apfs_volume_label) diff --git a/src/action/macos/enable_ownership.rs b/src/action/macos/enable_ownership.rs index ef5e69476..b770de185 100644 --- a/src/action/macos/enable_ownership.rs +++ b/src/action/macos/enable_ownership.rs @@ -14,6 +14,7 @@ use crate::os::darwin::DiskUtilInfoOutput; Enable ownership on a volume */ #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(tag = "action_name", rename = "enable_ownership")] pub struct EnableOwnership { path: PathBuf, } @@ -52,14 +53,12 @@ impl Action for EnableOwnership { #[tracing::instrument(level = "debug", skip_all)] async fn execute(&mut self) -> Result<(), ActionError> { - let Self { path } = self; - let should_enable_ownership = { let buf = execute_command( Command::new("/usr/sbin/diskutil") .process_group(0) .args(["info", "-plist"]) - .arg(&path) + .arg(&self.path) .stdin(std::process::Stdio::null()), ) .await @@ -76,7 +75,7 @@ impl Action for EnableOwnership { Command::new("/usr/sbin/diskutil") .process_group(0) .arg("enableOwnership") - .arg(path) + .arg(&self.path) .stdin(std::process::Stdio::null()), ) .await diff --git a/src/action/macos/encrypt_apfs_volume.rs b/src/action/macos/encrypt_apfs_volume.rs index aa2f60ae9..032ee2630 100644 --- a/src/action/macos/encrypt_apfs_volume.rs +++ b/src/action/macos/encrypt_apfs_volume.rs @@ -20,8 +20,9 @@ use super::CreateApfsVolume; Encrypt an APFS volume */ #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(tag = "action_name", rename = "encrypt_volume")] pub struct EncryptApfsVolume { - enterprise_edition: bool, + determinate_nix: bool, disk: PathBuf, name: String, } @@ -29,7 +30,7 @@ pub struct EncryptApfsVolume { impl EncryptApfsVolume { #[tracing::instrument(level = "debug", skip_all)] pub async fn plan( - enterprise_edition: bool, + determinate_nix: bool, disk: impl AsRef, name: impl AsRef, planned_create_apfs_volume: &StatefulAction, @@ -60,7 +61,7 @@ impl EncryptApfsVolume { if planned_create_apfs_volume.state == ActionState::Completed { // We detected a created volume already, and a password exists, so we can keep using that and skip doing anything return Ok(StatefulAction::completed(Self { - enterprise_edition, + determinate_nix, name, disk, })); @@ -94,7 +95,7 @@ impl EncryptApfsVolume { )); } else { return Ok(StatefulAction::completed(Self { - enterprise_edition, + determinate_nix, disk, name, })); @@ -104,7 +105,7 @@ impl EncryptApfsVolume { } Ok(StatefulAction::uncompleted(Self { - enterprise_edition, + determinate_nix, name, disk, })) @@ -141,12 +142,6 @@ impl Action for EncryptApfsVolume { disk = %self.disk.display(), ))] async fn execute(&mut self) -> Result<(), ActionError> { - let Self { - enterprise_edition, - disk, - name, - } = self; - // Generate a random password. let password: String = { const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\ @@ -163,18 +158,22 @@ impl Action for EncryptApfsVolume { .collect() }; - let disk_str = disk.to_str().expect("Could not turn disk into string"); /* Should not reasonably ever fail */ + let disk_str = &self.disk.to_str().expect("Could not turn disk into string"); /* Should not reasonably ever fail */ - execute_command(Command::new("/usr/sbin/diskutil").arg("mount").arg(&name)) - .await - .map_err(Self::error)?; + execute_command( + Command::new("/usr/sbin/diskutil") + .arg("mount") + .arg(&self.name), + ) + .await + .map_err(Self::error)?; // Add the password to the user keychain so they can unlock it later. let mut cmd = Command::new("/usr/bin/security"); cmd.process_group(0).args([ "add-generic-password", "-a", - name.as_str(), + self.name.as_str(), "-s", "Nix Store", "-l", @@ -194,8 +193,8 @@ impl Action for EncryptApfsVolume { "/usr/bin/security", ]); - if *enterprise_edition { - cmd.args(["-T", "/usr/local/bin/determinate-nix-ee"]); + if self.determinate_nix { + cmd.args(["-T", "/usr/local/bin/determinate-nixd"]); } cmd.arg("/Library/Keychains/System.keychain"); @@ -207,7 +206,7 @@ impl Action for EncryptApfsVolume { execute_command(Command::new("/usr/sbin/diskutil").process_group(0).args([ "apfs", "encryptVolume", - name.as_str(), + self.name.as_str(), "-user", "disk", "-passphrase", @@ -221,7 +220,7 @@ impl Action for EncryptApfsVolume { .process_group(0) .arg("unmount") .arg("force") - .arg(&name), + .arg(&self.name), ) .await .map_err(Self::error)?; diff --git a/src/action/macos/kickstart_launchctl_service.rs b/src/action/macos/kickstart_launchctl_service.rs index f6d699c50..ba730e4d4 100644 --- a/src/action/macos/kickstart_launchctl_service.rs +++ b/src/action/macos/kickstart_launchctl_service.rs @@ -1,10 +1,10 @@ use std::process::Output; +use std::time::Duration; use tokio::process::Command; use tracing::{span, Span}; use crate::action::{ActionError, ActionErrorKind, ActionTag, StatefulAction}; -use crate::execute_command; use crate::action::{Action, ActionDescription}; @@ -12,6 +12,7 @@ use crate::action::{Action, ActionDescription}; Bootstrap and kickstart an APFS volume */ #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(tag = "action_name", rename = "kickstart_launchctl_service")] pub struct KickstartLaunchctlService { domain: String, service: String, @@ -98,15 +99,35 @@ impl Action for KickstartLaunchctlService { async fn execute(&mut self) -> Result<(), ActionError> { let Self { domain, service } = self; - execute_command( - Command::new("launchctl") - .process_group(0) - .args(["kickstart", "-k"]) - .arg(format!("{domain}/{service}")) - .stdin(std::process::Stdio::null()), - ) - .await - .map_err(Self::error)?; + let mut retry_tokens: usize = 10; + loop { + let mut command = Command::new("launchctl"); + command.process_group(0); + command.args(["kickstart", "-k"]); + command.arg(format!("{domain}/{service}")); + command.stdin(std::process::Stdio::null()); + command.stderr(std::process::Stdio::null()); + command.stdout(std::process::Stdio::null()); + tracing::trace!(%retry_tokens, command = ?command.as_std(), "Waiting for kickstart to succeed"); + + let output = command + .output() + .await + .map_err(|e| ActionErrorKind::command(&command, e)) + .map_err(Self::error)?; + + if output.status.success() { + break; + } else if retry_tokens == 0 { + return Err(Self::error(ActionErrorKind::command_output( + &command, output, + )))?; + } else { + retry_tokens = retry_tokens.saturating_sub(1); + } + + tokio::time::sleep(Duration::from_millis(500)).await; + } Ok(()) } diff --git a/src/action/macos/mod.rs b/src/action/macos/mod.rs index 17aa3ad7d..fe1366c08 100644 --- a/src/action/macos/mod.rs +++ b/src/action/macos/mod.rs @@ -4,8 +4,8 @@ pub(crate) mod bootstrap_launchctl_service; pub(crate) mod configure_remote_building; pub(crate) mod create_apfs_volume; -#[cfg(not(feature = "nix-community"))] -pub(crate) mod create_enterprise_edition_volume; +pub(crate) mod create_determinate_nix_volume; +pub(crate) mod create_determinate_volume_service; pub(crate) mod create_fstab_entry; pub(crate) mod create_nix_hook_service; pub(crate) mod create_nix_volume; @@ -18,11 +18,14 @@ pub(crate) mod set_tmutil_exclusion; pub(crate) mod set_tmutil_exclusions; pub(crate) mod unmount_apfs_volume; +use std::path::Path; +use std::time::Duration; + pub use bootstrap_launchctl_service::BootstrapLaunchctlService; pub use configure_remote_building::ConfigureRemoteBuilding; pub use create_apfs_volume::CreateApfsVolume; -#[cfg(not(feature = "nix-community"))] -pub use create_enterprise_edition_volume::CreateEnterpriseEditionVolume; +pub use create_determinate_nix_volume::CreateDeterminateNixVolume; +pub use create_determinate_volume_service::CreateDeterminateVolumeService; pub use create_nix_hook_service::CreateNixHookService; pub use create_nix_volume::{CreateNixVolume, NIX_VOLUME_MOUNTD_DEST}; pub use create_synthetic_objects::CreateSyntheticObjects; @@ -41,6 +44,8 @@ use crate::execute_command; use super::ActionErrorKind; +pub const DARWIN_LAUNCHD_DOMAIN: &str = "system"; + async fn get_uuid_for_label(apfs_volume_label: &str) -> Result, ActionErrorKind> { let mut command = Command::new("/usr/sbin/diskutil"); command.process_group(0); @@ -93,6 +98,7 @@ pub(crate) async fn service_is_disabled( ) -> Result { let output = execute_command( Command::new("launchctl") + .process_group(0) .arg("print-disabled") .arg(domain) .stdin(std::process::Stdio::null()) @@ -105,3 +111,143 @@ pub(crate) async fn service_is_disabled( tracing::trace!(is_disabled, "Service disabled status"); Ok(is_disabled) } + +/// Waits for the Nix Store mountpoint to exist, up to `retry_tokens * 100ms` amount of time. +#[tracing::instrument] +pub(crate) async fn wait_for_nix_store_dir() -> Result<(), ActionErrorKind> { + let mut retry_tokens: usize = 150; + loop { + let mut command = Command::new("/usr/sbin/diskutil"); + command.process_group(0); + command.args(["info", "/nix"]); + command.stderr(std::process::Stdio::null()); + command.stdout(std::process::Stdio::null()); + tracing::trace!(%retry_tokens, command = ?command.as_std(), "Checking for Nix Store mount path existence"); + let output = command + .output() + .await + .map_err(|e| ActionErrorKind::command(&command, e))?; + if output.status.success() { + break; + } else if retry_tokens == 0 { + return Err(ActionErrorKind::command_output(&command, output))?; + } else { + retry_tokens = retry_tokens.saturating_sub(1); + } + tokio::time::sleep(Duration::from_millis(100)).await; + } + + Ok(()) +} + +/// Wait for `launchctl bootstrap {domain} {service}` to succeed up to `retry_tokens * 500ms` amount +/// of time. +#[tracing::instrument] +pub(crate) async fn retry_bootstrap( + domain: &str, + service_name: &str, + service_path: &Path, +) -> Result<(), ActionErrorKind> { + let check_service_running = execute_command( + Command::new("launchctl") + .process_group(0) + .arg("print") + .arg([domain, service_name].join("/")) + .stdin(std::process::Stdio::null()) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()), + ) + .await; + + if check_service_running.is_ok() { + // NOTE(cole-h): if `launchctl print` succeeds, that means the service is already loaded + // and so our retry will fail. + return Ok(()); + } + + let mut retry_tokens: usize = 10; + loop { + let mut command = Command::new("launchctl"); + command.process_group(0); + command.arg("bootstrap"); + command.arg(domain); + command.arg(service_path); + command.stdin(std::process::Stdio::null()); + command.stderr(std::process::Stdio::null()); + command.stdout(std::process::Stdio::null()); + tracing::trace!(%retry_tokens, command = ?command.as_std(), "Waiting for bootstrap to succeed"); + + let output = command + .output() + .await + .map_err(|e| ActionErrorKind::command(&command, e))?; + + if output.status.success() { + break; + } else if retry_tokens == 0 { + return Err(ActionErrorKind::command_output(&command, output))?; + } else { + retry_tokens = retry_tokens.saturating_sub(1); + } + + tokio::time::sleep(Duration::from_millis(500)).await; + } + + Ok(()) +} + +/// Wait for `launchctl bootout {domain} {service_path}` to succeed up to `retry_tokens * 500ms` amount +/// of time. +#[tracing::instrument] +pub(crate) async fn retry_bootout( + domain: &str, + service_name: &str, + service_path: &Path, +) -> Result<(), ActionErrorKind> { + let check_service_running = execute_command( + Command::new("launchctl") + .process_group(0) + .arg("print") + .arg([domain, service_name].join("/")) + .stdin(std::process::Stdio::null()) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()), + ) + .await; + + if check_service_running.is_err() { + // NOTE(cole-h): if `launchctl print` fails, that means the service is already unloaded and + // so our retry will fail. + return Ok(()); + } + + let mut retry_tokens: usize = 10; + loop { + let mut command = Command::new("launchctl"); + command.process_group(0); + command.arg("bootout"); + command.arg(domain); + command.arg(service_path); + command.stdin(std::process::Stdio::null()); + command.stderr(std::process::Stdio::null()); + command.stdout(std::process::Stdio::null()); + tracing::trace!(%retry_tokens, command = ?command.as_std(), "Waiting for bootout to succeed"); + + let output = command + .output() + .await + .map_err(|e| ActionErrorKind::command(&command, e))?; + + if output.status.success() { + break; + } else if retry_tokens == 0 { + return Err(ActionErrorKind::command_output(&command, output))?; + } else { + retry_tokens = retry_tokens.saturating_sub(1); + } + + tokio::time::sleep(Duration::from_millis(500)).await; + } + + Ok(()) +} diff --git a/src/action/macos/set_tmutil_exclusion.rs b/src/action/macos/set_tmutil_exclusion.rs index 5c94dc032..1b4d797b9 100644 --- a/src/action/macos/set_tmutil_exclusion.rs +++ b/src/action/macos/set_tmutil_exclusion.rs @@ -25,6 +25,7 @@ to the list of applications which are allowed Full Disk Access. */ #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(tag = "action_name", rename = "set_tmutil_exclusion")] pub struct SetTmutilExclusion { path: PathBuf, } diff --git a/src/action/macos/set_tmutil_exclusions.rs b/src/action/macos/set_tmutil_exclusions.rs index da336e0aa..1693c3447 100644 --- a/src/action/macos/set_tmutil_exclusions.rs +++ b/src/action/macos/set_tmutil_exclusions.rs @@ -25,6 +25,7 @@ to the list of applications which are allowed Full Disk Access. */ #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(tag = "action_name", rename = "set_tmutil_exclusions")] pub struct SetTmutilExclusions { set_tmutil_exclusions: Vec>, } diff --git a/src/action/macos/unmount_apfs_volume.rs b/src/action/macos/unmount_apfs_volume.rs index e303c6e98..5dda0efcf 100644 --- a/src/action/macos/unmount_apfs_volume.rs +++ b/src/action/macos/unmount_apfs_volume.rs @@ -14,6 +14,7 @@ use crate::os::darwin::DiskUtilInfoOutput; Unmount an APFS volume */ #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[serde(tag = "action_name", rename = "unmount_volume")] pub struct UnmountApfsVolume { disk: PathBuf, name: String, @@ -55,14 +56,12 @@ impl Action for UnmountApfsVolume { #[tracing::instrument(level = "debug", skip_all)] async fn execute(&mut self) -> Result<(), ActionError> { - let Self { disk: _, name } = self; - let currently_mounted = { let buf = execute_command( Command::new("/usr/sbin/diskutil") .process_group(0) .args(["info", "-plist"]) - .arg(&name) + .arg(&self.name) .stdin(std::process::Stdio::null()), ) .await @@ -71,15 +70,15 @@ impl Action for UnmountApfsVolume { let the_plist: DiskUtilInfoOutput = plist::from_reader(Cursor::new(buf)).map_err(Self::error)?; - the_plist.mount_point.is_some() + the_plist.is_mounted() }; - if !currently_mounted { + if currently_mounted { execute_command( Command::new("/usr/sbin/diskutil") .process_group(0) .args(["unmount", "force"]) - .arg(name) + .arg(&self.name) .stdin(std::process::Stdio::null()), ) .await @@ -97,14 +96,12 @@ impl Action for UnmountApfsVolume { #[tracing::instrument(level = "debug", skip_all)] async fn revert(&mut self) -> Result<(), ActionError> { - let Self { disk: _, name } = self; - let currently_mounted = { let buf = execute_command( Command::new("/usr/sbin/diskutil") .process_group(0) .args(["info", "-plist"]) - .arg(&name) + .arg(&self.name) .stdin(std::process::Stdio::null()), ) .await @@ -113,15 +110,15 @@ impl Action for UnmountApfsVolume { let the_plist: DiskUtilInfoOutput = plist::from_reader(Cursor::new(buf)).map_err(Self::error)?; - the_plist.mount_point.is_some() + the_plist.is_mounted() }; - if !currently_mounted { + if currently_mounted { execute_command( Command::new("/usr/sbin/diskutil") .process_group(0) .args(["unmount", "force"]) - .arg(name) + .arg(&self.name) .stdin(std::process::Stdio::null()), ) .await diff --git a/src/action/mod.rs b/src/action/mod.rs index 03670e347..c34a77235 100644 --- a/src/action/mod.rs +++ b/src/action/mod.rs @@ -43,6 +43,8 @@ ideal. A custom [`Action`] can be created then used in a custom [`Planner`](crate::planner::Planner): +Note: if the struct has no fields, don't add the `serde` attribute to the struct. + ```rust,no_run use std::{error::Error, collections::HashMap}; use tracing::{Span, span}; @@ -54,13 +56,16 @@ use nix_installer::{ }; #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] -pub struct MyAction {} +#[serde(tag = "action_name", rename = "my_action")] +pub struct MyAction { + my_field: String, // Just an example +} impl MyAction { #[tracing::instrument(level = "debug", skip_all)] pub async fn plan() -> Result, ActionError> { - Ok(Self {}.into()) + Ok(Self { my_field: "my field".to_string() }.into()) } } @@ -166,6 +171,17 @@ impl Planner for MyPlanner { self.common.ssl_cert_file.clone(), )?) } + + async fn platform_check(&self) -> Result<(), PlannerError> { + use target_lexicon::OperatingSystem; + match target_lexicon::OperatingSystem::host() { + OperatingSystem::MacOSX { .. } | OperatingSystem::Darwin => Ok(()), + host_os => Err(PlannerError::IncompatibleOperatingSystem { + planner: self.typetag_name(), + host_os, + }), + } + } } # async fn custom_planner_install() -> color_eyre::Result<()> { @@ -207,7 +223,7 @@ use crate::{error::HasExpectedErrors, settings::UrlOrPathError, CertificateError /// /// Instead of calling [`execute`][Action::execute] or [`revert`][Action::revert], you should prefer [`try_execute`][StatefulAction::try_execute] and [`try_revert`][StatefulAction::try_revert] #[async_trait::async_trait] -#[typetag::serde(tag = "action")] +#[typetag::serde(tag = "action_name")] pub trait Action: Send + Sync + std::fmt::Debug + dyn_clone::DynClone { fn action_tag() -> ActionTag where @@ -270,7 +286,7 @@ pub trait Action: Send + Sync + std::fmt::Debug + dyn_clone::DynClone { dyn_clone::clone_trait_object!(Action); /** -A description of an [`Action`](crate::action::Action), intended for humans to review +A description of an [`Action`], intended for humans to review */ #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct ActionDescription { @@ -288,7 +304,7 @@ impl ActionDescription { } /// A 'tag' name an action has that corresponds to the one we serialize in [`typetag]` -pub struct ActionTag(&'static str); +pub struct ActionTag(pub &'static str); impl std::fmt::Display for ActionTag { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -373,7 +389,7 @@ pub enum ActionErrorKind { if let Some(source) = err.source() { format!("{err}\n{source}\n") } else { - format!("{err}\n") + format!("{err}\n") } }).collect::>().join("\n"))] MultipleChildren(Vec), @@ -386,6 +402,8 @@ pub enum ActionErrorKind { } }).collect::>().join("\n"))] Multiple(Vec), + #[error("Determinate Nix planned, but this installer is not equipped to install it.")] + DeterminateNixUnavailable, /// The path already exists with different content that expected #[error( "`{0}` exists with different content than planned, consider removing it with `rm {0}`" @@ -400,11 +418,11 @@ pub enum ActionErrorKind { /// The symlink already exists #[error("`{0}` already exists, consider removing it with `rm {0}`")] SymlinkExists(std::path::PathBuf), - #[error("`{0}` exists with a different uid ({1}) than planned ({2}), consider updating it with `chown {2} {0}`")] + #[error("`{0}` exists with a different uid ({1}) than planned ({2}), consider updating it with `chown {2} {0}` (you may need to do this recursively with the `-R` flag)")] PathUserMismatch(std::path::PathBuf, u32, u32), - #[error("`{0}` exists with a different gid ({1}) than planned ({2}), consider updating it with `chgrp {2} {0}`")] + #[error("`{0}` exists with a different gid ({1}) than planned ({2}), consider updating it with `chgrp {2} {0}` (you may need to do this recursively with the `-R` flag)")] PathGroupMismatch(std::path::PathBuf, u32, u32), - #[error("`{0}` exists with a different mode ({existing_mode:o}) than planned ({planned_mode:o}), consider updating it with `chmod {planned_mode:o} {0}`", + #[error("`{0}` exists with a different mode ({existing_mode:o}) than planned ({planned_mode:o}), consider updating it with `chmod {planned_mode:o} {0}` (you may need to do this recursively with the `-R` flag)", existing_mode = .1 & 0o777, planned_mode = .2 & 0o777)] PathModeMismatch(std::path::PathBuf, u32, u32), diff --git a/src/bin/nix-installer.rs b/src/bin/nix-installer.rs index 892820fe0..e61f95f08 100644 --- a/src/bin/nix-installer.rs +++ b/src/bin/nix-installer.rs @@ -21,5 +21,7 @@ async fn main() -> eyre::Result { cli.instrumentation.setup()?; + tracing::info!("nix-installer v{}", env!("CARGO_PKG_VERSION")); + cli.execute().await } diff --git a/src/cli/arg/instrumentation.rs b/src/cli/arg/instrumentation.rs index d4ecf652a..085eb8df1 100644 --- a/src/cli/arg/instrumentation.rs +++ b/src/cli/arg/instrumentation.rs @@ -32,10 +32,10 @@ pub struct Instrumentation { /// Enable debug logs, -vv for trace #[clap(short = 'v', env = "NIX_INSTALLER_VERBOSITY", long, action = clap::ArgAction::Count, global = true)] pub verbose: u8, - /// Which logger to use + /// Which logger to use (options are `compact`, `full`, `pretty`, and `json`) #[clap(long, env = "NIX_INSTALLER_LOGGER", default_value_t = Default::default(), global = true)] pub logger: Logger, - /// Tracing directives + /// Tracing directives delimited by comma /// /// See https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives #[clap(long = "log-directive", global = true, env = "NIX_INSTALLER_LOG_DIRECTIVES", value_delimiter = ',', num_args = 0..)] diff --git a/src/cli/subcommand/install.rs b/src/cli/subcommand/install.rs index 45b49c733..5d98523f6 100644 --- a/src/cli/subcommand/install.rs +++ b/src/cli/subcommand/install.rs @@ -40,6 +40,7 @@ Some planners have additional options which can be set from the planner's subcom #[derive(Debug, Parser)] #[command(args_conflicts_with_subcommands = true)] pub struct Install { + /// Run installation without requiring explicit user confirmation #[clap( long, env = "NIX_INSTALLER_NO_CONFIRM", @@ -52,6 +53,7 @@ pub struct Install { #[clap(flatten)] pub settings: CommonSettings, + /// Provide an explanation of the changes the installation process will make to your system #[clap( long, env = "NIX_INSTALLER_EXPLAIN", @@ -61,6 +63,7 @@ pub struct Install { )] pub explain: bool, + /// A path to a non-default installer plan #[clap(env = "NIX_INSTALLER_PLAN")] pub plan: Option, @@ -110,7 +113,7 @@ impl CommandExecute for Install { Some(existing_receipt) => { if let Err(e) = existing_receipt.check_compatible() { eprintln!( - "{}", + "{}", format!("\ {e}\n\ \n\ @@ -161,7 +164,7 @@ impl CommandExecute for Install { Some(existing_receipt) => { if let Err(e) = existing_receipt.check_compatible() { eprintln!( - "{}", + "{}", format!("\ {e}\n\ \n\ diff --git a/src/cli/subcommand/repair.rs b/src/cli/subcommand/repair.rs index 243ff20ad..7548b572b 100644 --- a/src/cli/subcommand/repair.rs +++ b/src/cli/subcommand/repair.rs @@ -1,14 +1,29 @@ +use std::io::IsTerminal as _; use std::process::ExitCode; +use std::time::SystemTime; -use crate::{ - action::common::ConfigureShellProfile, - cli::{ensure_root, CommandExecute}, - planner::{PlannerError, ShellProfileLocations}, -}; -use clap::{ArgAction, Parser}; +use clap::{ArgAction, Parser, Subcommand}; +use eyre::Context as _; +use serde::{Deserialize, Serialize}; +use target_lexicon::OperatingSystem; +use tokio::process::Command; + +use crate::action::base::{AddUserToGroup, CreateGroup, CreateUser}; +use crate::action::common::{ConfigureShellProfile, CreateUsersAndGroups}; +use crate::action::{Action, ActionState, StatefulAction}; +use crate::cli::interaction::PromptChoice; +use crate::cli::{ensure_root, CommandExecute}; +use crate::plan::RECEIPT_LOCATION; +use crate::planner::{PlannerError, ShellProfileLocations}; +use crate::{execute_command, InstallPlan}; + +/// The base UID that we temporarily move build users to while migrating macOS to the new range. +const TEMP_USER_ID_BASE: u32 = 31000; /** -Update the shell profiles to make Nix usable after system upgrades. +Various actions to repair Nix installations. + +The default is to repair shell hooks. */ #[derive(Debug, Parser)] #[command(args_conflicts_with_subcommands = true)] @@ -21,40 +36,538 @@ pub struct Repair { global = true )] pub no_confirm: bool, + + #[command(subcommand)] + command: Option, +} + +#[derive(Clone, Debug, Subcommand, serde::Deserialize, serde::Serialize)] +pub enum RepairKind { + /// Update the shell profiles to make Nix usable after system upgrades. + Hooks, + /// Recover from the macOS 15 Sequoia update taking over _nixbld users. + /// + /// Default functionality is to only attempt the fix if _nixbld users are missing. + /// + /// Can be run before taking a macOS 15 Sequoia update by passing the `--move-existing-users` + /// flag (which will move the Nix build users to the new UID range even if they all currently + /// exist). + Sequoia { + /// The Nix build user prefix (user numbers will be postfixed) + #[cfg_attr( + feature = "cli", + clap(long, env = "NIX_INSTALLER_NIX_BUILD_USER_PREFIX", global = true) + )] + #[cfg_attr( + all(target_os = "macos", feature = "cli"), + clap(default_value = "_nixbld") + )] + #[cfg_attr( + all(target_os = "linux", feature = "cli"), + clap(default_value = "nixbld") + )] + nix_build_user_prefix: String, + + /// The number of build users to ensure exist + #[cfg_attr( + feature = "cli", + clap( + long, + alias = "daemon-user-count", + env = "NIX_INSTALLER_NIX_BUILD_USER_COUNT", + default_value = "32", + global = true + ) + )] + nix_build_user_count: u32, + + /// The Nix build group name + #[cfg_attr( + feature = "cli", + clap( + long, + default_value = "nixbld", + env = "NIX_INSTALLER_NIX_BUILD_GROUP_NAME", + global = true + ) + )] + nix_build_group_name: String, + + /// If `nix-installer` should move the build users to a Sequoia-compatible range, even when + /// they all currently exist + #[cfg_attr( + feature = "cli", + clap( + long, + action(ArgAction::SetTrue), + default_value = "false", + global = true, + env = "NIX_INSTALLER_MOVE_EXISTING_USERS" + ) + )] + move_existing_users: bool, + }, +} + +impl Repair { + pub fn command(&self) -> RepairKind { + self.command.to_owned().unwrap_or(RepairKind::Hooks) + } } #[async_trait::async_trait] impl CommandExecute for Repair { #[tracing::instrument(level = "trace", skip_all)] async fn execute(self) -> eyre::Result { - let Self { no_confirm: _ } = self; + let command = self.command(); ensure_root()?; - let mut reconfigure = ConfigureShellProfile::plan(ShellProfileLocations::default()) - .await - .map_err(PlannerError::Action)? - .boxed(); + let mut repair_actions = Vec::new(); + let (prompt_before_repairing, brief_repair_summary) = match command { + RepairKind::Hooks => ( + false, + String::from("Will ensure the Nix shell profiles are still being sourced"), + ), + RepairKind::Sequoia { + ref nix_build_user_prefix, + nix_build_user_count, + ref nix_build_group_name, + .. + } => { + let maybe_users_and_groups_from_receipt = maybe_users_and_groups_from_receipt( + nix_build_user_prefix, + nix_build_user_count, + nix_build_group_name, + ) + .await?; + + let user_base = crate::settings::default_nix_build_user_id_base(); + let brief_summary = format!( + "Will move the {nix_build_user_prefix} users to the Sequoia-compatible \ + {user_base}+ UID range and {maybe_update_receipt} update the receipt", + maybe_update_receipt = if maybe_users_and_groups_from_receipt + .receipt_action_idx_create_group + .is_some() + { + "WILL" + } else { + "WILL NOT" + } + ); + (!self.no_confirm, brief_summary) + }, + }; - if let Err(err) = reconfigure.try_execute().await { - println!("{:#?}", err); - return Ok(ExitCode::FAILURE); + if prompt_before_repairing { + loop { + match crate::cli::interaction::prompt( + &brief_repair_summary, + PromptChoice::Yes, + true, + ) + .await? + { + PromptChoice::Yes => break, + PromptChoice::No => { + crate::cli::interaction::clean_exit_with_message( + "Okay, didn't do anything! Bye!", + ) + .await + }, + PromptChoice::Explain => (), + } + } + } else { + tracing::info!("{}", brief_repair_summary); } - // TODO: Using `cfg` based on OS is not a long term solution. - // Make this read the planner from the `/nix/receipt.json` to determine which tasks to run. - #[cfg(target_os = "macos")] - { - let mut reconfigure = crate::action::macos::ConfigureRemoteBuilding::plan() - .await - .map_err(PlannerError::Action)? - .boxed(); - - if let Err(err) = reconfigure.try_execute().await { + + // TODO(cole-h): if we add another repair command, make this whole thing more generic + let updated_receipt = match command.clone() { + RepairKind::Hooks => { + let reconfigure = ConfigureShellProfile::plan(ShellProfileLocations::default()) + .await + .map_err(PlannerError::Action)? + .boxed(); + repair_actions.push(reconfigure); + + match OperatingSystem::host() { + OperatingSystem::MacOSX { .. } | OperatingSystem::Darwin => { + let reconfigure = crate::action::macos::ConfigureRemoteBuilding::plan() + .await + .map_err(PlannerError::Action)? + .boxed(); + repair_actions.push(reconfigure); + }, + _ => { + // Linux-specific hook repair actions, once we have them + }, + } + + None + }, + RepairKind::Sequoia { + nix_build_user_prefix, + nix_build_user_count, + nix_build_group_name, + move_existing_users, + } => { + if !matches!( + OperatingSystem::host(), + OperatingSystem::MacOSX { .. } | OperatingSystem::Darwin + ) { + return Err(color_eyre::eyre::eyre!( + "The `sequoia` repair command is only available on macOS" + )); + } + + if !std::io::stdin().is_terminal() && !self.no_confirm { + return Err(color_eyre::eyre::eyre!( + "The `sequoia` repair command should be run in an interactive terminal. If \ + you accept the risks of an unattended repair, pass `--no-confirm`." + )); + } + + let user_base = crate::settings::default_nix_build_user_id_base(); + + let maybe_users_and_groups_from_receipt = maybe_users_and_groups_from_receipt( + &nix_build_user_prefix, + nix_build_user_count, + &nix_build_group_name, + ) + .await?; + + let user_prefix = maybe_users_and_groups_from_receipt.user_prefix; + let user_count = maybe_users_and_groups_from_receipt.user_count; + let group_name = maybe_users_and_groups_from_receipt.group_name; + let group_gid = maybe_users_and_groups_from_receipt.group_gid; + let receipt_action_idx_create_group = + maybe_users_and_groups_from_receipt.receipt_action_idx_create_group; + + if receipt_action_idx_create_group.is_none() { + tracing::warn!( + "Unable to find {} in receipt (receipt didn't exist or is unable to be \ + parsed by this version of the installer). Your receipt at {RECEIPT_LOCATION} \ + will not reflect the changed UIDs, but the users will still be relocated \ + to the new Sequoia-compatible UID range, starting at {user_base}, and \ + uninstallation will continue to work as normal, even if the UIDs do not match.", + CreateUsersAndGroups::action_tag() + ); + } + + let group_plist = { + let buf = execute_command( + Command::new("/usr/bin/dscl") + .process_group(0) + .args(["-plist", ".", "-read", &format!("/Groups/{group_name}")]) + .stdin(std::process::Stdio::null()), + ) + .await? + .stdout; + + let group_plist: GroupPlist = plist::from_bytes(&buf)?; + group_plist + }; + + let expected_users = group_plist + .group_membership + .into_iter() + .enumerate() + .map(|(idx, name)| ((idx + 1) as u32, name)) + .collect::>(); + + let mut missing_users = Vec::new(); + for (user_idx, user_name) in &expected_users { + let ret = execute_command( + Command::new("/usr/bin/dscl") + .process_group(0) + .args([".", "-read", &format!("/Users/{user_name}")]) + .stdin(std::process::Stdio::null()), + ) + .await; + + if let Err(e) = ret { + tracing::debug!(%e, user_name, "Couldn't read user, assuming it's missing"); + missing_users.push((user_idx, user_name)); + } + } + + if missing_users.is_empty() && !move_existing_users { + tracing::info!("Nothing to do! All users appear to be in place!"); + return Ok(ExitCode::SUCCESS); + } + + let mut existing_users = expected_users.clone(); + existing_users.retain(|(idx, _name)| { + !missing_users.iter().any(|(idx2, _name2)| idx == *idx2) + }); + + // NOTE(coleh-h): We move all existing build users into a temp UID range in case a + // user customized the number of users they created and the UIDs would overlap in + // this new range, i.e. with 128 build users, _nixbld81 prior to migration would + // have the same ID as _nixbld31 after the migration and would likely fail. + for (user_idx, user_name) in existing_users { + let temp_user_id = TEMP_USER_ID_BASE + user_idx; + + execute_command( + Command::new("/usr/bin/dscl") + .process_group(0) + // NOTE(cole-h): even though it says "create" it's really "create-or-update" + .args([".", "-create", &format!("/Users/{user_name}"), "UniqueID"]) + .arg(temp_user_id.to_string()) + .stdin(std::process::Stdio::null()), + ) + .await?; + } + + let mut create_users = Vec::with_capacity(user_count as usize); + let group_gid = group_gid.unwrap_or(group_plist.gid); + + for (idx, name) in expected_users { + let create_user = CreateUser::plan( + name, + user_base + idx, + group_name.clone(), + group_gid, + format!("Nix build user {idx}"), + false, + ) + .await?; + create_users.push(create_user); + } + + let mut maybe_updated_receipt = None; + if let Some((mut receipt, action_idx, create_group)) = + receipt_action_idx_create_group + { + // NOTE(cole-h): Once we write the updated receipt, these steps will have been + // completed, so manually setting them to completed with + // StatefulAction::completed is fine. + + let (add_users_to_groups, create_users): ( + Vec>, + Vec>, + ) = create_users + .iter() + .cloned() + .map(|create_user| { + let action = create_user.action; + ( + StatefulAction::completed(AddUserToGroup { + name: action.name.clone(), + uid: action.uid, + groupname: action.groupname.clone(), + gid: action.gid, + }), + StatefulAction::completed(action), + ) + }) + .unzip(); + + let create_users_and_groups = StatefulAction::completed(CreateUsersAndGroups { + nix_build_group_name: group_name.clone(), + nix_build_group_id: group_gid, + nix_build_user_count: user_count, + nix_build_user_prefix: user_prefix.clone(), + nix_build_user_id_base: user_base, + create_group, + create_users: create_users.clone(), + add_users_to_groups, + }); + + let _replaced = std::mem::replace( + &mut receipt.actions[action_idx], + create_users_and_groups.boxed(), + ); + + maybe_updated_receipt = Some(receipt); + } + + let create_users = create_users + .into_iter() + .map(|create_user| create_user.boxed()) + .collect::>(); + repair_actions.extend(create_users); + + maybe_updated_receipt + }, + }; + + for mut action in repair_actions { + if let Err(err) = action.try_execute().await { println!("{:#?}", err); return Ok(ExitCode::FAILURE); } + action.state = ActionState::Completed; + } + + if let Some(updated_receipt) = updated_receipt { + let timestamp_millis = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH)? + .as_millis(); + + let mut old_receipt = std::path::PathBuf::from(RECEIPT_LOCATION); + old_receipt.set_extension(format!("pre-repair.{timestamp_millis}.json")); + tokio::fs::copy(RECEIPT_LOCATION, &old_receipt).await?; + tracing::info!("Backed up pre-repair receipt to {}", old_receipt.display()); + + updated_receipt.write_receipt().await?; + tracing::info!("Wrote updated receipt"); } + tracing::info!("Finished repairing successfully!"); + Ok(ExitCode::SUCCESS) } } + +#[derive(Serialize, Deserialize)] +/// Structured output of `dscl -plist . -read /Groups/{name}` +struct GroupPlist { + #[serde(rename = "dsAttrTypeStandard:GroupMembership")] + group_membership: Vec, + #[serde( + rename = "dsAttrTypeStandard:PrimaryGroupID", + deserialize_with = "deserialize_gid" + )] + gid: u32, +} + +pub fn deserialize_gid<'de, D>(deserializer: D) -> Result +where + D: serde::de::Deserializer<'de>, +{ + let s: Vec = serde::Deserialize::deserialize(deserializer)?; + + let gid_str = s + .first() + .ok_or_else(|| serde::de::Error::invalid_length(0, &"a gid entry"))?; + + let gid: u32 = gid_str.parse().map_err(serde::de::Error::custom)?; + + Ok(gid) +} + +#[tracing::instrument] +async fn get_existing_receipt() -> Option { + match std::path::Path::new(RECEIPT_LOCATION).exists() { + true => { + tracing::debug!("Reading existing receipt"); + let install_plan_string = tokio::fs::read_to_string(RECEIPT_LOCATION).await.ok(); + + match install_plan_string { + Some(s) => match serde_json::from_str::(s.as_str()) { + Ok(plan) => { + tracing::debug!(plan_version = %plan.version, "Able to parse receipt"); + Some(plan) + }, + Err(e) => { + tracing::debug!(?e); + tracing::warn!("Could not parse receipt. Your receipt will not be updated to account for the new UIDs"); + None + }, + }, + _ => None, + } + }, + false => None, + } +} + +#[tracing::instrument(skip_all)] +fn find_users_and_groups( + existing_receipt: Option, +) -> color_eyre::Result> { + let ret = match existing_receipt { + Some(receipt) => { + tracing::debug!("Got existing receipt"); + + let mut maybe_create_users_and_groups_idx_action = None; + for (idx, stateful_action) in receipt.actions.iter().enumerate() { + let action_tag = stateful_action.inner_typetag_name(); + tracing::trace!("Found {action_tag} in receipt"); + + if action_tag == CreateUsersAndGroups::action_tag().0 { + tracing::debug!( + "Found {} in receipt, preparing to roundtrip to extract the real type", + CreateUsersAndGroups::action_tag().0 + ); + // NOTE(cole-h): this round-trip is kinda jank... but Action is not + // object-safe, and I can't think of any other way to get the + // concrete `CreateUsersAndGroups` type out of a `Box`. + let action = &stateful_action.action; + let create_users_and_groups_json = + serde_json::to_string(action).with_context(|| { + format!("round-tripping {action_tag} json to extract real type") + })?; + let create_users_and_groups: CreateUsersAndGroups = + serde_json::from_str(&create_users_and_groups_json).with_context(|| { + format!("round-tripping {action_tag} json to extract real type") + })?; + + maybe_create_users_and_groups_idx_action = + Some((receipt, idx, create_users_and_groups)); + + break; + } + } + + maybe_create_users_and_groups_idx_action + }, + None => { + tracing::debug!( + "Receipt didn't exist or is unable to be parsed by this version of the installer" + ); + None + }, + }; + + Ok(ret) +} + +struct UsersAndGroupsMeta { + user_prefix: String, + user_count: u32, + group_name: String, + group_gid: Option, + receipt_action_idx_create_group: Option<(InstallPlan, usize, StatefulAction)>, +} + +async fn maybe_users_and_groups_from_receipt( + nix_build_user_prefix: &str, + nix_build_user_count: u32, + nix_build_group_name: &str, +) -> eyre::Result { + let existing_receipt = get_existing_receipt().await; + let maybe_create_users_and_groups_idx_action = find_users_and_groups(existing_receipt)?; + + match maybe_create_users_and_groups_idx_action { + Some((receipt, create_users_and_groups_idx, action)) => { + tracing::debug!("Found {} in receipt", CreateUsersAndGroups::action_tag()); + + let user_prefix = action.nix_build_user_prefix; + let user_count = action.nix_build_user_count; + let group_gid = action.nix_build_group_id; + let group_name = action.nix_build_group_name; + + Ok(UsersAndGroupsMeta { + user_prefix, + user_count, + group_name, + group_gid: Some(group_gid), + receipt_action_idx_create_group: Some(( + receipt, + create_users_and_groups_idx, + action.create_group, + )), + }) + }, + None => Ok(UsersAndGroupsMeta { + user_prefix: nix_build_user_prefix.to_string(), + user_count: nix_build_user_count, + group_name: nix_build_group_name.to_string(), + group_gid: None, + receipt_action_idx_create_group: None, + }), + } +} diff --git a/src/cli/subcommand/uninstall.rs b/src/cli/subcommand/uninstall.rs index 6237a9463..d6bd50526 100644 --- a/src/cli/subcommand/uninstall.rs +++ b/src/cli/subcommand/uninstall.rs @@ -141,7 +141,7 @@ impl CommandExecute for Uninstall { if let Err(e) = plan.check_compatible() { let version = plan.version; eprintln!( - "{}", + "{}", format!("\ {e}\n\ \n\ diff --git a/src/error.rs b/src/error.rs index ab8f376de..3c3dc2e80 100644 --- a/src/error.rs +++ b/src/error.rs @@ -19,7 +19,7 @@ pub enum NixInstallerError { if let Some(source) = err.source() { format!("{err}\n{source}\n") } else { - format!("{err}\n") + format!("{err}\n") } }).collect::>().join("\n"))] SelfTest(Vec), @@ -28,7 +28,7 @@ pub enum NixInstallerError { if let Some(source) = err.source() { format!("{err}\n{source}\n") } else { - format!("{err}\n") + format!("{err}\n") } }).collect::>().join("\n"))] ActionRevert(Vec), diff --git a/src/lib.rs b/src/lib.rs index 2ecd8e45a..a2c2793bd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -100,7 +100,14 @@ async fn execute_command(command: &mut Command) -> Result Ok(output), + true => { + tracing::trace!( + stderr = %String::from_utf8_lossy(&output.stderr), + stdout = %String::from_utf8_lossy(&output.stdout), + "Command success" + ); + Ok(output) + }, false => Err(ActionErrorKind::command_output(command, output)), } } diff --git a/src/os/darwin/diskutil.rs b/src/os/darwin/diskutil.rs index 601769faf..0d48e7383 100644 --- a/src/os/darwin/diskutil.rs +++ b/src/os/darwin/diskutil.rs @@ -3,11 +3,21 @@ use std::path::PathBuf; #[derive(serde::Deserialize)] #[serde(rename_all = "PascalCase")] pub struct DiskUtilInfoOutput { + #[cfg_attr(not(target_os = "macos"), allow(dead_code))] pub parent_whole_disk: String, pub global_permissions_enabled: bool, pub mount_point: Option, } +impl DiskUtilInfoOutput { + pub fn is_mounted(&self) -> bool { + match self.mount_point { + None => false, + Some(ref mp) => !mp.as_os_str().is_empty(), + } + } +} + #[derive(serde::Deserialize, Clone, Debug)] #[serde(rename_all = "PascalCase")] pub struct DiskUtilApfsListOutput { @@ -26,3 +36,19 @@ pub struct DiskUtilApfsListVolume { pub name: Option, pub encryption: bool, } + +#[derive(serde::Deserialize, Clone, Debug)] +#[serde(rename_all = "PascalCase")] +pub struct DiskUtilList { + pub all_disks_and_partitions: Vec, +} + +#[derive(serde::Deserialize, Clone, Debug)] +#[serde(rename_all = "PascalCase")] +pub struct DiskUtilListDisk { + #[serde(rename = "OSInternal")] + pub os_internal: bool, + pub device_identifier: String, + #[serde(rename = "Size")] + pub size_bytes: u64, +} diff --git a/src/plan.rs b/src/plan.rs index 6543f0cbc..5505e2c62 100644 --- a/src/plan.rs +++ b/src/plan.rs @@ -15,7 +15,7 @@ pub const RECEIPT_LOCATION: &str = "/nix/receipt.json"; A set of [`Action`]s, along with some metadata, which can be carried out to drive an install or revert */ -#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[derive(Debug, serde::Deserialize, serde::Serialize)] pub struct InstallPlan { pub(crate) version: Version, @@ -50,6 +50,8 @@ impl InstallPlan { where P: Planner + 'static, { + planner.platform_check().await?; + #[cfg(feature = "diagnostics")] let diagnostic_data = Some(planner.diagnostic_data().await?); @@ -67,11 +69,13 @@ impl InstallPlan { } pub async fn pre_uninstall_check(&self) -> Result<(), NixInstallerError> { + self.planner.platform_check().await?; self.planner.pre_uninstall_check().await?; Ok(()) } pub async fn pre_install_check(&self) -> Result<(), NixInstallerError> { + self.planner.platform_check().await?; self.planner.pre_install_check().await?; Ok(()) } @@ -156,7 +160,7 @@ impl InstallPlan { cancel_channel: impl Into>>, ) -> Result<(), NixInstallerError> { self.check_compatible()?; - self.planner.pre_install_check().await?; + self.pre_install_check().await?; let Self { actions, .. } = self; let mut cancel_channel = cancel_channel.into(); @@ -169,7 +173,7 @@ impl InstallPlan { if cancel_channel.try_recv() != Err(tokio::sync::broadcast::error::TryRecvError::Empty) { - if let Err(err) = write_receipt(self.clone()).await { + if let Err(err) = self.write_receipt().await { tracing::error!("Error saving receipt: {:?}", err); } @@ -190,7 +194,7 @@ impl InstallPlan { tracing::info!("Step: {}", action.tracing_synopsis()); if let Err(err) = action.try_execute().await { - if let Err(err) = write_receipt(self.clone()).await { + if let Err(err) = self.write_receipt().await { tracing::error!("Error saving receipt: {:?}", err); } let err = NixInstallerError::Action(err); @@ -210,7 +214,7 @@ impl InstallPlan { } } - write_receipt(self.clone()).await?; + self.write_receipt().await?; if let Err(err) = crate::self_test::self_test() .await @@ -327,7 +331,7 @@ impl InstallPlan { cancel_channel: impl Into>>, ) -> Result<(), NixInstallerError> { self.check_compatible()?; - self.planner.pre_uninstall_check().await?; + self.pre_uninstall_check().await?; let Self { actions, .. } = self; let mut cancel_channel = cancel_channel.into(); @@ -341,7 +345,7 @@ impl InstallPlan { if cancel_channel.try_recv() != Err(tokio::sync::broadcast::error::TryRecvError::Empty) { - if let Err(err) = write_receipt(self.clone()).await { + if let Err(err) = self.write_receipt().await { tracing::error!("Error saving receipt: {:?}", err); } @@ -410,19 +414,31 @@ impl InstallPlan { }) } } -} -async fn write_receipt(plan: InstallPlan) -> Result<(), NixInstallerError> { - tokio::fs::create_dir_all("/nix") - .await - .map_err(|e| NixInstallerError::RecordingReceipt(PathBuf::from("/nix"), e))?; - let install_receipt_path = PathBuf::from(RECEIPT_LOCATION); - let self_json = - serde_json::to_string_pretty(&plan).map_err(NixInstallerError::SerializingReceipt)?; - tokio::fs::write(&install_receipt_path, format!("{self_json}\n")) - .await - .map_err(|e| NixInstallerError::RecordingReceipt(install_receipt_path, e))?; - Result::<(), NixInstallerError>::Ok(()) + pub(crate) async fn write_receipt(&self) -> Result<(), NixInstallerError> { + let install_receipt_path = PathBuf::from(RECEIPT_LOCATION); + let install_receipt_path_tmp = { + let mut install_receipt_path_tmp = install_receipt_path.clone(); + install_receipt_path_tmp.set_extension("tmp"); + install_receipt_path_tmp + }; + let self_json = + serde_json::to_string_pretty(&self).map_err(NixInstallerError::SerializingReceipt)?; + + tokio::fs::create_dir_all("/nix") + .await + .map_err(|e| NixInstallerError::RecordingReceipt(PathBuf::from("/nix"), e))?; + tokio::fs::write(&install_receipt_path_tmp, format!("{self_json}\n")) + .await + .map_err(|e| { + NixInstallerError::RecordingReceipt(install_receipt_path_tmp.clone(), e) + })?; + tokio::fs::rename(&install_receipt_path_tmp, &install_receipt_path) + .await + .map_err(|e| NixInstallerError::RecordingReceipt(install_receipt_path.clone(), e))?; + + Ok(()) + } } pub fn current_version() -> Result { diff --git a/src/planner/linux.rs b/src/planner/linux.rs index 9fc348764..f4ff9291c 100644 --- a/src/planner/linux.rs +++ b/src/planner/linux.rs @@ -1,21 +1,29 @@ +use std::{collections::HashMap, path::Path}; + +use tokio::process::Command; +use which::which; + +use super::ShellProfileLocations; use crate::{ action::{ base::{CreateDirectory, RemoveDirectory}, - common::{ConfigureInitService, ConfigureNix, CreateUsersAndGroups, ProvisionNix}, - linux::ProvisionSelinux, + common::{ + ConfigureNix, ConfigureDeterminateNixdInitService, ConfigureUpstreamInitService, + CreateUsersAndGroups, ProvisionDeterminateNixd, ProvisionNix, + }, + linux::{ + provision_selinux::{DETERMINATE_SELINUX_POLICY_PP_CONTENT, SELINUX_POLICY_PP_CONTENT}, + ProvisionSelinux, + }, StatefulAction, }, error::HasExpectedErrors, planner::{Planner, PlannerError}, - settings::CommonSettings, - settings::{InitSettings, InitSystem, InstallSettingsError}, + settings::{ + determinate_nix_settings, CommonSettings, InitSettings, InitSystem, InstallSettingsError, + }, Action, BuiltinPlanner, }; -use std::{collections::HashMap, path::Path}; -use tokio::process::Command; -use which::which; - -use super::ShellProfileLocations; /// A planner for traditional, mutable Linux systems like Debian, RHEL, or Arch #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] @@ -49,6 +57,15 @@ impl Planner for Linux { .boxed(), ); + if self.settings.determinate_nix { + plan.push( + ProvisionDeterminateNixd::plan() + .await + .map_err(PlannerError::Action)? + .boxed(), + ); + } + plan.push( ProvisionNix::plan(&self.settings.clone()) .await @@ -62,18 +79,29 @@ impl Planner for Linux { .boxed(), ); plan.push( - ConfigureNix::plan(ShellProfileLocations::default(), &self.settings) - .await - .map_err(PlannerError::Action)? - .boxed(), + ConfigureNix::plan( + ShellProfileLocations::default(), + &self.settings, + self.settings.determinate_nix.then(determinate_nix_settings), + ) + .await + .map_err(PlannerError::Action)? + .boxed(), ); if has_selinux { plan.push( - ProvisionSelinux::plan("/usr/share/selinux/packages/nix.pp".into()) - .await - .map_err(PlannerError::Action)? - .boxed(), + ProvisionSelinux::plan( + "/usr/share/selinux/packages/nix.pp".into(), + if self.settings.determinate_nix { + DETERMINATE_SELINUX_POLICY_PP_CONTENT + } else { + SELINUX_POLICY_PP_CONTENT + }, + ) + .await + .map_err(PlannerError::Action)? + .boxed(), ); } @@ -84,12 +112,22 @@ impl Planner for Linux { .boxed(), ); - plan.push( - ConfigureInitService::plan(self.init.init, self.init.start_daemon) - .await - .map_err(PlannerError::Action)? - .boxed(), - ); + if self.settings.determinate_nix { + // effectively disabled by skipping the cli option in settings.rs + plan.push( + ConfigureDeterminateNixdInitService::plan(self.init.init, self.init.start_daemon) + .await + .map_err(PlannerError::Action)? + .boxed(), + ); + } else { + plan.push( + ConfigureUpstreamInitService::plan(self.init.init, self.init.start_daemon) + .await + .map_err(PlannerError::Action)? + .boxed(), + ); + } plan.push( RemoveDirectory::plan(crate::settings::SCRATCH_DIR) .await @@ -139,6 +177,18 @@ impl Planner for Linux { self.settings.ssl_cert_file.clone(), )?) } + + async fn platform_check(&self) -> Result<(), PlannerError> { + use target_lexicon::OperatingSystem; + match target_lexicon::OperatingSystem::host() { + OperatingSystem::Linux => Ok(()), + host_os => Err(PlannerError::IncompatibleOperatingSystem { + planner: self.typetag_name(), + host_os, + }), + } + } + async fn pre_uninstall_check(&self) -> Result<(), PlannerError> { check_not_wsl1()?; diff --git a/src/planner/macos/mod.rs b/src/planner/macos/mod.rs index 352b14244..5bdb55665 100644 --- a/src/planner/macos/mod.rs +++ b/src/planner/macos/mod.rs @@ -11,12 +11,17 @@ use crate::planner::HasExpectedErrors; mod profile_queries; mod profiles; +use crate::os::darwin::diskutil::DiskUtilList; use crate::{ action::{ base::RemoveDirectory, - common::{ConfigureInitService, ConfigureNix, CreateUsersAndGroups, ProvisionNix}, + common::{ + ConfigureNix, ConfigureDeterminateNixdInitService, ConfigureUpstreamInitService, + CreateUsersAndGroups, ProvisionDeterminateNixd, ProvisionNix, + }, macos::{ - ConfigureRemoteBuilding, CreateNixHookService, CreateNixVolume, SetTmutilExclusions, + ConfigureRemoteBuilding, CreateDeterminateNixVolume, CreateNixHookService, + CreateNixVolume, SetTmutilExclusions, }, StatefulAction, }, @@ -24,7 +29,7 @@ use crate::{ os::darwin::DiskUtilInfoOutput, planner::{Planner, PlannerError}, settings::InstallSettingsError, - settings::{CommonSettings, InitSystem}, + settings::{determinate_nix_settings, CommonSettings, InitSystem}, Action, BuiltinPlanner, }; @@ -66,6 +71,24 @@ pub struct Macos { /// The root disk of the target #[cfg_attr(feature = "cli", clap(long, env = "NIX_INSTALLER_ROOT_DISK"))] pub root_disk: Option, + + /// On AWS, put the Nix Store volume on the EC2 instances' instance store volume. + /// + /// WARNING: Using the instance store volume means the machine must never be Stopped in AWS. + /// If the instance is Stopped, the instance store volume is erased, and the installation is broken. + /// The machine can be safely rebooted. + /// + /// Using the instance store volume bypasses the interactive "enable full disk access" step. + /// Without this flag, installations on macOS on EC2 will require manual, graphical intervention when first installed to grant Full Disk Access. + /// + /// Setting this option: + /// * Requires passing --determinate due to complications of AWS's deployment of macOS. + /// * Sets --root-disk to an auto-detected disk + #[cfg_attr( + feature = "cli", + clap(long, default_value = "false", requires = "determinate_nix") + )] + pub use_ec2_instance_store: bool, } async fn default_root_disk() -> Result { @@ -75,19 +98,42 @@ async fn default_root_disk() -> Result { .stdin(std::process::Stdio::null()), ) .await - .unwrap() + .map_err(|e| PlannerError::Custom(Box::new(e)))? .stdout; let the_plist: DiskUtilInfoOutput = plist::from_reader(Cursor::new(buf))?; Ok(the_plist.parent_whole_disk) } +async fn default_internal_root_disk() -> Result, PlannerError> { + let buf = execute_command( + Command::new("/usr/sbin/diskutil") + .args(["list", "-plist", "internal", "virtual"]) + .stdin(std::process::Stdio::null()), + ) + .await + .map_err(|e| PlannerError::Custom(Box::new(e)))? + .stdout; + let the_plist: DiskUtilList = plist::from_reader(Cursor::new(buf))?; + + let mut disks = the_plist + .all_disks_and_partitions + .into_iter() + .filter(|disk| !disk.os_internal) + .collect::>(); + + disks.sort_by_key(|d| d.size_bytes); + + Ok(disks.pop().map(|d| d.device_identifier)) +} + #[async_trait::async_trait] #[typetag::serde(name = "macos")] impl Planner for Macos { async fn default() -> Result { Ok(Self { settings: CommonSettings::default().await?, + use_ec2_instance_store: false, root_disk: Some(default_root_disk().await?), case_sensitive: false, encrypt: None, @@ -96,29 +142,28 @@ impl Planner for Macos { } async fn plan(&self) -> Result>>, PlannerError> { + if self.use_ec2_instance_store && !self.settings.determinate_nix { + return Err(PlannerError::Ec2InstanceStoreRequiresDeterminateNix); + } + let root_disk = match &self.root_disk { root_disk @ Some(_) => root_disk.clone(), None => { - let buf = execute_command( - Command::new("/usr/sbin/diskutil") - .args(["info", "-plist", "/"]) - .stdin(std::process::Stdio::null()), - ) - .await - .unwrap() - .stdout; - let the_plist: DiskUtilInfoOutput = plist::from_reader(Cursor::new(buf)).unwrap(); - - Some(the_plist.parent_whole_disk) + if self.use_ec2_instance_store { + default_internal_root_disk().await? + } else { + Some(default_root_disk().await?) + } }, }; - // The encrypt variable isn't used in the enterprise edition since we have our own plan step for it, - // however this match accounts for enterprise edition so the receipt indicates encrypt: true. + // The encrypt variable isn't used in Determinate Nix since we have our own plan step for it, + // however this match accounts for Determinate Nix so the receipt indicates encrypt: true. // This is a goofy thing to do, but it is in an attempt to make a more globally coherent plan / receipt. - let encrypt = match self.encrypt { - Some(choice) => choice, - None => { + let encrypt = match (self.settings.determinate_nix, self.encrypt) { + (true, _) => true, + (false, Some(choice)) => choice, + (false, None) => { let output = Command::new("/usr/bin/fdesetup") .arg("isactive") .stdout(std::process::Stdio::null()) @@ -137,17 +182,41 @@ impl Planner for Macos { let mut plan = vec![]; - plan.push( - CreateNixVolume::plan( - root_disk.unwrap(), /* We just ensured it was populated */ - self.volume_label.clone(), - self.case_sensitive, - encrypt, - ) - .await - .map_err(PlannerError::Action)? - .boxed(), - ); + if self.settings.determinate_nix { + plan.push( + ProvisionDeterminateNixd::plan() + .await + .map_err(PlannerError::Action)? + .boxed(), + ); + } + + if self.settings.determinate_nix { + plan.push( + CreateDeterminateNixVolume::plan( + root_disk.unwrap(), /* We just ensured it was populated */ + self.volume_label.clone(), + self.case_sensitive, + self.settings.force, + self.use_ec2_instance_store, + ) + .await + .map_err(PlannerError::Action)? + .boxed(), + ); + } else { + plan.push( + CreateNixVolume::plan( + root_disk.unwrap(), /* We just ensured it was populated */ + self.volume_label.clone(), + self.case_sensitive, + encrypt, + ) + .await + .map_err(PlannerError::Action)? + .boxed(), + ); + } plan.push( ProvisionNix::plan(&self.settings) @@ -170,10 +239,14 @@ impl Planner for Macos { .boxed(), ); plan.push( - ConfigureNix::plan(ShellProfileLocations::default(), &self.settings) - .await - .map_err(PlannerError::Action)? - .boxed(), + ConfigureNix::plan( + ShellProfileLocations::default(), + &self.settings, + self.settings.determinate_nix.then(determinate_nix_settings), + ) + .await + .map_err(PlannerError::Action)? + .boxed(), ); plan.push( ConfigureRemoteBuilding::plan() @@ -191,12 +264,22 @@ impl Planner for Macos { ); } - plan.push( - ConfigureInitService::plan(InitSystem::Launchd, true) - .await - .map_err(PlannerError::Action)? - .boxed(), - ); + if self.settings.determinate_nix { + // effectively disabled by skipping the cli option in settings.rs + plan.push( + ConfigureDeterminateNixdInitService::plan(InitSystem::Launchd, true) + .await + .map_err(PlannerError::Action)? + .boxed(), + ); + } else { + plan.push( + ConfigureUpstreamInitService::plan(InitSystem::Launchd, true) + .await + .map_err(PlannerError::Action)? + .boxed(), + ); + } plan.push( RemoveDirectory::plan(crate::settings::SCRATCH_DIR) .await @@ -214,6 +297,7 @@ impl Planner for Macos { volume_label, case_sensitive, root_disk, + use_ec2_instance_store, } = self; let mut map = HashMap::default(); @@ -221,6 +305,10 @@ impl Planner for Macos { map.insert("volume_encrypt".into(), serde_json::to_value(encrypt)?); map.insert("volume_label".into(), serde_json::to_value(volume_label)?); map.insert("root_disk".into(), serde_json::to_value(root_disk)?); + map.insert( + "use_ec2_instance_store".into(), + serde_json::to_value(use_ec2_instance_store)?, + ); map.insert( "case_sensitive".into(), serde_json::to_value(case_sensitive)?, @@ -259,6 +347,17 @@ impl Planner for Macos { )?) } + async fn platform_check(&self) -> Result<(), PlannerError> { + use target_lexicon::OperatingSystem; + match target_lexicon::OperatingSystem::host() { + OperatingSystem::MacOSX { .. } | OperatingSystem::Darwin => Ok(()), + host_os => Err(PlannerError::IncompatibleOperatingSystem { + planner: self.typetag_name(), + host_os, + }), + } + } + async fn pre_uninstall_check(&self) -> Result<(), PlannerError> { check_nix_darwin_not_installed().await?; @@ -359,15 +458,6 @@ async fn check_suis() -> Result<(), PlannerError> { .map_err(|e| PlannerError::Custom(Box::new(e))) } -#[cfg(not(feature = "nix-community"))] -async fn check_enterprise_edition_available() -> Result<(), PlannerError> { - tokio::fs::metadata("/usr/local/bin/determinate-nix-ee") - .await - .map_err(|_| PlannerError::EnterpriseEditionUnavailable)?; - - Ok(()) -} - #[non_exhaustive] #[derive(thiserror::Error, Debug)] pub enum MacosError { diff --git a/src/planner/macos/profile.sample.block.plist b/src/planner/macos/profile.sample.block.plist index 97abe5a98..7d0c58b46 100644 --- a/src/planner/macos/profile.sample.block.plist +++ b/src/planner/macos/profile.sample.block.plist @@ -36,7 +36,7 @@ harddisk-internal - + --> deny diff --git a/src/planner/macos/profile.sample.unknown.plist b/src/planner/macos/profile.sample.unknown.plist index 571f94ef2..89e3e7604 100644 --- a/src/planner/macos/profile.sample.unknown.plist +++ b/src/planner/macos/profile.sample.unknown.plist @@ -42,7 +42,7 @@ ProfileVerificationState verified ProfileVersion - 1 + 1 diff --git a/src/planner/macos/profile_queries.rs b/src/planner/macos/profile_queries.rs index 36fa23803..9f22c29c4 100644 --- a/src/planner/macos/profile_queries.rs +++ b/src/planner/macos/profile_queries.rs @@ -77,11 +77,11 @@ pub fn blocks_internal_mounting(policies: &Policies) -> Vec Result<(), PlannerError> { + use target_lexicon::OperatingSystem; + match target_lexicon::OperatingSystem::host() { + OperatingSystem::MacOSX { .. } | OperatingSystem::Darwin => Ok(()), + host_os => Err(PlannerError::IncompatibleOperatingSystem { + planner: self.typetag_name(), + host_os, + }), + } + } } # async fn custom_planner_install() -> color_eyre::Result<()> { @@ -103,13 +114,9 @@ match plan.install(None).await { ``` */ -#[cfg(target_os = "linux")] pub mod linux; -#[cfg(target_os = "macos")] pub mod macos; -#[cfg(target_os = "linux")] pub mod ostree; -#[cfg(target_os = "linux")] pub mod steam_deck; use std::{collections::HashMap, path::PathBuf, string::FromUtf8Error}; @@ -147,6 +154,8 @@ pub trait Planner: std::fmt::Debug + Send + Sync + dyn_clone::DynClone { Box::new(self) } + async fn platform_check(&self) -> Result<(), PlannerError>; + async fn pre_uninstall_check(&self) -> Result<(), PlannerError> { Ok(()) } @@ -165,17 +174,17 @@ dyn_clone::clone_trait_object!(Planner); #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "cli", derive(clap::Subcommand))] pub enum BuiltinPlanner { - #[cfg(target_os = "linux")] + #[cfg_attr(not(target_os = "linux"), clap(hide = true))] /// A planner for traditional, mutable Linux systems like Debian, RHEL, or Arch Linux(linux::Linux), + #[cfg_attr(not(target_os = "linux"), clap(hide = true))] /// A planner for the Valve Steam Deck running SteamOS - #[cfg(target_os = "linux")] SteamDeck(steam_deck::SteamDeck), + #[cfg_attr(not(target_os = "linux"), clap(hide = true))] /// A planner suitable for immutable systems using ostree, such as Fedora Silverblue - #[cfg(target_os = "linux")] Ostree(ostree::Ostree), + #[cfg_attr(not(target_os = "macos"), clap(hide = true))] /// A planner for MacOS (Darwin) systems - #[cfg(target_os = "macos")] Macos(macos::Macos), } @@ -184,22 +193,17 @@ impl BuiltinPlanner { pub async fn default() -> Result { use target_lexicon::{Architecture, OperatingSystem}; match (Architecture::host(), OperatingSystem::host()) { - #[cfg(target_os = "linux")] (Architecture::X86_64, OperatingSystem::Linux) => Self::detect_linux_distro().await, - #[cfg(target_os = "linux")] (Architecture::X86_32(_), OperatingSystem::Linux) => { Ok(Self::Linux(linux::Linux::default().await?)) }, - #[cfg(target_os = "linux")] (Architecture::Aarch64(_), OperatingSystem::Linux) => { Ok(Self::Linux(linux::Linux::default().await?)) }, - #[cfg(target_os = "macos")] (Architecture::X86_64, OperatingSystem::MacOSX { .. }) | (Architecture::X86_64, OperatingSystem::Darwin) => { Ok(Self::Macos(macos::Macos::default().await?)) }, - #[cfg(target_os = "macos")] (Architecture::Aarch64(_), OperatingSystem::MacOSX { .. }) | (Architecture::Aarch64(_), OperatingSystem::Darwin) => { Ok(Self::Macos(macos::Macos::default().await?)) @@ -208,7 +212,6 @@ impl BuiltinPlanner { } } - #[cfg(target_os = "linux")] async fn detect_linux_distro() -> Result { let is_steam_deck = os_release::OsRelease::new().is_ok_and(|os_release| os_release.id == "steamos"); @@ -231,13 +234,9 @@ impl BuiltinPlanner { pub async fn from_common_settings(settings: CommonSettings) -> Result { let mut built = Self::default().await?; match &mut built { - #[cfg(target_os = "linux")] BuiltinPlanner::Linux(inner) => inner.settings = settings, - #[cfg(target_os = "linux")] BuiltinPlanner::SteamDeck(inner) => inner.settings = settings, - #[cfg(target_os = "linux")] BuiltinPlanner::Ostree(inner) => inner.settings = settings, - #[cfg(target_os = "macos")] BuiltinPlanner::Macos(inner) => inner.settings = settings, } Ok(built) @@ -247,64 +246,44 @@ impl BuiltinPlanner { &self, ) -> Result, PlannerError> { match self { - #[cfg(target_os = "linux")] BuiltinPlanner::Linux(inner) => inner.configured_settings().await, - #[cfg(target_os = "linux")] BuiltinPlanner::SteamDeck(inner) => inner.configured_settings().await, - #[cfg(target_os = "linux")] BuiltinPlanner::Ostree(inner) => inner.configured_settings().await, - #[cfg(target_os = "macos")] BuiltinPlanner::Macos(inner) => inner.configured_settings().await, } } pub async fn plan(self) -> Result { match self { - #[cfg(target_os = "linux")] BuiltinPlanner::Linux(planner) => InstallPlan::plan(planner).await, - #[cfg(target_os = "linux")] BuiltinPlanner::SteamDeck(planner) => InstallPlan::plan(planner).await, - #[cfg(target_os = "linux")] BuiltinPlanner::Ostree(planner) => InstallPlan::plan(planner).await, - #[cfg(target_os = "macos")] BuiltinPlanner::Macos(planner) => InstallPlan::plan(planner).await, } } pub fn boxed(self) -> Box { match self { - #[cfg(target_os = "linux")] BuiltinPlanner::Linux(i) => i.boxed(), - #[cfg(target_os = "linux")] BuiltinPlanner::SteamDeck(i) => i.boxed(), - #[cfg(target_os = "linux")] BuiltinPlanner::Ostree(i) => i.boxed(), - #[cfg(target_os = "macos")] BuiltinPlanner::Macos(i) => i.boxed(), } } pub fn typetag_name(&self) -> &'static str { match self { - #[cfg(target_os = "linux")] BuiltinPlanner::Linux(i) => i.typetag_name(), - #[cfg(target_os = "linux")] BuiltinPlanner::SteamDeck(i) => i.typetag_name(), - #[cfg(target_os = "linux")] BuiltinPlanner::Ostree(i) => i.typetag_name(), - #[cfg(target_os = "macos")] BuiltinPlanner::Macos(i) => i.typetag_name(), } } pub fn settings(&self) -> Result, InstallSettingsError> { match self { - #[cfg(target_os = "linux")] BuiltinPlanner::Linux(i) => i.settings(), - #[cfg(target_os = "linux")] BuiltinPlanner::SteamDeck(i) => i.settings(), - #[cfg(target_os = "linux")] BuiltinPlanner::Ostree(i) => i.settings(), - #[cfg(target_os = "macos")] BuiltinPlanner::Macos(i) => i.settings(), } } @@ -314,13 +293,9 @@ impl BuiltinPlanner { &self, ) -> Result { match self { - #[cfg(target_os = "linux")] BuiltinPlanner::Linux(i) => i.diagnostic_data().await, - #[cfg(target_os = "linux")] BuiltinPlanner::SteamDeck(i) => i.diagnostic_data().await, - #[cfg(target_os = "linux")] BuiltinPlanner::Ostree(i) => i.diagnostic_data().await, - #[cfg(target_os = "macos")] BuiltinPlanner::Macos(i) => i.diagnostic_data().await, } } @@ -392,6 +367,11 @@ impl Default for FishShellProfileLocations { #[non_exhaustive] #[derive(thiserror::Error, Debug, strum::IntoStaticStr)] pub enum PlannerError { + #[error("The selected planner (`{planner}`) does not support the host's operating system (`{host_os}`)")] + IncompatibleOperatingSystem { + planner: &'static str, + host_os: target_lexicon::OperatingSystem, + }, /// `nix-installer` does not have a default planner for the target architecture right now #[error("`nix-installer` does not have a default planner for the `{0}` architecture right now, pass a specific archetype")] UnsupportedArchitecture(target_lexicon::Triple), @@ -415,8 +395,10 @@ pub enum PlannerError { Sysctl(#[from] sysctl::SysctlError), #[error("Detected that this process is running under Rosetta, using Nix in Rosetta is not supported (Please open an issue with your use case)")] RosettaDetected, - #[error("Determinate Nix Enterprise Edition is not available. See: https://determinate.systems/enterprise")] - EnterpriseEditionUnavailable, + #[error("Determinate Nix is not available. See: https://determinate.systems/enterprise")] + DeterminateNixUnavailable, + #[error("Running Nix on the EC2 instance store requires Determinate Nix to be enabled")] + Ec2InstanceStoreRequiresDeterminateNix, /// A Linux SELinux related error #[error("Unable to install on an SELinux system without common SELinux tooling, the binaries `restorecon`, and `semodule` are required")] SelinuxRequirements, @@ -448,8 +430,10 @@ impl HasExpectedErrors for PlannerError { PlannerError::InstallSettings(_) => None, PlannerError::Plist(_) => None, PlannerError::Sysctl(_) => None, + this @ PlannerError::IncompatibleOperatingSystem { .. } => Some(Box::new(this)), this @ PlannerError::RosettaDetected => Some(Box::new(this)), - this @ PlannerError::EnterpriseEditionUnavailable => Some(Box::new(this)), + this @ PlannerError::DeterminateNixUnavailable => Some(Box::new(this)), + this @ PlannerError::Ec2InstanceStoreRequiresDeterminateNix => Some(Box::new(this)), PlannerError::OsRelease(_) => None, PlannerError::Utf8(_) => None, PlannerError::SelinuxRequirements => Some(Box::new(self)), diff --git a/src/planner/ostree.rs b/src/planner/ostree.rs index fa246a443..f92d7d9e5 100644 --- a/src/planner/ostree.rs +++ b/src/planner/ostree.rs @@ -1,14 +1,19 @@ use crate::{ action::{ base::{CreateDirectory, CreateFile, RemoveDirectory}, - common::{ConfigureInitService, ConfigureNix, CreateUsersAndGroups, ProvisionNix}, - linux::{ProvisionSelinux, StartSystemdUnit, SystemctlDaemonReload}, + common::{ + ConfigureNix, ConfigureUpstreamInitService, CreateUsersAndGroups, + ProvisionDeterminateNixd, ProvisionNix, + }, + linux::{ + provision_selinux::{DETERMINATE_SELINUX_POLICY_PP_CONTENT, SELINUX_POLICY_PP_CONTENT}, + ProvisionSelinux, StartSystemdUnit, SystemctlDaemonReload, + }, StatefulAction, }, error::HasExpectedErrors, planner::{Planner, PlannerError}, - settings::CommonSettings, - settings::{InitSystem, InstallSettingsError}, + settings::{determinate_nix_settings, CommonSettings, InitSystem, InstallSettingsError}, Action, BuiltinPlanner, }; use std::{collections::HashMap, path::PathBuf}; @@ -171,6 +176,15 @@ impl Planner for Ostree { .boxed(), ); + if self.settings.determinate_nix { + plan.push( + ProvisionDeterminateNixd::plan() + .await + .map_err(PlannerError::Action)? + .boxed(), + ); + } + plan.push( ProvisionNix::plan(&self.settings.clone()) .await @@ -184,18 +198,29 @@ impl Planner for Ostree { .boxed(), ); plan.push( - ConfigureNix::plan(shell_profile_locations, &self.settings) - .await - .map_err(PlannerError::Action)? - .boxed(), + ConfigureNix::plan( + shell_profile_locations, + &self.settings, + self.settings.determinate_nix.then(determinate_nix_settings), + ) + .await + .map_err(PlannerError::Action)? + .boxed(), ); if has_selinux { plan.push( - ProvisionSelinux::plan("/etc/nix-installer/selinux/packages/nix.pp".into()) - .await - .map_err(PlannerError::Action)? - .boxed(), + ProvisionSelinux::plan( + "/etc/nix-installer/selinux/packages/nix.pp".into(), + if self.settings.determinate_nix { + DETERMINATE_SELINUX_POLICY_PP_CONTENT + } else { + SELINUX_POLICY_PP_CONTENT + }, + ) + .await + .map_err(PlannerError::Action)? + .boxed(), ); } @@ -207,7 +232,7 @@ impl Planner for Ostree { ); plan.push( - ConfigureInitService::plan(InitSystem::Systemd, true) + ConfigureUpstreamInitService::plan(InitSystem::Systemd, true) .await .map_err(PlannerError::Action)? .boxed(), @@ -279,6 +304,18 @@ impl Planner for Ostree { self.settings.ssl_cert_file.clone(), )?) } + + async fn platform_check(&self) -> Result<(), PlannerError> { + use target_lexicon::OperatingSystem; + match target_lexicon::OperatingSystem::host() { + OperatingSystem::Linux => Ok(()), + host_os => Err(PlannerError::IncompatibleOperatingSystem { + planner: self.typetag_name(), + host_os, + }), + } + } + async fn pre_uninstall_check(&self) -> Result<(), PlannerError> { check_not_wsl1()?; diff --git a/src/planner/steam_deck.rs b/src/planner/steam_deck.rs index 915da62ad..dca2f4b36 100644 --- a/src/planner/steam_deck.rs +++ b/src/planner/steam_deck.rs @@ -103,7 +103,10 @@ use tokio::process::Command; use crate::{ action::{ base::{CreateDirectory, CreateFile, RemoveDirectory}, - common::{ConfigureInitService, ConfigureNix, CreateUsersAndGroups, ProvisionNix}, + common::{ + ConfigureNix, ConfigureUpstreamInitService, CreateUsersAndGroups, + ProvisionDeterminateNixd, ProvisionNix, + }, linux::{ EnsureSteamosNixDirectory, RevertCleanSteamosNixOffload, StartSystemdUnit, SystemctlDaemonReload, @@ -111,7 +114,7 @@ use crate::{ Action, StatefulAction, }, planner::{Planner, PlannerError}, - settings::{CommonSettings, InitSystem, InstallSettingsError}, + settings::{determinate_nix_settings, CommonSettings, InitSystem, InstallSettingsError}, BuiltinPlanner, }; @@ -319,6 +322,15 @@ impl Planner for SteamDeck { ) } + if self.settings.determinate_nix { + actions.push( + ProvisionDeterminateNixd::plan() + .await + .map_err(PlannerError::Action)? + .boxed(), + ); + } + actions.append(&mut vec![ ProvisionNix::plan(&self.settings.clone()) .await @@ -328,12 +340,16 @@ impl Planner for SteamDeck { .await .map_err(PlannerError::Action)? .boxed(), - ConfigureNix::plan(shell_profile_locations, &self.settings) - .await - .map_err(PlannerError::Action)? - .boxed(), + ConfigureNix::plan( + shell_profile_locations, + &self.settings, + self.settings.determinate_nix.then(determinate_nix_settings), + ) + .await + .map_err(PlannerError::Action)? + .boxed(), // Init is required for the steam-deck archetype to make the `/nix` mount - ConfigureInitService::plan(InitSystem::Systemd, true) + ConfigureUpstreamInitService::plan(InitSystem::Systemd, true) .await .map_err(PlannerError::Action)? .boxed(), @@ -399,6 +415,17 @@ impl Planner for SteamDeck { )?) } + async fn platform_check(&self) -> Result<(), PlannerError> { + use target_lexicon::OperatingSystem; + match target_lexicon::OperatingSystem::host() { + OperatingSystem::Linux => Ok(()), + host_os => Err(PlannerError::IncompatibleOperatingSystem { + planner: self.typetag_name(), + host_os, + }), + } + } + async fn pre_uninstall_check(&self) -> Result<(), PlannerError> { super::linux::check_not_wsl1()?; diff --git a/src/self_test.rs b/src/self_test.rs index d06b6c658..845c8a5f7 100644 --- a/src/self_test.rs +++ b/src/self_test.rs @@ -93,8 +93,6 @@ impl Shell { #[cfg(all(target_os = "linux", target_arch = "x86_64"))] const SYSTEM: &str = "x86_64-linux"; - #[cfg(all(target_os = "linux", target_arch = "x86"))] - const SYSTEM: &str = "x86-linux"; #[cfg(all(target_os = "linux", target_arch = "aarch64"))] const SYSTEM: &str = "aarch64-linux"; #[cfg(all(target_os = "macos", target_arch = "x86_64"))] @@ -107,7 +105,7 @@ impl Shell { .as_millis(); command.arg(format!( - r#"nix-build --no-link --expr 'derivation {{ name = "self-test-{executable}-{timestamp_millis}"; system = "{SYSTEM}"; builder = "/bin/sh"; args = ["-c" "echo hello > \$out"]; }}'"# + r#"nix-build --option substitute false --no-link --expr 'derivation {{ name = "self-test-{executable}-{timestamp_millis}"; system = "{SYSTEM}"; builder = "/bin/sh"; args = ["-c" "echo hello > \$out"]; }}'"# )); let command_str = format!("{:?}", command.as_std()); diff --git a/src/settings.rs b/src/settings.rs index b7c7904ba..717cbf7c0 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -7,6 +7,7 @@ use clap::{ error::{ContextKind, ContextValue}, ArgAction, }; +use indexmap::map::Entry; use url::Url; pub const SCRATCH_DIR: &str = "/nix/temp-install-dir"; @@ -17,25 +18,32 @@ pub const NIX_TARBALL_PATH: &str = env!("NIX_INSTALLER_TARBALL_PATH"); /// in the resulting binary. pub const NIX_TARBALL: &[u8] = include_bytes!(env!("NIX_INSTALLER_TARBALL_PATH")); +#[cfg(feature = "determinate-nix")] +/// The DETERMINATE_NIXD_BINARY_PATH environment variable should point to a target-appropriate +/// static build of the Determinate Nixd binary. The contents are embedded in the resulting +/// binary if the determinate-nix feature is turned on. +pub const DETERMINATE_NIXD_BINARY: Option<&[u8]> = + Some(include_bytes!(env!("DETERMINATE_NIXD_BINARY_PATH"))); + +#[cfg(not(feature = "determinate-nix"))] +/// The DETERMINATE_NIXD_BINARY_PATH environment variable should point to a target-appropriate +/// static build of the Determinate Nixd binary. The contents are embedded in the resulting +/// binary if the determinate-nix feature is turned on. +pub const DETERMINATE_NIXD_BINARY: Option<&[u8]> = None; + #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, Copy, PartialEq, Eq)] #[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"), } } @@ -50,7 +58,20 @@ Settings which only apply to certain [`Planner`](crate::planner::Planner)s shoul #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[cfg_attr(feature = "cli", derive(clap::Parser))] pub struct CommonSettings { - /// Modify the user profile to automatically load nix + /// Enable Determinate Nix. See: + // setting skip = false means this always gets the value false, which is easier than completely removing this from the struct and changing all instances it is used + #[cfg_attr( + feature = "cli", + clap( + // long = "determinate", + // env = "NIX_INSTALLER_DETERMINATE", + // default_value = "false", + skip = false, + ) + )] + pub determinate_nix: bool, + + /// Modify the user profile to automatically load Nix #[cfg_attr( feature = "cli", clap( @@ -78,12 +99,11 @@ pub struct CommonSettings { /// The Nix build group GID #[cfg_attr( feature = "cli", - clap( - long, - default_value_t = 30_000, - env = "NIX_INSTALLER_NIX_BUILD_GROUP_ID", - global = true - ) + clap(long, env = "NIX_INSTALLER_NIX_BUILD_GROUP_ID", global = true) + )] + #[cfg_attr( + all(feature = "cli"), + clap(default_value_t = default_nix_build_group_id()) )] pub nix_build_group_id: u32, @@ -102,7 +122,7 @@ pub struct CommonSettings { )] pub nix_build_user_prefix: String, - /// Number of build users to create + /// The number of build users to create #[cfg_attr( feature = "cli", clap( @@ -125,10 +145,9 @@ pub struct CommonSettings { all(target_os = "macos", feature = "cli"), doc = "Service users on Mac should be between 200-400" )] - #[cfg_attr(all(target_os = "macos", feature = "cli"), clap(default_value_t = 300))] #[cfg_attr( - all(target_os = "linux", feature = "cli"), - clap(default_value_t = 30_000) + all(feature = "cli"), + clap(default_value_t = default_nix_build_user_id_base()) )] pub nix_build_user_id_base: u32, @@ -139,11 +158,11 @@ pub struct CommonSettings { )] pub nix_package_url: Option, - /// The proxy to use (if any), valid proxy bases are `https://$URL`, `http://$URL` and `socks5://$URL` + /// The proxy to use (if any); valid proxy bases are `https://$URL`, `http://$URL` and `socks5://$URL` #[cfg_attr(feature = "cli", clap(long, env = "NIX_INSTALLER_PROXY"))] pub proxy: Option, - /// An SSL cert to use (if any), used for fetching Nix and sets `ssl-cert-file` in `/etc/nix/nix.conf` + /// An SSL cert to use (if any); used for fetching Nix and sets `ssl-cert-file` in `/etc/nix/nix.conf` #[cfg_attr(feature = "cli", clap(long, env = "NIX_INSTALLER_SSL_CERT_FILE"))] pub ssl_cert_file: Option, @@ -220,46 +239,47 @@ pub struct CommonSettings { pub add_channel: bool, } +pub(crate) fn default_nix_build_user_id_base() -> u32 { + use target_lexicon::OperatingSystem; + + match OperatingSystem::host() { + OperatingSystem::MacOSX { .. } | OperatingSystem::Darwin => 350, + _ => 30_000, + } +} + +pub(crate) fn default_nix_build_group_id() -> u32 { + use target_lexicon::OperatingSystem; + + match OperatingSystem::host() { + OperatingSystem::MacOSX { .. } | OperatingSystem::Darwin => 350, + _ => 30_000, + } +} + impl CommonSettings { /// The default settings for the given Architecture & Operating System pub async fn default() -> Result { let nix_build_user_prefix; - let nix_build_user_id_base; - let nix_build_user_count; use target_lexicon::{Architecture, OperatingSystem}; match (Architecture::host(), OperatingSystem::host()) { - #[cfg(target_os = "linux")] (Architecture::X86_64, OperatingSystem::Linux) => { nix_build_user_prefix = "nixbld"; - nix_build_user_id_base = 30000; - nix_build_user_count = 32; }, - #[cfg(target_os = "linux")] (Architecture::X86_32(_), OperatingSystem::Linux) => { nix_build_user_prefix = "nixbld"; - nix_build_user_id_base = 30000; - nix_build_user_count = 32; }, - #[cfg(target_os = "linux")] (Architecture::Aarch64(_), OperatingSystem::Linux) => { nix_build_user_prefix = "nixbld"; - nix_build_user_id_base = 30000; - nix_build_user_count = 32; }, - #[cfg(target_os = "macos")] (Architecture::X86_64, OperatingSystem::MacOSX { .. }) | (Architecture::X86_64, OperatingSystem::Darwin) => { nix_build_user_prefix = "_nixbld"; - nix_build_user_id_base = 300; - nix_build_user_count = 32; }, - #[cfg(target_os = "macos")] (Architecture::Aarch64(_), OperatingSystem::MacOSX { .. }) | (Architecture::Aarch64(_), OperatingSystem::Darwin) => { nix_build_user_prefix = "_nixbld"; - nix_build_user_id_base = 300; - nix_build_user_count = 32; }, _ => { return Err(InstallSettingsError::UnsupportedArchitecture( @@ -269,11 +289,12 @@ impl CommonSettings { }; Ok(Self { + determinate_nix: false, modify_profile: true, nix_build_group_name: String::from("nixbld"), - nix_build_group_id: 30_000, - nix_build_user_id_base, - nix_build_user_count, + nix_build_group_id: default_nix_build_group_id(), + nix_build_user_id_base: default_nix_build_user_id_base(), + nix_build_user_count: 32, nix_build_user_prefix: nix_build_user_prefix.to_string(), nix_package_url: None, proxy: Default::default(), @@ -291,6 +312,7 @@ impl CommonSettings { /// A listing of the settings, suitable for [`Planner::settings`](crate::planner::Planner::settings) pub fn settings(&self) -> Result, InstallSettingsError> { let Self { + determinate_nix, modify_profile, nix_build_group_name, nix_build_group_id, @@ -310,6 +332,10 @@ impl CommonSettings { } = self; let mut map = HashMap::default(); + map.insert( + "determinate_nix".into(), + serde_json::to_value(determinate_nix)?, + ); map.insert( "modify_profile".into(), serde_json::to_value(modify_profile)?, @@ -355,7 +381,6 @@ impl CommonSettings { } } -#[cfg(target_os = "linux")] async fn linux_detect_systemd_started() -> bool { use std::process::Stdio; @@ -413,22 +438,17 @@ impl InitSettings { pub async fn default() -> Result { use target_lexicon::{Architecture, OperatingSystem}; let (init, start_daemon) = match (Architecture::host(), OperatingSystem::host()) { - #[cfg(target_os = "linux")] (Architecture::X86_64, OperatingSystem::Linux) => { (InitSystem::Systemd, linux_detect_systemd_started().await) }, - #[cfg(target_os = "linux")] (Architecture::X86_32(_), OperatingSystem::Linux) => { (InitSystem::Systemd, linux_detect_systemd_started().await) }, - #[cfg(target_os = "linux")] (Architecture::Aarch64(_), OperatingSystem::Linux) => { (InitSystem::Systemd, linux_detect_systemd_started().await) }, - #[cfg(target_os = "macos")] (Architecture::X86_64, OperatingSystem::MacOSX { .. }) | (Architecture::X86_64, OperatingSystem::Darwin) => (InitSystem::Launchd, true), - #[cfg(target_os = "macos")] (Architecture::Aarch64(_), OperatingSystem::MacOSX { .. }) | (Architecture::Aarch64(_), OperatingSystem::Darwin) => (InitSystem::Launchd, true), _ => { @@ -688,3 +708,28 @@ mod tests { Ok(()) } } + +pub fn determinate_nix_settings() -> nix_config_parser::NixConfig { + let mut cfg = nix_config_parser::NixConfig::new(); + let settings = cfg.settings_mut(); + + settings.insert("netrc-file".into(), "/nix/var/determinate/netrc".into()); + + let extra_substituters = ["https://cache.flakehub.com"]; + match settings.entry("extra-substituters".to_string()) { + Entry::Occupied(mut slot) => { + let slot_mut = slot.get_mut(); + for extra_substituter in extra_substituters { + if !slot_mut.contains(extra_substituter) { + *slot_mut += " "; + *slot_mut += extra_substituter; + } + } + }, + Entry::Vacant(slot) => { + let _ = slot.insert(extra_substituters.join(" ")); + }, + }; + + cfg +} diff --git a/tests/fixtures/linux/linux.json b/tests/fixtures/linux/linux.json index a300bb358..471c9e94f 100644 --- a/tests/fixtures/linux/linux.json +++ b/tests/fixtures/linux/linux.json @@ -1,9 +1,9 @@ { - "version": "0.19.0", + "version": "0.27.0", "actions": [ { "action": { - "action": "create_directory", + "action_name": "create_directory", "path": "/nix", "user": null, "group": null, @@ -15,9 +15,10 @@ }, { "action": { - "action": "provision_nix", + "action_name": "provision_nix", "fetch_nix": { "action": { + "action_name": "fetch_and_unpack_nix", "url_or_path": { "Url": "https://releases.nixos.org/nix/nix-2.17.0/nix-2.17.0-x86_64-linux.tar.xz" }, @@ -30,6 +31,7 @@ "delete_users": [], "create_group": { "action": { + "action_name": "create_group", "name": "nixbld", "gid": 30000 }, @@ -37,9 +39,11 @@ }, "create_nix_tree": { "action": { + "action_name": "create_nix_tree", "create_directories": [ { "action": { + "action_name": "create_directory", "path": "/nix/var", "user": "root", "group": null, @@ -51,6 +55,7 @@ }, { "action": { + "action_name": "create_directory", "path": "/nix/var/log", "user": "root", "group": null, @@ -62,6 +67,7 @@ }, { "action": { + "action_name": "create_directory", "path": "/nix/var/log/nix", "user": "root", "group": null, @@ -73,6 +79,7 @@ }, { "action": { + "action_name": "create_directory", "path": "/nix/var/log/nix/drvs", "user": "root", "group": null, @@ -84,6 +91,7 @@ }, { "action": { + "action_name": "create_directory", "path": "/nix/var/nix", "user": "root", "group": null, @@ -95,6 +103,7 @@ }, { "action": { + "action_name": "create_directory", "path": "/nix/var/nix/db", "user": "root", "group": null, @@ -106,6 +115,7 @@ }, { "action": { + "action_name": "create_directory", "path": "/nix/var/nix/gcroots", "user": "root", "group": null, @@ -117,6 +127,7 @@ }, { "action": { + "action_name": "create_directory", "path": "/nix/var/nix/gcroots/per-user", "user": "root", "group": null, @@ -128,6 +139,7 @@ }, { "action": { + "action_name": "create_directory", "path": "/nix/var/nix/profiles", "user": "root", "group": null, @@ -139,6 +151,7 @@ }, { "action": { + "action_name": "create_directory", "path": "/nix/var/nix/profiles/per-user", "user": "root", "group": null, @@ -150,6 +163,7 @@ }, { "action": { + "action_name": "create_directory", "path": "/nix/var/nix/temproots", "user": "root", "group": null, @@ -172,6 +186,7 @@ }, { "action": { + "action_name": "create_directory", "path": "/nix/var/nix/daemon-socket", "user": "root", "group": null, @@ -187,6 +202,7 @@ }, "move_unpacked_nix": { "action": { + "action_name": "mount_unpacked_nix", "unpacked_path": "/nix/temp-install-dir" }, "state": "Uncompleted" @@ -196,15 +212,17 @@ }, { "action": { - "action": "configure_nix", + "action_name": "configure_nix", "setup_default_profile": { "action": { + "action_name": "setup_default_profile", "unpacked_path": "/nix/temp-install-dir" }, "state": "Uncompleted" }, "configure_shell_profile": { "action": { + "action_name": "configure_shell_profile", "locations": { "fish": { "confd_suffix": "conf.d/nix.fish", @@ -233,6 +251,7 @@ "create_directories": [ { "action": { + "action_name": "create_directory", "path": "/etc/fish/conf.d", "user": null, "group": null, @@ -244,6 +263,7 @@ }, { "action": { + "action_name": "create_directory", "path": "/usr/share/fish/vendor_conf.d", "user": null, "group": null, @@ -257,6 +277,7 @@ "create_or_insert_into_files": [ { "action": { + "action_name": "create_or_insert_into_file", "path": "/etc/bashrc", "user": null, "group": null, @@ -268,6 +289,7 @@ }, { "action": { + "action_name": "create_or_insert_into_file", "path": "/etc/profile.d/nix.sh", "user": null, "group": null, @@ -279,6 +301,7 @@ }, { "action": { + "action_name": "create_or_insert_into_file", "path": "/etc/bash.bashrc", "user": null, "group": null, @@ -290,6 +313,7 @@ }, { "action": { + "action_name": "create_or_insert_into_file", "path": "/etc/zshrc", "user": null, "group": null, @@ -301,6 +325,7 @@ }, { "action": { + "action_name": "create_or_insert_into_file", "path": "/etc/zsh/zshrc", "user": null, "group": null, @@ -312,6 +337,7 @@ }, { "action": { + "action_name": "create_or_insert_into_file", "path": "/etc/fish/conf.d/nix.fish", "user": null, "group": null, @@ -323,6 +349,7 @@ }, { "action": { + "action_name": "create_or_insert_into_file", "path": "/usr/share/fish/vendor_conf.d/nix.fish", "user": null, "group": null, @@ -338,8 +365,10 @@ }, "place_nix_configuration": { "action": { + "action_name": "place_nix_configuration", "create_directory": { "action": { + "action_name": "create_directory", "path": "/etc/nix", "user": null, "group": null, @@ -351,9 +380,11 @@ }, "create_or_merge_nix_config": { "action": { + "action_name": "create_or_merge_nix_config", "path": "/etc/nix/nix.conf", "pending_nix_config": { "settings": { + "always-allow-substitutes": "true", "experimental-features": "nix-command flakes auto-allocate-uids", "build-users-group": "nixbld", "auto-optimise-store": "true", @@ -390,7 +421,7 @@ }, { "action": { - "action": "create_directory", + "action_name": "create_directory", "path": "/etc/tmpfiles.d", "user": null, "group": null, @@ -402,17 +433,27 @@ }, { "action": { - "action": "configure_init_service", + "action_name": "configure_init_service", "init": "Systemd", "start_daemon": true, "ssl_cert_file": null, - "enterprise_edition": false + "determinate_nix": false, + "service_src": "/nix/var/nix/profiles/default/lib/systemd/system/nix-daemon.service", + "socket_files": [ + { + "name": "nix-daemon.socket", + "src": { + "Path": "/nix/var/nix/profiles/default/lib/systemd/system/nix-daemon.socket" + }, + "dest": "/etc/systemd/system/nix-daemon.socket" + } + ] }, "state": "Uncompleted" }, { "action": { - "action": "remove_directory", + "action_name": "remove_directory", "path": "/nix/temp-install-dir" }, "state": "Uncompleted" @@ -421,6 +462,7 @@ "planner": { "planner": "linux", "settings": { + "determinate_nix": false, "modify_profile": true, "nix_build_group_name": "nixbld", "nix_build_group_id": 30000, diff --git a/tests/fixtures/linux/steam-deck.json b/tests/fixtures/linux/steam-deck.json index ca9a20803..e6d5a5541 100644 --- a/tests/fixtures/linux/steam-deck.json +++ b/tests/fixtures/linux/steam-deck.json @@ -1,9 +1,9 @@ { - "version": "0.19.0", + "version": "0.27.0", "actions": [ { "action": { - "action": "create_directory", + "action_name": "create_directory", "path": "/home/nix", "user": null, "group": null, @@ -15,7 +15,7 @@ }, { "action": { - "action": "create_file", + "action_name": "create_file", "path": "/etc/systemd/system/nix-directory.service", "user": null, "group": null, @@ -27,7 +27,7 @@ }, { "action": { - "action": "create_file", + "action_name": "create_file", "path": "/etc/systemd/system/nix.mount", "user": null, "group": null, @@ -39,7 +39,7 @@ }, { "action": { - "action": "create_file", + "action_name": "create_file", "path": "/etc/systemd/system/ensure-symlinked-units-resolve.service", "user": null, "group": null, @@ -51,7 +51,7 @@ }, { "action": { - "action": "start_systemd_unit", + "action_name": "start_systemd_unit", "unit": "nix.mount", "enable": false }, @@ -59,9 +59,10 @@ }, { "action": { - "action": "provision_nix", + "action_name": "provision_nix", "fetch_nix": { "action": { + "action_name": "fetch_and_unpack_nix", "url_or_path": { "Url": "https://releases.nixos.org/nix/nix-2.17.0/nix-2.17.0-x86_64-linux.tar.xz" }, @@ -74,6 +75,7 @@ "delete_users": [], "create_group": { "action": { + "action_name": "create_group", "name": "nixbld", "gid": 30000 }, @@ -81,9 +83,11 @@ }, "create_nix_tree": { "action": { + "action_name": "create_nix_tree", "create_directories": [ { "action": { + "action_name": "create_directory", "path": "/nix/var", "user": "root", "group": null, @@ -95,6 +99,7 @@ }, { "action": { + "action_name": "create_directory", "path": "/nix/var/log", "user": "root", "group": null, @@ -106,6 +111,7 @@ }, { "action": { + "action_name": "create_directory", "path": "/nix/var/log/nix", "user": "root", "group": null, @@ -117,6 +123,7 @@ }, { "action": { + "action_name": "create_directory", "path": "/nix/var/log/nix/drvs", "user": "root", "group": null, @@ -128,6 +135,7 @@ }, { "action": { + "action_name": "create_directory", "path": "/nix/var/nix", "user": "root", "group": null, @@ -139,6 +147,7 @@ }, { "action": { + "action_name": "create_directory", "path": "/nix/var/nix/db", "user": "root", "group": null, @@ -150,6 +159,7 @@ }, { "action": { + "action_name": "create_directory", "path": "/nix/var/nix/gcroots", "user": "root", "group": null, @@ -161,6 +171,7 @@ }, { "action": { + "action_name": "create_directory", "path": "/nix/var/nix/gcroots/per-user", "user": "root", "group": null, @@ -172,6 +183,7 @@ }, { "action": { + "action_name": "create_directory", "path": "/nix/var/nix/profiles", "user": "root", "group": null, @@ -183,6 +195,7 @@ }, { "action": { + "action_name": "create_directory", "path": "/nix/var/nix/profiles/per-user", "user": "root", "group": null, @@ -194,6 +207,7 @@ }, { "action": { + "action_name": "create_directory", "path": "/nix/var/nix/temproots", "user": "root", "group": null, @@ -205,6 +219,7 @@ }, { "action": { + "action_name": "create_directory", "path": "/nix/var/nix/userpool", "user": "root", "group": null, @@ -216,6 +231,7 @@ }, { "action": { + "action_name": "create_directory", "path": "/nix/var/nix/daemon-socket", "user": "root", "group": null, @@ -231,6 +247,7 @@ }, "move_unpacked_nix": { "action": { + "action_name": "mount_unpacked_nix", "unpacked_path": "/nix/temp-install-dir" }, "state": "Uncompleted" @@ -240,15 +257,17 @@ }, { "action": { - "action": "configure_nix", + "action_name": "configure_nix", "setup_default_profile": { "action": { + "action_name": "setup_default_profile", "unpacked_path": "/nix/temp-install-dir" }, "state": "Uncompleted" }, "configure_shell_profile": { "action": { + "action_name": "configure_shell_profile", "locations": { "fish": { "confd_suffix": "conf.d/nix.fish", @@ -277,6 +296,7 @@ "create_or_insert_into_files": [ { "action": { + "action_name": "create_or_insert_into_file", "path": "/etc/bashrc", "user": null, "group": null, @@ -288,6 +308,7 @@ }, { "action": { + "action_name": "create_or_insert_into_file", "path": "/etc/profile.d/nix.sh", "user": null, "group": null, @@ -299,6 +320,7 @@ }, { "action": { + "action_name": "create_or_insert_into_file", "path": "/etc/bash.bashrc", "user": null, "group": null, @@ -310,6 +332,7 @@ }, { "action": { + "action_name": "create_or_insert_into_file", "path": "/etc/zshrc", "user": null, "group": null, @@ -325,8 +348,10 @@ }, "place_nix_configuration": { "action": { + "action_name": "place_nix_configuration", "create_directory": { "action": { + "action_name": "create_directory", "path": "/etc/nix", "user": null, "group": null, @@ -338,9 +363,11 @@ }, "create_or_merge_nix_config": { "action": { + "action_name": "create_or_merge_nix_config", "path": "/etc/nix/nix.conf", "pending_nix_config": { "settings": { + "always-allow-substitutes": "true", "auto-optimise-store": "true", "bash-prompt-prefix": "(nix:$name)\\040", "build-users-group": "nixbld", @@ -377,17 +404,27 @@ }, { "action": { - "action": "configure_init_service", + "action_name": "configure_init_service", "init": "Systemd", "start_daemon": true, "ssl_cert_file": null, - "enterprise_edition": false + "determinate_nix": false, + "service_src": "/nix/var/nix/profiles/default/lib/systemd/system/nix-daemon.service", + "socket_files": [ + { + "name": "nix-daemon.socket", + "src": { + "Path": "/nix/var/nix/profiles/default/lib/systemd/system/nix-daemon.socket" + }, + "dest": "/etc/systemd/system/nix-daemon.socket" + } + ] }, "state": "Uncompleted" }, { "action": { - "action": "start_systemd_unit", + "action_name": "start_systemd_unit", "unit": "ensure-symlinked-units-resolve.service", "enable": true }, @@ -395,7 +432,7 @@ }, { "action": { - "action": "remove_directory", + "action_name": "remove_directory", "path": "/nix/temp-install-dir" }, "state": "Uncompleted" @@ -405,6 +442,7 @@ "planner": "steam-deck", "persistence": "/home/nix", "settings": { + "determinate_nix": false, "modify_profile": true, "nix_build_group_name": "nixbld", "nix_build_group_id": 30000, diff --git a/tests/fixtures/macos/macos.json b/tests/fixtures/macos/macos.json index bf72e7432..69933659b 100644 --- a/tests/fixtures/macos/macos.json +++ b/tests/fixtures/macos/macos.json @@ -1,16 +1,17 @@ { - "version": "0.19.0", + "version": "0.27.0", "actions": [ { "action": { - "action": "create_apfs_volume", + "action_name": "create_apfs_volume", "disk": "disk3", "name": "Nix Store", "case_sensitive": false, "encrypt": false, - "enterprise_edition": false, + "determinate_nix": false, "create_or_append_synthetic_conf": { "action": { + "action_name": "create_of_insert_into_file", "path": "/etc/synthetic.conf", "user": null, "group": null, @@ -26,6 +27,7 @@ }, "unmount_volume": { "action": { + "action_name": "unmount_volume", "disk": "disk3", "name": "Nix Store" }, @@ -33,6 +35,7 @@ }, "create_volume": { "action": { + "action_name": "create_volume", "disk": "disk3", "name": "Nix Store", "case_sensitive": false @@ -41,6 +44,7 @@ }, "create_fstab_entry": { "action": { + "action_name": "create_fstab_entry", "apfs_volume_label": "Nix Store", "existing_entry": "None" }, @@ -49,6 +53,7 @@ "encrypt_volume": null, "setup_volume_daemon": { "action": { + "action_name": "create_volume_service", "path": "/Library/LaunchDaemons/org.nixos.darwin-store.plist", "apfs_volume_label": "Nix Store", "mount_service_label": "org.nixos.darwin-store", @@ -60,6 +65,7 @@ }, "bootstrap_volume": { "action": { + "action_name": "bootstrap_launchctl_service", "domain": "system", "service": "org.nixos.darwin-store", "path": "/Library/LaunchDaemons/org.nixos.darwin-store.plist", @@ -70,6 +76,7 @@ }, "kickstart_launchctl_service": { "action": { + "action_name": "kickstart_launchctl_service", "domain": "system", "service": "org.nixos.darwin-store" }, @@ -77,6 +84,7 @@ }, "enable_ownership": { "action": { + "action_name": "enable_ownership", "path": "/nix" }, "state": "Uncompleted" @@ -86,9 +94,10 @@ }, { "action": { - "action": "provision_nix", + "action_name": "provision_nix", "fetch_nix": { "action": { + "action_name": "fetch_and_unpack_nix", "url_or_path": { "Url": "https://releases.nixos.org/nix/nix-2.17.0/nix-2.17.0-x86_64-darwin.tar.xz" }, @@ -101,6 +110,7 @@ "delete_users_in_group": null, "create_group": { "action": { + "action_name": "create_group", "name": "nixbld", "gid": 30000 }, @@ -111,6 +121,7 @@ "create_directories": [ { "action": { + "action_name": "create_directory", "path": "/nix/var", "user": "root", "group": null, @@ -122,6 +133,7 @@ }, { "action": { + "action_name": "create_directory", "path": "/nix/var/log", "user": "root", "group": null, @@ -133,6 +145,7 @@ }, { "action": { + "action_name": "create_directory", "path": "/nix/var/log/nix", "user": "root", "group": null, @@ -144,6 +157,7 @@ }, { "action": { + "action_name": "create_directory", "path": "/nix/var/log/nix/drvs", "user": "root", "group": null, @@ -155,6 +169,7 @@ }, { "action": { + "action_name": "create_directory", "path": "/nix/var/nix", "user": "root", "group": null, @@ -166,6 +181,7 @@ }, { "action": { + "action_name": "create_directory", "path": "/nix/var/nix/db", "user": "root", "group": null, @@ -177,6 +193,7 @@ }, { "action": { + "action_name": "create_directory", "path": "/nix/var/nix/gcroots", "user": "root", "group": null, @@ -188,6 +205,7 @@ }, { "action": { + "action_name": "create_directory", "path": "/nix/var/nix/gcroots/per-user", "user": "root", "group": null, @@ -199,6 +217,7 @@ }, { "action": { + "action_name": "create_directory", "path": "/nix/var/nix/profiles", "user": "root", "group": null, @@ -210,6 +229,7 @@ }, { "action": { + "action_name": "create_directory", "path": "/nix/var/nix/profiles/per-user", "user": "root", "group": null, @@ -221,6 +241,7 @@ }, { "action": { + "action_name": "create_directory", "path": "/nix/var/nix/temproots", "user": "root", "group": null, @@ -232,6 +253,7 @@ }, { "action": { + "action_name": "create_directory", "path": "/nix/var/nix/userpool", "user": "root", "group": null, @@ -243,6 +265,7 @@ }, { "action": { + "action_name": "create_directory", "path": "/nix/var/nix/daemon-socket", "user": "root", "group": null, @@ -258,6 +281,7 @@ }, "move_unpacked_nix": { "action": { + "action_name": "mount_unpacked_nix", "unpacked_path": "/nix/temp-install-dir" }, "state": "Uncompleted" @@ -267,16 +291,18 @@ }, { "action": { - "action": "set_tmutil_exclusions", + "action_name": "set_tmutil_exclusions", "set_tmutil_exclusions": [ { "action": { + "action_name": "set_tmutil_exclusion", "path": "/nix/store" }, "state": "Uncompleted" }, { "action": { + "action_name": "set_tmutil_exclusion", "path": "/nix/var" }, "state": "Uncompleted" @@ -287,15 +313,17 @@ }, { "action": { - "action": "configure_nix", + "action_name": "configure_nix", "setup_default_profile": { "action": { + "action_name": "setup_default_profile", "unpacked_path": "/nix/temp-install-dir" }, "state": "Uncompleted" }, "configure_shell_profile": { "action": { + "action_name": "configure_shell_profile", "locations": { "fish": { "confd_suffix": "conf.d/nix.fish", @@ -325,6 +353,7 @@ "create_or_insert_into_files": [ { "action": { + "action_name": "create_of_insert_into_file", "path": "/etc/bashrc", "user": null, "group": null, @@ -336,6 +365,7 @@ }, { "action": { + "action_name": "create_of_insert_into_file", "path": "/etc/bash.bashrc", "user": null, "group": null, @@ -347,6 +377,7 @@ }, { "action": { + "action_name": "create_of_insert_into_file", "path": "/etc/zshrc", "user": null, "group": null, @@ -364,6 +395,7 @@ "action": { "create_directory": { "action": { + "action_name": "create_directory", "path": "/etc/nix", "user": null, "group": null, @@ -375,9 +407,11 @@ }, "create_or_merge_nix_config": { "action": { + "action_name": "create_or_merge_nix_config", "path": "/etc/nix/nix.conf", "pending_nix_config": { "settings": { + "always-allow-substitutes": "true", "extra-nix-path": "nixpkgs=flake:nixpkgs", "auto-allocate-uids": "true", "auto-optimise-store": "true", @@ -414,17 +448,19 @@ }, { "action": { - "action": "configure_init_service", + "action_name": "configure_init_service", "init": "Launchd", "start_daemon": true, "ssl_cert_file": null, - "enterprise_edition": false + "determinate_nix": false, + "service_src": "/nix/var/nix/profiles/default/Library/LaunchDaemons/org.nixos.nix-daemon.plist", + "socket_files": [] }, "state": "Uncompleted" }, { "action": { - "action": "remove_directory", + "action_name": "remove_directory", "path": "/nix/temp-install-dir" }, "state": "Uncompleted" @@ -433,6 +469,7 @@ "planner": { "planner": "macos", "settings": { + "determinate_nix": false, "modify_profile": true, "nix_build_group_name": "nixbld", "nix_build_group_id": 30000, @@ -449,11 +486,11 @@ "add_channel": true, "diagnostic_endpoint": "https://install.determinate.systems/nix/diagnostic" }, - "enterprise_edition": false, "encrypt": null, "case_sensitive": false, "volume_label": "Nix Store", - "root_disk": "disk3" + "root_disk": "disk3", + "use_ec2_instance_store": false }, "diagnostic_data": { "version": "0.19.0", diff --git a/tests/plan.rs b/tests/plan.rs index e34ad0f75..9f4ffa2c7 100644 --- a/tests/plan.rs +++ b/tests/plan.rs @@ -1,15 +1,11 @@ use nix_installer::InstallPlan; -#[cfg(target_os = "linux")] const LINUX: &str = include_str!("./fixtures/linux/linux.json"); -#[cfg(target_os = "linux")] const STEAM_DECK: &str = include_str!("./fixtures/linux/steam-deck.json"); -#[cfg(target_os = "macos")] const MACOS: &str = include_str!("./fixtures/macos/macos.json"); // 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. -#[cfg(target_os = "linux")] #[test] fn plan_compat_linux() -> eyre::Result<()> { let _: InstallPlan = serde_json::from_str(LINUX)?; @@ -18,7 +14,6 @@ fn plan_compat_linux() -> eyre::Result<()> { // 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. -#[cfg(target_os = "linux")] #[test] fn plan_compat_steam_deck() -> eyre::Result<()> { let _: InstallPlan = serde_json::from_str(STEAM_DECK)?; @@ -27,7 +22,6 @@ fn plan_compat_steam_deck() -> eyre::Result<()> { // 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. -#[cfg(target_os = "macos")] #[test] fn plan_compat_macos() -> eyre::Result<()> { let _: InstallPlan = serde_json::from_str(MACOS)?; diff --git a/tests/windows/test-wsl.ps1 b/tests/windows/test-wsl.ps1 index cc7c42ff8..b3ab95857 100644 --- a/tests/windows/test-wsl.ps1 +++ b/tests/windows/test-wsl.ps1 @@ -24,21 +24,21 @@ $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 + exit $LastExitCode } Write-Output "Preparing $DistroName for nix-installer..." wsl --distribution $DistroName bash --login -c "apt update --quiet" if ($LastExitCode -ne 0) { - exit $LastExitCode + exit $LastExitCode } wsl --distribution $DistroName bash --login -c "apt install --quiet --yes curl build-essential" if ($LastExitCode -ne 0) { - exit $LastExitCode + 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 + exit $LastExitCode } if ($Systemd) { @@ -46,7 +46,7 @@ if ($Systemd) { New-Item -Path "\\wsl$\$DistroName\etc\wsl.conf" -ItemType "file" -Value $wslConf wsl --shutdown if ($LastExitCode -ne 0) { - exit $LastExitCode + exit $LastExitCode } } @@ -58,18 +58,18 @@ $MaybeInitChoice = switch ($Systemd) { } wsl --distribution $DistroName bash --login -c "/root/.cargo/bin/cargo run --quiet --manifest-path /nix-installer/Cargo.toml -- install linux --no-confirm $MaybeInitChoice" if ($LastExitCode -ne 0) { - exit $LastExitCode + 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 + exit $LastExitCode } Write-Output "Unregistering $DistroName and removing $InstallRoot..." wsl --unregister $DistroName if ($LastExitCode -ne 0) { - exit $LastExitCode + exit $LastExitCode } -Remove-Item $InstallRoot \ No newline at end of file +Remove-Item $InstallRoot