Compare commits

...

25 commits

Author SHA1 Message Date
Luc Perkins 8d4286b90e
Merge pull request #87 from DeterminateSystems/fix-docker-invocation
Add CI run for aarch64-darwin
2024-05-15 17:25:33 -04:00
Luc Perkins ca7cf68c63
Add megajob called tests 2024-05-15 18:16:09 -03:00
Luc Perkins 94a9e4375c
Rename jobs 2024-05-15 17:56:08 -03:00
Luc Perkins da29cfd994
Merge remote-tracking branch 'origin/main' into fix-docker-invocation 2024-05-15 17:51:53 -03:00
Graham Christensen 1e58ce3980
Revert "Fix ESM build" (#86) 2024-05-15 20:45:00 +00:00
Luc Perkins 83c8f7dfdd
Fix formatting 2024-05-15 17:44:11 -03:00
Luc Perkins 37d6eb5161
Add CI run for aarch64-darwin 2024-05-15 17:39:35 -03:00
Luc Perkins 813cf108af
Merge pull request #83 from DeterminateSystems/fix-esm-build
Fix ESM build
2024-05-15 16:24:28 -04:00
Luc Perkins 4a8c7256d3
Don't clean dist using tsup 2024-05-15 17:18:32 -03:00
Luc Perkins e4a38c246a
Regenerate dist 2024-05-15 17:17:12 -03:00
Luc Perkins 9243e9b760
Fix merge conflicts with main 2024-05-15 17:16:08 -03:00
Graham Christensen 8cdf194da9
Update dependencies (#85)
* Update dependencies

* Update deps again :)
2024-05-09 20:29:18 +00:00
Luc Perkins 74b8a1f4e8
Update dependencies 2024-05-03 11:05:02 -03:00
Luc Perkins 51bc05e2ea
Fix method of finding docker shims 2024-05-02 17:19:07 -03:00
Luc Perkins 9ffa76fa74
Fix ESM build 2024-05-02 17:07:51 -03:00
Luc Perkins 10f43c0d32
Merge pull request #82 from DeterminateSystems/tsup-build
Switch to tsup for building
2024-05-02 16:45:35 -03:00
Luc Perkins 337589f84d
Switch to tsup for building 2024-05-02 16:35:58 -03:00
Luc Perkins 330a0ca1bc
Merge pull request #80 from DeterminateSystems/update-checkout-action
Standardize on v4 of the checkout action
2024-05-02 14:20:56 -03:00
Luc Perkins 2c90bb97d0
Standardize on v4 of the checkout action 2024-05-02 14:10:17 -03:00
Luc Perkins 91b8eb110e
Merge pull request #79 from DeterminateSystems/fix-mnc-issue
Fix broken CI
2024-05-02 14:05:28 -03:00
Luc Perkins ff2ea37e97
Remove MNC from workflow 2024-05-02 13:58:19 -03:00
Luc Perkins f957521643
Reinstate the MNC prior to reinstall 2024-05-02 13:54:44 -03:00
Luc Perkins 23ddac323e
Remove second reinstall setting 2024-05-02 13:49:26 -03:00
Luc Perkins 3785eaaa9b
Fix other instance of uninstall/reinstall 2024-05-02 13:48:27 -03:00
Luc Perkins 6b7860826e
Remove reinstall setting 2024-05-02 13:47:23 -03:00
10 changed files with 907 additions and 1600 deletions

View file

@ -7,6 +7,15 @@ on:
workflow_dispatch:
jobs:
tests:
runs-on: ubuntu-22.04
needs:
- check-dist-up-to-date
- install-nix-linux
- install-nix-macos
steps:
- run: true
check-dist-up-to-date:
name: Check the dist/ folder is up to date
runs-on: ubuntu-22.04
@ -28,8 +37,9 @@ jobs:
run: git status --porcelain=v1
- name: Ensure no staged changes
run: git diff --exit-code
run-test-suite:
name: Run test suite
install-nix-linux:
name: Run test suite for Linux systems
strategy:
matrix:
runner:
@ -41,14 +51,13 @@ jobs:
contents: read
id-token: write
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install Nix
uses: ./
with:
logger: pretty
log-directives: nix_installer=trace
backtrace: full
- uses: DeterminateSystems/magic-nix-cache-action@main
- name: echo $PATH
run: echo $PATH
@ -117,21 +126,27 @@ jobs:
duration: 5m
authorized-users: grahamc
run-x86_64-darwin:
name: Run x86_64 Darwin
runs-on: macos-12
install-nix-macos:
name: Run test suite for macOS systems
strategy:
matrix:
runner:
# x86_64-darwin
- macos-12
# aarch64-darwin
- macos-latest-xlarge
runs-on: ${{ matrix.runner }}
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install Nix
uses: ./
with:
logger: pretty
log-directives: nix_installer=trace
backtrace: full
- uses: DeterminateSystems/magic-nix-cache-action@main
- name: echo $PATH
run: echo $PATH
- name: Test `nix` with `$GITHUB_PATH`
@ -168,10 +183,6 @@ jobs:
hello
nix store gc
nix run nixpkgs#hello
- name: Terminate the magic nix cache pre-reinstall
if: success() || failure()
run: |
pkill magic-nix-cache
- name: Reinstall Nix
uses: ./
with:

View file

@ -27,7 +27,7 @@ jobs:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: DeterminateSystems/nix-installer-action@main
- run: nix build .
```
@ -50,7 +50,7 @@ jobs:
id-token: "write"
contents: "read"
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: DeterminateSystems/nix-installer-action@main
with:
flakehub: true

2
dist/index.d.ts generated vendored Normal file
View file

@ -0,0 +1,2 @@
export { }

1570
dist/index.js generated vendored

File diff suppressed because it is too large Load diff

1
dist/main.d.ts generated vendored
View file

@ -1 +0,0 @@
export {};

773
dist/main.js generated vendored
View file

@ -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();

View file

@ -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",
@ -33,7 +33,7 @@
},
"devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/node": "^20.12.8",
"@types/node": "^20.12.11",
"@types/uuid": "^9.0.8",
"@typescript-eslint/eslint-plugin": "^7.8.0",
"@vercel/ncc": "^0.38.1",

View file

@ -16,7 +16,7 @@ dependencies:
version: 5.1.1
detsys-ts:
specifier: github:DeterminateSystems/detsys-ts
version: github.com/DeterminateSystems/detsys-ts/cd38b227c4d6faca10aed591b1f8863ef7b93dce
version: github.com/DeterminateSystems/detsys-ts/2391ba1ef3d22027cd4d9ecce147007a88f63643
string-argv:
specifier: ^0.3.2
version: 0.3.2
@ -26,8 +26,8 @@ devDependencies:
specifier: ^4.3.0
version: 4.3.0(prettier@3.2.5)
"@types/node":
specifier: ^20.12.8
version: 20.12.8
specifier: ^20.12.11
version: 20.12.11
"@types/uuid":
specifier: ^9.0.8
version: 9.0.8
@ -1327,14 +1327,14 @@ packages:
integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==,
}
dependencies:
"@types/node": 20.12.8
"@types/node": 20.12.11
form-data: 4.0.0
dev: false
/@types/node@20.12.8:
/@types/node@20.12.11:
resolution:
{
integrity: sha512-NU0rJLJnshZWdE/097cdCBbyW1h4hEg0xpovcoAQYHl8dnEyp/NAOiE45pvc+Bd1Dt+2r94v2eGFpQJ4R7g+2w==,
integrity: sha512-vDg9PZ/zi+Nqp6boSOT7plNuthRugEKixDv5sFTIpkE89MmNtEArAShI4mxuX2+UrLEe9pxC1vm2cjm9YlWbJw==,
}
dependencies:
undici-types: 5.26.5
@ -1352,7 +1352,7 @@ packages:
integrity: sha512-sOUTGn6h1SfQ+gbgqC364jLFBw2lnFqkgF3q0WovEHRLMrVD1sd5aufqi/aJObLekJO+Aq5z646U4Oxy6shXMA==,
}
dependencies:
"@types/node": 20.12.8
"@types/node": 20.12.11
dev: false
/@types/uuid@9.0.8:
@ -1387,7 +1387,7 @@ packages:
graphemer: 1.4.0
ignore: 5.3.1
natural-compare: 1.4.0
semver: 7.6.0
semver: 7.6.2
ts-api-utils: 1.3.0(typescript@5.4.5)
typescript: 5.4.5
transitivePeerDependencies:
@ -1478,7 +1478,7 @@ packages:
globby: 11.1.0
is-glob: 4.0.3
minimatch: 9.0.4
semver: 7.6.0
semver: 7.6.2
ts-api-utils: 1.3.0(typescript@5.4.5)
typescript: 5.4.5
transitivePeerDependencies:
@ -1501,7 +1501,7 @@ packages:
"@typescript-eslint/types": 7.8.0
"@typescript-eslint/typescript-estree": 7.8.0(typescript@5.4.5)
eslint: 8.57.0
semver: 7.6.0
semver: 7.6.2
transitivePeerDependencies:
- supports-color
- typescript
@ -1843,10 +1843,10 @@ packages:
engines: { node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7 }
hasBin: true
dependencies:
caniuse-lite: 1.0.30001615
electron-to-chromium: 1.4.754
caniuse-lite: 1.0.30001617
electron-to-chromium: 1.4.761
node-releases: 2.0.14
update-browserslist-db: 1.0.14(browserslist@4.23.0)
update-browserslist-db: 1.0.15(browserslist@4.23.0)
dev: true
/bundle-require@4.1.0(esbuild@0.19.12):
@ -1916,10 +1916,10 @@ packages:
engines: { node: ">=6" }
dev: true
/caniuse-lite@1.0.30001615:
/caniuse-lite@1.0.30001617:
resolution:
{
integrity: sha512-1IpazM5G3r38meiae0bHRnPhz+CBQ3ZLqbQMtrg+AsTPKAXgW38JNsXkyZ+v8waCsDmPq87lmfun5Q2AGysNEQ==,
integrity: sha512-mLyjzNI9I+Pix8zwcrpxEbGlfqOkF9kM3ptzmKNw5tizSyYwMe+nGLTqMK9cO+0E+Bh6TsBxNAaHWEM8xwSsmA==,
}
dev: true
@ -2214,10 +2214,10 @@ packages:
}
dev: true
/electron-to-chromium@1.4.754:
/electron-to-chromium@1.4.761:
resolution:
{
integrity: sha512-7Kr5jUdns5rL/M9wFFmMZAgFDuL2YOnanFH4OI4iFzUqyh3XOL7nAGbSlSMZdzKMIyyTpNSbqZsWG9odwLeKvA==,
integrity: sha512-PIbxpiJGx6Bb8dQaonNc6CGTRlVntdLg/2nMa1YhnrwYOORY9a3ZgGN0UQYE6lAcj/lkyduJN7BPt/JiY+jAQQ==,
}
dev: true
@ -2235,10 +2235,10 @@ packages:
}
dev: true
/enhanced-resolve@5.16.0:
/enhanced-resolve@5.16.1:
resolution:
{
integrity: sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA==,
integrity: sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==,
}
engines: { node: ">=10.13.0" }
dependencies:
@ -2479,12 +2479,12 @@ packages:
eslint-plugin-import: "*"
dependencies:
debug: 4.3.4
enhanced-resolve: 5.16.0
enhanced-resolve: 5.16.1
eslint: 8.57.0
eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.8.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.8.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
fast-glob: 3.3.2
get-tsconfig: 4.7.3
get-tsconfig: 4.7.5
is-core-module: 2.13.1
is-glob: 4.0.3
transitivePeerDependencies:
@ -3110,10 +3110,10 @@ packages:
get-intrinsic: 1.2.4
dev: true
/get-tsconfig@4.7.3:
/get-tsconfig@4.7.5:
resolution:
{
integrity: sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg==,
integrity: sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==,
}
dependencies:
resolve-pkg-maps: 1.0.0
@ -3139,10 +3139,10 @@ packages:
is-glob: 4.0.3
dev: true
/glob@10.3.12:
/glob@10.3.14:
resolution:
{
integrity: sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==,
integrity: sha512-4fkAqu93xe9Mk7le9v0y3VrPDqLKHarNi2s4Pv7f2yOvfhWfhc7hRPHC/JyqMqb8B/Dt/eGS4n7ykwf3fOsl8g==,
}
engines: { node: ">=16 || 14 >=14.17" }
hasBin: true
@ -3150,8 +3150,8 @@ packages:
foreground-child: 3.1.1
jackspeak: 2.3.6
minimatch: 9.0.4
minipass: 7.0.4
path-scurry: 1.10.2
minipass: 7.1.1
path-scurry: 1.11.0
dev: true
/glob@7.2.3:
@ -3940,16 +3940,6 @@ packages:
engines: { node: 14 || >=16.14 }
dev: true
/lru-cache@6.0.0:
resolution:
{
integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==,
}
engines: { node: ">=10" }
dependencies:
yallist: 4.0.0
dev: true
/merge-stream@2.0.0:
resolution:
{
@ -4043,10 +4033,10 @@ packages:
}
dev: true
/minipass@7.0.4:
/minipass@7.1.1:
resolution:
{
integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==,
integrity: sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==,
}
engines: { node: ">=16 || 14 >=14.17" }
dev: true
@ -4318,15 +4308,15 @@ packages:
}
dev: true
/path-scurry@1.10.2:
/path-scurry@1.11.0:
resolution:
{
integrity: sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==,
integrity: sha512-LNHTaVkzaYaLGlO+0u3rQTz7QrHTFOuKyba9JMTQutkmtNew8dw8wOD7mTU/5fCPZzCWpfW0XnQKzY61P0aTaw==,
}
engines: { node: ">=16 || 14 >=14.17" }
dependencies:
lru-cache: 10.2.2
minipass: 7.0.4
minipass: 7.1.1
dev: true
/path-type@4.0.0:
@ -4638,15 +4628,13 @@ packages:
}
hasBin: true
/semver@7.6.0:
/semver@7.6.2:
resolution:
{
integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==,
integrity: sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==,
}
engines: { node: ">=10" }
hasBin: true
dependencies:
lru-cache: 6.0.0
dev: true
/set-function-length@1.2.2:
@ -4871,7 +4859,7 @@ packages:
dependencies:
"@jridgewell/gen-mapping": 0.3.5
commander: 4.1.1
glob: 10.3.12
glob: 10.3.14
lines-and-columns: 1.2.4
mz: 2.7.0
pirates: 4.0.6
@ -5212,10 +5200,10 @@ packages:
}
dev: false
/update-browserslist-db@1.0.14(browserslist@4.23.0):
/update-browserslist-db@1.0.15(browserslist@4.23.0):
resolution:
{
integrity: sha512-JixKH8GR2pWYshIPUg/NujK3JO7JiqEEUiNArE86NQyrgUuZeTlZQN3xuS/yiV5Kb48ev9K6RqNkaJjXsdg7Jw==,
integrity: sha512-K9HWH62x3/EalU1U6sjSZiylm9C8tgq2mSvshZpqc7QE69RaA2qjhkW2HlNA0tFpEbtyFz7HTqbSdN4MSwUodA==,
}
hasBin: true
peerDependencies:
@ -5416,13 +5404,6 @@ packages:
engines: { node: ">=4.0" }
dev: false
/yallist@4.0.0:
resolution:
{
integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==,
}
dev: true
/yaml@2.4.2:
resolution:
{
@ -5440,10 +5421,10 @@ packages:
engines: { node: ">=10" }
dev: true
github.com/DeterminateSystems/detsys-ts/cd38b227c4d6faca10aed591b1f8863ef7b93dce:
github.com/DeterminateSystems/detsys-ts/2391ba1ef3d22027cd4d9ecce147007a88f63643:
resolution:
{
tarball: https://codeload.github.com/DeterminateSystems/detsys-ts/tar.gz/cd38b227c4d6faca10aed591b1f8863ef7b93dce,
tarball: https://codeload.github.com/DeterminateSystems/detsys-ts/tar.gz/2391ba1ef3d22027cd4d9ecce147007a88f63643,
}
name: detsys-ts
version: 1.0.0

15
tsup.config.ts Normal file
View file

@ -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,
},
});