Support private flakes on FlakeHub (#38)

Use the GitHub Actions-issued JWT to authenticate with FlakeHub.
The repository will be granted its due permissions on FlakeHub,
and be able to pull the user's private flakes.
This commit is contained in:
Graham Christensen 2023-10-04 17:35:16 -04:00 committed by GitHub
parent d654f7b93a
commit 07ebb8d274
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 194 additions and 107 deletions

2
.envrc
View file

@ -1 +1 @@
use flake use flake

View file

@ -15,13 +15,38 @@ jobs:
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Install Nix - name: Install Nix
uses: DeterminateSystems/nix-installer-action@v4 uses: DeterminateSystems/nix-installer-action@main
- name: Run `nix build` - name: Run `nix build`
run: nix build . run: nix build .
``` ```
See [`.github/workflows/ci.yml`](.github/workflows/ci.yml) for a full example. See [`.github/workflows/ci.yml`](.github/workflows/ci.yml) for a full example.
To use private flakes from FlakeHub, use a configuration like this:
```yaml
on:
pull_request:
push:
branches: [main]
jobs:
lints:
name: Build
runs-on: ubuntu-22.04
permissions:
id-token: "write"
contents: "read"
steps:
- uses: actions/checkout@v3
- name: Install Nix
uses: DeterminateSystems/nix-installer-action@main
with:
flakehub: true
- name: Run `nix build`
run: nix build .
```
## Configuration ## Configuration
| Parameter | Description | Type | Default | | Parameter | Description | Type | Default |
@ -29,6 +54,7 @@ See [`.github/workflows/ci.yml`](.github/workflows/ci.yml) for a full example.
| `backtrace` | The setting for [`RUST_BACKTRACE`][backtrace] | string | | | `backtrace` | The setting for [`RUST_BACKTRACE`][backtrace] | string | |
| `extra-args` | Extra arguments to pass to the planner (prefer using structured `with:` arguments unless using a custom [planner]!) | string | | | `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 | | | `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` |
| `github-token` | A [GitHub token] for making authenticated requests (which have a higher rate-limit quota than unauthenticated requests) | string | `${{ github.token }}` | | `github-token` | A [GitHub token] for making authenticated requests (which have a higher rate-limit quota than unauthenticated requests) | string | `${{ github.token }}` |
| `init` | The init system to configure (requires `planner: linux-multi`) | enum (`none` or `systemd`) | | | `init` | The init system to configure (requires `planner: linux-multi`) | enum (`none` or `systemd`) | |
| `local-root` | A local `nix-installer` binary root. Overrides the `nix-installer-url` setting (a `nix-installer.sh` should exist, binaries should be named `nix-installer-$ARCH`, eg. `nix-installer-x86_64-linux`). | Boolean | `false` | | `local-root` | A local `nix-installer` binary root. Overrides the `nix-installer-url` setting (a `nix-installer.sh` should exist, binaries should be named `nix-installer-$ARCH`, eg. `nix-installer-x86_64-linux`). | Boolean | `false` |

View file

@ -13,6 +13,10 @@ inputs:
extra-conf: extra-conf:
description: Extra configuration lines for `/etc/nix/nix.conf` (includes `access-tokens` with `secrets.GITHUB_TOKEN` automatically if `github-token` is set) description: Extra configuration lines for `/etc/nix/nix.conf` (includes `access-tokens` with `secrets.GITHUB_TOKEN` automatically if `github-token` is set)
required: false required: false
flakehub:
description: Automatically log in to your [FlakeHub](https://flakehub.com) account, for accessing private flakes.
required: false
default: false
github-token: github-token:
description: A GitHub token for making authenticated requests (which have a higher rate-limit quota than unauthenticated requests) description: A GitHub token for making authenticated requests (which have a higher rate-limit quota than unauthenticated requests)
default: ${{ github.token }} default: ${{ github.token }}

227
dist/index.js vendored
View file

@ -61,6 +61,7 @@ class NixInstallerAction {
this.backtrace = action_input_string_or_null("backtrace"); this.backtrace = action_input_string_or_null("backtrace");
this.extra_args = action_input_string_or_null("extra-args"); this.extra_args = action_input_string_or_null("extra-args");
this.extra_conf = action_input_multiline_string_or_null("extra-conf"); this.extra_conf = action_input_multiline_string_or_null("extra-conf");
this.flakehub = action_input_bool("flakehub");
this.github_token = action_input_string_or_null("github-token"); this.github_token = action_input_string_or_null("github-token");
this.init = action_input_string_or_null("init"); this.init = action_input_string_or_null("init");
this.local_root = action_input_string_or_null("local-root"); this.local_root = action_input_string_or_null("local-root");
@ -87,119 +88,125 @@ class NixInstallerAction {
this.nix_installer_url = resolve_nix_installer_url(this.platform, this.correlation); this.nix_installer_url = resolve_nix_installer_url(this.platform, this.correlation);
} }
executionEnvironment() { executionEnvironment() {
const execution_env = {}; return __awaiter(this, void 0, void 0, function* () {
execution_env.NIX_INSTALLER_NO_CONFIRM = "true"; const execution_env = {};
execution_env.NIX_INSTALLER_DIAGNOSTIC_ATTRIBUTION = this.correlation; execution_env.NIX_INSTALLER_NO_CONFIRM = "true";
if (this.backtrace !== null) { execution_env.NIX_INSTALLER_DIAGNOSTIC_ATTRIBUTION = this.correlation;
execution_env.RUST_BACKTRACE = this.backtrace; 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 { if (this.modify_profile !== null) {
execution_env.NIX_INSTALLER_MODIFY_PROFILE = "false"; 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) {
if (this.nix_build_group_id !== null) { execution_env.NIX_INSTALLER_NIX_BUILD_GROUP_ID = `${this.nix_build_group_id}`;
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;
}
if (this.diagnostic_endpoint !== null) {
execution_env.NIX_INSTALLER_DIAGNOSTIC_ENDPOINT =
this.diagnostic_endpoint;
}
// 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.nix_build_group_name !== null) {
} execution_env.NIX_INSTALLER_NIX_BUILD_GROUP_NAME =
if (this.mac_case_sensitive !== null) { this.nix_build_group_name;
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.nix_build_user_prefix !== null) {
} execution_env.NIX_INSTALLER_NIX_BUILD_USER_PREFIX =
if (this.mac_volume_label !== null) { this.nix_build_user_prefix;
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.nix_build_user_count !== null) {
} execution_env.NIX_INSTALLER_NIX_BUILD_USER_COUNT = `${this.nix_build_user_count}`;
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.nix_build_user_base !== null) {
} execution_env.NIX_INSTALLER_NIX_BUILD_USER_ID_BASE = `${this.nix_build_user_count}`;
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.nix_package_url !== null) {
} execution_env.NIX_INSTALLER_NIX_PACKAGE_URL = `${this.nix_package_url}`;
if (this.start_daemon !== null) {
if (this.start_daemon) {
execution_env.NIX_INSTALLER_START_DAEMON = "true";
} }
else { if (this.proxy !== null) {
execution_env.NIX_INSTALLER_START_DAEMON = "false"; execution_env.NIX_INSTALLER_PROXY = this.proxy;
} }
} if (this.ssl_cert_file !== null) {
let extra_conf = ""; execution_env.NIX_INSTALLER_SSL_CERT_FILE = this.ssl_cert_file;
if (this.github_token !== null) { }
extra_conf += `access-tokens = github.com=${this.github_token}`; if (this.diagnostic_endpoint !== null) {
extra_conf += "\n"; execution_env.NIX_INSTALLER_DIAGNOSTIC_ENDPOINT =
} this.diagnostic_endpoint;
if (this.trust_runner_user !== null) { }
extra_conf += `trusted-users = root ${process.env.USER}`; // TODO: Error if the user uses these on not-MacOS
extra_conf += "\n"; if (this.mac_encrypt !== null) {
} if (process.env.RUNNER_OS !== "macOS") {
if (this.extra_conf !== null && this.extra_conf.length !== 0) { throw new Error("`mac-encrypt` while `$RUNNER_OS` was not `macOS`");
extra_conf += this.extra_conf.join("\n"); }
extra_conf += "\n"; execution_env.NIX_INSTALLER_ENCRYPT = this.mac_encrypt;
} }
execution_env.NIX_INSTALLER_EXTRA_CONF = extra_conf; if (this.mac_case_sensitive !== null) {
if (process.env.ACT && !process.env.NOT_ACT) { if (process.env.RUNNER_OS !== "macOS") {
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`"); throw new Error("`mac-case-sensitive` while `$RUNNER_OS` was not `macOS`");
execution_env.NIX_INSTALLER_INIT = "none"; }
} execution_env.NIX_INSTALLER_CASE_SENSITIVE = this.mac_case_sensitive;
return execution_env; }
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_token !== null) {
extra_conf += `access-tokens = github.com=${this.github_token}`;
extra_conf += "\n";
}
if (this.trust_runner_user !== null) {
extra_conf += `trusted-users = root ${process.env.USER}`;
extra_conf += "\n";
}
if (this.flakehub) {
extra_conf += `netrc-file = ${yield 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) {
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";
}
return execution_env;
});
} }
execute_install(binary_path) { execute_install(binary_path) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const execution_env = this.executionEnvironment(); const execution_env = yield this.executionEnvironment();
actions_core.info(`Execution environment: ${JSON.stringify(execution_env, null, 4)}`); actions_core.info(`Execution environment: ${JSON.stringify(execution_env, null, 4)}`);
const args = ["install"]; const args = ["install"];
if (this.planner) { if (this.planner) {
@ -276,6 +283,24 @@ class NixInstallerAction {
} }
}); });
} }
flakehub_login() {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const netrc_path = `${process.env["RUNNER_TEMP"]}/determinate-nix-installer-netrc`;
const jwt = yield actions_core.getIDToken("api.flakehub.com");
yield (0, promises_1.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 ((_a = this.extra_conf) === null || _a === void 0 ? void 0 : _a.join("\n").match(/^netrc-file/m)) {
actions_core.warning("Logging in to FlakeHub conflicts with the Nix option `netrc-file`.");
}
return netrc_path;
});
}
execute_uninstall() { execute_uninstall() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const spawned = (0, node_child_process_1.spawn)(`/nix/nix-installer`, ["uninstall"], { const spawned = (0, node_child_process_1.spawn)(`/nix/nix-installer`, ["uninstall"], {

2
dist/index.js.map vendored

File diff suppressed because one or more lines are too long

View file

@ -1,6 +1,6 @@
import * as actions_core from "@actions/core"; import * as actions_core from "@actions/core";
import * as github from "@actions/github"; import * as github from "@actions/github";
import { mkdtemp, chmod, access } from "node:fs/promises"; import { mkdtemp, chmod, access, writeFile } from "node:fs/promises";
import { spawn } from "node:child_process"; import { spawn } from "node:child_process";
import { randomUUID } from "node:crypto"; import { randomUUID } from "node:crypto";
import { join } from "node:path"; import { join } from "node:path";
@ -17,6 +17,7 @@ class NixInstallerAction {
backtrace: string | null; backtrace: string | null;
extra_args: string | null; extra_args: string | null;
extra_conf: string[] | null; extra_conf: string[] | null;
flakehub: boolean;
github_token: string | null; github_token: string | null;
// TODO: linux_init // TODO: linux_init
init: string | null; init: string | null;
@ -54,6 +55,7 @@ class NixInstallerAction {
this.backtrace = action_input_string_or_null("backtrace"); this.backtrace = action_input_string_or_null("backtrace");
this.extra_args = action_input_string_or_null("extra-args"); this.extra_args = action_input_string_or_null("extra-args");
this.extra_conf = action_input_multiline_string_or_null("extra-conf"); this.extra_conf = action_input_multiline_string_or_null("extra-conf");
this.flakehub = action_input_bool("flakehub");
this.github_token = action_input_string_or_null("github-token"); this.github_token = action_input_string_or_null("github-token");
this.init = action_input_string_or_null("init"); this.init = action_input_string_or_null("init");
this.local_root = action_input_string_or_null("local-root"); this.local_root = action_input_string_or_null("local-root");
@ -93,7 +95,7 @@ class NixInstallerAction {
); );
} }
private executionEnvironment(): ExecuteEnvironment { private async executionEnvironment(): Promise<ExecuteEnvironment> {
const execution_env: ExecuteEnvironment = {}; const execution_env: ExecuteEnvironment = {};
execution_env.NIX_INSTALLER_NO_CONFIRM = "true"; execution_env.NIX_INSTALLER_NO_CONFIRM = "true";
@ -217,6 +219,10 @@ class NixInstallerAction {
extra_conf += `trusted-users = root ${process.env.USER}`; extra_conf += `trusted-users = root ${process.env.USER}`;
extra_conf += "\n"; 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) { if (this.extra_conf !== null && this.extra_conf.length !== 0) {
extra_conf += this.extra_conf.join("\n"); extra_conf += this.extra_conf.join("\n");
extra_conf += "\n"; extra_conf += "\n";
@ -234,7 +240,7 @@ class NixInstallerAction {
} }
private async execute_install(binary_path: string): Promise<number> { private async execute_install(binary_path: string): Promise<number> {
const execution_env = this.executionEnvironment(); const execution_env = await this.executionEnvironment();
actions_core.info( actions_core.info(
`Execution environment: ${JSON.stringify(execution_env, null, 4)}`, `Execution environment: ${JSON.stringify(execution_env, null, 4)}`,
); );
@ -326,6 +332,32 @@ class NixInstallerAction {
} }
} }
async flakehub_login(): Promise<string> {
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(): Promise<number> { async execute_uninstall(): Promise<number> {
const spawned = spawn(`/nix/nix-installer`, ["uninstall"], { const spawned = spawn(`/nix/nix-installer`, ["uninstall"], {
env: { env: {