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:
parent
da5c84033f
commit
4800fda0c4
11 changed files with 330 additions and 257 deletions
19
.github/dependabot.yml
vendored
19
.github/dependabot.yml
vendored
|
@ -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"
|
||||
|
|
150
.github/workflows/test.yml
vendored
150
.github/workflows/test.yml
vendored
|
@ -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
|
||||
|
|
|
@ -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
|
||||
```
|
||||
|
|
42
action.yml
42
action.yml
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -3,4 +3,3 @@ inputs:
|
|||
url: github:NixOS/nixpkgs/nixpkgs-unstable
|
||||
permittedInsecurePackages:
|
||||
- nodejs-16.20.2
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
289
src/main.ts
289
src/main.ts
|
@ -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}`);
|
||||
|
|
|
@ -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}
|
||||
''
|
||||
|
|
20
test.nix
20
test.nix
|
@ -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))
|
||||
|
|
|
@ -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/**/*"]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue