1
0
Fork 0
mirror of https://github.com/cachix/cachix-action.git synced 2025-06-07 22:57:01 +09:00

dev: enable git hooks and lint

This commit is contained in:
Sander 2024-11-21 23:32:49 +04:00
parent da5c84033f
commit 4800fda0c4
No known key found for this signature in database
GPG key ID: D1A763BC84F34603
11 changed files with 330 additions and 257 deletions

View file

@ -1,13 +1,12 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: daily
time: '00:00'
timezone: UTC
open-pull-requests-limit: 10
commit-message:
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: daily
time: "00:00"
timezone: UTC
open-pull-requests-limit: 10
commit-message:
prefix: "chore"
include: "scope"
include: "scope"

View file

@ -16,19 +16,19 @@ jobs:
useDaemon: [true, false]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- run: yarn install --frozen-lockfile
- run: yarn build
# check if typescript generation was committed
- run: git diff --exit-code
- uses: cachix/install-nix-action@v30
- name: Test public cache
uses: ./
with:
name: cachix-action
signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}'
useDaemon: ${{ matrix.useDaemon }}
- run: nix-build test.nix
- uses: actions/checkout@v4
- run: yarn install --frozen-lockfile
- run: yarn build
# check if typescript generation was committed
- run: git diff --exit-code
- uses: cachix/install-nix-action@v30
- name: Test public cache
uses: ./
with:
name: cachix-action
signingKey: "${{ secrets.CACHIX_SIGNING_KEY }}"
useDaemon: ${{ matrix.useDaemon }}
- run: nix-build test.nix
public-cache-no-signing-key:
strategy:
@ -37,16 +37,16 @@ jobs:
useDaemon: [true, false]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- run: yarn install --frozen-lockfile
- run: yarn build
- uses: cachix/install-nix-action@v30
- name: Test public cache no signing key
uses: ./
with:
name: cachix-action
useDaemon: ${{ matrix.useDaemon }}
- run: nix-build test.nix
- uses: actions/checkout@v4
- run: yarn install --frozen-lockfile
- run: yarn build
- uses: cachix/install-nix-action@v30
- name: Test public cache no signing key
uses: ./
with:
name: cachix-action
useDaemon: ${{ matrix.useDaemon }}
- run: nix-build test.nix
private-cache:
if: ${{ github.ref == 'refs/heads/master' }}
@ -56,18 +56,18 @@ jobs:
useDaemon: [true, false]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- run: yarn install --frozen-lockfile
- run: yarn build
- uses: cachix/install-nix-action@v30
- name: Test private cache
uses: ./
with:
name: cachix-action-private
signingKey: '${{ secrets.CACHIX_SIGNING_KEY_PRIVATE }}'
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
useDaemon: ${{ matrix.useDaemon }}
- run: nix-build test.nix
- uses: actions/checkout@v4
- run: yarn install --frozen-lockfile
- run: yarn build
- uses: cachix/install-nix-action@v30
- name: Test private cache
uses: ./
with:
name: cachix-action-private
signingKey: "${{ secrets.CACHIX_SIGNING_KEY_PRIVATE }}"
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
useDaemon: ${{ matrix.useDaemon }}
- run: nix-build test.nix
push-paths:
strategy:
@ -75,20 +75,20 @@ jobs:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- run: yarn install --frozen-lockfile
- run: yarn build
- uses: cachix/install-nix-action@v30
- id: paths
run: |
paths=$(nix-instantiate test.nix | tr '\n' ' ')
echo "OUT_PATHS=$paths" >> $GITHUB_OUTPUT
- name: Test pushPaths
uses: ./
with:
name: cachix-action
signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}'
pathsToPush: '${{ steps.paths.outputs.OUT_PATHS }}'
- uses: actions/checkout@v4
- run: yarn install --frozen-lockfile
- run: yarn build
- uses: cachix/install-nix-action@v30
- id: paths
run: |
paths=$(nix-instantiate test.nix | tr '\n' ' ')
echo "OUT_PATHS=$paths" >> $GITHUB_OUTPUT
- name: Test pushPaths
uses: ./
with:
name: cachix-action
signingKey: "${{ secrets.CACHIX_SIGNING_KEY }}"
pathsToPush: "${{ steps.paths.outputs.OUT_PATHS }}"
installCommand:
strategy:
@ -96,17 +96,17 @@ jobs:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- run: yarn install --frozen-lockfile
- run: yarn build
- uses: cachix/install-nix-action@v30
- name: Test private cache
uses: ./
with:
name: cachix-action
signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}'
installCommand: 'nix-env -j8 -f https://cachix.org/api/v1/install -iA cachix'
- run: nix-build test.nix
- uses: actions/checkout@v4
- run: yarn install --frozen-lockfile
- run: yarn build
- uses: cachix/install-nix-action@v30
- name: Test private cache
uses: ./
with:
name: cachix-action
signingKey: "${{ secrets.CACHIX_SIGNING_KEY }}"
installCommand: "nix-env -j8 -f https://cachix.org/api/v1/install -iA cachix"
- run: nix-build test.nix
nix-master:
strategy:
@ -114,18 +114,18 @@ jobs:
os: [ubuntu-latest, macos-13]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Install Nix
uses: cachix/install-nix-action@v30
with:
nix_path: nixpkgs=channel:nixos-22.11
install_url: https://nixos-nix-install-tests.cachix.org/serve/kkq45x7yrzvxq8627fi6hkswnfa7mg2l/install
install_options: '--tarball-url-prefix https://nixos-nix-install-tests.cachix.org/serve'
- run: yarn install --frozen-lockfile
- run: yarn build
- name: Test public cache
uses: ./
with:
name: cachix-action
signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}'
- run: nix-build test.nix
- uses: actions/checkout@v4
- name: Install Nix
uses: cachix/install-nix-action@v30
with:
nix_path: nixpkgs=channel:nixos-22.11
install_url: https://nixos-nix-install-tests.cachix.org/serve/kkq45x7yrzvxq8627fi6hkswnfa7mg2l/install
install_options: "--tarball-url-prefix https://nixos-nix-install-tests.cachix.org/serve"
- run: yarn install --frozen-lockfile
- run: yarn build
- name: Test public cache
uses: ./
with:
name: cachix-action
signingKey: "${{ secrets.CACHIX_SIGNING_KEY }}"
- run: nix-build test.nix

