Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

KVM support out of the box, plus a refreshed README #56

Merged
merged 5 commits into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 48 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
# Nix Installer Action
# The Determinate Nix Installer Action

You can use [`nix-installer`](https://github.com/DeterminateSystems/nix-installer) as a Github action like so:
Based on the [Determinate Nix Installer](https://github.com/DeterminateSystems/nix-installer), responsible for over tens of thousands of Nix installs daily.
The fast, friendly, and reliable GitHub Action to install Nix with Flakes.

## Supports

* ✅ **Accelerated KVM** on open source projects and larger runners. See [GitHub's announcement](https://github.blog/changelog/2023-02-23-hardware-accelerated-androidvirtualization-on-actions-windows-and-linux-larger-hosted-runners) for more info.
grahamc marked this conversation as resolved.
Show resolved Hide resolved
* ✅ Linux, x86_64, aarch64, and i686
* ✅ macOS, x86_64 and aarch64
* ✅ WSL2, x86_64 and aarch64
* ✅ Containers
* ✅ Valve's SteamOS
* ✅ GitHub Hosted, self-hosted, and long running Actions Runners

## Usage

```yaml
on:
Expand All @@ -11,18 +24,16 @@ on:
jobs:
lints:
name: Build
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@main
- name: Run `nix build`
run: nix build .
- uses: DeterminateSystems/nix-installer-action@main
- run: nix build .
```

See [`.github/workflows/ci.yml`](.github/workflows/ci.yml) for a full example.
### With FlakeHub

To use private flakes from FlakeHub, use a configuration like this:
To fetch private flakes from FlakeHub, update the `permissions` block and pass `flakehub: true`:

```yaml
on:
Expand All @@ -33,20 +44,42 @@ on:
jobs:
lints:
name: Build
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
permissions:
id-token: "write"
contents: "read"
steps:
- uses: actions/checkout@v3
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@main
- uses: DeterminateSystems/nix-installer-action@main
with:
flakehub: true
- name: Run `nix build`
run: nix build .
- run: nix build .
```

See [`.github/workflows/ci.yml`](.github/workflows/ci.yml) for a full example.

### Advanced Usage

* If KVM is available, the installer sets up KVM so that Nix can use it ,and exports the `DETERMINATE_NIX_KVM` environment variable set to 1.
If KVM is not available, `DETERMINATE_NIX_KVM` is set to 0.
This can be used in combination with GitHub Actions' `if` syntax for turning on and off steps.

## Installation Differences

Differing from the upstream [Nix](https://github.com/NixOS/nix) installer scripts:

* In `nix.conf`:
+ the `nix-command` and `flakes` features are 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`
* `max-jobs` is set to `auto`
* KVM is enabled by default.
* an installation receipt (for uninstalling) is stored at `/nix/receipt.json` as well as a copy of the install binary at `/nix/nix-installer`
* `nix-channel --update` is not run, `~/.nix-channels` is not provisioned
* `ssl-cert-file` is set in `/etc/nix/nix.conf` if the `ssl-cert-file` argument is used.


## Configuration

| Parameter | Description | Type | Default |
Expand All @@ -57,6 +90,7 @@ jobs:
| `flakehub` | Log in to FlakeHub to pull private flakes using the GitHub Actions [JSON Web Token](https://jwt.io) (JWT), which is bound to the `api.flakehub.com` audience. | Boolean | `false` |
| `github-token` | A [GitHub token] for making authenticated requests (which have a higher rate-limit quota than unauthenticated requests) | string | `${{ github.token }}` |
| `init` | The init system to configure (requires `planner: linux-multi`) | enum (`none` or `systemd`) | |
| `kvm` | Automatically configure the GitHub Actions Runner for NixOS test support, if the host supports it. | Boolean | `true` |
| `local-root` | A local `nix-installer` binary root. Overrides the `nix-installer-url` setting (a `nix-installer.sh` should exist, binaries should be named `nix-installer-$ARCH`, eg. `nix-installer-x86_64-linux`). | Boolean | `false` |
| `log-directives` | A list of [tracing directives], comma separated with `-`s replaced with `_` (eg. `nix_installer=trace`) | string | |
| `logger` | The logger to use during installation | enum (`pretty`, `json`, `full`, `compact`) | |
Expand Down
4 changes: 4 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ inputs:
init:
description: "The init system to configure, requires `planner: linux-multi` (allowing the choice between `none` or `systemd`)"
required: false
kvm:
description: Automatically configure the GitHub Actions Runner for NixOS test supports, if the host supports it.
required: false
default: true
local-root:
description: A local `nix-installer` binary root, overrides any settings which change the `nix-installer` used (binaries should be named `nix-installer-$ARCH-$OS`, eg. `nix-installer-x86_64-linux`)
required: false
Expand Down
81 changes: 81 additions & 0 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class NixInstallerAction {
this.extra_args = action_input_string_or_null("extra-args");
this.extra_conf = action_input_multiline_string_or_null("extra-conf");
this.flakehub = action_input_bool("flakehub");
this.kvm = action_input_bool("kvm");
this.github_token = action_input_string_or_null("github-token");
this.init = action_input_string_or_null("init");
this.local_root = action_input_string_or_null("local-root");
Expand Down Expand Up @@ -236,6 +237,20 @@ class NixInstallerAction {
return;
}
}
if (this.kvm) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__.startGroup("Configuring KVM");
if (await this.setup_kvm()) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__.endGroup();
_actions_core__WEBPACK_IMPORTED_MODULE_0__.info("\u001b[32m Accelerated KVM is enabled \u001b[33m⚡️");
_actions_core__WEBPACK_IMPORTED_MODULE_0__.exportVariable("DETERMINATE_NIX_KVM", "1");
}
else {
_actions_core__WEBPACK_IMPORTED_MODULE_0__.endGroup();
_actions_core__WEBPACK_IMPORTED_MODULE_0__.info("KVM is not available.");
_actions_core__WEBPACK_IMPORTED_MODULE_0__.exportVariable("DETERMINATE_NIX_KVM", "0");
}
_actions_core__WEBPACK_IMPORTED_MODULE_0__.exportVariable("DETERMINATE_NIX_KVM", "0");
}
// Normal just doing of the install
const binary_path = await this.fetch_binary();
await this.execute_install(binary_path);
Expand Down Expand Up @@ -305,6 +320,72 @@ class NixInstallerAction {
return false;
}
}
async setup_kvm() {
const kvm_rules = "/etc/udev/rules.d/99-determinate-nix-installer-kvm.rules";
try {
const write_file_exit_code = await _actions_exec__WEBPACK_IMPORTED_MODULE_3__.exec("sh", [
"-c",
`echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee ${kvm_rules} > /dev/null`,
grahamc marked this conversation as resolved.
Show resolved Hide resolved
], {
listeners: {
stderr: (data) => {
const trimmed = data.toString("utf-8").trimEnd();
if (trimmed.length >= 0) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__.debug(trimmed);
}
},
},
});
if (write_file_exit_code !== 0) {
throw new Error(`Non-zero exit code of \`${write_file_exit_code}\` detected while writing '${kvm_rules}'`);
}
const debug_run_throw = async (action, command, args) => {
const reload_exit_code = await _actions_exec__WEBPACK_IMPORTED_MODULE_3__.exec(command, args, {
listeners: {
stdout: (data) => {
const trimmed = data.toString("utf-8").trimEnd();
if (trimmed.length >= 0) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__.debug(trimmed);
}
},
stderr: (data) => {
const trimmed = data.toString("utf-8").trimEnd();
if (trimmed.length >= 0) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__.debug(trimmed);
}
},
},
});
if (reload_exit_code !== 0) {
throw new Error(`Non-zero exit code of \`${reload_exit_code}\` detected while ${action}.`);
}
};
await debug_run_throw("reloading udev rules", `sudo`, [
"udevadm",
"control",
"--reload-rules",
]);
await debug_run_throw("triggering udev against kvm", `sudo`, [
"udevadm",
"trigger",
"--name-match=kvm",
]);
return true;
}
catch (error) {
await _actions_exec__WEBPACK_IMPORTED_MODULE_3__.exec("sudo", ["rm", "-f", kvm_rules], {
listeners: {
stderr: (data) => {
const trimmed = data.toString("utf-8").trimEnd();
if (trimmed.length >= 0) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__.info(trimmed);
}
},
},
});
return false;
}
}
async fetch_binary() {
if (!this.local_root) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__.info(`Fetching binary from ${this.nix_installer_url}`);
Expand Down
2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

106 changes: 106 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class NixInstallerAction {
extra_args: string | null;
extra_conf: string[] | null;
flakehub: boolean;
kvm: boolean;
github_token: string | null;
// TODO: linux_init
init: string | null;
Expand Down Expand Up @@ -53,6 +54,7 @@ class NixInstallerAction {
this.extra_args = action_input_string_or_null("extra-args");
this.extra_conf = action_input_multiline_string_or_null("extra-conf");
this.flakehub = action_input_bool("flakehub");
this.kvm = action_input_bool("kvm");
this.github_token = action_input_string_or_null("github-token");
this.init = action_input_string_or_null("init");
this.local_root = action_input_string_or_null("local-root");
Expand Down Expand Up @@ -305,6 +307,24 @@ class NixInstallerAction {
return;
}
}

if (this.kvm) {
actions_core.startGroup("Configuring KVM");
if (await this.setup_kvm()) {
actions_core.endGroup();
actions_core.info(
"\u001b[32m Accelerated KVM is enabled \u001b[33m⚡️",
);
actions_core.exportVariable("DETERMINATE_NIX_KVM", "1");
} else {
actions_core.endGroup();
actions_core.info("KVM is not available.");
actions_core.exportVariable("DETERMINATE_NIX_KVM", "0");
}

actions_core.exportVariable("DETERMINATE_NIX_KVM", "0");
}

// Normal just doing of the install
const binary_path = await this.fetch_binary();
await this.execute_install(binary_path);
Expand Down Expand Up @@ -399,6 +419,92 @@ class NixInstallerAction {
}
}

private async setup_kvm(): Promise<boolean> {
const kvm_rules =
"/etc/udev/rules.d/99-determinate-nix-installer-kvm.rules";
try {
const write_file_exit_code = await actions_exec.exec(
"sh",
[
"-c",
`echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee ${kvm_rules} > /dev/null`,
],
{
listeners: {
stderr: (data: Buffer) => {
const trimmed = data.toString("utf-8").trimEnd();
if (trimmed.length >= 0) {
actions_core.debug(trimmed);
}
},
},
},
);

if (write_file_exit_code !== 0) {
throw new Error(
`Non-zero exit code of \`${write_file_exit_code}\` detected while writing '${kvm_rules}'`,
);
}

const debug_run_throw = async (
action: string,
command: string,
args: string[],
): Promise<void> => {
const reload_exit_code = await actions_exec.exec(command, args, {
listeners: {
stdout: (data: Buffer) => {
const trimmed = data.toString("utf-8").trimEnd();
if (trimmed.length >= 0) {
actions_core.debug(trimmed);
}
},
stderr: (data: Buffer) => {
const trimmed = data.toString("utf-8").trimEnd();
if (trimmed.length >= 0) {
actions_core.debug(trimmed);
}
},
},
});

if (reload_exit_code !== 0) {
throw new Error(
`Non-zero exit code of \`${reload_exit_code}\` detected while ${action}.`,
);
}
};

await debug_run_throw("reloading udev rules", `sudo`, [
"udevadm",
"control",
"--reload-rules",
]);

await debug_run_throw("triggering udev against kvm", `sudo`, [
"udevadm",
"trigger",
"--name-match=kvm",
]);

return true;
} catch (error) {
await actions_exec.exec("sudo", ["rm", "-f", kvm_rules], {
listeners: {
stderr: (data: Buffer) => {
const trimmed = data.toString("utf-8").trimEnd();
if (trimmed.length >= 0) {
actions_core.info(trimmed);
}
},
},
});

return false;
}
}

private async fetch_binary(): Promise<string> {
if (!this.local_root) {
actions_core.info(`Fetching binary from ${this.nix_installer_url}`);
Expand Down