diff --git a/.github/actions/install-nix-action/action.yml b/.github/actions/install-nix-action/action.yml deleted file mode 100644 index 81335087..00000000 --- a/.github/actions/install-nix-action/action.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: "Install Nix" -description: "Helper action for installing Nix" - -inputs: - dogfood: - description: "Whether to dogfood the latest version of the installer" - default: false - dogfood-path: - description: "Path to the local `nix-installer` binary root" - required: false - use-cache: - description: "Whether to cache paths in /nix/store" - default: true - no-init: - description: "Whether to install Nix without init system integration" - default: false - cachix-authtoken: - description: "cachix authentication token" - default: "" - -runs: - using: "composite" - steps: - - uses: DeterminateSystems/nix-installer-action@786fff0690178f1234e4e1fe9b536e94f5433196 # v20 - with: - determinate: false - - backtrace: ${{ inputs.dogfood == 'true' && 'full' || '' }} - local-root: ${{ inputs.dogfood == 'true' && inputs.dogfood-path || '' }} - log-directives: ${{ inputs.dogfood == 'true' && 'nix_installer=debug' || '' }} - logger: ${{ inputs.dogfood == 'true' && 'pretty' || '' }} - - init: ${{ inputs.no-init == 'true' && 'none' || '' }} - planner: ${{ inputs.no-init == 'true' && 'linux' || '' }} - - #- uses: cachix/cachix-action@v15 - # with: - # name: nix-installer - # authToken: "${{ inputs.cachix-authtoken}}" diff --git a/.github/workflows/act-test.yml b/.github/workflows/act-test.yml new file mode 100644 index 00000000..805debec --- /dev/null +++ b/.github/workflows/act-test.yml @@ -0,0 +1,33 @@ +name: Act Test + +on: + workflow_dispatch: + +jobs: + test-release: + name: Test Release Mode + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Nix from latest release + uses: ./ + with: + no-init: true + add-channel: true + + - name: Verify Nix installation + run: | + echo "Checking Nix is available..." + which nix + nix --version + + echo "Testing nix run command..." + nix run nixpkgs#hello + + echo "Testing nix-env install..." + nix-env --install hello + hello + nix-env --uninstall hello diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fb331162..2db3f451 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,11 +33,18 @@ jobs: uses: actions/checkout@v4 - name: Install Nix - uses: ./.github/actions/install-nix-action + uses: ./ with: - cachix-authtoken: "${{ secrets.CACHIX_AUTH_TOKEN }}" + extra-conf: | + access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} + - name: Setup Cachix + uses: cachix/cachix-action@v15 + with: + name: nix-installer + authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" + - name: Build the installer run: | if [[ $RUNNER_OS == "Linux" ]]; then @@ -77,9 +84,16 @@ jobs: check-outdated: false # PRs shouldn't fail because main's nixpkgs is out of date - name: Install Nix - uses: ./.github/actions/install-nix-action + uses: ./ + with: + extra-conf: | + access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} + + - name: Setup Cachix + uses: cachix/cachix-action@v15 with: - cachix-authtoken: "${{ secrets.CACHIX_AUTH_TOKEN }}" + name: nix-installer + authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" - name: Check rustfmt run: nix develop --command check-rustfmt @@ -146,13 +160,15 @@ jobs: fi - name: Initial install - uses: ./.github/actions/install-nix-action + uses: ./ with: - cachix-authtoken: "${{ secrets.CACHIX_AUTH_TOKEN }}" dogfood: true dogfood-path: ${{ steps.download-installer.outputs.download-path }} - use-cache: false no-init: ${{ matrix.no-init }} + add-channel: true + verbosity: 2 + extra-conf: | + access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} - name: Ensure daemon was not configured with init if: ${{ matrix.no-init != '' }} @@ -191,13 +207,15 @@ jobs: fi - name: Repeated install - uses: ./.github/actions/install-nix-action + uses: ./ with: - cachix-authtoken: "${{ secrets.CACHIX_AUTH_TOKEN }}" dogfood: true dogfood-path: ${{ steps.download-installer.outputs.download-path }} - use-cache: false no-init: ${{ matrix.no-init }} + add-channel: true + verbosity: 2 + extra-conf: | + access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} - name: echo $PATH run: echo "$PATH" @@ -206,6 +224,8 @@ jobs: - name: Test installation if: ${{ matrix.no-init == '' }} run: | + set -x + echo $PATH nix-shell -p hello --command hello nix-env --install hello hello @@ -276,11 +296,20 @@ jobs: find "$INSTALL_ROOT" -type f -exec chmod +x {} + - name: Initial install - uses: ./.github/actions/install-nix-action + uses: ./ with: - cachix-authtoken: "${{ secrets.CACHIX_AUTH_TOKEN }}" dogfood: true dogfood-path: ${{ steps.download-installer.outputs.download-path }} + add-channel: true + verbosity: 2 + extra-conf: | + access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} + + - name: Setup Cachix + uses: cachix/cachix-action@v15 + with: + name: nix-installer + authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" - run: nix flake check -L - run: | diff --git a/.github/workflows/release-script.yml b/.github/workflows/release-script.yml index fbef0376..021638d3 100644 --- a/.github/workflows/release-script.yml +++ b/.github/workflows/release-script.yml @@ -14,9 +14,13 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - - uses: cachix/install-nix-action@v25 + - name: Install Nix + uses: ./ + - name: Setup Cachix + uses: cachix/cachix-action@v15 with: - cachix-authtoken: "${{ secrets.CACHIX_AUTH_TOKEN }}" + name: nix-installer + authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" - name: Create draft release # The script also depends on gh and git but those are both pre-installed on the runner run: nix run --inputs-from .# nixpkgs#python3 -- assemble_installer.py "${{ github.event.inputs.testing_hydra_eval_id }}" diff --git a/.github/workflows/update.yml b/.github/workflows/update.yml index c7b86a2a..a9a4d476 100644 --- a/.github/workflows/update.yml +++ b/.github/workflows/update.yml @@ -14,9 +14,12 @@ jobs: - name: Checkout uses: actions/checkout@v4 - name: Install Nix - uses: ./.github/actions/install-nix-action + uses: ./ + - name: Setup Cachix + uses: cachix/cachix-action@v15 with: - cachix-authtoken: "${{ secrets.CACHIX_AUTH_TOKEN }}" + name: nix-installer + authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" - name: Check flake uses: DeterminateSystems/flake-checker-action@main - name: Update flake.lock diff --git a/README.md b/README.md index 816938d5..ff2d47e5 100644 --- a/README.md +++ b/README.md @@ -215,6 +215,31 @@ podman rmi $IMAGE With some container tools, such as [Docker], you can omit `sandbox = false`. Omitting this will negatively impact compatibility with container tools like [Podman]. +### In GitHub Actions + +This repository provides a GitHub Action for installing Nix in CI workflows. + +**Basic usage:** +```yaml +- uses: NixOS/experimental-nix-installer@main +``` + +**Install specific version:** +```yaml +- uses: NixOS/experimental-nix-installer@main + with: + installer-version: v3.11.3-experimental-prerelease +``` + +**No-init mode (for containers):** +```yaml +- uses: NixOS/experimental-nix-installer@main + with: + no-init: true +``` + +See the [action inputs](action.yml) for all available options. + ### In WSL2 We **strongly recommend** first [enabling systemd][enabling-systemd] and then installing Nix as normal: diff --git a/action.yml b/action.yml new file mode 100644 index 00000000..bb72fb2b --- /dev/null +++ b/action.yml @@ -0,0 +1,260 @@ +name: "Install Nix" +description: "Install Nix using the experimental nix-installer" + +inputs: + installer-version: + description: "Installer version to use from releases (default: latest)" + required: false + default: "latest" + extra-conf: + description: "Extra configuration lines to append to /etc/nix/nix.conf" + required: false + default: "" + logger: + description: "Logger format: compact, full, pretty, json" + required: false + default: "compact" + verbosity: + description: "Verbosity level: 0 (info), 1 (debug), 2 (trace)" + required: false + default: "0" + add-channel: + description: "Setup the default system channels" + required: false + default: "false" + dogfood: + description: "Use a locally built installer instead of downloading from releases (for CI)" + required: false + default: "false" + dogfood-path: + description: "Path to the locally built installer (used with dogfood)" + required: false + default: "" + no-init: + description: "Skip init system configuration (Nix will be root-only)" + required: false + default: "false" + trust-runner-user: + description: "Add the current user to trusted-users in nix.conf" + required: false + default: "true" + +runs: + using: "composite" + steps: + - name: Detect platform + shell: bash + id: platform + run: | + case "$RUNNER_OS" in + Linux) + OS_PART="linux" + ;; + macOS) + OS_PART="darwin" + ;; + *) + echo "::error ::Unsupported RUNNER_OS: $RUNNER_OS. Supported: Linux, macOS" + exit 1 + ;; + esac + + case "$RUNNER_ARCH" in + X64) + ARCH_PART="x86_64" + ;; + ARM64) + ARCH_PART="aarch64" + ;; + ARM) + ARCH_PART="armv7l" + ;; + *) + echo "::error ::Unsupported RUNNER_ARCH: $RUNNER_ARCH. Supported: X64, ARM64, ARM" + exit 1 + ;; + esac + + INSTALLER_SYSTEM="${ARCH_PART}-${OS_PART}" + echo "system=$INSTALLER_SYSTEM" >> "$GITHUB_OUTPUT" + echo "Detected platform: $INSTALLER_SYSTEM (OS=$RUNNER_OS, ARCH=$RUNNER_ARCH)" + + - name: Download installer from release + shell: bash + id: download + run: | + SYSTEM="${{ steps.platform.outputs.system }}" + BINARY_NAME="nix-installer-${SYSTEM}" + + if [ "${{ inputs.dogfood }}" == "true" ]; then + DOGFOOD_PATH="${{ inputs.dogfood-path }}" + if [ -z "$DOGFOOD_PATH" ]; then + echo "::error ::dogfood is enabled but dogfood-path is not set" + exit 1 + fi + + INSTALLER_DIR="$DOGFOOD_PATH" + if [ ! -f "$INSTALLER_DIR/$BINARY_NAME" ]; then + echo "::error ::Installer binary not found at: $INSTALLER_DIR/$BINARY_NAME" + exit 1 + fi + + chmod +x "$INSTALLER_DIR/$BINARY_NAME" + echo "path=$INSTALLER_DIR" >> "$GITHUB_OUTPUT" + echo "Using locally built installer: $INSTALLER_DIR/$BINARY_NAME" + else + VERSION="${{ inputs.installer-version }}" + INSTALLER_DIR="/tmp/nix-installer" + + mkdir -p "$INSTALLER_DIR" + + echo "Downloading nix-installer $VERSION for $SYSTEM from GitHub releases" + + if [ "$VERSION" == "latest" ]; then + DOWNLOAD_URL="https://github.com/NixOS/experimental-nix-installer/releases/latest/download/${BINARY_NAME}" + else + DOWNLOAD_URL="https://github.com/NixOS/experimental-nix-installer/releases/download/${VERSION}/${BINARY_NAME}" + fi + + echo "Downloading from: $DOWNLOAD_URL" + curl -L -f -o "$INSTALLER_DIR/$BINARY_NAME" "$DOWNLOAD_URL" + + if [ $? -ne 0 ] || [ ! -f "$INSTALLER_DIR/$BINARY_NAME" ]; then + echo "::error ::Failed to download installer binary: $BINARY_NAME" + echo "::error ::Version: $VERSION" + echo "::error ::Check available releases: https://github.com/NixOS/experimental-nix-installer/releases" + exit 1 + fi + + chmod +x "$INSTALLER_DIR/$BINARY_NAME" + echo "path=$INSTALLER_DIR" >> "$GITHUB_OUTPUT" + echo "Downloaded installer to: $INSTALLER_DIR/$BINARY_NAME" + fi + + - name: Run nix-installer + shell: bash + env: + NIX_INSTALLER_NO_CONFIRM: "true" + run: | + set -x + INSTALLER_DIR="${{ steps.download.outputs.path }}" + SYSTEM="${{ steps.platform.outputs.system }}" + INSTALLER_BINARY="$INSTALLER_DIR/nix-installer-$SYSTEM" + + if [ ! -f "$INSTALLER_BINARY" ]; then + echo "::error ::Installer binary not found: $INSTALLER_BINARY" + exit 1 + fi + + INSTALL_ARGS=(--no-confirm --explain) + + if [ -n "${{ inputs.logger }}" ] && [ "${{ inputs.logger }}" != "compact" ]; then + INSTALL_ARGS+=(--logger "${{ inputs.logger }}") + fi + + case "${{ inputs.verbosity }}" in + 1) + INSTALL_ARGS+=(-v) + ;; + 2) + INSTALL_ARGS+=(-vv) + ;; + esac + + if [ "${{ inputs.add-channel }}" == "true" ]; then + INSTALL_ARGS+=(--add-channel) + fi + + if [ "${{ inputs.no-init }}" == "true" ]; then + INSTALL_ARGS+=(--init none) + fi + + # Build extra-conf with optional trusted-users + EXTRA_CONF="" + if [ "${{ inputs.trust-runner-user }}" == "true" ]; then + EXTRA_CONF="trusted-users = root $USER" + fi + if [ -n "${{ inputs.extra-conf }}" ]; then + if [ -n "$EXTRA_CONF" ]; then + EXTRA_CONF="$EXTRA_CONF"$'\n'"${{ inputs.extra-conf }}" + else + EXTRA_CONF="${{ inputs.extra-conf }}" + fi + fi + if [ -n "$EXTRA_CONF" ]; then + INSTALL_ARGS+=(--extra-conf "$EXTRA_CONF") + fi + + case "$RUNNER_OS" in + Linux) + PLANNER="linux" + ;; + macOS) + PLANNER="" + ;; + *) + echo "::error ::Unsupported RUNNER_OS: $RUNNER_OS" + exit 1 + ;; + esac + + echo "::group::Installation command" + echo "PLANNER: '$PLANNER'" + echo "add-channel input: ${{ inputs.add-channel }}" + if [ -n "$PLANNER" ]; then + echo "Full command: sudo $INSTALLER_BINARY install $PLANNER ${INSTALL_ARGS[@]}" + else + echo "Full command: sudo $INSTALLER_BINARY install ${INSTALL_ARGS[@]}" + fi + echo "::endgroup::" + + echo "Starting Nix installation..." + if [ -n "$PLANNER" ]; then + sudo "$INSTALLER_BINARY" install "$PLANNER" "${INSTALL_ARGS[@]}" + else + sudo "$INSTALLER_BINARY" install "${INSTALL_ARGS[@]}" + fi + + if [ $? -ne 0 ]; then + echo "::error ::nix-installer failed" + exit 1 + fi + + # Set per user profile + if [ -n "${NIX_STATE_HOME-}" ]; then + NIX_LINK="$NIX_STATE_HOME/profile" + else + NIX_LINK="$HOME/.nix-profile" + if [ -n "${XDG_STATE_HOME-}" ]; then + NIX_LINK_NEW="$XDG_STATE_HOME/nix/profile" + else + NIX_LINK_NEW="$HOME/.local/state/nix/profile" + fi + if [ -e "$NIX_LINK_NEW" ]; then + if [ -t 2 ] && [ -e "$NIX_LINK" ]; then + warning="\033[1;35mwarning:\033[0m" + printf "$warning Both %s and legacy %s exist; using the former.\n" "$NIX_LINK_NEW" "$NIX_LINK" 1>&2 + fi + NIX_LINK="$NIX_LINK_NEW" + fi + fi + echo "NIX_PROFILES=/nix/var/nix/profiles/default $NIX_LINK" >>"$GITHUB_ENV" + echo "Nix installation completed successfully" + + - name: Setup Nix in PATH + shell: bash + run: | + echo "/nix/var/nix/profiles/default/bin" >> "$GITHUB_PATH" + echo "$HOME/.nix-profile/bin" >> "$GITHUB_PATH" + + if [ -f /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh ]; then + . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh + fi + + if command -v nix >/dev/null 2>&1; then + NIX_VERSION=$(nix --version) + echo "Nix is ready: $NIX_VERSION" + else + echo "nix command not immediately available in PATH." + exit 1 + fi diff --git a/flake.nix b/flake.nix index 08ec3e10..3b84be78 100644 --- a/flake.nix +++ b/flake.nix @@ -128,6 +128,7 @@ check.check-semver check.check-clippy editorconfig-checker + act ] ++ lib.optionals (pkgs.stdenv.isDarwin) (with pkgs; [ libiconv @@ -182,6 +183,17 @@ default = pkgs.nix-installer; }); + apps = forAllSystems ({ pkgs, ... }: { + test-action = { + type = "app"; + program = toString (pkgs.writeShellScript "test-action" '' + set -e + echo "Testing GitHub Action with act..." + ${pkgs.act}/bin/act -W .github/workflows/act-test.yml -j test-release --pull=false + ''); + }; + }); + hydraJobs = { build = forAllSystems ({ system, pkgs, ... }: self.packages.${system}.default); # vm-test = import ./nix/tests/vm-test { diff --git a/src/action/common/setup_channels.rs b/src/action/common/setup_channels.rs index a7d43be3..22b747f7 100644 --- a/src/action/common/setup_channels.rs +++ b/src/action/common/setup_channels.rs @@ -24,11 +24,27 @@ pub struct SetupChannels { } impl SetupChannels { + fn get_root_home() -> Result { + // Use nix::unistd to get the actual root user's home, not $HOME env var + // This avoids issues where sudo preserves HOME on some platforms (macOS) + use nix::unistd::{Uid, User}; + + if Uid::effective().is_root() { + User::from_uid(Uid::from_raw(0)) + .ok() + .flatten() + .map(|user| user.dir) + .ok_or(SetupChannelsError::NoRootHome) + } else { + dirs::home_dir().ok_or(SetupChannelsError::NoRootHome) + } + } + #[tracing::instrument(level = "debug", skip_all)] pub async fn plan(unpacked_path: PathBuf) -> Result, ActionError> { let create_file = CreateFile::plan( - dirs::home_dir() - .ok_or_else(|| Self::error(SetupChannelsError::NoRootHome))? + Self::get_root_home() + .map_err(Self::error)? .join(".nix-channels"), None, None, @@ -89,10 +105,7 @@ impl Action for SetupChannels { .arg("--update") .arg("nixpkgs") .stdin(std::process::Stdio::null()) - .env( - "HOME", - dirs::home_dir().ok_or_else(|| Self::error(SetupChannelsError::NoRootHome))?, - ) + .env("HOME", Self::get_root_home().map_err(Self::error)?) .env( "NIX_SSL_CERT_FILE", nss_ca_cert_pkg.join("etc/ssl/certs/ca-bundle.crt"), diff --git a/src/settings.rs b/src/settings.rs index 54a27c1d..abdc6c0f 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -169,8 +169,8 @@ pub struct CommonSettings { #[cfg_attr( feature = "cli", clap( - action(ArgAction::SetFalse), - default_value = "true", + action(ArgAction::SetTrue), + default_value = "false", global = true, env = "NIX_INSTALLER_ADD_CHANNEL", long("add-channel"), @@ -241,7 +241,7 @@ impl CommonSettings { force: false, skip_nix_conf: false, ssl_cert_file: Default::default(), - add_channel: true, + add_channel: false, }) }