Compare commits

..

1 commit

Author SHA1 Message Date
dependabot[bot] 81586bb9ac
Bump actions/cache from 3 to 4
Bumps [actions/cache](https://github.com/actions/cache) from 3 to 4.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-01 20:25:08 +00:00
48 changed files with 2126 additions and 816 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@v4
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@v4
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@v4
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@v4
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

4
.gitignore vendored
View file

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

View file

@ -60,7 +60,8 @@ representative at an online or offline event.
## Enforcement
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 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
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:
@ -85,10 +85,10 @@ It should contain:
For **minor** fixes, documentation, or changes which **do not** have 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
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.
Please:
@ -103,7 +103,7 @@ Please:
## 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
the project.
@ -162,8 +162,8 @@ These should be visible in `nix flake show`:
```
nix flake show
warning: Git tree '/home/ana/git/detsys/nix-installer' is dirty
git+file:///home/ana/git/detsys/nix-installer
warning: Git tree '/home/ana/git/determinatesystems/nix-installer' is dirty
git+file:///home/ana/git/determinatesystems/nix-installer
# ...
├───hydraJobs
│ └───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.
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>
<summary><strong>Adding a distro?</strong></summary>
@ -243,8 +249,8 @@ These should be visible in `nix flake show`:
```
nix flake show
warning: Git tree '/home/ana/git/detsys/nix-installer' is dirty
git+file:///home/ana/git/detsys/nix-installer
warning: Git tree '/home/ana/git/determinatesystems/nix-installer' is dirty
git+file:///home/ana/git/determinatesystems/nix-installer
# ...
├───hydraJobs
│ ├───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
```
For PR review, you can also test arbitrary branches or checkouts like so:
```bash
nix build github:determinatesystems/nix-installer/${BRANCH}#hydraJobs.container-test.ubuntu-v22_04.x86_64-linux.podman -L
```
<details>
<summary><strong>Adding a distro?</strong></summary>
@ -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`
* 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.

710
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,12 +1,12 @@
[package]
name = "lix-installer"
name = "nix-installer"
description = "The Determinate Nix Installer"
version = "0.17.1"
version = "0.17.1-unreleased"
edition = "2021"
resolver = "2"
license = "LGPL-2.1"
repository = "https://git.lix.systems/lix-project/lix-installer"
documentation = "https://docs.rs/lix-installer/latest/lix_installer"
repository = "https://github.com/DeterminateSystems/nix-installer"
documentation = "https://docs.rs/nix-installer/latest/nix_installer"
[package.metadata.riff.targets.aarch64-apple-darwin]
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"]
[features]
default = ["cli"]
default = ["cli", "diagnostics"]
cli = ["eyre", "color-eyre", "clap", "tracing-subscriber", "tracing-error"]
diagnostics = ["is_ci"]
[[bin]]
name = "lix-installer"
name = "nix-installer"
required-features = [ "cli" ]
[dependencies]
@ -29,7 +30,7 @@ clap = { version = "4", features = ["std", "color", "usage", "help", "error-cont
color-eyre = { version = "0.6.2", default-features = false, features = [ "track-caller", "issue-url", "tracing-error", "capture-spantrace", "color-spantrace" ], optional = true }
eyre = { version = "0.6.8", default-features = false, features = [ "track-caller" ], optional = true }
glob = { version = "0.3.0", default-features = false }
nix = { version = "0.28.0", default-features = false, features = ["user", "fs", "process", "term"] }
nix = { version = "0.27.0", default-features = false, features = ["user", "fs", "process", "term"] }
owo-colors = { version = "4.0.0", default-features = false, features = [ "supports-colors" ] }
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" ] }

251
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.
Based on the [Determinate Installer](https://install.determinate.systems).
[![Crates.io](https://img.shields.io/crates/v/nix-installer)](https://crates.io/crates/nix-installer)
[![Docs.rs](https://img.shields.io/docsrs/nix-installer)](https://docs.rs/nix-installer/latest/nix_installer/)
A fast, friendly, and reliable tool to help you use [Nix] with Flakes everywhere.
```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 2,000,000 installs in a number of environments, including [Github Actions](#as-a-github-action) and [GitLab](#on-gitlab):
| 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
Install Nix with the default planner and options:
```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:
```bash
$ curl -sL -o lix-installer https://install.lix.systems/lix/lix-installer-x86_64-linux
$ chmod +x lix-installer
$ ./lix-installer
$ curl -sL -o nix-installer https://install.determinate.systems/nix/nix-installer-x86_64-linux
$ chmod +x nix-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
$ ./lix-installer install --help
$ ./nix-installer install --help
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]
lix-installer install <COMMAND>
Usage: nix-installer install [OPTIONS] [PLAN]
nix-installer install <COMMAND>
Commands:
linux
@ -48,10 +66,10 @@ Commands:
Planners have their own options and defaults, sharing most of them in common:
```bash
$ ./lix-installer install linux --help
$ ./nix-installer install linux --help
A planner for Linux installs
Usage: lix-installer install linux [OPTIONS]
Usage: nix-installer install linux [OPTIONS]
Options:
# ...
@ -72,33 +90,74 @@ Options:
Planners can be configured via environment variable or command arguments:
```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...
$ 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 described in [`DeterminateSystems/nix-upgrade`][nix-upgrade] by running:
```
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
You can remove a `lix-installer`-installed Nix by running
You can remove a `nix-installer`-installed Nix by running
```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 .
```
### On GitLab
GitLab CI runners are typically Docker based and run as the `root` user. This means `systemd` is not present, so the `--init none` option needs to be passed to the Linux planner.
On the default [GitLab.com](https://gitlab.com/) runners, `nix` can be installed and used like so:
```yaml
test:
script:
- curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install linux --no-confirm --init none
- . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh
- nix run nixpkgs#hello
- nix profile install nixpkgs#hello
- hello
```
If you are using different runners, the above example may need to be adjusted.
### Without systemd (Linux only)
> **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
> sudo -i nix run nixpkgs#hello
@ -107,7 +166,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`:
```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
@ -117,7 +176,7 @@ In Docker/Podman containers or WSL2 instances where an init (like `systemd`) is
For containers (without an init):
> **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
> sudo -i nix run nixpkgs#hello
@ -128,7 +187,7 @@ For containers (without an init):
FROM ubuntu:latest
RUN apt update -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" \
--init none \
--no-confirm
@ -153,7 +212,7 @@ For containers with a systemd init:
FROM ubuntu:latest
RUN apt update -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" \
--no-start-daemon \
--no-confirm
@ -175,11 +234,11 @@ On some container tools, such as `docker`, `sandbox = false` can be omitted. Omi
### 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
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:
@ -198,7 +257,7 @@ wsl nix run --impure github:guibou/nixGL nix run nixpkgs#obs-studio
If enabling systemd is not an option, pass `--init none` at the end of the command:
> **Warning**
> 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
> sudo -i nix run nixpkgs#hello
@ -206,7 +265,7 @@ If enabling systemd is not an option, pass `--init none` at the end of the comma
```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
@ -214,7 +273,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:
```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.
@ -222,11 +281,11 @@ This is especially useful when using the installer in non-interactive scripts.
## 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 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
$ nix shell nixpkgs#curl
@ -251,46 +310,56 @@ It's possible to resolve this situation by removing the `org.nixos.activate-syst
```bash
$ sudo rm /Library/LaunchDaemons/org.nixos.activate-system.plist
$ sudo launchctl bootout system/org.nixos.activate-system
$ /nix/lix-installer uninstall
$ /nix/nix-installer uninstall
$ 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
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:
```bash
# 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:
```bash
# 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
# to build and run a local copy
RUSTFLAGS="--cfg tokio_unstable" cargo run -- --help
# to build the remote main development branch
RUSTFLAGS="--cfg tokio_unstable" cargo install --git https://git.lix.systems/lix-project/lix-installer
lix-installer --help
RUSTFLAGS="--cfg tokio_unstable" cargo install --git https://github.com/DeterminateSystems/nix-installer
nix-installer --help
# for a specific version of the installer:
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
lix-installer --help
RUSTFLAGS="--cfg tokio_unstable" cargo install --git https://github.com/DeterminateSystems/nix-installer --tag $NIX_INSTALLER_TAG
nix-installer --help
```
To make this build portable, pass ` --target x86_64-unknown-linux-musl`.
@ -304,10 +373,10 @@ To make this build portable, pass ` --target x86_64-unknown-linux-musl`.
> **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.
Add `lix-installer` to your dependencies:
Add `nix-installer` to your dependencies:
```bash
cargo add lix-installer
cargo add nix-installer
```
If you are **building a CLI**, check out the `cli` feature flag for `clap` integration.
@ -320,14 +389,39 @@ You'll also need to edit your `.cargo/config.toml` to use `tokio_unstable` as we
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
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
For users who desire version pinning, the version of `nix-installer` to use can be specified in the `curl` command:
```bash
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
```
Each installer version has an [associated supported nix version](src/settings.rs) -- if you pin the installer version, you'll also indirectly pin to the associated nix version.
You can also override the `nix` version via `--nix-package-url` or `NIX_INSTALLER_NIX_PACKAGE_URL=` but doing so is not recommended since we haven't tested that combination.
@ -346,12 +440,63 @@ Differing from the upstream [Nix](https://github.com/NixOS/nix) installer script
+ `auto-optimise-store` is set to `true` (On Linux only)
* `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/lix-installer`
* `upgrade-nix-store-path-url` is set to `https://install.determinate.systems/nix-upgrade/stable/universal`
* an installation receipt (for uninstalling) is stored at `/nix/receipt.json` as well as a copy of the install binary at `/nix/nix-installer`
* `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.
## 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:
* survives macOS upgrades
* keeping an installation receipt for easy uninstallation
* offering users a chance to review an accurate, calculated install plan
* having 'planners' which can create appropriate install plans for complicated targets
* offering users with a failing install the chance to do a best-effort revert
* improving performance by maximizing parallel operations
* supporting a expanded test suite including 'curing' cases
* supporting SELinux and OSTree based distributions without asking users to make compromises
* operating as a single, static binary with external dependencies such as `openssl`, only calling existing system tools (like `useradd`) where necessary
* As a MacOS remote build target, ensures `nix` is not absent from path
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/policies/privacy
[systemd]: https://systemd.io
[wslg]: https://github.com/microsoft/wslg
[nixgl]: https://github.com/guibou/nixGL
[Nix]: https://nixos.org
[nix-upgrade]: https://github.com/DeterminateSystems/nix-upgrade/blob/main/versions.nix

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,17 +8,16 @@
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1714890282,
"narHash": "sha256-0dRK2ChvkhWrLM6H3d4r+rXP/UDxTJ6Vkdr22uGb1H0=",
"owner": "nix-community",
"repo": "fenix",
"rev": "24d83329e95a3bc48cbe9f3cd23813c210a25ea6",
"type": "github"
"lastModified": 1706768574,
"narHash": "sha256-4o6TMpzBHO659EiJTzd/EGQGUDdbgwKwhqf3u6b23U8=",
"rev": "668102037129923cd0fc239d864fce71eabdc6a3",
"revCount": 1762,
"type": "tarball",
"url": "https://api.flakehub.com/f/pinned/nix-community/fenix/0.1.1762%2Brev-668102037129923cd0fc239d864fce71eabdc6a3/018d63bb-6455-7a2f-98c6-74a36b8216a4/source.tar.gz"
},
"original": {
"owner": "nix-community",
"repo": "fenix",
"type": "github"
"type": "tarball",
"url": "https://flakehub.com/f/nix-community/fenix/0.1.1584.tar.gz"
}
},
"flake-compat": {
@ -95,16 +94,16 @@
"nixpkgs-regression": "nixpkgs-regression"
},
"locked": {
"lastModified": 1709808984,
"narHash": "sha256-bfFe38BkoQws7om4gBtBWoNTLkt9piMXdLLoHYl+vBQ=",
"rev": "f8170ce9f119e5e6724eb81ff1b5a2d4c0024000",
"revCount": 16143,
"lastModified": 1708517151,
"narHash": "sha256-s7QTMxLzVA5UF80sFCv8jwaTMBLA8/110YFkZNkNsCk=",
"rev": "8a8172cd2b5ef2f6dd2d9673a6379447d780ff17",
"revCount": 16129,
"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.20.3/018dcc43-c784-772a-8da1-64165044e9cd/source.tar.gz"
},
"original": {
"type": "tarball",
"url": "https://flakehub.com/f/NixOS/nix/%3D2.20.5.tar.gz"
"url": "https://flakehub.com/f/NixOS/nix/%3D2.20.3.tar.gz"
}
},
"nixpkgs": {
@ -141,18 +140,16 @@
},
"nixpkgs_2": {
"locked": {
"lastModified": 1714763106,
"narHash": "sha256-DrDHo74uTycfpAF+/qxZAMlP/Cpe04BVioJb6fdI0YY=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "e9be42459999a253a9f92559b1f5b72e1b44c13d",
"type": "github"
"lastModified": 1704538339,
"narHash": "sha256-1734d3mQuux9ySvwf6axRWZRBhtcZA9Q8eftD6EZg6U=",
"rev": "46ae0210ce163b3cba6c7da08840c1d63de9c701",
"revCount": 567011,
"type": "tarball",
"url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.1.567011%2Brev-46ae0210ce163b3cba6c7da08840c1d63de9c701/018ce71d-40cb-7594-bc00-f31ffedcdfa4/source.tar.gz"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
"type": "tarball",
"url": "https://flakehub.com/f/NixOS/nixpkgs/0.1.0.tar.gz"
}
},
"root": {

View file

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

View file

@ -1,11 +1,11 @@
#!/bin/sh
# shellcheck shell=dash
# 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
# If you need an offline install, or you'd prefer to run the binary directly, head to
# https://github.com/DeterminateSystems/nix-installer/releases then pick the version and platform
# most appropriate for your deployment target.
#
# 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.
#
# It runs on Unix shells like {a,ba,da,k,z}sh. It uses the common `local`
@ -17,7 +17,7 @@ if [ "$KSH_VERSION" = 'Version JM 93t+ 2010-03-05' ]; then
# The version of ksh93 that ships with many illumos systems does not
# support the "local" extension. Print a message rather than fail in
# 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
fi
@ -25,7 +25,7 @@ fi
set -u
# 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() {
downloader --check
@ -47,7 +47,7 @@ main() {
;;
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
if ! _dir="$(ensure mktemp -d)"; then
@ -55,7 +55,7 @@ main() {
# propagate exit status.
exit 1
fi
local _file="${_dir}/lix-installer${_ext}"
local _file="${_dir}/nix-installer${_ext}"
local _ansi_escapes_are_valid=false
if [ -t 2 ]; then
@ -95,7 +95,7 @@ main() {
ensure chmod u+x "$_file"
if [ ! -x "$_file" ]; then
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
fi
@ -229,7 +229,7 @@ get_architecture() {
}
say() {
printf 'lix-installer: %s\n' "$1"
printf 'nix-installer: %s\n' "$1"
}
err() {

View file

@ -1,9 +1,9 @@
FROM default
COPY lix-installer /lix-installer
RUN chmod +x /lix-installer
COPY nix-installer /nix-installer
RUN chmod +x /nix-installer
COPY binary-tarball /binary-tarball
RUN mv /binary-tarball/nix-*.tar.xz nix.tar.xz
RUN /nix-installer/bin/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"
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())
.map_err(|e| ActionErrorKind::GettingGroupId(name.clone(), e))
.map_err(Self::error)?

View file

@ -17,7 +17,7 @@ use crate::action::{
/// The `nix.conf` configuration names that are safe to merge.
// 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_COMMENT_CHAR: char = '#';
@ -410,7 +410,7 @@ impl Action for CreateOrMergeNixConfig {
}
new_config
.push_str("# Generated by https://install.lix.systems/.\n");
.push_str("# Generated by https://github.com/DeterminateSystems/nix-installer.\n");
new_config.push_str("# See `/nix/nix-installer --version` for the version details.\n");
new_config.push('\n');

View file

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

View file

@ -30,7 +30,6 @@ impl PlaceNixConfiguration {
proxy: Option<Url>,
ssl_cert_file: Option<PathBuf>,
extra_conf: Vec<UrlOrPathOrString>,
enable_flakes: bool,
force: bool,
) -> Result<StatefulAction<Self>, ActionError> {
let mut extra_conf_text = vec![];
@ -92,13 +91,7 @@ impl PlaceNixConfiguration {
let settings = nix_config.settings_mut();
settings.insert("build-users-group".to_string(), nix_build_group_name);
let mut experimental_features = vec!["nix-command"];
// Enable flakes if desired.
if enable_flakes {
experimental_features.push("flakes");
}
let experimental_features = ["nix-command", "flakes", "repl-flake"];
match settings.entry("experimental-features".to_string()) {
Entry::Occupied(mut slot) => {
let slot_mut = slot.get_mut();
@ -132,24 +125,15 @@ impl PlaceNixConfiguration {
ssl_cert_file_canonical.display().to_string(),
);
}
// Set up our substituters.
settings.insert(
"substituters".to_string(),
"https://cache.nixos.org https://cache.lix.systems".to_string(),
"extra-nix-path".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()
"upgrade-nix-store-path-url".to_string(),
"https://install.determinate.systems/nix-upgrade/stable/universal".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)
.await
.map_err(Self::error)?;

View file

@ -27,9 +27,9 @@ impl CreateNixHookService {
pub async fn plan() -> Result<StatefulAction<Self>, ActionError> {
let mut this = Self {
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,
};

View file

@ -24,7 +24,7 @@ You can manually plan, execute, then revert an [`Action`] like so:
```rust,no_run
# 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();
action.try_execute().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
use std::{error::Error, collections::HashMap};
use tracing::{Span, span};
use lix_installer::{
use nix_installer::{
InstallPlan,
settings::{CommonSettings, InstallSettingsError},
planner::{Planner, PlannerError},
@ -152,6 +152,20 @@ impl Planner for MyPlanner {
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<()> {
@ -315,6 +329,12 @@ impl ActionError {
pub fn action_tag(&self) -> &ActionTag {
&self.action_tag
}
#[cfg(feature = "diagnostics")]
pub fn diagnostic(&self) -> String {
use crate::diagnostics::ErrorDiagnostic;
self.kind.diagnostic()
}
}
impl std::fmt::Display for ActionError {
@ -473,6 +493,8 @@ pub enum ActionErrorKind {
command = .command,
)]
Command {
#[cfg(feature = "diagnostics")]
program: String,
command: String,
#[source]
error: std::io::Error,
@ -489,6 +511,8 @@ pub enum ActionErrorKind {
}
)]
CommandOutput {
#[cfg(feature = "diagnostics")]
program: String,
command: String,
output: Output,
},
@ -531,7 +555,7 @@ pub enum ActionErrorKind {
MissingRemoveUserFromGroupCommand,
#[error("\
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,
#[error("`{command}` failed, message: {message}")]
@ -551,12 +575,16 @@ pub enum ActionErrorKind {
impl ActionErrorKind {
pub fn command(command: &tokio::process::Command, error: std::io::Error) -> Self {
Self::Command {
#[cfg(feature = "diagnostics")]
program: command.as_std().get_program().to_string_lossy().into(),
command: format!("{:?}", command.as_std()),
error,
}
}
pub fn command_output(command: &tokio::process::Command, output: std::process::Output) -> Self {
Self::CommandOutput {
#[cfg(feature = "diagnostics")]
program: command.as_std().get_program().to_string_lossy().into(),
command: format!("{:?}", command.as_std()),
output,
}
@ -574,3 +602,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 clap::Parser;
use lix_installer::cli::CommandExecute;
use nix_installer::cli::CommandExecute;
#[tokio::main]
async fn main() -> eyre::Result<ExitCode> {
@ -17,7 +17,7 @@ async fn main() -> eyre::Result<ExitCode> {
})
.install()?;
let cli = lix_installer::cli::NixInstallerCli::parse();
let cli = nix_installer::cli::NixInstallerCli::parse();
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
pub(crate) async fn prompt(
question: impl AsRef<str>,
prompt_text: impl AsRef<str>,
default: PromptChoice,
currently_explaining: bool,
) -> eyre::Result<PromptChoice> {
@ -30,7 +29,7 @@ pub(crate) async fn prompt(
{are_you_sure} ({yes}/{no}{maybe_explain}): \
",
question = question.as_ref(),
are_you_sure = prompt_text.as_ref().bold(),
are_you_sure = "Proceed?".bold(),
no = if default == PromptChoice::No {
"[N]o"
} 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.
*/
@ -92,7 +92,7 @@ pub fn ensure_root() -> eyre::Result<()> {
if !is_root() {
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()
.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() {
arg_vec_cstring
.push(CString::new("env").wrap_err("Building a `env` argument for `sudo`")?);

View file

@ -1,5 +1,4 @@
use std::{
io::{stdout, Write},
os::unix::prelude::PermissionsExt,
path::{Path, PathBuf},
process::ExitCode,
@ -26,13 +25,13 @@ use color_eyre::{
use owo_colors::OwoColorize;
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 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 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 trying to upgrade Nix, try running `sudo -i nix upgrade-nix` instead.\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 `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.
@ -81,22 +80,6 @@ impl CommandExecute for Install {
explain,
} = 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()?;
let existing_receipt: Option<InstallPlan> = match Path::new(RECEIPT_LOCATION).exists() {
@ -107,7 +90,7 @@ impl CommandExecute for Install {
.wrap_err("Reading plan")?;
Some(
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() {
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) {
@ -170,39 +153,7 @@ impl CommandExecute for Install {
serde_json::from_str(&install_plan_string)?
},
(None, None) => {
let mut settings_to_apply = 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)
let builtin_planner = BuiltinPlanner::from_common_settings(settings.clone())
.await
.map_err(|e| eyre::eyre!(e))?;
@ -268,7 +219,6 @@ impl CommandExecute for Install {
.describe_install(currently_explaining)
.await
.map_err(|e| eyre!(e))?,
"Proceed?",
PromptChoice::Yes,
currently_explaining,
)
@ -309,7 +259,6 @@ impl CommandExecute for Install {
.describe_uninstall(currently_explaining)
.await
.map_err(|e| eyre!(e))?,
"Proceed?",
PromptChoice::Yes,
currently_explaining,
)

View file

@ -129,7 +129,7 @@ impl CommandExecute for Uninstall {
format!(
"\
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/v{plan_version} | sh -s -- uninstall`\
").red().to_string()
});
},
@ -147,7 +147,7 @@ impl CommandExecute for Uninstall {
\n\
Found existing plan in `{RECEIPT_LOCATION}` which was created by a version incompatible `nix-installer`.\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/v${version} | sh -s -- uninstall`\n\
\n\
").red()
);
@ -169,7 +169,6 @@ impl CommandExecute for Uninstall {
plan.describe_uninstall(currently_explaining)
.await
.map_err(|e| eyre!(e))?,
"Proceed?",
PromptChoice::Yes,
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::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,
),
#[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
#[error("Could not parse `{0}` as a version requirement in order to ensure it's compatible")]
InvalidVersionRequirement(String, semver::Error),
@ -107,6 +115,36 @@ impl HasExpectedErrors for NixInstallerError {
this @ NixInstallerError::IncompatibleVersion { binary: _, plan: _ } => {
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
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.
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:
```rust,no_run
use std::error::Error;
use lix_installer::InstallPlan;
use nix_installer::InstallPlan;
# async fn default_install() -> color_eyre::Result<()> {
let mut plan = InstallPlan::default().await?;
@ -38,7 +38,7 @@ Sometimes choosing a specific planner is desired:
```rust,no_run
use std::error::Error;
use lix_installer::{InstallPlan, planner::Planner};
use nix_installer::{InstallPlan, planner::Planner};
# async fn chosen_planner_install() -> color_eyre::Result<()> {
#[cfg(target_os = "linux")]
@ -72,6 +72,8 @@ match plan.install(None).await {
pub mod action;
#[cfg(feature = "cli")]
pub mod cli;
#[cfg(feature = "diagnostics")]
pub mod diagnostics;
mod error;
mod os;
mod plan;

View file

@ -22,12 +22,18 @@ pub struct InstallPlan {
pub(crate) actions: Vec<StatefulAction<Box<dyn Action>>>,
pub(crate) planner: Box<dyn Planner>,
#[cfg(feature = "diagnostics")]
pub(crate) diagnostic_data: Option<crate::diagnostics::DiagnosticData>,
}
impl InstallPlan {
pub async fn default() -> Result<Self, NixInstallerError> {
let planner = BuiltinPlanner::default().await?;
#[cfg(feature = "diagnostics")]
let diagnostic_data = Some(planner.diagnostic_data().await?);
let planner = planner.boxed();
let actions = planner.plan().await?;
@ -35,6 +41,8 @@ impl InstallPlan {
planner,
actions,
version: current_version()?,
#[cfg(feature = "diagnostics")]
diagnostic_data,
})
}
@ -42,6 +50,9 @@ impl InstallPlan {
where
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
planner.pre_install_check().await?;
@ -50,6 +61,8 @@ impl InstallPlan {
planner: planner.boxed(),
actions,
version: current_version()?,
#[cfg(feature = "diagnostics")]
diagnostic_data,
})
}
@ -88,7 +101,7 @@ impl InstallPlan {
let buf = format!(
"\
Lix install plan (v{version})\n\
Nix install plan (v{version})\n\
Planner: {planner}{maybe_default_setting_note}\n\
\n\
{maybe_plan_settings}\
@ -160,6 +173,17 @@ impl InstallPlan {
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);
}
}
@ -170,6 +194,17 @@ impl InstallPlan {
tracing::error!("Error saving receipt: {:?}", 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);
}
@ -181,7 +216,30 @@ impl InstallPlan {
.await
.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:?}")
} 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(())
@ -212,7 +270,7 @@ impl InstallPlan {
let buf = format!(
"\
Lix uninstall plan (v{version})\n\
Nix uninstall plan (v{version})\n\
\n\
Planner: {planner}{maybe_default_setting_note}\n\
\n\
@ -287,6 +345,16 @@ impl InstallPlan {
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);
}
}
@ -298,9 +366,32 @@ impl InstallPlan {
}
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(())
} else {
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)
}
}

View file

@ -126,6 +126,19 @@ impl Planner for Linux {
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> {
check_not_wsl1()?;

View file

@ -238,6 +238,20 @@ impl Planner for Macos {
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> {
check_nix_darwin_not_installed().await?;

View file

@ -12,7 +12,7 @@ A custom [`Planner`] can be created:
```rust,no_run
use std::{error::Error, collections::HashMap};
use lix_installer::{
use nix_installer::{
InstallPlan,
settings::{CommonSettings, InstallSettingsError},
planner::{Planner, PlannerError},
@ -69,6 +69,19 @@ impl Planner for MyPlanner {
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<()> {
@ -141,6 +154,9 @@ pub trait Planner: std::fmt::Debug + Send + Sync + dyn_clone::DynClone {
async fn pre_install_check(&self) -> Result<(), PlannerError> {
Ok(())
}
#[cfg(feature = "diagnostics")]
async fn diagnostic_data(&self) -> Result<crate::diagnostics::DiagnosticData, PlannerError>;
}
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)]
@ -361,8 +392,8 @@ impl Default for FishShellProfileLocations {
#[non_exhaustive]
#[derive(thiserror::Error, Debug, strum::IntoStaticStr)]
pub enum PlannerError {
/// `lix-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")]
/// `nix-installer` does not have a default planner for the target architecture right now
#[error("`nix-installer` does not have a default planner for the `{0}` architecture right now, pass a specific archetype")]
UnsupportedArchitecture(target_lexicon::Triple),
/// Error executing action
#[error("Error executing action")]
@ -402,6 +433,9 @@ pub enum PlannerError {
/// Failed to execute command
#[error("Failed to execute command `{0}`")]
Command(String, #[source] std::io::Error),
#[cfg(feature = "diagnostics")]
#[error(transparent)]
Diagnostic(#[from] crate::diagnostics::DiagnosticError),
}
impl HasExpectedErrors for PlannerError {
@ -431,7 +465,16 @@ impl HasExpectedErrors for PlannerError {
this @ PlannerError::NixExists => Some(Box::new(this)),
this @ PlannerError::Wsl1 => Some(Box::new(this)),
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

@ -266,6 +266,19 @@ impl Planner for Ostree {
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> {
check_not_wsl1()?;

View file

@ -385,6 +385,20 @@ impl Planner for SteamDeck {
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> {
super::linux::check_not_wsl1()?;

View file

@ -26,6 +26,27 @@ pub enum SelfTestError {
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)]
pub enum Shell {
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
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.20.3/nix-2.20.3-x86_64-linux.tar.xz";
/// Default [`nix_package_url`](CommonSettings::nix_package_url) for Linux x86 (32 bit)
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.20.3/nix-2.20.3-i686-linux.tar.xz";
/// Default [`nix_package_url`](CommonSettings::nix_package_url) for Linux aarch64
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.20.3/nix-2.20.3-aarch64-linux.tar.xz";
/// Default [`nix_package_url`](CommonSettings::nix_package_url) for Darwin x86_64
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.20.3/nix-2.20.3-x86_64-darwin.tar.xz";
/// Default [`nix_package_url`](CommonSettings::nix_package_url) for Darwin aarch64
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.20.3/nix-2.20.3-aarch64-darwin.tar.xz";
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "cli", derive(clap::ValueEnum))]
@ -204,19 +204,47 @@ pub struct CommonSettings {
)]
pub force: bool,
/// If `nix-installer` should enable flakes.
#[cfg(feature = "diagnostics")]
/// Relate the install diagnostic to a specific value
#[cfg_attr(
feature = "cli",
clap(
long,
action(ArgAction::SetFalse),
default_value = "true",
global = true,
env = "NIX_INSTALLER_ENABLE_FLAKES"
default_value = None,
env = "NIX_INSTALLER_DIAGNOSTIC_ATTRIBUTION",
global = true
)
)]
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 {
@ -284,8 +312,11 @@ impl CommonSettings {
proxy: Default::default(),
extra_conf: Default::default(),
force: false,
enable_flakes: true,
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,
extra_conf,
force,
enable_flakes,
ssl_cert_file,
#[cfg(feature = "diagnostics")]
diagnostic_attribution: _,
#[cfg(feature = "diagnostics")]
diagnostic_endpoint,
} = self;
let mut map = HashMap::default();
@ -338,8 +372,14 @@ impl CommonSettings {
map.insert("proxy".into(), serde_json::to_value(proxy)?);
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("enable_flakes".into(), serde_json::to_value(enable_flakes)?);
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)
}
}
@ -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)]
mod tests {
use super::{FromStr, PathBuf, Url, UrlOrPath, UrlOrPathOrString};

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
use lix_installer::InstallPlan;
use nix_installer::InstallPlan;
#[cfg(target_os = "linux")]
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