@le-space/rootfs 0.11.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/package.json +1 -1
- package/reference/github-runner/README.md +23 -0
- package/reference/github-runner/contract.json +34 -0
- package/reference/github-runner/rootfs/Dockerfile.rootfs +16 -0
- package/reference/github-runner/rootfs/build-rootfs-image.sh +104 -0
- package/reference/github-runner/rootfs/forget-self.mjs +69 -0
- package/reference/github-runner/rootfs/github-runner-finish +31 -0
- package/reference/github-runner/rootfs/github-runner-start +29 -0
- package/reference/github-runner/rootfs/github-runner-ttl.service +6 -0
- package/reference/github-runner/rootfs/github-runner-ttl.timer +10 -0
- package/reference/github-runner/rootfs/github-runner.service +17 -0
- package/reference/uc-go-peer/rootfs/__pycache__/uc-go-peer-autotls-refresh.cpython-312.pyc +0 -0
- package/reference/uc-go-peer/rootfs/__pycache__/uc-go-peer-describe.cpython-312.pyc +0 -0
package/README.md
CHANGED
|
@@ -13,6 +13,8 @@ Shared rootfs contract parsing, reference profile assets, and build helpers.
|
|
|
13
13
|
ports and consumer env expectations
|
|
14
14
|
- added an ephemeral `playwright-runner` profile with Node.js 24, exact
|
|
15
15
|
Playwright/Chromium `1.61.1`, authenticated WSS, bounded logs, and a guest TTL
|
|
16
|
+
- added an ephemeral `github-runner` profile: a one-job GitHub Actions runner
|
|
17
|
+
that forgets its own INSTANCE after the job
|
|
16
18
|
|
|
17
19
|
## Reference assets
|
|
18
20
|
|
|
@@ -26,3 +28,5 @@ Current shared reference profiles live under:
|
|
|
26
28
|
- `reference/ucan-store/rootfs/*`
|
|
27
29
|
- `reference/playwright-runner/contract.json`
|
|
28
30
|
- `reference/playwright-runner/rootfs/*`
|
|
31
|
+
- `reference/github-runner/contract.json`
|
|
32
|
+
- `reference/github-runner/rootfs/*`
|
package/package.json
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# GitHub runner RootFS
|
|
2
|
+
|
|
3
|
+
Ubuntu 24.04 (as GitHub's own runners) with the GitHub `actions-runner` (pinned, sha256-checked, updates
|
|
4
|
+
off), Node.js 24, Docker, git, jq and rootfs build tools. It contains no
|
|
5
|
+
credential.
|
|
6
|
+
|
|
7
|
+
After boot, `scripts/aleph-github-runner.mjs` (`pnpm relay-button gh-runner up`)
|
|
8
|
+
writes over SSH, in this order:
|
|
9
|
+
|
|
10
|
+
- `/etc/github-runner/ttl`: maximum lifetime in minutes (default 360);
|
|
11
|
+
- `/etc/github-runner/forget.env` (optional): `ALEPH_FORGET_KEY`, `ALEPH_OWNER`,
|
|
12
|
+
`ALEPH_INSTANCE`, a delegate key authorized for INSTANCE and FORGET (Aleph
|
|
13
|
+
lets a delegate forget only what it could have created);
|
|
14
|
+
- `/etc/github-runner/jitconfig`: a one-job just-in-time runner config.
|
|
15
|
+
|
|
16
|
+
`github-runner.service` waits for the jitconfig, deletes it once read, and runs
|
|
17
|
+
one job as the `runner` user (passwordless sudo, docker group). When the runner
|
|
18
|
+
exits, `github-runner-finish` forgets the VM's own INSTANCE (if a forget key was
|
|
19
|
+
given) and powers off. The TTL timer runs the same path as a last resort.
|
|
20
|
+
|
|
21
|
+
Self-hosted runners execute workflow code with root rights on this VM: use them
|
|
22
|
+
for private repositories, or public ones whose workflows never run for forks.
|
|
23
|
+
See the docs page `architecture/aleph-github-runner`.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"id": "github-runner",
|
|
4
|
+
"displayName": "Ephemeral GitHub Actions Runner",
|
|
5
|
+
"source": {
|
|
6
|
+
"repository": "NiKrause/relay-button",
|
|
7
|
+
"subdirectory": "packages/rootfs/reference/github-runner"
|
|
8
|
+
},
|
|
9
|
+
"rootfs": {
|
|
10
|
+
"profile": "github-runner",
|
|
11
|
+
"installMode": "prebaked",
|
|
12
|
+
"installDir": "/opt/actions-runner",
|
|
13
|
+
"binaryPath": "/opt/actions-runner/run.sh",
|
|
14
|
+
"dataDir": "/var/lib/github-runner",
|
|
15
|
+
"envFile": "/etc/github-runner/runner.env"
|
|
16
|
+
},
|
|
17
|
+
"services": {
|
|
18
|
+
"bootstrap": "github-runner.service",
|
|
19
|
+
"main": "github-runner.service",
|
|
20
|
+
"autotlsRefresh": "github-runner-ttl.timer"
|
|
21
|
+
},
|
|
22
|
+
"ports": [
|
|
23
|
+
{
|
|
24
|
+
"port": 22,
|
|
25
|
+
"tcp": true,
|
|
26
|
+
"udp": false,
|
|
27
|
+
"purpose": "SSH bootstrap and diagnostics"
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"manifest": {
|
|
31
|
+
"copyTarget": "packages/rootfs/reference/github-runner/latest.json",
|
|
32
|
+
"notes": "GitHub actions-runner 2.338.0, Node.js 24, Docker and rootfs build tools are prebaked. A one-job just-in-time runner config must be injected after boot; the image contains no credential. After its job the runner forgets its own INSTANCE when a forget key was injected, and powers off; a bounded TTL timer is the last resort."
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
FROM debian:12
|
|
2
|
+
|
|
3
|
+
ENV DEBIAN_FRONTEND=noninteractive
|
|
4
|
+
ENV LIBGUESTFS_BACKEND=direct
|
|
5
|
+
|
|
6
|
+
RUN apt-get update \
|
|
7
|
+
&& apt-get install -y --no-install-recommends \
|
|
8
|
+
ca-certificates \
|
|
9
|
+
curl \
|
|
10
|
+
libguestfs-tools \
|
|
11
|
+
linux-image-amd64 \
|
|
12
|
+
qemu-system-x86 \
|
|
13
|
+
qemu-utils \
|
|
14
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
15
|
+
|
|
16
|
+
WORKDIR /workspace/shared-rootfs
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
OUT_DIR="${OUT_DIR:-${SCRIPT_DIR}/../dist-rootfs}"
|
|
6
|
+
# Ubuntu 24.04, the system of GitHub's own ubuntu-24.04 runners: jobs bring
|
|
7
|
+
# tools built for it, such as the aleph CLI release binary, which needs glibc
|
|
8
|
+
# 2.38 (Debian 12 has 2.36). Debian 13's cloud image did not boot on Aleph
|
|
9
|
+
# nodes (reboot loop after GRUB); Aleph offers ubuntu24 as a preset.
|
|
10
|
+
BASE_URL="${BASE_URL:-https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img}"
|
|
11
|
+
BASE_IMAGE="${OUT_DIR}/noble-server-cloudimg-amd64.img"
|
|
12
|
+
IMAGE="${OUT_DIR}/aleph-github-runner.qcow2"
|
|
13
|
+
ROOTFS_IMAGE_SIZE="${ROOTFS_IMAGE_SIZE:-10G}"
|
|
14
|
+
# Pinned on purpose: the runner is started with --disableupdate, so the image
|
|
15
|
+
# decides the version. Bump both lines together.
|
|
16
|
+
RUNNER_VERSION="${RUNNER_VERSION:-2.338.0}"
|
|
17
|
+
RUNNER_SHA256="${RUNNER_SHA256:-af4b794c1bc41d73d40535e3fe092a39f9679cd8d965954c2aca25a05ca41d32}"
|
|
18
|
+
ETHERS_VERSION="6.16.0"
|
|
19
|
+
HOST_UID="${HOST_UID:-}"
|
|
20
|
+
HOST_GID="${HOST_GID:-}"
|
|
21
|
+
ROOTFS_SPARSIFY_TMPDIR=""
|
|
22
|
+
|
|
23
|
+
require() {
|
|
24
|
+
command -v "$1" >/dev/null 2>&1 || { echo "Missing required command: $1" >&2; exit 1; }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
for command in curl qemu-img virt-customize sha256sum; do require "${command}"; done
|
|
28
|
+
|
|
29
|
+
cleanup() {
|
|
30
|
+
if [ -n "${ROOTFS_SPARSIFY_TMPDIR}" ] && [ -d "${ROOTFS_SPARSIFY_TMPDIR}" ]; then
|
|
31
|
+
rm -rf "${ROOTFS_SPARSIFY_TMPDIR}"
|
|
32
|
+
fi
|
|
33
|
+
}
|
|
34
|
+
trap cleanup EXIT
|
|
35
|
+
|
|
36
|
+
mkdir -p "${OUT_DIR}"
|
|
37
|
+
if [ ! -f "${BASE_IMAGE}" ]; then
|
|
38
|
+
curl --fail --show-error --location --retry 5 --retry-all-errors \
|
|
39
|
+
--connect-timeout 20 --max-time 300 "${BASE_URL}" -o "${BASE_IMAGE}"
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
RUNNER_TARBALL="${OUT_DIR}/actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz"
|
|
43
|
+
if [ ! -f "${RUNNER_TARBALL}" ]; then
|
|
44
|
+
curl --fail --show-error --location --retry 5 --retry-all-errors \
|
|
45
|
+
--connect-timeout 20 --max-time 300 \
|
|
46
|
+
"https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz" \
|
|
47
|
+
-o "${RUNNER_TARBALL}"
|
|
48
|
+
fi
|
|
49
|
+
echo "${RUNNER_SHA256} ${RUNNER_TARBALL}" | sha256sum --check --strict
|
|
50
|
+
|
|
51
|
+
cp "${BASE_IMAGE}" "${IMAGE}"
|
|
52
|
+
qemu-img resize "${IMAGE}" "${ROOTFS_IMAGE_SIZE}"
|
|
53
|
+
# Growing the file alone leaves the cloud image's ~2.5 GB root partition full
|
|
54
|
+
# before Docker is unpacked. The root partition is the last one on the disk,
|
|
55
|
+
# so growpart extends it in place; virt-resize would rewrite the partition
|
|
56
|
+
# table, and Ubuntu's cloud image finds its root by partition UUID (a resized
|
|
57
|
+
# image never brought up SSH on Aleph).
|
|
58
|
+
virt-customize -a "${IMAGE}" --run-command "growpart /dev/sda 1 && resize2fs /dev/sda1"
|
|
59
|
+
|
|
60
|
+
virt-customize \
|
|
61
|
+
-a "${IMAGE}" \
|
|
62
|
+
--network \
|
|
63
|
+
--mkdir /opt/actions-runner \
|
|
64
|
+
--mkdir /opt/github-runner \
|
|
65
|
+
--mkdir /etc/github-runner \
|
|
66
|
+
--mkdir /etc/systemd/journald.conf.d \
|
|
67
|
+
--mkdir /var/lib/github-runner \
|
|
68
|
+
--copy-in "${RUNNER_TARBALL}:/opt/github-runner" \
|
|
69
|
+
--copy-in "${SCRIPT_DIR}/forget-self.mjs:/opt/github-runner" \
|
|
70
|
+
--copy-in "${SCRIPT_DIR}/github-runner-start:/usr/local/sbin" \
|
|
71
|
+
--copy-in "${SCRIPT_DIR}/github-runner-finish:/usr/local/sbin" \
|
|
72
|
+
--copy-in "${SCRIPT_DIR}/github-runner.service:/etc/systemd/system" \
|
|
73
|
+
--copy-in "${SCRIPT_DIR}/github-runner-ttl.service:/etc/systemd/system" \
|
|
74
|
+
--copy-in "${SCRIPT_DIR}/github-runner-ttl.timer:/etc/systemd/system" \
|
|
75
|
+
--run-command "apt-get update && apt-get install -y --no-install-recommends ca-certificates curl gnupg git jq unzip zip xz-utils zstd sudo python3 rsync e2fsprogs openssh-client qemu-utils cryptsetup debootstrap docker.io" \
|
|
76
|
+
--run-command "apt-get purge -y --auto-remove snapd lxd-installer || true" \
|
|
77
|
+
--run-command "curl -fsSL https://deb.nodesource.com/setup_24.x | bash -" \
|
|
78
|
+
--run-command "apt-get install -y --no-install-recommends nodejs" \
|
|
79
|
+
--run-command "useradd --create-home --home-dir /home/runner --shell /bin/bash --groups docker runner" \
|
|
80
|
+
--run-command "printf 'runner ALL=(ALL) NOPASSWD:ALL\\n' >/etc/sudoers.d/runner && chmod 0440 /etc/sudoers.d/runner" \
|
|
81
|
+
--run-command "tar -xzf /opt/github-runner/actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz -C /opt/actions-runner && rm /opt/github-runner/actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz" \
|
|
82
|
+
--run-command "/opt/actions-runner/bin/installdependencies.sh" \
|
|
83
|
+
--run-command "chown -R runner:runner /opt/actions-runner /var/lib/github-runner" \
|
|
84
|
+
--run-command "cd /opt/github-runner && npm init -y >/dev/null && npm install --omit=dev --no-audit --no-fund ethers@${ETHERS_VERSION}" \
|
|
85
|
+
--run-command "chmod 0700 /etc/github-runner && chmod 0755 /usr/local/sbin/github-runner-start /usr/local/sbin/github-runner-finish" \
|
|
86
|
+
--run-command "printf '[Journal]\\nSystemMaxUse=256M\\nRuntimeMaxUse=64M\\nMaxRetentionSec=2day\\n' >/etc/systemd/journald.conf.d/github-runner.conf" \
|
|
87
|
+
--run-command "npm cache clean --force" \
|
|
88
|
+
--run-command "apt-get clean && rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/* /root/.npm /tmp/* /var/tmp/*" \
|
|
89
|
+
--run-command "systemctl enable docker.service github-runner.service github-runner-ttl.timer"
|
|
90
|
+
|
|
91
|
+
if command -v virt-sparsify >/dev/null 2>&1; then
|
|
92
|
+
SPARSE_IMAGE="${IMAGE%.qcow2}.sparse.qcow2"
|
|
93
|
+
ROOTFS_SPARSIFY_TMPDIR="$(mktemp -d "${OUT_DIR}/virt-sparsify-tmp.XXXXXX")"
|
|
94
|
+
TMPDIR="${ROOTFS_SPARSIFY_TMPDIR}" virt-sparsify --check-tmpdir=continue --compress \
|
|
95
|
+
"${IMAGE}" "${SPARSE_IMAGE}"
|
|
96
|
+
mv "${SPARSE_IMAGE}" "${IMAGE}"
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
if [ -n "${HOST_UID}" ] && [ -n "${HOST_GID}" ]; then
|
|
100
|
+
chown -R "${HOST_UID}:${HOST_GID}" "${OUT_DIR}"
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
echo "Compressed RootFS size: $(stat -c '%s bytes' "${IMAGE}") ($(du -h "${IMAGE}" | cut -f1) on disk)"
|
|
104
|
+
echo "GitHub runner ${RUNNER_VERSION} RootFS ready: ${IMAGE}"
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Sends FORGET for this VM's own INSTANCE on behalf of the wallet that pays
|
|
3
|
+
// for it. The key is a delegate the owner authorized for INSTANCE and FORGET:
|
|
4
|
+
// Aleph lets a delegate forget only messages it could have created itself. Usage: forget-self.mjs <env-file> [reason]
|
|
5
|
+
//
|
|
6
|
+
// env file: ALEPH_FORGET_KEY, ALEPH_OWNER, ALEPH_INSTANCE, optional ALEPH_API_HOST
|
|
7
|
+
import { createHash } from 'node:crypto'
|
|
8
|
+
import { readFileSync } from 'node:fs'
|
|
9
|
+
|
|
10
|
+
export function parseEnv(text) {
|
|
11
|
+
const env = {}
|
|
12
|
+
for (const line of text.split('\n')) {
|
|
13
|
+
const match = /^\s*([A-Z_][A-Z0-9_]*)=(.*)$/u.exec(line)
|
|
14
|
+
if (match) env[match[1]] = match[2].trim().replace(/^(['"])(.*)\1$/u, '$2')
|
|
15
|
+
}
|
|
16
|
+
return env
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function buildForget({ sender, owner, instance, reason, now = Date.now() / 1000 }) {
|
|
20
|
+
if (!/^[a-f0-9]{64}$/u.test(instance)) throw new Error('ALEPH_INSTANCE must be a 64-hex item hash')
|
|
21
|
+
if (!/^0x[0-9a-fA-F]{40}$/u.test(owner)) throw new Error('ALEPH_OWNER must be an 0x address')
|
|
22
|
+
const itemContent = JSON.stringify({
|
|
23
|
+
address: owner,
|
|
24
|
+
time: now,
|
|
25
|
+
hashes: [instance],
|
|
26
|
+
aggregates: [],
|
|
27
|
+
reason
|
|
28
|
+
})
|
|
29
|
+
return {
|
|
30
|
+
sender,
|
|
31
|
+
chain: 'ETH',
|
|
32
|
+
type: 'FORGET',
|
|
33
|
+
item_hash: createHash('sha256').update(itemContent).digest('hex'),
|
|
34
|
+
item_type: 'inline',
|
|
35
|
+
item_content: itemContent,
|
|
36
|
+
time: now,
|
|
37
|
+
channel: 'ALEPH-CLOUDSOLUTIONS'
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function signaturePayload(message) {
|
|
42
|
+
return [message.chain, message.sender, message.type, message.item_hash].join('\n')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function main() {
|
|
46
|
+
const [envFile, reason = 'github-runner finished'] = process.argv.slice(2)
|
|
47
|
+
const env = parseEnv(readFileSync(envFile, 'utf8'))
|
|
48
|
+
const { Wallet } = await import('ethers')
|
|
49
|
+
const wallet = new Wallet(env.ALEPH_FORGET_KEY)
|
|
50
|
+
const message = buildForget({
|
|
51
|
+
sender: wallet.address,
|
|
52
|
+
owner: env.ALEPH_OWNER,
|
|
53
|
+
instance: env.ALEPH_INSTANCE,
|
|
54
|
+
reason: `github-runner: ${reason}`
|
|
55
|
+
})
|
|
56
|
+
message.signature = await wallet.signMessage(signaturePayload(message))
|
|
57
|
+
const api = env.ALEPH_API_HOST || 'https://api2.aleph.im'
|
|
58
|
+
const response = await fetch(`${api}/api/v0/messages`, {
|
|
59
|
+
method: 'POST',
|
|
60
|
+
headers: { 'content-type': 'application/json' },
|
|
61
|
+
body: JSON.stringify({ sync: false, message }),
|
|
62
|
+
signal: AbortSignal.timeout(20_000)
|
|
63
|
+
})
|
|
64
|
+
const body = await response.json().catch(() => ({}))
|
|
65
|
+
console.log(`FORGET ${message.item_hash}: HTTP ${response.status} ${body.message_status ?? ''}`)
|
|
66
|
+
if (![200, 202].includes(response.status) || body.message_status === 'rejected') process.exit(1)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (import.meta.url === `file://${process.argv[1]}`) await main()
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Runs once the runner has exited (its one job is done, or it failed to
|
|
3
|
+
# start) and from the TTL timer. Powering off alone keeps the INSTANCE
|
|
4
|
+
# allocated and billed, so when the launcher injected a forget key the VM
|
|
5
|
+
# first sends FORGET for its own INSTANCE on behalf of the owner.
|
|
6
|
+
set -uo pipefail
|
|
7
|
+
|
|
8
|
+
reason="${1:-runner-exited}"
|
|
9
|
+
FORGET_ENV=/etc/github-runner/forget.env
|
|
10
|
+
MARKER=/run/github-runner-finished
|
|
11
|
+
|
|
12
|
+
# The TTL path powers off, which stops the runner, which runs this again.
|
|
13
|
+
[ -e "${MARKER}" ] && exit 0
|
|
14
|
+
touch "${MARKER}"
|
|
15
|
+
|
|
16
|
+
LOG=/var/lib/github-runner/finish.log
|
|
17
|
+
|
|
18
|
+
# Every attempt is bounded, and the whole loop stays well inside the unit's
|
|
19
|
+
# TimeoutStopSec: if systemd kills this script, the poweroff below never runs.
|
|
20
|
+
echo "$(date -Is) finish: ${reason}" | tee -a "${LOG}"
|
|
21
|
+
if [ -s "${FORGET_ENV}" ]; then
|
|
22
|
+
for attempt in 1 2 3; do
|
|
23
|
+
# pipefail: the pipeline fails when node does.
|
|
24
|
+
if timeout 30 node /opt/github-runner/forget-self.mjs "${FORGET_ENV}" "${reason}" 2>&1 | tee -a "${LOG}"; then
|
|
25
|
+
break
|
|
26
|
+
fi
|
|
27
|
+
sleep $((attempt * 5))
|
|
28
|
+
done
|
|
29
|
+
fi
|
|
30
|
+
echo "$(date -Is) poweroff" | tee -a "${LOG}"
|
|
31
|
+
systemctl --no-block poweroff
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Waits for the launcher to inject a one-job just-in-time config, then runs
|
|
3
|
+
# exactly one job as the unprivileged runner user. The config is removed as
|
|
4
|
+
# soon as the runner has read it, so a job cannot find it on disk.
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
CONF_DIR=/etc/github-runner
|
|
8
|
+
JIT="${CONF_DIR}/jitconfig"
|
|
9
|
+
|
|
10
|
+
until [ -s "${JIT}" ]; do sleep 2; done
|
|
11
|
+
|
|
12
|
+
if [ -s "${CONF_DIR}/ttl" ]; then
|
|
13
|
+
ttl="$(tr -dc '0-9' <"${CONF_DIR}/ttl")"
|
|
14
|
+
if [ -n "${ttl}" ] && [ "${ttl}" -gt 0 ]; then
|
|
15
|
+
mkdir -p /run/systemd/system/github-runner-ttl.timer.d
|
|
16
|
+
printf '[Timer]\nOnActiveSec=\nOnActiveSec=%smin\n' "${ttl}" \
|
|
17
|
+
>/run/systemd/system/github-runner-ttl.timer.d/ttl.conf
|
|
18
|
+
systemctl daemon-reload
|
|
19
|
+
fi
|
|
20
|
+
fi
|
|
21
|
+
# The timer already runs from boot (a VM that never gets a config still powers
|
|
22
|
+
# off); restarting it counts the TTL from now.
|
|
23
|
+
systemctl restart github-runner-ttl.timer
|
|
24
|
+
|
|
25
|
+
config="$(cat "${JIT}")"
|
|
26
|
+
rm -f "${JIT}"
|
|
27
|
+
|
|
28
|
+
cd /opt/actions-runner
|
|
29
|
+
exec runuser -u runner -- ./run.sh --jitconfig "${config}"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=Ephemeral GitHub Actions runner (one job, then forget and power off)
|
|
3
|
+
After=network-online.target docker.service
|
|
4
|
+
Wants=network-online.target docker.service
|
|
5
|
+
|
|
6
|
+
[Service]
|
|
7
|
+
Type=simple
|
|
8
|
+
ExecStart=/usr/local/sbin/github-runner-start
|
|
9
|
+
ExecStopPost=/usr/local/sbin/github-runner-finish
|
|
10
|
+
Restart=no
|
|
11
|
+
# Room for github-runner-finish: three bounded FORGET attempts, then poweroff.
|
|
12
|
+
TimeoutStopSec=180
|
|
13
|
+
StandardOutput=journal
|
|
14
|
+
StandardError=journal
|
|
15
|
+
|
|
16
|
+
[Install]
|
|
17
|
+
WantedBy=multi-user.target
|
|
Binary file
|