Compare commits

..

4 commits

Author SHA1 Message Date
Graham Christensen 2164106795 ... 2023-11-08 20:56:48 -05:00
Graham Christensen 52aff53955 nits 2023-11-08 15:37:31 -05:00
Graham Christensen 576f3f688f fixup: use only our one shell location 2023-11-08 14:32:27 -05:00
Graham Christensen eaea1483f7 fix fish support on ostree 2023-11-08 14:26:52 -05:00
56 changed files with 2431 additions and 1333 deletions

51
.buildkite/pipeline.yml Normal file
View file

@ -0,0 +1,51 @@
steps:
- label: nix-installer-x86_64-darwin
agents:
mac: 1
system: x86_64-darwin
nix: 1
command:
- nix --extra-experimental-features "nix-command flakes" build .#packages.x86_64-darwin.nix-installer -L
- cp result/bin/nix-installer ./nix-installer-x86_64-darwin
- buildkite-agent artifact upload nix-installer-x86_64-darwin
- label: nix-installer-aarch64-darwin
agents:
mac: 1
system: aarch64-darwin
nix: 1
command:
- nix --extra-experimental-features "nix-command flakes" build .#packages.aarch64-darwin.nix-installer -L
- cp result/bin/nix-installer ./nix-installer-aarch64-darwin
- buildkite-agent artifact upload nix-installer-aarch64-darwin
- label: nix-installer-x86_64-linux
agents:
system: x86_64-linux
nix: 1
command:
- nix --extra-experimental-features "nix-command flakes" build .#packages.x86_64-linux.nix-installer-static -L
- cp result/bin/nix-installer ./nix-installer-x86_64-linux
- buildkite-agent artifact upload nix-installer-x86_64-linux
- label: nix-installer-x86_64-linux-variants
agents:
system: x86_64-linux
nix: 1
command:
- nix --extra-experimental-features "nix-command flakes" develop --store ~/.ci-store --print-build-logs .# --command "cargo" build --no-default-features
- nix --extra-experimental-features "nix-command flakes" develop --store ~/.ci-store --print-build-logs .# --command "cargo" build --all-features
- nix --extra-experimental-features "nix-command flakes" build --store ~/.ci-store --print-build-logs .#packages.x86_64-linux.nix-installer
- label: nix-installer-i686-linux
agents:
system: x86_64-linux
nix: 1
command:
- nix --extra-experimental-features "nix-command flakes" build .#packages.i686-linux.nix-installer-static -L
- cp result/bin/nix-installer ./nix-installer-i686-linux
- buildkite-agent artifact upload nix-installer-i686-linux
- label: nix-installer-aarch64-linux
agents:
system: aarch64-linux
nix: 1
command:
- nix --extra-experimental-features "nix-command flakes" build .#packages.aarch64-linux.nix-installer-static -L
- cp result/bin/nix-installer ./nix-installer-aarch64-linux
- buildkite-agent artifact upload nix-installer-aarch64-linux

22
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View file

@ -0,0 +1,22 @@
##### Description
<!---
Please include a short description of what your PR does and / or the motivation behind it
--->
##### Checklist
- [ ] Formatted with `cargo fmt`
- [ ] Built with `nix build`
- [ ] Ran flake checks with `nix flake check`
- [ ] Added or updated relevant tests (leave unchecked if not applicable)
- [ ] Added or updated relevant documentation (leave unchecked if not applicable)
- [ ] Linked to related issues (leave unchecked if not applicable)
##### Validating with `install.determinate.systems`
If a maintainer has added the `upload to s3` label to this PR, it will become available for installation via `install.determinate.systems`:
```shell
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix/pr/$PR_NUMBER | sh -s -- install
```

10
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,10 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
- package-ecosystem: "cargo"
directory: "/"
schedule:
interval: "monthly"