View file

@ -35,17 +35,20 @@ Note that malicious code submitted via forked pull request can, once merged into
## Hacking
Install the dependencies
Install the dependencies
```bash
$ yarn install
```
Build the typescript
```bash
$ yarn build
```
Run the tests :heavy_check_mark:
Run the tests :heavy_check_mark:
```bash
$ yarn test
```

View file

@ -1,39 +1,39 @@
name: 'Cachix'
description: 'nix-build with the help of caching to Cachix'
author: 'Domen Kožar'
name: "Cachix"
description: "nix-build with the help of caching to Cachix"
author: "Domen Kožar"
inputs:
name:
description: 'Name of a cachix cache to push and pull/substitute'
description: "Name of a cachix cache to push and pull/substitute"
required: true
extraPullNames:
description: 'Comma-separated list of names for extra cachix caches to pull/substitute'
description: "Comma-separated list of names for extra cachix caches to pull/substitute"
authToken:
description: 'Authentication token for Cachix, needed for private cache access or to push using an Auth Token'
description: "Authentication token for Cachix, needed for private cache access or to push using an Auth Token"
signingKey:
description: 'Signing key secret retrieved after creating binary cache on https://cachix.org'
description: "Signing key secret retrieved after creating binary cache on https://cachix.org"
skipPush:
description: 'Set to true to disable pushing build results to the cache'
default: 'false'
description: "Set to true to disable pushing build results to the cache"
default: "false"
pathsToPush:
description: 'Whitespace-separated list of paths to push. Leave empty to push every build result.'
description: "Whitespace-separated list of paths to push. Leave empty to push every build result."
pushFilter:
description: 'Ignored if pathsToPush is set. Regular expression to exclude derivations for the cache push, for example "(-source$|nixpkgs\.tar\.gz$)". Warning: this filter does not guarantee it will not get pushed in case the path is part of the closure of something that will get pushed.'
cachixArgs:
description: 'Extra command-line arguments to pass to cachix. If empty, defaults to -j8'
description: "Extra command-line arguments to pass to cachix. If empty, defaults to -j8"
skipAddingSubstituter:
description: 'Set to true to skip adding cachix cache as a substitute'
default: 'false'
description: "Set to true to skip adding cachix cache as a substitute"
default: "false"
useDaemon:
description: "Push store paths to the cache as they're built with the Cachix Daemon"
default: 'true'
default: "true"
cachixBin:
description: 'Provide a custom path to the cachix binary'
description: "Provide a custom path to the cachix binary"
installCommand:
description: 'Override the default cachix installation method'
description: "Override the default cachix installation method"
branding:
color: 'blue'
icon: 'database'
color: "blue"
icon: "database"
runs:
using: 'node20'
main: 'dist/main/index.js'
post: 'dist/main/index.js'
using: "node20"
main: "dist/main/index.js"
post: "dist/main/index.js"

