Skip to content

Commit

Permalink
KVM support out of the box, plus a refreshed README (#56)
Browse files Browse the repository at this point in the history
* support kvm

* Refresh the readme

* Update README.md

Co-authored-by: Ana Hobden <[email protected]>

* Update README.md

Co-authored-by: Luc Perkins <[email protected]>

* Update README.md

---------

Co-authored-by: Ana Hobden <[email protected]>
Co-authored-by: Luc Perkins <[email protected]>
  • Loading branch information
3 people authored Nov 21, 2023
1 parent 5620eb4 commit 07b8bcb
Show file tree
Hide file tree
Showing 5 changed files with 240 additions and 15 deletions.
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-android-virtualization-on-actions-windows-and-linux-larger-hosted-runners/) for more info.
* ✅ 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`,
], {
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

0 comments on commit 07b8bcb

Please sign in to comment.