feat!: support addAptKey options through installAptPack

BREAKING no default keys are added during apt installations anymore. Explicitly pass the keys needed.
BREAKING the arguments to addAptKeyViaServer and addAptKeyViaDownload has changed. addAptKeyViaDownload renamed to addAptKeyViaURL
This commit is contained in:
Amin Yahyaabadi 2024-08-28 13:54:48 -07:00
parent 12442d6b61
commit 19bf09e888
No known key found for this signature in database
GPG Key ID: F52AF77F636088F0
17 changed files with 258 additions and 143 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -135,7 +135,7 @@ Add an apt key via a keyserver
**returns:** Promise<string>
### `addAptKeyViaDownload` (function)
### `addAptKeyViaURL` (function)
Add an apt key via a download

View File

@ -1,6 +1,6 @@
{
"name": "setup-apt",
"version": "1.0.1",
"version": "2.0.0",
"description": "Setup apt packages and repositories in Debian/Ubuntu-based distributions",
"repository": "https://github.com/aminya/setup-cpp",
"homepage": "https://github.com/aminya/setup-cpp/tree/master/packages/setup-apt",

View File

@ -1,25 +1,78 @@
import { tmpdir } from "os"
import { join } from "path"
import { execRoot, execRootSync } from "admina"
import { warning } from "ci-log"
import { DownloaderHelper } from "node-downloader-helper"
import { pathExists } from "path-exists"
import { installAptPack } from "./install.js"
function initGpg() {
execRootSync("gpg", ["-k"])
export type AddAptKeyOptions = KeyServerOptions | KeyUrl
/**
* Add an apt key
* @param options The options for adding the key
* @returns The file name of the key that was added or `undefined` if it failed
*
* @example
* ```ts
* await addAptKey({ keys: ["3B4FE6ACC0B21F32", "40976EAF437D05B5"], fileName: "bazel-archive-keyring.gpg"})
* ```
*
* @example
* ```ts
* await addAptKey({ keyUrl: "https://bazel.build/bazel-release.pub.gpg", fileName: "bazel-archive-keyring.gpg"})
* ```
*/
export function addAptKey(options: AddAptKeyOptions) {
if ("keyUrl" in options) {
return addAptKeyViaURL(options)
} else {
return addAptKeyViaServer(options)
}
}
type GpgKeyOptions = {
/**
* The file name for the key (should end in `.gpg`)
*/
fileName: string
/**
* The key store path (Defaults to `/etc/apt/trusted.gpg.d`)
*/
keyStorePath?: string
}
export const defaultKeyStorePath = "/etc/apt/trusted.gpg.d"
export type KeyServerOptions = {
/**
* The keys to add
*
* @example
* ```ts
* ["3B4FE6ACC0B21F32", "40976EAF437D05B5"]
* ```
*/
keys: string[]
/**
* The keyserver to use (Defaults to `keyserver.ubuntu.com`)
*/
keyServer?: string
} & GpgKeyOptions
export const defaultKeyServer = "keyserver.ubuntu.com"
/**
* Add an apt key via a keyserver
* @param keys The keys to add
* @param name The name of the key
* @param server The keyserver to use (Defaults to `keyserver.ubuntu.com`)
* @returns The file name of the key that was added or `undefined` if it failed
*/
export async function addAptKeyViaServer(keys: string[], name: string, server = "keyserver.ubuntu.com") {
export async function addAptKeyViaServer(
{ keys, keyServer = defaultKeyServer, fileName, keyStorePath = defaultKeyServer }: KeyServerOptions,
) {
try {
const fileName = `/etc/apt/trusted.gpg.d/${name}`
if (!(await pathExists(fileName))) {
assertGpgFileName(fileName)
const filePath = join(keyStorePath, fileName)
if (!(await pathExists(filePath))) {
initGpg()
await Promise.all(
@ -27,43 +80,73 @@ export async function addAptKeyViaServer(keys: string[], name: string, server =
await execRoot("gpg", [
"--no-default-keyring",
"--keyring",
`gnupg-ring:${fileName}`,
`gnupg-ring:${filePath}`,
"--keyserver",
server,
keyServer,
"--recv-keys",
key,
])
await execRoot("chmod", ["644", fileName])
await execRoot("chmod", ["644", filePath])
}),
)
}
return fileName
return filePath
} catch (err) {
warning(`Failed to add apt key via server ${server}: ${err}`)
warning(`Failed to add apt key via server ${keyServer}: ${err}`)
return undefined
}
}
export type KeyUrl = {
/**
* The URL to download the key from
*/
keyUrl: string
} & GpgKeyOptions
/**
* Add an apt key via a download
* @param name The name of the key
* @param url The URL of the key
* @param options The options for adding the key
* @returns The file name of the key that was added
*/
export async function addAptKeyViaDownload(name: string, url: string) {
const fileName = `/etc/apt/trusted.gpg.d/${name}`
if (!(await pathExists(fileName))) {
initGpg()
export async function addAptKeyViaURL({ keyUrl, fileName, keyStorePath = defaultKeyStorePath }: KeyUrl) {
try {
assertGpgFileName(fileName)
const filePath = join(keyStorePath, fileName)
if (!(await pathExists(filePath))) {
initGpg()
await installAptPack([{ name: "ca-certificates" }])
const dl = new DownloaderHelper(url, tmpdir(), { fileName: name })
dl.on("error", (err) => {
throw new Error(`Failed to download ${url}: ${err}`)
})
await dl.start()
await installAptPack([{ name: "ca-certificates" }])
execRootSync("gpg", ["--no-default-keyring", "--keyring", `gnupg-ring:${fileName}`, "--import", `/tmp/${name}`])
execRootSync("chmod", ["644", fileName])
const dlPath = join(tmpdir(), fileName)
const dl = new DownloaderHelper(keyUrl, tmpdir(), { fileName })
dl.on("error", (err) => {
throw new Error(`Failed to download ${keyUrl}: ${err}`)
})
await dl.start()
execRootSync("gpg", [
"--no-default-keyring",
"--keyring",
`gnupg-ring:${filePath}`,
"--import",
dlPath,
])
execRootSync("chmod", ["644", filePath])
}
return filePath
} catch (err) {
warning(`Failed to add apt key via download ${keyUrl}: ${err}`)
return undefined
}
}
function initGpg() {
execRootSync("gpg", ["-k"])
}
function assertGpgFileName(fileName: string) {
if (!fileName.endsWith(".gpg")) {
throw new Error(`Key file name must end with .gpg: ${fileName}`)
}
return fileName
}

View File

@ -3,7 +3,7 @@ import { info, warning } from "ci-log"
import escapeRegex from "escape-string-regexp"
import { type ExecaError, execa } from "execa"
import which from "which"
import { addAptKeyViaServer } from "./apt-key.js"
import { type AddAptKeyOptions, addAptKey } from "./apt-key.js"
import { isAptPackInstalled } from "./is-installed.js"
import { updateAptRepos } from "./update.js"
@ -40,6 +40,8 @@ export type AptPackage = {
version?: string
/** The repositories to add before installing the package (optional) */
repositories?: string[]
/** The keys to add before installing the package (optional) */
addAptKey?: AddAptKeyOptions[]
}
const retryErrors = [
@ -83,8 +85,11 @@ export async function installAptPack(packages: AptPackage[], update = false): Pr
didInit = true
}
// Install
try {
// Add the keys if needed
await addAptKeys(packages)
// Install
execRootSync(apt, ["install", "--fix-broken", "-y", ...needToInstall], {
...defaultExecOptions,
env: getAptEnv(apt),
@ -107,6 +112,14 @@ export async function installAptPack(packages: AptPackage[], update = false): Pr
return { binDir: "/usr/bin/" }
}
async function addAptKeys(packages: AptPackage[]) {
await Promise.all(packages.map(async (pack) => {
if (pack.addAptKey !== undefined) {
await Promise.all(pack.addAptKey.map(addAptKey))
}
}))
}
function isExecaError(err: unknown): err is ExecaError {
return typeof (err as ExecaError).stderr === "string"
}
@ -285,9 +298,4 @@ async function initApt(apt: string) {
env: getAptEnv(apt),
})
}
await Promise.all([
addAptKeyViaServer(["3B4FE6ACC0B21F32", "40976EAF437D05B5"], "setup-cpp-ubuntu-archive.gpg"),
addAptKeyViaServer(["1E9377A2BA9EF27F"], "launchpad-toolchain.gpg"),
])
}

View File

@ -28,7 +28,8 @@
},
"devDependencies": {
"path-exists": "5.0.0",
"patha": "0.4.1"
"patha": "0.4.1",
"cross-spawn": "7.0.3"
},
"engines": {
"node": ">=12"

View File

@ -346,6 +346,9 @@ importers:
specifier: 4.0.0
version: 4.0.0
devDependencies:
cross-spawn:
specifier: 7.0.3
version: 7.0.3
path-exists:
specifier: 5.0.0
version: 5.0.0

View File

@ -1,5 +1,5 @@
import { execRootSync } from "admina"
import { addAptKeyViaDownload, installAptPack } from "setup-apt"
import { addAptKeyViaURL, installAptPack } from "setup-apt"
import { installBrewPack } from "setup-brew"
import { hasDnf } from "../utils/env/hasDnf.js"
import { isArch } from "../utils/env/isArch.js"
@ -28,10 +28,10 @@ export async function setupBazel(version: string, _setupDir: string, _arch: stri
return setupDnfPack([{ name: "bazel4" }])
} else if (isUbuntu()) {
// https://bazel.build/install/ubuntu
const keyFileName = await addAptKeyViaDownload(
"bazel-archive-keyring.gpg",
"https://bazel.build/bazel-release.pub.gpg",
)
const keyFileName = await addAptKeyViaURL({
fileName: "bazel-archive-keyring.gpg",
keyUrl: "https://bazel.build/bazel-release.pub.gpg",
})
execRootSync("bash", [
"-c",
`echo "deb [arch=amd64 signed-by=${keyFileName}] https://storage.googleapis.com/bazel-apt stable jdk1.8" | tee /etc/apt/sources.list.d/bazel.list`,

View File

@ -111,8 +111,18 @@ export async function setupGcc(version: string, setupDir: string, arch: string,
])
} else if (isUbuntu()) {
installationInfo = await installAptPack([
{ name: "gcc", version, repositories: ["ppa:ubuntu-toolchain-r/test"] },
{ name: "g++", version, repositories: ["ppa:ubuntu-toolchain-r/test"] },
{
name: "gcc",
version,
repositories: ["ppa:ubuntu-toolchain-r/test"],
addAptKey: [{ keys: ["1E9377A2BA9EF27F"], fileName: "ubuntu-toolchain-r-test.gpg" }],
},
{
name: "g++",
version,
repositories: ["ppa:ubuntu-toolchain-r/test"],
addAptKey: [{ keys: ["1E9377A2BA9EF27F"], fileName: "ubuntu-toolchain-r-test.gpg" }],
},
])
}
} else {
@ -120,7 +130,12 @@ export async function setupGcc(version: string, setupDir: string, arch: string,
if (isArch()) {
await setupPacmanPack("gcc-multilib", version)
} else if (isUbuntu()) {
await installAptPack([{ name: "gcc-multilib", version, repositories: ["ppa:ubuntu-toolchain-r/test"] }])
await installAptPack([{
name: "gcc-multilib",
version,
repositories: ["ppa:ubuntu-toolchain-r/test"],
addAptKey: [{ keys: ["1E9377A2BA9EF27F"], fileName: "ubuntu-toolchain-r-test.gpg" }],
}])
}
}
break
@ -162,7 +177,12 @@ export async function setupMingw(version: string, setupDir: string, arch: string
installationInfo = await setupDnfPack([{ name: "mingw64-gcc", version }])
} else if (isUbuntu()) {
installationInfo = await installAptPack([
{ name: "mingw-w64", version, repositories: ["ppa:ubuntu-toolchain-r/test"] },
{
name: "mingw-w64",
version,
repositories: ["ppa:ubuntu-toolchain-r/test"],
addAptKey: [{ keys: ["1E9377A2BA9EF27F"], fileName: "ubuntu-toolchain-r-test.gpg" }],
},
])
}
break

View File

@ -1,6 +1,6 @@
import { execRootSync } from "admina"
import { dirname } from "patha"
import { addAptKeyViaDownload, hasNala, installAptPack } from "setup-apt"
import { addAptKeyViaURL, hasNala, installAptPack } from "setup-apt"
import which from "which"
import { isUbuntu } from "../utils/env/isUbuntu.js"
@ -24,10 +24,10 @@ export async function setupNala(version: string, _setupDir: string, _arch: strin
await installAptPack([{ name: "python3-apt" }])
// https://gitlab.com/volian/nala/-/wikis/Installation
const keyFileName = await addAptKeyViaDownload(
"volian-archive-nala.gpg",
"https://deb.volian.org/volian/nala.key",
)
const keyFileName = await addAptKeyViaURL({
fileName: "volian-archive-nala.gpg",
keyUrl: "https://deb.volian.org/volian/nala.key",
})
execRootSync("/bin/bash", [
"-c",
`echo "deb [signed-by=${keyFileName}] http://deb.volian.org/volian/ nala main" | tee /etc/apt/sources.list.d/volian-archive-nala.list`,

View File

@ -103,7 +103,7 @@ export async function setupPowershellSystem(version: string | undefined, _setupD
execRootSync("dpkg", ["-i", "packages-microsoft-prod.deb"])
// TODO Debian
// const keyFileName = await addAptKeyViaDownload(
// const keyFileName = await addAptKeyViaURL(
// "microsoft.asc",
// "https://packages.microsoft.com/keys/microsoft.asc"
// )

View File

@ -76,7 +76,7 @@ async function setupWheel(foundPython: string) {
}
}
async function findOrSetupPython(version: string, setupDir: string, arch: string) {
async function findOrSetupPython(version: string, setupDir: string, arch: string): Promise<InstallationInfo> {
let installInfo: InstallationInfo | undefined
let foundPython = await findPython(setupDir)
@ -108,12 +108,12 @@ async function findOrSetupPython(version: string, setupDir: string, arch: string
}
}
if (foundPython === undefined || installInfo.bin === undefined) {
if (foundPython === undefined || installInfo?.bin === undefined) {
foundPython = await findPython(setupDir)
if (foundPython === undefined) {
throw new Error("Python binary could not be found")
}
installInfo.bin = foundPython
installInfo = { bin: foundPython, installDir: dirname(foundPython), binDir: dirname(foundPython) }
}
return installInfo