View file

@ -21,4 +21,12 @@ in
yarn.enable = true;
yarn.install.enable = true;
};
git-hooks.hooks = {
prettier = {
enable = true;
excludes = [ "dist" ];
};
nixfmt-rfc-style.enable = true;
};
}

View file

@ -3,4 +3,3 @@ inputs:
url: github:NixOS/nixpkgs/nixpkgs-unstable
permittedInsecurePackages:
- nodejs-16.20.2

View file

@ -1,11 +1,11 @@
module.exports = {
clearMocks: true,
moduleFileExtensions: ['js', 'ts'],
testEnvironment: 'node',
testMatch: ['**/*.test.ts'],
testRunner: 'jest-circus/runner',
moduleFileExtensions: ["js", "ts"],
testEnvironment: "node",
testMatch: ["**/*.test.ts"],
testRunner: "jest-circus/runner",
transform: {
'^.+\\.ts$': 'ts-jest'
"^.+\\.ts$": "ts-jest",
},
verbose: true
}
verbose: true,
};

View file

@ -1,47 +1,47 @@
import * as core from '@actions/core';
import * as exec from '@actions/exec';
import { spawn } from 'node:child_process';
import * as fs from 'node:fs/promises';
import { openSync } from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { Tail } from 'tail';
import which from 'which';
import semver from 'semver';
import * as core from "@actions/core";
import * as exec from "@actions/exec";
import { spawn } from "node:child_process";
import * as fs from "node:fs/promises";
import { openSync } from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import { Tail } from "tail";
import which from "which";
import semver from "semver";
// inputs
const name = core.getInput('name', { required: true });
const extraPullNames = core.getInput('extraPullNames');
const signingKey = core.getInput('signingKey');
const authToken = core.getInput('authToken')
const skipPush = core.getBooleanInput('skipPush');
const pathsToPush = core.getInput('pathsToPush');
const pushFilter = core.getInput('pushFilter');
const cachixArgs = core.getInput('cachixArgs');
const skipAddingSubstituter = core.getBooleanInput('skipAddingSubstituter');
const useDaemon = core.getBooleanInput('useDaemon');
const cachixBinInput = core.getInput('cachixBin');
const name = core.getInput("name", { required: true });
const extraPullNames = core.getInput("extraPullNames");
const signingKey = core.getInput("signingKey");
const authToken = core.getInput("authToken");
const skipPush = core.getBooleanInput("skipPush");
const pathsToPush = core.getInput("pathsToPush");
const pushFilter = core.getInput("pushFilter");
const cachixArgs = core.getInput("cachixArgs");
const skipAddingSubstituter = core.getBooleanInput("skipAddingSubstituter");
const useDaemon = core.getBooleanInput("useDaemon");
const cachixBinInput = core.getInput("cachixBin");
const installCommand =
core.getInput('installCommand') ||
core.getInput("installCommand") ||
"nix-env --quiet -j8 -iA cachix -f https://cachix.org/api/v1/install";
const ENV_CACHIX_DAEMON_DIR = 'CACHIX_DAEMON_DIR';
const ENV_CACHIX_DAEMON_DIR = "CACHIX_DAEMON_DIR";
enum PushMode {
// Disable pushing entirely.
None = 'None',
None = "None",
// Push paths provided via the `pathsToPush` input.
PushPaths = 'PushPaths',
PushPaths = "PushPaths",
// Scans the entire store during the pre- and post-hooks and uploads the difference.
// This is a very simple method and is likely to work in any environment.
// There are two downsides:
// 1. The final set of paths to push is computed in the post-build hook, so paths are not pushed during builds.
// 2. It is not safe to use in a multi-user environment, as it may leak store paths built by other users.
StoreScan = 'StoreScan',
StoreScan = "StoreScan",
// Uses the Cachix Daemon to register a post-build hook with the Nix Daemon.
// Very efficient as it can upload paths as they are built.
// May not be supported in all environment (e.g. NixOS Containers) and inherits all of the implementation deficiencies of Nix's post-build hook.
Daemon = 'Daemon',
Daemon = "Daemon",
}
async function setup() {
@ -51,56 +51,58 @@ async function setup() {
core.debug(`Using Cachix executable from input: ${cachixBin}`);
} else {
// Find the Cachix executable in PATH
let resolvedCachixBin = which.sync('cachix', { nothrow: true });
let resolvedCachixBin = which.sync("cachix", { nothrow: true });
if (resolvedCachixBin) {
core.debug(`Found Cachix executable: ${cachixBin}`);
cachixBin = resolvedCachixBin;
} else {
core.startGroup('Cachix: installing')
await exec.exec('bash', ['-c', installCommand]);
cachixBin = which.sync('cachix');
core.startGroup("Cachix: installing");
await exec.exec("bash", ["-c", installCommand]);
cachixBin = which.sync("cachix");
core.debug(`Installed Cachix executable: ${cachixBin}`);
core.endGroup()
core.endGroup();
}
}
core.saveState('cachixBin', cachixBin);
core.saveState("cachixBin", cachixBin);
// Print the executable version.
// Also verifies that the binary exists and is executable.
core.startGroup('Cachix: checking version')
let cachixVersion =
await execToVariable(cachixBin, ['--version'])
.then((res) => semver.coerce(res.split(" ")[1]));
core.endGroup()
core.startGroup("Cachix: checking version");
let cachixVersion = await execToVariable(cachixBin, ["--version"]).then(
(res) => semver.coerce(res.split(" ")[1]),
);
core.endGroup();
// For managed signing key and private caches
if (authToken !== "") {
await exec.exec(cachixBin, ['authtoken', authToken]);
await exec.exec(cachixBin, ["authtoken", authToken]);
}
if (signingKey !== "") {
core.exportVariable('CACHIX_SIGNING_KEY', signingKey);
core.exportVariable("CACHIX_SIGNING_KEY", signingKey);
}
let hasPushTokens = signingKey !== "" || authToken !== "";
core.saveState('hasPushTokens', hasPushTokens);
core.saveState("hasPushTokens", hasPushTokens);
if (skipAddingSubstituter) {
core.info('Not adding Cachix cache to substituters as skipAddingSubstituter is set to true')
core.info(
"Not adding Cachix cache to substituters as skipAddingSubstituter is set to true",
);
} else {
core.startGroup(`Cachix: using cache ` + name);
await exec.exec(cachixBin, ['use', name]);
await exec.exec(cachixBin, ["use", name]);
core.endGroup();
}
if (extraPullNames != "") {
core.startGroup(`Cachix: using extra caches ` + extraPullNames);
const extraPullNameList = extraPullNames.split(',');
const extraPullNameList = extraPullNames.split(",");
for (let itemName of extraPullNameList) {
const trimmedItemName = itemName.trim();
await exec.exec(cachixBin, ['use', trimmedItemName]);
await exec.exec(cachixBin, ["use", trimmedItemName]);
}
core.endGroup();
}
@ -112,55 +114,66 @@ async function setup() {
if (pathsToPush) {
pushMode = PushMode.PushPaths;
} else if (useDaemon) {
let supportsDaemonInterface = (cachixVersion) ? semver.gte(cachixVersion, '1.7.0') : false;
let supportsDaemonInterface = cachixVersion
? semver.gte(cachixVersion, "1.7.0")
: false;
let supportsPostBuildHook = await isTrustedUser();
if (!supportsDaemonInterface) {
core.warning(`Cachix Daemon is not supported by this version of Cachix (${cachixVersion}). Ignoring the 'useDaemon' option.`)
core.warning(
`Cachix Daemon is not supported by this version of Cachix (${cachixVersion}). Ignoring the 'useDaemon' option.`,
);
}
if (!supportsPostBuildHook) {
core.warning("This user is not allowed to set the post-build-hook. Ignoring the 'useDaemon' option.");
core.warning(
"This user is not allowed to set the post-build-hook. Ignoring the 'useDaemon' option.",
);
}
pushMode = (supportsDaemonInterface && supportsPostBuildHook) ? PushMode.Daemon : PushMode.StoreScan;
pushMode =
supportsDaemonInterface && supportsPostBuildHook
? PushMode.Daemon
: PushMode.StoreScan;
} else {
pushMode = PushMode.StoreScan;
}
}
core.saveState('pushMode', pushMode);
core.saveState("pushMode", pushMode);
const tmpdir = process.env['RUNNER_TEMP'] ?? os.tmpdir();
const tmpdir = process.env["RUNNER_TEMP"] ?? os.tmpdir();
switch (pushMode) {
case PushMode.Daemon: {
const daemonDir = await fs.mkdtemp(path.join(tmpdir, 'cachix-daemon-'));
const daemonLog = openSync(`${daemonDir}/daemon.log`, 'a');
const daemonDir = await fs.mkdtemp(path.join(tmpdir, "cachix-daemon-"));
const daemonLog = openSync(`${daemonDir}/daemon.log`, "a");
const daemon = spawn(
cachixBin,
[
'daemon', 'run',
'--socket', `${daemonDir}/daemon.sock`,
"daemon",
"run",
"--socket",
`${daemonDir}/daemon.sock`,
name,
...splitArgs(cachixArgs),
],
{
stdio: ['ignore', daemonLog, daemonLog],
stdio: ["ignore", daemonLog, daemonLog],
detached: true,
}
},
);
daemon.on('error', (err) => {
daemon.on("error", (err) => {
core.error(`Cachix Daemon failed: ${err}`);
});
if (typeof daemon.pid === 'number') {
if (typeof daemon.pid === "number") {
const pid = daemon.pid.toString();
core.debug(`Spawned Cachix Daemon with PID: ${pid}`);
await fs.writeFile(pidFilePath(daemonDir), pid);
} else {
core.error('Failed to spawn Cachix Daemon');
core.error("Failed to spawn Cachix Daemon");
return;
}
@ -178,8 +191,11 @@ async function setup() {
case PushMode.StoreScan: {
// Remember existing store paths
const preBuildPathsFile = `${tmpdir}/store-path-pre-build`;
core.saveState('preBuildPathsFile', preBuildPathsFile);
await exec.exec("sh", ["-c", `${__dirname}/list-nix-store.sh > ${preBuildPathsFile}`]);
core.saveState("preBuildPathsFile", preBuildPathsFile);
await exec.exec("sh", [
"-c",
`${__dirname}/list-nix-store.sh > ${preBuildPathsFile}`,
]);
break;
}
@ -189,28 +205,35 @@ async function setup() {
}
async function upload() {
core.startGroup('Cachix: push');
core.startGroup("Cachix: push");
const cachixBin = core.getState('cachixBin');
const pushMode = core.getState('pushMode');
const cachixBin = core.getState("cachixBin");
const pushMode = core.getState("pushMode");
switch (pushMode) {
case PushMode.None: {
core.info("Pushing is disabled.");
const hasPushTokens = !!core.getState('hasPushTokens');
const hasPushTokens = !!core.getState("hasPushTokens");
if (skipPush) {
core.info('skipPush is enabled.');
core.info("skipPush is enabled.");
} else if (!hasPushTokens) {
core.info('Missing a Cachix auth token. Provide an authToken and/or signingKey to enable pushing to the cache.');
core.info(
"Missing a Cachix auth token. Provide an authToken and/or signingKey to enable pushing to the cache.",
);
}
break;
}
case PushMode.PushPaths: {
await exec.exec(cachixBin, ["push", ...splitArgs(cachixArgs), name, ...splitArgs(pathsToPush)]);
await exec.exec(cachixBin, [
"push",
...splitArgs(cachixArgs),
name,
...splitArgs(pathsToPush),
]);
break;
}
@ -218,25 +241,34 @@ async function upload() {
const daemonDir = process.env[ENV_CACHIX_DAEMON_DIR];
if (!daemonDir) {
core.error('Cachix Daemon not started. Skipping push');
core.error("Cachix Daemon not started. Skipping push");
break;
}
const daemonPid = parseInt(await fs.readFile(pidFilePath(daemonDir), { encoding: 'utf8' }));
const daemonPid = parseInt(
await fs.readFile(pidFilePath(daemonDir), { encoding: "utf8" }),
);
if (!daemonPid) {
core.error('Failed to find PID of Cachix Daemon. Skipping push.');
core.error("Failed to find PID of Cachix Daemon. Skipping push.");
break;
}
core.debug(`Found Cachix daemon with pid ${daemonPid}`);
let daemonLog = new Tail(`${daemonDir}/daemon.log`, { fromBeginning: true });
daemonLog.on('line', (line) => core.info(line));
let daemonLog = new Tail(`${daemonDir}/daemon.log`, {
fromBeginning: true,
});
daemonLog.on("line", (line) => core.info(line));
try {
core.debug('Waiting for Cachix daemon to exit...');
await exec.exec(cachixBin, ["daemon", "stop", "--socket", `${daemonDir}/daemon.sock`]);
core.debug("Waiting for Cachix daemon to exit...");
await exec.exec(cachixBin, [
"daemon",
"stop",
"--socket",
`${daemonDir}/daemon.sock`,
]);
} finally {
// Wait a bit for the logs to flush through
await new Promise((resolve) => setTimeout(resolve, 1000));
@ -247,8 +279,14 @@ async function upload() {
}
case PushMode.StoreScan: {
const preBuildPathsFile = core.getState('preBuildPathsFile');
await exec.exec(`${__dirname}/push-paths.sh`, [cachixBin, cachixArgs, name, preBuildPathsFile, pushFilter]);
const preBuildPathsFile = core.getState("preBuildPathsFile");
await exec.exec(`${__dirname}/push-paths.sh`, [
cachixBin,
cachixArgs,
name,
preBuildPathsFile,
pushFilter,
]);
break;
}
}
@ -257,15 +295,19 @@ async function upload() {
}
function pidFilePath(daemonDir: string): string {
return path.join(daemonDir, 'daemon.pid');
return path.join(daemonDir, "daemon.pid");
}
// Exec a command and return the stdout as a string.
async function execToVariable(command: string, args?: string[], options?: exec.ExecOptions): Promise<string> {
let res = '';
async function execToVariable(
command: string,
args?: string[],
options?: exec.ExecOptions,
): Promise<string> {
let res = "";
options = options ?? {};
options['listeners'] = {
options["listeners"] = {
stdout: (data: Buffer) => {
res += data.toString();
},
@ -290,7 +332,9 @@ async function execToVariable(command: string, args?: string[], options?: exec.E
// If the user has set NIX_CONF, we append our config to it.
async function registerPostBuildHook(cachixBin: string, daemonDir: string) {
const postBuildHookScriptPath = `${daemonDir}/post-build-hook.sh`;
await fs.writeFile(postBuildHookScriptPath, `
await fs.writeFile(
postBuildHookScriptPath,
`
#!/usr/bin/env bash
set -eu
@ -316,37 +360,41 @@ async function registerPostBuildHook(cachixBin: string, daemonDir: string) {
$OUT_PATHS
`,
// Make the post-build-hook executable
{ mode: 0o755 }
{ mode: 0o755 },
);
core.debug(`Wrote post-build-hook script to ${postBuildHookScriptPath}`);
const postBuildHookConfigPath = `${daemonDir}/nix.conf`;
await fs.writeFile(
postBuildHookConfigPath,
`post-build-hook = ${postBuildHookScriptPath}`
`post-build-hook = ${postBuildHookScriptPath}`,
);
core.debug(`Wrote post-build-hook nix config to ${postBuildHookConfigPath}`);
const existingNixConf = process.env['NIX_CONF'];
const existingNixConf = process.env["NIX_CONF"];
if (existingNixConf) {
core.exportVariable('NIX_CONF', `${existingNixConf}\npost-build-hook = ${postBuildHookScriptPath}`);
core.debug('Registered post-build-hook in NIX_CONF');
core.exportVariable(
"NIX_CONF",
`${existingNixConf}\npost-build-hook = ${postBuildHookScriptPath}`,
);
core.debug("Registered post-build-hook in NIX_CONF");
} else {
const existingUserConfEnv = process.env['NIX_USER_CONF_FILES'] ?? '';
let nixUserConfFilesEnv = '';
const existingUserConfEnv = process.env["NIX_USER_CONF_FILES"] ?? "";
let nixUserConfFilesEnv = "";
if (existingUserConfEnv) {
nixUserConfFilesEnv = postBuildHookConfigPath + ':' + existingUserConfEnv;
nixUserConfFilesEnv = postBuildHookConfigPath + ":" + existingUserConfEnv;
} else {
const userConfigFiles = getUserConfigFiles();
nixUserConfFilesEnv = [postBuildHookConfigPath, ...userConfigFiles].filter((x) => x !== '').join(':');
nixUserConfFilesEnv = [postBuildHookConfigPath, ...userConfigFiles]
.filter((x) => x !== "")
.join(":");
}
core.exportVariable(
'NIX_USER_CONF_FILES',
nixUserConfFilesEnv,
core.exportVariable("NIX_USER_CONF_FILES", nixUserConfFilesEnv);
core.debug(
`Registered post-build-hook in NIX_USER_CONF_FILES=${process.env["NIX_USER_CONF_FILES"]}`,
);
core.debug(`Registered post-build-hook in NIX_USER_CONF_FILES=${process.env['NIX_USER_CONF_FILES']}`);
}
}
@ -358,8 +406,11 @@ function getUserConfigFiles(): string[] {
// Get the user config directories.
function getUserConfigDirs(): string[] {
const xdgConfigHome = process.env['XDG_CONFIG_HOME'] ?? `${os.homedir()}/.config`;
const xdgConfigDirs = (process.env['XDG_CONFIG_DIRS'] ?? '/etc/xdg').split(':');
const xdgConfigHome =
process.env["XDG_CONFIG_HOME"] ?? `${os.homedir()}/.config`;
const xdgConfigDirs = (process.env["XDG_CONFIG_DIRS"] ?? "/etc/xdg").split(
":",
);
return [xdgConfigHome, ...xdgConfigDirs];
}
@ -368,27 +419,33 @@ async function isTrustedUser(): Promise<boolean> {
let user = os.userInfo().username;
core.debug(`Checking if user ${user} is trusted`);
let userGroups = await execToVariable('id', ['-Gn', user], { silent: true }).then((str) => str.trim().split(' '));
let userGroups = await execToVariable("id", ["-Gn", user], {
silent: true,
}).then((str) => str.trim().split(" "));
core.debug(`User ${user} is in groups ${userGroups}`);
let [trustedUsers, trustedGroups] = await fetchTrustedUsers().then(partitionUsersAndGroups);
let [trustedUsers, trustedGroups] = await fetchTrustedUsers().then(
partitionUsersAndGroups,
);
core.debug(`Trusted users: ${trustedUsers}`);
core.debug(`Trusted groups: ${trustedGroups}`);
// Chech if Nix is installed in single-user mode.
let isStoreWritable = await isWritable('/nix/store');
let isStoreWritable = await isWritable("/nix/store");
core.debug(`Is store writable: ${isStoreWritable}`);
let isTrustedUser =
isStoreWritable
|| trustedUsers.includes(user)
|| trustedGroups.some((group) => userGroups.includes(group));
isStoreWritable ||
trustedUsers.includes(user) ||
trustedGroups.some((group) => userGroups.includes(group));
core.debug(`User ${user} is trusted: ${isTrustedUser}`);
return isTrustedUser;
} catch (err) {
core.warning('Failed to determine if the user is trusted. Assuming untrusted user.');
core.warning(
"Failed to determine if the user is trusted. Assuming untrusted user.",
);
core.debug(`error: ${err}`);
return false;
}
@ -405,21 +462,21 @@ async function isWritable(path: string): Promise<boolean> {
async function fetchTrustedUsers(): Promise<string[]> {
try {
let conf = await execToVariable('nix', ['show-config'], { silent: true });
let conf = await execToVariable("nix", ["show-config"], { silent: true });
let match = conf.match(/trusted-users = (.+)/m);
return match?.length === 2 ? match[1].split(' ') : [];
return match?.length === 2 ? match[1].split(" ") : [];
} catch (error) {
core.warning('Failed to read the Nix configuration');
core.warning("Failed to read the Nix configuration");
return [];
}
}
function partitionUsersAndGroups(mixedUsers: string[]): [string[], string[]] {
let users: string[] = [];
let groups: string[] = []
let groups: string[] = [];
mixedUsers.forEach((item) => {
if (item.startsWith('@')) {
if (item.startsWith("@")) {
groups.push(item.slice(1));
} else {
users.push(item);
@ -430,22 +487,22 @@ function partitionUsersAndGroups(mixedUsers: string[]): [string[], string[]] {
}
function splitArgs(args: string): string[] {
return args.split(' ').filter((arg) => arg !== '');
return args.split(" ").filter((arg) => arg !== "");
}
const isPost = !!core.getState('isPost');
const isPost = !!core.getState("isPost");
// Main
try {
if (!isPost) {
// Publish a variable so that when the POST action runs, it can determine it should run the cleanup logic.
// This is necessary since we don't have a separate entry point.
core.saveState('isPost', 'true');
setup()
core.debug('Setup done');
core.saveState("isPost", "true");
setup();
core.debug("Setup done");
} else {
// Post
upload()
upload();
}
} catch (error) {
core.setFailed(`Action failed with error: ${error}`);

View file

@ -1,10 +1,13 @@
{ arg ? null }:
{
arg ? null,
}:
with import <nixpkgs> {};
with import <nixpkgs> { };
if arg == null
then abort "arg is not set"
else writeText "test-with-arg" ''
${toString builtins.currentTime}
${arg}
''
if arg == null then
abort "arg is not set"
else
writeText "test-with-arg" ''
${toString builtins.currentTime}
${arg}
''

View file

@ -1,13 +1,17 @@
# Realizes <num>> of derivations with size of <size>MB
{ size ? 1 # MB
, num ? 10 # count
, currentTime ? builtins.currentTime
{
size ? 1, # MB
num ? 10, # count
currentTime ? builtins.currentTime,
}:
with (import (fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixos-23.11") {});
with (import (fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixos-23.11") { });
let
drv = i: runCommand "${toString currentTime}-${toString i}" {} ''
dd if=/dev/zero of=$out bs=${toString size}MB count=1
'';
in lib.listToAttrs (builtins.map (i: lib.nameValuePair "drv${toString i}" (drv i)) (lib.range 1 num))
drv =
i:
runCommand "${toString currentTime}-${toString i}" { } ''
dd if=/dev/zero of=$out bs=${toString size}MB count=1
'';
in
lib.listToAttrs (builtins.map (i: lib.nameValuePair "drv${toString i}" (drv i)) (lib.range 1 num))

View file

@ -12,7 +12,7 @@
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2022", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
"target": "es2022" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
@ -26,9 +26,9 @@
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "node16", /* Specify what module code is generated. */
"module": "node16" /* Specify what module code is generated. */,
// "rootDir": "./", /* Specify the root folder within your source files. */
"moduleResolution": "node16", /* Specify how TypeScript looks up a file from a given module specifier. */
"moduleResolution": "node16" /* Specify how TypeScript looks up a file from a given module specifier. */,
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
@ -56,7 +56,7 @@
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
"outDir": "./lib", /* Specify an output folder for all emitted files. */
"outDir": "./lib" /* Specify an output folder for all emitted files. */,
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
@ -78,13 +78,13 @@
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
"noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
"strict": true /* Enable all strict type-checking options. */,
"noImplicitAny": true /* Enable error reporting for expressions and declarations with an implied 'any' type. */,
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
@ -105,7 +105,7 @@
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
},
"include": ["src/**/*"]
}