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> **returns:** Promise<string>
### `addAptKeyViaDownload` (function) ### `addAptKeyViaURL` (function)
Add an apt key via a download Add an apt key via a download

View File

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

View File

@ -1,25 +1,78 @@
import { tmpdir } from "os" import { tmpdir } from "os"
import { join } from "path"
import { execRoot, execRootSync } from "admina" import { execRoot, execRootSync } from "admina"
import { warning } from "ci-log" import { warning } from "ci-log"
import { DownloaderHelper } from "node-downloader-helper" import { DownloaderHelper } from "node-downloader-helper"
import { pathExists } from "path-exists" import { pathExists } from "path-exists"
import { installAptPack } from "./install.js" import { installAptPack } from "./install.js"
function initGpg() { export type AddAptKeyOptions = KeyServerOptions | KeyUrl
execRootSync("gpg", ["-k"])
/**
* 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 * 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 * @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 { try {
const fileName = `/etc/apt/trusted.gpg.d/${name}` assertGpgFileName(fileName)
if (!(await pathExists(fileName))) { const filePath = join(keyStorePath, fileName)
if (!(await pathExists(filePath))) {
initGpg() initGpg()
await Promise.all( await Promise.all(
@ -27,43 +80,73 @@ export async function addAptKeyViaServer(keys: string[], name: string, server =
await execRoot("gpg", [ await execRoot("gpg", [
"--no-default-keyring", "--no-default-keyring",
"--keyring", "--keyring",
`gnupg-ring:${fileName}`, `gnupg-ring:${filePath}`,
"--keyserver", "--keyserver",
server, keyServer,
"--recv-keys", "--recv-keys",
key, key,
]) ])
await execRoot("chmod", ["644", fileName]) await execRoot("chmod", ["644", filePath])
}), }),
) )
} }
return fileName return filePath
} catch (err) { } catch (err) {
warning(`Failed to add apt key via server ${server}: ${err}`) warning(`Failed to add apt key via server ${keyServer}: ${err}`)
return undefined return undefined
} }
} }
export type KeyUrl = {
/**
* The URL to download the key from
*/
keyUrl: string
} & GpgKeyOptions
/** /**
* Add an apt key via a download * Add an apt key via a download
* @param name The name of the key * @param options The options for adding the key
* @param url The URL of the key
* @returns The file name of the key that was added * @returns The file name of the key that was added
*/ */
export async function addAptKeyViaDownload(name: string, url: string) { export async function addAptKeyViaURL({ keyUrl, fileName, keyStorePath = defaultKeyStorePath }: KeyUrl) {
const fileName = `/etc/apt/trusted.gpg.d/${name}` try {
if (!(await pathExists(fileName))) { assertGpgFileName(fileName)
initGpg() const filePath = join(keyStorePath, fileName)
if (!(await pathExists(filePath))) {
initGpg()
await installAptPack([{ name: "ca-certificates" }]) 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()
execRootSync("gpg", ["--no-default-keyring", "--keyring", `gnupg-ring:${fileName}`, "--import", `/tmp/${name}`]) const dlPath = join(tmpdir(), fileName)
execRootSync("chmod", ["644", 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 escapeRegex from "escape-string-regexp"
import { type ExecaError, execa } from "execa" import { type ExecaError, execa } from "execa"
import which from "which" 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 { isAptPackInstalled } from "./is-installed.js"
import { updateAptRepos } from "./update.js" import { updateAptRepos } from "./update.js"
@ -40,6 +40,8 @@ export type AptPackage = {
version?: string version?: string
/** The repositories to add before installing the package (optional) */ /** The repositories to add before installing the package (optional) */
repositories?: string[] repositories?: string[]
/** The keys to add before installing the package (optional) */
addAptKey?: AddAptKeyOptions[]
} }
const retryErrors = [ const retryErrors = [
@ -83,8 +85,11 @@ export async function installAptPack(packages: AptPackage[], update = false): Pr
didInit = true didInit = true
} }
// Install
try { try {
// Add the keys if needed
await addAptKeys(packages)
// Install
execRootSync(apt, ["install", "--fix-broken", "-y", ...needToInstall], { execRootSync(apt, ["install", "--fix-broken", "-y", ...needToInstall], {
...defaultExecOptions, ...defaultExecOptions,
env: getAptEnv(apt), env: getAptEnv(apt),
@ -107,6 +112,14 @@ export async function installAptPack(packages: AptPackage[], update = false): Pr
return { binDir: "/usr/bin/" } 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 { function isExecaError(err: unknown): err is ExecaError {
return typeof (err as ExecaError).stderr === "string" return typeof (err as ExecaError).stderr === "string"
} }
@ -285,9 +298,4 @@ async function initApt(apt: string) {
env: getAptEnv(apt), 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": { "devDependencies": {
"path-exists": "5.0.0", "path-exists": "5.0.0",
"patha": "0.4.1" "patha": "0.4.1",
"cross-spawn": "7.0.3"
}, },
"engines": { "engines": {
"node": ">=12" "node": ">=12"

View File

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

View File

@ -1,5 +1,5 @@
import { execRootSync } from "admina" import { execRootSync } from "admina"
import { addAptKeyViaDownload, installAptPack } from "setup-apt" import { addAptKeyViaURL, installAptPack } from "setup-apt"
import { installBrewPack } from "setup-brew" import { installBrewPack } from "setup-brew"
import { hasDnf } from "../utils/env/hasDnf.js" import { hasDnf } from "../utils/env/hasDnf.js"
import { isArch } from "../utils/env/isArch.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" }]) return setupDnfPack([{ name: "bazel4" }])
} else if (isUbuntu()) { } else if (isUbuntu()) {
// https://bazel.build/install/ubuntu // https://bazel.build/install/ubuntu
const keyFileName = await addAptKeyViaDownload( const keyFileName = await addAptKeyViaURL({
"bazel-archive-keyring.gpg", fileName: "bazel-archive-keyring.gpg",
"https://bazel.build/bazel-release.pub.gpg", keyUrl: "https://bazel.build/bazel-release.pub.gpg",
) })
execRootSync("bash", [ execRootSync("bash", [
"-c", "-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`, `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()) { } else if (isUbuntu()) {
installationInfo = await installAptPack([ 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 { } else {
@ -120,7 +130,12 @@ export async function setupGcc(version: string, setupDir: string, arch: string,
if (isArch()) { if (isArch()) {
await setupPacmanPack("gcc-multilib", version) await setupPacmanPack("gcc-multilib", version)
} else if (isUbuntu()) { } 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 break
@ -162,7 +177,12 @@ export async function setupMingw(version: string, setupDir: string, arch: string
installationInfo = await setupDnfPack([{ name: "mingw64-gcc", version }]) installationInfo = await setupDnfPack([{ name: "mingw64-gcc", version }])
} else if (isUbuntu()) { } else if (isUbuntu()) {
installationInfo = await installAptPack([ 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 break

View File

@ -1,6 +1,6 @@
import { execRootSync } from "admina" import { execRootSync } from "admina"
import { dirname } from "patha" import { dirname } from "patha"
import { addAptKeyViaDownload, hasNala, installAptPack } from "setup-apt" import { addAptKeyViaURL, hasNala, installAptPack } from "setup-apt"
import which from "which" import which from "which"
import { isUbuntu } from "../utils/env/isUbuntu.js" 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" }]) await installAptPack([{ name: "python3-apt" }])
// https://gitlab.com/volian/nala/-/wikis/Installation // https://gitlab.com/volian/nala/-/wikis/Installation
const keyFileName = await addAptKeyViaDownload( const keyFileName = await addAptKeyViaURL({
"volian-archive-nala.gpg", fileName: "volian-archive-nala.gpg",
"https://deb.volian.org/volian/nala.key", keyUrl: "https://deb.volian.org/volian/nala.key",
) })
execRootSync("/bin/bash", [ execRootSync("/bin/bash", [
"-c", "-c",
`echo "deb [signed-by=${keyFileName}] http://deb.volian.org/volian/ nala main" | tee /etc/apt/sources.list.d/volian-archive-nala.list`, `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"]) execRootSync("dpkg", ["-i", "packages-microsoft-prod.deb"])
// TODO Debian // TODO Debian
// const keyFileName = await addAptKeyViaDownload( // const keyFileName = await addAptKeyViaURL(
// "microsoft.asc", // "microsoft.asc",
// "https://packages.microsoft.com/keys/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 installInfo: InstallationInfo | undefined
let foundPython = await findPython(setupDir) 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) foundPython = await findPython(setupDir)
if (foundPython === undefined) { if (foundPython === undefined) {
throw new Error("Python binary could not be found") throw new Error("Python binary could not be found")
} }
installInfo.bin = foundPython installInfo = { bin: foundPython, installDir: dirname(foundPython), binDir: dirname(foundPython) }
} }
return installInfo return installInfo