mirror of
https://github.com/DeterminateSystems/nix-installer-action.git
synced 2024-12-23 05:22:21 +01:00
Support GitHub Enterprise Server using ARC (#59)
* Test nix-installer-action on Namespace.so It is special in that it doesn't have systemd, and it'd be great to support Namespace.so. It is also a good test case for a variety of self-hosted GHA runner use cases. * Make correlation more confident * Borrow docker as a process supervisor on Linux GHA runners without systemd This change introduces a Docker container shim which spawns the Nix daemon after bind mounting all the relevant paths into the container. The image is actually completely empty, other than metadata about what to run. This is a cheap and cheerful way to get decent process supervision in environments that don't bring systemd, but do have docker ... which is most everywhere in the GHA ecosystem. * Ignore generated files * Run on arm64 why not * Load a pre-built image, don't build * Check the userInfo.username instead of an env var * Stop double-printing output to the console * can't rm and restart * what * Clean up the container at the end * Emit the fetch line in the 'installing nix' section * tweak output * delete what
This commit is contained in:
parent
84fe9e450f
commit
cd46bde16a
15 changed files with 525 additions and 558 deletions
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
dist/* linguist-generated=true
|
||||
|
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
|
@ -18,7 +18,13 @@ jobs:
|
|||
- run: test $(git status --porcelain=v1 2>/dev/null | wc -l) -eq 0
|
||||
run-x86_64-linux:
|
||||
name: Run x86_64 Linux
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
matrix:
|
||||
runner:
|
||||
- ubuntu-latest
|
||||
- nscloud-ubuntu-22.04-amd64-4x16
|
||||
- namespace-profile-default-arm64
|
||||
runs-on: ${{ matrix.runner }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Nix
|
||||
|
|
|
@ -91,6 +91,7 @@ Differing from the upstream [Nix](https://github.com/NixOS/nix) installer script
|
|||
| `extra-args` | Extra arguments to pass to the planner (prefer using structured `with:` arguments unless using a custom [planner]!) | string | |
|
||||
| `extra-conf` | Extra configuration lines for `/etc/nix/nix.conf` (includes `access-tokens` with `secrets.GITHUB_TOKEN` automatically if `github-token` is set) | string | |
|
||||
| `flakehub` | Log in to FlakeHub to pull private flakes using the GitHub Actions [JSON Web Token](https://jwt.io) (JWT), which is bound to the `api.flakehub.com` audience. | Boolean | `false` |
|
||||
| `force-docker-shim` | Force the use of Docker as a process supervisor. This setting is automatically enabled when necessary. | Boolean | `false` |
|
||||
| `github-token` | A [GitHub token] for making authenticated requests (which have a higher rate-limit quota than unauthenticated requests) | string | `${{ github.token }}` |
|
||||
| `github-server-url` | The URL for the GitHub server, to use with the `github-token` token. Defaults to the current GitHub server, supporting GitHub Enterprise Server automatically. Only change this value if the provided `github-token` is for a different GitHub server than the current server. | string | `${{ github.server }}` |
|
||||
| `init` | The init system to configure (requires `planner: linux-multi`) | enum (`none` or `systemd`) | |
|
||||
|
|
|
@ -17,6 +17,9 @@ inputs:
|
|||
description: Automatically log in to your [FlakeHub](https://flakehub.com) account, for accessing private flakes.
|
||||
required: false
|
||||
default: false
|
||||
force-docker-shim:
|
||||
description: Force the use of Docker as a process supervisor. This setting is automatically enabled when necessary.
|
||||
default: false
|
||||
github-token:
|
||||
description: A GitHub token for making authenticated requests (which have a higher rate-limit quota than unauthenticated requests)
|
||||
default: ${{ github.token }}
|
||||
|
|
453
dist/37.index.js
generated
vendored
453
dist/37.index.js
generated
vendored
|
@ -1,453 +0,0 @@
|
|||
"use strict";
|
||||
exports.id = 37;
|
||||
exports.ids = [37];
|
||||
exports.modules = {
|
||||
|
||||
/***/ 4037:
|
||||
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
|
||||
|
||||
__webpack_require__.r(__webpack_exports__);
|
||||
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
||||
/* harmony export */ "toFormData": () => (/* binding */ toFormData)
|
||||
/* harmony export */ });
|
||||
/* harmony import */ var fetch_blob_from_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2185);
|
||||
/* harmony import */ var formdata_polyfill_esm_min_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(8010);
|
||||
|
||||
|
||||
|
||||
let s = 0;
|
||||
const S = {
|
||||
START_BOUNDARY: s++,
|
||||
HEADER_FIELD_START: s++,
|
||||
HEADER_FIELD: s++,
|
||||
HEADER_VALUE_START: s++,
|
||||
HEADER_VALUE: s++,
|
||||
HEADER_VALUE_ALMOST_DONE: s++,
|
||||
HEADERS_ALMOST_DONE: s++,
|
||||
PART_DATA_START: s++,
|
||||
PART_DATA: s++,
|
||||
END: s++
|
||||
};
|
||||
|
||||
let f = 1;
|
||||
const F = {
|
||||
PART_BOUNDARY: f,
|
||||
LAST_BOUNDARY: f *= 2
|
||||
};
|
||||
|
||||
const LF = 10;
|
||||
const CR = 13;
|
||||
const SPACE = 32;
|
||||
const HYPHEN = 45;
|
||||
const COLON = 58;
|
||||
const A = 97;
|
||||
const Z = 122;
|
||||
|
||||
const lower = c => c | 0x20;
|
||||
|
||||
const noop = () => {};
|
||||
|
||||
class MultipartParser {
|
||||
/**
|
||||
* @param {string} boundary
|
||||
*/
|
||||
constructor(boundary) {
|
||||
this.index = 0;
|
||||
this.flags = 0;
|
||||
|
||||
this.onHeaderEnd = noop;
|
||||
this.onHeaderField = noop;
|
||||
this.onHeadersEnd = noop;
|
||||
this.onHeaderValue = noop;
|
||||
this.onPartBegin = noop;
|
||||
this.onPartData = noop;
|
||||
this.onPartEnd = noop;
|
||||
|
||||
this.boundaryChars = {};
|
||||
|
||||
boundary = '\r\n--' + boundary;
|
||||
const ui8a = new Uint8Array(boundary.length);
|
||||
for (let i = 0; i < boundary.length; i++) {
|
||||
ui8a[i] = boundary.charCodeAt(i);
|
||||
this.boundaryChars[ui8a[i]] = true;
|
||||
}
|
||||
|
||||
this.boundary = ui8a;
|
||||
this.lookbehind = new Uint8Array(this.boundary.length + 8);
|
||||
this.state = S.START_BOUNDARY;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Uint8Array} data
|
||||
*/
|
||||
write(data) {
|
||||
let i = 0;
|
||||
const length_ = data.length;
|
||||
let previousIndex = this.index;
|
||||
let {lookbehind, boundary, boundaryChars, index, state, flags} = this;
|
||||
const boundaryLength = this.boundary.length;
|
||||
const boundaryEnd = boundaryLength - 1;
|
||||
const bufferLength = data.length;
|
||||
let c;
|
||||
let cl;
|
||||
|
||||
const mark = name => {
|
||||
this[name + 'Mark'] = i;
|
||||
};
|
||||
|
||||
const clear = name => {
|
||||
delete this[name + 'Mark'];
|
||||
};
|
||||
|
||||
const callback = (callbackSymbol, start, end, ui8a) => {
|
||||
if (start === undefined || start !== end) {
|
||||
this[callbackSymbol](ui8a && ui8a.subarray(start, end));
|
||||
}
|
||||
};
|
||||
|
||||
const dataCallback = (name, clear) => {
|
||||
const markSymbol = name + 'Mark';
|
||||
if (!(markSymbol in this)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (clear) {
|
||||
callback(name, this[markSymbol], i, data);
|
||||
delete this[markSymbol];
|
||||
} else {
|
||||
callback(name, this[markSymbol], data.length, data);
|
||||
this[markSymbol] = 0;
|
||||
}
|
||||
};
|
||||
|
||||
for (i = 0; i < length_; i++) {
|
||||
c = data[i];
|
||||
|
||||
switch (state) {
|
||||
case S.START_BOUNDARY:
|
||||
if (index === boundary.length - 2) {
|
||||
if (c === HYPHEN) {
|
||||
flags |= F.LAST_BOUNDARY;
|
||||
} else if (c !== CR) {
|
||||
return;
|
||||
}
|
||||
|
||||
index++;
|
||||
break;
|
||||
} else if (index - 1 === boundary.length - 2) {
|
||||
if (flags & F.LAST_BOUNDARY && c === HYPHEN) {
|
||||
state = S.END;
|
||||
flags = 0;
|
||||
} else if (!(flags & F.LAST_BOUNDARY) && c === LF) {
|
||||
index = 0;
|
||||
callback('onPartBegin');
|
||||
state = S.HEADER_FIELD_START;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (c !== boundary[index + 2]) {
|
||||
index = -2;
|
||||
}
|
||||
|
||||
if (c === boundary[index + 2]) {
|
||||
index++;
|
||||
}
|
||||
|
||||
break;
|
||||
case S.HEADER_FIELD_START:
|
||||
state = S.HEADER_FIELD;
|
||||
mark('onHeaderField');
|
||||
index = 0;
|
||||
// falls through
|
||||
case S.HEADER_FIELD:
|
||||
if (c === CR) {
|
||||
clear('onHeaderField');
|
||||
state = S.HEADERS_ALMOST_DONE;
|
||||
break;
|
||||
}
|
||||
|
||||
index++;
|
||||
if (c === HYPHEN) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (c === COLON) {
|
||||
if (index === 1) {
|
||||
// empty header field
|
||||
return;
|
||||
}
|
||||
|
||||
dataCallback('onHeaderField', true);
|
||||
state = S.HEADER_VALUE_START;
|
||||
break;
|
||||
}
|
||||
|
||||
cl = lower(c);
|
||||
if (cl < A || cl > Z) {
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
case S.HEADER_VALUE_START:
|
||||
if (c === SPACE) {
|
||||
break;
|
||||
}
|
||||
|
||||
mark('onHeaderValue');
|
||||
state = S.HEADER_VALUE;
|
||||
// falls through
|
||||
case S.HEADER_VALUE:
|
||||
if (c === CR) {
|
||||
dataCallback('onHeaderValue', true);
|
||||
callback('onHeaderEnd');
|
||||
state = S.HEADER_VALUE_ALMOST_DONE;
|
||||
}
|
||||
|
||||
break;
|
||||
case S.HEADER_VALUE_ALMOST_DONE:
|
||||
if (c !== LF) {
|
||||
return;
|
||||
}
|
||||
|
||||
state = S.HEADER_FIELD_START;
|
||||
break;
|
||||
case S.HEADERS_ALMOST_DONE:
|
||||
if (c !== LF) {
|
||||
return;
|
||||
}
|
||||
|
||||
callback('onHeadersEnd');
|
||||
state = S.PART_DATA_START;
|
||||
break;
|
||||
case S.PART_DATA_START:
|
||||
state = S.PART_DATA;
|
||||
mark('onPartData');
|
||||
// falls through
|
||||
case S.PART_DATA:
|
||||
previousIndex = index;
|
||||
|
||||
if (index === 0) {
|
||||
// boyer-moore derrived algorithm to safely skip non-boundary data
|
||||
i += boundaryEnd;
|
||||
while (i < bufferLength && !(data[i] in boundaryChars)) {
|
||||
i += boundaryLength;
|
||||
}
|
||||
|
||||
i -= boundaryEnd;
|
||||
c = data[i];
|
||||
}
|
||||
|
||||
if (index < boundary.length) {
|
||||
if (boundary[index] === c) {
|
||||
if (index === 0) {
|
||||
dataCallback('onPartData', true);
|
||||
}
|
||||
|
||||
index++;
|
||||
} else {
|
||||
index = 0;
|
||||
}
|
||||
} else if (index === boundary.length) {
|
||||
index++;
|
||||
if (c === CR) {
|
||||
// CR = part boundary
|
||||
flags |= F.PART_BOUNDARY;
|
||||
} else if (c === HYPHEN) {
|
||||
// HYPHEN = end boundary
|
||||
flags |= F.LAST_BOUNDARY;
|
||||
} else {
|
||||
index = 0;
|
||||
}
|
||||
} else if (index - 1 === boundary.length) {
|
||||
if (flags & F.PART_BOUNDARY) {
|
||||
index = 0;
|
||||
if (c === LF) {
|
||||
// unset the PART_BOUNDARY flag
|
||||
flags &= ~F.PART_BOUNDARY;
|
||||
callback('onPartEnd');
|
||||
callback('onPartBegin');
|
||||
state = S.HEADER_FIELD_START;
|
||||
break;
|
||||
}
|
||||
} else if (flags & F.LAST_BOUNDARY) {
|
||||
if (c === HYPHEN) {
|
||||
callback('onPartEnd');
|
||||
state = S.END;
|
||||
flags = 0;
|
||||
} else {
|
||||
index = 0;
|
||||
}
|
||||
} else {
|
||||
index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (index > 0) {
|
||||
// when matching a possible boundary, keep a lookbehind reference
|
||||
// in case it turns out to be a false lead
|
||||
lookbehind[index - 1] = c;
|
||||
} else if (previousIndex > 0) {
|
||||
// if our boundary turned out to be rubbish, the captured lookbehind
|
||||
// belongs to partData
|
||||
const _lookbehind = new Uint8Array(lookbehind.buffer, lookbehind.byteOffset, lookbehind.byteLength);
|
||||
callback('onPartData', 0, previousIndex, _lookbehind);
|
||||
previousIndex = 0;
|
||||
mark('onPartData');
|
||||
|
||||
// reconsider the current character even so it interrupted the sequence
|
||||
// it could be the beginning of a new sequence
|
||||
i--;
|
||||
}
|
||||
|
||||
break;
|
||||
case S.END:
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unexpected state entered: ${state}`);
|
||||
}
|
||||
}
|
||||
|
||||
dataCallback('onHeaderField');
|
||||
dataCallback('onHeaderValue');
|
||||
dataCallback('onPartData');
|
||||
|
||||
// Update properties for the next call
|
||||
this.index = index;
|
||||
this.state = state;
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
end() {
|
||||
if ((this.state === S.HEADER_FIELD_START && this.index === 0) ||
|
||||
(this.state === S.PART_DATA && this.index === this.boundary.length)) {
|
||||
this.onPartEnd();
|
||||
} else if (this.state !== S.END) {
|
||||
throw new Error('MultipartParser.end(): stream ended unexpectedly');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _fileName(headerValue) {
|
||||
// matches either a quoted-string or a token (RFC 2616 section 19.5.1)
|
||||
const m = headerValue.match(/\bfilename=("(.*?)"|([^()<>@,;:\\"/[\]?={}\s\t]+))($|;\s)/i);
|
||||
if (!m) {
|
||||
return;
|
||||
}
|
||||
|
||||
const match = m[2] || m[3] || '';
|
||||
let filename = match.slice(match.lastIndexOf('\\') + 1);
|
||||
filename = filename.replace(/%22/g, '"');
|
||||
filename = filename.replace(/&#(\d{4});/g, (m, code) => {
|
||||
return String.fromCharCode(code);
|
||||
});
|
||||
return filename;
|
||||
}
|
||||
|
||||
async function toFormData(Body, ct) {
|
||||
if (!/multipart/i.test(ct)) {
|
||||
throw new TypeError('Failed to fetch');
|
||||
}
|
||||
|
||||
const m = ct.match(/boundary=(?:"([^"]+)"|([^;]+))/i);
|
||||
|
||||
if (!m) {
|
||||
throw new TypeError('no or bad content-type header, no multipart boundary');
|
||||
}
|
||||
|
||||
const parser = new MultipartParser(m[1] || m[2]);
|
||||
|
||||
let headerField;
|
||||
let headerValue;
|
||||
let entryValue;
|
||||
let entryName;
|
||||
let contentType;
|
||||
let filename;
|
||||
const entryChunks = [];
|
||||
const formData = new formdata_polyfill_esm_min_js__WEBPACK_IMPORTED_MODULE_1__/* .FormData */ .Ct();
|
||||
|
||||
const onPartData = ui8a => {
|
||||
entryValue += decoder.decode(ui8a, {stream: true});
|
||||
};
|
||||
|
||||
const appendToFile = ui8a => {
|
||||
entryChunks.push(ui8a);
|
||||
};
|
||||
|
||||
const appendFileToFormData = () => {
|
||||
const file = new fetch_blob_from_js__WEBPACK_IMPORTED_MODULE_0__/* .File */ .$B(entryChunks, filename, {type: contentType});
|
||||
formData.append(entryName, file);
|
||||
};
|
||||
|
||||
const appendEntryToFormData = () => {
|
||||
formData.append(entryName, entryValue);
|
||||
};
|
||||
|
||||
const decoder = new TextDecoder('utf-8');
|
||||
decoder.decode();
|
||||
|
||||
parser.onPartBegin = function () {
|
||||
parser.onPartData = onPartData;
|
||||
parser.onPartEnd = appendEntryToFormData;
|
||||
|
||||
headerField = '';
|
||||
headerValue = '';
|
||||
entryValue = '';
|
||||
entryName = '';
|
||||
contentType = '';
|
||||
filename = null;
|
||||
entryChunks.length = 0;
|
||||
};
|
||||
|
||||
parser.onHeaderField = function (ui8a) {
|
||||
headerField += decoder.decode(ui8a, {stream: true});
|
||||
};
|
||||
|
||||
parser.onHeaderValue = function (ui8a) {
|
||||
headerValue += decoder.decode(ui8a, {stream: true});
|
||||
};
|
||||
|
||||
parser.onHeaderEnd = function () {
|
||||
headerValue += decoder.decode();
|
||||
headerField = headerField.toLowerCase();
|
||||
|
||||
if (headerField === 'content-disposition') {
|
||||
// matches either a quoted-string or a token (RFC 2616 section 19.5.1)
|
||||
const m = headerValue.match(/\bname=("([^"]*)"|([^()<>@,;:\\"/[\]?={}\s\t]+))/i);
|
||||
|
||||
if (m) {
|
||||
entryName = m[2] || m[3] || '';
|
||||
}
|
||||
|
||||
filename = _fileName(headerValue);
|
||||
|
||||
if (filename) {
|
||||
parser.onPartData = appendToFile;
|
||||
parser.onPartEnd = appendFileToFormData;
|
||||
}
|
||||
} else if (headerField === 'content-type') {
|
||||
contentType = headerValue;
|
||||
}
|
||||
|
||||
headerValue = '';
|
||||
headerField = '';
|
||||
};
|
||||
|
||||
for await (const chunk of Body) {
|
||||
parser.write(chunk);
|
||||
}
|
||||
|
||||
parser.end();
|
||||
|
||||
return formData;
|
||||
}
|
||||
|
||||
|
||||
/***/ })
|
||||
|
||||
};
|
||||
;
|
||||
//# sourceMappingURL=37.index.js.map
|
1
dist/37.index.js.map
generated
vendored
1
dist/37.index.js.map
generated
vendored
File diff suppressed because one or more lines are too long
BIN
dist/amd64.tar.gz
generated
vendored
Normal file
BIN
dist/amd64.tar.gz
generated
vendored
Normal file
Binary file not shown.
BIN
dist/arm64.tar.gz
generated
vendored
Normal file
BIN
dist/arm64.tar.gz
generated
vendored
Normal file
Binary file not shown.
256
dist/index.js
generated
vendored
256
dist/index.js
generated
vendored
|
@ -23,7 +23,13 @@ __nccwpck_require__.r(__webpack_exports__);
|
|||
/* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_6___default = /*#__PURE__*/__nccwpck_require__.n(node_path__WEBPACK_IMPORTED_MODULE_6__);
|
||||
/* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_7__ = __nccwpck_require__(7561);
|
||||
/* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_7___default = /*#__PURE__*/__nccwpck_require__.n(node_fs__WEBPACK_IMPORTED_MODULE_7__);
|
||||
/* harmony import */ var string_argv__WEBPACK_IMPORTED_MODULE_8__ = __nccwpck_require__(1810);
|
||||
/* harmony import */ var node_os__WEBPACK_IMPORTED_MODULE_8__ = __nccwpck_require__(612);
|
||||
/* harmony import */ var node_os__WEBPACK_IMPORTED_MODULE_8___default = /*#__PURE__*/__nccwpck_require__.n(node_os__WEBPACK_IMPORTED_MODULE_8__);
|
||||
/* harmony import */ var string_argv__WEBPACK_IMPORTED_MODULE_10__ = __nccwpck_require__(1810);
|
||||
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_9__ = __nccwpck_require__(1017);
|
||||
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_9___default = /*#__PURE__*/__nccwpck_require__.n(path__WEBPACK_IMPORTED_MODULE_9__);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -34,7 +40,7 @@ __nccwpck_require__.r(__webpack_exports__);
|
|||
|
||||
|
||||
class NixInstallerAction {
|
||||
constructor() {
|
||||
constructor(correlation) {
|
||||
this.platform = get_nix_platform();
|
||||
this.nix_package_url = action_input_string_or_null("nix-package-url");
|
||||
this.backtrace = action_input_string_or_null("backtrace");
|
||||
|
@ -42,6 +48,7 @@ class NixInstallerAction {
|
|||
this.extra_conf = action_input_multiline_string_or_null("extra-conf");
|
||||
this.flakehub = action_input_bool("flakehub");
|
||||
this.kvm = action_input_bool("kvm");
|
||||
this.force_docker_shim = action_input_bool("force-docker-shim");
|
||||
this.github_token = action_input_string_or_null("github-token");
|
||||
this.github_server_url = action_input_string_or_null("github-server-url");
|
||||
this.init = action_input_string_or_null("init");
|
||||
|
@ -65,9 +72,69 @@ class NixInstallerAction {
|
|||
this.start_daemon = action_input_bool("start-daemon");
|
||||
this.diagnostic_endpoint = action_input_string_or_null("diagnostic-endpoint");
|
||||
this.trust_runner_user = action_input_bool("trust-runner-user");
|
||||
this.correlation = process.env["STATE_correlation"];
|
||||
this.correlation = correlation;
|
||||
this.nix_installer_url = resolve_nix_installer_url(this.platform, this.correlation);
|
||||
}
|
||||
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__WEBPACK_IMPORTED_MODULE_0__.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 = node_fs__WEBPACK_IMPORTED_MODULE_7___default().statSync("/run/systemd/system", {
|
||||
throwIfNoEntry: false,
|
||||
});
|
||||
if (systemdCheck === null || systemdCheck === void 0 ? void 0 : systemdCheck.isDirectory()) {
|
||||
if (this.force_docker_shim) {
|
||||
_actions_core__WEBPACK_IMPORTED_MODULE_0__.warning("Systemd is detected, but ignoring it since force-docker-shim is enabled.");
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
_actions_core__WEBPACK_IMPORTED_MODULE_0__.debug("Linux detected without systemd, testing for Docker with `docker info` as an alternative daemon supervisor.");
|
||||
const exit_code = await _actions_exec__WEBPACK_IMPORTED_MODULE_3__.exec("docker", ["info"], {
|
||||
silent: true,
|
||||
listeners: {
|
||||
stdout: (data) => {
|
||||
const trimmed = data.toString("utf-8").trimEnd();
|
||||
if (trimmed.length >= 0) {
|
||||
_actions_core__WEBPACK_IMPORTED_MODULE_0__.debug(trimmed);
|
||||
}
|
||||
},
|
||||
stderr: (data) => {
|
||||
const trimmed = data.toString("utf-8").trimEnd();
|
||||
if (trimmed.length >= 0) {
|
||||
_actions_core__WEBPACK_IMPORTED_MODULE_0__.debug(trimmed);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
if (exit_code !== 0) {
|
||||
if (this.force_docker_shim) {
|
||||
_actions_core__WEBPACK_IMPORTED_MODULE_0__.warning("docker info check failed, but trying anyway since force-docker-shim is enabled.");
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
_actions_core__WEBPACK_IMPORTED_MODULE_0__.startGroup("Enabling the Docker shim for running Nix on Linux in CI without Systemd.");
|
||||
if (this.init !== "none") {
|
||||
_actions_core__WEBPACK_IMPORTED_MODULE_0__.info(`Changing init from '${this.init}' to 'none'`);
|
||||
this.init = "none";
|
||||
}
|
||||
if (this.planner !== "linux") {
|
||||
_actions_core__WEBPACK_IMPORTED_MODULE_0__.info(`Changing planner from '${this.planner}' to 'linux'`);
|
||||
this.planner = "linux";
|
||||
}
|
||||
this.force_docker_shim = true;
|
||||
_actions_core__WEBPACK_IMPORTED_MODULE_0__.endGroup();
|
||||
}
|
||||
async executionEnvironment() {
|
||||
const execution_env = {};
|
||||
execution_env.NIX_INSTALLER_NO_CONFIRM = "true";
|
||||
|
@ -166,7 +233,13 @@ class NixInstallerAction {
|
|||
extra_conf += "\n";
|
||||
}
|
||||
if (this.trust_runner_user !== null) {
|
||||
extra_conf += `trusted-users = root ${process.env.USER}`;
|
||||
const user = (0,node_os__WEBPACK_IMPORTED_MODULE_8__.userInfo)().username;
|
||||
if (user) {
|
||||
extra_conf += `trusted-users = root ${user}`;
|
||||
}
|
||||
else {
|
||||
extra_conf += `trusted-users = root`;
|
||||
}
|
||||
extra_conf += "\n";
|
||||
}
|
||||
if (this.flakehub) {
|
||||
|
@ -190,7 +263,7 @@ class NixInstallerAction {
|
|||
}
|
||||
async execute_install(binary_path) {
|
||||
const execution_env = await this.executionEnvironment();
|
||||
_actions_core__WEBPACK_IMPORTED_MODULE_0__.info(`Execution environment: ${JSON.stringify(execution_env, null, 4)}`);
|
||||
_actions_core__WEBPACK_IMPORTED_MODULE_0__.debug(`Execution environment: ${JSON.stringify(execution_env, null, 4)}`);
|
||||
const args = ["install"];
|
||||
if (this.planner) {
|
||||
args.push(this.planner);
|
||||
|
@ -199,25 +272,11 @@ class NixInstallerAction {
|
|||
args.push(get_default_planner());
|
||||
}
|
||||
if (this.extra_args) {
|
||||
const extra_args = (0,string_argv__WEBPACK_IMPORTED_MODULE_8__/* ["default"] */ .Z)(this.extra_args);
|
||||
const extra_args = (0,string_argv__WEBPACK_IMPORTED_MODULE_10__/* ["default"] */ .Z)(this.extra_args);
|
||||
args.concat(extra_args);
|
||||
}
|
||||
const exit_code = await _actions_exec__WEBPACK_IMPORTED_MODULE_3__.exec(binary_path, args, {
|
||||
env: Object.assign(Object.assign({}, execution_env), process.env),
|
||||
listeners: {
|
||||
stdout: (data) => {
|
||||
const trimmed = data.toString("utf-8").trimEnd();
|
||||
if (trimmed.length >= 0) {
|
||||
_actions_core__WEBPACK_IMPORTED_MODULE_0__.info(trimmed);
|
||||
}
|
||||
},
|
||||
stderr: (data) => {
|
||||
const trimmed = data.toString("utf-8").trimEnd();
|
||||
if (trimmed.length >= 0) {
|
||||
_actions_core__WEBPACK_IMPORTED_MODULE_0__.info(trimmed);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
if (exit_code !== 0) {
|
||||
throw new Error(`Non-zero exit code of \`${exit_code}\` detected`);
|
||||
|
@ -254,10 +313,110 @@ class NixInstallerAction {
|
|||
_actions_core__WEBPACK_IMPORTED_MODULE_0__.exportVariable("DETERMINATE_NIX_KVM", "0");
|
||||
}
|
||||
// Normal just doing of the install
|
||||
_actions_core__WEBPACK_IMPORTED_MODULE_0__.startGroup("Installing Nix");
|
||||
const binary_path = await this.fetch_binary();
|
||||
await this.execute_install(binary_path);
|
||||
_actions_core__WEBPACK_IMPORTED_MODULE_0__.endGroup();
|
||||
if (this.force_docker_shim) {
|
||||
await this.spawnDockerShim();
|
||||
}
|
||||
await this.set_github_path();
|
||||
}
|
||||
async spawnDockerShim() {
|
||||
_actions_core__WEBPACK_IMPORTED_MODULE_0__.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",
|
||||
};
|
||||
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__WEBPACK_IMPORTED_MODULE_0__.debug("Loading image: determinate-nix-shim:latest...");
|
||||
{
|
||||
const exit_code = await _actions_exec__WEBPACK_IMPORTED_MODULE_3__.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__WEBPACK_IMPORTED_MODULE_0__.debug(trimmed);
|
||||
}
|
||||
},
|
||||
stderr: (data) => {
|
||||
const trimmed = data.toString("utf-8").trimEnd();
|
||||
if (trimmed.length >= 0) {
|
||||
_actions_core__WEBPACK_IMPORTED_MODULE_0__.debug(trimmed);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
if (exit_code !== 0) {
|
||||
throw new Error(`Failed to build the shim image, exit code: \`${exit_code}\``);
|
||||
}
|
||||
}
|
||||
{
|
||||
_actions_core__WEBPACK_IMPORTED_MODULE_0__.debug("Starting the Nix daemon through Docker...");
|
||||
const exit_code = await _actions_exec__WEBPACK_IMPORTED_MODULE_3__.exec("docker", [
|
||||
"--log-level=debug",
|
||||
"run",
|
||||
"--detach",
|
||||
"--privileged",
|
||||
"--userns=host",
|
||||
"--pid=host",
|
||||
"--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.correlation}`,
|
||||
"determinate-nix-shim:latest",
|
||||
], {
|
||||
silent: true,
|
||||
listeners: {
|
||||
stdline: (data) => {
|
||||
_actions_core__WEBPACK_IMPORTED_MODULE_0__.saveState("docker_shim_container_id", data.trimEnd());
|
||||
},
|
||||
stdout: (data) => {
|
||||
const trimmed = data.toString("utf-8").trimEnd();
|
||||
if (trimmed.length >= 0) {
|
||||
_actions_core__WEBPACK_IMPORTED_MODULE_0__.debug(trimmed);
|
||||
}
|
||||
},
|
||||
stderr: (data) => {
|
||||
const trimmed = data.toString("utf-8").trimEnd();
|
||||
if (trimmed.length >= 0) {
|
||||
_actions_core__WEBPACK_IMPORTED_MODULE_0__.debug(trimmed);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
if (exit_code !== 0) {
|
||||
throw new Error(`Failed to start the Nix daemon through Docker, exit code: \`${exit_code}\``);
|
||||
}
|
||||
}
|
||||
_actions_core__WEBPACK_IMPORTED_MODULE_0__.endGroup();
|
||||
return;
|
||||
}
|
||||
async cleanupDockerShim() {
|
||||
const container_id = _actions_core__WEBPACK_IMPORTED_MODULE_0__.getState("docker_shim_container_id");
|
||||
if (container_id !== "") {
|
||||
_actions_core__WEBPACK_IMPORTED_MODULE_0__.startGroup("Cleaning up the Nix daemon's Docker shim");
|
||||
await _actions_exec__WEBPACK_IMPORTED_MODULE_3__.exec("docker", ["rm", "--force", container_id]);
|
||||
_actions_core__WEBPACK_IMPORTED_MODULE_0__.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 {
|
||||
|
@ -290,20 +449,6 @@ class NixInstallerAction {
|
|||
async execute_uninstall() {
|
||||
const exit_code = await _actions_exec__WEBPACK_IMPORTED_MODULE_3__.exec(`/nix/nix-installer`, ["uninstall"], {
|
||||
env: Object.assign({ NIX_INSTALLER_NO_CONFIRM: "true" }, process.env),
|
||||
listeners: {
|
||||
stdout: (data) => {
|
||||
const trimmed = data.toString("utf-8").trimEnd();
|
||||
if (trimmed.length >= 0) {
|
||||
_actions_core__WEBPACK_IMPORTED_MODULE_0__.info(trimmed);
|
||||
}
|
||||
},
|
||||
stderr: (data) => {
|
||||
const trimmed = data.toString("utf-8").trimEnd();
|
||||
if (trimmed.length >= 0) {
|
||||
_actions_core__WEBPACK_IMPORTED_MODULE_0__.info(trimmed);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
if (exit_code !== 0) {
|
||||
throw new Error(`Non-zero exit code of \`${exit_code}\` detected`);
|
||||
|
@ -329,7 +474,14 @@ class NixInstallerAction {
|
|||
"-c",
|
||||
`echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee ${kvm_rules} > /dev/null`,
|
||||
], {
|
||||
silent: true,
|
||||
listeners: {
|
||||
stdout: (data) => {
|
||||
const trimmed = data.toString("utf-8").trimEnd();
|
||||
if (trimmed.length >= 0) {
|
||||
_actions_core__WEBPACK_IMPORTED_MODULE_0__.debug(trimmed);
|
||||
}
|
||||
},
|
||||
stderr: (data) => {
|
||||
const trimmed = data.toString("utf-8").trimEnd();
|
||||
if (trimmed.length >= 0) {
|
||||
|
@ -343,6 +495,7 @@ class NixInstallerAction {
|
|||
}
|
||||
const debug_run_throw = async (action, command, args) => {
|
||||
const reload_exit_code = await _actions_exec__WEBPACK_IMPORTED_MODULE_3__.exec(command, args, {
|
||||
silent: true,
|
||||
listeners: {
|
||||
stdout: (data) => {
|
||||
const trimmed = data.toString("utf-8").trimEnd();
|
||||
|
@ -375,16 +528,7 @@ class NixInstallerAction {
|
|||
return true;
|
||||
}
|
||||
catch (error) {
|
||||
await _actions_exec__WEBPACK_IMPORTED_MODULE_3__.exec("sudo", ["rm", "-f", kvm_rules], {
|
||||
listeners: {
|
||||
stderr: (data) => {
|
||||
const trimmed = data.toString("utf-8").trimEnd();
|
||||
if (trimmed.length >= 0) {
|
||||
_actions_core__WEBPACK_IMPORTED_MODULE_0__.info(trimmed);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
await _actions_exec__WEBPACK_IMPORTED_MODULE_3__.exec("sudo", ["rm", "-f", kvm_rules]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -562,18 +706,20 @@ function action_input_bool(name) {
|
|||
}
|
||||
async function main() {
|
||||
try {
|
||||
if (!process.env["STATE_correlation"]) {
|
||||
const correlation = `GH-${(0,node_crypto__WEBPACK_IMPORTED_MODULE_5__.randomUUID)()}`;
|
||||
let correlation = _actions_core__WEBPACK_IMPORTED_MODULE_0__.getState("correlation");
|
||||
if (correlation === "") {
|
||||
correlation = `GH-${(0,node_crypto__WEBPACK_IMPORTED_MODULE_5__.randomUUID)()}`;
|
||||
_actions_core__WEBPACK_IMPORTED_MODULE_0__.saveState("correlation", correlation);
|
||||
process.env["STATE_correlation"] = correlation;
|
||||
}
|
||||
const installer = new NixInstallerAction();
|
||||
const isPost = !!process.env["STATE_isPost"];
|
||||
if (!isPost) {
|
||||
_actions_core__WEBPACK_IMPORTED_MODULE_0__.saveState("isPost", "true");
|
||||
const installer = new NixInstallerAction(correlation);
|
||||
const isPost = _actions_core__WEBPACK_IMPORTED_MODULE_0__.getState("isPost");
|
||||
if (isPost !== "true") {
|
||||
await installer.detectAndForceDockerShim();
|
||||
await installer.install();
|
||||
_actions_core__WEBPACK_IMPORTED_MODULE_0__.saveState("isPost", "true");
|
||||
}
|
||||
else {
|
||||
await installer.cleanupDockerShim();
|
||||
await installer.report_overall();
|
||||
}
|
||||
}
|
||||
|
@ -17293,6 +17439,14 @@ module.exports = require("node:fs/promises");
|
|||
|
||||
/***/ }),
|
||||
|
||||
/***/ 612:
|
||||
/***/ ((module) => {
|
||||
|
||||
"use strict";
|
||||
module.exports = require("node:os");
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 9411:
|
||||
/***/ ((module) => {
|
||||
|
||||
|
|
2
dist/index.js.map
generated
vendored
2
dist/index.js.map
generated
vendored
File diff suppressed because one or more lines are too long
19
docker-shim/Dockerfile
Normal file
19
docker-shim/Dockerfile
Normal file
|
@ -0,0 +1,19 @@
|
|||
# Determinate Nix Installer: Docker Shim
|
||||
#
|
||||
# This empty image exists to lean on Docker as a process supervisor when
|
||||
# systemd isn't available. Specifically intended for self-hosted GitHub
|
||||
# Actions runners using Docker-in-Docker.
|
||||
#
|
||||
# See: https://github.com/DeterminateSystems/nix-installer-action
|
||||
|
||||
FROM scratch
|
||||
|
||||
ENTRYPOINT [ "/nix/var/nix/profiles/default/bin/nix-daemon"]
|
||||
CMD []
|
||||
|
||||
HEALTHCHECK \
|
||||
--interval=5m \
|
||||
--timeout=3s \
|
||||
CMD ["/nix/var/nix/profiles/default/bin/nix", "store", "ping", "--store", "daemon"]
|
||||
|
||||
COPY ./Dockerfile /README.md
|
52
docker-shim/README.md
Normal file
52
docker-shim/README.md
Normal file
|
@ -0,0 +1,52 @@
|
|||
# Determinate Nix Installer Action: Docker Shim
|
||||
|
||||
The image in this repository is a product of the contained Dockerfile.
|
||||
It is an otherwise empty image with a configuration layer.
|
||||
|
||||
This image is to be used in GitHub Actions runners which don't have systemd available, like self-hosted ARC runners.
|
||||
|
||||
The image would have no layers / content at all, however Docker has a bug and refuses to export those images.
|
||||
This isn't a technical limitation preventing us from creating and distributing that image, but an ease-of-use limitation.
|
||||
Since some of Docker's inspection tools break on an empty image, the image contains a single layer containing a README.
|
||||
|
||||
To build:
|
||||
|
||||
```shell
|
||||
docker build . --tag determinate-nix-shim:latest
|
||||
docker image save determinate-nix-shim:latest | gzip --best > amd64.tar
|
||||
```
|
||||
|
||||
Then, extract the tarball:
|
||||
|
||||
```
|
||||
mkdir extract
|
||||
cd extract
|
||||
tar -xf ../amd64.tar
|
||||
```
|
||||
|
||||
It'll look like this, though the hashes will be different.
|
||||
|
||||
```
|
||||
.
|
||||
├── 771204abb853cdde06bbbc680001a02642050a1db1a7b0a48cf5f20efa8bdc5d.json
|
||||
├── c4088111818e553e834adfc81bda8fe6da281afa9a40012eaa82796fb5476e98
|
||||
│ ├── VERSION
|
||||
│ ├── json
|
||||
│ └── layer.tar
|
||||
├── manifest.json
|
||||
└── repositories
|
||||
```
|
||||
|
||||
Ignore `manifest.json`, and edit the other two JSON documents to replace `amd64` with `arm64`, both in a key named "architecture:
|
||||
|
||||
```
|
||||
"architecture":"amd64"
|
||||
```
|
||||
|
||||
Then re-create the tar, from within the `extract` directory:
|
||||
|
||||
```
|
||||
tar --options gzip:compression-level=9 -zcf ../arm64.tar.gz .
|
||||
```
|
||||
|
||||
Then `git add` the two .tar.gz's and you're done.
|
BIN
docker-shim/amd64.tar.gz
Normal file
BIN
docker-shim/amd64.tar.gz
Normal file
Binary file not shown.
BIN
docker-shim/arm64.tar.gz
Normal file
BIN
docker-shim/arm64.tar.gz
Normal file
Binary file not shown.
286
src/main.ts
286
src/main.ts
|
@ -6,7 +6,9 @@ import { chmod, access, writeFile } from "node:fs/promises";
|
|||
import { randomUUID } from "node:crypto";
|
||||
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";
|
||||
|
||||
class NixInstallerAction {
|
||||
platform: string;
|
||||
|
@ -18,7 +20,7 @@ class NixInstallerAction {
|
|||
kvm: boolean;
|
||||
github_server_url: string | null;
|
||||
github_token: string | null;
|
||||
// TODO: linux_init
|
||||
force_docker_shim: boolean | null;
|
||||
init: string | null;
|
||||
local_root: string | null;
|
||||
log_directives: string | null;
|
||||
|
@ -46,9 +48,9 @@ class NixInstallerAction {
|
|||
// This is for monitoring the real impact of Nix updates, to avoid breaking large
|
||||
// swaths of users at once with botched Nix releases. For example:
|
||||
// https://github.com/NixOS/nix/issues/9052.
|
||||
correlation: string | undefined;
|
||||
correlation: string;
|
||||
|
||||
constructor() {
|
||||
constructor(correlation: string) {
|
||||
this.platform = get_nix_platform();
|
||||
this.nix_package_url = action_input_string_or_null("nix-package-url");
|
||||
this.backtrace = action_input_string_or_null("backtrace");
|
||||
|
@ -56,6 +58,7 @@ class NixInstallerAction {
|
|||
this.extra_conf = action_input_multiline_string_or_null("extra-conf");
|
||||
this.flakehub = action_input_bool("flakehub");
|
||||
this.kvm = action_input_bool("kvm");
|
||||
this.force_docker_shim = action_input_bool("force-docker-shim");
|
||||
this.github_token = action_input_string_or_null("github-token");
|
||||
this.github_server_url = action_input_string_or_null("github-server-url");
|
||||
this.init = action_input_string_or_null("init");
|
||||
|
@ -89,13 +92,89 @@ class NixInstallerAction {
|
|||
"diagnostic-endpoint",
|
||||
);
|
||||
this.trust_runner_user = action_input_bool("trust-runner-user");
|
||||
this.correlation = process.env["STATE_correlation"];
|
||||
this.correlation = correlation;
|
||||
this.nix_installer_url = resolve_nix_installer_url(
|
||||
this.platform,
|
||||
this.correlation,
|
||||
);
|
||||
}
|
||||
|
||||
async detectAndForceDockerShim(): Promise<void> {
|
||||
// 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 {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
actions_core.debug(
|
||||
"Linux detected without systemd, testing for Docker with `docker info` as an alternative daemon supervisor.",
|
||||
);
|
||||
const exit_code = await actions_exec.exec("docker", ["info"], {
|
||||
silent: true,
|
||||
listeners: {
|
||||
stdout: (data: Buffer) => {
|
||||
const trimmed = data.toString("utf-8").trimEnd();
|
||||
if (trimmed.length >= 0) {
|
||||
actions_core.debug(trimmed);
|
||||
}
|
||||
},
|
||||
stderr: (data: Buffer) => {
|
||||
const trimmed = data.toString("utf-8").trimEnd();
|
||||
if (trimmed.length >= 0) {
|
||||
actions_core.debug(trimmed);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (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;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
private async executionEnvironment(): Promise<ExecuteEnvironment> {
|
||||
const execution_env: ExecuteEnvironment = {};
|
||||
|
||||
|
@ -218,7 +297,12 @@ class NixInstallerAction {
|
|||
extra_conf += "\n";
|
||||
}
|
||||
if (this.trust_runner_user !== null) {
|
||||
extra_conf += `trusted-users = root ${process.env.USER}`;
|
||||
const user = userInfo().username;
|
||||
if (user) {
|
||||
extra_conf += `trusted-users = root ${user}`;
|
||||
} else {
|
||||
extra_conf += `trusted-users = root`;
|
||||
}
|
||||
extra_conf += "\n";
|
||||
}
|
||||
if (this.flakehub) {
|
||||
|
@ -250,7 +334,7 @@ class NixInstallerAction {
|
|||
|
||||
private async execute_install(binary_path: string): Promise<number> {
|
||||
const execution_env = await this.executionEnvironment();
|
||||
actions_core.info(
|
||||
actions_core.debug(
|
||||
`Execution environment: ${JSON.stringify(execution_env, null, 4)}`,
|
||||
);
|
||||
|
||||
|
@ -271,20 +355,6 @@ class NixInstallerAction {
|
|||
...execution_env,
|
||||
...process.env, // To get $PATH, etc
|
||||
},
|
||||
listeners: {
|
||||
stdout: (data: Buffer) => {
|
||||
const trimmed = data.toString("utf-8").trimEnd();
|
||||
if (trimmed.length >= 0) {
|
||||
actions_core.info(trimmed);
|
||||
}
|
||||
},
|
||||
stderr: (data: Buffer) => {
|
||||
const trimmed = data.toString("utf-8").trimEnd();
|
||||
if (trimmed.length >= 0) {
|
||||
actions_core.info(trimmed);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (exit_code !== 0) {
|
||||
|
@ -329,11 +399,137 @@ class NixInstallerAction {
|
|||
}
|
||||
|
||||
// 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(): Promise<void> {
|
||||
actions_core.startGroup(
|
||||
"Configuring the Docker shim as the Nix Daemon's process supervisor",
|
||||
);
|
||||
|
||||
const images: { [key: string]: string } = {
|
||||
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: Buffer) => {
|
||||
const trimmed = data.toString("utf-8").trimEnd();
|
||||
if (trimmed.length >= 0) {
|
||||
actions_core.debug(trimmed);
|
||||
}
|
||||
},
|
||||
stderr: (data: Buffer) => {
|
||||
const trimmed = data.toString("utf-8").trimEnd();
|
||||
if (trimmed.length >= 0) {
|
||||
actions_core.debug(trimmed);
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (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...");
|
||||
const exit_code = await actions_exec.exec(
|
||||
"docker",
|
||||
[
|
||||
"--log-level=debug",
|
||||
"run",
|
||||
"--detach",
|
||||
"--privileged",
|
||||
"--userns=host",
|
||||
"--pid=host",
|
||||
"--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.correlation}`,
|
||||
"determinate-nix-shim:latest",
|
||||
],
|
||||
{
|
||||
silent: true,
|
||||
listeners: {
|
||||
stdline: (data: string) => {
|
||||
actions_core.saveState(
|
||||
"docker_shim_container_id",
|
||||
data.trimEnd(),
|
||||
);
|
||||
},
|
||||
stdout: (data: Buffer) => {
|
||||
const trimmed = data.toString("utf-8").trimEnd();
|
||||
if (trimmed.length >= 0) {
|
||||
actions_core.debug(trimmed);
|
||||
}
|
||||
},
|
||||
stderr: (data: Buffer) => {
|
||||
const trimmed = data.toString("utf-8").trimEnd();
|
||||
if (trimmed.length >= 0) {
|
||||
actions_core.debug(trimmed);
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (exit_code !== 0) {
|
||||
throw new Error(
|
||||
`Failed to start the Nix daemon through Docker, exit code: \`${exit_code}\``,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
actions_core.endGroup();
|
||||
|
||||
return;
|
||||
}
|
||||
async cleanupDockerShim(): Promise<void> {
|
||||
const container_id = actions_core.getState("docker_shim_container_id");
|
||||
if (container_id !== "") {
|
||||
actions_core.startGroup("Cleaning up the Nix daemon's Docker shim");
|
||||
|
||||
await actions_exec.exec("docker", ["rm", "--force", container_id]);
|
||||
|
||||
actions_core.endGroup();
|
||||
}
|
||||
}
|
||||
|
||||
async set_github_path(): Promise<void> {
|
||||
// Interim versions of the `nix-installer` crate may have already manipulated `$GITHUB_PATH`, as root even! Accessing that will be an error.
|
||||
try {
|
||||
|
@ -386,20 +582,6 @@ class NixInstallerAction {
|
|||
NIX_INSTALLER_NO_CONFIRM: "true",
|
||||
...process.env, // To get $PATH, etc
|
||||
},
|
||||
listeners: {
|
||||
stdout: (data: Buffer) => {
|
||||
const trimmed = data.toString("utf-8").trimEnd();
|
||||
if (trimmed.length >= 0) {
|
||||
actions_core.info(trimmed);
|
||||
}
|
||||
},
|
||||
stderr: (data: Buffer) => {
|
||||
const trimmed = data.toString("utf-8").trimEnd();
|
||||
if (trimmed.length >= 0) {
|
||||
actions_core.info(trimmed);
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -433,7 +615,14 @@ class NixInstallerAction {
|
|||
`echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee ${kvm_rules} > /dev/null`,
|
||||
],
|
||||
{
|
||||
silent: true,
|
||||
listeners: {
|
||||
stdout: (data: Buffer) => {
|
||||
const trimmed = data.toString("utf-8").trimEnd();
|
||||
if (trimmed.length >= 0) {
|
||||
actions_core.debug(trimmed);
|
||||
}
|
||||
},
|
||||
stderr: (data: Buffer) => {
|
||||
const trimmed = data.toString("utf-8").trimEnd();
|
||||
if (trimmed.length >= 0) {
|
||||
|
@ -456,6 +645,7 @@ class NixInstallerAction {
|
|||
args: string[],
|
||||
): Promise<void> => {
|
||||
const reload_exit_code = await actions_exec.exec(command, args, {
|
||||
silent: true,
|
||||
listeners: {
|
||||
stdout: (data: Buffer) => {
|
||||
const trimmed = data.toString("utf-8").trimEnd();
|
||||
|
@ -493,16 +683,7 @@ class NixInstallerAction {
|
|||
|
||||
return true;
|
||||
} catch (error) {
|
||||
await actions_exec.exec("sudo", ["rm", "-f", kvm_rules], {
|
||||
listeners: {
|
||||
stderr: (data: Buffer) => {
|
||||
const trimmed = data.toString("utf-8").trimEnd();
|
||||
if (trimmed.length >= 0) {
|
||||
actions_core.info(trimmed);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
await actions_exec.exec("sudo", ["rm", "-f", kvm_rules]);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -751,18 +932,21 @@ function action_input_bool(name: string): boolean {
|
|||
|
||||
async function main(): Promise<void> {
|
||||
try {
|
||||
if (!process.env["STATE_correlation"]) {
|
||||
const correlation = `GH-${randomUUID()}`;
|
||||
let correlation: string = actions_core.getState("correlation");
|
||||
if (correlation === "") {
|
||||
correlation = `GH-${randomUUID()}`;
|
||||
actions_core.saveState("correlation", correlation);
|
||||
process.env["STATE_correlation"] = correlation;
|
||||
}
|
||||
const installer = new NixInstallerAction();
|
||||
|
||||
const isPost = !!process.env["STATE_isPost"];
|
||||
if (!isPost) {
|
||||
actions_core.saveState("isPost", "true");
|
||||
const installer = new NixInstallerAction(correlation);
|
||||
|
||||
const isPost = actions_core.getState("isPost");
|
||||
if (isPost !== "true") {
|
||||
await installer.detectAndForceDockerShim();
|
||||
await installer.install();
|
||||
actions_core.saveState("isPost", "true");
|
||||
} else {
|
||||
await installer.cleanupDockerShim();
|
||||
await installer.report_overall();
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
Loading…
Reference in a new issue