Skip to content

Commit c1c31b8

Browse files
authored
feat: pluggable bootstrap — pre-runner-script + user-data-template (unfork Ubuntu et al.) (#58)
"Fork and replace the userData array" was a maintenance sentence nobody should serve. Keep the built-in yum bootstrap as the only supported path, but make the bootstrap pluggable so any distro/extra-setup need is a template away. - pre-runner-script (the 80% case): a root snippet injected into the built-in bootstrap before runner configuration (install docker, mount caches, certs). Runs under set -euo pipefail with its own phase tag, so a failure surfaces as failed:pre-runner-script in the diagnostics flow. - user-data-template (full control): replaces the bootstrap entirely. Repo- relative file path or inline string. The action substitutes documented placeholders — {{RUNNER_VERSION}}, {{RUNNER_CHECKSUM_X64/ARM64}}, {{REGISTRATION_TOKEN}}, {{REPO_URL}}, {{LABEL}}, {{TTL_MINUTES}} — and submits the result. Unknown {{...}} tokens error at render (typo protection). - The two inputs are mutually exclusive (config error). Rendered payload is size-guarded against the EC2 16 KB limit. Built-in path with neither input is byte-identical to today (regression). - examples/user-data/ubuntu.sh.tpl (community-maintained) + examples/user-data/ README stating the support boundary — converts the perennial Ubuntu ask from "fork" to "copy one file". Tests: renderUserDataTemplate (substitution, repeats, unknown-token error, token rendered), assertUserDataSize (limit + multibyte), pre-runner-script injection + default-absent, config mutual-exclusion (203 tests total). README support- boundary section replaces the "fork and replace" paragraph; action.yml inputs. Closes #46 Signed-off-by: kurok <22548029+kurok@users.noreply.github.com>
1 parent 0c422af commit c1c31b8

11 files changed

Lines changed: 492 additions & 5 deletions

File tree

README.md

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ And all this automatically as a part of your GitHub Actions workflow.
1212
>
1313
> The bootstrap script that this action injects as EC2 `user-data` is hardcoded to use `yum`, `useradd`, `sudo`, `bash`, and a `tmpfs` `/tmp`. That means the AMI you pass via `ec2-image-id` **must** be a yum-based distribution — Amazon Linux 2023 (the tested baseline), Amazon Linux 2, or a RHEL-family image (RHEL / CentOS Stream / Rocky / Alma) whose `/tmp` is mounted as tmpfs.
1414
>
15-
> **Debian, Ubuntu, Alpine, and any other non-yum distributions are not supported.** If you launch this action against such an AMI, the EC2 instance will boot but the runner bootstrap will fail. The action now surfaces this quickly: it fails fast naming the failing step and prints the instance's console output (see [Troubleshooting a failed start](#troubleshooting-a-failed-start)). Cross-distro support is not on the roadmap — if you need it, fork and replace the `userData` bootstrap in `src/aws.js`.
15+
> **The built-in bootstrap targets yum-based Linux (Amazon Linux 2023) only.** Launching the built-in bootstrap against a non-yum AMI (Debian, Ubuntu, Alpine, …) fails — the action surfaces this fast, naming the failing step and printing the console output (see [Troubleshooting a failed start](#troubleshooting-a-failed-start)). You **don't need to fork** for other distros or extra setup: use [`pre-runner-script`](#custom-bootstrap-pre-runner-script--user-data-template) to inject steps into the built-in bootstrap, or [`user-data-template`](#custom-bootstrap-pre-runner-script--user-data-template) to replace it entirely (an Ubuntu example ships in [`examples/user-data/`](examples/user-data/)). Custom templates are unsupported by design — the boundary *is* the feature.
1616
1717
![GitHub Actions self-hosted EC2 runner](docs/images/github-actions-runner.gif)
1818

@@ -323,6 +323,8 @@ Now you're ready to go!
323323
| `eip-allocation-id` | Optional. Used only with the `start` mode. | Allocation Id of an Elastic IP to associate with the runner instance once it is running. |
324324
| `runner-version` | Optional. Used only with the `start` mode. | Version of the `actions/runner` binary to download and register (default `2.335.1`). <br><br> Must have a matching entry in `src/runner-checksums.js`; the action verifies the downloaded tarball's SHA-256 against that table before extraction. |
325325
| `architecture` | Optional. Used only with the `start` mode. | Runner CPU architecture: `x64` (default) or `arm64` (Graviton). Must match the AMI (validated at start). All types in an `ec2-instance-type` fallback list must share this arch. See [Running on Graviton (arm64)](#running-on-graviton-arm64). |
326+
| `pre-runner-script` | Optional. Used only with the `start` mode. | Shell snippet run as root by the built-in bootstrap **before** runner config (install docker, mount caches, add certs). Fail-fast, tagged `failed:pre-runner-script`. Mutually exclusive with `user-data-template`. See [Custom bootstrap](#custom-bootstrap-pre-runner-script--user-data-template). |
327+
| `user-data-template` | Optional. Used only with the `start` mode. | Full bootstrap override — a repo-relative file path or inline string with `{{PLACEHOLDERS}}`. Replaces the built-in bootstrap (unsupported by design). Mutually exclusive with `pre-runner-script`. See [Custom bootstrap](#custom-bootstrap-pre-runner-script--user-data-template). |
326328
| `http-tokens` | Optional. Used only with the `start` mode. | Instance Metadata Service (IMDS) token mode (default `required`). <br><br> - `required` — IMDSv2 only; mitigates SSRF-style credential theft. <br> - `optional` — also allows IMDSv1; set only if a workload on the runner needs it. |
327329
| `encrypt-ebs` | Optional. Used only with the `start` mode. | When `true`, the root EBS volume is created with SSE-EBS encryption using the account's default AWS-managed key (default `false`). Volume size / type / IOPS are preserved from the AMI unless overridden by the `volume-*` inputs below. |
328330
| `volume-size` | Optional. Used only with the `start` mode. | Root EBS volume size in GiB. Omitted = AMI default (Amazon Linux 2023: 8 GiB). Must be ≥ the AMI snapshot size. See [Disk space for Docker workloads](#disk-space-for-docker-workloads). |
@@ -524,6 +526,51 @@ A single `subnet-id` + `ec2-instance-type` means a single point of failure: when
524526

525527
The `instance-type-used` and `subnet-id-used` outputs report what actually launched. Single values keep the original single-attempt behavior.
526528

529+
## Custom bootstrap (`pre-runner-script` / `user-data-template`)
530+
531+
Need an extra package or a different distro? Two escape hatches mean you never have to fork.
532+
533+
### `pre-runner-script` — the 80% case
534+
535+
Inject shell into the built-in (supported) bootstrap, run as root before the runner registers:
536+
537+
```yml
538+
- uses: namecheap/ec2-github-runner@v3
539+
with:
540+
mode: start
541+
# ... other inputs ...
542+
pre-runner-script: |
543+
yum install -y docker
544+
systemctl start docker
545+
```
546+
547+
It runs under `set -euo pipefail` and a failure is tagged `failed:pre-runner-script`, so it shows up in the [fast-fail diagnostics](#troubleshooting-a-failed-start) like any other phase.
548+
549+
### `user-data-template` — full control
550+
551+
Replace the bootstrap entirely with your own script (repo-relative path or inline string). The action substitutes documented placeholders and submits the result:
552+
553+
```yml
554+
- uses: namecheap/ec2-github-runner@v3
555+
with:
556+
mode: start
557+
# ... other inputs ...
558+
user-data-template: ./examples/user-data/ubuntu.sh.tpl
559+
```
560+
561+
| Placeholder | Value |
562+
| --- | --- |
563+
| `{{RUNNER_VERSION}}` | Pinned `actions/runner` version |
564+
| `{{RUNNER_CHECKSUM_X64}}` / `{{RUNNER_CHECKSUM_ARM64}}` | Tarball SHA-256 per arch |
565+
| `{{REGISTRATION_TOKEN}}` | Ephemeral registration token (secret) |
566+
| `{{REPO_URL}}` | `https://github.com/<owner>/<repo>` |
567+
| `{{LABEL}}` | The unique runner label |
568+
| `{{TTL_MINUTES}}` | `max-lifetime-minutes` (`0` = disabled) |
569+
570+
Unknown `{{...}}` tokens fail the run (typo protection); the rendered payload must stay under the EC2 16 KB limit.
571+
572+
**Support boundary:** the built-in yum bootstrap is the only *supported* path. With a custom template, the action renders your placeholders and gives you the diagnostics tooling — but the script is yours. See [`examples/user-data/`](examples/user-data/) (an Ubuntu 24.04 template ships there as a community-maintained starting point). The two inputs are mutually exclusive.
573+
527574
## Running on Graviton (arm64)
528575

529576
Graviton instances (c7g/m7g/r7g/…) deliver ~20–40% better price/performance for the compile/test workloads CI runs, and Go/Rust/Node/Java toolchains are all arm64-native. Set `architecture: arm64` and point at an arm64 AMI:

action.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,26 @@ inputs:
118118
change is needed to run on Graviton.
119119
required: false
120120
default: 'x64'
121+
pre-runner-script:
122+
description: >-
123+
Used only with the 'start' mode. A shell snippet run as root by the
124+
built-in bootstrap BEFORE runner configuration (e.g. install docker,
125+
mount caches, add corporate certs). Runs under set -euo pipefail; a
126+
failure is tagged 'failed:pre-runner-script'. Mutually exclusive with
127+
user-data-template.
128+
required: false
129+
user-data-template:
130+
description: >-
131+
Used only with the 'start' mode. Full user-data bootstrap override —
132+
a repo-relative file path or an inline template string. The action
133+
substitutes the documented placeholders ({{RUNNER_VERSION}},
134+
{{RUNNER_CHECKSUM_X64}}, {{RUNNER_CHECKSUM_ARM64}},
135+
{{REGISTRATION_TOKEN}}, {{REPO_URL}}, {{LABEL}}, {{TTL_MINUTES}}) and
136+
submits the result as-is. The template MUST register the runner.
137+
Custom templates are unsupported (the built-in bootstrap is the
138+
supported path); see examples/user-data/. Mutually exclusive with
139+
pre-runner-script.
140+
required: false
121141
encrypt-ebs:
122142
description: >-
123143
When 'true', the root EBS volume is created with SSE-EBS

dist/index.js

Lines changed: 127 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104659,11 +104659,13 @@ const {
104659104659
AssociateAddressCommand,
104660104660
waitUntilInstanceRunning,
104661104661
} = __nccwpck_require__(5193);
104662+
const fs = __nccwpck_require__(9896);
104662104663
const core = __nccwpck_require__(7484);
104663104664
const config = __nccwpck_require__(1283);
104664104665
const log = __nccwpck_require__(7223);
104665104666
const { withRetry } = __nccwpck_require__(6759);
104666104667
const { sortByCreationDate, parseCsv } = __nccwpck_require__(5804);
104668+
const { renderUserDataTemplate, assertUserDataSize } = __nccwpck_require__(9275);
104667104669
const checksums = __nccwpck_require__(2874);
104668104670

104669104671
// RunInstances error classification for the capacity-fallback chain.
@@ -104994,7 +104996,21 @@ function buildRootDeviceMapping(image, opts = {}) {
104994104996
// registration timeout. Tagging is best-effort: it needs
104995104997
// `ec2:CreateTags` on the instance profile and degrades to
104996104998
// timeout-only detection when absent (every write is `|| true`).
104997-
function buildUserData({ runnerVersion, owner, repo, label, githubRegistrationToken, shaX64, shaArm64, maxLifetimeMinutes }) {
104999+
function buildUserData({ runnerVersion, owner, repo, label, githubRegistrationToken, shaX64, shaArm64, maxLifetimeMinutes, preRunnerScript }) {
105000+
// User-supplied pre-runner-script (#46): injected verbatim into the outer
105001+
// (root) shell before runner configuration, under the same set -euo
105002+
// pipefail + ERR trap, tagged as its own phase so a failure surfaces as
105003+
// failed:pre-runner-script.
105004+
const preRunnerLines = preRunnerScript && preRunnerScript.trim()
105005+
? [
105006+
'# User-supplied pre-runner-script (runs as root before runner config).',
105007+
'GH_RUNNER_STEP=pre-runner-script',
105008+
'gh_runner_phone_home pre-runner-script',
105009+
...preRunnerScript.replace(/\r\n/g, '\n').split('\n'),
105010+
'',
105011+
]
105012+
: [];
105013+
104998105014
// TTL self-destruct (#42): arm a shutdown timer as an absolute upper
104999105015
// bound on instance lifetime. Combined with
105000105016
// InstanceInitiatedShutdownBehavior: terminate on RunInstances (set in
@@ -105053,6 +105069,7 @@ function buildUserData({ runnerVersion, owner, repo, label, githubRegistrationTo
105053105069
' useradd -m -s /bin/bash runner',
105054105070
'fi',
105055105071
'',
105072+
...preRunnerLines,
105056105073
'# The runner-user shell owns the download/configure/register phases and',
105057105074
'# reports them itself; drop the outer ERR trap so it does not overwrite',
105058105075
'# the inner shell\'s more specific failed:<step> tag.',
@@ -105127,6 +105144,15 @@ function buildTagSpecifications(label, startedAtIso) {
105127105144
];
105128105145
}
105129105146

105147+
// Resolve the user-data-template input: a repo-relative path is read from
105148+
// disk; anything else is treated as an inline template string.
105149+
function resolveUserDataTemplate(templateInput) {
105150+
if (fs.existsSync(templateInput)) {
105151+
return fs.readFileSync(templateInput, 'utf8');
105152+
}
105153+
return templateInput;
105154+
}
105155+
105130105156
async function startEc2Instance(label, githubRegistrationToken) {
105131105157
const client = ec2Client();
105132105158

@@ -105144,7 +105170,30 @@ async function startEc2Instance(label, githubRegistrationToken) {
105144105170
}
105145105171

105146105172
const maxLifetimeMinutes = config.input.maxLifetimeMinutes;
105147-
const userData = buildUserData({ runnerVersion, owner, repo, label, githubRegistrationToken, shaX64, shaArm64, maxLifetimeMinutes });
105173+
105174+
// Bootstrap source (#46): a full user-data-template override renders the
105175+
// documented placeholders; otherwise the built-in yum bootstrap is used,
105176+
// optionally with an injected pre-runner-script. Either way the rendered
105177+
// payload is size-checked against the EC2 16 KB limit.
105178+
let userData;
105179+
if (config.input.userDataTemplate) {
105180+
const template = resolveUserDataTemplate(config.input.userDataTemplate);
105181+
userData = renderUserDataTemplate(template, {
105182+
RUNNER_VERSION: runnerVersion,
105183+
RUNNER_CHECKSUM_X64: shaX64,
105184+
RUNNER_CHECKSUM_ARM64: shaArm64,
105185+
REGISTRATION_TOKEN: githubRegistrationToken,
105186+
REPO_URL: `https://github.com/${owner}/${repo}`,
105187+
LABEL: label,
105188+
TTL_MINUTES: maxLifetimeMinutes,
105189+
});
105190+
} else {
105191+
userData = buildUserData({
105192+
runnerVersion, owner, repo, label, githubRegistrationToken, shaX64, shaArm64, maxLifetimeMinutes,
105193+
preRunnerScript: config.input.preRunnerScript,
105194+
});
105195+
}
105196+
assertUserDataSize(userData);
105148105197

105149105198
const resolved = await resolveImage(client);
105150105199
config.input.ec2ImageId = resolved.id;
@@ -105682,6 +105731,8 @@ class Config {
105682105731
ec2InstanceIds: core.getInput('ec2-instance-ids') ? JSON.parse(core.getInput('ec2-instance-ids')) : null,
105683105732
count: core.getInput('count') || '1',
105684105733
allowPartial: core.getInput('allow-partial') || 'false',
105734+
preRunnerScript: core.getInput('pre-runner-script'),
105735+
userDataTemplate: core.getInput('user-data-template'),
105685105736
iamRoleName: core.getInput('iam-role-name'),
105686105737
runnerVersion: core.getInput('runner-version') || '2.335.1',
105687105738
architecture: core.getInput('architecture') || 'x64',
@@ -105735,6 +105786,7 @@ class Config {
105735105786
this.validateMarketInputs();
105736105787
this.validateArchitectureInputs();
105737105788
this.validateCountInput();
105789+
this.validateBootstrapInputs();
105738105790
} else if (this.input.mode === 'stop') {
105739105791
// A stop needs the shared label plus at least one instance id — either
105740105792
// the compat scalar or the JSON array from a batched start.
@@ -105813,6 +105865,14 @@ class Config {
105813105865
}
105814105866
}
105815105867

105868+
// The two bootstrap-extension inputs are mutually exclusive: pre-runner-
105869+
// script augments the built-in bootstrap, user-data-template replaces it.
105870+
validateBootstrapInputs() {
105871+
if (this.input.userDataTemplate && this.input.preRunnerScript) {
105872+
throw new Error(`'user-data-template' and 'pre-runner-script' are mutually exclusive`);
105873+
}
105874+
}
105875+
105816105876
// Validate the multi-runner batch size.
105817105877
validateCountInput() {
105818105878
if (!(/^[0-9]+$/.test(this.input.count) && Number(this.input.count) >= 1)) {
@@ -106159,6 +106219,71 @@ module.exports = {
106159106219
};
106160106220

106161106221

106222+
/***/ }),
106223+
106224+
/***/ 9275:
106225+
/***/ ((module) => {
106226+
106227+
// User-data template rendering for the `user-data-template` input (full
106228+
// bootstrap override). The action renders a fixed set of placeholders and
106229+
// submits the result verbatim; the script itself is the user's business.
106230+
// Pure functions — no I/O — so rendering and validation are unit-testable.
106231+
106232+
// EC2 caps user-data at 16 KB (raw, before base64). Guard against it up
106233+
// front with a clear error rather than a cryptic RunInstances rejection.
106234+
const MAX_USER_DATA_BYTES = 16 * 1024;
106235+
106236+
// The documented placeholders a template may reference.
106237+
const PLACEHOLDERS = [
106238+
'RUNNER_VERSION',
106239+
'RUNNER_CHECKSUM_X64',
106240+
'RUNNER_CHECKSUM_ARM64',
106241+
'REGISTRATION_TOKEN',
106242+
'REPO_URL',
106243+
'LABEL',
106244+
'TTL_MINUTES',
106245+
];
106246+
106247+
// Substitute every documented {{PLACEHOLDER}} with its value, then fail if
106248+
// any unknown {{TOKEN}} remains (catches typos before boot). Unused known
106249+
// placeholders are fine.
106250+
function renderUserDataTemplate(template, vars) {
106251+
let out = template;
106252+
for (const key of PLACEHOLDERS) {
106253+
const value = vars[key] != null ? String(vars[key]) : '';
106254+
out = out.split(`{{${key}}}`).join(value);
106255+
}
106256+
const unknown = [...out.matchAll(/\{\{\s*([A-Za-z0-9_]+)\s*\}\}/g)].map((m) => m[1]);
106257+
if (unknown.length > 0) {
106258+
const uniq = [...new Set(unknown)];
106259+
throw new Error(
106260+
`Unknown placeholder(s) in user-data-template: ${uniq.join(', ')}. ` +
106261+
`Supported placeholders: ${PLACEHOLDERS.join(', ')}`,
106262+
);
106263+
}
106264+
return out;
106265+
}
106266+
106267+
// Throw if the rendered user-data exceeds the EC2 16 KB limit.
106268+
function assertUserDataSize(userData) {
106269+
const bytes = Buffer.byteLength(userData, 'utf8');
106270+
if (bytes > MAX_USER_DATA_BYTES) {
106271+
throw new Error(
106272+
`Rendered user-data is ${bytes} bytes, over the EC2 limit of ${MAX_USER_DATA_BYTES} bytes. ` +
106273+
'Trim the pre-runner-script / user-data-template (fetch large payloads at runtime instead).',
106274+
);
106275+
}
106276+
return userData;
106277+
}
106278+
106279+
module.exports = {
106280+
renderUserDataTemplate,
106281+
assertUserDataSize,
106282+
MAX_USER_DATA_BYTES,
106283+
PLACEHOLDERS,
106284+
};
106285+
106286+
106162106287
/***/ }),
106163106288

106164106289
/***/ 5804:

examples/user-data/README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Custom user-data templates
2+
3+
> **Support boundary.** The built-in **yum-based** bootstrap (Amazon Linux 2023) is the only *supported* path. Templates in this directory are **community-maintained starting points**. When you supply `user-data-template`, the action renders your placeholders and gives you the same diagnostics tooling (bootstrap phone-home tags, console capture, cleanup) — but the script is yours to maintain. Bugs in a custom bootstrap are not action bugs.
4+
5+
## Using a template
6+
7+
```yml
8+
- uses: namecheap/ec2-github-runner@v3
9+
with:
10+
mode: start
11+
github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
12+
ec2-image-id: ami-ubuntu-... # an Ubuntu AMI
13+
ec2-instance-type: c7i.4xlarge
14+
subnet-id: subnet-123
15+
security-group-id: sg-123
16+
user-data-template: ./examples/user-data/ubuntu.sh.tpl
17+
```
18+
19+
`user-data-template` accepts a **repo-relative file path** or an **inline string**.
20+
21+
## Placeholders
22+
23+
The action substitutes these before launch (unknown `{{...}}` tokens fail the run so typos are caught early):
24+
25+
| Placeholder | Value |
26+
| --- | --- |
27+
| `{{RUNNER_VERSION}}` | Pinned `actions/runner` version |
28+
| `{{RUNNER_CHECKSUM_X64}}` | SHA-256 of the linux-x64 tarball |
29+
| `{{RUNNER_CHECKSUM_ARM64}}` | SHA-256 of the linux-arm64 tarball |
30+
| `{{REGISTRATION_TOKEN}}` | Ephemeral runner registration token (secret — never logged) |
31+
| `{{REPO_URL}}` | `https://github.com/<owner>/<repo>` |
32+
| `{{LABEL}}` | The unique runner label |
33+
| `{{TTL_MINUTES}}` | `max-lifetime-minutes` (`0` = disabled) |
34+
35+
**Contract:** the template must register the runner (`config.sh … --ephemeral --unattended` then `run.sh`). Verify the tarball checksum. Everything else is your distro's business. Rendered user-data must stay under the EC2 16 KB limit — fetch large payloads at runtime.
36+
37+
## Templates here
38+
39+
- [`ubuntu.sh.tpl`](ubuntu.sh.tpl) — Ubuntu 24.04 (community-maintained).
40+
41+
For a one-package tweak (e.g. install docker) you usually don't need a full template — use `pre-runner-script` instead, which keeps the supported bootstrap.

examples/user-data/ubuntu.sh.tpl

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#!/bin/bash
2+
# Community-maintained user-data-template for Ubuntu (24.04 tested).
3+
#
4+
# UNSUPPORTED: the built-in yum bootstrap is the only supported path. This
5+
# template is a starting point so Ubuntu users copy one file instead of
6+
# forking. The action substitutes the {{PLACEHOLDERS}} before launch; keep
7+
# them intact. Pass it via: user-data-template: ./examples/user-data/ubuntu.sh.tpl
8+
set -euo pipefail
9+
10+
# TTL self-destruct (max-lifetime-minutes). Requires the action's
11+
# InstanceInitiatedShutdownBehavior=terminate, which it sets when TTL > 0.
12+
if [ "{{TTL_MINUTES}}" != "0" ]; then
13+
shutdown -h +{{TTL_MINUTES}} || true
14+
fi
15+
16+
export DEBIAN_FRONTEND=noninteractive
17+
apt-get update -y
18+
apt-get install -y curl tar libicu-dev sudo
19+
20+
# Non-root runner user (idempotent).
21+
if ! id runner >/dev/null 2>&1; then
22+
useradd -m -s /bin/bash runner
23+
fi
24+
25+
sudo -u runner -H bash <<'RUNNER_BOOTSTRAP'
26+
set -euo pipefail
27+
cd "$HOME"
28+
mkdir -p actions-runner && cd actions-runner
29+
30+
case "$(uname -m)" in
31+
aarch64) RUNNER_ARCH="arm64"; EXPECTED_SHA="{{RUNNER_CHECKSUM_ARM64}}" ;;
32+
amd64|x86_64) RUNNER_ARCH="x64"; EXPECTED_SHA="{{RUNNER_CHECKSUM_X64}}" ;;
33+
*) echo "unsupported arch: $(uname -m)" >&2; exit 1 ;;
34+
esac
35+
36+
RUNNER_VERSION="{{RUNNER_VERSION}}"
37+
TARBALL="actions-runner-linux-${RUNNER_ARCH}-${RUNNER_VERSION}.tar.gz"
38+
curl -fsSLo "$TARBALL" "https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/${TARBALL}"
39+
echo "$EXPECTED_SHA $TARBALL" | sha256sum -c -
40+
tar xzf "$TARBALL"
41+
42+
export DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
43+
./config.sh --url "{{REPO_URL}}" --token "{{REGISTRATION_TOKEN}}" --labels "{{LABEL}}" --ephemeral --unattended --disableupdate
44+
./run.sh
45+
RUNNER_BOOTSTRAP

0 commit comments

Comments
 (0)