forked from lix-project/lix-installer
BUGFIX: Call nix-store --load-db
& add sandboxed Qemu tests (#138)
* Add metadata do Cargo.toml * Add docs link * wip * Get it mostly working * Handle empty channels, local files, offline mode * Get them working * Expand CONTRIBUTING.md * Expand more * Correct some formatting/mistypes * More notes about steps * Correct speeling * Improve ubuntu naming * Add note about specific branch/checkout testing * Review corrections * Change match to if
This commit is contained in:
parent
44b9e623cc
commit
3706bd387a
11 changed files with 588 additions and 36 deletions
132
CONTRIBUTING.md
132
CONTRIBUTING.md
|
@ -74,7 +74,7 @@ Create an issue on [the issue page](https://github.com/DeterminateSystems/nix-in
|
||||||
It should contain:
|
It should contain:
|
||||||
|
|
||||||
1. Your OS (Linux, Mac) and architecture (x86_64, aarch64)
|
1. Your OS (Linux, Mac) and architecture (x86_64, aarch64)
|
||||||
2. Your `nix-installer`` version (`nix-installer --version`)
|
2. Your `nix-installer` version (`nix-installer --version`)
|
||||||
3. The thing you tried to run (eg `nix-installer`)
|
3. The thing you tried to run (eg `nix-installer`)
|
||||||
4. What happened (the output of the command, please)
|
4. What happened (the output of the command, please)
|
||||||
5. What you expected to happen
|
5. What you expected to happen
|
||||||
|
@ -107,6 +107,136 @@ Please open an [issue](https://github.com/DeterminateSystems/nix-installer/issue
|
||||||
to chat about your contribution and figure out how to best integrate it into
|
to chat about your contribution and figure out how to best integrate it into
|
||||||
the project.
|
the project.
|
||||||
|
|
||||||
|
# Development
|
||||||
|
|
||||||
|
Some snippets or workflows for development.
|
||||||
|
|
||||||
|
|
||||||
|
## Direnv support
|
||||||
|
|
||||||
|
While `nix develop` should work perfectly fine for development, contributors may prefer to enable [`direnv`](https://direnv.net/) or [`nix-direnv`](https://github.com/nix-community/nix-direnv) support.
|
||||||
|
|
||||||
|
From the project folder:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
direnv allow
|
||||||
|
```
|
||||||
|
|
||||||
|
If using an editor, it may be preferable to adopt an addon to enter the environment:
|
||||||
|
|
||||||
|
* [`vim`](https://github.com/direnv/direnv.vim)
|
||||||
|
* [VSCode](https://marketplace.visualstudio.com/items?itemName=mkhl.direnv)
|
||||||
|
|
||||||
|
|
||||||
|
## Testing Installs
|
||||||
|
|
||||||
|
If you're hacking on `nix-installer`, you likely already have Nix and cannot test locally.
|
||||||
|
|
||||||
|
> That's probably a good thing! You should test in a sandbox.
|
||||||
|
|
||||||
|
Automated [`qemu` tests][#qemu-vm-tests] exist and should be preferred for oneshot testing of changes.
|
||||||
|
|
||||||
|
For interactive testing, tools like [`libvirt`](https://libvirt.org/) via [`virt-manager`](https://virt-manager.org/) or [`vagrant`](https://www.vagrantup.com/) can be used to spin up machines and run experiments.
|
||||||
|
|
||||||
|
When running such interactive tests, consider creating a snapshot of the VM right before running the installer, so you can quickly roll back if something happens.
|
||||||
|
|
||||||
|
In general, it's a good idea to test on the closest you can get to the desired target environment. For example, when testing the Steam Deck planner it's a good idea to run that test in a Steam Deck VM as described in detail in the planner.
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><strong>Adding a planner for specific hardware?</strong></summary>
|
||||||
|
|
||||||
|
Please include an full guide on how to create the best known virtual testing environment for that device.
|
||||||
|
|
||||||
|
**A link is not sufficient, it may break.** Please provide a full summary of steps to take, link to any original source and give them credit if it is appropriate.
|
||||||
|
|
||||||
|
It's perfectly fine if they are manual or labor intensive, as these should be a one time thing and get snapshotted prior to running tests.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## `qemu` VM tests
|
||||||
|
|
||||||
|
In `nix/tests/vm-test` there exists some Nix derivations which we expose in the flake via `hydraJobs`.
|
||||||
|
|
||||||
|
These should be visible in `nix flake show`:
|
||||||
|
|
||||||
|
```
|
||||||
|
❯ nix flake show
|
||||||
|
warning: Git tree '/home/ana/git/determinatesystems/nix-installer' is dirty
|
||||||
|
git+file:///home/ana/git/determinatesystems/nix-installer
|
||||||
|
# ...
|
||||||
|
├───hydraJobs
|
||||||
|
│ └───vm-test
|
||||||
|
│ ├───all
|
||||||
|
│ │ └───x86_64-linux
|
||||||
|
│ │ └───install-default: derivation 'all'
|
||||||
|
│ ├───fedora-v36
|
||||||
|
│ │ └───x86_64-linux
|
||||||
|
│ │ └───install-default: derivation 'installer-test-fedora-v36-install-default'
|
||||||
|
│ ├───rhel-v7
|
||||||
|
│ │ └───x86_64-linux
|
||||||
|
│ │ └───install-default: derivation 'installer-test-rhel-v7-install-default'
|
||||||
|
│ ├───rhel-v8
|
||||||
|
│ │ └───x86_64-linux
|
||||||
|
│ │ └───install-default: derivation 'installer-test-rhel-v8-install-default'
|
||||||
|
│ ├───rhel-v9
|
||||||
|
│ │ └───x86_64-linux
|
||||||
|
│ │ └───install-default: derivation 'installer-test-rhel-v9-install-default'
|
||||||
|
│ └───ubuntu-v22_04
|
||||||
|
│ └───x86_64-linux
|
||||||
|
│ └───install-default: derivation 'installer-test-ubuntu-v22_04-install-default'
|
||||||
|
```
|
||||||
|
|
||||||
|
To run all of the currently supported tests:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix build .#hydraJobs.vm-test.all.x86_64-linux.install-default -L
|
||||||
|
```
|
||||||
|
|
||||||
|
To run a specific distribution listed in the `nix flake show` output:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix build .#hydraJobs.vm-test.rhel-v7.x86_64-linux.install-default -L
|
||||||
|
```
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
||||||
|
Notice how `rhel-v7` has a `v7`, not just `7`? That's so the test output shows correctly, as Nix will interpret the first `-\d` (eg `-7`, `-123213`) as a version, and not show it in the output.
|
||||||
|
|
||||||
|
Using `v7` instead turns:
|
||||||
|
|
||||||
|
```
|
||||||
|
# ...
|
||||||
|
installer-test-rhel> Unpacking Vagrant box /nix/store/8maga4w267f77agb93inbg54whh5lxhn-libvirt.box...
|
||||||
|
installer-test-rhel> Vagrantfile
|
||||||
|
installer-test-rhel> box.img
|
||||||
|
installer-test-rhel> info.json
|
||||||
|
installer-test-rhel> metadata.json
|
||||||
|
installer-test-rhel> Formatting './disk.qcow2', fmt=qcow2 cluster_size=65536 extended_l2=off compression_type=zlib size=137438953472 backing_file=./box.img backing_fmt=qcow2 lazy_refcounts=off refcount_bits=16
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Into this:
|
||||||
|
|
||||||
|
```
|
||||||
|
# ...
|
||||||
|
installer-test-rhel-v7-install-default> Unpacking Vagrant box /nix/store/8maga4w267f77agb93inbg54whh5lxhn-libvirt.box...
|
||||||
|
installer-test-rhel-v7-install-default> Vagrantfile
|
||||||
|
installer-test-rhel-v7-install-default> box.img
|
||||||
|
installer-test-rhel-v7-install-default> info.json
|
||||||
|
installer-test-rhel-v7-install-default> metadata.json
|
||||||
|
installer-test-rhel-v7-install-default> Formatting './disk.qcow2', fmt=qcow2 cluster_size=65536 extended_l2=off compression_type=zlib size=137438953472 backing_file=./box.img backing_fmt=qcow2 lazy_refcounts=off refcount_bits=16
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
# Who maintains `nix-installer` and why?
|
# Who maintains `nix-installer` and why?
|
||||||
|
|
||||||
|
|
55
flake.lock
55
flake.lock
|
@ -21,6 +21,22 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"lowdown-src": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1633514407,
|
||||||
|
"narHash": "sha256-Dw32tiMjdK9t3ETl5fzGrutQTzh2rufgZV4A/BbxuD4=",
|
||||||
|
"owner": "kristapsdz",
|
||||||
|
"repo": "lowdown",
|
||||||
|
"rev": "d2c2b44ff6c27b936ec27358a2653caaef8f73b8",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "kristapsdz",
|
||||||
|
"repo": "lowdown",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"naersk": {
|
"naersk": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
|
@ -41,6 +57,28 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"nix": {
|
||||||
|
"inputs": {
|
||||||
|
"lowdown-src": "lowdown-src",
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"nixpkgs-regression": "nixpkgs-regression"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1671624594,
|
||||||
|
"narHash": "sha256-OOPFRuEGzLRIaMHw93fFlqVTT+vAwlOWQut/emCnJgc=",
|
||||||
|
"owner": "nixos",
|
||||||
|
"repo": "nix",
|
||||||
|
"rev": "b1223e1b621ed4ad11562f0ef65c65d1c78c5e4b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nixos",
|
||||||
|
"repo": "nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1671323474,
|
"lastModified": 1671323474,
|
||||||
|
@ -57,10 +95,27 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"nixpkgs-regression": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1643052045,
|
||||||
|
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"fenix": "fenix",
|
"fenix": "fenix",
|
||||||
"naersk": "naersk",
|
"naersk": "naersk",
|
||||||
|
"nix": "nix",
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
26
flake.nix
26
flake.nix
|
@ -13,6 +13,12 @@
|
||||||
url = "github:nix-community/naersk";
|
url = "github:nix-community/naersk";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
nix = {
|
||||||
|
url = "github:nixos/nix";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs =
|
outputs =
|
||||||
|
@ -20,16 +26,19 @@
|
||||||
, nixpkgs
|
, nixpkgs
|
||||||
, fenix
|
, fenix
|
||||||
, naersk
|
, naersk
|
||||||
|
, nix
|
||||||
, ...
|
, ...
|
||||||
} @ inputs:
|
} @ inputs:
|
||||||
let
|
let
|
||||||
supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
|
supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
|
||||||
|
|
||||||
forAllSystems = f: nixpkgs.lib.genAttrs supportedSystems (system: f rec {
|
forAllSystems = f: nixpkgs.lib.genAttrs supportedSystems (system: (forSystem system f));
|
||||||
|
|
||||||
|
forSystem = system: f: f rec {
|
||||||
inherit system;
|
inherit system;
|
||||||
pkgs = import nixpkgs { inherit system; overlays = [ self.overlays.default ]; };
|
pkgs = import nixpkgs { inherit system; overlays = [ self.overlays.default ]; };
|
||||||
lib = pkgs.lib;
|
lib = pkgs.lib;
|
||||||
});
|
};
|
||||||
|
|
||||||
fenixToolchain = system: with fenix.packages.${system};
|
fenixToolchain = system: with fenix.packages.${system};
|
||||||
combine ([
|
combine ([
|
||||||
|
@ -55,7 +64,11 @@
|
||||||
sharedAttrs = {
|
sharedAttrs = {
|
||||||
pname = "nix-installer";
|
pname = "nix-installer";
|
||||||
version = "0.0.0-unreleased";
|
version = "0.0.0-unreleased";
|
||||||
src = self;
|
src = builtins.path {
|
||||||
|
name = "nix-installer-source";
|
||||||
|
path = self;
|
||||||
|
filter = (path: type: baseNameOf path != "nix" || baseNameOf path != ".github");
|
||||||
|
};
|
||||||
|
|
||||||
nativeBuildInputs = with final; [ ];
|
nativeBuildInputs = with final; [ ];
|
||||||
buildInputs = with final; [ ] ++ lib.optionals (final.stdenv.isDarwin) (with final.darwin.apple_sdk.frameworks; [
|
buildInputs = with final; [ ] ++ lib.optionals (final.stdenv.isDarwin) (with final.darwin.apple_sdk.frameworks; [
|
||||||
|
@ -163,5 +176,12 @@
|
||||||
} // nixpkgs.lib.optionalAttrs (pkgs.stdenv.isDarwin) {
|
} // nixpkgs.lib.optionalAttrs (pkgs.stdenv.isDarwin) {
|
||||||
default = pkgs.nix-installer;
|
default = pkgs.nix-installer;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
hydraJobs = {
|
||||||
|
vm-test = import ./nix/tests/vm-test {
|
||||||
|
inherit forSystem;
|
||||||
|
inherit (nix.hydraJobs) binaryTarball;
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
222
nix/tests/vm-test/default.nix
Normal file
222
nix/tests/vm-test/default.nix
Normal file
|
@ -0,0 +1,222 @@
|
||||||
|
# Largely derived from https://github.com/NixOS/nix/blob/14f7dae3e4eb0c34192d0077383a7f2a2d630129/tests/installer/default.nix
|
||||||
|
{ forSystem, binaryTarball }:
|
||||||
|
|
||||||
|
let
|
||||||
|
|
||||||
|
installScripts = {
|
||||||
|
install-default = {
|
||||||
|
script = ''
|
||||||
|
NIX_PATH=$(readlink -f nix.tar.xz)
|
||||||
|
RUST_BACKTRACE="full" ./nix-installer install --logger pretty --log-directive nix_installer=trace --channel --nix-package-url "file://$NIX_PATH" --no-confirm
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
disableSELinux = "sudo setenforce 0";
|
||||||
|
|
||||||
|
images = {
|
||||||
|
|
||||||
|
# End of standard support https://wiki.ubuntu.com/Releases
|
||||||
|
/*
|
||||||
|
"ubuntu-v14_04" = {
|
||||||
|
image = import <nix/fetchurl.nix> {
|
||||||
|
url = "https://app.vagrantup.com/ubuntu/boxes/trusty64/versions/20190514.0.0/providers/virtualbox.box";
|
||||||
|
hash = "sha256-iUUXyRY8iW7DGirb0zwGgf1fRbLA7wimTJKgP7l/OQ8=";
|
||||||
|
};
|
||||||
|
rootDisk = "box-disk1.vmdk";
|
||||||
|
system = "x86_64-linux";
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
|
# End of standard support https://wiki.ubuntu.com/Releases
|
||||||
|
/* "ubuntu-v16_04" = {
|
||||||
|
image = import <nix/fetchurl.nix> {
|
||||||
|
url = "https://app.vagrantup.com/generic/boxes/ubuntu1604/versions/4.1.12/providers/libvirt.box";
|
||||||
|
hash = "sha256-lO4oYQR2tCh5auxAYe6bPOgEqOgv3Y3GC1QM1tEEEU8=";
|
||||||
|
};
|
||||||
|
rootDisk = "box.img";
|
||||||
|
system = "x86_64-linux";
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
|
"ubuntu-v22_04" = {
|
||||||
|
image = import <nix/fetchurl.nix> {
|
||||||
|
url = "https://app.vagrantup.com/generic/boxes/ubuntu2204/versions/4.1.12/providers/libvirt.box";
|
||||||
|
hash = "sha256-HNll0Qikw/xGIcogni5lz01vUv+R3o8xowP2EtqjuUQ=";
|
||||||
|
};
|
||||||
|
rootDisk = "box.img";
|
||||||
|
system = "x86_64-linux";
|
||||||
|
};
|
||||||
|
|
||||||
|
"fedora-v36" = {
|
||||||
|
image = import <nix/fetchurl.nix> {
|
||||||
|
url = "https://app.vagrantup.com/generic/boxes/fedora36/versions/4.1.12/providers/libvirt.box";
|
||||||
|
hash = "sha256-rxPgnDnFkTDwvdqn2CV3ZUo3re9AdPtSZ9SvOHNvaks=";
|
||||||
|
};
|
||||||
|
rootDisk = "box.img";
|
||||||
|
system = "x86_64-linux";
|
||||||
|
postBoot = disableSELinux;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Currently fails with 'error while loading shared libraries:
|
||||||
|
# libsodium.so.23: cannot stat shared object: Invalid argument'.
|
||||||
|
/*
|
||||||
|
"rhel-v6" = {
|
||||||
|
image = import <nix/fetchurl.nix> {
|
||||||
|
url = "https://app.vagrantup.com/generic/boxes/rhel6/versions/4.1.12/providers/libvirt.box";
|
||||||
|
hash = "sha256-QwzbvRoRRGqUCQptM7X/InRWFSP2sqwRt2HaaO6zBGM=";
|
||||||
|
};
|
||||||
|
rootDisk = "box.img";
|
||||||
|
system = "x86_64-linux";
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
|
"rhel-v7" = {
|
||||||
|
image = import <nix/fetchurl.nix> {
|
||||||
|
url = "https://app.vagrantup.com/generic/boxes/rhel7/versions/4.1.12/providers/libvirt.box";
|
||||||
|
hash = "sha256-b4afnqKCO9oWXgYHb9DeQ2berSwOjS27rSd9TxXDc/U=";
|
||||||
|
};
|
||||||
|
rootDisk = "box.img";
|
||||||
|
postBoot = disableSELinux;
|
||||||
|
system = "x86_64-linux";
|
||||||
|
};
|
||||||
|
|
||||||
|
"rhel-v8" = {
|
||||||
|
image = import <nix/fetchurl.nix> {
|
||||||
|
url = "https://app.vagrantup.com/generic/boxes/rhel8/versions/4.1.12/providers/libvirt.box";
|
||||||
|
hash = "sha256-zFOPjSputy1dPgrQRixBXmlyN88cAKjJ21VvjSWUCUY=";
|
||||||
|
};
|
||||||
|
rootDisk = "box.img";
|
||||||
|
system = "x86_64-linux";
|
||||||
|
postBoot = disableSELinux;
|
||||||
|
};
|
||||||
|
|
||||||
|
"rhel-v9" = {
|
||||||
|
image = import <nix/fetchurl.nix> {
|
||||||
|
url = "https://app.vagrantup.com/generic/boxes/rhel9/versions/4.1.12/providers/libvirt.box";
|
||||||
|
hash = "sha256-vL/FbB3kK1rcSaR627nWmScYGKGk4seSmAdq6N5diMg=";
|
||||||
|
};
|
||||||
|
rootDisk = "box.img";
|
||||||
|
system = "x86_64-linux";
|
||||||
|
postBoot = disableSELinux;
|
||||||
|
extraQemuOpts = "-cpu Westmere-v2";
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
makeTest = imageName: testName:
|
||||||
|
let image = images.${imageName}; in
|
||||||
|
with (forSystem image.system ({ system, pkgs, ... }: pkgs));
|
||||||
|
runCommand
|
||||||
|
"installer-test-${imageName}-${testName}"
|
||||||
|
{
|
||||||
|
buildInputs = [ qemu_kvm openssh ];
|
||||||
|
image = image.image;
|
||||||
|
postBoot = image.postBoot or "";
|
||||||
|
installScript = installScripts.${testName}.script;
|
||||||
|
installer = nix-installer-static;
|
||||||
|
binaryTarball = binaryTarball.${system};
|
||||||
|
}
|
||||||
|
''
|
||||||
|
shopt -s nullglob
|
||||||
|
|
||||||
|
echo "Unpacking Vagrant box $image..."
|
||||||
|
tar xvf $image
|
||||||
|
|
||||||
|
image_type=$(qemu-img info ${image.rootDisk} | sed 's/file format: \(.*\)/\1/; t; d')
|
||||||
|
|
||||||
|
qemu-img create -b ./${image.rootDisk} -F "$image_type" -f qcow2 ./disk.qcow2
|
||||||
|
|
||||||
|
extra_qemu_opts="${image.extraQemuOpts or ""}"
|
||||||
|
|
||||||
|
# Add the config disk, required by the Ubuntu images.
|
||||||
|
config_drive=$(echo *configdrive.vmdk || true)
|
||||||
|
if [[ -n $config_drive ]]; then
|
||||||
|
extra_qemu_opts+=" -drive id=disk2,file=$config_drive,if=virtio"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Starting qemu..."
|
||||||
|
qemu-kvm -m 4096 -nographic \
|
||||||
|
-drive id=disk1,file=./disk.qcow2,if=virtio \
|
||||||
|
-netdev user,id=net0,restrict=yes,hostfwd=tcp::20022-:22 -device virtio-net-pci,netdev=net0 \
|
||||||
|
$extra_qemu_opts &
|
||||||
|
qemu_pid=$!
|
||||||
|
trap "kill $qemu_pid" EXIT
|
||||||
|
|
||||||
|
if ! [ -e ./vagrant_insecure_key ]; then
|
||||||
|
cp ${./vagrant_insecure_key} vagrant_insecure_key
|
||||||
|
fi
|
||||||
|
|
||||||
|
chmod 0400 ./vagrant_insecure_key
|
||||||
|
|
||||||
|
ssh_opts="-o StrictHostKeyChecking=no -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa -i ./vagrant_insecure_key"
|
||||||
|
ssh="ssh -p 20022 -q $ssh_opts vagrant@localhost"
|
||||||
|
|
||||||
|
echo "Waiting for SSH..."
|
||||||
|
for ((i = 0; i < 120; i++)); do
|
||||||
|
echo "[ssh] Trying to connect..."
|
||||||
|
if $ssh -- true; then
|
||||||
|
echo "[ssh] Connected!"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
if ! kill -0 $qemu_pid; then
|
||||||
|
echo "qemu died unexpectedly"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -n $postBoot ]]; then
|
||||||
|
echo "Running post-boot commands..."
|
||||||
|
$ssh "set -ex; $postBoot"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Copying installer..."
|
||||||
|
scp -P 20022 $ssh_opts $installer/bin/nix-installer vagrant@localhost:nix-installer
|
||||||
|
|
||||||
|
echo "Copying nix tarball..."
|
||||||
|
scp -P 20022 $ssh_opts $binaryTarball/nix-*.tar.xz vagrant@localhost:nix.tar.xz
|
||||||
|
|
||||||
|
echo "Running installer..."
|
||||||
|
$ssh "set -eux; $installScript"
|
||||||
|
|
||||||
|
echo "Testing Nix installation..."
|
||||||
|
$ssh <<EOF
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
# FIXME: get rid of this; ideally ssh should just work.
|
||||||
|
source ~/.bash_profile || true
|
||||||
|
source ~/.bash_login || true
|
||||||
|
source ~/.profile || true
|
||||||
|
source /etc/bashrc || true
|
||||||
|
|
||||||
|
nix-env --version
|
||||||
|
nix --extra-experimental-features nix-command store ping
|
||||||
|
|
||||||
|
out=\$(nix-build --no-substitute -E 'derivation { name = "foo"; system = "x86_64-linux"; builder = "/bin/sh"; args = ["-c" "echo foobar > \$out"]; }')
|
||||||
|
[[ \$(cat \$out) = foobar ]]
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "Done!"
|
||||||
|
touch $out
|
||||||
|
'';
|
||||||
|
|
||||||
|
vm-tests = builtins.mapAttrs
|
||||||
|
(imageName: image:
|
||||||
|
{
|
||||||
|
${image.system} = builtins.mapAttrs
|
||||||
|
(testName: test:
|
||||||
|
makeTest imageName testName
|
||||||
|
)
|
||||||
|
installScripts;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
images;
|
||||||
|
|
||||||
|
in
|
||||||
|
vm-tests // {
|
||||||
|
all."x86_64-linux".install-default = (with (forSystem "x86_64-linux" ({ system, pkgs, ... }: pkgs)); pkgs.releaseTools.aggregate {
|
||||||
|
name = "all";
|
||||||
|
constituents = pkgs.lib.mapAttrsToList (name: value: value."x86_64-linux".install-default) vm-tests;
|
||||||
|
});
|
||||||
|
}
|
27
nix/tests/vm-test/vagrant_insecure_key
Normal file
27
nix/tests/vm-test/vagrant_insecure_key
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEogIBAAKCAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzI
|
||||||
|
w+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoP
|
||||||
|
kcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2
|
||||||
|
hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NO
|
||||||
|
Td0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcW
|
||||||
|
yLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQIBIwKCAQEA4iqWPJXtzZA68mKd
|
||||||
|
ELs4jJsdyky+ewdZeNds5tjcnHU5zUYE25K+ffJED9qUWICcLZDc81TGWjHyAqD1
|
||||||
|
Bw7XpgUwFgeUJwUlzQurAv+/ySnxiwuaGJfhFM1CaQHzfXphgVml+fZUvnJUTvzf
|
||||||
|
TK2Lg6EdbUE9TarUlBf/xPfuEhMSlIE5keb/Zz3/LUlRg8yDqz5w+QWVJ4utnKnK
|
||||||
|
iqwZN0mwpwU7YSyJhlT4YV1F3n4YjLswM5wJs2oqm0jssQu/BT0tyEXNDYBLEF4A
|
||||||
|
sClaWuSJ2kjq7KhrrYXzagqhnSei9ODYFShJu8UWVec3Ihb5ZXlzO6vdNQ1J9Xsf
|
||||||
|
4m+2ywKBgQD6qFxx/Rv9CNN96l/4rb14HKirC2o/orApiHmHDsURs5rUKDx0f9iP
|
||||||
|
cXN7S1uePXuJRK/5hsubaOCx3Owd2u9gD6Oq0CsMkE4CUSiJcYrMANtx54cGH7Rk
|
||||||
|
EjFZxK8xAv1ldELEyxrFqkbE4BKd8QOt414qjvTGyAK+OLD3M2QdCQKBgQDtx8pN
|
||||||
|
CAxR7yhHbIWT1AH66+XWN8bXq7l3RO/ukeaci98JfkbkxURZhtxV/HHuvUhnPLdX
|
||||||
|
3TwygPBYZFNo4pzVEhzWoTtnEtrFueKxyc3+LjZpuo+mBlQ6ORtfgkr9gBVphXZG
|
||||||
|
YEzkCD3lVdl8L4cw9BVpKrJCs1c5taGjDgdInQKBgHm/fVvv96bJxc9x1tffXAcj
|
||||||
|
3OVdUN0UgXNCSaf/3A/phbeBQe9xS+3mpc4r6qvx+iy69mNBeNZ0xOitIjpjBo2+
|
||||||
|
dBEjSBwLk5q5tJqHmy/jKMJL4n9ROlx93XS+njxgibTvU6Fp9w+NOFD/HvxB3Tcz
|
||||||
|
6+jJF85D5BNAG3DBMKBjAoGBAOAxZvgsKN+JuENXsST7F89Tck2iTcQIT8g5rwWC
|
||||||
|
P9Vt74yboe2kDT531w8+egz7nAmRBKNM751U/95P9t88EDacDI/Z2OwnuFQHCPDF
|
||||||
|
llYOUI+SpLJ6/vURRbHSnnn8a/XG+nzedGH5JGqEJNQsz+xT2axM0/W/CRknmGaJ
|
||||||
|
kda/AoGANWrLCz708y7VYgAtW2Uf1DPOIYMdvo6fxIB5i9ZfISgcJ/bbCUkFrhoH
|
||||||
|
+vq/5CIWxCPp0f85R4qxxQ5ihxJ0YDQT9Jpx4TMss4PSavPaBH3RXow5Ohe+bYoQ
|
||||||
|
NE5OgEXk2wVfZczCZpigBKbKZHNYcelXtTt/nP3rsCuGcM4h53s=
|
||||||
|
-----END RSA PRIVATE KEY-----
|
|
@ -1,6 +1,6 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use bytes::Buf;
|
use bytes::{Buf, Bytes};
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
use tracing::{span, Span};
|
use tracing::{span, Span};
|
||||||
|
|
||||||
|
@ -21,6 +21,15 @@ impl FetchAndUnpackNix {
|
||||||
// TODO(@hoverbear): Check URL exists?
|
// TODO(@hoverbear): Check URL exists?
|
||||||
// TODO(@hoverbear): Check tempdir exists
|
// TODO(@hoverbear): Check tempdir exists
|
||||||
|
|
||||||
|
match url.scheme() {
|
||||||
|
"https" | "http" | "file" => (),
|
||||||
|
_ => {
|
||||||
|
return Err(ActionError::Custom(Box::new(
|
||||||
|
FetchUrlError::UnknownUrlScheme,
|
||||||
|
)))
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
Ok(Self { url, dest }.into())
|
Ok(Self { url, dest }.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,13 +58,28 @@ impl Action for FetchAndUnpackNix {
|
||||||
async fn execute(&mut self) -> Result<(), ActionError> {
|
async fn execute(&mut self) -> Result<(), ActionError> {
|
||||||
let Self { url, dest } = self;
|
let Self { url, dest } = self;
|
||||||
|
|
||||||
|
let bytes = match url.scheme() {
|
||||||
|
"https" | "http" => {
|
||||||
let res = reqwest::get(url.clone())
|
let res = reqwest::get(url.clone())
|
||||||
.await
|
.await
|
||||||
.map_err(|e| ActionError::Custom(Box::new(FetchUrlError::Reqwest(e))))?;
|
.map_err(|e| ActionError::Custom(Box::new(FetchUrlError::Reqwest(e))))?;
|
||||||
let bytes = res
|
res.bytes()
|
||||||
.bytes()
|
|
||||||
.await
|
.await
|
||||||
.map_err(|e| ActionError::Custom(Box::new(FetchUrlError::Reqwest(e))))?;
|
.map_err(|e| ActionError::Custom(Box::new(FetchUrlError::Reqwest(e))))?
|
||||||
|
},
|
||||||
|
"file" => {
|
||||||
|
let buf = tokio::fs::read(url.path())
|
||||||
|
.await
|
||||||
|
.map_err(|e| ActionError::Read(PathBuf::from(url.path()), e))?;
|
||||||
|
Bytes::from(buf)
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
return Err(ActionError::Custom(Box::new(
|
||||||
|
FetchUrlError::UnknownUrlScheme,
|
||||||
|
)))
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// TODO(@Hoverbear): Pick directory
|
// TODO(@Hoverbear): Pick directory
|
||||||
tracing::trace!("Unpacking tar.xz");
|
tracing::trace!("Unpacking tar.xz");
|
||||||
let dest_clone = dest.clone();
|
let dest_clone = dest.clone();
|
||||||
|
@ -91,4 +115,6 @@ pub enum FetchUrlError {
|
||||||
),
|
),
|
||||||
#[error("Unarchiving error")]
|
#[error("Unarchiving error")]
|
||||||
Unarchive(#[source] std::io::Error),
|
Unarchive(#[source] std::io::Error),
|
||||||
|
#[error("Unknown url scheme")]
|
||||||
|
UnknownUrlScheme,
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ use tracing::{span, Span};
|
||||||
|
|
||||||
use crate::action::{Action, ActionDescription, ActionError, StatefulAction};
|
use crate::action::{Action, ActionDescription, ActionError, StatefulAction};
|
||||||
|
|
||||||
const DEST: &str = "/nix/store";
|
pub(crate) const DEST: &str = "/nix/";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Move an unpacked Nix at `src` to `/nix`
|
Move an unpacked Nix at `src` to `/nix`
|
||||||
|
@ -64,15 +64,23 @@ impl Action for MoveUnpackedNix {
|
||||||
);
|
);
|
||||||
let found_nix_path = found_nix_paths.into_iter().next().unwrap();
|
let found_nix_path = found_nix_paths.into_iter().next().unwrap();
|
||||||
let src_store = found_nix_path.join("store");
|
let src_store = found_nix_path.join("store");
|
||||||
let dest = Path::new(DEST);
|
let dest = Path::new(DEST).join("store");
|
||||||
tracing::trace!(src = %src_store.display(), dest = %dest.display(), "Renaming");
|
tracing::trace!(src = %src_store.display(), dest = %dest.display(), "Renaming");
|
||||||
tokio::fs::rename(src_store.clone(), dest)
|
tokio::fs::rename(&src_store, &dest)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| ActionError::Rename(src_store.clone(), dest.to_owned(), e))?;
|
.map_err(|e| ActionError::Rename(src_store.clone(), dest.to_owned(), e))?;
|
||||||
|
|
||||||
tokio::fs::remove_dir_all(src)
|
let src_reginfo = found_nix_path.join(".reginfo");
|
||||||
|
|
||||||
|
// Move_unpacked_nix expects it here
|
||||||
|
let dest_reginfo = Path::new(DEST).join(".reginfo");
|
||||||
|
tokio::fs::rename(&src_reginfo, &dest_reginfo)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| ActionError::Rename(src_store, dest.to_owned(), e))?;
|
.map_err(|e| ActionError::Rename(src_reginfo.clone(), dest_reginfo.to_owned(), e))?;
|
||||||
|
|
||||||
|
tokio::fs::remove_dir_all(&src)
|
||||||
|
.await
|
||||||
|
.map_err(|e| ActionError::Remove(src.clone(), e))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
action::{ActionError, StatefulAction},
|
action::{ActionError, StatefulAction},
|
||||||
execute_command, set_env,
|
execute_command, set_env,
|
||||||
|
@ -5,7 +7,7 @@ use crate::{
|
||||||
|
|
||||||
use glob::glob;
|
use glob::glob;
|
||||||
|
|
||||||
use tokio::process::Command;
|
use tokio::{io::AsyncWriteExt, process::Command};
|
||||||
use tracing::{span, Span};
|
use tracing::{span, Span};
|
||||||
|
|
||||||
use crate::action::{Action, ActionDescription};
|
use crate::action::{Action, ActionDescription};
|
||||||
|
@ -94,12 +96,87 @@ impl Action for SetupDefaultProfile {
|
||||||
)));
|
)));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
let reginfo_path =
|
||||||
|
Path::new(crate::action::base::move_unpacked_nix::DEST).join(".reginfo");
|
||||||
|
let reginfo = tokio::fs::read(®info_path)
|
||||||
|
.await
|
||||||
|
.map_err(|e| ActionError::Read(reginfo_path.to_path_buf(), e))?;
|
||||||
|
let mut load_db_command = Command::new(nix_pkg.join("bin/nix-store"));
|
||||||
|
load_db_command.process_group(0);
|
||||||
|
load_db_command.arg("--load-db");
|
||||||
|
load_db_command.stdin(std::process::Stdio::piped());
|
||||||
|
load_db_command.env(
|
||||||
|
"HOME",
|
||||||
|
dirs::home_dir().ok_or_else(|| {
|
||||||
|
ActionError::Custom(Box::new(SetupDefaultProfileError::NoRootHome))
|
||||||
|
})?,
|
||||||
|
);
|
||||||
|
let load_db_command_str = format!("{:?}", load_db_command.as_std());
|
||||||
|
tracing::trace!(
|
||||||
|
"Executing `{load_db_command_str}` with stdin from `{}`",
|
||||||
|
reginfo_path.display()
|
||||||
|
);
|
||||||
|
let mut handle = load_db_command
|
||||||
|
.spawn()
|
||||||
|
.map_err(|e| ActionError::Command(e))?;
|
||||||
|
|
||||||
|
let mut stdin = handle.stdin.take().unwrap();
|
||||||
|
stdin
|
||||||
|
.write_all(®info)
|
||||||
|
.await
|
||||||
|
.map_err(|e| ActionError::Write(PathBuf::from("/dev/stdin"), e))?;
|
||||||
|
stdin
|
||||||
|
.flush()
|
||||||
|
.await
|
||||||
|
.map_err(|e| ActionError::Write(PathBuf::from("/dev/stdin"), e))?;
|
||||||
|
drop(stdin);
|
||||||
|
tracing::trace!(
|
||||||
|
"Wrote `{}` to stdin of `nix-store --load-db`",
|
||||||
|
reginfo_path.display()
|
||||||
|
);
|
||||||
|
|
||||||
|
let output = handle
|
||||||
|
.wait_with_output()
|
||||||
|
.await
|
||||||
|
.map_err(ActionError::Command)?;
|
||||||
|
if !output.status.success() {
|
||||||
|
return Err(ActionError::Command(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::Other,
|
||||||
|
format!(
|
||||||
|
"Command `{load_db_command_str}` failed status, stderr:\n{}\n",
|
||||||
|
String::from_utf8(output.stderr)
|
||||||
|
.unwrap_or_else(|_e| String::from("<Non-UTF-8>"))
|
||||||
|
),
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Install `nix` itself into the store
|
// Install `nix` itself into the store
|
||||||
execute_command(
|
execute_command(
|
||||||
Command::new(nix_pkg.join("bin/nix-env"))
|
Command::new(nix_pkg.join("bin/nix-env"))
|
||||||
.process_group(0)
|
.process_group(0)
|
||||||
.arg("-i")
|
.arg("-i")
|
||||||
.arg(&nix_pkg)
|
.arg(&nix_pkg)
|
||||||
|
.stdin(std::process::Stdio::null())
|
||||||
|
.env(
|
||||||
|
"HOME",
|
||||||
|
dirs::home_dir().ok_or_else(|| {
|
||||||
|
ActionError::Custom(Box::new(SetupDefaultProfileError::NoRootHome))
|
||||||
|
})?,
|
||||||
|
)
|
||||||
|
.env(
|
||||||
|
"NIX_SSL_CERT_FILE",
|
||||||
|
nss_ca_cert_pkg.join("etc/ssl/certs/ca-bundle.crt"),
|
||||||
|
), /* This is apparently load bearing... */
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|e| ActionError::Command(e))?;
|
||||||
|
|
||||||
|
// Install `nix` itself into the store
|
||||||
|
execute_command(
|
||||||
|
Command::new(nix_pkg.join("bin/nix-env"))
|
||||||
|
.process_group(0)
|
||||||
.arg("-i")
|
.arg("-i")
|
||||||
.arg(&nss_ca_cert_pkg)
|
.arg(&nss_ca_cert_pkg)
|
||||||
.stdin(std::process::Stdio::null())
|
.stdin(std::process::Stdio::null())
|
||||||
|
@ -117,19 +194,6 @@ impl Action for SetupDefaultProfile {
|
||||||
.await
|
.await
|
||||||
.map_err(|e| ActionError::Command(e))?;
|
.map_err(|e| ActionError::Command(e))?;
|
||||||
|
|
||||||
// Install `nss-cacert` into the store
|
|
||||||
// execute_command(
|
|
||||||
// Command::new(nix_pkg.join("bin/nix-env"))
|
|
||||||
// .arg("-i")
|
|
||||||
// .arg(&nss_ca_cert_pkg)
|
|
||||||
// .env(
|
|
||||||
// "NIX_SSL_CERT_FILE",
|
|
||||||
// nss_ca_cert_pkg.join("etc/ssl/certs/ca-bundle.crt"),
|
|
||||||
// ),
|
|
||||||
// )
|
|
||||||
// .await
|
|
||||||
// .map_err(|e| SetupDefaultProfileError::Command(e).boxed())?;
|
|
||||||
|
|
||||||
set_env(
|
set_env(
|
||||||
"NIX_SSL_CERT_FILE",
|
"NIX_SSL_CERT_FILE",
|
||||||
"/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt",
|
"/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt",
|
||||||
|
|
|
@ -290,7 +290,7 @@ pub enum ActionError {
|
||||||
std::path::PathBuf,
|
std::path::PathBuf,
|
||||||
#[source] std::io::Error,
|
#[source] std::io::Error,
|
||||||
),
|
),
|
||||||
#[error("Remove path `{0}`")]
|
#[error("Read path `{0}`")]
|
||||||
Read(std::path::PathBuf, #[source] std::io::Error),
|
Read(std::path::PathBuf, #[source] std::io::Error),
|
||||||
#[error("Open path `{0}`")]
|
#[error("Open path `{0}`")]
|
||||||
Open(std::path::PathBuf, #[source] std::io::Error),
|
Open(std::path::PathBuf, #[source] std::io::Error),
|
||||||
|
|
|
@ -40,7 +40,7 @@ impl clap::builder::TypedValueParser for ChannelValueParser {
|
||||||
let (name, url) = buf.split_once('=').ok_or_else(|| {
|
let (name, url) = buf.split_once('=').ok_or_else(|| {
|
||||||
clap::Error::raw(
|
clap::Error::raw(
|
||||||
clap::error::ErrorKind::InvalidValue,
|
clap::error::ErrorKind::InvalidValue,
|
||||||
"Should be formatted `name=url`",
|
"`--channel` should be formatted `name=url`, eg `--channel nixpkgs=https://nixos.org/channels/nixpkgs-unstable`. To not use a channel, pass `--channel`",
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
let name = name.to_owned();
|
let name = name.to_owned();
|
||||||
|
|
|
@ -30,13 +30,13 @@ Settings which only apply to certain [`Planner`](crate::planner::Planner)s shoul
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
#[cfg_attr(feature = "cli", derive(clap::Parser))]
|
#[cfg_attr(feature = "cli", derive(clap::Parser))]
|
||||||
pub struct CommonSettings {
|
pub struct CommonSettings {
|
||||||
/// Channel(s) to add
|
/// Channel(s) to add, for no default channel, pass `--channel`
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
feature = "cli",
|
feature = "cli",
|
||||||
clap(
|
clap(
|
||||||
long,
|
|
||||||
value_parser,
|
value_parser,
|
||||||
name = "channel",
|
long = "channel",
|
||||||
|
num_args = 0..,
|
||||||
action = clap::ArgAction::Append,
|
action = clap::ArgAction::Append,
|
||||||
env = "NIX_INSTALLER_CHANNELS",
|
env = "NIX_INSTALLER_CHANNELS",
|
||||||
default_value = "nixpkgs=https://nixos.org/channels/nixpkgs-unstable",
|
default_value = "nixpkgs=https://nixos.org/channels/nixpkgs-unstable",
|
||||||
|
|
Loading…
Reference in a new issue