forked from lix-project/lix-installer
Compare commits
9 commits
9705a12ec0
...
570cc523a6
Author | SHA1 | Date | |
---|---|---|---|
Artemis Tosini | 570cc523a6 | ||
Artemis Tosini | 3bf2630935 | ||
Artemis Tosini | eb253d097d | ||
Artemis Tosini | 4711dd7167 | ||
Artemis Tosini | 6512b8d2cc | ||
jade | 8d04ca8d3d | ||
jade | 0256e915e7 | ||
jade | 4a4f16676d | ||
Artemis Tosini | f1a45806c7 |
|
@ -1,14 +1,14 @@
|
|||
# Contributing to `nix-installer`
|
||||
# Contributing to `lix-installer`
|
||||
|
||||
We're excited to see what you'd like to contribute to `nix-installer`!
|
||||
We're excited to see what you'd like to contribute to `lix-installer`!
|
||||
|
||||
Regardless of what (or how much) you contribute to `nix-installer`, we value your time
|
||||
Regardless of what (or how much) you contribute to `lix-installer`, we value your time
|
||||
and energy trying to improve the tool.
|
||||
|
||||
In order to ensure we all have a good experience, please review this document
|
||||
if you have any questions about the process.
|
||||
|
||||
**Regular Rust committer?** Contributing to `nix-installer` should feel similar to
|
||||
**Regular Rust committer?** Contributing to `lix-installer` should feel similar to
|
||||
contributing to other serious Rust ecosystem projects. You may already know
|
||||
the process and expectations of you, this document shouldn't contain any
|
||||
surprises.
|
||||
|
@ -16,7 +16,7 @@ surprises.
|
|||
|
||||
# What kinds of contributions are needed?
|
||||
|
||||
`nix-installer` can benefit from all kinds of contributions:
|
||||
`lix-installer` can benefit from all kinds of contributions:
|
||||
|
||||
* Bug reports
|
||||
* Code improvements
|
||||
|
@ -27,33 +27,29 @@ surprises.
|
|||
* Graphical/visual asset improvement
|
||||
* Kind words or recommendation on your own site, repo, stream, or social media
|
||||
account
|
||||
* Onboarding others to using `nix-installer`
|
||||
* Onboarding others to using `lix-installer`
|
||||
|
||||
|
||||
# What are the expectations you can have of the maintainers?
|
||||
|
||||
You can expect us to:
|
||||
|
||||
* Follow the [Contributor Covenant](CODE_OF_CONDUCT.md), just like you
|
||||
* Follow the [Lix community standards], just like you
|
||||
* Help diagnose bug reports (for supported platforms using supported
|
||||
languages)
|
||||
* Give constructive feedback on pull requests
|
||||
* Merge pull requests which:
|
||||
* Give constructive feedback on changes
|
||||
* Merge changes which:
|
||||
+ Have been approved of by at least 1 maintainer
|
||||
+ Pass all tests
|
||||
+ Have no complex conflicts with in-flight high priority work
|
||||
|
||||
The maintainers of this project use a separate issue tracker for some internal
|
||||
tasks. Unfortunately, the contents of this tracker is not publicly visible as
|
||||
it may contain sensitive or confidential data. Our maintainers will endeavor to
|
||||
ensure you are not 'left out' of the discussion about your contributions.
|
||||
|
||||
[Lix community standards]: https://lix.systems/community-standards/
|
||||
|
||||
# What kind of expectations do the maintainers have from you?
|
||||
|
||||
We expect you to:
|
||||
|
||||
* Follow the [Contributor Covenant](CODE_OF_CONDUCT.md), just like them
|
||||
* Follow the [Lix community standards], just like them
|
||||
* Make an earnest attempt to follow the contribution process described in this
|
||||
document
|
||||
* Update bug reports with a solution, if you find one before we do
|
||||
|
@ -74,8 +70,8 @@ Create an issue on [the issue page](https://git.lix.systems/lix-project/lix-inst
|
|||
It should contain:
|
||||
|
||||
1. Your OS (Linux, Mac) and architecture (x86_64, aarch64)
|
||||
2. Your `nix-installer` version (`/nix/nix-installer --version`)
|
||||
3. The thing you tried to run (eg `nix-installer`)
|
||||
2. Your `lix-installer` version (`/nix/lix-installer --version`)
|
||||
3. The thing you tried to run (eg `lix-installer`)
|
||||
4. What happened (the output of the command, please)
|
||||
5. What you expected to happen
|
||||
6. If you tried to fix it, what did you try?
|
||||
|
@ -364,6 +360,9 @@ To cut a release:
|
|||
+ `nix flake check -L`
|
||||
+ `nix build .#hydraJobs.container-test.all.x86_64-linux.all -L -j 6`
|
||||
+ `nix build .#hydraJobs.vm-test.all.x86_64-linux.all -L -j 6`
|
||||
|
||||
FIXME: the following is outdated for Lix and needs rewriting:
|
||||
|
||||
* Push the branch, create a PR ("Release v0.0.1")
|
||||
* Once the PR tests pass and it has been reviewed, merge it
|
||||
* `git pull` on the `main` branch
|
||||
|
|
281
Cargo.lock
generated
281
Cargo.lock
generated
|
@ -134,6 +134,12 @@ version = "0.22.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
|
@ -146,6 +152,15 @@ version = "2.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.16.0"
|
||||
|
@ -177,11 +192,22 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "camino"
|
||||
version = "1.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.96"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "065a29261d53ba54260972629f9ca6bffa69bac13cd1fed61420f7fa68b9f8bd"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
|
@ -282,6 +308,12 @@ version = "1.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
|
@ -298,6 +330,52 @@ version = "0.8.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "4.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"curve25519-dalek-derive",
|
||||
"digest",
|
||||
"fiat-crypto",
|
||||
"rustc_version",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek-derive"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.8"
|
||||
|
@ -333,6 +411,16 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.7.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.3.11"
|
||||
|
@ -343,6 +431,16 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "5.0.1"
|
||||
|
@ -391,6 +489,31 @@ version = "1.0.17"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
|
||||
|
||||
[[package]]
|
||||
name = "ed25519"
|
||||
version = "2.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
|
||||
dependencies = [
|
||||
"pkcs8",
|
||||
"serde",
|
||||
"signature",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ed25519-dalek"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"ed25519",
|
||||
"serde",
|
||||
"sha2",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.11.0"
|
||||
|
@ -459,6 +582,12 @@ version = "2.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
|
||||
|
||||
[[package]]
|
||||
name = "fiat-crypto"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.23"
|
||||
|
@ -553,6 +682,16 @@ dependencies = [
|
|||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.14"
|
||||
|
@ -817,6 +956,15 @@ version = "1.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45"
|
||||
|
||||
[[package]]
|
||||
name = "is_executable"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa9acdc6d67b75e626ad644734e8bc6df893d9cd2a834129065d3dd6158ea9c8"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.0"
|
||||
|
@ -829,6 +977,15 @@ version = "1.0.11"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.69"
|
||||
|
@ -877,17 +1034,20 @@ name = "lix-installer"
|
|||
version = "0.17.1"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"base64 0.22.1",
|
||||
"bytes 1.6.0",
|
||||
"clap",
|
||||
"color-eyre",
|
||||
"dirs",
|
||||
"dyn-clone",
|
||||
"ed25519-dalek",
|
||||
"eyre",
|
||||
"glob",
|
||||
"indexmap 2.2.6",
|
||||
"is_ci",
|
||||
"nix",
|
||||
"nix-config-parser",
|
||||
"nix-nar",
|
||||
"os-release",
|
||||
"owo-colors 4.0.0",
|
||||
"plist",
|
||||
|
@ -897,6 +1057,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
"sha2",
|
||||
"strum",
|
||||
"sysctl",
|
||||
"tar",
|
||||
|
@ -914,6 +1075,7 @@ dependencies = [
|
|||
"walkdir",
|
||||
"which",
|
||||
"xz2",
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1007,6 +1169,18 @@ dependencies = [
|
|||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix-nar"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5549158a8b179c4fcd06a19f4bcc557db60c9cbd6771add9563f46c8d0325b5"
|
||||
dependencies = [
|
||||
"camino",
|
||||
"is_executable",
|
||||
"symlink",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
|
@ -1140,6 +1314,16 @@ version = "0.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "pkcs8"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
|
||||
dependencies = [
|
||||
"der",
|
||||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.30"
|
||||
|
@ -1367,6 +1551,15 @@ version = "0.1.23"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.34"
|
||||
|
@ -1574,6 +1767,17 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
|
@ -1592,6 +1796,15 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signature"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.9"
|
||||
|
@ -1623,6 +1836,16 @@ version = "0.9.8"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||
|
||||
[[package]]
|
||||
name = "spki"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"der",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
|
@ -1657,6 +1880,12 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "supports-color"
|
||||
version = "2.1.0"
|
||||
|
@ -1667,6 +1896,12 @@ dependencies = [
|
|||
"is_ci",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "symlink"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.60"
|
||||
|
@ -1995,6 +2230,12 @@ version = "0.2.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||
|
||||
[[package]]
|
||||
name = "typetag"
|
||||
version = "0.2.16"
|
||||
|
@ -2079,6 +2320,12 @@ version = "0.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
|
@ -2421,3 +2668,37 @@ dependencies = [
|
|||
"lzma-sys",
|
||||
"tokio-io",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9"
|
||||
dependencies = [
|
||||
"zstd-safe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-safe"
|
||||
version = "7.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa556e971e7b568dc775c136fc9de8c779b1c2fc3a63defaafadffdbd3181afa"
|
||||
dependencies = [
|
||||
"zstd-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-sys"
|
||||
version = "2.0.12+zstd.1.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a4e40c320c3cb459d9a9ff6de98cff88f4751ee9275d140e2be94a2b74e4c13"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
|
|
@ -60,6 +60,11 @@ which = "6.0.0"
|
|||
sysctl = "0.5.4"
|
||||
walkdir = "2.3.3"
|
||||
indexmap = { version = "2.0.2", features = ["serde"] }
|
||||
nix-nar = "0.3.0"
|
||||
zstd = { version = "0.13.2", default-features = false }
|
||||
sha2 = "0.10.8"
|
||||
ed25519-dalek = { version = "2.1.1", features = ["serde"] }
|
||||
base64 = "0.22.1"
|
||||
|
||||
[dev-dependencies]
|
||||
eyre = { version = "0.6.8", default-features = false, features = [ "track-caller" ] }
|
||||
|
|
|
@ -338,13 +338,14 @@ Here are some example `nix` package URLs including nix version, OS and architect
|
|||
|
||||
## Installation Differences
|
||||
|
||||
Differing from the upstream [Nix](https://github.com/NixOS/nix) installer scripts:
|
||||
Differing from the [CppNix](https://github.com/NixOS/nix) installer scripts:
|
||||
|
||||
* In `nix.conf`:
|
||||
+ the `nix-command` and `flakes` features are enabled
|
||||
+ the `nix-command` and `flakes` features are optionally enabled
|
||||
+ `bash-prompt-prefix` is set
|
||||
+ `auto-optimise-store` is set to `true` (On Linux only)
|
||||
* `extra-nix-path` is set to `nixpkgs=flake:nixpkgs`
|
||||
* `extra-nix-path` is set to `nixpkgs=flake:nixpkgs` if flakes are enabled
|
||||
when installing
|
||||
* `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`
|
||||
* `nix-channel --update` is not run, `~/.nix-channels` is not provisioned
|
||||
|
|
107
flake.lock
107
flake.lock
|
@ -22,27 +22,13 @@
|
|||
}
|
||||
},
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1696426674,
|
||||
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
||||
"revCount": 57,
|
||||
"type": "tarball",
|
||||
"url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.0.1/018afb31-abd1-7bff-a5e4-cff7e18efb7a/source.tar.gz"
|
||||
},
|
||||
"original": {
|
||||
"type": "tarball",
|
||||
"url": "https://flakehub.com/f/edolstra/flake-compat/1.0.0.tar.gz"
|
||||
}
|
||||
},
|
||||
"flake-compat_2": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1673956053,
|
||||
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -51,20 +37,24 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"libgit2": {
|
||||
"flake": false,
|
||||
"lix": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat",
|
||||
"nix2container": "nix2container",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"nixpkgs-regression": "nixpkgs-regression",
|
||||
"pre-commit-hooks": "pre-commit-hooks"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1697646580,
|
||||
"narHash": "sha256-oX4Z3S9WtJlwvj0uH9HlYcWv+x1hqp8mhXl7HsLu2f0=",
|
||||
"owner": "libgit2",
|
||||
"repo": "libgit2",
|
||||
"rev": "45fd9ed7ae1a9b74b957ef4f337bc3c8b3df01b5",
|
||||
"type": "github"
|
||||
"lastModified": 1718419213,
|
||||
"narHash": "sha256-WY7BGnu5PnbK4O8cKKv9kvxwzZIGbIQUQLGPHFXitI0=",
|
||||
"rev": "253546d5fbf8a5aa60ac8164c1b4f5794dc4e9d1",
|
||||
"type": "tarball",
|
||||
"url": "https://git.lix.systems/api/v1/repos/lix-project/lix/archive/253546d5fbf8a5aa60ac8164c1b4f5794dc4e9d1.tar.gz"
|
||||
},
|
||||
"original": {
|
||||
"owner": "libgit2",
|
||||
"repo": "libgit2",
|
||||
"type": "github"
|
||||
"type": "tarball",
|
||||
"url": "https://git.lix.systems/lix-project/lix/archive/2.90.0-rc1.tar.gz"
|
||||
}
|
||||
},
|
||||
"naersk": {
|
||||
|
@ -87,38 +77,34 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat_2",
|
||||
"libgit2": "libgit2",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"nixpkgs-regression": "nixpkgs-regression"
|
||||
},
|
||||
"nix2container": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1709808984,
|
||||
"narHash": "sha256-bfFe38BkoQws7om4gBtBWoNTLkt9piMXdLLoHYl+vBQ=",
|
||||
"rev": "f8170ce9f119e5e6724eb81ff1b5a2d4c0024000",
|
||||
"revCount": 16143,
|
||||
"type": "tarball",
|
||||
"url": "https://api.flakehub.com/f/pinned/NixOS/nix/2.20.5/018e199b-ae2c-703d-ab99-4c648be473b2/source.tar.gz"
|
||||
"lastModified": 1712990762,
|
||||
"narHash": "sha256-hO9W3w7NcnYeX8u8cleHiSpK2YJo7ecarFTUlbybl7k=",
|
||||
"owner": "nlewo",
|
||||
"repo": "nix2container",
|
||||
"rev": "20aad300c925639d5d6cbe30013c8357ce9f2a2e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"type": "tarball",
|
||||
"url": "https://flakehub.com/f/NixOS/nix/%3D2.20.5.tar.gz"
|
||||
"owner": "nlewo",
|
||||
"repo": "nix2container",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1705033721,
|
||||
"narHash": "sha256-K5eJHmL1/kev6WuqyqqbS1cdNnSidIZ3jeqJ7GbrYnQ=",
|
||||
"lastModified": 1718379166,
|
||||
"narHash": "sha256-B/Q/Pf4kD+yWk3fGh5H0fUpwxmLgEKt9KBon+bZ3d9U=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "a1982c92d8980a0114372973cbdfe0a307f1bdea",
|
||||
"rev": "93fbfcd45e966ea1cff043d48bd45d1285082770",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-23.05-small",
|
||||
"ref": "nixos-24.05-small",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
|
@ -141,26 +127,41 @@
|
|||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1714763106,
|
||||
"narHash": "sha256-DrDHo74uTycfpAF+/qxZAMlP/Cpe04BVioJb6fdI0YY=",
|
||||
"lastModified": 1718379166,
|
||||
"narHash": "sha256-B/Q/Pf4kD+yWk3fGh5H0fUpwxmLgEKt9KBon+bZ3d9U=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "e9be42459999a253a9f92559b1f5b72e1b44c13d",
|
||||
"rev": "93fbfcd45e966ea1cff043d48bd45d1285082770",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"ref": "nixos-24.05-small",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"pre-commit-hooks": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1712055707,
|
||||
"narHash": "sha256-4XLvuSIDZJGS17xEwSrNuJLL7UjDYKGJSbK1WWX2AK8=",
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"rev": "e35aed5fda3cc79f88ed7f1795021e559582093a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"fenix": "fenix",
|
||||
"flake-compat": "flake-compat",
|
||||
"lix": "lix",
|
||||
"naersk": "naersk",
|
||||
"nix": "nix",
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
}
|
||||
},
|
||||
|
|
55
flake.nix
55
flake.nix
|
@ -2,7 +2,7 @@
|
|||
description = "The Lix Installer";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05-small";
|
||||
|
||||
fenix = {
|
||||
url = "github:nix-community/fenix";
|
||||
|
@ -14,12 +14,13 @@
|
|||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
nix = {
|
||||
url = "https://flakehub.com/f/NixOS/nix/=2.20.5.tar.gz";
|
||||
lix = {
|
||||
# See set_version.py
|
||||
# BEGIN GENERATE-URLS
|
||||
url = "https://git.lix.systems/lix-project/lix/archive/2.90.0-rc1.tar.gz";
|
||||
# END GENERATE-URLS
|
||||
# Omitting `inputs.nixpkgs.follows = "nixpkgs";` on purpose
|
||||
};
|
||||
|
||||
flake-compat.url = "https://flakehub.com/f/edolstra/flake-compat/1.0.0.tar.gz";
|
||||
};
|
||||
|
||||
outputs =
|
||||
|
@ -27,7 +28,7 @@
|
|||
, nixpkgs
|
||||
, fenix
|
||||
, naersk
|
||||
, nix
|
||||
, lix
|
||||
, ...
|
||||
} @ inputs:
|
||||
let
|
||||
|
@ -94,6 +95,8 @@
|
|||
};
|
||||
postInstall = ''
|
||||
cp lix-installer.sh $out/bin/lix-installer.sh
|
||||
ln -s lix-installer $out/bin/nix-installer
|
||||
ln -s lix-installer.sh $out/bin/nix-installer.sh
|
||||
'';
|
||||
};
|
||||
in
|
||||
|
@ -124,6 +127,13 @@
|
|||
let
|
||||
toolchain = fenixToolchain system;
|
||||
check = import ./nix/check.nix { inherit pkgs toolchain; };
|
||||
|
||||
inherit (pkgs) lib;
|
||||
|
||||
pythonEnv = pkgs.python3.withPackages (p: [
|
||||
(p.toPythonModule pkgs.xonsh-unwrapped)
|
||||
p.requests
|
||||
]);
|
||||
in
|
||||
{
|
||||
default = pkgs.mkShell {
|
||||
|
@ -131,10 +141,21 @@
|
|||
|
||||
RUST_SRC_PATH = "${toolchain}/lib/rustlib/src/rust/library";
|
||||
|
||||
nativeBuildInputs = with pkgs; [ ];
|
||||
buildInputs = with pkgs; [
|
||||
buildInputs = lib.optionals (pkgs.stdenv.isDarwin)
|
||||
(with pkgs; [
|
||||
libiconv
|
||||
darwin.apple_sdk.frameworks.Security
|
||||
darwin.apple_sdk.frameworks.SystemConfiguration
|
||||
])
|
||||
++ lib.optionals (pkgs.stdenv.isLinux) (with pkgs; [
|
||||
checkpolicy
|
||||
semodule-utils
|
||||
/* users are expected to have a system docker, too */
|
||||
]);
|
||||
|
||||
nativeBuildInputs = with pkgs; [
|
||||
zig
|
||||
xonsh
|
||||
pythonEnv
|
||||
awscli2
|
||||
toolchain
|
||||
rust-analyzer
|
||||
|
@ -150,17 +171,7 @@
|
|||
check.check-editorconfig
|
||||
check.check-semver
|
||||
check.check-clippy
|
||||
]
|
||||
++ lib.optionals (pkgs.stdenv.isDarwin) (with pkgs; [
|
||||
libiconv
|
||||
darwin.apple_sdk.frameworks.Security
|
||||
darwin.apple_sdk.frameworks.SystemConfiguration
|
||||
])
|
||||
++ lib.optionals (pkgs.stdenv.isLinux) (with pkgs; [
|
||||
checkpolicy
|
||||
semodule-utils
|
||||
/* users are expected to have a system docker, too */
|
||||
]);
|
||||
];
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -211,12 +222,12 @@
|
|||
hydraJobs = {
|
||||
vm-test = import ./nix/tests/vm-test {
|
||||
inherit forSystem;
|
||||
inherit (nix.hydraJobs) binaryTarball;
|
||||
inherit (lix.hydraJobs) binaryTarball;
|
||||
inherit (nixpkgs) lib;
|
||||
};
|
||||
container-test = import ./nix/tests/container-test {
|
||||
inherit forSystem;
|
||||
inherit (nix.hydraJobs) binaryTarball;
|
||||
inherit (lix.hydraJobs) binaryTarball;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
@ -2,7 +2,7 @@ FROM default
|
|||
COPY lix-installer /lix-installer
|
||||
RUN chmod +x /lix-installer
|
||||
COPY binary-tarball /binary-tarball
|
||||
RUN mv /binary-tarball/nix-*.tar.xz nix.tar.xz
|
||||
RUN mv /binary-tarball/[nl]ix-*.tar.xz nix.tar.xz
|
||||
RUN /lix-installer/bin/lix-installer install linux --logger pretty --log-directive lix_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"]; }'
|
||||
|
|
|
@ -576,7 +576,7 @@ let
|
|||
scp -P 20022 $ssh_opts $installer/bin/lix-installer vagrant@localhost:nix-installer
|
||||
|
||||
echo "Copying nix tarball..."
|
||||
scp -P 20022 $ssh_opts $binaryTarball/nix-*.tar.xz vagrant@localhost:nix.tar.xz
|
||||
scp -P 20022 $ssh_opts $binaryTarball/lix-*.tar.xz vagrant@localhost:nix.tar.xz
|
||||
|
||||
echo "Running preinstall..."
|
||||
$ssh "set -eux; $preinstallScript"
|
||||
|
|
83
set_version.py
Executable file
83
set_version.py
Executable file
|
@ -0,0 +1,83 @@
|
|||
#!/usr/bin/env python
|
||||
from pathlib import Path
|
||||
import textwrap
|
||||
import dataclasses
|
||||
import requests
|
||||
|
||||
SYSTEMS = ['x86_64-linux', 'x86_64-darwin', 'aarch64-linux', 'aarch64-darwin']
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Package:
|
||||
system: str
|
||||
version: str
|
||||
|
||||
def url(self):
|
||||
return f"https://releases.lix.systems/lix/lix-{self.version}/lix-{self.version}-{self.system}.tar.xz";
|
||||
|
||||
def variable_name(self):
|
||||
system = self.system.replace('-', '_').upper()
|
||||
return f'LIX_{system}_URL'
|
||||
|
||||
|
||||
def make_urls_section(packages: list[Package]):
|
||||
def one_item(package: Package):
|
||||
return textwrap.dedent("""\
|
||||
/// Default [`nix_package_url`](CommonSettings::nix_package_url) for {system}.
|
||||
pub const {variable_name}: &str =
|
||||
"{url}";
|
||||
""").format(system=package.system, variable_name=package.variable_name(), url=package.url())
|
||||
|
||||
return '\n'.join(one_item(package) for package in packages)
|
||||
|
||||
def replace_section(old: str, section: str) -> str:
|
||||
lines = []
|
||||
eat = False
|
||||
|
||||
for line in old.splitlines():
|
||||
next_eat = eat
|
||||
|
||||
if 'BEGIN GENERATE-URLS' in line.strip():
|
||||
next_eat = True
|
||||
elif 'END GENERATE-URLS' in line.strip():
|
||||
lines.append(section)
|
||||
eat = False
|
||||
next_eat = False
|
||||
|
||||
if not eat:
|
||||
lines.append(line)
|
||||
eat = next_eat
|
||||
|
||||
return '\n'.join(lines)
|
||||
|
||||
def replace_in_file(file: Path, section: str):
|
||||
new_file = replace_section(file.read_text(), section)
|
||||
file.write_text(new_file)
|
||||
|
||||
|
||||
def make_flake_url_section(version: str) -> str:
|
||||
return f' url = "https://git.lix.systems/lix-project/lix/archive/{version}.tar.gz";'
|
||||
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
ap = argparse.ArgumentParser(description='Update the version of lix-installer')
|
||||
|
||||
ap.add_argument('new_version', help='The new version')
|
||||
|
||||
args = ap.parse_args()
|
||||
|
||||
settings_rs = Path('src/settings.rs')
|
||||
packages = [Package(system, args.new_version) for system in SYSTEMS]
|
||||
|
||||
for package in packages:
|
||||
resp = requests.head(package.url())
|
||||
if resp.status_code != 200:
|
||||
print(f'Warning: broken URL {package.url()} returns HTTP {resp.status_code}')
|
||||
|
||||
replace_in_file(settings_rs, make_urls_section(packages))
|
||||
flake_nix = Path('flake.nix')
|
||||
replace_in_file(flake_nix, make_flake_url_section(args.new_version))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
458
src/action/base/fetch_and_unpack_nix_substituter.rs
Normal file
458
src/action/base/fetch_and_unpack_nix_substituter.rs
Normal file
|
@ -0,0 +1,458 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
io::BufRead,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use base64::Engine;
|
||||
use ed25519_dalek::{SignatureError, VerifyingKey};
|
||||
use reqwest::Url;
|
||||
use tracing::{span, Span};
|
||||
|
||||
use crate::{
|
||||
action::{Action, ActionDescription, ActionError, ActionErrorKind, ActionTag, StatefulAction},
|
||||
parse_ssl_cert,
|
||||
settings::UrlOrPath,
|
||||
};
|
||||
|
||||
/// Fetch an output and its dependencies from a set of substituters,
|
||||
/// given an output path, subsititer URLs, and trusted keys.
|
||||
/// Also generates a ".reginfo" compatible with `nix-store --load-db`
|
||||
/// Only implements a subset of nix substitution features:
|
||||
/// * Substituter priorites are highest to lowest as given to [`plan`],
|
||||
/// instead of priority from nix-cache-info
|
||||
/// * narinfo signatures are always required
|
||||
/// * ca-derivations are not supported
|
||||
/// * NarHash must be sha256
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||
pub struct FetchAndUnpackNixSubstituter {
|
||||
/// Map from key name (e.g. cache.nixos.org-1) to parsed ed25519 key
|
||||
trusted_keys: HashMap<String, VerifyingKey>,
|
||||
/// Base URLs for substituters, e.g. https://cache.nixos.org/
|
||||
substituters: Vec<Url>,
|
||||
/// Desired derivation output, e.g.
|
||||
/// `/nix/store/n50jk09x9hshwx1lh6k3qaiygc7yxbv9-lix-2.90.0-rc1`
|
||||
target: StorePath,
|
||||
/// Destination directory, normally temporary.
|
||||
/// For compatibility with tarballs, files will be placed in
|
||||
/// the nix/store subdirectory of the destination
|
||||
dest: PathBuf,
|
||||
/// Proxy used for all requests from substituters
|
||||
proxy: Option<Url>,
|
||||
/// Extra SSL certificates trusted for all requests
|
||||
ssl_cert_file: Option<PathBuf>,
|
||||
}
|
||||
|
||||
/// Root directory of the nix store.
|
||||
/// Technically this could be something other than /nix/store,
|
||||
/// but that is rarely done in production
|
||||
const STORE_DIR: &str = "/nix/store/";
|
||||
|
||||
impl FetchAndUnpackNixSubstituter {
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
pub async fn plan(
|
||||
target: UrlOrPath,
|
||||
dest: PathBuf,
|
||||
trusted_keys: Vec<String>,
|
||||
substituters: Vec<Url>,
|
||||
proxy: Option<Url>,
|
||||
ssl_cert_file: Option<PathBuf>,
|
||||
) -> Result<StatefulAction<Self>, ActionError> {
|
||||
let UrlOrPath::Path(target_path) = target else {
|
||||
return Err(Self::error(SubstitutionError::InvalidStorePath));
|
||||
};
|
||||
|
||||
let trusted_keys_parsed = trusted_keys
|
||||
.iter()
|
||||
.map(|key| parse_key(key))
|
||||
.collect::<Result<HashMap<_, _>, _>>()
|
||||
.map_err(Self::error)?;
|
||||
|
||||
if let Some(proxy) = &proxy {
|
||||
match proxy.scheme() {
|
||||
"https" | "http" | "socks5" => (),
|
||||
_ => return Err(Self::error(SubstitutionError::UnknownProxyScheme)),
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(ssl_cert_file) = &ssl_cert_file {
|
||||
parse_ssl_cert(ssl_cert_file).await.map_err(Self::error)?;
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
target: StorePath::from_path(&target_path)
|
||||
.ok_or_else(|| Self::error(SubstitutionError::InvalidStorePath))?,
|
||||
trusted_keys: trusted_keys_parsed,
|
||||
dest,
|
||||
proxy,
|
||||
substituters,
|
||||
ssl_cert_file,
|
||||
}
|
||||
.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
#[typetag::serde(name = "fetch_and_unpack_nix")]
|
||||
impl Action for FetchAndUnpackNixSubstituter {
|
||||
fn action_tag() -> ActionTag {
|
||||
ActionTag("fetch_and_unpack_nix_substituter")
|
||||
}
|
||||
fn tracing_synopsis(&self) -> String {
|
||||
format!(
|
||||
"Fetch `{}` from substituters to `{}`",
|
||||
self.target.full_path,
|
||||
self.dest.display()
|
||||
)
|
||||
}
|
||||
|
||||
fn tracing_span(&self) -> Span {
|
||||
let span = span!(
|
||||
tracing::Level::DEBUG,
|
||||
"fetch_and_unpack_nix_substituter",
|
||||
target = tracing::field::debug(&self.target.full_path),
|
||||
proxy = tracing::field::Empty,
|
||||
ssl_cert_file = tracing::field::Empty,
|
||||
dest = tracing::field::display(self.dest.display()),
|
||||
);
|
||||
if let Some(proxy) = &self.proxy {
|
||||
span.record("proxy", tracing::field::display(&proxy));
|
||||
}
|
||||
if let Some(ssl_cert_file) = &self.ssl_cert_file {
|
||||
span.record(
|
||||
"ssl_cert_file",
|
||||
tracing::field::display(&ssl_cert_file.display()),
|
||||
);
|
||||
}
|
||||
span
|
||||
}
|
||||
|
||||
fn execute_description(&self) -> Vec<ActionDescription> {
|
||||
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn execute(&mut self) -> Result<(), ActionError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn revert_description(&self) -> Vec<ActionDescription> {
|
||||
vec![/* Deliberately empty -- this is a noop */]
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn revert(&mut self) -> Result<(), ActionError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a nix trusted key into name and ed25519
|
||||
fn parse_key(key: &str) -> Result<(String, VerifyingKey), SubstitutionError> {
|
||||
let (name, key_base64) = key
|
||||
.split_once(':')
|
||||
.ok_or_else(|| SubstitutionError::PublicKey)?;
|
||||
|
||||
// seems to be the best way to handle keys both with and without padding
|
||||
let key_bytes = base64::engine::general_purpose::STANDARD_NO_PAD
|
||||
.decode(key_base64.trim_end_matches("=").as_bytes())
|
||||
.map_err(|_| SubstitutionError::PublicKey)?;
|
||||
|
||||
let key_array: [u8; ed25519_dalek::PUBLIC_KEY_LENGTH] = key_bytes
|
||||
.try_into()
|
||||
.map_err(|_| SubstitutionError::PublicKey)?;
|
||||
|
||||
let verifying_key = ed25519_dalek::VerifyingKey::from_bytes(&key_array)
|
||||
.map_err(|_| SubstitutionError::PublicKey)?;
|
||||
|
||||
Ok((name.to_string(), verifying_key))
|
||||
}
|
||||
|
||||
/// Utility struct representing a store path
|
||||
#[derive(Clone, Eq, Debug)]
|
||||
struct StorePath {
|
||||
/// Full path of the output including STORE_DIR,
|
||||
/// as seen in StorePath in narinfo
|
||||
/// e.g. `/nix/store/n50jk09x9hshwx1lh6k3qaiygc7yxbv9-lix-2.90.0-rc1`
|
||||
pub full_path: String,
|
||||
/// The base32 hash part of a store path,
|
||||
/// e.g. `n50jk09x9hshwx1lh6k3qaiygc7yxbv9`
|
||||
pub digest: String,
|
||||
/// The full name of a path, not including STORE_DIR,
|
||||
/// e.g. `n50jk09x9hshwx1lh6k3qaiygc7yxbv9-lix-2.90.0-rc1`
|
||||
pub full_name: String,
|
||||
}
|
||||
|
||||
impl StorePath {
|
||||
pub fn from_full_path(full_path: &str) -> Option<Self> {
|
||||
if !full_path.starts_with(STORE_DIR) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let (_, full_name) = full_path.split_at(STORE_DIR.len());
|
||||
|
||||
let (digest, name) = full_name.split_once('-')?;
|
||||
|
||||
if digest.len() != 32
|
||||
|| digest.contains(|c: char| !c.is_ascii_lowercase() && !c.is_ascii_digit())
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
if name.contains(|c: char| {
|
||||
!c.is_ascii_alphanumeric()
|
||||
&& c != '+'
|
||||
&& c != '-'
|
||||
&& c != '.'
|
||||
&& c != '_'
|
||||
&& c != '?'
|
||||
&& c != '='
|
||||
}) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Self {
|
||||
full_path: full_path.to_string(),
|
||||
digest: digest.to_string(),
|
||||
full_name: full_name.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_path(path: &Path) -> Option<Self> {
|
||||
Self::from_full_path(path.to_str()?)
|
||||
}
|
||||
|
||||
pub fn from_full_name(full_name: &str) -> Option<Self> {
|
||||
Self::from_full_path(
|
||||
&Path::new(STORE_DIR)
|
||||
.join(&full_name)
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
.ok()?,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for StorePath {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.full_path == other.full_path
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for StorePath {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
self.full_path.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for StorePath {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_string(StorePathVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
struct StorePathVisitor;
|
||||
impl serde::de::Visitor<'_> for StorePathVisitor {
|
||||
type Value = StorePath;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
formatter.write_str("a valid nix store path starting with /nix/store")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
StorePath::from_full_path(value).ok_or_else(|| E::custom("invalid store path"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Compression types for nar files.
|
||||
/// Not exhaustive, this is just what
|
||||
/// I've seen in the real world
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
enum NarCompression {
|
||||
Zstd,
|
||||
Xz,
|
||||
}
|
||||
|
||||
impl NarCompression {
|
||||
pub fn from_name(name: &str) -> Option<Self> {
|
||||
match name {
|
||||
"zstd" => Some(Self::Zstd),
|
||||
"xz" => Some(Self::Zstd),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extracted data from a narinfo.
|
||||
/// May only be constructed by verifying signature
|
||||
/// Missing some fields, like Deriver and FileHash because they aren't signed
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
struct NarInfo {
|
||||
/// Store path represented by the nar
|
||||
pub store_path: StorePath,
|
||||
/// Full URL to download the nar
|
||||
pub url: Url,
|
||||
/// Method used to compress the nar
|
||||
pub compression: NarCompression,
|
||||
/// sha256 hash of the nar after decompression
|
||||
/// encoded in nix base32 format
|
||||
pub nar_hash: String,
|
||||
/// Size of the decompressed nar in bytes
|
||||
pub nar_size: u64,
|
||||
/// Other store paths referenced by the nar
|
||||
pub references: Vec<StorePath>,
|
||||
/// Signature of the nar, used to sign other items
|
||||
pub sig: (String, Vec<u8>),
|
||||
}
|
||||
|
||||
impl NarInfo {
|
||||
fn parse(substituter_url: &Url, contents: &bytes::Bytes) -> Result<Self, SubstitutionError> {
|
||||
let mut store_path = None;
|
||||
let mut url = None;
|
||||
let mut compression = None;
|
||||
let mut nar_hash = None;
|
||||
let mut nar_size = None;
|
||||
let mut references = None;
|
||||
let mut sig = None;
|
||||
|
||||
for maybe_line in contents.lines() {
|
||||
let line = maybe_line.map_err(SubstitutionError::UndecodableNarInfo)?;
|
||||
let (tag, rest) = line
|
||||
.split_once(':')
|
||||
.ok_or_else(|| SubstitutionError::BadNarInfo)?;
|
||||
let value = rest.trim_start_matches(' ');
|
||||
|
||||
match tag {
|
||||
"StorePath" => store_path = StorePath::from_full_path(value),
|
||||
"URL" => url = substituter_url.join(value).ok(),
|
||||
"Compression" => compression = NarCompression::from_name(value),
|
||||
"NarHash" => {
|
||||
nar_hash = {
|
||||
let (algorithm, digest) = value
|
||||
.split_once(':')
|
||||
.ok_or_else(|| SubstitutionError::BadNarInfo)?;
|
||||
if algorithm == "sha256" {
|
||||
Some(digest.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
},
|
||||
"NarSize" => nar_size = u64::from_str_radix(value, 10).ok(),
|
||||
"References" => {
|
||||
references = value
|
||||
.split(' ')
|
||||
.map(StorePath::from_full_name)
|
||||
.collect::<Option<Vec<_>>>()
|
||||
},
|
||||
"Sig" => {
|
||||
let (signer, base64_signature) = value
|
||||
.split_once(':')
|
||||
.ok_or_else(|| SubstitutionError::BadNarInfo)?;
|
||||
let signature = base64::engine::general_purpose::STANDARD
|
||||
.decode(base64_signature)
|
||||
.map_err(|_| SubstitutionError::BadNarInfo)?;
|
||||
|
||||
sig = Some((signer.to_string(), signature))
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
Ok(Self {
|
||||
store_path: store_path.ok_or_else(|| SubstitutionError::BadNarInfo)?,
|
||||
url: url.ok_or_else(|| SubstitutionError::BadNarInfo)?,
|
||||
compression: compression.ok_or_else(|| SubstitutionError::BadNarInfo)?,
|
||||
nar_hash: nar_hash.ok_or_else(|| SubstitutionError::BadNarInfo)?,
|
||||
nar_size: nar_size.ok_or_else(|| SubstitutionError::BadNarInfo)?,
|
||||
references: references.ok_or_else(|| SubstitutionError::BadNarInfo)?,
|
||||
sig: sig.ok_or_else(|| SubstitutionError::BadNarInfo)?,
|
||||
})
|
||||
}
|
||||
|
||||
fn verify(
|
||||
&self,
|
||||
trusted_keys: &HashMap<String, VerifyingKey>,
|
||||
) -> Result<(), SubstitutionError> {
|
||||
let Some(key) = trusted_keys.get(&self.sig.0) else {
|
||||
return Err(SubstitutionError::BadSignature);
|
||||
};
|
||||
|
||||
// Fingerprint format not documented, but implemented in lix:
|
||||
// https://git.lix.systems/lix-project/lix/src/commit/d461cc1d7b2f489c3886f147166ba5b5e0e37541/src/libstore/path-info.cc#L25
|
||||
let fingerprint = format!(
|
||||
"1;{};{};{};{}",
|
||||
self.store_path.full_path,
|
||||
self.nar_hash,
|
||||
self.nar_size,
|
||||
self.references
|
||||
.iter()
|
||||
.map(|reference| reference.full_path.as_ref())
|
||||
.collect::<Vec<&str>>()
|
||||
.join(",")
|
||||
);
|
||||
|
||||
key.verify_strict(
|
||||
fingerprint.as_bytes(),
|
||||
&ed25519_dalek::Signature::from_bytes(
|
||||
self.sig
|
||||
.1
|
||||
.as_slice()
|
||||
.try_into()
|
||||
.map_err(|_| SubstitutionError::BadSignature)?,
|
||||
),
|
||||
)
|
||||
.map_err(|_| SubstitutionError::BadSignature)
|
||||
}
|
||||
|
||||
pub fn parse_and_verify(
|
||||
trusted_keys: &HashMap<String, VerifyingKey>,
|
||||
substituter_url: &Url,
|
||||
expected_store_path: &StorePath,
|
||||
contents: &bytes::Bytes,
|
||||
) -> Result<Self, SubstitutionError> {
|
||||
let parsed = Self::parse(substituter_url, contents)?;
|
||||
|
||||
if &parsed.store_path != expected_store_path {
|
||||
return Err(SubstitutionError::BadSignature);
|
||||
}
|
||||
|
||||
parsed.verify(trusted_keys)?;
|
||||
|
||||
Ok(parsed)
|
||||
}
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum SubstitutionError {
|
||||
#[error("Unarchiving error")]
|
||||
Unarchive(#[source] std::io::Error),
|
||||
#[error("Unknown proxy scheme, `https://`, `socks5://`, and `http://` supported")]
|
||||
UnknownProxyScheme,
|
||||
#[error("Invalid public key")]
|
||||
/// Normally an ed25519_dalek::SignatureError,
|
||||
/// but that comes with no extra information so no need to include it
|
||||
PublicKey,
|
||||
#[error("Undecodable narinfo")]
|
||||
UndecodableNarInfo(#[source] std::io::Error),
|
||||
#[error("Bad narinfo contents")]
|
||||
BadNarInfo,
|
||||
#[error("Bad narinfo signature")]
|
||||
BadSignature,
|
||||
#[error("Invalid nix store path")]
|
||||
InvalidStorePath,
|
||||
}
|
||||
|
||||
impl From<SubstitutionError> for ActionErrorKind {
|
||||
fn from(val: SubstitutionError) -> Self {
|
||||
ActionErrorKind::Custom(Box::new(val))
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ pub(crate) mod create_or_merge_nix_config;
|
|||
pub(crate) mod create_user;
|
||||
pub(crate) mod delete_user;
|
||||
pub(crate) mod fetch_and_unpack_nix;
|
||||
pub(crate) mod fetch_and_unpack_nix_substituter;
|
||||
pub(crate) mod move_unpacked_nix;
|
||||
pub(crate) mod remove_directory;
|
||||
pub(crate) mod setup_default_profile;
|
||||
|
@ -22,6 +23,7 @@ pub use create_or_merge_nix_config::CreateOrMergeNixConfig;
|
|||
pub use create_user::CreateUser;
|
||||
pub use delete_user::DeleteUser;
|
||||
pub use fetch_and_unpack_nix::{FetchAndUnpackNix, FetchUrlError};
|
||||
pub use fetch_and_unpack_nix_substituter::{FetchAndUnpackNixSubstituter, SubstitutionError};
|
||||
pub use move_unpacked_nix::{MoveUnpackedNix, MoveUnpackedNixError};
|
||||
pub use remove_directory::RemoveDirectory;
|
||||
pub use setup_default_profile::{SetupDefaultProfile, SetupDefaultProfileError};
|
||||
|
|
|
@ -6,8 +6,9 @@ use std::{
|
|||
use tracing::{span, Span};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use crate::action::{
|
||||
Action, ActionDescription, ActionError, ActionErrorKind, ActionTag, StatefulAction,
|
||||
use crate::{
|
||||
action::{Action, ActionDescription, ActionError, ActionErrorKind, ActionTag, StatefulAction},
|
||||
release_tarball,
|
||||
};
|
||||
|
||||
pub(crate) const DEST: &str = "/nix/";
|
||||
|
@ -62,15 +63,9 @@ impl Action for MoveUnpackedNix {
|
|||
let Self { unpacked_path } = self;
|
||||
|
||||
// This is the `nix-$VERSION` folder which unpacks from the tarball, not a nix derivation
|
||||
let found_nix_paths = glob::glob(&format!("{}/nix-*", unpacked_path.display()))
|
||||
.map_err(|e| Self::error(MoveUnpackedNixError::from(e)))?
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(|e| Self::error(MoveUnpackedNixError::from(e)))?;
|
||||
if found_nix_paths.len() != 1 {
|
||||
return Err(Self::error(ActionErrorKind::MalformedBinaryTarball));
|
||||
}
|
||||
let found_nix_path = found_nix_paths.into_iter().next().unwrap();
|
||||
let src_store = found_nix_path.join("store");
|
||||
let toplevel_path =
|
||||
release_tarball::find_toplevel_path(&unpacked_path).map_err(Self::error)?;
|
||||
let src_store = toplevel_path.join("store");
|
||||
let mut src_store_listing = tokio::fs::read_dir(src_store.clone())
|
||||
.await
|
||||
.map_err(|e| ActionErrorKind::ReadDir(src_store.clone(), e))
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::path::PathBuf;
|
|||
|
||||
use crate::{
|
||||
action::{ActionError, ActionErrorKind, ActionTag, StatefulAction},
|
||||
execute_command, set_env,
|
||||
execute_command, release_tarball, set_env,
|
||||
};
|
||||
|
||||
use glob::glob;
|
||||
|
@ -51,8 +51,10 @@ impl Action for SetupDefaultProfile {
|
|||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn execute(&mut self) -> Result<(), ActionError> {
|
||||
// Find an `nix` package
|
||||
let nix_pkg_glob = format!("{}/nix-*/store/*-nix-*.*.*", self.unpacked_path.display());
|
||||
let toplevel_path =
|
||||
release_tarball::find_toplevel_path(&self.unpacked_path).map_err(Self::error)?;
|
||||
// Find a `nix` package
|
||||
let nix_pkg_glob = format!("{}/store/*-[nl]ix-*.*.*", toplevel_path.display());
|
||||
let mut found_nix_pkg = None;
|
||||
for entry in glob(&nix_pkg_glob).map_err(Self::error)? {
|
||||
match entry {
|
||||
|
@ -78,10 +80,7 @@ impl Action for SetupDefaultProfile {
|
|||
};
|
||||
|
||||
// Find an `nss-cacert` package, add it too.
|
||||
let nss_ca_cert_pkg_glob = format!(
|
||||
"{}/nix-*/store/*-nss-cacert-*.*",
|
||||
self.unpacked_path.display()
|
||||
);
|
||||
let nss_ca_cert_pkg_glob = format!("{}/store/*-nss-cacert-*.*", toplevel_path.display());
|
||||
let mut found_nss_ca_cert_pkg = None;
|
||||
for entry in glob(&nss_ca_cert_pkg_glob).map_err(Self::error)? {
|
||||
match entry {
|
||||
|
@ -108,14 +107,8 @@ impl Action for SetupDefaultProfile {
|
|||
return Err(Self::error(SetupDefaultProfileError::NoNssCacert));
|
||||
};
|
||||
|
||||
let found_nix_paths = glob::glob(&format!("{}/nix-*", self.unpacked_path.display()))
|
||||
.map_err(Self::error)?
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(Self::error)?;
|
||||
if found_nix_paths.len() != 1 {
|
||||
return Err(Self::error(ActionErrorKind::MalformedBinaryTarball));
|
||||
}
|
||||
let found_nix_path = found_nix_paths.into_iter().next().unwrap();
|
||||
let found_nix_path =
|
||||
release_tarball::find_toplevel_path(&self.unpacked_path).map_err(Self::error)?;
|
||||
let reginfo_path = found_nix_path.join(".reginfo");
|
||||
let reginfo = tokio::fs::read(®info_path)
|
||||
.await
|
||||
|
|
|
@ -7,7 +7,7 @@ use crate::action::{
|
|||
Action, ActionDescription, ActionError, ActionErrorKind, ActionTag, StatefulAction,
|
||||
};
|
||||
use crate::parse_ssl_cert;
|
||||
use crate::settings::UrlOrPathOrString;
|
||||
use crate::settings::{UrlOrPathOrString, LIX_DEFAULT_SUBSTITUTERS, LIX_DEFAULT_SUBSTITUTER_KEYS};
|
||||
use indexmap::map::Entry;
|
||||
use std::path::PathBuf;
|
||||
|
||||
|
@ -136,11 +136,11 @@ impl PlaceNixConfiguration {
|
|||
// Set up our substituters.
|
||||
settings.insert(
|
||||
"substituters".to_string(),
|
||||
"https://cache.nixos.org https://cache.lix.systems".to_string(),
|
||||
LIX_DEFAULT_SUBSTITUTERS.join(" "),
|
||||
);
|
||||
settings.insert(
|
||||
"trusted-public-keys".to_string(),
|
||||
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o=".to_string()
|
||||
LIX_DEFAULT_SUBSTITUTER_KEYS.join(" "),
|
||||
);
|
||||
|
||||
if enable_flakes {
|
||||
|
|
|
@ -3,19 +3,52 @@ use tracing::{span, Span};
|
|||
use super::CreateNixTree;
|
||||
use crate::{
|
||||
action::{
|
||||
base::{FetchAndUnpackNix, MoveUnpackedNix},
|
||||
base::{FetchAndUnpackNix, FetchAndUnpackNixSubstituter, MoveUnpackedNix},
|
||||
Action, ActionDescription, ActionError, ActionErrorKind, ActionTag, StatefulAction,
|
||||
},
|
||||
settings::{CommonSettings, SCRATCH_DIR},
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||
pub enum FetchNix {
|
||||
FromTarball(StatefulAction<FetchAndUnpackNix>),
|
||||
FromSubstituter(StatefulAction<FetchAndUnpackNixSubstituter>),
|
||||
}
|
||||
|
||||
impl FetchNix {
|
||||
pub async fn try_execute(&mut self) -> Result<(), ActionError> {
|
||||
match self {
|
||||
FetchNix::FromTarball(action) => action.try_execute().await,
|
||||
FetchNix::FromSubstituter(action) => action.try_execute().await,
|
||||
}
|
||||
}
|
||||
pub fn describe_execute(&self) -> Vec<ActionDescription> {
|
||||
match self {
|
||||
FetchNix::FromTarball(action) => action.describe_execute(),
|
||||
FetchNix::FromSubstituter(action) => action.describe_execute(),
|
||||
}
|
||||
}
|
||||
pub async fn try_revert(&mut self) -> Result<(), ActionError> {
|
||||
match self {
|
||||
FetchNix::FromTarball(action) => action.try_revert().await,
|
||||
FetchNix::FromSubstituter(action) => action.try_revert().await,
|
||||
}
|
||||
}
|
||||
pub fn describe_revert(&self) -> Vec<ActionDescription> {
|
||||
match self {
|
||||
FetchNix::FromTarball(action) => action.describe_revert(),
|
||||
FetchNix::FromSubstituter(action) => action.describe_revert(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Place Nix and it's requirements onto the target
|
||||
*/
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||
pub struct ProvisionNix {
|
||||
fetch_nix: StatefulAction<FetchAndUnpackNix>,
|
||||
fetch_nix: FetchNix,
|
||||
create_nix_tree: StatefulAction<CreateNixTree>,
|
||||
move_unpacked_nix: StatefulAction<MoveUnpackedNix>,
|
||||
}
|
||||
|
@ -23,13 +56,29 @@ pub struct ProvisionNix {
|
|||
impl ProvisionNix {
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
pub async fn plan(settings: &CommonSettings) -> Result<StatefulAction<Self>, ActionError> {
|
||||
let fetch_nix = FetchAndUnpackNix::plan(
|
||||
settings.nix_package_url.clone(),
|
||||
PathBuf::from(SCRATCH_DIR),
|
||||
settings.proxy.clone(),
|
||||
settings.ssl_cert_file.clone(),
|
||||
)
|
||||
.await?;
|
||||
let fetch_nix = if settings.use_substituters {
|
||||
FetchNix::FromSubstituter(
|
||||
FetchAndUnpackNixSubstituter::plan(
|
||||
settings.nix_package_url.clone(),
|
||||
PathBuf::from(SCRATCH_DIR),
|
||||
settings.substituter_trusted_keys.clone(),
|
||||
settings.substituters.clone(),
|
||||
settings.proxy.clone(),
|
||||
settings.ssl_cert_file.clone(),
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
} else {
|
||||
FetchNix::FromTarball(
|
||||
FetchAndUnpackNix::plan(
|
||||
settings.nix_package_url.clone(),
|
||||
PathBuf::from(SCRATCH_DIR),
|
||||
settings.proxy.clone(),
|
||||
settings.ssl_cert_file.clone(),
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
};
|
||||
|
||||
let create_nix_tree = CreateNixTree::plan().await.map_err(Self::error)?;
|
||||
let move_unpacked_nix = MoveUnpackedNix::plan(PathBuf::from(SCRATCH_DIR))
|
||||
|
|
|
@ -488,10 +488,7 @@ pub enum ActionErrorKind {
|
|||
"".to_string()
|
||||
}
|
||||
)]
|
||||
CommandOutput {
|
||||
command: String,
|
||||
output: Output,
|
||||
},
|
||||
CommandOutput { command: String, output: Output },
|
||||
#[error("Joining spawned async task")]
|
||||
Join(
|
||||
#[source]
|
||||
|
@ -509,7 +506,7 @@ pub enum ActionErrorKind {
|
|||
/// A MacOS (Darwin) plist related error
|
||||
#[error(transparent)]
|
||||
Plist(#[from] plist::Error),
|
||||
#[error("Unexpected binary tarball contents found, the build result from `https://releases.nixos.org/?prefix=nix/` or `nix build nix#hydraJobs.binaryTarball.$SYSTEM` is expected")]
|
||||
#[error("Unexpected binary tarball contents found, the build result from `https://releases.lix.systems/?prefix=nix/` or `nix build lix#hydraJobs.binaryTarball.$SYSTEM` is expected")]
|
||||
MalformedBinaryTarball,
|
||||
#[error("Could not find `{0}` in PATH; This action only works on SteamOS, which should have this present in PATH.")]
|
||||
MissingSteamosBinary(String),
|
||||
|
|
|
@ -20,9 +20,9 @@ pub trait CommandExecute {
|
|||
}
|
||||
|
||||
/**
|
||||
The Determinate Nix installer (lix variant)
|
||||
The Lix installer.
|
||||
|
||||
A fast, friendly, and reliable tool to help you use Nix with Flakes everywhere.
|
||||
A fast, friendly, and reliable tool to help you install CppNix or Lix on your system.
|
||||
*/
|
||||
#[derive(Debug, Parser)]
|
||||
#[clap(version)]
|
||||
|
|
|
@ -76,6 +76,7 @@ mod error;
|
|||
mod os;
|
||||
mod plan;
|
||||
pub mod planner;
|
||||
mod release_tarball;
|
||||
pub mod self_test;
|
||||
pub mod settings;
|
||||
|
||||
|
|
17
src/release_tarball.rs
Normal file
17
src/release_tarball.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::action::ActionErrorKind;
|
||||
|
||||
/// Finds the top-level path in the tarball, e.g. lix-2.90.0-rc1-x86_64-linux
|
||||
pub fn find_toplevel_path(unpacked_path: &Path) -> Result<PathBuf, ActionErrorKind> {
|
||||
let found_nix_paths = glob::glob(&format!("{}/[nl]ix-*", unpacked_path.display()))
|
||||
.map_err(ActionErrorKind::from)?
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(ActionErrorKind::from)?;
|
||||
if found_nix_paths.len() != 1 {
|
||||
return Err(ActionErrorKind::MalformedBinaryTarball);
|
||||
}
|
||||
let found_nix_path = found_nix_paths.into_iter().next().unwrap();
|
||||
|
||||
Ok(found_nix_path)
|
||||
}
|
|
@ -86,7 +86,7 @@ impl Shell {
|
|||
.as_millis();
|
||||
|
||||
command.arg(format!(
|
||||
r#"nix build --no-link --expr 'derivation {{ name = "self-test-{executable}-{timestamp_millis}"; system = "{SYSTEM}"; builder = "/bin/sh"; args = ["-c" "echo hello > \$out"]; }}'"#
|
||||
r#"nix build --no-substitute --no-link --expr 'derivation {{ name = "self-test-{executable}-{timestamp_millis}"; system = "{SYSTEM}"; builder = "/bin/sh"; args = ["-c" "echo hello > \$out"]; }}'"#
|
||||
));
|
||||
let command_str = format!("{:?}", command.as_std());
|
||||
|
||||
|
|
108
src/settings.rs
108
src/settings.rs
|
@ -11,21 +11,33 @@ use url::Url;
|
|||
|
||||
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";
|
||||
/// 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";
|
||||
/// 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";
|
||||
/// 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";
|
||||
/// 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";
|
||||
// See set_version.py
|
||||
// BEGIN GENERATE-URLS
|
||||
/// Default [`nix_package_url`](CommonSettings::nix_package_url) for x86_64-linux.
|
||||
pub const LIX_X86_64_LINUX_URL: &str =
|
||||
"https://releases.lix.systems/lix/lix-2.90.0-rc1/lix-2.90.0-rc1-x86_64-linux.tar.xz";
|
||||
|
||||
/// Default [`nix_package_url`](CommonSettings::nix_package_url) for x86_64-darwin.
|
||||
pub const LIX_X86_64_DARWIN_URL: &str =
|
||||
"https://releases.lix.systems/lix/lix-2.90.0-rc1/lix-2.90.0-rc1-x86_64-darwin.tar.xz";
|
||||
|
||||
/// Default [`nix_package_url`](CommonSettings::nix_package_url) for aarch64-linux.
|
||||
pub const LIX_AARCH64_LINUX_URL: &str =
|
||||
"https://releases.lix.systems/lix/lix-2.90.0-rc1/lix-2.90.0-rc1-aarch64-linux.tar.xz";
|
||||
|
||||
/// Default [`nix_package_url`](CommonSettings::nix_package_url) for aarch64-darwin.
|
||||
pub const LIX_AARCH64_DARWIN_URL: &str =
|
||||
"https://releases.lix.systems/lix/lix-2.90.0-rc1/lix-2.90.0-rc1-aarch64-darwin.tar.xz";
|
||||
|
||||
// END GENERATE-URLS
|
||||
|
||||
pub const LIX_DEFAULT_SUBSTITUTERS: &[&str; 2] =
|
||||
&["https://cache.nixos.org", "https://cache.lix.systems"];
|
||||
|
||||
pub const LIX_DEFAULT_SUBSTITUTER_KEYS: &[&str; 2] = &[
|
||||
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=",
|
||||
"cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o=",
|
||||
];
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "cli", derive(clap::ValueEnum))]
|
||||
|
@ -142,6 +154,24 @@ pub struct CommonSettings {
|
|||
)]
|
||||
pub nix_build_user_id_base: u32,
|
||||
|
||||
/// Substituters used to download Lix, when enabled
|
||||
#[cfg_attr(
|
||||
feature = "cli",
|
||||
clap(long, env = "NIX_INSTALLER_SUBSTITUTERS", default_values = LIX_DEFAULT_SUBSTITUTERS)
|
||||
)]
|
||||
pub substituters: Vec<Url>,
|
||||
|
||||
/// Trusted signing keys for substituters
|
||||
#[cfg_attr(
|
||||
feature = "cli",
|
||||
clap(long, env = "NIX_INSTALLER_TRUSTED_KEYS", default_values = LIX_DEFAULT_SUBSTITUTER_KEYS)
|
||||
)]
|
||||
pub substituter_trusted_keys: Vec<String>,
|
||||
|
||||
/// Download Lix from a substituter instead of an install tarball
|
||||
#[cfg_attr(feature = "cli", clap(long, env = "NIX_INSTALLER_USE_SUBSTITUTER"))]
|
||||
pub use_substituters: bool,
|
||||
|
||||
/// The Nix package URL
|
||||
#[cfg_attr(
|
||||
feature = "cli",
|
||||
|
@ -150,31 +180,34 @@ pub struct CommonSettings {
|
|||
#[cfg_attr(
|
||||
all(target_os = "macos", target_arch = "x86_64", feature = "cli"),
|
||||
clap(
|
||||
default_value = NIX_X64_64_DARWIN_URL,
|
||||
default_value = LIX_X86_64_DARWIN_URL,
|
||||
)
|
||||
)]
|
||||
#[cfg_attr(
|
||||
all(target_os = "macos", target_arch = "aarch64", feature = "cli"),
|
||||
clap(
|
||||
default_value = NIX_AARCH64_DARWIN_URL,
|
||||
default_value = LIX_AARCH64_DARWIN_URL,
|
||||
)
|
||||
)]
|
||||
#[cfg_attr(
|
||||
all(target_os = "linux", target_arch = "x86_64", feature = "cli"),
|
||||
clap(
|
||||
default_value = NIX_X64_64_LINUX_URL,
|
||||
default_value = LIX_X86_64_LINUX_URL,
|
||||
)
|
||||
)]
|
||||
// FIXME(i686): release i686 binaries again
|
||||
/*
|
||||
#[cfg_attr(
|
||||
all(target_os = "linux", target_arch = "x86", feature = "cli"),
|
||||
clap(
|
||||
default_value = NIX_I686_LINUX_URL,
|
||||
default_value = LIX_I686_LINUX_URL,
|
||||
)
|
||||
)]
|
||||
*/
|
||||
#[cfg_attr(
|
||||
all(target_os = "linux", target_arch = "aarch64", feature = "cli"),
|
||||
clap(
|
||||
default_value = NIX_AARCH64_LINUX_URL,
|
||||
default_value = LIX_AARCH64_LINUX_URL,
|
||||
)
|
||||
)]
|
||||
pub nix_package_url: UrlOrPath,
|
||||
|
@ -216,7 +249,6 @@ pub struct CommonSettings {
|
|||
)
|
||||
)]
|
||||
pub enable_flakes: bool,
|
||||
|
||||
}
|
||||
|
||||
impl CommonSettings {
|
||||
|
@ -231,21 +263,24 @@ impl CommonSettings {
|
|||
match (Architecture::host(), OperatingSystem::host()) {
|
||||
#[cfg(target_os = "linux")]
|
||||
(Architecture::X86_64, OperatingSystem::Linux) => {
|
||||
url = NIX_X64_64_LINUX_URL;
|
||||
url = LIX_X86_64_LINUX_URL;
|
||||
nix_build_user_prefix = "nixbld";
|
||||
nix_build_user_id_base = 30000;
|
||||
nix_build_user_count = 32;
|
||||
},
|
||||
// FIXME(i686): support i686-linux again
|
||||
/*
|
||||
#[cfg(target_os = "linux")]
|
||||
(Architecture::X86_32(_), OperatingSystem::Linux) => {
|
||||
url = NIX_I686_LINUX_URL;
|
||||
url = LIX_I686_LINUX_URL;
|
||||
nix_build_user_prefix = "nixbld";
|
||||
nix_build_user_id_base = 30000;
|
||||
nix_build_user_count = 32;
|
||||
},
|
||||
*/
|
||||
#[cfg(target_os = "linux")]
|
||||
(Architecture::Aarch64(_), OperatingSystem::Linux) => {
|
||||
url = NIX_AARCH64_LINUX_URL;
|
||||
url = LIX_AARCH64_LINUX_URL;
|
||||
nix_build_user_prefix = "nixbld";
|
||||
nix_build_user_id_base = 30000;
|
||||
nix_build_user_count = 32;
|
||||
|
@ -253,7 +288,7 @@ impl CommonSettings {
|
|||
#[cfg(target_os = "macos")]
|
||||
(Architecture::X86_64, OperatingSystem::MacOSX { .. })
|
||||
| (Architecture::X86_64, OperatingSystem::Darwin) => {
|
||||
url = NIX_X64_64_DARWIN_URL;
|
||||
url = LIX_X86_64_DARWIN_URL;
|
||||
nix_build_user_prefix = "_nixbld";
|
||||
nix_build_user_id_base = 300;
|
||||
nix_build_user_count = 32;
|
||||
|
@ -261,7 +296,7 @@ impl CommonSettings {
|
|||
#[cfg(target_os = "macos")]
|
||||
(Architecture::Aarch64(_), OperatingSystem::MacOSX { .. })
|
||||
| (Architecture::Aarch64(_), OperatingSystem::Darwin) => {
|
||||
url = NIX_AARCH64_DARWIN_URL;
|
||||
url = LIX_AARCH64_DARWIN_URL;
|
||||
nix_build_user_prefix = "_nixbld";
|
||||
nix_build_user_id_base = 300;
|
||||
nix_build_user_count = 32;
|
||||
|
@ -280,6 +315,15 @@ impl CommonSettings {
|
|||
nix_build_user_id_base,
|
||||
nix_build_user_count,
|
||||
nix_build_user_prefix: nix_build_user_prefix.to_string(),
|
||||
substituters: LIX_DEFAULT_SUBSTITUTERS
|
||||
.iter()
|
||||
.map(|s| s.parse())
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
substituter_trusted_keys: LIX_DEFAULT_SUBSTITUTER_KEYS
|
||||
.iter()
|
||||
.map(|s| s.to_string())
|
||||
.collect(),
|
||||
use_substituters: false,
|
||||
nix_package_url: url.parse()?,
|
||||
proxy: Default::default(),
|
||||
extra_conf: Default::default(),
|
||||
|
@ -298,6 +342,9 @@ impl CommonSettings {
|
|||
nix_build_user_prefix,
|
||||
nix_build_user_id_base,
|
||||
nix_build_user_count,
|
||||
substituters,
|
||||
substituter_trusted_keys,
|
||||
use_substituters,
|
||||
nix_package_url,
|
||||
proxy,
|
||||
extra_conf,
|
||||
|
@ -331,6 +378,15 @@ impl CommonSettings {
|
|||
"nix_build_user_count".into(),
|
||||
serde_json::to_value(nix_build_user_count)?,
|
||||
);
|
||||
map.insert("substituters".into(), serde_json::to_value(substituters)?);
|
||||
map.insert(
|
||||
"substituter_trusted_keys".into(),
|
||||
serde_json::to_value(substituter_trusted_keys)?,
|
||||
);
|
||||
map.insert(
|
||||
"use_substituters".into(),
|
||||
serde_json::to_value(use_substituters)?,
|
||||
);
|
||||
map.insert(
|
||||
"nix_package_url".into(),
|
||||
serde_json::to_value(nix_package_url)?,
|
||||
|
|
27
tests/fixtures/linux/linux.json
vendored
27
tests/fixtures/linux/linux.json
vendored
|
@ -17,15 +17,17 @@
|
|||
"action": {
|
||||
"action": "provision_nix",
|
||||
"fetch_nix": {
|
||||
"action": {
|
||||
"url_or_path": {
|
||||
"Url": "https://releases.nixos.org/nix/nix-2.17.0/nix-2.17.0-x86_64-linux.tar.xz"
|
||||
"FromTarball": {
|
||||
"action": {
|
||||
"url_or_path": {
|
||||
"Url": "https://releases.nixos.org/nix/nix-2.17.0/nix-2.17.0-x86_64-linux.tar.xz"
|
||||
},
|
||||
"dest": "/nix/temp-install-dir",
|
||||
"proxy": null,
|
||||
"ssl_cert_file": null
|
||||
},
|
||||
"dest": "/nix/temp-install-dir",
|
||||
"proxy": null,
|
||||
"ssl_cert_file": null
|
||||
},
|
||||
"state": "Uncompleted"
|
||||
"state": "Uncompleted"
|
||||
}
|
||||
},
|
||||
"delete_users": [],
|
||||
"create_group": {
|
||||
|
@ -409,6 +411,15 @@
|
|||
"nix_build_user_count": 0,
|
||||
"nix_build_user_prefix": "nixbld",
|
||||
"nix_build_user_id_base": 30000,
|
||||
"substituters": [
|
||||
"https://cache.nixos.org/",
|
||||
"https://cache.lix.systems/"
|
||||
],
|
||||
"substituter_trusted_keys": [
|
||||
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=",
|
||||
"cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o="
|
||||
],
|
||||
"use_substituters": false,
|
||||
"nix_package_url": {
|
||||
"Url": "https://releases.nixos.org/nix/nix-2.17.0/nix-2.17.0-x86_64-linux.tar.xz"
|
||||
},
|
||||
|
|
27
tests/fixtures/linux/steam-deck.json
vendored
27
tests/fixtures/linux/steam-deck.json
vendored
|
@ -61,15 +61,17 @@
|
|||
"action": {
|
||||
"action": "provision_nix",
|
||||
"fetch_nix": {
|
||||
"action": {
|
||||
"url_or_path": {
|
||||
"Url": "https://releases.nixos.org/nix/nix-2.17.0/nix-2.17.0-x86_64-linux.tar.xz"
|
||||
"FromTarball": {
|
||||
"action": {
|
||||
"url_or_path": {
|
||||
"Url": "https://releases.nixos.org/nix/nix-2.17.0/nix-2.17.0-x86_64-linux.tar.xz"
|
||||
},
|
||||
"dest": "/nix/temp-install-dir",
|
||||
"proxy": null,
|
||||
"ssl_cert_file": null
|
||||
},
|
||||
"dest": "/nix/temp-install-dir",
|
||||
"proxy": null,
|
||||
"ssl_cert_file": null
|
||||
},
|
||||
"state": "Uncompleted"
|
||||
"state": "Uncompleted"
|
||||
}
|
||||
},
|
||||
"delete_users": [],
|
||||
"create_group": {
|
||||
|
@ -393,6 +395,15 @@
|
|||
"nix_build_user_count": 0,
|
||||
"nix_build_user_prefix": "nixbld",
|
||||
"nix_build_user_id_base": 30000,
|
||||
"substituters": [
|
||||
"https://cache.nixos.org/",
|
||||
"https://cache.lix.systems/"
|
||||
],
|
||||
"substituter_trusted_keys": [
|
||||
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=",
|
||||
"cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o="
|
||||
],
|
||||
"use_substituters": false,
|
||||
"nix_package_url": {
|
||||
"Url": "https://releases.nixos.org/nix/nix-2.17.0/nix-2.17.0-x86_64-linux.tar.xz"
|
||||
},
|
||||
|
|
27
tests/fixtures/macos/macos.json
vendored
27
tests/fixtures/macos/macos.json
vendored
|
@ -87,15 +87,17 @@
|
|||
"action": {
|
||||
"action": "provision_nix",
|
||||
"fetch_nix": {
|
||||
"action": {
|
||||
"url_or_path": {
|
||||
"Url": "https://releases.nixos.org/nix/nix-2.17.0/nix-2.17.0-x86_64-darwin.tar.xz"
|
||||
"FromTarball": {
|
||||
"action": {
|
||||
"url_or_path": {
|
||||
"Url": "https://releases.nixos.org/nix/nix-2.17.0/nix-2.17.0-x86_64-darwin.tar.xz"
|
||||
},
|
||||
"dest": "/nix/temp-install-dir",
|
||||
"proxy": null,
|
||||
"ssl_cert_file": null
|
||||
},
|
||||
"dest": "/nix/temp-install-dir",
|
||||
"proxy": null,
|
||||
"ssl_cert_file": null
|
||||
},
|
||||
"state": "Uncompleted"
|
||||
"state": "Uncompleted"
|
||||
}
|
||||
},
|
||||
"delete_users_in_group": null,
|
||||
"create_group": {
|
||||
|
@ -420,6 +422,15 @@
|
|||
"nix_build_user_count": 32,
|
||||
"nix_build_user_prefix": "_nixbld",
|
||||
"nix_build_user_id_base": 300,
|
||||
"substituters": [
|
||||
"https://cache.nixos.org/",
|
||||
"https://cache.lix.systems/"
|
||||
],
|
||||
"substituter_trusted_keys": [
|
||||
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=",
|
||||
"cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o="
|
||||
],
|
||||
"use_substituters": false,
|
||||
"nix_package_url": {
|
||||
"Url": "https://releases.nixos.org/nix/nix-2.17.0/nix-2.17.0-x86_64-darwin.tar.xz"
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue