Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,20 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Extract runner version from src/aws.js
- name: Extract default runner version from action.yml
id: extract
run: |
version=$(grep -oE 'actions/runner/releases/download/v[0-9]+\.[0-9]+\.[0-9]+' src/aws.js | head -1 | sed 's|.*/v||')
# action.yml declares:
# runner-version:
# ...
# default: '2.333.1'
version=$(awk '/^ runner-version:/{found=1} found && /^ default:/{gsub(/[^0-9.]/, "", $2); print $2; exit}' action.yml)
if [ -z "$version" ]; then
echo "::error::Could not locate the pinned actions/runner version in src/aws.js"
echo "::error::Could not locate the default runner-version in action.yml"
exit 1
fi
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "Pinned actions/runner: v$version"
echo "Default actions/runner: v$version"
- name: HEAD check the Linux x64 release asset
env:
VERSION: ${{ steps.extract.outputs.version }}
Expand Down
9 changes: 9 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,15 @@ inputs:
IAM Role Name to attach to the created EC2 instance.
This requires additional permissions on the AWS role used to launch instances.
required: false
runner-version:
description: >-
Version of the actions/runner binary to download and register.
Must match a released tag from https://github.com/actions/runner/releases
(without the 'v' prefix). Defaults to the version tested with this action release.
Bumping this lets consumers pick up a newer runner (e.g. when GitHub gates
JS actions on a newer node runtime) without waiting for an action release.
required: false
default: '2.333.1'
aws-resource-tags:
description: >-
Tags to attach to the launched EC2 instance and volume.
Expand Down
66 changes: 56 additions & 10 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -87903,21 +87903,66 @@ async function resolveImageId(client) {
async function startEc2Instance(label, githubRegistrationToken) {
const client = ec2Client();

// User data scripts are run as the root user.
// Docker and git are necessary for GitHub runner and should be pre-installed on the AMI.
const userData = [
// User-data runs as root. We install dependencies + create a dedicated
// 'runner' user, then drop to that user for every subsequent step via
// a sudo-heredoc. The runner never needs root and never gets it; the
// old RUNNER_ALLOW_RUNASROOT=1 escape hatch is gone.
//
// Runner version is read from config so consumers can override without
// waiting for an action release (see #10 for the motivation chain).
//
// The tarball is SHA-256 verified against actions/runner's published
// checksum before extraction — same defense-in-depth pattern the
// provider repo uses for its Go / Terraform downloads.
//
// --ephemeral tells GitHub to auto-deregister the runner after it
// completes a single job; the stop-runner step's explicit removeRunner()
// call becomes belt-and-braces rather than the primary deregister path.
const runnerVersion = config.input.runnerVersion;
const owner = config.githubContext.owner;
const repo = config.githubContext.repo;
const userDataScript = [
'#!/bin/bash',
'set -euo pipefail',
'',
'# Root-required setup.',
'mount -o remount,size=1G /tmp',
'yum install -y libicu make',
'mkdir actions-runner && cd actions-runner',
'case $(uname -m) in aarch64) ARCH="arm64" ;; amd64|x86_64) ARCH="x64" ;; esac && export RUNNER_ARCH=${ARCH}',
'curl -O -L https://github.com/actions/runner/releases/download/v2.333.1/actions-runner-linux-${RUNNER_ARCH}-2.333.1.tar.gz',
'tar xzf ./actions-runner-linux-${RUNNER_ARCH}-2.333.1.tar.gz',
'export RUNNER_ALLOW_RUNASROOT=1',
'yum install -y libicu make sudo',
'',
'# Create the non-root runner user.',
'if ! id runner >/dev/null 2>&1; then',
' useradd -m -s /bin/bash runner',
'fi',
'',
'# Drop to the runner user for download + configure + run.',
"sudo -u runner -H bash <<'RUNNER_BOOTSTRAP'",
'set -euo pipefail',
'cd "$HOME"',
'mkdir -p actions-runner && cd actions-runner',
'',
'case "$(uname -m)" in',
' aarch64) RUNNER_ARCH="arm64" ;;',
' amd64|x86_64) RUNNER_ARCH="x64" ;;',
' *) echo "unsupported arch: $(uname -m)" >&2; exit 1 ;;',
'esac',
'',
`RUNNER_VERSION="${runnerVersion}"`,
'TARBALL="actions-runner-linux-${RUNNER_ARCH}-${RUNNER_VERSION}.tar.gz"',
'BASE="https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}"',
'',
'curl -fsSLo "$TARBALL" "$BASE/$TARBALL"',
'expected="$(curl -fsSL "$BASE/$TARBALL.sha256" | awk \'{print $1}\')"',
'echo "$expected $TARBALL" | sha256sum -c -',
'',
'tar xzf "$TARBALL"',
'',
'export DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1',
`./config.sh --url https://github.com/${config.githubContext.owner}/${config.githubContext.repo} --token ${githubRegistrationToken} --labels ${label}`,
`./config.sh --url "https://github.com/${owner}/${repo}" --token "${githubRegistrationToken}" --labels "${label}" --ephemeral --unattended --disableupdate`,
'./run.sh',
'RUNNER_BOOTSTRAP',
'',
];
const userData = userDataScript;

config.input.ec2ImageId = await resolveImageId(client);

Expand Down Expand Up @@ -88003,6 +88048,7 @@ class Config {
label: core.getInput('label'),
ec2InstanceId: core.getInput('ec2-instance-id'),
iamRoleName: core.getInput('iam-role-name'),
runnerVersion: core.getInput('runner-version') || '2.333.1',
};

const tags = JSON.parse(core.getInput('aws-resource-tags'));
Expand Down
65 changes: 55 additions & 10 deletions src/aws.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,21 +57,66 @@ async function resolveImageId(client) {
async function startEc2Instance(label, githubRegistrationToken) {
const client = ec2Client();

// User data scripts are run as the root user.
// Docker and git are necessary for GitHub runner and should be pre-installed on the AMI.
const userData = [
// User-data runs as root. We install dependencies + create a dedicated
// 'runner' user, then drop to that user for every subsequent step via
// a sudo-heredoc. The runner never needs root and never gets it; the
// old RUNNER_ALLOW_RUNASROOT=1 escape hatch is gone.
//
// Runner version is read from config so consumers can override without
// waiting for an action release (see #10 for the motivation chain).
//
// The tarball is SHA-256 verified against actions/runner's published
// checksum before extraction — same defense-in-depth pattern the
// provider repo uses for its Go / Terraform downloads.
//
// --ephemeral tells GitHub to auto-deregister the runner after it
// completes a single job; the stop-runner step's explicit removeRunner()
// call becomes belt-and-braces rather than the primary deregister path.
const runnerVersion = config.input.runnerVersion;
const owner = config.githubContext.owner;
const repo = config.githubContext.repo;
const userDataScript = [
'#!/bin/bash',
'set -euo pipefail',
'',
'# Root-required setup.',
'mount -o remount,size=1G /tmp',
'yum install -y libicu make',
'mkdir actions-runner && cd actions-runner',
'case $(uname -m) in aarch64) ARCH="arm64" ;; amd64|x86_64) ARCH="x64" ;; esac && export RUNNER_ARCH=${ARCH}',
'curl -O -L https://github.com/actions/runner/releases/download/v2.333.1/actions-runner-linux-${RUNNER_ARCH}-2.333.1.tar.gz',
'tar xzf ./actions-runner-linux-${RUNNER_ARCH}-2.333.1.tar.gz',
'export RUNNER_ALLOW_RUNASROOT=1',
'yum install -y libicu make sudo',
'',
'# Create the non-root runner user.',
'if ! id runner >/dev/null 2>&1; then',
' useradd -m -s /bin/bash runner',
'fi',
'',
'# Drop to the runner user for download + configure + run.',
"sudo -u runner -H bash <<'RUNNER_BOOTSTRAP'",
'set -euo pipefail',
'cd "$HOME"',
'mkdir -p actions-runner && cd actions-runner',
'',
'case "$(uname -m)" in',
' aarch64) RUNNER_ARCH="arm64" ;;',
' amd64|x86_64) RUNNER_ARCH="x64" ;;',
' *) echo "unsupported arch: $(uname -m)" >&2; exit 1 ;;',
'esac',
'',
`RUNNER_VERSION="${runnerVersion}"`,
'TARBALL="actions-runner-linux-${RUNNER_ARCH}-${RUNNER_VERSION}.tar.gz"',
'BASE="https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}"',
'',
'curl -fsSLo "$TARBALL" "$BASE/$TARBALL"',
'expected="$(curl -fsSL "$BASE/$TARBALL.sha256" | awk \'{print $1}\')"',
'echo "$expected $TARBALL" | sha256sum -c -',
'',
'tar xzf "$TARBALL"',
'',
'export DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1',
`./config.sh --url https://github.com/${config.githubContext.owner}/${config.githubContext.repo} --token ${githubRegistrationToken} --labels ${label}`,
`./config.sh --url "https://github.com/${owner}/${repo}" --token "${githubRegistrationToken}" --labels "${label}" --ephemeral --unattended --disableupdate`,
'./run.sh',
'RUNNER_BOOTSTRAP',
'',
];
const userData = userDataScript;

config.input.ec2ImageId = await resolveImageId(client);

Expand Down
1 change: 1 addition & 0 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class Config {
label: core.getInput('label'),
ec2InstanceId: core.getInput('ec2-instance-id'),
iamRoleName: core.getInput('iam-role-name'),
runnerVersion: core.getInput('runner-version') || '2.333.1',
};

const tags = JSON.parse(core.getInput('aws-resource-tags'));
Expand Down
12 changes: 12 additions & 0 deletions tests/config.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,18 @@ describe('Config — mode validation', () => {
});
});

describe('Config — runner-version input', () => {
test('defaults to 2.333.1 when input is unset', () => {
const config = loadConfig(startModeInputs);
expect(config.input.runnerVersion).toBe('2.333.1');
});

test('honors an explicit runner-version override', () => {
const config = loadConfig({ ...startModeInputs, 'runner-version': '2.340.0' });
expect(config.input.runnerVersion).toBe('2.340.0');
});
});

describe('Config — generateUniqueLabel', () => {
test('returns a 5-character alphanumeric string', () => {
const config = loadConfig(startModeInputs);
Expand Down
Loading