340
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,340 @@
name: CI
on:
pull_request:
push:
branches: [main]
jobs:
lints:
name: Lints
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- name: Check Nixpkgs input
uses: DeterminateSystems/flake-checker-action@main
with:
fail-mode: true
check-outdated: false # PRs shouldn't fail because main's nixpkgs is out of date
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@main
- uses: DeterminateSystems/magic-nix-cache-action@main
- name: Check rustfmt
run: nix develop --command check-rustfmt
- name: Check Clippy
run: nix develop --command check-clippy
- name: Check Spelling
run: nix develop --command check-spelling
- name: Check nixpkgs-fmt formatting
run: nix develop --command check-nixpkgs-fmt
- name: Check EditorConfig conformance
run: nix develop --command check-editorconfig
- name: Download Buildkite Artifacts
uses: EnricoMi/download-buildkite-artifact-action@v1.14
with:
buildkite_token: ${{ secrets.BUILDKITE_TOKEN }}
output_path: artifacts
- name: Output list of Buildkite artifacts
run: |
ls -lah artifacts/
ls -lah artifacts/**/*
# Mac's can't run this action, so we're forced to do this.
- name: Create Github cache from Buildkite artifacts
id: cache-buildkite-artifacts
uses: actions/cache/save@v3
with:
path: artifacts
key: buildkite-artifacts-${{ github.sha }}
run-x86_64-linux:
name: Run x86_64 Linux
runs-on: ubuntu-22.04
needs: [lints]
steps:
- uses: actions/checkout@v3
- name: Restore Github cache of Buildkite artifacts
id: cache-buildkite-artifacts
uses: actions/cache/restore@v3
with:
path: artifacts
key: buildkite-artifacts-${{ github.sha }}
- run: sudo apt install fish zsh
- name: Move & set executable
run: |
mkdir install-root
cp nix-installer.sh install-root/nix-installer.sh
mv ./artifacts/nix-installer-x86_64-linux-*/* 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:
local-root: install-root/
logger: pretty
log-directives: nix_installer=debug
backtrace: full
github-token: ${{ secrets.GITHUB_TOKEN }}
- 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:
local-root: install-root/
logger: pretty
log-directives: nix_installer=debug
backtrace: full
github-token: ${{ secrets.GITHUB_TOKEN }}
- 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-x86_64-linux-no-init:
name: Run x86_64 Linux (No init)
runs-on: ubuntu-22.04
needs: [lints]
steps:
- uses: actions/checkout@v3
- name: Restore Github cache of Buildkite artifacts
id: cache-buildkite-artifacts
uses: actions/cache/restore@v3
with:
path: artifacts
key: buildkite-artifacts-${{ github.sha }}
- run: sudo apt install fish zsh
- name: Move & set executable
run: |
mkdir install-root
cp nix-installer.sh install-root/nix-installer.sh
mv ./artifacts/nix-installer-x86_64-linux-*/* 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:
init: none
planner: linux
local-root: install-root/
logger: pretty
log-directives: nix_installer=debug
backtrace: full
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Ensure daemon was not configured with init
run: |
if systemctl is-active nix-daemon.socket; then
echo "nix-daemon.socket was running"
exit 1
fi
if systemctl is-active nix-daemon.service; then
echo "nix-daemon.service was running"
exit 1
fi
- name: Initial uninstall (without a `nix run` first)
run: sudo -E /nix/nix-installer uninstall
env:
NIX_INSTALLER_NO_CONFIRM: true
NIX_INSTALLER_LOGGER: pretty
NIX_INSTALLER_LOG_DIRECTIVES: nix_installer=debug
RUST_BACKTRACE: full
- name: Ensure `nix` is removed
run: |
if [ -e /nix ]; then
echo "/nix exists"
exit 1
fi
- name: Repeated install
uses: DeterminateSystems/nix-installer-action@main
with:
init: none
planner: linux
local-root: install-root/
logger: pretty
log-directives: nix_installer=debug
backtrace: full
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: echo $PATH
run: echo $PATH
- name: Test `nix` with `$GITHUB_PATH`
if: success() || failure()
run: |
sudo -i nix run nixpkgs#hello
sudo -i nix profile install nixpkgs#hello
hello
sudo -i nix store gc
sudo -i nix run nixpkgs#hello
- name: Test bash
run: sudo -i nix-instantiate -E 'builtins.currentTime' --eval
if: success() || failure()
shell: bash --login {0}
- name: Test sh
run: sudo -i nix-instantiate -E 'builtins.currentTime' --eval
if: success() || failure()
shell: sh -l {0}
- name: Test zsh
run: sudo -i nix-instantiate -E 'builtins.currentTime' --eval
if: success() || failure()
shell: zsh --login --interactive {0}
- name: Test fish
run: sudo -i nix-instantiate -E 'builtins.currentTime' --eval
if: success() || failure()
shell: fish --login {0}
- name: Repeated uninstall
run: sudo -E /nix/nix-installer uninstall
env:
NIX_INSTALLER_NO_CONFIRM: true
NIX_INSTALLER_LOGGER: pretty
NIX_INSTALLER_LOG_DIRECTIVES: nix_installer=debug
RUST_BACKTRACE: full
- name: Ensure `nix` is removed
run: |
if systemctl is-active nix-daemon.socket; then
echo "nix-daemon.socket was running"
exit 1
fi
if systemctl is-active nix-daemon.service; then
echo "nix-daemon.service was running"
exit 1
fi
if [ -e /nix ]; then
echo "/nix exists"
exit 1
fi
run-x86_64-darwin:
name: Run x86_64 Darwin
runs-on: macos-12
needs: [lints]
steps:
- uses: actions/checkout@v3
- name: Restore Github cache of Buildkite artifacts
id: cache-buildkite-artifacts
uses: actions/cache/restore@v3
with:
path: artifacts
key: buildkite-artifacts-${{ github.sha }}
- run: brew install fish coreutils
- name: Move & set executable
run: |
mkdir install-root
cp nix-installer.sh install-root/nix-installer.sh
mv ./artifacts/nix-installer-x86_64-darwin-*/* install-root/nix-installer-x86_64-darwin
chmod +x install-root/nix-installer-x86_64-darwin install-root/nix-installer.sh
- name: Initial install
uses: DeterminateSystems/nix-installer-action@main
with:
local-root: install-root/
logger: pretty
log-directives: nix_installer=debug
backtrace: full
github-token: ${{ secrets.GITHUB_TOKEN }}
extra-conf: |
trusted-users = root runner
- 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:
local-root: install-root/
logger: pretty
log-directives: nix_installer=debug
backtrace: full
github-token: ${{ secrets.GITHUB_TOKEN }}
extra-conf: trusted-users = root runner
- 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

46
.github/workflows/release-branches.yml vendored Normal file
View file

@ -0,0 +1,46 @@
name: Release Branch
on:
push:
branches:
# NOTE: make sure any branches here are also valid directory names,
# otherwise creating the directory and uploading to s3 will fail
- 'main'
jobs:
release:
concurrency: release
runs-on: ubuntu-latest
permissions:
id-token: write # In order to request a JWT for AWS auth
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Download Buildkite Artifacts
uses: EnricoMi/download-buildkite-artifact-action@v1.14
with:
buildkite_token: ${{ secrets.BUILDKITE_TOKEN }}
output_path: artifacts
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v2
with:
role-to-assume: ${{ secrets.AWS_S3_UPLOAD_ROLE }}
aws-region: us-east-2
- name: Publish Release (Branch)
env:
AWS_BUCKET: ${{ secrets.AWS_S3_UPLOAD_BUCKET }}
run: |
BRANCH="branch_${{ github.ref_name }}"
GIT_ISH="$GITHUB_SHA"
./upload_s3.sh "$BRANCH" "$GIT_ISH" "https://install.determinate.systems/nix/rev/$GIT_ISH"
- name: Install Instructions (Branch)
run: |
cat <<EOF
This commit can be installed by running the following command:
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix/rev/$GITHUB_SHA | sh -s -- install
The latest commit from this branch can be installed by running the following command:
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix/branch/${{ github.ref_name }} | sh -s -- install
EOF

55
.github/workflows/release-prs.yml vendored Normal file
View file

@ -0,0 +1,55 @@
name: Release PR
on:
pull_request:
types:
- opened
- reopened
- synchronize
- labeled
jobs:
release:
concurrency: release
# 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: |
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'))
)
runs-on: ubuntu-latest
permissions:
id-token: write # In order to request a JWT for AWS auth
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Download Buildkite Artifacts
uses: EnricoMi/download-buildkite-artifact-action@v1.14
with:
buildkite_token: ${{ secrets.BUILDKITE_TOKEN }}
output_path: artifacts
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v2
with:
role-to-assume: ${{ secrets.AWS_S3_UPLOAD_ROLE }}
aws-region: us-east-2
- name: Publish Release (PR)
env:
AWS_BUCKET: ${{ secrets.AWS_S3_UPLOAD_BUCKET }}
run: |
PR="pr_${{ github.event.pull_request.number }}"
GIT_ISH="${{ github.event.pull_request.head.sha }}"
./upload_s3.sh "$PR" "$GIT_ISH" "https://install.determinate.systems/nix/rev/$GIT_ISH"
- name: Install Instructions (PR)
run: |
cat <<EOF
This commit can be installed by running the following command:
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix/rev/${{ github.event.pull_request.head.sha }} | sh -s -- install
The latest commit from this PR can be installed by running the following command:
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix/pr/${{ github.event.pull_request.number }} | sh -s -- install
EOF

47
.github/workflows/release-tags.yml vendored Normal file
View file

@ -0,0 +1,47 @@
name: Release Tags
on:
push:
tags:
- "v*.*.*"
jobs:
release:
concurrency: release
runs-on: ubuntu-latest
permissions:
contents: write # In order to upload artifacts to GitHub releases
id-token: write # In order to request a JWT for AWS auth
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Download Buildkite Artifacts
uses: EnricoMi/download-buildkite-artifact-action@v1.14
with:
buildkite_token: ${{ secrets.BUILDKITE_TOKEN }}
output_path: artifacts
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v2
with:
role-to-assume: ${{ secrets.AWS_S3_UPLOAD_ROLE }}
aws-region: us-east-2
- name: Publish Release to S3 (Tag)
env:
AWS_BUCKET: ${{ secrets.AWS_S3_UPLOAD_BUCKET }}
run: |
./upload_s3.sh "$GITHUB_REF_NAME" "$GITHUB_SHA" "https://install.determinate.systems/nix/tag/$GITHUB_REF_NAME"
- name: Publish Release to GitHub (Tag)
uses: softprops/action-gh-release@v1
with:
fail_on_unmatched_files: true
draft: true
files: |
artifacts/**
nix-installer.sh
- name: Install Instructions (Tag)
run: |
cat <<EOF
This tag can be installed by running the following command:
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix/tag/$GITHUB_REF_NAME | sh -s -- install
EOF

20
.github/workflows/update.yml vendored Normal file
View file

@ -0,0 +1,20 @@
name: update-flake-lock
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * 0'
jobs:
lockfile:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@main
- name: Enable magic Nix cache
uses: DeterminateSystems/magic-nix-cache-action@main
- name: Check flake
uses: DeterminateSystems/flake-checker-action@main
- name: Update flake.lock
uses: DeterminateSystems/update-flake-lock@main

2
.gitignore vendored
View file

@ -2,6 +2,4 @@
.ci-store .ci-store
.direnv .direnv
result* result*
release-assets
src/action/linux/selinux/nix.mod src/action/linux/selinux/nix.mod
.idea

View file

@ -60,7 +60,8 @@ representative at an online or offline event.
## Enforcement ## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at [community@lix.systems](community@lix.systems). reported to the community leaders responsible for enforcement at
[coc-grahamc@determinate.systems](coc-grahamc@determinate.systems) or [coc-ana@determinate.systems](coc-ana@determinate.systems) .
All complaints will be reviewed and investigated promptly and fairly. All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the All community leaders are obligated to respect the privacy and security of the

View file

@ -69,7 +69,7 @@ Here are how to do various kinds of contributions.
## Bug Reports ## Bug Reports
Create an issue on [the issue page](https://git.lix.systems/lix-project/lix-installer/issues). Create an issue on [the issue page](https://github.com/DeterminateSystems/nix-installer/issues).
It should contain: It should contain:
@ -85,10 +85,10 @@ It should contain:
For **minor** fixes, documentation, or changes which **do not** have a For **minor** fixes, documentation, or changes which **do not** have a
tangible impact on user experience, feel free to open a tangible impact on user experience, feel free to open a
[pull request](https://git.lix.systems/lix-project/lix-installer/pulls) directly. [pull request](https://github.com/DeterminateSystems/nix-installer/pulls) directly.
If the code improvement is not minor, such as new features or user facing If the code improvement is not minor, such as new features or user facing
changes, an [issue](https://git.lix.systems/lix-project/lix-installer/issues) changes, an [issue](https://github.com/DeterminateSystems/nix-installer/issues)
proposing the change is **required** for non-maintainers. proposing the change is **required** for non-maintainers.
Please: Please:
@ -103,7 +103,7 @@ Please:
## Non-code contributions ## Non-code contributions
Please open an [issue](https://git.lix.systems/lix-project/lix-installer/issues) Please open an [issue](https://github.com/DeterminateSystems/nix-installer/issues)
to chat about your contribution and figure out how to best integrate it into to chat about your contribution and figure out how to best integrate it into
the project. the project.
@ -162,8 +162,8 @@ These should be visible in `nix flake show`:
``` ```
nix flake show nix flake show
warning: Git tree '/home/ana/git/detsys/nix-installer' is dirty warning: Git tree '/home/ana/git/determinatesystems/nix-installer' is dirty
git+file:///home/ana/git/detsys/nix-installer git+file:///home/ana/git/determinatesystems/nix-installer
# ... # ...
├───hydraJobs ├───hydraJobs
│ └───vm-test │ └───vm-test
@ -201,6 +201,12 @@ nix build .#hydraJobs.vm-test.rhel-v7.x86_64-linux.all -L -j 4
> You may wish to set `-j 4` to some other number. Some OS's (Ubuntu 16.04) exhibit problems rapidly updating their users/groups on a system running dozens of VMs. > You may wish to set `-j 4` to some other number. Some OS's (Ubuntu 16.04) exhibit problems rapidly updating their users/groups on a system running dozens of VMs.
For PR review, you can also test arbitrary branches or checkouts like so:
```bash
nix build github:determinatesystems/nix-installer/${BRANCH}#hydraJobs.vm-test.ubuntu-v22_04.x86_64-linux.install-default -L
```
<details> <details>
<summary><strong>Adding a distro?</strong></summary> <summary><strong>Adding a distro?</strong></summary>
@ -243,8 +249,8 @@ These should be visible in `nix flake show`:
``` ```
nix flake show nix flake show
warning: Git tree '/home/ana/git/detsys/nix-installer' is dirty warning: Git tree '/home/ana/git/determinatesystems/nix-installer' is dirty
git+file:///home/ana/git/detsys/nix-installer git+file:///home/ana/git/determinatesystems/nix-installer
# ... # ...
├───hydraJobs ├───hydraJobs
│ ├───container-test │ ├───container-test
@ -285,6 +291,12 @@ To run a specific distribution listed in the `nix flake show` output:
nix build .#hydraJobs.container-test.ubuntu-v22_04.x86_64-linux.docker -L nix build .#hydraJobs.container-test.ubuntu-v22_04.x86_64-linux.docker -L
``` ```
For PR review, you can also test arbitrary branches or checkouts like so:
```bash
nix build github:determinatesystems/nix-installer/${BRANCH}#hydraJobs.container-test.ubuntu-v22_04.x86_64-linux.podman -L
```
<details> <details>
<summary><strong>Adding a distro?</strong></summary> <summary><strong>Adding a distro?</strong></summary>
@ -353,9 +365,9 @@ This package uses [Semantic Versioning](https://semver.org/). When determining t
To cut a release: To cut a release:
* Ensure the `flake.lock`, `Cargo.lock`, and Rust dependencies are up-to-date with the following: * Ensure the `flake.lock`, `Cargo.lock`, and Rust dependencies are up-to-date with the following:
+ `nix flake update --commit-lock-file` + `nix flake update`
+ `cargo outdated --ignore-external-rel --aggressive` + `cargo update`
+ `cargo update --aggressive` + `cargo outdated`
+ Make a PR for for this and let it get merged separately + 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`) * 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 * Remove the `-unreleased` from the `version` field in `Cargo.toml`, `flake.nix`, and the fixture JSON files
@ -378,6 +390,11 @@ To cut a release:
+ **Warning:** While you can re-release Github releases, it is not possible to do the same on `crates.io` + **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 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`)
# Who maintains `lix-installer` and why? # Who maintains `nix-installer` and why?
`lix-installer` is maintained by [the Lix community](https://lix.systems/) as part of the Lix Project. `nix-installer` is maintained by [Determinate Systems](https://determinate.systems/) in
an effort to explore Nix installer ideas.
Determinate Systems has no plans to monetize or relicense `nix-installer`. If your
enterprise requires a support contact in order to adopt a tool, please contact
Determinate Systems and something can be worked out.

1123
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,12 +1,12 @@
[package] [package]
name = "lix-installer" name = "nix-installer"
description = "The Determinate Nix Installer" description = "The Determinate Nix Installer"
version = "0.17.1" version = "0.14.0"
edition = "2021" edition = "2021"
resolver = "2" resolver = "2"
license = "LGPL-2.1" license = "LGPL-2.1"
repository = "https://git.lix.systems/lix-project/lix-installer" repository = "https://github.com/DeterminateSystems/nix-installer"
documentation = "https://docs.rs/lix-installer/latest/lix_installer" documentation = "https://docs.rs/nix-installer/latest/nix_installer"
[package.metadata.riff.targets.aarch64-apple-darwin] [package.metadata.riff.targets.aarch64-apple-darwin]
build-inputs = ["darwin.apple_sdk.frameworks.Security"] build-inputs = ["darwin.apple_sdk.frameworks.Security"]
@ -15,11 +15,12 @@ build-inputs = ["darwin.apple_sdk.frameworks.Security"]
build-inputs = ["darwin.apple_sdk.frameworks.Security"] build-inputs = ["darwin.apple_sdk.frameworks.Security"]
[features] [features]
default = ["cli"] default = ["cli", "diagnostics"]
cli = ["eyre", "color-eyre", "clap", "tracing-subscriber", "tracing-error"] cli = ["eyre", "color-eyre", "clap", "tracing-subscriber", "tracing-error"]
diagnostics = ["is_ci"]
[[bin]] [[bin]]
name = "lix-installer" name = "nix-installer"
required-features = [ "cli" ] required-features = [ "cli" ]
[dependencies] [dependencies]
@ -29,8 +30,8 @@ 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 } 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 } eyre = { version = "0.6.8", default-features = false, features = [ "track-caller" ], optional = true }
glob = { version = "0.3.0", default-features = false } glob = { version = "0.3.0", default-features = false }
nix = { version = "0.28.0", default-features = false, features = ["user", "fs", "process", "term"] } nix = { version = "0.27.0", default-features = false, features = ["user", "fs", "process", "term"] }
owo-colors = { version = "4.0.0", default-features = false, features = [ "supports-colors" ] } owo-colors = { version = "3.5.0", default-features = false, features = [ "supports-colors" ] }
reqwest = { version = "0.11.11", default-features = false, features = ["rustls-tls-native-roots", "stream", "socks"] } reqwest = { version = "0.11.11", default-features = false, features = ["rustls-tls-native-roots", "stream", "socks"] }
serde = { version = "1.0.144", default-features = false, features = [ "std", "derive" ] } serde = { version = "1.0.144", default-features = false, features = [ "std", "derive" ] }
serde_json = { version = "1.0.85", default-features = false, features = [ "std" ] } serde_json = { version = "1.0.85", default-features = false, features = [ "std" ] }
@ -54,9 +55,9 @@ term = { version = "0.7.0", default-features = false }
uuid = { version = "1.2.2", features = ["serde"] } uuid = { version = "1.2.2", features = ["serde"] }
os-release = { version = "0.1.0", default-features = false } os-release = { version = "0.1.0", default-features = false }
is_ci = { version = "1.1.1", default-features = false, optional = true } is_ci = { version = "1.1.1", default-features = false, optional = true }
strum = { version = "0.26.1", features = ["derive"] } strum = { version = "0.25.0", features = ["derive"] }
nix-config-parser = { version = "0.2", features = ["serde"] } nix-config-parser = { version = "0.2", features = ["serde"] }
which = "6.0.0" which = "4.4.0"
sysctl = "0.5.4" sysctl = "0.5.4"
walkdir = "2.3.3" walkdir = "2.3.3"
indexmap = { version = "2.0.2", features = ["serde"] } indexmap = { version = "2.0.2", features = ["serde"] }

279
README.md
View file

@ -1,39 +1,57 @@
# The Lix Installer # The Determinate Nix Installer
A fast, friendly, and reliable tool to help you use Lix, the community implementation of the nix tooling. [![Crates.io](https://img.shields.io/crates/v/nix-installer)](https://crates.io/crates/nix-installer)
Based on the [Determinate Installer](https://install.determinate.systems). [![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.
```bash ```bash
curl --proto '=https' --tlsv1.2 -sSf -L https://install.lix.systems/lix | sh -s -- install curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
``` ```
The `nix-installer` has successfully completed over 500,000 installs in a number of environments, including [Github Actions](#as-a-github-action):
| 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 |
> **Note**
> On **MacOS only**, removing users and/or groups may fail if there are no users who are logged in graphically.
## Usage ## Usage
Install Nix with the default planner and options: Install Nix with the default planner and options:
```bash ```bash
curl --proto '=https' --tlsv1.2 -sSf -L https://install.lix.systems/lix | sh -s -- install curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
``` ```
Or, to download a platform specific Installer binary yourself: Or, to download a platform specific Installer binary yourself:
```bash ```bash
$ curl -sL -o lix-installer https://install.lix.systems/lix/lix-installer-x86_64-linux $ curl -sL -o nix-installer https://install.determinate.systems/nix/nix-installer-x86_64-linux
$ chmod +x lix-installer $ chmod +x nix-installer
$ ./lix-installer $ ./nix-installer
``` ```
`lix-installer` installs Lix by following a *plan* made by a *planner*. Review the available planners: `nix-installer` installs Nix by following a *plan* made by a *planner*. Review the available planners:
```bash ```bash
$ ./lix-installer install --help $ ./nix-installer install --help
Execute an install (possibly using an existing plan) Execute an install (possibly using an existing plan)
To pass custom options, select a planner, for example `lix-installer install linux-multi --help` To pass custom options, select a planner, for example `nix-installer install linux-multi --help`
Usage: lix-installer install [OPTIONS] [PLAN] Usage: nix-installer install [OPTIONS] [PLAN]
lix-installer install <COMMAND> nix-installer install <COMMAND>
Commands: Commands:
linux linux
@ -48,10 +66,10 @@ Commands:
Planners have their own options and defaults, sharing most of them in common: Planners have their own options and defaults, sharing most of them in common:
```bash ```bash
$ ./lix-installer install linux --help $ ./nix-installer install linux --help
A planner for Linux installs A planner for Linux installs
Usage: lix-installer install linux [OPTIONS] Usage: nix-installer install linux [OPTIONS]
Options: Options:
# ... # ...
@ -72,33 +90,56 @@ Options:
Planners can be configured via environment variable or command arguments: Planners can be configured via environment variable or command arguments:
```bash ```bash
$ curl --proto '=https' --tlsv1.2 -sSf -L https://install.lix.systems/lix | NIX_BUILD_GROUP_NAME=nixbuilder sh -s -- install linux-multi --nix-build-group-id 4000 $ curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | NIX_BUILD_GROUP_NAME=nixbuilder sh -s -- install linux-multi --nix-build-group-id 4000
# Or... # Or...
$ NIX_BUILD_GROUP_NAME=nixbuilder ./lix-installer install linux-multi --nix-build-group-id 4000 $ NIX_BUILD_GROUP_NAME=nixbuilder ./nix-installer install linux-multi --nix-build-group-id 4000
``` ```
### Upgrading Lix ### Upgrading Nix
You can upgrade Lix with: You can upgrade Nix (to the version specified [here](https://raw.githubusercontent.com/NixOS/nixpkgs/master/nixos/modules/installer/tools/nix-fallback-paths.nix)) by running:
``` ```
sudo -i nix upgrade-nix sudo -i nix upgrade-nix
``` ```
Alternatively, you can [uninstall](#uninstalling) and [reinstall](#usage) with a different version of the `lix-installer`. Alternatively, you can [uninstall](#uninstalling) and [reinstall](#usage) with a different version of the `nix-installer`.
### Uninstalling ### Uninstalling
You can remove a `lix-installer`-installed Nix by running You can remove a `nix-installer`-installed Nix by running
```bash ```bash
/nix/lix-installer uninstall /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:
```yaml
on:
pull_request:
push:
branches: [main]
jobs:
lints:
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 .
``` ```
### Without systemd (Linux only) ### Without systemd (Linux only)
> **Warning** > **Warning**
> When `--init none` is used, _only_ `root` or users who can elevate to `root` privileges can run Lix's nix command: > When `--init none` is used, _only_ `root` or users who can elevate to `root` privileges can run Nix:
> >
> ```bash > ```bash
> sudo -i nix run nixpkgs#hello > sudo -i nix run nixpkgs#hello
@ -107,7 +148,7 @@ You can remove a `lix-installer`-installed Nix by running
If you don't use [systemd], you can still install Nix by explicitly specifying the `linux` plan and `--init none`: If you don't use [systemd], you can still install Nix by explicitly specifying the `linux` plan and `--init none`:
```bash ```bash
curl --proto '=https' --tlsv1.2 -sSf -L https://install.lix.systems/lix | sh -s -- install linux --init none curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install linux --init none
``` ```
### In a container ### In a container
@ -117,7 +158,7 @@ In Docker/Podman containers or WSL2 instances where an init (like `systemd`) is
For containers (without an init): For containers (without an init):
> **Warning** > **Warning**
> When `--init none` is used, _only_ `root` or users who can elevate to `root` privileges can run Lix's nix command: > When `--init none` is used, _only_ `root` or users who can elevate to `root` privileges can run Nix:
> >
> ```bash > ```bash
> sudo -i nix run nixpkgs#hello > sudo -i nix run nixpkgs#hello
@ -128,7 +169,7 @@ For containers (without an init):
FROM ubuntu:latest FROM ubuntu:latest
RUN apt update -y RUN apt update -y
RUN apt install curl -y RUN apt install curl -y
RUN curl --proto '=https' --tlsv1.2 -sSf -L https://install.lix.systems/lix | sh -s -- install linux \ RUN curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install linux \
--extra-conf "sandbox = false" \ --extra-conf "sandbox = false" \
--init none \ --init none \
--no-confirm --no-confirm
@ -153,7 +194,7 @@ For containers with a systemd init:
FROM ubuntu:latest FROM ubuntu:latest
RUN apt update -y RUN apt update -y
RUN apt install curl systemd -y RUN apt install curl systemd -y
RUN curl --proto '=https' --tlsv1.2 -sSf -L https://install.lix.systems/lix | sh -s -- install linux \ RUN curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install linux \
--extra-conf "sandbox = false" \ --extra-conf "sandbox = false" \
--no-start-daemon \ --no-start-daemon \
--no-confirm --no-confirm
@ -175,11 +216,11 @@ On some container tools, such as `docker`, `sandbox = false` can be omitted. Omi
### In WSL2 ### In WSL2
We **strongly recommend** [enabling systemd](https://ubuntu.com/blog/ubuntu-wsl-enable-systemd), then installing Lix as normal: We **strongly recommend** [enabling systemd](https://ubuntu.com/blog/ubuntu-wsl-enable-systemd), then installing Nix as normal:
```bash ```bash
curl --proto '=https' --tlsv1.2 -sSf -L https://install.lix.systems/lix | sh -s -- install 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: If [WSLg][wslg] is enabled, you can do things like open a Linux Firefox from Windows on Powershell:
@ -195,10 +236,10 @@ 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: If enabling system 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 Lix's nix commands: > When `--init none` is used, _only_ `root` or users who can elevate to `root` privileges can run Nix:
> >
> ```bash > ```bash
> sudo -i nix run nixpkgs#hello > sudo -i nix run nixpkgs#hello
@ -206,7 +247,7 @@ If enabling systemd is not an option, pass `--init none` at the end of the comma
```bash ```bash
curl --proto '=https' --tlsv1.2 -sSf -L https://install.lix.systems/lix | sh -s -- install linux --init none curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install linux --init none
``` ```
### Skip confirmation ### Skip confirmation
@ -214,7 +255,7 @@ curl --proto '=https' --tlsv1.2 -sSf -L https://install.lix.systems/lix | sh -s
If you'd like to bypass the confirmation step, you can apply the `--no-confirm` flag: If you'd like to bypass the confirmation step, you can apply the `--no-confirm` flag:
```bash ```bash
curl --proto '=https' --tlsv1.2 -sSf -L https://install.lix.systems/lix | sh -s -- install --no-confirm 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. This is especially useful when using the installer in non-interactive scripts.
@ -222,11 +263,54 @@ This is especially useful when using the installer in non-interactive scripts.
## Quirks ## Quirks
While `lix-installer` tries to provide a comprehensive and unquirky experience, there are unfortunately some issues which may require manual intervention or operator choices. While `nix-installer` tries to provide a comprehensive and unquirky experience, there are unfortunately some issues which may require manual intervention or operator choices.
### Using MacOS remote SSH builders, Nix binaries are not on `$PATH`
When connecting to a Mac remote SSH builder users may sometimes see this error:
```bash
$ nix store ping --store "ssh://$USER@$HOST"
Store URL: ssh://$USER@$HOST
zsh:1: command not found: nix-store
error: cannot connect to '$USER@$HOST'
```
The way MacOS populates the `PATH` environment differs from other environments. ([Some background](https://gist.github.com/Linerre/f11ad4a6a934dcf01ee8415c9457e7b2))
There are two possible workarounds for this:
* **(Preferred)** Update the remote builder URL to include the `remote-program` parameter pointing to `nix-store`. For example:
```bash
nix store ping --store "ssh://$USER@$HOST?remote-program=/nix/var/nix/profiles/default/bin/nix-store"
```
If you are unsure where the `nix-store` binary is located, run `which nix-store` on the remote.
* Update `/etc/zshenv` on the remote so that `zsh` populates the Nix path for every shell, even those that are neither *interactive* or *login*:
```bash
# Nix
if [ -e '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh' ]; then
. '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh'
fi
# End Nix
```
<details>
<summary>This strategy has some behavioral caveats, namely, <code>$PATH</code> may have unexpected contents</summary>
For example, if `$PATH` gets unset then a script invoked, `$PATH` may not be as empty as expected:
```bash
$ cat example.sh
#! /bin/zsh
echo $PATH
$ PATH= ./example.sh
/Users/ephemeraladmin/.nix-profile/bin:/nix/var/nix/profiles/default/bin:
```
This strategy results in Nix's paths being present on `$PATH` twice and may have a minor impact on performance.
</details>
### 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 any variant of `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, users may experience errors similar to this:
```bash ```bash
$ nix shell nixpkgs#curl $ nix shell nixpkgs#curl
@ -251,46 +335,56 @@ It's possible to resolve this situation by removing the `org.nixos.activate-syst
```bash ```bash
$ sudo rm /Library/LaunchDaemons/org.nixos.activate-system.plist $ sudo rm /Library/LaunchDaemons/org.nixos.activate-system.plist
$ sudo launchctl bootout system/org.nixos.activate-system $ sudo launchctl bootout system/org.nixos.activate-system
$ /nix/lix-installer uninstall $ /nix/nix-installer uninstall
$ sudo rm /etc/ssl/certs/ca-certificates.crt $ sudo rm /etc/ssl/certs/ca-certificates.crt
``` ```
Then run the `lix-installer` again, and it should work. Then run the `nix-installer` again, and it should work.
Up-to-date versions of the `lix-installer` will refuse to uninstall until `nix-darwin` is uninstalled first, helping mitigate this problem. Up-to-date versions of the `nix-installer` will refuse to uninstall until `nix-darwin` is uninstalled first, helping mitigate this problem.
## Building a binary ## Building a binary
Since you'll be using `lix-installer` to install Nix on systems without Nix, the default build is a static binary. Since you'll be using `nix-installer` to install Nix on systems without Nix, the default build is a static binary.
Build a portable Linux binary on a system with Nix: Build a portable Linux binary on a system with Nix:
```bash ```bash
# to build a local copy # to build a local copy
nix build -L ".#lix-installer-static" nix build -L ".#nix-installer-static"
# to build the remote main development branch
nix build -L "github:determinatesystems/nix-installer#nix-installer-static"
# for a specific version of the installer:
export NIX_INSTALLER_TAG="v0.6.0"
nix build -L "github:determinatesystems/nix-installer/$NIX_INSTALLER_TAG#nix-installer-static"
``` ```
On Mac: On Mac:
```bash ```bash
# to build a local copy # to build a local copy
nix build -L ".#lix-installer" nix build -L ".#nix-installer"
# to build the remote main development branch
nix build -L "github:determinatesystems/nix-installer#nix-installer"
# for a specific version of the installer:
export NIX_INSTALLER_TAG="v0.6.0"
nix build -L "github:determinatesystems/nix-installer/$NIX_INSTALLER_TAG#nix-installer"
``` ```
Then copy the `result/bin/lix-installer` to the machine you wish to run it on. Then copy the `result/bin/nix-installer` to the machine you wish to run it on.
You can also add `lix-installer` to a system without Lix via `cargo`. There are no system dependencies to worry about: You can also add `nix-installer` to a system without Nix via `cargo`, there are no system dependencies to worry about:
```bash ```bash
# to build and run a local copy # to build and run a local copy
RUSTFLAGS="--cfg tokio_unstable" cargo run -- --help RUSTFLAGS="--cfg tokio_unstable" cargo run -- --help
# to build the remote main development branch # to build the remote main development branch
RUSTFLAGS="--cfg tokio_unstable" cargo install --git https://git.lix.systems/lix-project/lix-installer RUSTFLAGS="--cfg tokio_unstable" cargo install --git https://github.com/DeterminateSystems/nix-installer
lix-installer --help nix-installer --help
# for a specific version of the installer: # for a specific version of the installer:
export NIX_INSTALLER_TAG="v0.6.0" export NIX_INSTALLER_TAG="v0.6.0"
RUSTFLAGS="--cfg tokio_unstable" cargo install --git https://git.lix.systems/lix-project/lix-installer --tag $NIX_INSTALLER_TAG RUSTFLAGS="--cfg tokio_unstable" cargo install --git https://github.com/DeterminateSystems/nix-installer --tag $NIX_INSTALLER_TAG
lix-installer --help nix-installer --help
``` ```
To make this build portable, pass ` --target x86_64-unknown-linux-musl`. To make this build portable, pass ` --target x86_64-unknown-linux-musl`.
@ -302,12 +396,12 @@ To make this build portable, pass ` --target x86_64-unknown-linux-musl`.
## As a library ## As a library
> **Warning** > **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. > 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 stablization.
Add `lix-installer` to your dependencies: Add `nix-installer` to your dependencies:
```bash ```bash
cargo add lix-installer cargo add nix-installer
``` ```
If you are **building a CLI**, check out the `cli` feature flag for `clap` integration. If you are **building a CLI**, check out the `cli` feature flag for `clap` integration.
@ -320,21 +414,39 @@ You'll also need to edit your `.cargo/config.toml` to use `tokio_unstable` as we
rustflags=["--cfg", "tokio_unstable"] rustflags=["--cfg", "tokio_unstable"]
``` ```
Then it's possible to review the [documentation](https://docs.rs/lix-installer/latest/lix_installer/): Then it's possible to review the [documentation](https://docs.rs/nix-installer/latest/nix_installer/):
```bash ```bash
cargo doc --open -p lix-installer cargo doc --open -p nix-installer
```
Documentation is also available via `nix` build:
```bash
nix build github:DeterminateSystems/nix-installer#nix-installer.doc
firefox result-doc/nix-installer/index.html
``` ```
## Accessing other versions ## Accessing other versions
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. For users who desire version pinning, the version of `nix-installer` to use can be specified in the `curl` command:
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. ```bash
Here are some example `nix` package URLs including nix version, OS and architecture: VERSION="v0.6.0"
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).
These releases can be downloaded and used directly:
```bash
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
```
* 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
@ -345,13 +457,58 @@ Differing from the upstream [Nix](https://github.com/NixOS/nix) installer script
+ `bash-prompt-prefix` is set + `bash-prompt-prefix` is set
+ `auto-optimise-store` is set to `true` (On Linux only) + `auto-optimise-store` is set to `true` (On Linux only)
* `extra-nix-path` is set to `nixpkgs=flake:nixpkgs` * `extra-nix-path` is set to `nixpkgs=flake:nixpkgs`
* `max-jobs` is set to `auto` * an installation receipt (for uninstalling) is stored at `/nix/receipt.json` as well as a copy of the install binary at `/nix/nix-installer`
* an installation receipt (for uninstalling) is stored at `/nix/receipt.json` as well as a copy of the install binary at `/nix/lix-installer`
* `nix-channel --update` is not run, `~/.nix-channels` is not provisioned * `nix-channel --update` is not run, `~/.nix-channels` is not provisioned
* `ssl-cert-file` is set in `/etc/nix/nix.conf` if the `ssl-cert-file` argument is used. * `ssl-cert-file` is set in `/etc/nix/nix.conf` if the `ssl-cert-file` argument is used.
## Motivations
## No Telemetry Included The existing upstream scripts do a good job, however they are difficult to maintain.
The Lix installer respects user privacy, and thus collects no information. Subtle differences in the shell implementations and tool used in the scripts make it difficult to make meaningful changes to the installer.
The Determinate Nix installer has numerous advantages:
* 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
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/).
## Diagnostics
The goal of the Determinate Nix Installer is to successfully and correctly install Nix.
The `curl | sh` pipeline and the installer collects a little bit of diagnostic information to help us make that true.
Here is a table of the [diagnostic data we collect][diagnosticdata]:
| Field | Use |
| --------------------- | ----------------------------------------------------------------------------------------------------- |
| `version` | The version of the Determinate Nix Installer. |
| `planner` | The method of installing Nix (`linux`, `macos`, `steam-deck`) |
| `configured_settings` | The names of planner settings which were changed from their default. Does _not_ include the values. |
| `os_name` | The running operating system. |
| `os_version` | The version of the operating system. |
| `triple` | The architecture/operating system/binary format of your system. |
| `is_ci` | Whether the installer is being used in CI (e.g. GitHub Actions). |
| `action` | Either `Install` or `Uninstall`. |
| `status` | One of `Success`, `Failure`, `Pending`, or `Cancelled`. |
| `attribution` | Optionally defined by the user, associate the diagnostics of this run to the provided value. |
| `failure_chain` | A high level description of what the failure was, if any. For example: `Command("diskutil")` if the command `diskutil list` failed. |
To disable diagnostic reporting, set the diagnostics URL to an empty string by passing `--diagnostic-endpoint=""` or setting `NIX_INSTALLER_DIAGNOSTIC_ENDPOINT=""`.
You can read the full privacy policy for [Determinate Systems][detsys], the creators of the Determinate Nix Installer, [here][privacy].
[detsys]: https://determinate.systems/
[diagnosticdata]: https://github.com/DeterminateSystems/nix-installer/blob/f9f927840d532b71f41670382a30cfcbea2d8a35/src/diagnostics.rs#L29-L43
[privacy]: https://determinate.systems/privacy
[systemd]: https://systemd.io
[wslg]: https://github.com/microsoft/wslg
[nixgl]: https://github.com/guibou/nixGL

View file

@ -1,63 +0,0 @@
#! /usr/bin/env nix-shell
#! nix-shell -i xonsh -p xonsh rustup cargo-zigbuild zig
#
# vim: ts=4 sw=4 et
#
# If the shebang line above was necessary, you probably should have used
# the flake, instead. But that's okay! You're valid. <3
#
""" Lix installer generation script.
This uses cargo-zigbuild to generate a cross-compiled variant for each platform,
and places the results in the `results` subdirectory of the current working dir.
"""
import sys
import xonsh
import functools
# Ensure we fail if any of our subcommands do.
$RAISE_SUBPROC_ERROR=True
# Specify the platforms we want to build for.
TARGET_PLATFORMS = [
"aarch64-apple-darwin",
"x86_64-apple-darwin",
"x86_64-unknown-linux-musl",
"aarch64-unknown-linux-musl",
]
# Create an alias for printing to stderr.
printerr = functools.partial(print, file=sys.stderr)
# Platform helpers.
IS_MACOS = not (xonsh.tools.ON_LINUX or xonsh.tools.ON_WINDOWS)
# Until our flake ships this with osxcross, we'll have to run this on macOS.
if not IS_MACOS:
printerr("This currently must be run from macOS due to cross-compile wonk. Sorry :(.")
sys.exit(-1)
# Pre-flight check: ensure we have all the rustup platforms we need.
all_targets_present = True
for platform in TARGET_PLATFORMS:
if platform not in $(rustup target list --installed):
printerr(f"ERROR: You don't have a rustup toolchain for {platform}! Install it with `rustup target add {platform}`")
all_targets_present = False
if not all_targets_present:
printerr("Failing out; install the platforms above and retry.")
sys.exit(-2)
# Build for each of our platforms.
printerr("> Building any platforms that need updating.")
for platform in TARGET_PLATFORMS:
# Build...
printerr(f"> Building for target {platform}")
cargo zigbuild --quiet --release --target=@(platform)
# ... and copy the output to the "results" directory.
mkdir -p ./results
cp target/@(platform)/release/lix-installer ./results/lix-installer-@(platform)

99
enter-env.sh Executable file
View file

@ -0,0 +1,99 @@
#!/usr/bin/env nix-shell
#!nix-shell -p vault awscli2 jq -i bash
# shellcheck shell=bash
set +x # don't leak secrets!
set -eu
umask 077
scriptroot=$(dirname "$(realpath "$0")")
scratch=$(mktemp -d -t tmp.XXXXXXXXXX)
vault token lookup &>/dev/null || {
echo "You're not logged in to vault! Exiting."
exit 1
}
function finish {
set +e
rm -rf "$scratch"
if [ "${VAULT_EXIT_ACCESSOR:-}" != "" ]; then
if vault token lookup &>/dev/null; then
echo "--> Revoking my token..." >&2
vault token revoke -self
fi
fi
set -e
}
trap finish EXIT
assume_role() {
role=$1
echo "--> Assuming role: $role" >&2
vault_creds=$(vault token create \
-display-name="$role" \
-format=json \
-role "$role")
VAULT_EXIT_ACCESSOR=$(jq -r .auth.accessor <<<"$vault_creds")
export VAULT_TOKEN
VAULT_TOKEN=$(jq -r .auth.client_token <<<"$vault_creds")
}
function provision_aws_creds() {
url="$1"
local ok=
echo "--> Setting AWS variables: " >&2
echo " AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN" >&2
aws_creds=$(vault kv get -format=json "$url")
export AWS_ACCESS_KEY_ID
AWS_ACCESS_KEY_ID=$(jq -r .data.access_key <<<"$aws_creds")
export AWS_SECRET_ACCESS_KEY
AWS_SECRET_ACCESS_KEY=$(jq -r .data.secret_key <<<"$aws_creds")
export AWS_SESSION_TOKEN
AWS_SESSION_TOKEN=$(jq -r .data.security_token <<<"$aws_creds")
if [ -z "$AWS_SESSION_TOKEN" ] || [ "$AWS_SESSION_TOKEN" == "null" ]; then
unset AWS_SESSION_TOKEN
fi
echo "--> Preflight testing the AWS credentials..." >&2
for _ in {0..20}; do
if check_output=$(aws sts get-caller-identity 2>&1 >/dev/null); then
ok=1
break
else
echo -n "." >&2
sleep 1
fi
done
if [[ -z "$ok" ]]; then
echo $'\nPreflight test failed:\n'"$check_output" >&2
return 1
fi
echo
unset aws_creds
}
assume_role "internalservices_nix_installer_developer"
provision_aws_creds "internalservices/aws/creds/nix_installer"
if [ "${1:-}" == "" ]; then
cat <<\BASH > "$scratch/bashrc"
expiration_ts=$(date +%s -d "$(vault token lookup -format=json | jq -r '.data.expire_time')")
vault_prompt() {
local remaining=$(( $expiration_ts - $(date '+%s')))
if [[ "$remaining" -lt 1 ]]; then
remaining=expired
printf '\n\e[01;33mtoken expired\e[m';
return
fi
printf '\n\e[01;32mTTL:%ss\e[m' "$remaining"
}
PROMPT_COMMAND=vault_prompt
BASH
bash --init-file "$scratch/bashrc"
else
"$@"
fi

View file

@ -8,22 +8,19 @@
"rust-analyzer-src": "rust-analyzer-src" "rust-analyzer-src": "rust-analyzer-src"
}, },
"locked": { "locked": {
"lastModified": 1714890282, "narHash": "sha256-0dZpggYjjmWEk+rGixiBHOHuQfLzEzNfrtjSig04s6Q=",
"narHash": "sha256-0dRK2ChvkhWrLM6H3d4r+rXP/UDxTJ6Vkdr22uGb1H0=", "rev": "9ccae1754eec0341b640d5705302ac0923d22875",
"owner": "nix-community", "revCount": 1618,
"repo": "fenix", "type": "tarball",
"rev": "24d83329e95a3bc48cbe9f3cd23813c210a25ea6", "url": "https://api.flakehub.com/f/pinned/nix-community/fenix/0.1.1618%2Brev-9ccae1754eec0341b640d5705302ac0923d22875/018aea4c-03c9-7734-95d5-b84cc8881e3d/source.tar.gz"
"type": "github"
}, },
"original": { "original": {
"owner": "nix-community", "type": "tarball",
"repo": "fenix", "url": "https://flakehub.com/f/nix-community/fenix/0.1.1584.tar.gz"
"type": "github"
} }
}, },
"flake-compat": { "flake-compat": {
"locked": { "locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"revCount": 57, "revCount": 57,
@ -51,19 +48,19 @@
"type": "github" "type": "github"
} }
}, },
"libgit2": { "lowdown-src": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1697646580, "lastModified": 1633514407,
"narHash": "sha256-oX4Z3S9WtJlwvj0uH9HlYcWv+x1hqp8mhXl7HsLu2f0=", "narHash": "sha256-Dw32tiMjdK9t3ETl5fzGrutQTzh2rufgZV4A/BbxuD4=",
"owner": "libgit2", "owner": "kristapsdz",
"repo": "libgit2", "repo": "lowdown",
"rev": "45fd9ed7ae1a9b74b957ef4f337bc3c8b3df01b5", "rev": "d2c2b44ff6c27b936ec27358a2653caaef8f73b8",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "libgit2", "owner": "kristapsdz",
"repo": "libgit2", "repo": "lowdown",
"type": "github" "type": "github"
} }
}, },
@ -74,11 +71,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1698420672, "lastModified": 1694081375,
"narHash": "sha256-/TdeHMPRjjdJub7p7+w55vyABrsJlt5QkznPYy55vKA=", "narHash": "sha256-vzJXOUnmkMCm3xw8yfPP5m8kypQ3BhAIRe4RRCWpzy8=",
"owner": "nix-community", "owner": "nix-community",
"repo": "naersk", "repo": "naersk",
"rev": "aeb58d5e8faead8980a807c840232697982d47b9", "rev": "3f976d822b7b37fc6fb8e6f157c2dd05e7e94e89",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -90,30 +87,29 @@
"nix": { "nix": {
"inputs": { "inputs": {
"flake-compat": "flake-compat_2", "flake-compat": "flake-compat_2",
"libgit2": "libgit2", "lowdown-src": "lowdown-src",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",
"nixpkgs-regression": "nixpkgs-regression" "nixpkgs-regression": "nixpkgs-regression"
}, },
"locked": { "locked": {
"lastModified": 1709808984, "narHash": "sha256-WNmifcTsN9aG1ONkv+l2BC4sHZZxtNKy0keqBHXXQ7w=",
"narHash": "sha256-bfFe38BkoQws7om4gBtBWoNTLkt9piMXdLLoHYl+vBQ=", "rev": "f5f4de6a550327b4b1a06123c2e450f1b92c73b6",
"rev": "f8170ce9f119e5e6724eb81ff1b5a2d4c0024000", "revCount": 14900,
"revCount": 16143,
"type": "tarball", "type": "tarball",
"url": "https://api.flakehub.com/f/pinned/NixOS/nix/2.20.5/018e199b-ae2c-703d-ab99-4c648be473b2/source.tar.gz" "url": "https://api.flakehub.com/f/pinned/NixOS/nix/2.18.1/018af406-b173-7112-9c1c-82f5b645e9d3/source.tar.gz"
}, },
"original": { "original": {
"type": "tarball", "type": "tarball",
"url": "https://flakehub.com/f/NixOS/nix/%3D2.20.5.tar.gz" "url": "https://flakehub.com/f/NixOS/nix/2.18.0.tar.gz"
} }
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1705033721, "lastModified": 1695283060,
"narHash": "sha256-K5eJHmL1/kev6WuqyqqbS1cdNnSidIZ3jeqJ7GbrYnQ=", "narHash": "sha256-CJz71xhCLlRkdFUSQEL0pIAAfcnWFXMzd9vXhPrnrEg=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "a1982c92d8980a0114372973cbdfe0a307f1bdea", "rev": "31ed632c692e6a36cfc18083b88ece892f863ed4",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -141,18 +137,15 @@
}, },
"nixpkgs_2": { "nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1714763106, "narHash": "sha256-9NJcFF9CEYPvHJ5ckE8kvINvI84SZZ87PvqMbH6pro0=",
"narHash": "sha256-DrDHo74uTycfpAF+/qxZAMlP/Cpe04BVioJb6fdI0YY=", "rev": "5e4c2ada4fcd54b99d56d7bd62f384511a7e2593",
"owner": "NixOS", "revCount": 534806,
"repo": "nixpkgs", "type": "tarball",
"rev": "e9be42459999a253a9f92559b1f5b72e1b44c13d", "url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.1.534806%2Brev-5e4c2ada4fcd54b99d56d7bd62f384511a7e2593/018b29e9-ae6d-72f2-993b-19cb9a64a3b5/source.tar.gz"
"type": "github"
}, },
"original": { "original": {
"owner": "NixOS", "type": "tarball",
"ref": "nixos-unstable", "url": "https://flakehub.com/f/NixOS/nixpkgs/0.1.0.tar.gz"
"repo": "nixpkgs",
"type": "github"
} }
}, },
"root": { "root": {
@ -167,11 +160,11 @@
"rust-analyzer-src": { "rust-analyzer-src": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1706735270, "lastModified": 1696050837,
"narHash": "sha256-IJk+UitcJsxzMQWm9pa1ZbJBriQ4ginXOlPyVq+Cu40=", "narHash": "sha256-2K3Aq4gjPZBDnkAMJaMA4ElE+BNbmrqtSBWtt9kPGaM=",
"owner": "rust-lang", "owner": "rust-lang",
"repo": "rust-analyzer", "repo": "rust-analyzer",
"rev": "42cb1a2bd79af321b0cc503d2960b73f34e2f92b", "rev": "0840038f02daec6ba3238f05d8caa037d28701a0",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

@ -1,11 +1,11 @@
{ {
description = "The Lix Installer"; description = "The Determinate Nix Installer";
inputs = { inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; nixpkgs.url = "https://flakehub.com/f/NixOS/nixpkgs/0.1.0.tar.gz";
fenix = { fenix = {
url = "github:nix-community/fenix"; url = "https://flakehub.com/f/nix-community/fenix/0.1.1584.tar.gz";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
@ -15,7 +15,7 @@
}; };
nix = { nix = {
url = "https://flakehub.com/f/NixOS/nix/=2.20.5.tar.gz"; url = "https://flakehub.com/f/NixOS/nix/2.18.0.tar.gz";
# Omitting `inputs.nixpkgs.follows = "nixpkgs";` on purpose # Omitting `inputs.nixpkgs.follows = "nixpkgs";` on purpose
}; };
@ -65,10 +65,10 @@
rustc = toolchain; rustc = toolchain;
}; };
sharedAttrs = { sharedAttrs = {
pname = "lix-installer"; pname = "nix-installer";
version = "0.17.1"; version = "0.14.0";
src = builtins.path { src = builtins.path {
name = "lix-installer-source"; name = "nix-installer-source";
path = self; path = self;
filter = (path: type: baseNameOf path != "nix" && baseNameOf path != ".github"); filter = (path: type: baseNameOf path != "nix" && baseNameOf path != ".github");
}; };
@ -93,27 +93,27 @@
''; '';
}; };
postInstall = '' postInstall = ''
cp lix-installer.sh $out/bin/lix-installer.sh cp nix-installer.sh $out/bin/nix-installer.sh
''; '';
}; };
in in
rec { rec {
lix-installer = naerskLib.buildPackage sharedAttrs; nix-installer = naerskLib.buildPackage sharedAttrs;
} // nixpkgs.lib.optionalAttrs (prev.stdenv.system == "x86_64-linux") rec { } // nixpkgs.lib.optionalAttrs (prev.stdenv.system == "x86_64-linux") rec {
default = lix-installer-static; default = nix-installer-static;
lix-installer-static = naerskLib.buildPackage nix-installer-static = naerskLib.buildPackage
(sharedAttrs // { (sharedAttrs // {
CARGO_BUILD_TARGET = "x86_64-unknown-linux-musl"; CARGO_BUILD_TARGET = "x86_64-unknown-linux-musl";
}); });
} // nixpkgs.lib.optionalAttrs (prev.stdenv.system == "i686-linux") rec { } // nixpkgs.lib.optionalAttrs (prev.stdenv.system == "i686-linux") rec {
default = lix-installer-static; default = nix-installer-static;
lix-installer-static = naerskLib.buildPackage nix-installer-static = naerskLib.buildPackage
(sharedAttrs // { (sharedAttrs // {
CARGO_BUILD_TARGET = "i686-unknown-linux-musl"; CARGO_BUILD_TARGET = "i686-unknown-linux-musl";
}); });
} // nixpkgs.lib.optionalAttrs (prev.stdenv.system == "aarch64-linux") rec { } // nixpkgs.lib.optionalAttrs (prev.stdenv.system == "aarch64-linux") rec {
default = lix-installer-static; default = nix-installer-static;
lix-installer-static = naerskLib.buildPackage nix-installer-static = naerskLib.buildPackage
(sharedAttrs // { (sharedAttrs // {
CARGO_BUILD_TARGET = "aarch64-unknown-linux-musl"; CARGO_BUILD_TARGET = "aarch64-unknown-linux-musl";
}); });
@ -133,13 +133,9 @@
nativeBuildInputs = with pkgs; [ ]; nativeBuildInputs = with pkgs; [ ];
buildInputs = with pkgs; [ buildInputs = with pkgs; [
zig
xonsh
awscli2
toolchain toolchain
rust-analyzer rust-analyzer
cargo-outdated cargo-outdated
cargo-zigbuild
cacert cacert
cargo-audit cargo-audit
cargo-watch cargo-watch
@ -194,18 +190,18 @@
packages = forAllSystems ({ system, pkgs, ... }: packages = forAllSystems ({ system, pkgs, ... }:
{ {
inherit (pkgs) lix-installer; inherit (pkgs) nix-installer;
} // nixpkgs.lib.optionalAttrs (system == "x86_64-linux") { } // nixpkgs.lib.optionalAttrs (system == "x86_64-linux") {
inherit (pkgs) lix-installer-static; inherit (pkgs) nix-installer-static;
default = pkgs.lix-installer-static; default = pkgs.nix-installer-static;
} // nixpkgs.lib.optionalAttrs (system == "i686-linux") { } // nixpkgs.lib.optionalAttrs (system == "i686-linux") {
inherit (pkgs) lix-installer-static; inherit (pkgs) nix-installer-static;
default = pkgs.lix-installer-static; default = pkgs.nix-installer-static;
} // nixpkgs.lib.optionalAttrs (system == "aarch64-linux") { } // nixpkgs.lib.optionalAttrs (system == "aarch64-linux") {
inherit (pkgs) lix-installer-static; inherit (pkgs) nix-installer-static;
default = pkgs.lix-installer-static; default = pkgs.nix-installer-static;
} // nixpkgs.lib.optionalAttrs (pkgs.stdenv.isDarwin) { } // nixpkgs.lib.optionalAttrs (pkgs.stdenv.isDarwin) {
default = pkgs.lix-installer; default = pkgs.nix-installer;
}); });
hydraJobs = { hydraJobs = {

View file

@ -2,22 +2,22 @@
# shellcheck shell=dash # 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://git.lix.systems/lix-project/lix-installer/releases then pick the version and platform # https://github.com/DeterminateSystems/nix-installer/releases then pick the version and platform
# most appropriate for your deployment target. # most appropriate for your deployment target.
# #
# This is just a little script that selects and downloads the right `lix-installer`. It does # This is just a little script that selects and downloads the right `nix-installer`. It does
# platform detection, downloads the installer, and runs it; that's it. # platform detection, downloads the installer, and runs it; that's it.
# #
# It runs on Unix shells like {a,ba,da,k,z}sh. It uses the common `local` # It runs on Unix shells like {a,ba,da,k,z}sh. It uses the common `local`
# extension. Note: Most shells limit `local` to 1 var per line, contra bash. # extension. Note: Most shells limit `local` to 1 var per line, contra bash.
# This script is based off https://github.com/rust-lang/rustup/blob/f8d7b3baba7a63237cb2b82ef49a68a37dd0633c/rustup-init.sh # This script is based off https://github.com/rust-lang/rustup/blob/8f6b53628ad996ad86f9c6225fa500cddf860905/rustup-init.sh
if [ "$KSH_VERSION" = 'Version JM 93t+ 2010-03-05' ]; then if [ "$KSH_VERSION" = 'Version JM 93t+ 2010-03-05' ]; then
# The version of ksh93 that ships with many illumos systems does not # The version of ksh93 that ships with many illumos systems does not
# support the "local" extension. Print a message rather than fail in # support the "local" extension. Print a message rather than fail in
# subtle ways later on: # subtle ways later on:
echo 'lix-installer does not work with this ksh93 version; please try bash!' >&2 echo 'nix-installer does not work with this ksh93 version; please try bash!' >&2
exit 1 exit 1
fi fi
@ -25,7 +25,7 @@ fi
set -u set -u
# If NIX_INSTALLER_FORCE_ALLOW_HTTP is unset or empty, default it. # If NIX_INSTALLER_FORCE_ALLOW_HTTP is unset or empty, default it.
NIX_INSTALLER_BINARY_ROOT="${NIX_INSTALLER_BINARY_ROOT:-https://install.lix.systems/lix}" NIX_INSTALLER_BINARY_ROOT="${NIX_INSTALLER_BINARY_ROOT:-https://install.determinate.systems/nix}"
main() { main() {
downloader --check downloader --check
@ -47,7 +47,7 @@ main() {
;; ;;
esac esac
local _url="${NIX_INSTALLER_OVERRIDE_URL-${NIX_INSTALLER_BINARY_ROOT}/lix-installer-${_arch}${_ext}}" local _url="${NIX_INSTALLER_OVERRIDE_URL-${NIX_INSTALLER_BINARY_ROOT}/nix-installer-${_arch}${_ext}}"
local _dir local _dir
if ! _dir="$(ensure mktemp -d)"; then if ! _dir="$(ensure mktemp -d)"; then
@ -55,7 +55,7 @@ main() {
# propagate exit status. # propagate exit status.
exit 1 exit 1
fi fi
local _file="${_dir}/lix-installer${_ext}" local _file="${_dir}/nix-installer${_ext}"
local _ansi_escapes_are_valid=false local _ansi_escapes_are_valid=false
if [ -t 2 ]; then if [ -t 2 ]; then
@ -95,7 +95,7 @@ main() {
ensure chmod u+x "$_file" ensure chmod u+x "$_file"
if [ ! -x "$_file" ]; then if [ ! -x "$_file" ]; then
printf '%s\n' "Cannot execute $_file (likely because of mounting /tmp as noexec)." 1>&2 printf '%s\n' "Cannot execute $_file (likely because of mounting /tmp as noexec)." 1>&2
printf '%s\n' "Please copy the file to a location where you can execute binaries and run ./lix-installer${_ext}." 1>&2 printf '%s\n' "Please copy the file to a location where you can execute binaries and run ./nix-installer${_ext}." 1>&2
exit 1 exit 1
fi fi
@ -143,30 +143,10 @@ get_architecture() {
fi fi
fi fi
if [ "$_ostype" = Darwin ]; then if [ "$_ostype" = Darwin ] && [ "$_cputype" = i386 ]; then
# Darwin `uname -m` can lie due to Rosetta shenanigans. If you manage to # Darwin `uname -m` lies
# invoke a native shell binary and then a native uname binary, you can if sysctl hw.optional.x86_64 | grep -q ': 1'; then
# get the real answer, but that's hard to ensure, so instead we use _cputype=x86_64
# `sysctl` (which doesn't lie) to check for the actual architecture.
if [ "$_cputype" = i386 ]; then
# Handling i386 compatibility mode in older macOS versions (<10.15)
# running on x86_64-based Macs.
# Starting from 10.15, macOS explicitly bans all i386 binaries from running.
# See: <https://support.apple.com/en-us/HT208436>
# Avoid `sysctl: unknown oid` stderr output and/or non-zero exit code.
if sysctl hw.optional.x86_64 2> /dev/null || true | grep -q ': 1'; then
_cputype=x86_64
fi
elif [ "$_cputype" = x86_64 ]; then
# Handling x86-64 compatibility mode (a.k.a. Rosetta 2)
# in newer macOS versions (>=11) running on arm64-based Macs.
# Rosetta 2 is built exclusively for x86-64 and cannot run i386 binaries.
# Avoid `sysctl: unknown oid` stderr output and/or non-zero exit code.
if sysctl hw.optional.arm64 2> /dev/null || true | grep -q ': 1'; then
_cputype=arm64
fi
fi fi
fi fi
@ -229,7 +209,7 @@ get_architecture() {
} }
say() { say() {
printf 'lix-installer: %s\n' "$1" printf 'nix-installer: %s\n' "$1"
} }
err() { err() {

View file

@ -1,9 +1,9 @@
FROM default FROM default
COPY lix-installer /lix-installer COPY nix-installer /nix-installer
RUN chmod +x /lix-installer RUN chmod +x /nix-installer
COPY binary-tarball /binary-tarball COPY binary-tarball /binary-tarball
RUN mv /binary-tarball/nix-*.tar.xz nix.tar.xz RUN mv /binary-tarball/nix-*.tar.xz nix.tar.xz
RUN /nix-installer/bin/lix-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=debug --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" 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-build --no-substitute -E 'derivation { name = "foo"; system = "x86_64-linux"; builder = "/bin/sh"; args = ["-c" "echo foobar > $out"]; }'
RUN /nix/lix-installer uninstall --no-confirm RUN /nix/nix-installer uninstall --no-confirm

View file

@ -37,7 +37,7 @@ impl CreateGroup {
}, },
} }
// Ensure group does not exist // Ensure group does not exists
if let Some(group) = Group::from_name(name.as_str()) if let Some(group) = Group::from_name(name.as_str())
.map_err(|e| ActionErrorKind::GettingGroupId(name.clone(), e)) .map_err(|e| ActionErrorKind::GettingGroupId(name.clone(), e))
.map_err(Self::error)? .map_err(Self::error)?

View file

@ -17,7 +17,7 @@ use crate::action::{
/// The `nix.conf` configuration names that are safe to merge. /// The `nix.conf` configuration names that are safe to merge.
// FIXME(@cole-h): make configurable by downstream users? // FIXME(@cole-h): make configurable by downstream users?
const MERGEABLE_CONF_NAMES: &[&str] = &["experimental-features", "substituters", "trusted-public-keys"]; const MERGEABLE_CONF_NAMES: &[&str] = &["experimental-features"];
const NIX_CONF_MODE: u32 = 0o664; const NIX_CONF_MODE: u32 = 0o664;
const NIX_CONF_COMMENT_CHAR: char = '#'; const NIX_CONF_COMMENT_CHAR: char = '#';
@ -369,7 +369,7 @@ impl Action for CreateOrMergeNixConfig {
new_config.push_str(name); new_config.push_str(name);
new_config.push_str(" = "); new_config.push_str(" = ");
if let Some(merged_value) = merged_nix_config.settings_mut().swap_remove(name) { if let Some(merged_value) = merged_nix_config.settings_mut().remove(name) {
new_config.push_str(&merged_value); new_config.push_str(&merged_value);
new_config.push(' '); new_config.push(' ');
} else { } else {
@ -390,7 +390,7 @@ impl Action for CreateOrMergeNixConfig {
}; };
if let Some(to_remove) = to_remove { if let Some(to_remove) = to_remove {
existing_nix_config.settings_mut().swap_remove(&to_remove); existing_nix_config.settings_mut().remove(&to_remove);
} }
} }
@ -409,10 +409,13 @@ impl Action for CreateOrMergeNixConfig {
new_config.push('\n'); new_config.push('\n');
} }
new_config new_config.push_str(
.push_str("# Generated by https://install.lix.systems/.\n"); &[
new_config.push_str("# See `/nix/nix-installer --version` for the version details.\n"); "# Generated by https://github.com/DeterminateSystems/nix-installer.",
new_config.push('\n'); "# See `/nix/nix-installer --version` for the version details.",
]
.join("\n"),
);
for (name, value) in merged_nix_config.settings() { for (name, value) in merged_nix_config.settings() {
new_config.push_str(name); new_config.push_str(name);

View file

@ -237,7 +237,6 @@ impl Action for CreateUser {
.args([ .args([
"--home", "--home",
"/var/empty", "/var/empty",
"-H", // Don't create a home.
"--gecos", "--gecos",
comment, comment,
"--ingroup", "--ingroup",

View file

@ -1,4 +1,5 @@
use std::{ use std::{
fs::Permissions,
os::unix::prelude::PermissionsExt, os::unix::prelude::PermissionsExt,
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
@ -109,21 +110,13 @@ impl Action for MoveUnpackedNix {
.map_err(|e| ActionErrorKind::Rename(entry.path(), entry_dest.to_owned(), e)) .map_err(|e| ActionErrorKind::Rename(entry.path(), entry_dest.to_owned(), e))
.map_err(Self::error)?; .map_err(Self::error)?;
let perms: Permissions = PermissionsExt::from_mode(0o555);
for entry_item in WalkDir::new(&entry_dest) for entry_item in WalkDir::new(&entry_dest)
.into_iter() .into_iter()
.filter_map(Result::ok) .filter_map(Result::ok)
.filter(|e| !e.file_type().is_symlink()) .filter(|e| !e.file_type().is_symlink())
{ {
let path = entry_item.path(); tokio::fs::set_permissions(&entry_item.path(), perms.clone())
let mut perms = path
.metadata()
.map_err(|e| ActionErrorKind::GetMetadata(path.to_owned(), e))
.map_err(Self::error)?
.permissions();
perms.set_readonly(true);
tokio::fs::set_permissions(path, perms.clone())
.await .await
.map_err(|e| { .map_err(|e| {
ActionErrorKind::SetPermissions( ActionErrorKind::SetPermissions(

View file

@ -79,14 +79,10 @@ impl ConfigureInitService {
}, },
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
InitSystem::Systemd => { InitSystem::Systemd => {
// If `no_start_daemon` is set, then we don't require a running systemd, // If /run/systemd/system exists, we can be reasonably sure the machine is booted
// so we don't need to check if `/run/systemd/system` exists. // with systemd: https://www.freedesktop.org/software/systemd/man/sd_booted.html
if start_daemon { if !Path::new("/run/systemd/system").exists() {
// If /run/systemd/system exists, we can be reasonably sure the machine is booted return Err(Self::error(ActionErrorKind::SystemdMissing));
// with systemd: https://www.freedesktop.org/software/systemd/man/sd_booted.html
if !Path::new("/run/systemd/system").exists() {
return Err(Self::error(ActionErrorKind::SystemdMissing));
}
} }
if which::which("systemctl").is_err() { if which::which("systemctl").is_err() {
@ -173,7 +169,7 @@ impl Action for ConfigureInitService {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
InitSystem::Launchd => { InitSystem::Launchd => {
let src = std::path::Path::new(DARWIN_NIX_DAEMON_SOURCE); let src = std::path::Path::new(DARWIN_NIX_DAEMON_SOURCE);
tokio::fs::copy(src, DARWIN_NIX_DAEMON_DEST) tokio::fs::copy(src.clone(), DARWIN_NIX_DAEMON_DEST)
.await .await
.map_err(|e| { .map_err(|e| {
Self::error(ActionErrorKind::Copy( Self::error(ActionErrorKind::Copy(

View file

@ -46,7 +46,6 @@ impl ConfigureNix {
settings.proxy.clone(), settings.proxy.clone(),
settings.ssl_cert_file.clone(), settings.ssl_cert_file.clone(),
settings.extra_conf.clone(), settings.extra_conf.clone(),
settings.enable_flakes,
settings.force, settings.force,
) )
.await .await

View file

@ -44,29 +44,25 @@ impl ConfigureShellProfile {
for profile_target in locations.bash.iter().chain(locations.zsh.iter()) { for profile_target in locations.bash.iter().chain(locations.zsh.iter()) {
let profile_target_path = Path::new(profile_target); let profile_target_path = Path::new(profile_target);
if let Some(parent) = profile_target_path.parent() { if let Some(parent) = profile_target_path.parent() {
// Some tools (eg `nix-darwin`) create symlinks to these files, don't write to them if that's the case. if !parent.exists() {
if !profile_target_path.is_symlink() { create_directories.push(
if !parent.exists() { CreateDirectory::plan(parent, None, None, 0o0755, false)
create_directories.push( .await
CreateDirectory::plan(parent, None, None, 0o0755, false) .map_err(Self::error)?,
.await
.map_err(Self::error)?,
);
}
create_or_insert_files.push(
CreateOrInsertIntoFile::plan(
profile_target_path,
None,
None,
0o644,
shell_buf.to_string(),
create_or_insert_into_file::Position::Beginning,
)
.await
.map_err(Self::error)?,
); );
} }
create_or_insert_files.push(
CreateOrInsertIntoFile::plan(
profile_target_path,
None,
None,
0o644,
shell_buf.to_string(),
create_or_insert_into_file::Position::Beginning,
)
.await
.map_err(Self::error)?,
);
} }
} }
@ -92,27 +88,23 @@ impl ConfigureShellProfile {
let mut profile_target = fish_prefix_path; let mut profile_target = fish_prefix_path;
profile_target.push(locations.fish.confd_suffix.clone()); profile_target.push(locations.fish.confd_suffix.clone());
// Some tools (eg `nix-darwin`) create symlinks to these files, don't write to them if that's the case. if let Some(conf_d) = profile_target.parent() {
if !profile_target.is_symlink() { create_directories.push(
if let Some(conf_d) = profile_target.parent() { CreateDirectory::plan(conf_d.to_path_buf(), None, None, 0o755, false).await?,
create_directories.push(
CreateDirectory::plan(conf_d.to_path_buf(), None, None, 0o755, false)
.await?,
);
}
create_or_insert_files.push(
CreateOrInsertIntoFile::plan(
profile_target,
None,
None,
0o644,
fish_buf.to_string(),
create_or_insert_into_file::Position::Beginning,
)
.await?,
); );
} }
create_or_insert_files.push(
CreateOrInsertIntoFile::plan(
profile_target,
None,
None,
0o644,
fish_buf.to_string(),
create_or_insert_into_file::Position::Beginning,
)
.await?,
);
} }
for fish_prefix in &locations.fish.vendor_confd_prefixes { for fish_prefix in &locations.fish.vendor_confd_prefixes {
let fish_prefix_path = PathBuf::from(fish_prefix); let fish_prefix_path = PathBuf::from(fish_prefix);

View file

@ -77,7 +77,7 @@ impl Action for CreateUsersAndGroups {
} else { } else {
format!( format!(
"Create build users (UID {}-{}) and group (GID {})", "Create build users (UID {}-{}) and group (GID {})",
self.nix_build_user_id_base + 1, self.nix_build_user_id_base,
self.nix_build_user_id_base + self.nix_build_user_count, self.nix_build_user_id_base + self.nix_build_user_count,
self.nix_build_group_id self.nix_build_group_id
) )

View file

@ -30,7 +30,6 @@ impl PlaceNixConfiguration {
proxy: Option<Url>, proxy: Option<Url>,
ssl_cert_file: Option<PathBuf>, ssl_cert_file: Option<PathBuf>,
extra_conf: Vec<UrlOrPathOrString>, extra_conf: Vec<UrlOrPathOrString>,
enable_flakes: bool,
force: bool, force: bool,
) -> Result<StatefulAction<Self>, ActionError> { ) -> Result<StatefulAction<Self>, ActionError> {
let mut extra_conf_text = vec![]; let mut extra_conf_text = vec![];
@ -92,13 +91,7 @@ impl PlaceNixConfiguration {
let settings = nix_config.settings_mut(); let settings = nix_config.settings_mut();
settings.insert("build-users-group".to_string(), nix_build_group_name); settings.insert("build-users-group".to_string(), nix_build_group_name);
let mut experimental_features = vec!["nix-command"]; let experimental_features = ["nix-command", "flakes", "repl-flake"];
// Enable flakes if desired.
if enable_flakes {
experimental_features.push("flakes");
}
match settings.entry("experimental-features".to_string()) { match settings.entry("experimental-features".to_string()) {
Entry::Occupied(mut slot) => { Entry::Occupied(mut slot) => {
let slot_mut = slot.get_mut(); let slot_mut = slot.get_mut();
@ -132,23 +125,10 @@ impl PlaceNixConfiguration {
ssl_cert_file_canonical.display().to_string(), ssl_cert_file_canonical.display().to_string(),
); );
} }
// Set up our substituters.
settings.insert( settings.insert(
"substituters".to_string(), "extra-nix-path".to_string(),
"https://cache.nixos.org https://cache.lix.systems".to_string(), "nixpkgs=flake:nixpkgs".to_string(),
); );
settings.insert(
"trusted-public-keys".to_string(),
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o=".to_string()
);
if enable_flakes {
settings.insert(
"extra-nix-path".to_string(),
"nixpkgs=flake:nixpkgs".to_string(),
);
}
let create_directory = CreateDirectory::plan(NIX_CONF_FOLDER, None, None, 0o0755, force) let create_directory = CreateDirectory::plan(NIX_CONF_FOLDER, None, None, 0o0755, force)
.await .await

View file

@ -1,112 +0,0 @@
use crate::action::base::{create_or_insert_into_file, CreateOrInsertIntoFile};
use crate::action::{Action, ActionDescription, ActionError, ActionTag, StatefulAction};
use std::path::Path;
use tracing::{span, Instrument, Span};
const PROFILE_NIX_FILE_SHELL: &str = "/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh";
/**
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)]
pub struct ConfigureRemoteBuilding {
create_or_insert_into_file: Option<StatefulAction<CreateOrInsertIntoFile>>,
}
impl ConfigureRemoteBuilding {
#[tracing::instrument(level = "debug", skip_all)]
pub async fn plan() -> Result<StatefulAction<Self>, ActionError> {
let shell_buf = format!(
r#"
# Set up Nix only on SSH connections
# See: https://github.com/DeterminateSystems/nix-installer/pull/714
if [ -e '{PROFILE_NIX_FILE_SHELL}' ] && [ -n "${{SSH_CONNECTION}}" ] && [ "${{SHLVL}}" -eq 1 ]; then
. '{PROFILE_NIX_FILE_SHELL}'
fi
# End Nix
"#
);
let zshenv = Path::new("/etc/zshenv");
let create_or_insert_into_file = if !zshenv.is_symlink() {
Some(
CreateOrInsertIntoFile::plan(
zshenv,
None,
None,
0o644,
shell_buf.to_string(),
create_or_insert_into_file::Position::Beginning,
)
.await
.map_err(Self::error)?,
)
} else {
None
};
Ok(Self {
create_or_insert_into_file,
}
.into())
}
}
#[async_trait::async_trait]
#[typetag::serde(name = "configure_remote_building")]
impl Action for ConfigureRemoteBuilding {
fn action_tag() -> ActionTag {
ActionTag("configure_remote_building")
}
fn tracing_synopsis(&self) -> String {
"Configuring zsh to support using Nix in non-interactive shells".to_string()
}
fn tracing_span(&self) -> Span {
span!(tracing::Level::DEBUG, "configure_remote_building",)
}
fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
if self.create_or_insert_into_file.is_none() {
"Skipping configuring zsh to support using Nix in non-interactive shells, `/etc/zshenv` is a symlink".to_string()
} else {
self.tracing_synopsis()
},
vec!["Update `/etc/zshenv` to import Nix".to_string()],
)]
}
#[tracing::instrument(level = "debug", skip_all)]
async fn execute(&mut self) -> Result<(), ActionError> {
let span = tracing::Span::current().clone();
if let Some(create_or_insert_into_file) = &mut self.create_or_insert_into_file {
create_or_insert_into_file
.try_execute()
.instrument(span)
.await
.map_err(Self::error)?;
}
Ok(())
}
fn revert_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
"Remove the Nix configuration from zsh's non-login shells".to_string(),
vec!["Update `/etc/zshenv` to no longer import Nix".to_string()],
)]
}
#[tracing::instrument(level = "debug", skip_all)]
async fn revert(&mut self) -> Result<(), ActionError> {
if let Some(create_or_insert_into_file) = &mut self.create_or_insert_into_file {
create_or_insert_into_file.try_revert().await?
};
Ok(())
}
}

View file

@ -27,9 +27,9 @@ impl CreateNixHookService {
pub async fn plan() -> Result<StatefulAction<Self>, ActionError> { pub async fn plan() -> Result<StatefulAction<Self>, ActionError> {
let mut this = Self { let mut this = Self {
path: PathBuf::from( path: PathBuf::from(
"/Library/LaunchDaemons/systems.lix.nix-installer.nix-hook.plist", "/Library/LaunchDaemons/systems.determinate.nix-installer.nix-hook.plist",
), ),
service_label: "systems.lix.nix-installer.nix-hook".into(), service_label: "systems.determinate.nix-installer.nix-hook".into(),
needs_bootout: false, needs_bootout: false,
}; };

View file

@ -2,7 +2,6 @@
*/ */
pub(crate) mod bootstrap_launchctl_service; pub(crate) mod bootstrap_launchctl_service;
pub(crate) mod configure_remote_building;
pub(crate) mod create_apfs_volume; pub(crate) mod create_apfs_volume;
pub(crate) mod create_fstab_entry; pub(crate) mod create_fstab_entry;
pub(crate) mod create_nix_hook_service; pub(crate) mod create_nix_hook_service;
@ -17,7 +16,6 @@ pub(crate) mod set_tmutil_exclusions;
pub(crate) mod unmount_apfs_volume; pub(crate) mod unmount_apfs_volume;
pub use bootstrap_launchctl_service::BootstrapLaunchctlService; pub use bootstrap_launchctl_service::BootstrapLaunchctlService;
pub use configure_remote_building::ConfigureRemoteBuilding;
pub use create_apfs_volume::CreateApfsVolume; pub use create_apfs_volume::CreateApfsVolume;
pub use create_nix_hook_service::CreateNixHookService; pub use create_nix_hook_service::CreateNixHookService;
pub use create_nix_volume::{CreateNixVolume, NIX_VOLUME_MOUNTD_DEST}; pub use create_nix_volume::{CreateNixVolume, NIX_VOLUME_MOUNTD_DEST};

