diff --git a/dist/index.d.ts b/dist/index.d.ts new file mode 100644 index 0000000..223e65e --- /dev/null +++ b/dist/index.d.ts @@ -0,0 +1,2 @@ + +export { } diff --git a/dist/index.js b/dist/index.js index 2533b6a..5046ef4 100644 --- a/dist/index.js +++ b/dist/index.js @@ -71491,7 +71491,7 @@ const { staticPropertyDescriptors, readOperation, fireAProgressEvent -} = __nccwpck_require__(2882) +} = __nccwpck_require__(5579) const { kState, kError, @@ -71935,7 +71935,7 @@ module.exports = { /***/ }), -/***/ 2882: +/***/ 5579: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { @@ -97258,7 +97258,8 @@ function mungeDiagnosticEndpoint(inputUrl) { * Copyright (c) 2018-2020 [Samuel Carreira] */ //# sourceMappingURL=index.js.map -;// CONCATENATED MODULE: ./dist/main.js +;// CONCATENATED MODULE: ./dist/index.js +// src/index.ts @@ -97270,766 +97271,787 @@ function mungeDiagnosticEndpoint(inputUrl) { -// 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_exports.getNixPlatform(platform_exports.getArchOs()); - this.nixPackageUrl = inputs_exports.getStringOrNull("nix-package-url"); - this.backtrace = inputs_exports.getStringOrNull("backtrace"); - this.extraArgs = inputs_exports.getStringOrNull("extra-args"); - this.extraConf = inputs_exports.getMultilineStringOrNull("extra-conf"); - this.flakehub = inputs_exports.getBool("flakehub"); - this.kvm = inputs_exports.getBool("kvm"); - this.forceDockerShim = inputs_exports.getBool("force-docker-shim"); - this.githubToken = inputs_exports.getStringOrNull("github-token"); - this.githubServerUrl = inputs_exports.getStringOrNull("github-server-url"); - this.init = inputs_exports.getStringOrNull("init"); - this.localRoot = inputs_exports.getStringOrNull("local-root"); - this.logDirectives = inputs_exports.getStringOrNull("log-directives"); - this.logger = inputs_exports.getStringOrNull("logger"); - this.sslCertFile = inputs_exports.getStringOrNull("ssl-cert-file"); - this.proxy = inputs_exports.getStringOrNull("proxy"); - this.macCaseSensitive = inputs_exports.getStringOrNull("mac-case-sensitive"); - this.macEncrypt = inputs_exports.getStringOrNull("mac-encrypt"); - this.macRootDisk = inputs_exports.getStringOrNull("mac-root-disk"); - this.macVolumeLabel = inputs_exports.getStringOrNull("mac-volume-label"); - this.modifyProfile = inputs_exports.getBool("modify-profile"); - this.nixBuildGroupId = inputs_exports.getNumberOrNull("nix-build-group-id"); - this.nixBuildGroupName = inputs_exports.getStringOrNull("nix-build-group-name"); - this.nixBuildUserBase = inputs_exports.getNumberOrNull("nix-build-user-base"); - this.nixBuildUserCount = inputs_exports.getNumberOrNull("nix-build-user-count"); - this.nixBuildUserPrefix = inputs_exports.getStringOrNull("nix-build-user-prefix"); - this.planner = inputs_exports.getStringOrNull("planner"); - this.reinstall = inputs_exports.getBool("reinstall"); - this.startDaemon = inputs_exports.getBool("start-daemon"); - this.trustRunnerUser = inputs_exports.getBool("trust-runner-user"); +var EVENT_INSTALL_NIX_FAILURE = "install_nix_failure"; +var EVENT_INSTALL_NIX_START = "install_nix_start"; +var EVENT_INSTALL_NIX_SUCCESS = "install_nix_start"; +var EVENT_SETUP_KVM = "setup_kvm"; +var EVENT_UNINSTALL_NIX = "uninstall"; +var EVENT_CLEAN_UP_DOCKER_SHIM = "clean_up_docker_shim"; +var EVENT_START_DOCKER_SHIM = "start_docker_shim"; +var EVENT_LOGIN_TO_FLAKEHUB = "login_to_flakehub"; +var EVENT_CONCLUDE_WORKFLOW = "conclude_workflow"; +var FACT_HAS_DOCKER = "has_docker"; +var FACT_HAS_SYSTEMD = "has_systemd"; +var FACT_IN_GITHUB_ACTIONS = "in_act"; +var FACT_IN_NAMESPACE_SO = "in_namespace_so"; +var FACT_NIX_INSTALLER_PLANNER = "nix_installer_planner"; +var NixInstallerAction = class { + constructor() { + this.idslib = new IdsToolbox({ + name: "nix-installer", + fetchStyle: "nix-style", + legacySourcePrefix: "nix-installer", + requireNix: "ignore" + }); + this.platform = platform_exports.getNixPlatform(platform_exports.getArchOs()); + this.nixPackageUrl = inputs_exports.getStringOrNull("nix-package-url"); + this.backtrace = inputs_exports.getStringOrNull("backtrace"); + this.extraArgs = inputs_exports.getStringOrNull("extra-args"); + this.extraConf = inputs_exports.getMultilineStringOrNull("extra-conf"); + this.flakehub = inputs_exports.getBool("flakehub"); + this.kvm = inputs_exports.getBool("kvm"); + this.forceDockerShim = inputs_exports.getBool("force-docker-shim"); + this.githubToken = inputs_exports.getStringOrNull("github-token"); + this.githubServerUrl = inputs_exports.getStringOrNull("github-server-url"); + this.init = inputs_exports.getStringOrNull("init"); + this.localRoot = inputs_exports.getStringOrNull("local-root"); + this.logDirectives = inputs_exports.getStringOrNull("log-directives"); + this.logger = inputs_exports.getStringOrNull("logger"); + this.sslCertFile = inputs_exports.getStringOrNull("ssl-cert-file"); + this.proxy = inputs_exports.getStringOrNull("proxy"); + this.macCaseSensitive = inputs_exports.getStringOrNull("mac-case-sensitive"); + this.macEncrypt = inputs_exports.getStringOrNull("mac-encrypt"); + this.macRootDisk = inputs_exports.getStringOrNull("mac-root-disk"); + this.macVolumeLabel = inputs_exports.getStringOrNull("mac-volume-label"); + this.modifyProfile = inputs_exports.getBool("modify-profile"); + this.nixBuildGroupId = inputs_exports.getNumberOrNull("nix-build-group-id"); + this.nixBuildGroupName = inputs_exports.getStringOrNull("nix-build-group-name"); + this.nixBuildUserBase = inputs_exports.getNumberOrNull("nix-build-user-base"); + this.nixBuildUserCount = inputs_exports.getNumberOrNull("nix-build-user-count"); + this.nixBuildUserPrefix = inputs_exports.getStringOrNull("nix-build-user-prefix"); + this.planner = inputs_exports.getStringOrNull("planner"); + this.reinstall = inputs_exports.getBool("reinstall"); + this.startDaemon = inputs_exports.getBool("start-daemon"); + this.trustRunnerUser = inputs_exports.getBool("trust-runner-user"); + } + async detectAndForceDockerShim() { + const runnerOs = process.env["RUNNER_OS"]; + if (runnerOs !== "Linux") { + if (this.forceDockerShim) { + core.warning( + "Ignoring force-docker-shim which is set to true, as it is only supported on Linux." + ); + this.forceDockerShim = false; + } + return; } - 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) { - core.warning("Ignoring force-docker-shim which is set to true, as it is only supported on Linux."); - this.forceDockerShim = false; - } - return; - } - const systemdCheck = external_node_fs_namespaceObject.statSync("/run/systemd/system", { - throwIfNoEntry: false, - }); - if (systemdCheck?.isDirectory()) { - if (this.forceDockerShim) { - core.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); - core.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 exec.exec("docker", ["info"], { - silent: true, - listeners: { - stdout: (data) => { - const trimmed = data.toString("utf-8").trimEnd(); - if (trimmed.length >= 0) { - core.debug(trimmed); - } - }, - stderr: (data) => { - const trimmed = data.toString("utf-8").trimEnd(); - if (trimmed.length >= 0) { - core.debug(trimmed); - } - }, - }, - }); - } - catch { - core.debug("Docker not detected, not enabling docker shim."); - return; - } - if (exitCode !== 0) { - if (this.forceDockerShim) { - core.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())) { - core.debug("Detected a Docker container with a Docker socket mounted, not enabling docker shim."); - return; - } - core.startGroup("Enabling the Docker shim for running Nix on Linux in CI without Systemd."); - if (this.init !== "none") { - core.info(`Changing init from '${this.init}' to 'none'`); - this.init = "none"; - } - if (this.planner !== "linux") { - core.info(`Changing planner from '${this.planner}' to 'linux'`); - this.planner = "linux"; - } - this.forceDockerShim = true; - core.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 (0,promises_namespaceObject.readFile)("/proc/self/cgroup", { - encoding: "utf-8", - }); - } - catch (e) { - core.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/")) { - core.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 exec.exec("docker", ["inspect", containerId], { - silent: true, - listeners: { - stdout: (data) => { - stdoutBuffer += data.toString("utf-8"); - }, - stderr: (data) => { - stderrBuffer += data.toString("utf-8"); - }, - }, - }); - } - catch (e) { - core.debug(`Could not execute \`docker inspect ${containerId}\`, bailing on docker container inspection:\n${e}`); - return false; - } - if (exitCode !== 0) { - core.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) { - core.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") { - core.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 = (0,external_node_os_.userInfo)().username; - if (user) { - extraConf += `trusted-users = root ${user}`; - } - else { - extraConf += `trusted-users = root`; - } - extraConf += "\n"; - } - if (this.flakehub) { - try { - const flakeHubNetrcFile = await this.flakehubLogin(); - extraConf += `netrc-file = ${flakeHubNetrcFile}`; - extraConf += "\n"; - } - catch (e) { - core.warning(`Failed to set up FlakeHub: ${e}`); - } - } - 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); - core.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); - core.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(); - core.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 = parseArgsStringToArgv(this.extraArgs); - args.concat(extraArgs); - } - this.idslib.recordEvent(EVENT_INSTALL_NIX_START); - const exitCode = await exec.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 - core.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(); - core.info("Nix was already installed, using existing install"); - return; - } - } - if (this.kvm) { - core.startGroup("Configuring KVM"); - if (await this.setupKvm()) { - core.endGroup(); - core.info("\u001b[32m Accelerated KVM is enabled \u001b[33m⚡️"); - core.exportVariable("DETERMINATE_NIX_KVM", "1"); - } - else { - core.endGroup(); - core.info("KVM is not available."); - core.exportVariable("DETERMINATE_NIX_KVM", "0"); - } - } - // Normal just doing of the install - core.startGroup("Installing Nix"); - const binaryPath = await this.fetchBinary(); - await this.executeInstall(binaryPath); - core.endGroup(); - if (this.forceDockerShim) { - await this.spawnDockerShim(); - } - await this.setGithubPath(); - } - async spawnDockerShim() { - core.startGroup("Configuring the Docker shim as the Nix Daemon's process supervisor"); - const images = { - X64: __nccwpck_require__.ab + "amd64.tar.gz", - ARM64: __nccwpck_require__.ab + "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."); - } - core.debug("Loading image: determinate-nix-shim:latest..."); - { - const exitCode = await exec.exec("docker", ["image", "load", "--input", images[arch]], { - silent: true, - listeners: { - stdout: (data) => { - const trimmed = data.toString("utf-8").trimEnd(); - if (trimmed.length >= 0) { - core.debug(trimmed); - } - }, - stderr: (data) => { - const trimmed = data.toString("utf-8").trimEnd(); - if (trimmed.length >= 0) { - core.debug(trimmed); - } - }, - }, - }); - if (exitCode !== 0) { - throw new Error(`Failed to build the shim image, exit code: \`${exitCode}\``); - } - } - { - core.debug("Starting the Nix daemon through Docker..."); - this.idslib.recordEvent(EVENT_START_DOCKER_SHIM); - const exitCode = await exec.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()}-${(0,external_node_crypto_namespaceObject.randomUUID)()}`, - "determinate-nix-shim:latest", - ], { - silent: true, - listeners: { - stdline: (data) => { - core.saveState("docker_shim_container_id", data.trimEnd()); - }, - stdout: (data) => { - const trimmed = data.toString("utf-8").trimEnd(); - if (trimmed.length >= 0) { - core.debug(trimmed); - } - }, - stderr: (data) => { - const trimmed = data.toString("utf-8").trimEnd(); - if (trimmed.length >= 0) { - core.debug(trimmed); - } - }, - }, - }); - if (exitCode !== 0) { - throw new Error(`Failed to start the Nix daemon through Docker, exit code: \`${exitCode}\``); - } - } - core.endGroup(); + const systemdCheck = external_node_fs_namespaceObject.statSync("/run/systemd/system", { + throwIfNoEntry: false + }); + if (systemdCheck?.isDirectory()) { + if (this.forceDockerShim) { + core.warning( + "Systemd is detected, but ignoring it since force-docker-shim is enabled." + ); + } else { + this.idslib.addFact(FACT_HAS_SYSTEMD, true); return; + } } - async cleanupDockerShim() { - const containerId = core.getState("docker_shim_container_id"); - if (containerId !== "") { - core.startGroup("Cleaning up the Nix daemon's Docker shim"); - let cleaned = false; - try { - await exec.exec("docker", ["rm", "--force", containerId]); - cleaned = true; + this.idslib.addFact(FACT_HAS_SYSTEMD, false); + core.debug( + "Linux detected without systemd, testing for Docker with `docker info` as an alternative daemon supervisor." + ); + this.idslib.addFact(FACT_HAS_DOCKER, false); + let exitCode; + try { + exitCode = await exec.exec("docker", ["info"], { + silent: true, + listeners: { + stdout: (data) => { + const trimmed = data.toString("utf-8").trimEnd(); + if (trimmed.length >= 0) { + core.debug(trimmed); } - catch { - core.warning("failed to cleanup nix daemon container"); + }, + stderr: (data) => { + const trimmed = data.toString("utf-8").trimEnd(); + if (trimmed.length >= 0) { + core.debug(trimmed); } - if (!cleaned) { - core.info("trying to pkill the container's shim process"); - try { - await exec.exec("pkill", [containerId]); - cleaned = true; - } - catch { - core.warning("failed to forcibly kill the container's shim process"); - } - } - if (cleaned) { - this.idslib.recordEvent(EVENT_CLEAN_UP_DOCKER_SHIM); - } - else { - core.warning("Giving up on cleaning up the nix daemon container"); - } - core.endGroup(); + } } + }); + } catch { + core.debug("Docker not detected, not enabling docker shim."); + return; } - 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`; - core.addPath(nixVarNixProfilePath); - core.addPath(homeNixProfilePath); - core.info(`Added \`${nixVarNixProfilePath}\` and \`${homeNixProfilePath}\` to \`$GITHUB_PATH\``); - } - catch { - core.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."); - } + if (exitCode !== 0) { + if (this.forceDockerShim) { + core.warning( + "docker info check failed, but trying anyway since force-docker-shim is enabled." + ); + } else { + return; + } } - async flakehubLogin() { - this.idslib.recordEvent(EVENT_LOGIN_TO_FLAKEHUB); - const netrcPath = `${process.env["RUNNER_TEMP"]}/determinate-nix-installer-netrc`; - const jwt = await core.getIDToken("api.flakehub.com"); - await (0,promises_namespaceObject.writeFile)(netrcPath, [ - `machine api.flakehub.com login flakehub password ${jwt}`, - `machine flakehub.com login flakehub password ${jwt}`, - ].join("\n")); - core.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)) { - core.warning("Logging in to FlakeHub conflicts with the Nix option `netrc-file`."); - } - return netrcPath; + this.idslib.addFact(FACT_HAS_DOCKER, true); + if (!this.forceDockerShim && await this.detectDockerWithMountedDockerSocket()) { + core.debug( + "Detected a Docker container with a Docker socket mounted, not enabling docker shim." + ); + return; } - async executeUninstall() { - this.idslib.recordEvent(EVENT_UNINSTALL_NIX); - const exitCode = await exec.exec(`/nix/nix-installer`, ["uninstall"], { - env: { - NIX_INSTALLER_NO_CONFIRM: "true", - ...process.env, // To get $PATH, etc + core.startGroup( + "Enabling the Docker shim for running Nix on Linux in CI without Systemd." + ); + if (this.init !== "none") { + core.info(`Changing init from '${this.init}' to 'none'`); + this.init = "none"; + } + if (this.planner !== "linux") { + core.info(`Changing planner from '${this.planner}' to 'linux'`); + this.planner = "linux"; + } + this.forceDockerShim = true; + core.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 { + cgroupsBuffer = await (0,promises_namespaceObject.readFile)("/proc/self/cgroup", { + encoding: "utf-8" + }); + } catch (e) { + core.debug( + `Did not detect \`/proc/self/cgroup\` existence, bailing on docker container ID detection: +${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/")) { + core.debug( + "Did not detect a container ID, bailing on docker.sock detection" + ); + return false; + } + const lastCgroupPathParts = lastCgroupPath.split("/"); + const containerId = lastCgroupPathParts[lastCgroupPathParts.length - 1]; + let stdoutBuffer = ""; + let stderrBuffer = ""; + let exitCode; + try { + exitCode = await exec.exec("docker", ["inspect", containerId], { + silent: true, + listeners: { + stdout: (data) => { + stdoutBuffer += data.toString("utf-8"); + }, + stderr: (data) => { + stderrBuffer += data.toString("utf-8"); + } + } + }); + } catch (e) { + core.debug( + `Could not execute \`docker inspect ${containerId}\`, bailing on docker container inspection: +${e}` + ); + return false; + } + if (exitCode !== 0) { + core.debug( + `Unable to inspect detected docker container with id \`${containerId}\`, bailing on container inspection (exit ${exitCode}): +${stderrBuffer}` + ); + return false; + } + const output = JSON.parse(stdoutBuffer); + if (output.length !== 1) { + core.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]; + const mounts = item["Mounts"]; + if (typeof mounts !== "object") { + core.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() ?? ""; + 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; + } + 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 = (0,external_node_os_.userInfo)().username; + if (user) { + extraConf += `trusted-users = root ${user}`; + } else { + extraConf += `trusted-users = root`; + } + extraConf += "\n"; + } + if (this.flakehub) { + try { + const flakeHubNetrcFile = await this.flakehubLogin(); + extraConf += `netrc-file = ${flakeHubNetrcFile}`; + extraConf += "\n"; + } catch (e) { + core.warning(`Failed to set up FlakeHub: ${e}`); + } + } + 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); + core.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); + core.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(); + core.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 = parseArgsStringToArgv(this.extraArgs); + args.concat(extraArgs); + } + this.idslib.recordEvent(EVENT_INSTALL_NIX_START); + const exitCode = await exec.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) { + core.info( + "Nix was already installed, `reinstall` is set, uninstalling for a reinstall" + ); + await this.executeUninstall(); + } else { + await this.setGithubPath(); + core.info("Nix was already installed, using existing install"); + return; + } + } + if (this.kvm) { + core.startGroup("Configuring KVM"); + if (await this.setupKvm()) { + core.endGroup(); + core.info("\x1B[32m Accelerated KVM is enabled \x1B[33m\u26A1\uFE0F"); + core.exportVariable("DETERMINATE_NIX_KVM", "1"); + } else { + core.endGroup(); + core.info("KVM is not available."); + core.exportVariable("DETERMINATE_NIX_KVM", "0"); + } + } + core.startGroup("Installing Nix"); + const binaryPath = await this.fetchBinary(); + await this.executeInstall(binaryPath); + core.endGroup(); + if (this.forceDockerShim) { + await this.spawnDockerShim(); + } + await this.setGithubPath(); + } + async spawnDockerShim() { + core.startGroup( + "Configuring the Docker shim as the Nix Daemon's process supervisor" + ); + const images = { + X64: __nccwpck_require__.ab + "amd64.tar.gz", + ARM64: __nccwpck_require__.ab + "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."); + } + core.debug("Loading image: determinate-nix-shim:latest..."); + { + const exitCode = await exec.exec( + "docker", + ["image", "load", "--input", images[arch]], + { + silent: true, + listeners: { + stdout: (data) => { + const trimmed = data.toString("utf-8").trimEnd(); + if (trimmed.length >= 0) { + core.debug(trimmed); + } }, + stderr: (data) => { + const trimmed = data.toString("utf-8").trimEnd(); + if (trimmed.length >= 0) { + core.debug(trimmed); + } + } + } + } + ); + if (exitCode !== 0) { + throw new Error( + `Failed to build the shim image, exit code: \`${exitCode}\`` + ); + } + } + { + core.debug("Starting the Nix daemon through Docker..."); + this.idslib.recordEvent(EVENT_START_DOCKER_SHIM); + const exitCode = await exec.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()}-${(0,external_node_crypto_namespaceObject.randomUUID)()}`, + "determinate-nix-shim:latest" + ], + { + silent: true, + listeners: { + stdline: (data) => { + core.saveState("docker_shim_container_id", data.trimEnd()); + }, + stdout: (data) => { + const trimmed = data.toString("utf-8").trimEnd(); + if (trimmed.length >= 0) { + core.debug(trimmed); + } + }, + stderr: (data) => { + const trimmed = data.toString("utf-8").trimEnd(); + if (trimmed.length >= 0) { + core.debug(trimmed); + } + } + } + } + ); + if (exitCode !== 0) { + throw new Error( + `Failed to start the Nix daemon through Docker, exit code: \`${exitCode}\`` + ); + } + } + core.endGroup(); + return; + } + async cleanupDockerShim() { + const containerId = core.getState("docker_shim_container_id"); + if (containerId !== "") { + core.startGroup("Cleaning up the Nix daemon's Docker shim"); + let cleaned = false; + try { + await exec.exec("docker", ["rm", "--force", containerId]); + cleaned = true; + } catch { + core.warning("failed to cleanup nix daemon container"); + } + if (!cleaned) { + core.info("trying to pkill the container's shim process"); + try { + await exec.exec("pkill", [containerId]); + cleaned = true; + } catch { + core.warning( + "failed to forcibly kill the container's shim process" + ); + } + } + if (cleaned) { + this.idslib.recordEvent(EVENT_CLEAN_UP_DOCKER_SHIM); + } else { + core.warning( + "Giving up on cleaning up the nix daemon container" + ); + } + core.endGroup(); + } + } + async setGithubPath() { + try { + const nixVarNixProfilePath = "/nix/var/nix/profiles/default/bin"; + const homeNixProfilePath = `${process.env["HOME"]}/.nix-profile/bin`; + core.addPath(nixVarNixProfilePath); + core.addPath(homeNixProfilePath); + core.info( + `Added \`${nixVarNixProfilePath}\` and \`${homeNixProfilePath}\` to \`$GITHUB_PATH\`` + ); + } catch { + core.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 core.getIDToken("api.flakehub.com"); + await (0,promises_namespaceObject.writeFile)( + netrcPath, + [ + `machine api.flakehub.com login flakehub password ${jwt}`, + `machine flakehub.com login flakehub password ${jwt}` + ].join("\n") + ); + core.info("Logging in to FlakeHub."); + if (this.extraConf?.join("\n").match(/^netrc-file/m)) { + core.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 exec.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 (0,promises_namespaceObject.access)(receiptPath); + return true; + } catch { + return false; + } + } + async setupKvm() { + this.idslib.recordEvent(EVENT_SETUP_KVM); + const currentUser = (0,external_node_os_.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 exec.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) { + core.debug(trimmed); + } + }, + stderr: (data) => { + const trimmed = data.toString("utf-8").trimEnd(); + if (trimmed.length >= 0) { + core.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 exec.exec(command, args, { + silent: true, + listeners: { + stdout: (data) => { + const trimmed = data.toString("utf-8").trimEnd(); + if (trimmed.length >= 0) { + core.debug(trimmed); + } + }, + stderr: (data) => { + const trimmed = data.toString("utf-8").trimEnd(); + if (trimmed.length >= 0) { + core.debug(trimmed); + } + } + } }); - if (exitCode !== 0) { - throw new Error(`Non-zero exit code of \`${exitCode}\` detected`); + if (reloadExitCode !== 0) { + throw new Error( + `Non-zero exit code of \`${reloadExitCode}\` detected while ${action}.` + ); } - return exitCode; + }; + await debugRootRunThrow("reloading udev rules", "udevadm", [ + "control", + "--reload-rules" + ]); + await debugRootRunThrow("triggering udev against kvm", "udevadm", [ + "trigger", + "--name-match=kvm" + ]); + return true; + } catch { + if (isRoot) { + await exec.exec("rm", ["-f", kvmRules]); + } else { + await exec.exec("sudo", ["rm", "-f", kvmRules]); + } + return false; } - async detectExisting() { - const receiptPath = "/nix/receipt.json"; - try { - await (0,promises_namespaceObject.access)(receiptPath); - // There is a /nix/receipt.json - return true; - } - catch { - // No /nix/receipt.json - return false; - } + } + async fetchBinary() { + if (!this.localRoot) { + return await this.idslib.fetchExecutable(); + } else { + const localPath = (0,external_node_path_namespaceObject.join)(this.localRoot, `nix-installer-${this.platform}`); + core.info(`Using binary ${localPath}`); + return localPath; } - async setupKvm() { - this.idslib.recordEvent(EVENT_SETUP_KVM); - const currentUser = (0,external_node_os_.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 exec.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) { - core.debug(trimmed); - } - }, - stderr: (data) => { - const trimmed = data.toString("utf-8").trimEnd(); - if (trimmed.length >= 0) { - core.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 exec.exec(command, args, { - silent: true, - listeners: { - stdout: (data) => { - const trimmed = data.toString("utf-8").trimEnd(); - if (trimmed.length >= 0) { - core.debug(trimmed); - } - }, - stderr: (data) => { - const trimmed = data.toString("utf-8").trimEnd(); - if (trimmed.length >= 0) { - core.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 { - if (isRoot) { - await exec.exec("rm", ["-f", kvmRules]); - } - else { - await exec.exec("sudo", ["rm", "-f", kvmRules]); - } - return false; - } + } + async reportOverall() { + try { + this.idslib.recordEvent(EVENT_CONCLUDE_WORKFLOW, { + conclusion: await this.getWorkflowConclusion() + }); + } catch (e) { + core.debug(`Error submitting post-run diagnostics report: ${e}`); } - async fetchBinary() { - if (!this.localRoot) { - return await this.idslib.fetchExecutable(); - } - else { - const localPath = (0,external_node_path_namespaceObject.join)(this.localRoot, `nix-installer-${this.platform}`); - core.info(`Using binary ${localPath}`); - return localPath; - } + } + async getWorkflowConclusion() { + if (this.githubToken == null) { + return void 0; } - async reportOverall() { - try { - this.idslib.recordEvent(EVENT_CONCLUDE_WORKFLOW, { - conclusion: await this.getWorkflowConclusion(), - }); - } - catch (e) { - core.debug(`Error submitting post-run diagnostics report: ${e}`); + 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 } + ); + core.debug(`awaited jobs: ${jobs}`); + const job = jobs.filter((candidate) => candidate.name === github.context.job).at(0); + if (job === void 0) { + return "no-jobs"; + } + const outcomes = (job.steps ?? []).map((j) => j.conclusion ?? "unknown"); + if (outcomes.includes("failure")) { + return "failure"; + } + if (outcomes.includes("cancelled")) { + return "cancelled"; + } + return "success"; + } catch (e) { + core.debug(`Error determining final disposition: ${e}`); + return "unavailable"; } - 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, - }); - core.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 (e) { - core.debug(`Error determining final disposition: ${e}`); - 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}\`)`); - } + 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(); + 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(); diff --git a/dist/main.d.ts b/dist/main.d.ts deleted file mode 100644 index cb0ff5c..0000000 --- a/dist/main.d.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/dist/main.js b/dist/main.js deleted file mode 100644 index 7aa866b..0000000 --- a/dist/main.js +++ /dev/null @@ -1,773 +0,0 @@ -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 { - 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) { - try { - const flakeHubNetrcFile = await this.flakehubLogin(); - extraConf += `netrc-file = ${flakeHubNetrcFile}`; - extraConf += "\n"; - } - catch (e) { - actionsCore.warning(`Failed to set up FlakeHub: ${e}`); - } - } - 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 { - 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 { - 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 (e) { - actionsCore.debug(`Error submitting post-run diagnostics report: ${e}`); - } - } - 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 (e) { - actionsCore.debug(`Error determining final disposition: ${e}`); - 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(); diff --git a/package.json b/package.json index 46ed045..9f169a5 100644 --- a/package.json +++ b/package.json @@ -2,11 +2,11 @@ "name": "nix-installer-action", "version": "1.0.0", "description": "You can use [`nix-installer`](https://github.com/DeterminateSystems/nix-installer) as a Github Action:", - "main": "./dist/main.js", - "types": "./dist/main.d.ts", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", "type": "module", "scripts": { - "build": "tsc", + "build": "tsup", "format": "prettier --write .", "check-fmt": "prettier --check .", "lint": "eslint src/**/*.ts", diff --git a/src/main.ts b/src/index.ts similarity index 100% rename from src/main.ts rename to src/index.ts diff --git a/tsup.config.ts b/tsup.config.ts new file mode 100644 index 0000000..e374aaf --- /dev/null +++ b/tsup.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from "tsup"; +import { name } from "./package.json"; + +export default defineConfig({ + name, + entry: ["src/index.ts"], + format: ["esm"], + target: "node20", + bundle: true, + splitting: false, + clean: true, + dts: { + resolve: true, + }, +});