From 3706bd387a1b6e07d4d62ea98bf52e0dd73f3f5b Mon Sep 17 00:00:00 2001 From: Ana Hobden Date: Fri, 23 Dec 2022 11:07:14 -0800 Subject: [PATCH] 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 --- CONTRIBUTING.md | 132 +++++++++++++- flake.lock | 55 ++++++ flake.nix | 26 ++- nix/tests/vm-test/default.nix | 222 +++++++++++++++++++++++ nix/tests/vm-test/vagrant_insecure_key | 27 +++ src/action/base/fetch_and_unpack_nix.rs | 42 ++++- src/action/base/move_unpacked_nix.rs | 18 +- src/action/base/setup_default_profile.rs | 92 ++++++++-- src/action/mod.rs | 2 +- src/channel_value.rs | 2 +- src/settings.rs | 6 +- 11 files changed, 588 insertions(+), 36 deletions(-) create mode 100644 nix/tests/vm-test/default.nix create mode 100644 nix/tests/vm-test/vagrant_insecure_key diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1bd1f6c..0c05359 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -74,7 +74,7 @@ Create an issue on [the issue page](https://github.com/DeterminateSystems/nix-in It should contain: 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`) 4. What happened (the output of the command, please) 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 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. + + +
+ Adding a planner for specific hardware? + +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. + +
+ +## `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 +``` + +
+ Adding a distro? + +Notice how `rhel-v7` has a `v7`, not just `7`? That's so the test output shows correctly, as Nix will interpret the first `-\d` (eg `-7`, `-123213`) as a version, and not show it in the output. + +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 +# ... +``` + +
# Who maintains `nix-installer` and why? diff --git a/flake.lock b/flake.lock index 624bfec..7e05c90 100644 --- a/flake.lock +++ b/flake.lock @@ -21,6 +21,22 @@ "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": { "inputs": { "nixpkgs": [ @@ -41,6 +57,28 @@ "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": { "locked": { "lastModified": 1671323474, @@ -57,10 +95,27 @@ "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": { "inputs": { "fenix": "fenix", "naersk": "naersk", + "nix": "nix", "nixpkgs": "nixpkgs" } }, diff --git a/flake.nix b/flake.nix index f48f041..78bc868 100644 --- a/flake.nix +++ b/flake.nix @@ -13,6 +13,12 @@ url = "github:nix-community/naersk"; inputs.nixpkgs.follows = "nixpkgs"; }; + + nix = { + url = "github:nixos/nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; outputs = @@ -20,16 +26,19 @@ , nixpkgs , fenix , naersk + , nix , ... } @ inputs: let 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; pkgs = import nixpkgs { inherit system; overlays = [ self.overlays.default ]; }; lib = pkgs.lib; - }); + }; fenixToolchain = system: with fenix.packages.${system}; combine ([ @@ -55,7 +64,11 @@ sharedAttrs = { pname = "nix-installer"; 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; [ ]; buildInputs = with final; [ ] ++ lib.optionals (final.stdenv.isDarwin) (with final.darwin.apple_sdk.frameworks; [ @@ -163,5 +176,12 @@ } // nixpkgs.lib.optionalAttrs (pkgs.stdenv.isDarwin) { default = pkgs.nix-installer; }); + + hydraJobs = { + vm-test = import ./nix/tests/vm-test { + inherit forSystem; + inherit (nix.hydraJobs) binaryTarball; + }; + }; }; } diff --git a/nix/tests/vm-test/default.nix b/nix/tests/vm-test/default.nix new file mode 100644 index 0000000..b9cda29 --- /dev/null +++ b/nix/tests/vm-test/default.nix @@ -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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 < \$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; + }); +} diff --git a/nix/tests/vm-test/vagrant_insecure_key b/nix/tests/vm-test/vagrant_insecure_key new file mode 100644 index 0000000..7d6a083 --- /dev/null +++ b/nix/tests/vm-test/vagrant_insecure_key @@ -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----- diff --git a/src/action/base/fetch_and_unpack_nix.rs b/src/action/base/fetch_and_unpack_nix.rs index 6a58f96..0f98ba8 100644 --- a/src/action/base/fetch_and_unpack_nix.rs +++ b/src/action/base/fetch_and_unpack_nix.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -use bytes::Buf; +use bytes::{Buf, Bytes}; use reqwest::Url; use tracing::{span, Span}; @@ -21,6 +21,15 @@ impl FetchAndUnpackNix { // TODO(@hoverbear): Check URL 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()) } } @@ -49,13 +58,28 @@ impl Action for FetchAndUnpackNix { async fn execute(&mut self) -> Result<(), ActionError> { let Self { url, dest } = self; - let res = reqwest::get(url.clone()) - .await - .map_err(|e| ActionError::Custom(Box::new(FetchUrlError::Reqwest(e))))?; - let bytes = res - .bytes() - .await - .map_err(|e| ActionError::Custom(Box::new(FetchUrlError::Reqwest(e))))?; + let bytes = match url.scheme() { + "https" | "http" => { + let res = reqwest::get(url.clone()) + .await + .map_err(|e| ActionError::Custom(Box::new(FetchUrlError::Reqwest(e))))?; + res.bytes() + .await + .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 tracing::trace!("Unpacking tar.xz"); let dest_clone = dest.clone(); @@ -91,4 +115,6 @@ pub enum FetchUrlError { ), #[error("Unarchiving error")] Unarchive(#[source] std::io::Error), + #[error("Unknown url scheme")] + UnknownUrlScheme, } diff --git a/src/action/base/move_unpacked_nix.rs b/src/action/base/move_unpacked_nix.rs index 03d4fe1..4e53c01 100644 --- a/src/action/base/move_unpacked_nix.rs +++ b/src/action/base/move_unpacked_nix.rs @@ -4,7 +4,7 @@ use tracing::{span, Span}; 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` @@ -64,15 +64,23 @@ impl Action for MoveUnpackedNix { ); let found_nix_path = found_nix_paths.into_iter().next().unwrap(); 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"); - tokio::fs::rename(src_store.clone(), dest) + tokio::fs::rename(&src_store, &dest) .await .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 - .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(()) } diff --git a/src/action/base/setup_default_profile.rs b/src/action/base/setup_default_profile.rs index 361cdaf..2e8f4a0 100644 --- a/src/action/base/setup_default_profile.rs +++ b/src/action/base/setup_default_profile.rs @@ -1,3 +1,5 @@ +use std::path::{Path, PathBuf}; + use crate::{ action::{ActionError, StatefulAction}, execute_command, set_env, @@ -5,7 +7,7 @@ use crate::{ use glob::glob; -use tokio::process::Command; +use tokio::{io::AsyncWriteExt, process::Command}; use tracing::{span, Span}; 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("")) + ), + ))); + }; + } + // Install `nix` itself into the store execute_command( Command::new(nix_pkg.join("bin/nix-env")) .process_group(0) .arg("-i") .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(&nss_ca_cert_pkg) .stdin(std::process::Stdio::null()) @@ -117,19 +194,6 @@ impl Action for SetupDefaultProfile { .await .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( "NIX_SSL_CERT_FILE", "/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt", diff --git a/src/action/mod.rs b/src/action/mod.rs index b3b56cc..f92626b 100644 --- a/src/action/mod.rs +++ b/src/action/mod.rs @@ -290,7 +290,7 @@ pub enum ActionError { std::path::PathBuf, #[source] std::io::Error, ), - #[error("Remove path `{0}`")] + #[error("Read path `{0}`")] Read(std::path::PathBuf, #[source] std::io::Error), #[error("Open path `{0}`")] Open(std::path::PathBuf, #[source] std::io::Error), diff --git a/src/channel_value.rs b/src/channel_value.rs index addc85d..29080a2 100644 --- a/src/channel_value.rs +++ b/src/channel_value.rs @@ -40,7 +40,7 @@ impl clap::builder::TypedValueParser for ChannelValueParser { let (name, url) = buf.split_once('=').ok_or_else(|| { clap::Error::raw( 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(); diff --git a/src/settings.rs b/src/settings.rs index b37ed7f..d077f66 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -30,13 +30,13 @@ Settings which only apply to certain [`Planner`](crate::planner::Planner)s shoul #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[cfg_attr(feature = "cli", derive(clap::Parser))] pub struct CommonSettings { - /// Channel(s) to add + /// Channel(s) to add, for no default channel, pass `--channel` #[cfg_attr( feature = "cli", clap( - long, value_parser, - name = "channel", + long = "channel", + num_args = 0.., action = clap::ArgAction::Append, env = "NIX_INSTALLER_CHANNELS", default_value = "nixpkgs=https://nixos.org/channels/nixpkgs-unstable",