From 9d2ebf8909a129d3a05ad91c4779e7f3e567d2aa Mon Sep 17 00:00:00 2001 From: Gabriel Mazzetto Date: Tue, 1 Sep 2026 22:55:13 +0200 Subject: [PATCH] Add Debian 12 and 13 support Download the matching Ubuntu ruby-builder binaries instead of treating Debian as an unsupported self-hosted image, and install missing CRuby runtime libraries with apt-get when the job is root. --- README.md | 6 ++++- common.js | 30 +++++++++++++++++++++++ dist/index.js | 64 +++++++++++++++++++++++++++++++++++++++++++++++-- ruby-builder.js | 33 +++++++++++++++++++++++-- 4 files changed, 128 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 73788e439..c4ebd98c8 100644 --- a/README.md +++ b/README.md @@ -50,9 +50,12 @@ The action works on these [GitHub-hosted runners](https://docs.github.com/en/act | Operating System | Supported | | ---------------- | --------- | | Ubuntu | `ubuntu-22.04`, `ubuntu-24.04`, `ubuntu-26.04`, `ubuntu-22.04-arm`, `ubuntu-24.04-arm`, `ubuntu-26.04-arm` | +| Debian | `debian-12` (Ubuntu 22.04 builds), `debian-13` (Ubuntu 24.04 builds), x64 and arm64 | | macOS | `macos-14` and newer versions | | Windows | `windows-2022`, `windows-2025`, `windows-11-arm` | +Debian is included so this action can run on Forgejo `act_runner` job containers. There are no Debian-specific ruby-builder artifacts; the action downloads the Ubuntu builds listed above. That works because Debian 12/13 have a newer glibc than those Ubuntu images and still ship `libssl.so.3`. `/opt/hostedtoolcache` must be writable. Missing CRuby runtime packages (`libssl3`, `libyaml-0-2`, `libgmp10`, …) are installed with `apt-get` when the job is root. + Not all combinations of runner images and versions are supported. The list of available Ruby versions can be seen in [ruby-builder-versions.json](ruby-builder-versions.json) for Ubuntu and macOS (although some combinations are not available, see [the full list](https://github.com/ruby/ruby-builder/releases/tag/toolcache)) @@ -273,7 +276,8 @@ This follows the [recommendations](https://github.com/actions/toolkit/blob/maste ## Using self-hosted runners -This action might work with [self-hosted runners](https://docs.github.com/en/actions/hosting-your-own-runners/about-self-hosted-runners) +Debian 12 and 13 runners (including Forgejo) are supported directly; see [Supported Platforms](#supported-platforms). +For other self-hosted Linux, this action might work with [self-hosted runners](https://docs.github.com/en/actions/hosting-your-own-runners/about-self-hosted-runners) if the [Runner Image](https://github.com/actions/runner-images) is very similar to the ones used by GitHub runners. Notably: * Make sure to use the same operating system and version. diff --git a/common.js b/common.js index 79ad6266f..eacc2fb2c 100644 --- a/common.js +++ b/common.js @@ -193,12 +193,41 @@ const GitHubHostedPlatforms = [ 'windows-11-arm64' ] +// ruby-builder artifacts are Ubuntu-specific. Debian can run them when its glibc +// is newer than the builder image and libssl is still SONAME 3. +// Debian 12 (glibc 2.36) → Ubuntu 22.04 (glibc 2.35). +// Debian 13 (glibc 2.41) → Ubuntu 24.04 (glibc 2.39). +const DebianToUbuntuBuilder = { + '12': 'ubuntu-22.04', + '13': 'ubuntu-24.04', +} + +function debianMajorVersion() { + return getOSVersion().split('.')[0] +} + +// Platform string used in ruby-builder download URLs (ubuntu-XX.YY, not debian-N). +export function getRubyBuilderPlatform() { + const name = getOSName() + if (name === 'debian') { + const mapped = DebianToUbuntuBuilder[debianMajorVersion()] + if (mapped) { + return mapped + } + } + return getOSNameVersion() +} + // Precisely: whether we have builds for that platform and there are GitHub-hosted runners to test it function isSupportedPlatform() { const platform = getOSName() switch (platform) { case 'ubuntu': return GitHubHostedPlatforms.includes(getOSNameVersionArch()) + case 'debian': { + const mapped = DebianToUbuntuBuilder[debianMajorVersion()] + return mapped !== undefined && GitHubHostedPlatforms.includes(`${mapped}-${os.arch()}`) + } case 'macos': // See https://github.com/ruby/ruby-builder/blob/master/README.md#naming // 13 on arm64 because of old macos-arm-oss runners @@ -318,6 +347,7 @@ function getDefaultToolCachePath() { const platform = getOSName() switch (platform) { case 'ubuntu': + case 'debian': return '/opt/hostedtoolcache' case 'macos': return '/Users/runner/hostedtoolcache' diff --git a/dist/index.js b/dist/index.js index 81fa86d40..abe4af9d8 100644 --- a/dist/index.js +++ b/dist/index.js @@ -306,6 +306,7 @@ __nccwpck_require__.r(__webpack_exports__); /* harmony export */ getOSNameVersion: () => (/* binding */ getOSNameVersion), /* harmony export */ getOSNameVersionArch: () => (/* binding */ getOSNameVersionArch), /* harmony export */ getOSVersion: () => (/* binding */ getOSVersion), +/* harmony export */ getRubyBuilderPlatform: () => (/* binding */ getRubyBuilderPlatform), /* harmony export */ getRunnerToolCache: () => (/* binding */ getRunnerToolCache), /* harmony export */ getToolCachePath: () => (/* binding */ getToolCachePath), /* harmony export */ getToolCacheRubyPrefix: () => (/* binding */ getToolCacheRubyPrefix), @@ -528,12 +529,41 @@ const GitHubHostedPlatforms = [ 'windows-11-arm64' ] +// ruby-builder artifacts are Ubuntu-specific. Debian can run them when its glibc +// is newer than the builder image and libssl is still SONAME 3. +// Debian 12 (glibc 2.36) → Ubuntu 22.04 (glibc 2.35). +// Debian 13 (glibc 2.41) → Ubuntu 24.04 (glibc 2.39). +const DebianToUbuntuBuilder = { + '12': 'ubuntu-22.04', + '13': 'ubuntu-24.04', +} + +function debianMajorVersion() { + return getOSVersion().split('.')[0] +} + +// Platform string used in ruby-builder download URLs (ubuntu-XX.YY, not debian-N). +function getRubyBuilderPlatform() { + const name = getOSName() + if (name === 'debian') { + const mapped = DebianToUbuntuBuilder[debianMajorVersion()] + if (mapped) { + return mapped + } + } + return getOSNameVersion() +} + // Precisely: whether we have builds for that platform and there are GitHub-hosted runners to test it function isSupportedPlatform() { const platform = getOSName() switch (platform) { case 'ubuntu': return GitHubHostedPlatforms.includes(getOSNameVersionArch()) + case 'debian': { + const mapped = DebianToUbuntuBuilder[debianMajorVersion()] + return mapped !== undefined && GitHubHostedPlatforms.includes(`${mapped}-${os.arch()}`) + } case 'macos': // See https://github.com/ruby/ruby-builder/blob/master/README.md#naming // 13 on arm64 because of old macos-arm-oss runners @@ -653,6 +683,7 @@ function getDefaultToolCachePath() { const platform = getOSName() switch (platform) { case 'ubuntu': + case 'debian': return '/opt/hostedtoolcache' case 'macos': return '/Users/runner/hostedtoolcache' @@ -50219,6 +50250,15 @@ function getAvailableVersions(platform, engine) { } async function install(platform, engine, version) { + const builderPlatform = common.getRubyBuilderPlatform() + if (builderPlatform !== platform) { + console.log(`Detected ${common.getOSNameVersionArch()}; using ${builderPlatform} prebuilt Ruby binaries`) + } + + if (platform.startsWith('debian-')) { + await ensureDebianRuntimeLibs() + } + let rubyPrefix, inToolCache if (common.shouldUseToolCache(engine, version)) { inToolCache = common.toolCacheFind(engine, version) @@ -50309,8 +50349,8 @@ function getDownloadURL(platform, engine, version) { builderPlatform = `windows-${os.arch()}` } else if (platform.startsWith('macos-')) { builderPlatform = `darwin-${os.arch()}` - } else if (platform.startsWith('ubuntu-')) { - builderPlatform = `${platform}-${os.arch()}` + } else if (platform.startsWith('ubuntu-') || platform.startsWith('debian-')) { + builderPlatform = `${common.getRubyBuilderPlatform()}-${os.arch()}` } if (builderPlatform === null || !['x64', 'arm64'].includes(os.arch())) { @@ -50324,6 +50364,26 @@ function getDownloadURL(platform, engine, version) { } } +const debianRuntimePackages = ['libssl3', 'libyaml-0-2', 'libgmp10', 'zlib1g', 'libcrypt1', 'libffi8'] + +async function ensureDebianRuntimeLibs() { + const missing = [] + for (const pkg of debianRuntimePackages) { + const status = await exec.exec('dpkg', ['-s', pkg], { ignoreReturnCode: true, silent: true }) + if (status !== 0) { + missing.push(pkg) + } + } + if (missing.length === 0) { + return + } + + await common.measure(`Installing Ruby runtime libraries (${missing.join(', ')})`, async () => { + await exec.exec('apt-get', ['update', '-qq']) + await exec.exec('apt-get', ['install', '-y', '-qq', ...missing]) + }) +} + function getLatestHeadBuildURL(platform, engine, version) { var repo = `${engine}-dev-builder` if (engine === 'truffleruby+graalvm') { diff --git a/ruby-builder.js b/ruby-builder.js index 52041c889..bc4a33a91 100644 --- a/ruby-builder.js +++ b/ruby-builder.js @@ -16,6 +16,15 @@ export function getAvailableVersions(platform, engine) { } export async function install(platform, engine, version) { + const builderPlatform = common.getRubyBuilderPlatform() + if (builderPlatform !== platform) { + console.log(`Detected ${common.getOSNameVersionArch()}; using ${builderPlatform} prebuilt Ruby binaries`) + } + + if (platform.startsWith('debian-')) { + await ensureDebianRuntimeLibs() + } + let rubyPrefix, inToolCache if (common.shouldUseToolCache(engine, version)) { inToolCache = common.toolCacheFind(engine, version) @@ -106,8 +115,8 @@ function getDownloadURL(platform, engine, version) { builderPlatform = `windows-${os.arch()}` } else if (platform.startsWith('macos-')) { builderPlatform = `darwin-${os.arch()}` - } else if (platform.startsWith('ubuntu-')) { - builderPlatform = `${platform}-${os.arch()}` + } else if (platform.startsWith('ubuntu-') || platform.startsWith('debian-')) { + builderPlatform = `${common.getRubyBuilderPlatform()}-${os.arch()}` } if (builderPlatform === null || !['x64', 'arm64'].includes(os.arch())) { @@ -121,6 +130,26 @@ function getDownloadURL(platform, engine, version) { } } +const debianRuntimePackages = ['libssl3', 'libyaml-0-2', 'libgmp10', 'zlib1g', 'libcrypt1', 'libffi8'] + +async function ensureDebianRuntimeLibs() { + const missing = [] + for (const pkg of debianRuntimePackages) { + const status = await exec.exec('dpkg', ['-s', pkg], { ignoreReturnCode: true, silent: true }) + if (status !== 0) { + missing.push(pkg) + } + } + if (missing.length === 0) { + return + } + + await common.measure(`Installing Ruby runtime libraries (${missing.join(', ')})`, async () => { + await exec.exec('apt-get', ['update', '-qq']) + await exec.exec('apt-get', ['install', '-y', '-qq', ...missing]) + }) +} + function getLatestHeadBuildURL(platform, engine, version) { var repo = `${engine}-dev-builder` if (engine === 'truffleruby+graalvm') {