2024-04-11 15:58:56 +00:00
import * as actions _core from "@actions/core" ;
import * as github from "@actions/github" ;
import * as actions _exec 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" ;
2024-04-15 22:09:42 +00:00
import { IdsToolbox , inputs } from "detsys-ts" ;
2024-04-11 15:58:56 +00:00
import { randomUUID } from "node:crypto" ;
class NixInstallerAction {
constructor ( ) {
this . idslib = new IdsToolbox ( {
name : "nix-installer" ,
fetchStyle : "nix-style" ,
legacySourcePrefix : "nix-installer" ,
} ) ;
this . platform = get _nix _platform ( ) ;
2024-04-15 22:09:42 +00:00
this . nix _package _url = inputs . getStringOrNull ( "nix-package-url" ) ;
this . backtrace = inputs . getStringOrNull ( "backtrace" ) ;
this . extra _args = inputs . getStringOrNull ( "extra-args" ) ;
this . extra _conf = inputs . getMultilineStringOrNull ( "extra-conf" ) ;
this . flakehub = inputs . getBool ( "flakehub" ) ;
this . kvm = inputs . getBool ( "kvm" ) ;
this . force _docker _shim = inputs . getBool ( "force-docker-shim" ) ;
this . github _token = inputs . getStringOrNull ( "github-token" ) ;
this . github _server _url = inputs . getStringOrNull ( "github-server-url" ) ;
this . init = inputs . getStringOrNull ( "init" ) ;
this . local _root = inputs . getStringOrNull ( "local-root" ) ;
this . log _directives = inputs . getStringOrNull ( "log-directives" ) ;
this . logger = inputs . getStringOrNull ( "logger" ) ;
this . ssl _cert _file = inputs . getStringOrNull ( "ssl-cert-file" ) ;
this . proxy = inputs . getStringOrNull ( "proxy" ) ;
this . mac _case _sensitive = inputs . getStringOrNull ( "mac-case-sensitive" ) ;
this . mac _encrypt = inputs . getStringOrNull ( "mac-encrypt" ) ;
this . mac _root _disk = inputs . getStringOrNull ( "mac-root-disk" ) ;
this . mac _volume _label = inputs . getStringOrNull ( "mac-volume-label" ) ;
this . modify _profile = inputs . getBool ( "modify-profile" ) ;
this . nix _build _group _id = inputs . getNumberOrNull ( "nix-build-group-id" ) ;
this . nix _build _group _name = inputs . getStringOrNull ( "nix-build-group-name" ) ;
this . nix _build _user _base = inputs . getNumberOrNull ( "nix_build-user-base" ) ;
this . nix _build _user _count = inputs . getNumberOrNull ( "nix-build-user-count" ) ;
this . nix _build _user _prefix = inputs . getStringOrNull ( "nix-build-user-prefix" ) ;
this . planner = inputs . getStringOrNull ( "planner" ) ;
this . reinstall = inputs . getBool ( "reinstall" ) ;
this . start _daemon = inputs . getBool ( "start-daemon" ) ;
this . trust _runner _user = inputs . getBool ( "trust-runner-user" ) ;
2024-04-11 15:58:56 +00:00
}
async detectAndForceDockerShim ( ) {
// 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 ( process . env . RUNNER _OS !== "Linux" ) {
if ( this . force _docker _shim ) {
actions _core . warning ( "Ignoring force-docker-shim which is set to true, as it is only supported on Linux." ) ;
this . force _docker _shim = false ;
}
return ;
}
const systemdCheck = fs . statSync ( "/run/systemd/system" , {
throwIfNoEntry : false ,
} ) ;
if ( systemdCheck ? . isDirectory ( ) ) {
if ( this . force _docker _shim ) {
actions _core . warning ( "Systemd is detected, but ignoring it since force-docker-shim is enabled." ) ;
}
else {
this . idslib . addFact ( "has_systemd" , true ) ;
return ;
}
}
this . idslib . addFact ( "has_systemd" , false ) ;
actions _core . debug ( "Linux detected without systemd, testing for Docker with `docker info` as an alternative daemon supervisor." ) ;
this . idslib . addFact ( "has_docker" , false ) ; // Set to false here, and only in the success case do we set it to true
let exit _code ;
try {
exit _code = await actions _exec . exec ( "docker" , [ "info" ] , {
silent : true ,
listeners : {
stdout : ( data ) => {
const trimmed = data . toString ( "utf-8" ) . trimEnd ( ) ;
if ( trimmed . length >= 0 ) {
actions _core . debug ( trimmed ) ;
}
} ,
stderr : ( data ) => {
const trimmed = data . toString ( "utf-8" ) . trimEnd ( ) ;
if ( trimmed . length >= 0 ) {
actions _core . debug ( trimmed ) ;
}
} ,
} ,
} ) ;
}
catch ( e ) {
actions _core . debug ( "Docker not detected, not enabling docker shim." ) ;
return ;
}
if ( exit _code !== 0 ) {
if ( this . force _docker _shim ) {
actions _core . warning ( "docker info check failed, but trying anyway since force-docker-shim is enabled." ) ;
}
else {
return ;
}
}
this . idslib . addFact ( "has_docker" , true ) ;
if ( ! this . force _docker _shim &&
( await this . detectDockerWithMountedDockerSocket ( ) ) ) {
actions _core . debug ( "Detected a Docker container with a Docker socket mounted, not enabling docker shim." ) ;
return ;
}
actions _core . startGroup ( "Enabling the Docker shim for running Nix on Linux in CI without Systemd." ) ;
if ( this . init !== "none" ) {
actions _core . info ( ` Changing init from ' ${ this . init } ' to 'none' ` ) ;
this . init = "none" ;
}
if ( this . planner !== "linux" ) {
actions _core . info ( ` Changing planner from ' ${ this . planner } ' to 'linux' ` ) ;
this . planner = "linux" ;
}
this . force _docker _shim = true ;
actions _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 cgroups _buffer ;
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::/`
cgroups _buffer = await readFile ( "/proc/self/cgroup" , {
encoding : "utf-8" ,
} ) ;
}
catch ( e ) {
actions _core . debug ( ` Did not detect \` /proc/self/cgroup \` existence, bailing on docker container ID detection: \n ${ e } ` ) ;
return false ;
}
const cgroups = cgroups _buffer . trim ( ) . split ( "\n" ) ;
const last _cgroup = cgroups [ cgroups . length - 1 ] ;
const last _cgroup _parts = last _cgroup . split ( ":" ) ;
const last _cgroup _path = last _cgroup _parts [ last _cgroup _parts . length - 1 ] ;
if ( ! last _cgroup _path . includes ( "/docker/" ) ) {
actions _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 last _cgroup _path _parts = last _cgroup _path . split ( "/" ) ;
const container _id = last _cgroup _path _parts [ last _cgroup _path _parts . length - 1 ] ;
// If we cannot `docker inspect` this discovered container ID, we'll fall through to the `catch` below.
let stdout _buffer = "" ;
let stderr _buffer = "" ;
let exit _code ;
try {
exit _code = await actions _exec . exec ( "docker" , [ "inspect" , container _id ] , {
silent : true ,
listeners : {
stdout : ( data ) => {
stdout _buffer += data . toString ( "utf-8" ) ;
} ,
stderr : ( data ) => {
stderr _buffer += data . toString ( "utf-8" ) ;
} ,
} ,
} ) ;
}
catch ( e ) {
actions _core . debug ( ` Could not execute \` docker inspect ${ container _id } \` , bailing on docker container inspection: \n ${ e } ` ) ;
return false ;
}
if ( exit _code !== 0 ) {
actions _core . debug ( ` Unable to inspect detected docker container with id \` ${ container _id } \` , bailing on container inspection (exit ${ exit _code } ): \n ${ stderr _buffer } ` ) ;
return false ;
}
const output = JSON . parse ( stdout _buffer ) ;
// `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 ) {
actions _core . debug ( ` Got \` docker inspect ${ container _id } \` 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" ) {
actions _core . debug ( ` Got non-object in \` Mounts \` field of \` docker inspect ${ container _id } \` output, bailing on docker.sock detection. ` ) ;
return false ;
}
let found _docker _sock _mount = false ;
for ( const mount of mounts ) {
const destination = mount [ "Destination" ] ;
if ( typeof destination === "string" ) {
if ( destination . endsWith ( "docker.sock" ) ) {
found _docker _sock _mount = true ;
break ;
}
}
}
return found _docker _sock _mount ;
}
async executionEnvironment ( ) {
const execution _env = { } ;
execution _env . NIX _INSTALLER _NO _CONFIRM = "true" ;
execution _env . NIX _INSTALLER _DIAGNOSTIC _ATTRIBUTION = JSON . stringify ( this . idslib . getCorrelationHashes ( ) ) ;
if ( this . backtrace !== null ) {
execution _env . RUST _BACKTRACE = this . backtrace ;
}
if ( this . modify _profile !== null ) {
if ( this . modify _profile ) {
execution _env . NIX _INSTALLER _MODIFY _PROFILE = "true" ;
}
else {
execution _env . NIX _INSTALLER _MODIFY _PROFILE = "false" ;
}
}
if ( this . nix _build _group _id !== null ) {
execution _env . NIX _INSTALLER _NIX _BUILD _GROUP _ID = ` ${ this . nix _build _group _id } ` ;
}
if ( this . nix _build _group _name !== null ) {
execution _env . NIX _INSTALLER _NIX _BUILD _GROUP _NAME =
this . nix _build _group _name ;
}
if ( this . nix _build _user _prefix !== null ) {
execution _env . NIX _INSTALLER _NIX _BUILD _USER _PREFIX =
this . nix _build _user _prefix ;
}
if ( this . nix _build _user _count !== null ) {
execution _env . NIX _INSTALLER _NIX _BUILD _USER _COUNT = ` ${ this . nix _build _user _count } ` ;
}
if ( this . nix _build _user _base !== null ) {
execution _env . NIX _INSTALLER _NIX _BUILD _USER _ID _BASE = ` ${ this . nix _build _user _count } ` ;
}
if ( this . nix _package _url !== null ) {
execution _env . NIX _INSTALLER _NIX _PACKAGE _URL = ` ${ this . nix _package _url } ` ;
}
if ( this . proxy !== null ) {
execution _env . NIX _INSTALLER _PROXY = this . proxy ;
}
if ( this . ssl _cert _file !== null ) {
execution _env . NIX _INSTALLER _SSL _CERT _FILE = this . ssl _cert _file ;
}
execution _env . NIX _INSTALLER _DIAGNOSTIC _ENDPOINT =
this . idslib . getDiagnosticsUrl ( ) ? . toString ( ) || "" ;
// TODO: Error if the user uses these on not-MacOS
if ( this . mac _encrypt !== null ) {
if ( process . env . RUNNER _OS !== "macOS" ) {
throw new Error ( "`mac-encrypt` while `$RUNNER_OS` was not `macOS`" ) ;
}
execution _env . NIX _INSTALLER _ENCRYPT = this . mac _encrypt ;
}
if ( this . mac _case _sensitive !== null ) {
if ( process . env . RUNNER _OS !== "macOS" ) {
throw new Error ( "`mac-case-sensitive` while `$RUNNER_OS` was not `macOS`" ) ;
}
execution _env . NIX _INSTALLER _CASE _SENSITIVE = this . mac _case _sensitive ;
}
if ( this . mac _volume _label !== null ) {
if ( process . env . RUNNER _OS !== "macOS" ) {
throw new Error ( "`mac-volume-label` while `$RUNNER_OS` was not `macOS`" ) ;
}
execution _env . NIX _INSTALLER _VOLUME _LABEL = this . mac _volume _label ;
}
if ( this . mac _root _disk !== null ) {
if ( process . env . RUNNER _OS !== "macOS" ) {
throw new Error ( "`mac-root-disk` while `$RUNNER_OS` was not `macOS`" ) ;
}
execution _env . NIX _INSTALLER _ROOT _DISK = this . mac _root _disk ;
}
if ( this . logger !== null ) {
execution _env . NIX _INSTALLER _LOGGER = this . logger ;
}
if ( this . log _directives !== null ) {
execution _env . NIX _INSTALLER _LOG _DIRECTIVES = this . log _directives ;
}
// TODO: Error if the user uses these on MacOS
if ( this . init !== null ) {
if ( process . env . RUNNER _OS === "macOS" ) {
throw new Error ( "`init` is not a valid option when `$RUNNER_OS` is `macOS`" ) ;
}
execution _env . NIX _INSTALLER _INIT = this . init ;
}
if ( this . start _daemon !== null ) {
if ( this . start _daemon ) {
execution _env . NIX _INSTALLER _START _DAEMON = "true" ;
}
else {
execution _env . NIX _INSTALLER _START _DAEMON = "false" ;
}
}
let extra _conf = "" ;
if ( this . github _server _url !== null && this . github _token !== null ) {
const server _url = this . github _server _url . replace ( "https://" , "" ) ;
extra _conf += ` access-tokens = ${ server _url } = ${ this . github _token } ` ;
extra _conf += "\n" ;
}
if ( this . trust _runner _user !== null ) {
const user = userInfo ( ) . username ;
if ( user ) {
extra _conf += ` trusted-users = root ${ user } ` ;
}
else {
extra _conf += ` trusted-users = root ` ;
}
extra _conf += "\n" ;
}
if ( this . flakehub ) {
extra _conf += ` netrc-file = ${ await this . flakehub _login ( ) } ` ;
extra _conf += "\n" ;
}
if ( this . extra _conf !== null && this . extra _conf . length !== 0 ) {
extra _conf += this . extra _conf . join ( "\n" ) ;
extra _conf += "\n" ;
}
execution _env . NIX _INSTALLER _EXTRA _CONF = extra _conf ;
if ( process . env . ACT && ! process . env . NOT _ACT ) {
this . idslib . addFact ( "in_act" , true ) ;
actions _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`" ) ;
execution _env . NIX _INSTALLER _INIT = "none" ;
}
if ( process . env . NSC _VM _ID && ! process . env . NOT _NAMESPACE ) {
this . idslib . addFact ( "in_namespace_so" , true ) ;
actions _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" ) ;
execution _env . NIX _INSTALLER _INIT = "none" ;
}
return execution _env ;
}
async execute _install ( binary _path ) {
const execution _env = await this . executionEnvironment ( ) ;
actions _core . debug ( ` Execution environment: ${ JSON . stringify ( execution _env , null , 4 ) } ` ) ;
const args = [ "install" ] ;
if ( this . planner ) {
this . idslib . addFact ( "nix_installer_planner" , this . planner ) ;
args . push ( this . planner ) ;
}
else {
this . idslib . addFact ( "nix_installer_planner" , get _default _planner ( ) ) ;
args . push ( get _default _planner ( ) ) ;
}
if ( this . extra _args ) {
const extra _args = stringArgv ( this . extra _args ) ;
args . concat ( extra _args ) ;
}
this . idslib . recordEvent ( "install_nix_start" ) ;
const exit _code = await actions _exec . exec ( binary _path , args , {
env : {
... execution _env ,
... process . env , // To get $PATH, etc
} ,
} ) ;
if ( exit _code !== 0 ) {
this . idslib . recordEvent ( "install_nix_failure" , {
exit _code ,
} ) ;
throw new Error ( ` Non-zero exit code of \` ${ exit _code } \` detected ` ) ;
}
this . idslib . recordEvent ( "install_nix_success" ) ;
return exit _code ;
}
async install ( ) {
const existing _install = await this . detect _existing ( ) ;
if ( existing _install ) {
if ( this . reinstall ) {
// We need to uninstall, then reinstall
actions _core . info ( "Nix was already installed, `reinstall` is set, uninstalling for a reinstall" ) ;
await this . execute _uninstall ( ) ;
}
else {
// We're already installed, and not reinstalling, just set GITHUB_PATH and finish early
await this . set _github _path ( ) ;
actions _core . info ( "Nix was already installed, using existing install" ) ;
return ;
}
}
if ( this . kvm ) {
actions _core . startGroup ( "Configuring KVM" ) ;
if ( await this . setup _kvm ( ) ) {
actions _core . endGroup ( ) ;
actions _core . info ( "\u001b[32m Accelerated KVM is enabled \u001b[33m⚡️ " ) ;
actions _core . exportVariable ( "DETERMINATE_NIX_KVM" , "1" ) ;
}
else {
actions _core . endGroup ( ) ;
actions _core . info ( "KVM is not available." ) ;
actions _core . exportVariable ( "DETERMINATE_NIX_KVM" , "0" ) ;
}
}
// Normal just doing of the install
actions _core . startGroup ( "Installing Nix" ) ;
const binary _path = await this . fetch _binary ( ) ;
await this . execute _install ( binary _path ) ;
actions _core . endGroup ( ) ;
if ( this . force _docker _shim ) {
await this . spawnDockerShim ( ) ;
}
await this . set _github _path ( ) ;
}
async spawnDockerShim ( ) {
actions _core . 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" ) ,
} ;
let arch ;
if ( process . env . RUNNER _ARCH === "X64" ) {
arch = "X64" ;
}
else if ( process . env . RUNNER _ARCH === "ARM64" ) {
arch = "ARM64" ;
}
else {
throw Error ( "Architecture not supported in Docker shim mode." ) ;
}
actions _core . debug ( "Loading image: determinate-nix-shim:latest..." ) ;
{
const exit _code = await actions _exec . exec ( "docker" , [ "image" , "load" , "--input" , images [ arch ] ] , {
silent : true ,
listeners : {
stdout : ( data ) => {
const trimmed = data . toString ( "utf-8" ) . trimEnd ( ) ;
if ( trimmed . length >= 0 ) {
actions _core . debug ( trimmed ) ;
}
} ,
stderr : ( data ) => {
const trimmed = data . toString ( "utf-8" ) . trimEnd ( ) ;
if ( trimmed . length >= 0 ) {
actions _core . debug ( trimmed ) ;
}
} ,
} ,
} ) ;
if ( exit _code !== 0 ) {
throw new Error ( ` Failed to build the shim image, exit code: \` ${ exit _code } \` ` ) ;
}
}
{
actions _core . debug ( "Starting the Nix daemon through Docker..." ) ;
this . idslib . recordEvent ( "start_docker_shim" ) ;
const exit _code = await actions _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 ( ) } - ${ randomUUID ( ) } ` ,
"determinate-nix-shim:latest" ,
] , {
silent : true ,
listeners : {
stdline : ( data ) => {
actions _core . saveState ( "docker_shim_container_id" , data . trimEnd ( ) ) ;
} ,
stdout : ( data ) => {
const trimmed = data . toString ( "utf-8" ) . trimEnd ( ) ;
if ( trimmed . length >= 0 ) {
actions _core . debug ( trimmed ) ;
}
} ,
stderr : ( data ) => {
const trimmed = data . toString ( "utf-8" ) . trimEnd ( ) ;
if ( trimmed . length >= 0 ) {
actions _core . debug ( trimmed ) ;
}
} ,
} ,
} ) ;
if ( exit _code !== 0 ) {
throw new Error ( ` Failed to start the Nix daemon through Docker, exit code: \` ${ exit _code } \` ` ) ;
}
}
actions _core . endGroup ( ) ;
return ;
}
async cleanupDockerShim ( ) {
const container _id = actions _core . getState ( "docker_shim_container_id" ) ;
if ( container _id !== "" ) {
actions _core . startGroup ( "Cleaning up the Nix daemon's Docker shim" ) ;
let cleaned = false ;
try {
await actions _exec . exec ( "docker" , [ "rm" , "--force" , container _id ] ) ;
cleaned = true ;
}
catch {
actions _core . warning ( "failed to cleanup nix daemon container" ) ;
}
if ( ! cleaned ) {
actions _core . info ( "trying to pkill the container's shim process" ) ;
try {
await actions _exec . exec ( "pkill" , [ container _id ] ) ;
cleaned = true ;
}
catch {
actions _core . warning ( "failed to forcibly kill the container's shim process" ) ;
}
}
if ( cleaned ) {
this . idslib . recordEvent ( "clean_up_docker_shim" ) ;
}
else {
actions _core . warning ( "Giving up on cleaning up the nix daemon container" ) ;
}
actions _core . endGroup ( ) ;
}
}
async set _github _path ( ) {
// Interim versions of the `nix-installer` crate may have already manipulated `$GITHUB_PATH`, as root even! Accessing that will be an error.
try {
const nix _var _nix _profile _path = "/nix/var/nix/profiles/default/bin" ;
const home _nix _profile _path = ` ${ process . env . HOME } /.nix-profile/bin ` ;
actions _core . addPath ( nix _var _nix _profile _path ) ;
actions _core . addPath ( home _nix _profile _path ) ;
actions _core . info ( ` Added \` ${ nix _var _nix _profile _path } \` and \` ${ home _nix _profile _path } \` to \` $ GITHUB_PATH \` ` ) ;
}
catch ( error ) {
actions _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 flakehub _login ( ) {
this . idslib . recordEvent ( "login_to_flakehub" ) ;
const netrc _path = ` ${ process . env [ "RUNNER_TEMP" ] } /determinate-nix-installer-netrc ` ;
const jwt = await actions _core . getIDToken ( "api.flakehub.com" ) ;
await writeFile ( netrc _path , [
` machine api.flakehub.com login flakehub password ${ jwt } ` ,
` machine flakehub.com login flakehub password ${ jwt } ` ,
] . join ( "\n" ) ) ;
actions _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 . extra _conf ? . join ( "\n" ) . match ( /^netrc-file/m ) ) {
actions _core . warning ( "Logging in to FlakeHub conflicts with the Nix option `netrc-file`." ) ;
}
return netrc _path ;
}
async execute _uninstall ( ) {
this . idslib . recordEvent ( "uninstall" ) ;
const exit _code = await actions _exec . exec ( ` /nix/nix-installer ` , [ "uninstall" ] , {
env : {
NIX _INSTALLER _NO _CONFIRM : "true" ,
... process . env , // To get $PATH, etc
} ,
} ) ;
if ( exit _code !== 0 ) {
throw new Error ( ` Non-zero exit code of \` ${ exit _code } \` detected ` ) ;
}
return exit _code ;
}
async detect _existing ( ) {
const receipt _path = "/nix/receipt.json" ;
try {
await access ( receipt _path ) ;
// There is a /nix/receipt.json
return true ;
}
catch {
// No /nix/receipt.json
return false ;
}
}
async setup _kvm ( ) {
this . idslib . recordEvent ( "setup_kvm" ) ;
const current _user = userInfo ( ) ;
const is _root = current _user . uid === 0 ;
const maybe _sudo = is _root ? "" : "sudo" ;
const kvm _rules = "/etc/udev/rules.d/99-determinate-nix-installer-kvm.rules" ;
try {
const write _file _exit _code = await actions _exec . exec ( "sh" , [
"-c" ,
` echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | ${ maybe _sudo } tee ${ kvm _rules } > /dev/null ` ,
] , {
silent : true ,
listeners : {
stdout : ( data ) => {
const trimmed = data . toString ( "utf-8" ) . trimEnd ( ) ;
if ( trimmed . length >= 0 ) {
actions _core . debug ( trimmed ) ;
}
} ,
stderr : ( data ) => {
const trimmed = data . toString ( "utf-8" ) . trimEnd ( ) ;
if ( trimmed . length >= 0 ) {
actions _core . debug ( trimmed ) ;
}
} ,
} ,
} ) ;
if ( write _file _exit _code !== 0 ) {
throw new Error ( ` Non-zero exit code of \` ${ write _file _exit _code } \` detected while writing ' ${ kvm _rules } ' ` ) ;
}
const debug _root _run _throw = async ( action , command , args ) => {
if ( ! is _root ) {
args = [ command , ... args ] ;
command = "sudo" ;
}
const reload _exit _code = await actions _exec . exec ( command , args , {
silent : true ,
listeners : {
stdout : ( data ) => {
const trimmed = data . toString ( "utf-8" ) . trimEnd ( ) ;
if ( trimmed . length >= 0 ) {
actions _core . debug ( trimmed ) ;
}
} ,
stderr : ( data ) => {
const trimmed = data . toString ( "utf-8" ) . trimEnd ( ) ;
if ( trimmed . length >= 0 ) {
actions _core . debug ( trimmed ) ;
}
} ,
} ,
} ) ;
if ( reload _exit _code !== 0 ) {
throw new Error ( ` Non-zero exit code of \` ${ reload _exit _code } \` detected while ${ action } . ` ) ;
}
} ;
await debug _root _run _throw ( "reloading udev rules" , "udevadm" , [
"control" ,
"--reload-rules" ,
] ) ;
await debug _root _run _throw ( "triggering udev against kvm" , "udevadm" , [
"trigger" ,
"--name-match=kvm" ,
] ) ;
return true ;
}
catch ( error ) {
if ( is _root ) {
await actions _exec . exec ( "rm" , [ "-f" , kvm _rules ] ) ;
}
else {
await actions _exec . exec ( "sudo" , [ "rm" , "-f" , kvm _rules ] ) ;
}
return false ;
}
}
async fetch _binary ( ) {
if ( ! this . local _root ) {
return await this . idslib . fetchExecutable ( ) ;
}
else {
const local _path = join ( this . local _root , ` nix-installer- ${ this . platform } ` ) ;
actions _core . info ( ` Using binary ${ local _path } ` ) ;
return local _path ;
}
}
async report _overall ( ) {
try {
this . idslib . recordEvent ( "conclude_workflow" , {
conclusion : await this . get _workflow _conclusion ( ) ,
} ) ;
}
catch ( error ) {
actions _core . debug ( ` Error submitting post-run diagnostics report: ${ error } ` ) ;
}
}
async get _workflow _conclusion ( ) {
if ( this . github _token == null ) {
return undefined ;
}
try {
const octokit = github . getOctokit ( this . github _token ) ;
const jobs = await octokit . paginate ( octokit . rest . actions . listJobsForWorkflowRun , {
owner : github . context . repo . owner ,
repo : github . context . repo . repo ,
run _id : github . context . runId ,
} ) ;
actions _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 ( error ) {
actions _core . debug ( ` Error determining final disposition: ${ error } ` ) ;
return "unavailable" ;
}
}
}
function get _nix _platform ( ) {
const env _os = process . env . RUNNER _OS ;
const env _arch = process . env . RUNNER _ARCH ;
if ( env _os === "macOS" && env _arch === "X64" ) {
return "x86_64-darwin" ;
}
else if ( env _os === "macOS" && env _arch === "ARM64" ) {
return "aarch64-darwin" ;
}
else if ( env _os === "Linux" && env _arch === "X64" ) {
return "x86_64-linux" ;
}
else if ( env _os === "Linux" && env _arch === "ARM64" ) {
return "aarch64-linux" ;
}
else {
throw new Error ( ` Unsupported \` RUNNER_OS \` (currently \` ${ env _os } \` ) and \` RUNNER_ARCH \` (currently \` ${ env _arch } \` ) combination ` ) ;
}
}
function get _default _planner ( ) {
const env _os = process . env . RUNNER _OS ;
if ( env _os === "macOS" ) {
return "macos" ;
}
else if ( env _os === "Linux" ) {
return "linux" ;
}
else {
throw new Error ( ` Unsupported \` RUNNER_OS \` (currently \` ${ env _os } \` ) ` ) ;
}
}
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 . report _overall ( ) ;
} ) ;
installer . idslib . execute ( ) ;
}
main ( ) ;