View file

@ -24,7 +24,7 @@ You can manually plan, execute, then revert an [`Action`] like so:
```rust,no_run ```rust,no_run
# async fn wrapper() { # async fn wrapper() {
use lix_installer::action::base::CreateDirectory; use nix_installer::action::base::CreateDirectory;
let mut action = CreateDirectory::plan("/nix", None, None, 0o0755, true).await.unwrap(); let mut action = CreateDirectory::plan("/nix", None, None, 0o0755, true).await.unwrap();
action.try_execute().await.unwrap(); action.try_execute().await.unwrap();
action.try_revert().await.unwrap(); action.try_revert().await.unwrap();
@ -46,7 +46,7 @@ A custom [`Action`] can be created then used in a custom [`Planner`](crate::plan
```rust,no_run ```rust,no_run
use std::{error::Error, collections::HashMap}; use std::{error::Error, collections::HashMap};
use tracing::{Span, span}; use tracing::{Span, span};
use lix_installer::{ use nix_installer::{
InstallPlan, InstallPlan,
settings::{CommonSettings, InstallSettingsError}, settings::{CommonSettings, InstallSettingsError},
planner::{Planner, PlannerError}, planner::{Planner, PlannerError},
@ -152,6 +152,20 @@ impl Planner for MyPlanner {
Ok(settings) Ok(settings)
} }
#[cfg(feature = "diagnostics")]
async fn diagnostic_data(&self) -> Result<nix_installer::diagnostics::DiagnosticData, PlannerError> {
Ok(nix_installer::diagnostics::DiagnosticData::new(
self.common.diagnostic_attribution.clone(),
self.common.diagnostic_endpoint.clone(),
self.typetag_name().into(),
self.configured_settings()
.await?
.into_keys()
.collect::<Vec<_>>(),
self.common.ssl_cert_file.clone(),
)?)
}
} }
# async fn custom_planner_install() -> color_eyre::Result<()> { # async fn custom_planner_install() -> color_eyre::Result<()> {
@ -315,6 +329,12 @@ impl ActionError {
pub fn action_tag(&self) -> &ActionTag { pub fn action_tag(&self) -> &ActionTag {
&self.action_tag &self.action_tag
} }
#[cfg(feature = "diagnostics")]
pub fn diagnostic(&self) -> String {
use crate::diagnostics::ErrorDiagnostic;
self.kind.diagnostic()
}
} }
impl std::fmt::Display for ActionError { impl std::fmt::Display for ActionError {
@ -402,8 +422,6 @@ pub enum ActionErrorKind {
std::path::PathBuf, std::path::PathBuf,
#[source] std::io::Error, #[source] std::io::Error,
), ),
#[error("Getting filesystem metadata for `{0}` on `{1}`")]
GetMetadata(std::path::PathBuf, #[source] std::io::Error),
#[error("Set mode `{0:#o}` on `{1}`")] #[error("Set mode `{0:#o}` on `{1}`")]
SetPermissions(u32, std::path::PathBuf, #[source] std::io::Error), SetPermissions(u32, std::path::PathBuf, #[source] std::io::Error),
#[error("Remove file `{0}`")] #[error("Remove file `{0}`")]
@ -473,6 +491,8 @@ pub enum ActionErrorKind {
command = .command, command = .command,
)] )]
Command { Command {
#[cfg(feature = "diagnostics")]
program: String,
command: String, command: String,
#[source] #[source]
error: std::io::Error, error: std::io::Error,
@ -489,6 +509,8 @@ pub enum ActionErrorKind {
} }
)] )]
CommandOutput { CommandOutput {
#[cfg(feature = "diagnostics")]
program: String,
command: String, command: String,
output: Output, output: Output,
}, },
@ -531,7 +553,7 @@ pub enum ActionErrorKind {
MissingRemoveUserFromGroupCommand, MissingRemoveUserFromGroupCommand,
#[error("\ #[error("\
Could not detect systemd; you may be able to get up and running without systemd with `nix-installer install linux --init none`.\n\ Could not detect systemd; you may be able to get up and running without systemd with `nix-installer install linux --init none`.\n\
See https://git.lix.systems/lix-project/lix-installer#without-systemd-linux-only for documentation on usage and drawbacks.\ See https://github.com/DeterminateSystems/nix-installer#without-systemd-linux-only for documentation on usage and drawbacks.\
")] ")]
SystemdMissing, SystemdMissing,
#[error("`{command}` failed, message: {message}")] #[error("`{command}` failed, message: {message}")]
@ -551,12 +573,16 @@ pub enum ActionErrorKind {
impl ActionErrorKind { impl ActionErrorKind {
pub fn command(command: &tokio::process::Command, error: std::io::Error) -> Self { pub fn command(command: &tokio::process::Command, error: std::io::Error) -> Self {
Self::Command { Self::Command {
#[cfg(feature = "diagnostics")]
program: command.as_std().get_program().to_string_lossy().into(),
command: format!("{:?}", command.as_std()), command: format!("{:?}", command.as_std()),
error, error,
} }
} }
pub fn command_output(command: &tokio::process::Command, output: std::process::Output) -> Self { pub fn command_output(command: &tokio::process::Command, output: std::process::Output) -> Self {
Self::CommandOutput { Self::CommandOutput {
#[cfg(feature = "diagnostics")]
program: command.as_std().get_program().to_string_lossy().into(),
command: format!("{:?}", command.as_std()), command: format!("{:?}", command.as_std()),
output, output,
} }
@ -574,3 +600,60 @@ impl HasExpectedErrors for ActionErrorKind {
} }
} }
} }
#[cfg(feature = "diagnostics")]
impl crate::diagnostics::ErrorDiagnostic for ActionErrorKind {
fn diagnostic(&self) -> String {
let static_str: &'static str = (self).into();
let context = match self {
Self::Child(child) => vec![child.diagnostic()],
Self::MultipleChildren(children) => {
children.iter().map(|child| child.diagnostic()).collect()
},
Self::Read(path, _)
| Self::Open(path, _)
| Self::Write(path, _)
| Self::Flush(path, _)
| Self::SetPermissions(_, path, _)
| Self::GettingMetadata(path, _)
| Self::CreateDirectory(path, _)
| Self::PathWasNotFile(path)
| Self::Remove(path, _) => {
vec![path.to_string_lossy().to_string()]
},
Self::Rename(first_path, second_path, _)
| Self::Copy(first_path, second_path, _)
| Self::Symlink(first_path, second_path, _) => {
vec![
first_path.to_string_lossy().to_string(),
second_path.to_string_lossy().to_string(),
]
},
Self::NoGroup(name) | Self::NoUser(name) => {
vec![name.clone()]
},
Self::Command {
program,
command: _,
error: _,
}
| Self::CommandOutput {
program,
command: _,
output: _,
} => {
vec![program.clone()]
},
_ => vec![],
};
format!(
"{}({})",
static_str,
context
.iter()
.map(|v| format!("\"{v}\""))
.collect::<Vec<_>>()
.join(", ")
)
}
}

