nix-installer-action/dist/main.js
2024-05-02 10:52:54 -03:00

767 lines
32 KiB
JavaScript
Generated
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import * as actionsCore from "@actions/core";
import * as github from "@actions/github";
import * as actionsExec from "@actions/exec";
import { access, writeFile, readFile } from "node:fs/promises";
import { join } from "node:path";
import fs from "node:fs";
import { userInfo } from "node:os";
import stringArgv from "string-argv";
import * as path from "path";
import { IdsToolbox, inputs, platform } from "detsys-ts";
import { randomUUID } from "node:crypto";
// Nix installation events
const EVENT_INSTALL_NIX_FAILURE = "install_nix_failure";
const EVENT_INSTALL_NIX_START = "install_nix_start";
const EVENT_INSTALL_NIX_SUCCESS = "install_nix_start";
const EVENT_SETUP_KVM = "setup_kvm";
const EVENT_UNINSTALL_NIX = "uninstall";
// Docker events
const EVENT_CLEAN_UP_DOCKER_SHIM = "clean_up_docker_shim";
const EVENT_START_DOCKER_SHIM = "start_docker_shim";
// FlakeHub events
const EVENT_LOGIN_TO_FLAKEHUB = "login_to_flakehub";
// Other events
const EVENT_CONCLUDE_WORKFLOW = "conclude_workflow";
// Facts
const FACT_HAS_DOCKER = "has_docker";
const FACT_HAS_SYSTEMD = "has_systemd";
const FACT_IN_GITHUB_ACTIONS = "in_act";
const FACT_IN_NAMESPACE_SO = "in_namespace_so";
const FACT_NIX_INSTALLER_PLANNER = "nix_installer_planner";
class NixInstallerAction {
constructor() {
this.idslib = new IdsToolbox({
name: "nix-installer",
fetchStyle: "nix-style",
legacySourcePrefix: "nix-installer",
requireNix: "ignore",
});
this.platform = platform.getNixPlatform(platform.getArchOs());
this.nixPackageUrl = inputs.getStringOrNull("nix-package-url");
this.backtrace = inputs.getStringOrNull("backtrace");
this.extraArgs = inputs.getStringOrNull("extra-args");
this.extraConf = inputs.getMultilineStringOrNull("extra-conf");
this.flakehub = inputs.getBool("flakehub");
this.kvm = inputs.getBool("kvm");
this.forceDockerShim = inputs.getBool("force-docker-shim");
this.githubToken = inputs.getStringOrNull("github-token");
this.githubServerUrl = inputs.getStringOrNull("github-server-url");
this.init = inputs.getStringOrNull("init");
this.localRoot = inputs.getStringOrNull("local-root");
this.logDirectives = inputs.getStringOrNull("log-directives");
this.logger = inputs.getStringOrNull("logger");
this.sslCertFile = inputs.getStringOrNull("ssl-cert-file");
this.proxy = inputs.getStringOrNull("proxy");
this.macCaseSensitive = inputs.getStringOrNull("mac-case-sensitive");
this.macEncrypt = inputs.getStringOrNull("mac-encrypt");
this.macRootDisk = inputs.getStringOrNull("mac-root-disk");
this.macVolumeLabel = inputs.getStringOrNull("mac-volume-label");
this.modifyProfile = inputs.getBool("modify-profile");
this.nixBuildGroupId = inputs.getNumberOrNull("nix-build-group-id");
this.nixBuildGroupName = inputs.getStringOrNull("nix-build-group-name");
this.nixBuildUserBase = inputs.getNumberOrNull("nix-build-user-base");
this.nixBuildUserCount = inputs.getNumberOrNull("nix-build-user-count");
this.nixBuildUserPrefix = inputs.getStringOrNull("nix-build-user-prefix");
this.planner = inputs.getStringOrNull("planner");
this.reinstall = inputs.getBool("reinstall");
this.startDaemon = inputs.getBool("start-daemon");
this.trustRunnerUser = inputs.getBool("trust-runner-user");
}
async detectAndForceDockerShim() {
const runnerOs = process.env["RUNNER_OS"];
// Detect if we're in a GHA runner which is Linux, doesn't have Systemd, and does have Docker.
// This is a common case in self-hosted runners, providers like [Namespace](https://namespace.so/),
// and especially GitHub Enterprise Server.
if (runnerOs !== "Linux") {
if (this.forceDockerShim) {
actionsCore.warning("Ignoring force-docker-shim which is set to true, as it is only supported on Linux.");
this.forceDockerShim = false;
}
return;
}
const systemdCheck = fs.statSync("/run/systemd/system", {
throwIfNoEntry: false,
});
if (systemdCheck?.isDirectory()) {
if (this.forceDockerShim) {
actionsCore.warning("Systemd is detected, but ignoring it since force-docker-shim is enabled.");
}
else {
this.idslib.addFact(FACT_HAS_SYSTEMD, true);
return;
}
}
this.idslib.addFact(FACT_HAS_SYSTEMD, false);
actionsCore.debug("Linux detected without systemd, testing for Docker with `docker info` as an alternative daemon supervisor.");
this.idslib.addFact(FACT_HAS_DOCKER, false); // Set to false here, and only in the success case do we set it to true
let exitCode;
try {
exitCode = await actionsExec.exec("docker", ["info"], {
silent: true,
listeners: {
stdout: (data) => {
const trimmed = data.toString("utf-8").trimEnd();
if (trimmed.length >= 0) {
actionsCore.debug(trimmed);
}
},
stderr: (data) => {
const trimmed = data.toString("utf-8").trimEnd();
if (trimmed.length >= 0) {
actionsCore.debug(trimmed);
}
},
},
});
}
catch (e) {
actionsCore.debug("Docker not detected, not enabling docker shim.");
return;
}
if (exitCode !== 0) {
if (this.forceDockerShim) {
actionsCore.warning("docker info check failed, but trying anyway since force-docker-shim is enabled.");
}
else {
return;
}
}
this.idslib.addFact(FACT_HAS_DOCKER, true);
if (!this.forceDockerShim &&
(await this.detectDockerWithMountedDockerSocket())) {
actionsCore.debug("Detected a Docker container with a Docker socket mounted, not enabling docker shim.");
return;
}
actionsCore.startGroup("Enabling the Docker shim for running Nix on Linux in CI without Systemd.");
if (this.init !== "none") {
actionsCore.info(`Changing init from '${this.init}' to 'none'`);
this.init = "none";
}
if (this.planner !== "linux") {
actionsCore.info(`Changing planner from '${this.planner}' to 'linux'`);
this.planner = "linux";
}
this.forceDockerShim = true;
actionsCore.endGroup();
}
// Detect if we are running under `act` or some other system which is not using docker-in-docker,
// and instead using a mounted docker socket.
// In the case of the socket mount solution, the shim will cause issues since the given mount paths will
// equate to mount paths on the host, not mount paths to the docker container in question.
async detectDockerWithMountedDockerSocket() {
let cgroupsBuffer;
try {
// If we are inside a docker container, the last line of `/proc/self/cgroup` should be
// 0::/docker/$SOME_ID
//
// If we are not, the line will likely be `0::/`
cgroupsBuffer = await readFile("/proc/self/cgroup", {
encoding: "utf-8",
});
}
catch (e) {
actionsCore.debug(`Did not detect \`/proc/self/cgroup\` existence, bailing on docker container ID detection:\n${e}`);
return false;
}
const cgroups = cgroupsBuffer.trim().split("\n");
const lastCgroup = cgroups[cgroups.length - 1];
const lastCgroupParts = lastCgroup.split(":");
const lastCgroupPath = lastCgroupParts[lastCgroupParts.length - 1];
if (!lastCgroupPath.includes("/docker/")) {
actionsCore.debug("Did not detect a container ID, bailing on docker.sock detection");
return false;
}
// We are in a docker container, now to determine if this container is visible from
// the `docker` command, and if so, if there is a `docker.socket` mounted.
const lastCgroupPathParts = lastCgroupPath.split("/");
const containerId = lastCgroupPathParts[lastCgroupPathParts.length - 1];
// If we cannot `docker inspect` this discovered container ID, we'll fall through to the `catch` below.
let stdoutBuffer = "";
let stderrBuffer = "";
let exitCode;
try {
exitCode = await actionsExec.exec("docker", ["inspect", containerId], {
silent: true,
listeners: {
stdout: (data) => {
stdoutBuffer += data.toString("utf-8");
},
stderr: (data) => {
stderrBuffer += data.toString("utf-8");
},
},
});
}
catch (e) {
actionsCore.debug(`Could not execute \`docker inspect ${containerId}\`, bailing on docker container inspection:\n${e}`);
return false;
}
if (exitCode !== 0) {
actionsCore.debug(`Unable to inspect detected docker container with id \`${containerId}\`, bailing on container inspection (exit ${exitCode}):\n${stderrBuffer}`);
return false;
}
const output = JSON.parse(stdoutBuffer);
// `docker inspect $ID` prints an array containing objects.
// In our use case, we should only see 1 item in the array.
if (output.length !== 1) {
actionsCore.debug(`Got \`docker inspect ${containerId}\` output which was not one item (was ${output.length}), bailing on docker.sock detection.`);
return false;
}
const item = output[0];
// On this array item we want the `Mounts` field, which is an array
// containing `{ Type, Source, Destination, Mode}`.
// We are looking for a `Destination` ending with `docker.sock`.
const mounts = item["Mounts"];
if (typeof mounts !== "object") {
actionsCore.debug(`Got non-object in \`Mounts\` field of \`docker inspect ${containerId}\` output, bailing on docker.sock detection.`);
return false;
}
let foundDockerSockMount = false;
for (const mount of mounts) {
const destination = mount["Destination"];
if (typeof destination === "string") {
if (destination.endsWith("docker.sock")) {
foundDockerSockMount = true;
break;
}
}
}
return foundDockerSockMount;
}
async executionEnvironment() {
const executionEnv = {};
const runnerOs = process.env["RUNNER_OS"];
executionEnv.NIX_INSTALLER_NO_CONFIRM = "true";
executionEnv.NIX_INSTALLER_DIAGNOSTIC_ATTRIBUTION = JSON.stringify(this.idslib.getCorrelationHashes());
if (this.backtrace !== null) {
executionEnv.RUST_BACKTRACE = this.backtrace;
}
if (this.modifyProfile !== null) {
if (this.modifyProfile) {
executionEnv.NIX_INSTALLER_MODIFY_PROFILE = "true";
}
else {
executionEnv.NIX_INSTALLER_MODIFY_PROFILE = "false";
}
}
if (this.nixBuildGroupId !== null) {
executionEnv.NIX_INSTALLER_NIX_BUILD_GROUP_ID = `${this.nixBuildGroupId}`;
}
if (this.nixBuildGroupName !== null) {
executionEnv.NIX_INSTALLER_NIX_BUILD_GROUP_NAME = this.nixBuildGroupName;
}
if (this.nixBuildUserPrefix !== null) {
executionEnv.NIX_INSTALLER_NIX_BUILD_USER_PREFIX =
this.nixBuildUserPrefix;
}
if (this.nixBuildUserCount !== null) {
executionEnv.NIX_INSTALLER_NIX_BUILD_USER_COUNT = `${this.nixBuildUserCount}`;
}
if (this.nixBuildUserBase !== null) {
executionEnv.NIX_INSTALLER_NIX_BUILD_USER_ID_BASE = `${this.nixBuildUserCount}`;
}
if (this.nixPackageUrl !== null) {
executionEnv.NIX_INSTALLER_NIX_PACKAGE_URL = `${this.nixPackageUrl}`;
}
if (this.proxy !== null) {
executionEnv.NIX_INSTALLER_PROXY = this.proxy;
}
if (this.sslCertFile !== null) {
executionEnv.NIX_INSTALLER_SSL_CERT_FILE = this.sslCertFile;
}
executionEnv.NIX_INSTALLER_DIAGNOSTIC_ENDPOINT =
this.idslib.getDiagnosticsUrl()?.toString() ?? "";
// TODO: Error if the user uses these on not-MacOS
if (this.macEncrypt !== null) {
if (runnerOs !== "macOS") {
throw new Error("`mac-encrypt` while `$RUNNER_OS` was not `macOS`");
}
executionEnv.NIX_INSTALLER_ENCRYPT = this.macEncrypt;
}
if (this.macCaseSensitive !== null) {
if (runnerOs !== "macOS") {
throw new Error("`mac-case-sensitive` while `$RUNNER_OS` was not `macOS`");
}
executionEnv.NIX_INSTALLER_CASE_SENSITIVE = this.macCaseSensitive;
}
if (this.macVolumeLabel !== null) {
if (runnerOs !== "macOS") {
throw new Error("`mac-volume-label` while `$RUNNER_OS` was not `macOS`");
}
executionEnv.NIX_INSTALLER_VOLUME_LABEL = this.macVolumeLabel;
}
if (this.macRootDisk !== null) {
if (runnerOs !== "macOS") {
throw new Error("`mac-root-disk` while `$RUNNER_OS` was not `macOS`");
}
executionEnv.NIX_INSTALLER_ROOT_DISK = this.macRootDisk;
}
if (this.logger !== null) {
executionEnv.NIX_INSTALLER_LOGGER = this.logger;
}
if (this.logDirectives !== null) {
executionEnv.NIX_INSTALLER_LOG_DIRECTIVES = this.logDirectives;
}
// TODO: Error if the user uses these on MacOS
if (this.init !== null) {
if (runnerOs === "macOS") {
throw new Error("`init` is not a valid option when `$RUNNER_OS` is `macOS`");
}
executionEnv.NIX_INSTALLER_INIT = this.init;
}
if (this.startDaemon !== null) {
if (this.startDaemon) {
executionEnv.NIX_INSTALLER_START_DAEMON = "true";
}
else {
executionEnv.NIX_INSTALLER_START_DAEMON = "false";
}
}
let extraConf = "";
if (this.githubServerUrl !== null && this.githubToken !== null) {
const serverUrl = this.githubServerUrl.replace("https://", "");
extraConf += `access-tokens = ${serverUrl}=${this.githubToken}`;
extraConf += "\n";
}
if (this.trustRunnerUser !== null) {
const user = userInfo().username;
if (user) {
extraConf += `trusted-users = root ${user}`;
}
else {
extraConf += `trusted-users = root`;
}
extraConf += "\n";
}
if (this.flakehub) {
extraConf += `netrc-file = ${await this.flakehubLogin()}`;
extraConf += "\n";
}
if (this.extraConf !== null && this.extraConf.length !== 0) {
extraConf += this.extraConf.join("\n");
extraConf += "\n";
}
executionEnv.NIX_INSTALLER_EXTRA_CONF = extraConf;
if (process.env["ACT"] && !process.env["NOT_ACT"]) {
this.idslib.addFact(FACT_IN_GITHUB_ACTIONS, true);
actionsCore.info("Detected `$ACT` environment, assuming this is a https://github.com/nektos/act created container, set `NOT_ACT=true` to override this. This will change the setting of the `init` to be compatible with `act`");
executionEnv.NIX_INSTALLER_INIT = "none";
}
if (process.env["NSC_VM_ID"] && !process.env["NOT_NAMESPACE"]) {
this.idslib.addFact(FACT_IN_NAMESPACE_SO, true);
actionsCore.info("Detected Namespace runner, assuming this is a https://namespace.so created container, set `NOT_NAMESPACE=true` to override this. This will change the setting of the `init` to be compatible with Namespace");
executionEnv.NIX_INSTALLER_INIT = "none";
}
return executionEnv;
}
async executeInstall(binaryPath) {
const executionEnv = await this.executionEnvironment();
actionsCore.debug(`Execution environment: ${JSON.stringify(executionEnv, null, 4)}`);
const args = ["install"];
if (this.planner) {
this.idslib.addFact(FACT_NIX_INSTALLER_PLANNER, this.planner);
args.push(this.planner);
}
else {
this.idslib.addFact(FACT_NIX_INSTALLER_PLANNER, getDefaultPlanner());
args.push(getDefaultPlanner());
}
if (this.extraArgs) {
const extraArgs = stringArgv(this.extraArgs);
args.concat(extraArgs);
}
this.idslib.recordEvent(EVENT_INSTALL_NIX_START);
const exitCode = await actionsExec.exec(binaryPath, args, {
env: {
...executionEnv,
...process.env, // To get $PATH, etc
},
});
if (exitCode !== 0) {
this.idslib.recordEvent(EVENT_INSTALL_NIX_FAILURE, {
exitCode,
});
throw new Error(`Non-zero exit code of \`${exitCode}\` detected`);
}
this.idslib.recordEvent(EVENT_INSTALL_NIX_SUCCESS);
return exitCode;
}
async install() {
const existingInstall = await this.detectExisting();
if (existingInstall) {
if (this.reinstall) {
// We need to uninstall, then reinstall
actionsCore.info("Nix was already installed, `reinstall` is set, uninstalling for a reinstall");
await this.executeUninstall();
}
else {
// We're already installed, and not reinstalling, just set GITHUB_PATH and finish early
await this.setGithubPath();
actionsCore.info("Nix was already installed, using existing install");
return;
}
}
if (this.kvm) {
actionsCore.startGroup("Configuring KVM");
if (await this.setupKvm()) {
actionsCore.endGroup();
actionsCore.info("\u001b[32m Accelerated KVM is enabled \u001b[33m⚡");
actionsCore.exportVariable("DETERMINATE_NIX_KVM", "1");
}
else {
actionsCore.endGroup();
actionsCore.info("KVM is not available.");
actionsCore.exportVariable("DETERMINATE_NIX_KVM", "0");
}
}
// Normal just doing of the install
actionsCore.startGroup("Installing Nix");
const binaryPath = await this.fetchBinary();
await this.executeInstall(binaryPath);
actionsCore.endGroup();
if (this.forceDockerShim) {
await this.spawnDockerShim();
}
await this.setGithubPath();
}
async spawnDockerShim() {
actionsCore.startGroup("Configuring the Docker shim as the Nix Daemon's process supervisor");
const images = {
X64: path.join(__dirname, "/../docker-shim/amd64.tar.gz"),
ARM64: path.join(__dirname, "/../docker-shim/arm64.tar.gz"),
};
const runnerArch = process.env["RUNNER_ARCH"];
let arch;
if (runnerArch === "X64") {
arch = "X64";
}
else if (runnerArch === "ARM64") {
arch = "ARM64";
}
else {
throw Error("Architecture not supported in Docker shim mode.");
}
actionsCore.debug("Loading image: determinate-nix-shim:latest...");
{
const exitCode = await actionsExec.exec("docker", ["image", "load", "--input", images[arch]], {
silent: true,
listeners: {
stdout: (data) => {
const trimmed = data.toString("utf-8").trimEnd();
if (trimmed.length >= 0) {
actionsCore.debug(trimmed);
}
},
stderr: (data) => {
const trimmed = data.toString("utf-8").trimEnd();
if (trimmed.length >= 0) {
actionsCore.debug(trimmed);
}
},
},
});
if (exitCode !== 0) {
throw new Error(`Failed to build the shim image, exit code: \`${exitCode}\``);
}
}
{
actionsCore.debug("Starting the Nix daemon through Docker...");
this.idslib.recordEvent(EVENT_START_DOCKER_SHIM);
const exitCode = await actionsExec.exec("docker", [
"--log-level=debug",
"run",
"--detach",
"--privileged",
"--network=host",
"--userns=host",
"--pid=host",
"--mount",
"type=bind,src=/bin,dst=/bin,readonly",
"--mount",
"type=bind,src=/lib,dst=/lib,readonly",
"--mount",
"type=bind,src=/home,dst=/home,readonly",
"--mount",
"type=bind,src=/tmp,dst=/tmp",
"--mount",
"type=bind,src=/nix,dst=/nix",
"--mount",
"type=bind,src=/etc,dst=/etc,readonly",
"--restart",
"always",
"--init",
"--name",
`determinate-nix-shim-${this.idslib.getUniqueId()}-${randomUUID()}`,
"determinate-nix-shim:latest",
], {
silent: true,
listeners: {
stdline: (data) => {
actionsCore.saveState("docker_shim_container_id", data.trimEnd());
},
stdout: (data) => {
const trimmed = data.toString("utf-8").trimEnd();
if (trimmed.length >= 0) {
actionsCore.debug(trimmed);
}
},
stderr: (data) => {
const trimmed = data.toString("utf-8").trimEnd();
if (trimmed.length >= 0) {
actionsCore.debug(trimmed);
}
},
},
});
if (exitCode !== 0) {
throw new Error(`Failed to start the Nix daemon through Docker, exit code: \`${exitCode}\``);
}
}
actionsCore.endGroup();
return;
}
async cleanupDockerShim() {
const containerId = actionsCore.getState("docker_shim_container_id");
if (containerId !== "") {
actionsCore.startGroup("Cleaning up the Nix daemon's Docker shim");
let cleaned = false;
try {
await actionsExec.exec("docker", ["rm", "--force", containerId]);
cleaned = true;
}
catch {
actionsCore.warning("failed to cleanup nix daemon container");
}
if (!cleaned) {
actionsCore.info("trying to pkill the container's shim process");
try {
await actionsExec.exec("pkill", [containerId]);
cleaned = true;
}
catch {
actionsCore.warning("failed to forcibly kill the container's shim process");
}
}
if (cleaned) {
this.idslib.recordEvent(EVENT_CLEAN_UP_DOCKER_SHIM);
}
else {
actionsCore.warning("Giving up on cleaning up the nix daemon container");
}
actionsCore.endGroup();
}
}
async setGithubPath() {
// Interim versions of the `nix-installer` crate may have already manipulated `$GITHUB_PATH`, as root even! Accessing that will be an error.
try {
const nixVarNixProfilePath = "/nix/var/nix/profiles/default/bin";
const homeNixProfilePath = `${process.env["HOME"]}/.nix-profile/bin`;
actionsCore.addPath(nixVarNixProfilePath);
actionsCore.addPath(homeNixProfilePath);
actionsCore.info(`Added \`${nixVarNixProfilePath}\` and \`${homeNixProfilePath}\` to \`$GITHUB_PATH\``);
}
catch (error) {
actionsCore.info("Skipping setting $GITHUB_PATH in action, the `nix-installer` crate seems to have done this already. From `nix-installer` version 0.11.0 and up, this step is done in the action. Prior to 0.11.0, this was only done in the `nix-installer` binary.");
}
}
async flakehubLogin() {
this.idslib.recordEvent(EVENT_LOGIN_TO_FLAKEHUB);
const netrcPath = `${process.env["RUNNER_TEMP"]}/determinate-nix-installer-netrc`;
const jwt = await actionsCore.getIDToken("api.flakehub.com");
await writeFile(netrcPath, [
`machine api.flakehub.com login flakehub password ${jwt}`,
`machine flakehub.com login flakehub password ${jwt}`,
].join("\n"));
actionsCore.info("Logging in to FlakeHub.");
// the join followed by a match on ^... looks silly, but extra_config
// could contain multi-line values
if (this.extraConf?.join("\n").match(/^netrc-file/m)) {
actionsCore.warning("Logging in to FlakeHub conflicts with the Nix option `netrc-file`.");
}
return netrcPath;
}
async executeUninstall() {
this.idslib.recordEvent(EVENT_UNINSTALL_NIX);
const exitCode = await actionsExec.exec(`/nix/nix-installer`, ["uninstall"], {
env: {
NIX_INSTALLER_NO_CONFIRM: "true",
...process.env, // To get $PATH, etc
},
});
if (exitCode !== 0) {
throw new Error(`Non-zero exit code of \`${exitCode}\` detected`);
}
return exitCode;
}
async detectExisting() {
const receiptPath = "/nix/receipt.json";
try {
await access(receiptPath);
// There is a /nix/receipt.json
return true;
}
catch {
// No /nix/receipt.json
return false;
}
}
async setupKvm() {
this.idslib.recordEvent(EVENT_SETUP_KVM);
const currentUser = userInfo();
const isRoot = currentUser.uid === 0;
const maybeSudo = isRoot ? "" : "sudo";
const kvmRules = "/etc/udev/rules.d/99-determinate-nix-installer-kvm.rules";
try {
const writeFileExitCode = await actionsExec.exec("sh", [
"-c",
`echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | ${maybeSudo} tee ${kvmRules} > /dev/null`,
], {
silent: true,
listeners: {
stdout: (data) => {
const trimmed = data.toString("utf-8").trimEnd();
if (trimmed.length >= 0) {
actionsCore.debug(trimmed);
}
},
stderr: (data) => {
const trimmed = data.toString("utf-8").trimEnd();
if (trimmed.length >= 0) {
actionsCore.debug(trimmed);
}
},
},
});
if (writeFileExitCode !== 0) {
throw new Error(`Non-zero exit code of \`${writeFileExitCode}\` detected while writing '${kvmRules}'`);
}
const debugRootRunThrow = async (action, command, args) => {
if (!isRoot) {
args = [command, ...args];
command = "sudo";
}
const reloadExitCode = await actionsExec.exec(command, args, {
silent: true,
listeners: {
stdout: (data) => {
const trimmed = data.toString("utf-8").trimEnd();
if (trimmed.length >= 0) {
actionsCore.debug(trimmed);
}
},
stderr: (data) => {
const trimmed = data.toString("utf-8").trimEnd();
if (trimmed.length >= 0) {
actionsCore.debug(trimmed);
}
},
},
});
if (reloadExitCode !== 0) {
throw new Error(`Non-zero exit code of \`${reloadExitCode}\` detected while ${action}.`);
}
};
await debugRootRunThrow("reloading udev rules", "udevadm", [
"control",
"--reload-rules",
]);
await debugRootRunThrow("triggering udev against kvm", "udevadm", [
"trigger",
"--name-match=kvm",
]);
return true;
}
catch (error) {
if (isRoot) {
await actionsExec.exec("rm", ["-f", kvmRules]);
}
else {
await actionsExec.exec("sudo", ["rm", "-f", kvmRules]);
}
return false;
}
}
async fetchBinary() {
if (!this.localRoot) {
return await this.idslib.fetchExecutable();
}
else {
const localPath = join(this.localRoot, `nix-installer-${this.platform}`);
actionsCore.info(`Using binary ${localPath}`);
return localPath;
}
}
async reportOverall() {
try {
this.idslib.recordEvent(EVENT_CONCLUDE_WORKFLOW, {
conclusion: await this.getWorkflowConclusion(),
});
}
catch (error) {
actionsCore.debug(`Error submitting post-run diagnostics report: ${error}`);
}
}
async getWorkflowConclusion() {
if (this.githubToken == null) {
return undefined;
}
try {
const octokit = github.getOctokit(this.githubToken);
const jobs = await octokit.paginate(octokit.rest.actions.listJobsForWorkflowRun, {
owner: github.context.repo.owner,
repo: github.context.repo.repo,
/* eslint-disable camelcase */
run_id: github.context.runId,
});
actionsCore.debug(`awaited jobs: ${jobs}`);
const job = jobs
.filter((candidate) => candidate.name === github.context.job)
.at(0);
if (job === undefined) {
return "no-jobs";
}
const outcomes = (job.steps ?? []).map((j) => j.conclusion ?? "unknown");
// Possible values: success, failure, cancelled, or skipped
// from: https://docs.github.com/en/actions/learn-github-actions/contexts
if (outcomes.includes("failure")) {
// Any failures fails the job
return "failure";
}
if (outcomes.includes("cancelled")) {
// Any cancellations cancels the job
return "cancelled";
}
// Assume success if no jobs failed or were canceled
return "success";
}
catch (error) {
actionsCore.debug(`Error determining final disposition: ${error}`);
return "unavailable";
}
}
}
function getDefaultPlanner() {
const envOs = process.env["RUNNER_OS"];
if (envOs === "macOS") {
return "macos";
}
else if (envOs === "Linux") {
return "linux";
}
else {
throw new Error(`Unsupported \`RUNNER_OS\` (currently \`${envOs}\`)`);
}
}
function main() {
const installer = new NixInstallerAction();
installer.idslib.onMain(async () => {
await installer.detectAndForceDockerShim();
await installer.install();
});
installer.idslib.onPost(async () => {
await installer.cleanupDockerShim();
await installer.reportOverall();
});
installer.idslib.execute();
}
main();