View file

@ -1,7 +1,7 @@
use std::{io::IsTerminal, process::ExitCode}; use std::{io::IsTerminal, process::ExitCode};
use clap::Parser; use clap::Parser;
use lix_installer::cli::CommandExecute; use nix_installer::cli::CommandExecute;
#[tokio::main] #[tokio::main]
async fn main() -> eyre::Result<ExitCode> { async fn main() -> eyre::Result<ExitCode> {
@ -17,7 +17,7 @@ async fn main() -> eyre::Result<ExitCode> {
}) })
.install()?; .install()?;
let cli = lix_installer::cli::NixInstallerCli::parse(); let cli = nix_installer::cli::NixInstallerCli::parse();
cli.instrumentation.setup()?; cli.instrumentation.setup()?;

View file

@ -16,7 +16,6 @@ pub enum PromptChoice {
// The below method was adopted from Rustup at https://github.com/rust-lang/rustup/blob/3331f34c01474bf216c99a1b1706725708833de1/src/cli/term2.rs#L37 // The below method was adopted from Rustup at https://github.com/rust-lang/rustup/blob/3331f34c01474bf216c99a1b1706725708833de1/src/cli/term2.rs#L37
pub(crate) async fn prompt( pub(crate) async fn prompt(
question: impl AsRef<str>, question: impl AsRef<str>,
prompt_text: impl AsRef<str>,
default: PromptChoice, default: PromptChoice,
currently_explaining: bool, currently_explaining: bool,
) -> eyre::Result<PromptChoice> { ) -> eyre::Result<PromptChoice> {
@ -30,7 +29,7 @@ pub(crate) async fn prompt(
{are_you_sure} ({yes}/{no}{maybe_explain}): \ {are_you_sure} ({yes}/{no}{maybe_explain}): \
", ",
question = question.as_ref(), question = question.as_ref(),
are_you_sure = prompt_text.as_ref().bold(), are_you_sure = "Proceed?".bold(),
no = if default == PromptChoice::No { no = if default == PromptChoice::No {
"[N]o" "[N]o"
} else { } else {

View file

@ -20,7 +20,7 @@ pub trait CommandExecute {
} }
/** /**
The Determinate Nix installer (lix variant) The Determinate Nix installer
A fast, friendly, and reliable tool to help you use Nix with Flakes everywhere. A fast, friendly, and reliable tool to help you use Nix with Flakes everywhere.
*/ */
@ -92,7 +92,7 @@ pub fn ensure_root() -> eyre::Result<()> {
if !is_root() { if !is_root() {
eprintln!( eprintln!(
"{}", "{}",
"`lix-installer` needs to run as `root`, attempting to escalate now via `sudo`..." "`nix-installer` needs to run as `root`, attempting to escalate now via `sudo`..."
.yellow() .yellow()
.dimmed() .dimmed()
); );
@ -122,6 +122,13 @@ pub fn ensure_root() -> eyre::Result<()> {
} }
} }
#[cfg(feature = "diagnostics")]
if is_ci::cached() {
// Normally `sudo` would erase those envs, so we detect and pass that along specifically to avoid having to pass around
// a bunch of environment variables
env_list.push("NIX_INSTALLER_CI=1".to_string());
}
if !env_list.is_empty() { if !env_list.is_empty() {
arg_vec_cstring arg_vec_cstring
.push(CString::new("env").wrap_err("Building a `env` argument for `sudo`")?); .push(CString::new("env").wrap_err("Building a `env` argument for `sudo`")?);

View file

@ -1,5 +1,4 @@
use std::{ use std::{
io::{stdout, Write},
os::unix::prelude::PermissionsExt, os::unix::prelude::PermissionsExt,
path::{Path, PathBuf}, path::{Path, PathBuf},
process::ExitCode, process::ExitCode,
@ -26,13 +25,13 @@ use color_eyre::{
use owo_colors::OwoColorize; use owo_colors::OwoColorize;
const EXISTING_INCOMPATIBLE_PLAN_GUIDANCE: &str = "\ const EXISTING_INCOMPATIBLE_PLAN_GUIDANCE: &str = "\
If you are trying to upgrade Lix, try running `sudo -i nix upgrade-nix` instead.\n\ If you are trying to upgrade Nix, try running `sudo -i nix upgrade-nix` instead.\n\
If you are trying to install Lix over an existing install (from an incompatible `nix-installer` install), try running `/nix/nix-installer uninstall` then try to install again.\n\ If you are trying to install Nix over an existing install (from an incompatible `nix-installer` install), try running `/nix/nix-installer uninstall` then try to install again.\n\
If you are using `lix-installer` in an automated curing process and seeing this message, consider pinning the version you use via https://git.lix.systems/lix-project/lix-installer#accessing-other-versions.\ If you are using `nix-installer` in an automated curing process and seeing this message, consider pinning the version you use via https://github.com/DeterminateSystems/nix-installer#accessing-other-versions.\
"; ";
/** /**
Install Lix-Nix using a planner Install Nix using a planner
By default, an appropriate planner is heuristically determined based on the system. By default, an appropriate planner is heuristically determined based on the system.
@ -81,22 +80,6 @@ impl CommandExecute for Install {
explain, explain,
} = self; } = self;
// Get our terminal object, explaining what to do if it's not there.
let term =
term::terminfo::TerminfoTerminal::new(stdout());
if term.is_none() && !no_confirm {
eprintln!(
"{}",
format!("\
\n\
Couldn't figure out which terminal you're using -- check the value of the $TERM variable.\n\n\
If you're using an interactive terminal, it's probably safe to set TERM to \"xterm\",\n\
by e.g. running 'export TERM=xterm'.\n\
").red()
);
interaction::clean_exit_with_message("Couldn't get the terminal! Aborting.").await;
}
ensure_root()?; ensure_root()?;
let existing_receipt: Option<InstallPlan> = match Path::new(RECEIPT_LOCATION).exists() { let existing_receipt: Option<InstallPlan> = match Path::new(RECEIPT_LOCATION).exists() {
@ -107,7 +90,7 @@ impl CommandExecute for Install {
.wrap_err("Reading plan")?; .wrap_err("Reading plan")?;
Some( Some(
serde_json::from_str(&install_plan_string).wrap_err_with(|| { serde_json::from_str(&install_plan_string).wrap_err_with(|| {
format!("Unable to parse existing receipt `{RECEIPT_LOCATION}`, it may be from an incompatible version of `nix-installer` or `lix-installer`. Try running `/nix/nix-installer uninstall`, then installing again.") format!("Unable to parse existing receipt `{RECEIPT_LOCATION}`, it may be from an incompatible version of `nix-installer`. Try running `/nix/nix-installer uninstall`, then installing again.")
})?, })?,
) )
}, },
@ -116,7 +99,7 @@ impl CommandExecute for Install {
let uninstall_command = match Path::new("/nix/nix-installer").exists() { let uninstall_command = match Path::new("/nix/nix-installer").exists() {
true => "/nix/nix-installer uninstall".into(), true => "/nix/nix-installer uninstall".into(),
false => format!("curl --proto '=https' --tlsv1.2 -sSf -L https://install.lix.systems/lix/tag/v{} | sh -s -- uninstall", env!("CARGO_PKG_VERSION")), false => format!("curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix/tag/v{} | sh -s -- uninstall", env!("CARGO_PKG_VERSION")),
}; };
let mut install_plan = match (planner, plan) { let mut install_plan = match (planner, plan) {
@ -170,39 +153,7 @@ impl CommandExecute for Install {
serde_json::from_str(&install_plan_string)? serde_json::from_str(&install_plan_string)?
}, },
(None, None) => { (None, None) => {
let mut settings_to_apply = settings.clone(); let builtin_planner = BuiltinPlanner::from_common_settings(settings.clone())
if !no_confirm {
// Say hello.
let mut term = term.expect("Internal consistency: term should have been None checked already!");
let hello_message = format!("{}{}", "\n\nWelcome to the Lix installer!".bold(), " Just a couple of quick questions.\n\n");
term.write_all(hello_message.as_bytes())?;
term.flush()?;
// Ask about flakes.
match interaction::prompt(
"Flakes are an experimental feature, but widely used in the community.\nYou can change this later in `/etc/nix/nix.conf`.",
"Enable flakes?",
PromptChoice::Yes,
true,
)
.await?
{
PromptChoice::Yes => settings_to_apply.enable_flakes = true,
PromptChoice::Explain => panic!("This prompt has no explanation."),
PromptChoice::No => settings_to_apply.enable_flakes = false,
}
// Notify the user about the nix command.
let nixcmd_message = format!("{}{}{}{}{}",
"\nQUICK NOTE:".bold().yellow(), " we've enabled the experimental", " nix ".bold(), "command for you!\n",
"Be aware that commands starting with `nix ` such as `nix build` may change syntax.\n\n".green());
term.write_all(nixcmd_message.as_bytes())?;
term.flush()?;
}
let builtin_planner = BuiltinPlanner::from_common_settings(settings_to_apply)
.await .await
.map_err(|e| eyre::eyre!(e))?; .map_err(|e| eyre::eyre!(e))?;
@ -268,7 +219,6 @@ impl CommandExecute for Install {
.describe_install(currently_explaining) .describe_install(currently_explaining)
.await .await
.map_err(|e| eyre!(e))?, .map_err(|e| eyre!(e))?,
"Proceed?",
PromptChoice::Yes, PromptChoice::Yes,
currently_explaining, currently_explaining,
) )
@ -309,7 +259,6 @@ impl CommandExecute for Install {
.describe_uninstall(currently_explaining) .describe_uninstall(currently_explaining)
.await .await
.map_err(|e| eyre!(e))?, .map_err(|e| eyre!(e))?,
"Proceed?",
PromptChoice::Yes, PromptChoice::Yes,
currently_explaining, currently_explaining,
) )

View file

@ -38,23 +38,9 @@ impl CommandExecute for Repair {
if let Err(err) = reconfigure.try_execute().await { if let Err(err) = reconfigure.try_execute().await {
println!("{:#?}", err); println!("{:#?}", err);
return Ok(ExitCode::FAILURE); Ok(ExitCode::FAILURE)
} else {
Ok(ExitCode::SUCCESS)
} }
// 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 {
println!("{:#?}", err);
return Ok(ExitCode::FAILURE);
}
}
Ok(ExitCode::SUCCESS)
} }
} }

View file

@ -129,7 +129,7 @@ impl CommandExecute for Uninstall {
format!( format!(
"\ "\
Unable to parse plan, this plan was created by `nix-installer` version `{plan_version}`, this is `nix-installer` version `{current_version}`\n\ Unable to parse plan, this plan was created by `nix-installer` version `{plan_version}`, this is `nix-installer` version `{current_version}`\n\
To uninstall, either run `/nix/nix-installer uninstall` or `curl --proto '=https' --tlsv1.2 -sSf -L https://install.lix.systems/lix/tag/v{plan_version} | sh -s -- uninstall`\ To uninstall, either run `/nix/nix-installer uninstall` or `curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix/tag/{plan_version} | sh -s -- uninstall`\
").red().to_string() ").red().to_string()
}); });
}, },
@ -147,7 +147,7 @@ impl CommandExecute for Uninstall {
\n\ \n\
Found existing plan in `{RECEIPT_LOCATION}` which was created by a version incompatible `nix-installer`.\n\ Found existing plan in `{RECEIPT_LOCATION}` which was created by a version incompatible `nix-installer`.\n\
\n \n
To uninstall, either run `/nix/nix-installer uninstall` or `curl --proto '=https' --tlsv1.2 -sSf -L https://install.lix.systems/lix/tag/v${version} | sh -s -- uninstall`\n\ To uninstall, either run `/nix/nix-installer uninstall` or `curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix/tag/${version} | sh -s -- uninstall`\n\
\n\ \n\
").red() ").red()
); );
@ -169,7 +169,6 @@ impl CommandExecute for Uninstall {
plan.describe_uninstall(currently_explaining) plan.describe_uninstall(currently_explaining)
.await .await
.map_err(|e| eyre!(e))?, .map_err(|e| eyre!(e))?,
"Proceed?",
PromptChoice::Yes, PromptChoice::Yes,
currently_explaining, currently_explaining,
) )

277
src/diagnostics.rs Normal file
View file

@ -0,0 +1,277 @@
/*! Diagnostic reporting functionality
When enabled with the `diagnostics` feature (default) this module provides automated install success/failure reporting to an endpoint.
That endpoint can be a URL such as `https://our.project.org/nix-installer/diagnostics` or `file:///home/$USER/diagnostic.json` which receives a [`DiagnosticReport`] in JSON format.
*/
use std::{path::PathBuf, time::Duration};
use os_release::OsRelease;
use reqwest::Url;
use crate::{
action::ActionError, parse_ssl_cert, planner::PlannerError, settings::InstallSettingsError,
CertificateError, NixInstallerError,
};
/// The static of an action attempt
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub enum DiagnosticStatus {
Cancelled,
Success,
Pending,
Failure,
}
/// The action attempted
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, Copy)]
pub enum DiagnosticAction {
Install,
Uninstall,
}
/// A report sent to an endpoint
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct DiagnosticReport {
pub attribution: Option<String>,
pub version: String,
pub planner: String,
pub configured_settings: Vec<String>,
pub os_name: String,
pub os_version: String,
pub triple: String,
pub is_ci: bool,
pub action: DiagnosticAction,
pub status: DiagnosticStatus,
/// Generally this includes the [`strum::IntoStaticStr`] representation of the error, we take special care not to include parameters of the error (which may include secrets)
pub failure_chain: Option<Vec<String>>,
}
/// A preparation of data to be sent to the `endpoint`.
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, Default)]
pub struct DiagnosticData {
attribution: Option<String>,
version: String,
planner: String,
configured_settings: Vec<String>,
os_name: String,
os_version: String,
triple: String,
is_ci: bool,
endpoint: Option<Url>,
ssl_cert_file: Option<PathBuf>,
/// Generally this includes the [`strum::IntoStaticStr`] representation of the error, we take special care not to include parameters of the error (which may include secrets)
failure_chain: Option<Vec<String>>,
}
impl DiagnosticData {
pub fn new(
attribution: Option<String>,
endpoint: Option<String>,
planner: String,
configured_settings: Vec<String>,
ssl_cert_file: Option<PathBuf>,
) -> Result<Self, DiagnosticError> {
let endpoint = match endpoint {
Some(endpoint) => diagnostic_endpoint_parser(&endpoint)?,
None => None,
};
let (os_name, os_version) = match OsRelease::new() {
Ok(os_release) => (os_release.name, os_release.version),
Err(_) => ("unknown".into(), "unknown".into()),
};
let is_ci = is_ci::cached()
|| std::env::var("NIX_INSTALLER_CI").unwrap_or_else(|_| "0".into()) == "1";
Ok(Self {
attribution,
endpoint,
version: env!("CARGO_PKG_VERSION").into(),
planner,
configured_settings,
os_name,
os_version,
triple: target_lexicon::HOST.to_string(),
is_ci,
ssl_cert_file: ssl_cert_file.and_then(|v| v.canonicalize().ok()),
failure_chain: None,
})
}
pub fn failure(mut self, err: &NixInstallerError) -> Self {
let mut failure_chain = vec![];
let diagnostic = err.diagnostic();
failure_chain.push(diagnostic);
let mut walker: &dyn std::error::Error = &err;
while let Some(source) = walker.source() {
if let Some(downcasted) = source.downcast_ref::<ActionError>() {
let downcasted_diagnostic = downcasted.kind().diagnostic();
failure_chain.push(downcasted_diagnostic);
}
if let Some(downcasted) = source.downcast_ref::<Box<ActionError>>() {
let downcasted_diagnostic = downcasted.kind().diagnostic();
failure_chain.push(downcasted_diagnostic);
}
if let Some(downcasted) = source.downcast_ref::<PlannerError>() {
let downcasted_diagnostic = downcasted.diagnostic();
failure_chain.push(downcasted_diagnostic);
}
if let Some(downcasted) = source.downcast_ref::<InstallSettingsError>() {
let downcasted_diagnostic = downcasted.diagnostic();
failure_chain.push(downcasted_diagnostic);
}
if let Some(downcasted) = source.downcast_ref::<DiagnosticError>() {
let downcasted_diagnostic = downcasted.diagnostic();
failure_chain.push(downcasted_diagnostic);
}
walker = source;
}
self.failure_chain = Some(failure_chain);
self
}
pub fn report(&self, action: DiagnosticAction, status: DiagnosticStatus) -> DiagnosticReport {
let Self {
attribution,
version,
planner,
configured_settings,
os_name,
os_version,
triple,
is_ci,
endpoint: _,
ssl_cert_file: _,
failure_chain,
} = self;
DiagnosticReport {
attribution: attribution.clone(),
version: version.clone(),
planner: planner.clone(),
configured_settings: configured_settings.clone(),
os_name: os_name.clone(),
os_version: os_version.clone(),
triple: triple.clone(),
is_ci: *is_ci,
action,
status,
failure_chain: failure_chain.clone(),
}
}
#[tracing::instrument(level = "debug", skip_all)]
pub async fn send(
self,
action: DiagnosticAction,
status: DiagnosticStatus,
) -> Result<(), DiagnosticError> {
let serialized = serde_json::to_string_pretty(&self.report(action, status))?;
let endpoint = match self.endpoint {
Some(endpoint) => endpoint,
None => return Ok(()),
};
match endpoint.scheme() {
"https" | "http" => {
tracing::debug!("Sending diagnostic to `{endpoint}`");
let mut buildable_client = reqwest::Client::builder();
if let Some(ssl_cert_file) = &self.ssl_cert_file {
let ssl_cert = parse_ssl_cert(ssl_cert_file).await.ok();
if let Some(ssl_cert) = ssl_cert {
buildable_client = buildable_client.add_root_certificate(ssl_cert);
}
}
let client = buildable_client.build().map_err(DiagnosticError::Reqwest)?;
let res = client
.post(endpoint.clone())
.body(serialized)
.header("Content-Type", "application/json")
.timeout(Duration::from_millis(3000))
.send()
.await;
if let Err(_err) = res {
tracing::info!("Failed to send diagnostic to `{endpoint}`, continuing")
}
},
"file" => {
let path = endpoint.path();
tracing::debug!("Writing diagnostic to `{path}`");
let res = tokio::fs::write(path, serialized).await;
if let Err(_err) = res {
tracing::info!("Failed to send diagnostic to `{path}`, continuing")
}
},
_ => return Err(DiagnosticError::UnknownUrlScheme),
};
Ok(())
}
}
#[non_exhaustive]
#[derive(thiserror::Error, Debug, strum::IntoStaticStr)]
pub enum DiagnosticError {
#[error("Unknown url scheme")]
UnknownUrlScheme,
#[error("Request error")]
Reqwest(
#[from]
#[source]
reqwest::Error,
),
/// Parsing URL
#[error("Parsing URL")]
Parse(
#[source]
#[from]
url::ParseError,
),
#[error("Write path `{0}`")]
Write(std::path::PathBuf, #[source] std::io::Error),
#[error("Serializing receipt")]
Serializing(
#[from]
#[source]
serde_json::Error,
),
#[error(transparent)]
Certificate(#[from] CertificateError),
}
pub trait ErrorDiagnostic {
fn diagnostic(&self) -> String;
}
impl ErrorDiagnostic for DiagnosticError {
fn diagnostic(&self) -> String {
let static_str: &'static str = (self).into();
static_str.to_string()
}
}
pub fn diagnostic_endpoint_parser(input: &str) -> Result<Option<Url>, DiagnosticError> {
match Url::parse(input) {
Ok(v) => match v.scheme() {
"https" | "http" | "file" => Ok(Some(v)),
_ => Err(DiagnosticError::UnknownUrlScheme),
},
Err(url_error) if url_error == url::ParseError::RelativeUrlWithoutBase => {
match Url::parse(&format!("file://{input}")) {
Ok(v) => Ok(Some(v)),
Err(file_error) => Err(file_error)?,
}
},
Err(url_error) => Err(url_error)?,
}
}
pub fn diagnostic_endpoint_validator(input: &str) -> Result<String, DiagnosticError> {
let _ = diagnostic_endpoint_parser(input)?;
Ok(input.to_string())
}

View file

@ -74,6 +74,14 @@ pub enum NixInstallerError {
InstallSettingsError, InstallSettingsError,
), ),
#[cfg(feature = "diagnostics")]
/// Diagnostic error
#[error("Diagnostic error")]
Diagnostic(
#[from]
#[source]
crate::diagnostics::DiagnosticError,
),
/// Could not parse the value as a version requirement in order to ensure it's compatible /// Could not parse the value as a version requirement in order to ensure it's compatible
#[error("Could not parse `{0}` as a version requirement in order to ensure it's compatible")] #[error("Could not parse `{0}` as a version requirement in order to ensure it's compatible")]
InvalidVersionRequirement(String, semver::Error), InvalidVersionRequirement(String, semver::Error),
@ -107,6 +115,36 @@ impl HasExpectedErrors for NixInstallerError {
this @ NixInstallerError::IncompatibleVersion { binary: _, plan: _ } => { this @ NixInstallerError::IncompatibleVersion { binary: _, plan: _ } => {
Some(Box::new(this)) Some(Box::new(this))
}, },
#[cfg(feature = "diagnostics")]
NixInstallerError::Diagnostic(_) => None,
} }
} }
} }
#[cfg(feature = "diagnostics")]
impl crate::diagnostics::ErrorDiagnostic for NixInstallerError {
fn diagnostic(&self) -> String {
let static_str: &'static str = (self).into();
let context = match self {
Self::SelfTest(self_tests) => self_tests
.iter()
.map(|self_test| self_test.diagnostic())
.collect::<Vec<_>>(),
Self::Action(action_error) => vec![action_error.diagnostic()],
Self::ActionRevert(action_errors) => action_errors
.iter()
.map(|action_error| action_error.diagnostic())
.collect(),
_ => vec![],
};
format!(
"{}({})",
static_str,
context
.iter()
.map(|v| format!("\"{v}\""))
.collect::<Vec<_>>()
.join(", ")
)
}
}

View file

@ -1,6 +1,6 @@
/*! The [Lix](https://lix.systems) Installer /*! The Determinate [Nix](https://github.com/NixOS/nix) Installer
`lix-installer` breaks down into three main concepts: `nix-installer` breaks down into three main concepts:
* [`Action`]: An executable or revertable step, possibly orchestrating sub-[`Action`]s using things * [`Action`]: An executable or revertable step, possibly orchestrating sub-[`Action`]s using things
like [`JoinSet`](tokio::task::JoinSet)s. like [`JoinSet`](tokio::task::JoinSet)s.
@ -10,12 +10,12 @@
It is possible to create custom [`Action`]s and [`Planner`](planner::Planner)s to suit the needs of your project, team, or organization. It is possible to create custom [`Action`]s and [`Planner`](planner::Planner)s to suit the needs of your project, team, or organization.
In the simplest case, `lix-installer` can be asked to determine a default plan for the platform and install In the simplest case, `nix-installer` can be asked to determine a default plan for the platform and install
it, uninstalling if anything goes wrong: it, uninstalling if anything goes wrong:
```rust,no_run ```rust,no_run
use std::error::Error; use std::error::Error;
use lix_installer::InstallPlan; use nix_installer::InstallPlan;
# async fn default_install() -> color_eyre::Result<()> { # async fn default_install() -> color_eyre::Result<()> {
let mut plan = InstallPlan::default().await?; let mut plan = InstallPlan::default().await?;
@ -38,7 +38,7 @@ Sometimes choosing a specific planner is desired:
```rust,no_run ```rust,no_run
use std::error::Error; use std::error::Error;
use lix_installer::{InstallPlan, planner::Planner}; use nix_installer::{InstallPlan, planner::Planner};
# async fn chosen_planner_install() -> color_eyre::Result<()> { # async fn chosen_planner_install() -> color_eyre::Result<()> {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
@ -72,6 +72,8 @@ match plan.install(None).await {
pub mod action; pub mod action;
#[cfg(feature = "cli")] #[cfg(feature = "cli")]
pub mod cli; pub mod cli;
#[cfg(feature = "diagnostics")]
pub mod diagnostics;
mod error; mod error;
mod os; mod os;
mod plan; mod plan;

View file

@ -22,12 +22,18 @@ pub struct InstallPlan {
pub(crate) actions: Vec<StatefulAction<Box<dyn Action>>>, pub(crate) actions: Vec<StatefulAction<Box<dyn Action>>>,
pub(crate) planner: Box<dyn Planner>, pub(crate) planner: Box<dyn Planner>,
#[cfg(feature = "diagnostics")]
pub(crate) diagnostic_data: Option<crate::diagnostics::DiagnosticData>,
} }
impl InstallPlan { impl InstallPlan {
pub async fn default() -> Result<Self, NixInstallerError> { pub async fn default() -> Result<Self, NixInstallerError> {
let planner = BuiltinPlanner::default().await?; let planner = BuiltinPlanner::default().await?;
#[cfg(feature = "diagnostics")]
let diagnostic_data = Some(planner.diagnostic_data().await?);
let planner = planner.boxed(); let planner = planner.boxed();
let actions = planner.plan().await?; let actions = planner.plan().await?;
@ -35,6 +41,8 @@ impl InstallPlan {
planner, planner,
actions, actions,
version: current_version()?, version: current_version()?,
#[cfg(feature = "diagnostics")]
diagnostic_data,
}) })
} }
@ -42,6 +50,9 @@ impl InstallPlan {
where where
P: Planner + 'static, P: Planner + 'static,
{ {
#[cfg(feature = "diagnostics")]
let diagnostic_data = Some(planner.diagnostic_data().await?);
// Some Action `plan` calls may fail if we don't do these checks // Some Action `plan` calls may fail if we don't do these checks
planner.pre_install_check().await?; planner.pre_install_check().await?;
@ -50,6 +61,8 @@ impl InstallPlan {
planner: planner.boxed(), planner: planner.boxed(),
actions, actions,
version: current_version()?, version: current_version()?,
#[cfg(feature = "diagnostics")]
diagnostic_data,
}) })
} }
@ -88,7 +101,7 @@ impl InstallPlan {
let buf = format!( let buf = format!(
"\ "\
Lix install plan (v{version})\n\ Nix install plan (v{version})\n\
Planner: {planner}{maybe_default_setting_note}\n\ Planner: {planner}{maybe_default_setting_note}\n\
\n\ \n\
{maybe_plan_settings}\ {maybe_plan_settings}\
@ -160,6 +173,17 @@ impl InstallPlan {
tracing::error!("Error saving receipt: {:?}", err); tracing::error!("Error saving receipt: {:?}", err);
} }
#[cfg(feature = "diagnostics")]
if let Some(diagnostic_data) = &self.diagnostic_data {
diagnostic_data
.clone()
.send(
crate::diagnostics::DiagnosticAction::Install,
crate::diagnostics::DiagnosticStatus::Cancelled,
)
.await?;
}
return Err(NixInstallerError::Cancelled); return Err(NixInstallerError::Cancelled);
} }
} }
@ -170,6 +194,17 @@ impl InstallPlan {
tracing::error!("Error saving receipt: {:?}", err); tracing::error!("Error saving receipt: {:?}", err);
} }
let err = NixInstallerError::Action(err); let err = NixInstallerError::Action(err);
#[cfg(feature = "diagnostics")]
if let Some(diagnostic_data) = &self.diagnostic_data {
diagnostic_data
.clone()
.failure(&err)
.send(
crate::diagnostics::DiagnosticAction::Install,
crate::diagnostics::DiagnosticStatus::Failure,
)
.await?;
}
return Err(err); return Err(err);
} }
@ -181,7 +216,30 @@ impl InstallPlan {
.await .await
.map_err(NixInstallerError::SelfTest) .map_err(NixInstallerError::SelfTest)
{ {
#[cfg(feature = "diagnostics")]
if let Some(diagnostic_data) = &self.diagnostic_data {
diagnostic_data
.clone()
.failure(&err)
.send(
crate::diagnostics::DiagnosticAction::Install,
crate::diagnostics::DiagnosticStatus::Failure,
)
.await?;
}
tracing::warn!("{err:?}") tracing::warn!("{err:?}")
} else {
#[cfg(feature = "diagnostics")]
if let Some(diagnostic_data) = &self.diagnostic_data {
diagnostic_data
.clone()
.send(
crate::diagnostics::DiagnosticAction::Install,
crate::diagnostics::DiagnosticStatus::Success,
)
.await?;
}
} }
Ok(()) Ok(())
@ -212,7 +270,7 @@ impl InstallPlan {
let buf = format!( let buf = format!(
"\ "\
Lix uninstall plan (v{version})\n\ Nix uninstall plan (v{version})\n\
\n\ \n\
Planner: {planner}{maybe_default_setting_note}\n\ Planner: {planner}{maybe_default_setting_note}\n\
\n\ \n\
@ -287,6 +345,16 @@ impl InstallPlan {
tracing::error!("Error saving receipt: {:?}", err); tracing::error!("Error saving receipt: {:?}", err);
} }
#[cfg(feature = "diagnostics")]
if let Some(diagnostic_data) = &self.diagnostic_data {
diagnostic_data
.clone()
.send(
crate::diagnostics::DiagnosticAction::Uninstall,
crate::diagnostics::DiagnosticStatus::Cancelled,
)
.await?;
}
return Err(NixInstallerError::Cancelled); return Err(NixInstallerError::Cancelled);
} }
} }
@ -298,9 +366,32 @@ impl InstallPlan {
} }
if errors.is_empty() { if errors.is_empty() {
#[cfg(feature = "diagnostics")]
if let Some(diagnostic_data) = &self.diagnostic_data {
diagnostic_data
.clone()
.send(
crate::diagnostics::DiagnosticAction::Uninstall,
crate::diagnostics::DiagnosticStatus::Success,
)
.await?;
}
Ok(()) Ok(())
} else { } else {
let error = NixInstallerError::ActionRevert(errors); let error = NixInstallerError::ActionRevert(errors);
#[cfg(feature = "diagnostics")]
if let Some(diagnostic_data) = &self.diagnostic_data {
diagnostic_data
.clone()
.failure(&error)
.send(
crate::diagnostics::DiagnosticAction::Uninstall,
crate::diagnostics::DiagnosticStatus::Failure,
)
.await?;
}
Err(error) Err(error)
} }
} }

View file

@ -126,6 +126,19 @@ impl Planner for Linux {
Ok(settings) Ok(settings)
} }
#[cfg(feature = "diagnostics")]
async fn diagnostic_data(&self) -> Result<crate::diagnostics::DiagnosticData, PlannerError> {
Ok(crate::diagnostics::DiagnosticData::new(
self.settings.diagnostic_attribution.clone(),
self.settings.diagnostic_endpoint.clone(),
self.typetag_name().into(),
self.configured_settings()
.await?
.into_keys()
.collect::<Vec<_>>(),
self.settings.ssl_cert_file.clone(),
)?)
}
async fn pre_uninstall_check(&self) -> Result<(), PlannerError> { async fn pre_uninstall_check(&self) -> Result<(), PlannerError> {
check_not_wsl1()?; check_not_wsl1()?;
@ -207,9 +220,9 @@ pub(crate) async fn check_nix_not_already_installed() -> Result<(), PlannerError
pub(crate) fn check_systemd_active() -> Result<(), PlannerError> { pub(crate) fn check_systemd_active() -> Result<(), PlannerError> {
if !Path::new("/run/systemd/system").exists() { if !Path::new("/run/systemd/system").exists() {
if std::env::var("WSL_DISTRO_NAME").is_ok() { if std::env::var("WSL_DISTRO_NAME").is_ok() {
return Err(LinuxErrorKind::Wsl2SystemdNotActive.into()); return Err(LinuxErrorKind::Wsl2SystemdNotActive)?;
} else { } else {
return Err(LinuxErrorKind::SystemdNotActive.into()); return Err(LinuxErrorKind::SystemdNotActive)?;
} }
} }

View file

@ -12,9 +12,7 @@ use crate::{
action::{ action::{
base::RemoveDirectory, base::RemoveDirectory,
common::{ConfigureInitService, ConfigureNix, CreateUsersAndGroups, ProvisionNix}, common::{ConfigureInitService, ConfigureNix, CreateUsersAndGroups, ProvisionNix},
macos::{ macos::{CreateNixHookService, CreateNixVolume, SetTmutilExclusions},
ConfigureRemoteBuilding, CreateNixHookService, CreateNixVolume, SetTmutilExclusions,
},
StatefulAction, StatefulAction,
}, },
execute_command, execute_command,
@ -135,7 +133,7 @@ impl Planner for Macos {
CreateNixVolume::plan( CreateNixVolume::plan(
root_disk.unwrap(), /* We just ensured it was populated */ root_disk.unwrap(), /* We just ensured it was populated */
self.volume_label.clone(), self.volume_label.clone(),
self.case_sensitive, false,
encrypt, encrypt,
) )
.await .await
@ -168,12 +166,6 @@ impl Planner for Macos {
.map_err(PlannerError::Action)? .map_err(PlannerError::Action)?
.boxed(), .boxed(),
); );
plan.push(
ConfigureRemoteBuilding::plan()
.await
.map_err(PlannerError::Action)?
.boxed(),
);
if self.settings.modify_profile { if self.settings.modify_profile {
plan.push( plan.push(
@ -238,6 +230,20 @@ impl Planner for Macos {
Ok(settings) Ok(settings)
} }
#[cfg(feature = "diagnostics")]
async fn diagnostic_data(&self) -> Result<crate::diagnostics::DiagnosticData, PlannerError> {
Ok(crate::diagnostics::DiagnosticData::new(
self.settings.diagnostic_attribution.clone(),
self.settings.diagnostic_endpoint.clone(),
self.typetag_name().into(),
self.configured_settings()
.await?
.into_keys()
.collect::<Vec<_>>(),
self.settings.ssl_cert_file.clone(),
)?)
}
async fn pre_uninstall_check(&self) -> Result<(), PlannerError> { async fn pre_uninstall_check(&self) -> Result<(), PlannerError> {
check_nix_darwin_not_installed().await?; check_nix_darwin_not_installed().await?;

View file

@ -12,7 +12,7 @@ A custom [`Planner`] can be created:
```rust,no_run ```rust,no_run
use std::{error::Error, collections::HashMap}; use std::{error::Error, collections::HashMap};
use lix_installer::{ use nix_installer::{
InstallPlan, InstallPlan,
settings::{CommonSettings, InstallSettingsError}, settings::{CommonSettings, InstallSettingsError},
planner::{Planner, PlannerError}, planner::{Planner, PlannerError},
@ -69,6 +69,19 @@ impl Planner for MyPlanner {
Ok(settings) Ok(settings)
} }
#[cfg(feature = "diagnostics")]
async fn diagnostic_data(&self) -> Result<nix_installer::diagnostics::DiagnosticData, PlannerError> {
Ok(nix_installer::diagnostics::DiagnosticData::new(
self.common.diagnostic_attribution.clone(),
self.common.diagnostic_endpoint.clone(),
self.typetag_name().into(),
self.configured_settings()
.await?
.into_keys()
.collect::<Vec<_>>(),
self.common.ssl_cert_file.clone(),
)?)
}
} }
# async fn custom_planner_install() -> color_eyre::Result<()> { # async fn custom_planner_install() -> color_eyre::Result<()> {
@ -141,6 +154,9 @@ pub trait Planner: std::fmt::Debug + Send + Sync + dyn_clone::DynClone {
async fn pre_install_check(&self) -> Result<(), PlannerError> { async fn pre_install_check(&self) -> Result<(), PlannerError> {
Ok(()) Ok(())
} }
#[cfg(feature = "diagnostics")]
async fn diagnostic_data(&self) -> Result<crate::diagnostics::DiagnosticData, PlannerError>;
} }
dyn_clone::clone_trait_object!(Planner); dyn_clone::clone_trait_object!(Planner);
@ -293,6 +309,21 @@ impl BuiltinPlanner {
} }
} }
#[cfg(feature = "diagnostics")]
pub async fn diagnostic_data(
&self,
) -> Result<crate::diagnostics::DiagnosticData, PlannerError> {
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,
}
}
} }
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)] #[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)]
@ -361,8 +392,8 @@ impl Default for FishShellProfileLocations {
#[non_exhaustive] #[non_exhaustive]
#[derive(thiserror::Error, Debug, strum::IntoStaticStr)] #[derive(thiserror::Error, Debug, strum::IntoStaticStr)]
pub enum PlannerError { pub enum PlannerError {
/// `lix-installer` does not have a default planner for the target architecture right now /// `nix-installer` does not have a default planner for the target architecture right now
#[error("`lix-installer` does not have a default planner for the `{0}` architecture right now, pass a specific archetype")] #[error("`nix-installer` does not have a default planner for the `{0}` architecture right now, pass a specific archetype")]
UnsupportedArchitecture(target_lexicon::Triple), UnsupportedArchitecture(target_lexicon::Triple),
/// Error executing action /// Error executing action
#[error("Error executing action")] #[error("Error executing action")]
@ -402,6 +433,9 @@ pub enum PlannerError {
/// Failed to execute command /// Failed to execute command
#[error("Failed to execute command `{0}`")] #[error("Failed to execute command `{0}`")]
Command(String, #[source] std::io::Error), Command(String, #[source] std::io::Error),
#[cfg(feature = "diagnostics")]
#[error(transparent)]
Diagnostic(#[from] crate::diagnostics::DiagnosticError),
} }
impl HasExpectedErrors for PlannerError { impl HasExpectedErrors for PlannerError {
@ -431,7 +465,16 @@ impl HasExpectedErrors for PlannerError {
this @ PlannerError::NixExists => Some(Box::new(this)), this @ PlannerError::NixExists => Some(Box::new(this)),
this @ PlannerError::Wsl1 => Some(Box::new(this)), this @ PlannerError::Wsl1 => Some(Box::new(this)),
PlannerError::Command(_, _) => None, PlannerError::Command(_, _) => None,
#[cfg(feature = "diagnostics")]
PlannerError::Diagnostic(diagnostic_error) => Some(Box::new(diagnostic_error)),
} }
} }
} }
#[cfg(feature = "diagnostics")]
impl crate::diagnostics::ErrorDiagnostic for PlannerError {
fn diagnostic(&self) -> String {
let static_str: &'static str = (self).into();
static_str.to_string()
}
}

View file

@ -11,7 +11,10 @@ use crate::{
settings::{InitSystem, InstallSettingsError}, settings::{InitSystem, InstallSettingsError},
Action, BuiltinPlanner, Action, BuiltinPlanner,
}; };
use std::{collections::HashMap, path::PathBuf}; use std::{
collections::HashMap,
path::{Path, PathBuf},
};
use super::{ use super::{
linux::{ linux::{
@ -21,6 +24,19 @@ use super::{
ShellProfileLocations, ShellProfileLocations,
}; };
// Fedora's ostree's fish package creates `/etc/fish` but fish doesn't read from it.
// Its fish does read from /usr/local/share/fish/, but the directory doesn't exist --
// so we ignore it.
//
// We use this const to forcefully create this directory and add it to shell locations
// to think about updating.
//
// This may not be suitable for all possible ostree based distros, but it'll fix
// a good number of selftest failures we're seeing.
//
// See: https://github.com/DeterminateSystems/nix-installer/issues/707
pub const OSTREE_FISH_PROFILE_LOCATION: &str = "/usr/local/share/fish/";
/// A planner suitable for immutable systems using ostree, such as Fedora Silverblue /// A planner suitable for immutable systems using ostree, such as Fedora Silverblue
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "cli", derive(clap::Parser))] #[cfg_attr(feature = "cli", derive(clap::Parser))]
@ -158,10 +174,8 @@ impl Planner for Ostree {
.iter() .iter()
.position(|v| *v == PathBuf::from("/usr/share/fish/")) .position(|v| *v == PathBuf::from("/usr/share/fish/"))
{ {
shell_profile_locations shell_profile_locations.fish.vendor_confd_prefixes =
.fish vec![OSTREE_FISH_PROFILE_LOCATION.into()];
.vendor_confd_prefixes
.remove(index);
} }
plan.push( plan.push(
@ -183,6 +197,16 @@ impl Planner for Ostree {
.map_err(PlannerError::Action)? .map_err(PlannerError::Action)?
.boxed(), .boxed(),
); );
if Path::new("/etc/fish").is_dir() {
plan.push(
CreateDirectory::plan(&OSTREE_FISH_PROFILE_LOCATION, None, None, 0o0755, true)
.await
.map_err(PlannerError::Action)?
.boxed(),
);
}
plan.push( plan.push(
ConfigureNix::plan(shell_profile_locations, &self.settings) ConfigureNix::plan(shell_profile_locations, &self.settings)
.await .await
@ -266,6 +290,19 @@ impl Planner for Ostree {
Ok(settings) Ok(settings)
} }
#[cfg(feature = "diagnostics")]
async fn diagnostic_data(&self) -> Result<crate::diagnostics::DiagnosticData, PlannerError> {
Ok(crate::diagnostics::DiagnosticData::new(
self.settings.diagnostic_attribution.clone(),
self.settings.diagnostic_endpoint.clone(),
self.typetag_name().into(),
self.configured_settings()
.await?
.into_keys()
.collect::<Vec<_>>(),
self.settings.ssl_cert_file.clone(),
)?)
}
async fn pre_uninstall_check(&self) -> Result<(), PlannerError> { async fn pre_uninstall_check(&self) -> Result<(), PlannerError> {
check_not_wsl1()?; check_not_wsl1()?;

View file

@ -385,6 +385,20 @@ impl Planner for SteamDeck {
Ok(settings) Ok(settings)
} }
#[cfg(feature = "diagnostics")]
async fn diagnostic_data(&self) -> Result<crate::diagnostics::DiagnosticData, PlannerError> {
Ok(crate::diagnostics::DiagnosticData::new(
self.settings.diagnostic_attribution.clone(),
self.settings.diagnostic_endpoint.clone(),
self.typetag_name().into(),
self.configured_settings()
.await?
.into_keys()
.collect::<Vec<_>>(),
self.settings.ssl_cert_file.clone(),
)?)
}
async fn pre_uninstall_check(&self) -> Result<(), PlannerError> { async fn pre_uninstall_check(&self) -> Result<(), PlannerError> {
super::linux::check_not_wsl1()?; super::linux::check_not_wsl1()?;

View file

@ -26,6 +26,27 @@ pub enum SelfTestError {
SystemTime(#[from] std::time::SystemTimeError), SystemTime(#[from] std::time::SystemTimeError),
} }
#[cfg(feature = "diagnostics")]
impl crate::diagnostics::ErrorDiagnostic for SelfTestError {
fn diagnostic(&self) -> String {
let static_str: &'static str = (self).into();
let context = match self {
Self::ShellFailed { shell, .. } => vec![shell.to_string()],
Self::Command { shell, .. } => vec![shell.to_string()],
Self::SystemTime(_) => vec![],
};
format!(
"{}({})",
static_str,
context
.iter()
.map(|v| format!("\"{v}\""))
.collect::<Vec<_>>()
.join(", ")
)
}
}
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum Shell { pub enum Shell {
Sh, Sh,

View file

@ -13,19 +13,19 @@ pub const SCRATCH_DIR: &str = "/nix/temp-install-dir";
/// Default [`nix_package_url`](CommonSettings::nix_package_url) for Linux x86_64 /// Default [`nix_package_url`](CommonSettings::nix_package_url) for Linux x86_64
pub const NIX_X64_64_LINUX_URL: &str = pub const NIX_X64_64_LINUX_URL: &str =
"https://releases.lix.systems/lix/lix-2.90-beta.1/nix-2.90.0-beta.1-x86_64-linux.tar.xz"; "https://releases.nixos.org/nix/nix-2.18.1/nix-2.18.1-x86_64-linux.tar.xz";
/// Default [`nix_package_url`](CommonSettings::nix_package_url) for Linux x86 (32 bit) /// Default [`nix_package_url`](CommonSettings::nix_package_url) for Linux x86 (32 bit)
pub const NIX_I686_LINUX_URL: &str = pub const NIX_I686_LINUX_URL: &str =
"https://releases.lix.systems/lix/lix-2.90-beta.1/nix-2.90.0-beta.1-i686-linux.tar.xz"; "https://releases.nixos.org/nix/nix-2.18.1/nix-2.18.1-i686-linux.tar.xz";
/// Default [`nix_package_url`](CommonSettings::nix_package_url) for Linux aarch64 /// Default [`nix_package_url`](CommonSettings::nix_package_url) for Linux aarch64
pub const NIX_AARCH64_LINUX_URL: &str = pub const NIX_AARCH64_LINUX_URL: &str =
"https://releases.lix.systems/lix/lix-2.90-beta.1/nix-2.90.0-beta.1-aarch64-linux.tar.xz"; "https://releases.nixos.org/nix/nix-2.18.1/nix-2.18.1-aarch64-linux.tar.xz";
/// Default [`nix_package_url`](CommonSettings::nix_package_url) for Darwin x86_64 /// Default [`nix_package_url`](CommonSettings::nix_package_url) for Darwin x86_64
pub const NIX_X64_64_DARWIN_URL: &str = pub const NIX_X64_64_DARWIN_URL: &str =
"https://releases.lix.systems/lix/lix-2.90-beta.1/nix-2.90.0-beta.1-x86_64-darwin.tar.xz"; "https://releases.nixos.org/nix/nix-2.18.1/nix-2.18.1-x86_64-darwin.tar.xz";
/// Default [`nix_package_url`](CommonSettings::nix_package_url) for Darwin aarch64 /// Default [`nix_package_url`](CommonSettings::nix_package_url) for Darwin aarch64
pub const NIX_AARCH64_DARWIN_URL: &str = pub const NIX_AARCH64_DARWIN_URL: &str =
"https://releases.lix.systems/lix/lix-2.90-beta.1/nix-2.90.0-beta.1-aarch64-darwin.tar.xz"; "https://releases.nixos.org/nix/nix-2.18.1/nix-2.18.1-aarch64-darwin.tar.xz";
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, Copy, PartialEq, Eq)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "cli", derive(clap::ValueEnum))] #[cfg_attr(feature = "cli", derive(clap::ValueEnum))]
@ -204,19 +204,47 @@ pub struct CommonSettings {
)] )]
pub force: bool, pub force: bool,
/// If `nix-installer` should enable flakes. #[cfg(feature = "diagnostics")]
/// Relate the install diagnostic to a specific value
#[cfg_attr( #[cfg_attr(
feature = "cli", feature = "cli",
clap( clap(
long, long,
action(ArgAction::SetFalse), default_value = None,
default_value = "true", env = "NIX_INSTALLER_DIAGNOSTIC_ATTRIBUTION",
global = true, global = true
env = "NIX_INSTALLER_ENABLE_FLAKES"
) )
)] )]
pub enable_flakes: bool, pub diagnostic_attribution: Option<String>,
#[cfg(feature = "diagnostics")]
/// The URL or file path for an installation diagnostic to be sent
///
/// Sample of the data sent:
///
/// {
/// "attribution": null,
/// "version": "0.4.0",
/// "planner": "linux",
/// "configured_settings": [ "modify_profile" ],
/// "os_name": "Ubuntu",
/// "os_version": "22.04.1 LTS (Jammy Jellyfish)",
/// "triple": "x86_64-unknown-linux-gnu",
/// "is_ci": false,
/// "action": "Install",
/// "status": "Success"
/// }
///
/// To disable diagnostic reporting, unset the default with `--diagnostic-endpoint ""`, or `NIX_INSTALLER_DIAGNOSTIC_ENDPOINT=""`
#[clap(
long,
env = "NIX_INSTALLER_DIAGNOSTIC_ENDPOINT",
global = true,
value_parser = crate::diagnostics::diagnostic_endpoint_validator,
num_args = 0..=1, // Required to allow `--diagnostic-endpoint` or `NIX_INSTALLER_DIAGNOSTIC_ENDPOINT=""`
default_value = "https://install.determinate.systems/nix/diagnostic"
)]
pub diagnostic_endpoint: Option<String>,
} }
impl CommonSettings { impl CommonSettings {
@ -284,8 +312,11 @@ impl CommonSettings {
proxy: Default::default(), proxy: Default::default(),
extra_conf: Default::default(), extra_conf: Default::default(),
force: false, force: false,
enable_flakes: true,
ssl_cert_file: Default::default(), ssl_cert_file: Default::default(),
#[cfg(feature = "diagnostics")]
diagnostic_attribution: None,
#[cfg(feature = "diagnostics")]
diagnostic_endpoint: Some("https://install.determinate.systems/nix/diagnostic".into()),
}) })
} }
@ -302,8 +333,11 @@ impl CommonSettings {
proxy, proxy,
extra_conf, extra_conf,
force, force,
enable_flakes,
ssl_cert_file, ssl_cert_file,
#[cfg(feature = "diagnostics")]
diagnostic_attribution: _,
#[cfg(feature = "diagnostics")]
diagnostic_endpoint,
} = self; } = self;
let mut map = HashMap::default(); let mut map = HashMap::default();
@ -338,8 +372,14 @@ impl CommonSettings {
map.insert("proxy".into(), serde_json::to_value(proxy)?); map.insert("proxy".into(), serde_json::to_value(proxy)?);
map.insert("ssl_cert_file".into(), serde_json::to_value(ssl_cert_file)?); map.insert("ssl_cert_file".into(), serde_json::to_value(ssl_cert_file)?);
map.insert("extra_conf".into(), serde_json::to_value(extra_conf)?); map.insert("extra_conf".into(), serde_json::to_value(extra_conf)?);
map.insert("enable_flakes".into(), serde_json::to_value(enable_flakes)?);
map.insert("force".into(), serde_json::to_value(force)?); map.insert("force".into(), serde_json::to_value(force)?);
#[cfg(feature = "diagnostics")]
map.insert(
"diagnostic_endpoint".into(),
serde_json::to_value(diagnostic_endpoint)?,
);
Ok(map) Ok(map)
} }
} }
@ -625,6 +665,14 @@ impl clap::builder::TypedValueParser for UrlOrPathOrString {
} }
} }
#[cfg(feature = "diagnostics")]
impl crate::diagnostics::ErrorDiagnostic for InstallSettingsError {
fn diagnostic(&self) -> String {
let static_str: &'static str = (self).into();
static_str.to_string()
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{FromStr, PathBuf, Url, UrlOrPath, UrlOrPathOrString}; use super::{FromStr, PathBuf, Url, UrlOrPath, UrlOrPathOrString};

View file

@ -1,5 +1,5 @@
{ {
"version": "0.17.1", "version": "0.14.0",
"actions": [ "actions": [
{ {
"action": { "action": {
@ -416,6 +416,7 @@
"ssl_cert_file": null, "ssl_cert_file": null,
"extra_conf": [], "extra_conf": [],
"force": false, "force": false,
"diagnostic_endpoint": "https://install.determinate.systems/nix/diagnostic"
}, },
"init": { "init": {
"init": "Systemd", "init": "Systemd",
@ -423,13 +424,14 @@
} }
}, },
"diagnostic_data": { "diagnostic_data": {
"version": "0.17.1", "version": "0.14.0",
"planner": "linux", "planner": "linux",
"configured_settings": [], "configured_settings": [],
"os_name": "Ubuntu", "os_name": "Ubuntu",
"os_version": "22.04.2 LTS (Jammy Jellyfish)", "os_version": "22.04.2 LTS (Jammy Jellyfish)",
"triple": "x86_64-unknown-linux-musl", "triple": "x86_64-unknown-linux-musl",
"is_ci": false, "is_ci": false,
"endpoint": "https://install.determinate.systems/nix/diagnostic",
"ssl_cert_file": null, "ssl_cert_file": null,
"failure_chain": null "failure_chain": null
} }

View file

@ -1,5 +1,5 @@
{ {
"version": "0.17.1", "version": "0.14.0",
"actions": [ "actions": [
{ {
"action": { "action": {
@ -400,16 +400,18 @@
"ssl_cert_file": null, "ssl_cert_file": null,
"extra_conf": [], "extra_conf": [],
"force": false, "force": false,
"diagnostic_endpoint": "https://install.determinate.systems/nix/diagnostic"
} }
}, },
"diagnostic_data": { "diagnostic_data": {
"version": "0.17.1", "version": "0.14.0",
"planner": "steam-deck", "planner": "steam-deck",
"configured_settings": [], "configured_settings": [],
"os_name": "Ubuntu", "os_name": "Ubuntu",
"os_version": "22.04.2 LTS (Jammy Jellyfish)", "os_version": "22.04.2 LTS (Jammy Jellyfish)",
"triple": "x86_64-unknown-linux-musl", "triple": "x86_64-unknown-linux-musl",
"is_ci": false, "is_ci": false,
"endpoint": "https://install.determinate.systems/nix/diagnostic",
"ssl_cert_file": null, "ssl_cert_file": null,
"failure_chain": null "failure_chain": null
} }

View file

@ -1,5 +1,5 @@
{ {
"version": "0.17.1", "version": "0.14.0",
"actions": [ "actions": [
{ {
"action": { "action": {
@ -427,6 +427,7 @@
"ssl_cert_file": null, "ssl_cert_file": null,
"extra_conf": [], "extra_conf": [],
"force": false, "force": false,
"diagnostic_endpoint": "https://install.determinate.systems/nix/diagnostic"
}, },
"encrypt": null, "encrypt": null,
"case_sensitive": false, "case_sensitive": false,
@ -434,13 +435,14 @@
"root_disk": "disk3" "root_disk": "disk3"
}, },
"diagnostic_data": { "diagnostic_data": {
"version": "0.17.1", "version": "0.14.0",
"planner": "macos", "planner": "macos",
"configured_settings": [], "configured_settings": [],
"os_name": "unknown", "os_name": "unknown",
"os_version": "unknown", "os_version": "unknown",
"triple": "aarch64-apple-darwin", "triple": "aarch64-apple-darwin",
"is_ci": false, "is_ci": false,
"endpoint": "https://install.determinate.systems/nix/diagnostic",
"ssl_cert_file": null, "ssl_cert_file": null,
"failure_chain": null "failure_chain": null
} }

View file

@ -1,4 +1,4 @@
use lix_installer::InstallPlan; use nix_installer::InstallPlan;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
const LINUX: &str = include_str!("./fixtures/linux/linux.json"); const LINUX: &str = include_str!("./fixtures/linux/linux.json");

View file

@ -1,109 +0,0 @@
#! /usr/bin/env nix-shell
#! nix-shell -i xonsh -p xonsh awscli2
#
# vim: ts=4 sw=4 et
#
# If the shebang line above was necessary, you probably should have used
# the flake, instead. But that's okay! You're valid. <3
#
""" Lix installer uploader.
Uploads our installers and install script to an S3 instance.
"""
import sys
import argparse
import functools
# Specify the platforms we want to build for.
TARGET_PLATFORMS = {
"aarch64-apple-darwin": "aarch64-darwin",
"x86_64-apple-darwin": "x86_64-darwin",
"aarch64-unknown-linux-musl": "aarch64-linux",
"x86_64-unknown-linux-musl": "x86_64-linux",
}
# Helpers functions.
printerr = functools.partial(print, file=sys.stderr)
#
# Arguments -- parsed while you wait!
#
parser = argparse.ArgumentParser(description="upload a lix-installer binary")
parser.add_argument("tag", help="the tag name to use while uploading")
parser.add_argument("folder", help="the results folder to use for uploading")
parser.add_argument("--make-default", help="makes this version the default for new installations",
action="store_true")
parser.add_argument("-E", "--endpoint", help="the endpoint URL to use for S3", default="https://s3.lix.systems")
parser.add_argument("-R", "--region", help="the region to use for the S3 upload", default="garage")
parser.add_argument("-B", "--bucket", help="the s3 bucket to target", default="install")
parser.add_argument("--force", help="allows overwriting an existing tag", action="store_true")
args = parser.parse_args()
# Extract our AWS command arguments from our argparse ones.
path_for = lambda platform : pf"{args.folder}/lix-installer-{platform}"
aws_args = [
"--endpoint-url",
args.endpoint,
"--region",
args.region
]
# Validate that we have the environment variables necessary to build.
if ('AWS_ACCESS_KEY_ID' not in ${...}) or ('AWS_SECRET_ACCESS_KEY' not in ${...}):
printerr("ERROR: the AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables must be set")
sys.exit(-1)
#
# First, make sure we have all of the artifacts that we need before we start.
#
found_all_files = True
for platform in TARGET_PLATFORMS:
if not path_for(platform).exists():
printerr(f"ERROR: {platform} installer not found in {path_for(platform)}\n")
found_all_files = False
if not found_all_files:
printerr("Aborting due to missing results. Perhaps you want to run `build-all.xsh`?\n")
sys.exit(-2)
#
# Next, handle our uploads.
#
tag = args.tag
bucket = args.bucket
folder = args.folder
target_path = f"s3://{bucket}/lix/{tag}"
default_path = f"s3://{bucket}/lix"
# First, check to ensure that the relevant tag does not exist.
tag_exists = !(aws s3 @(aws_args) ls @(target_path))
if tag_exists:
if args.force:
printerr(f"WARNING: Overwriting existing tag '{tag}' due to --force!")
else:
printerr(f"ERROR: Tag '{tag}' already exists! Refusing to overwrite without --force.\n")
sys.exit(-3)
# From this point forward, fail if any of our subcommands do.
$RAISE_SUBPROC_ERROR=True
# Copy the core inner pieces...
printerr(f"\n>> Uploading tag '{tag}' from folder '{folder}'.")
for in_filename, out_filename in TARGET_PLATFORMS.items():
aws s3 @(aws_args) cp @(folder)/lix-installer-@(in_filename) @(target_path)/lix-installer-@(out_filename) --acl public-read
# ... and, if requested, copy the pieces that make this the default.
if args.make_default:
printerr(f"\n>> Installing {tag} as the default install provider.")
for in_filename, out_filename in TARGET_PLATFORMS.items():
aws s3 @(aws_args) cp @(folder)/lix-installer-@(in_filename) @(default_path)/lix-installer-@(out_filename) --acl public-read
printerr(f"\n>> Updating base install script...")
aws s3 @(aws_args) cp nix-installer.sh @(default_path) --acl public-read
# Make sure all of our lines are out.
sys.stderr.flush()

71
upload_s3.sh Executable file
View file

@ -0,0 +1,71 @@
set -eu
DEST="$1"
GIT_ISH="$2"
DEST_INSTALL_URL="$3"
is_tag() {
if [[ "$GITHUB_REF_TYPE" == "tag" ]]; then
return 0
else
return 1
fi
}
# If the revision directory has already been created in S3 somehow, we don't want to reupload
if aws s3 ls "$AWS_BUCKET"/"$GIT_ISH"/; then
# Only exit if it's not a tag (since we're tagging a commit previously pushed to main)
if ! is_tag; then
echo "Revision $GIT_ISH was already uploaded; exiting"
exit 1
fi
fi
sudo chown $USER: -R artifacts/
mkdir "$DEST"
mkdir "$GIT_ISH"
cp nix-installer.sh "$DEST"/
cp nix-installer.sh "$GIT_ISH"/
for artifact in $(find artifacts/ -type f); do
chmod +x "$artifact"
cp "$artifact" "$DEST"/
cp "$artifact" "$GIT_ISH"/
done
sed -i "s@https://install.determinate.systems/nix@$DEST_INSTALL_URL@" "$DEST/nix-installer.sh"
sed -i "s@https://install.determinate.systems/nix@https://install.determinate.systems/nix/rev/$GIT_ISH@" "$GIT_ISH/nix-installer.sh"
if is_tag; then
cp "$DEST/nix-installer.sh" ./nix-installer.sh
fi
# If any artifact already exists in S3 and the hash is the same, we don't want to reupload
check_reupload() {
dest="$1"
for file in $(find "$dest" -type f); do
artifact_path="$dest"/"$(basename "$artifact")"
md5="$(md5sum "$artifact" | cut -d' ' -f1)"
obj="$(aws s3api head-object --bucket "$AWS_BUCKET" --key "$artifact_path" || echo '{}')"
obj_md5="$(jq -r .ETag <<<"$obj" | jq -r)" # head-object call returns ETag quoted, so `jq -r` again to unquote it
if [[ "$md5" == "$obj_md5" ]]; then
echo "Artifact $artifact was already uploaded; exiting"
# If we already uploaded to a tag, that's probably bad
is_tag && exit 1 || exit 0
fi
done
}
check_reupload "$DEST"
if ! is_tag; then
check_reupload "$GIT_ISH"
fi
aws s3 sync "$DEST"/ s3://"$AWS_BUCKET"/"$DEST"/ --acl public-read
if ! is_tag; then
aws s3 sync "$GIT_ISH"/ s3://"$AWS_BUCKET"/"$GIT_ISH"/ --acl public-read
fi