Created
February 17, 2022 10:02
-
-
Save VottusCode/45915f46bf2291c64a80cbd45203b44f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const {BaseCmdRemote} = require("@remotefull/commander/dist/remote/BaseCmdRemote") | |
class AptCommandError extends Error { | |
/** | |
* Raw command output. | |
* @type {string[]|null} | |
*/ | |
rawOut; | |
/** | |
* @param {string} message | |
* @param {string[]|null} rawOut | |
*/ | |
constructor(message, rawOut = null) { | |
super(message); | |
this.name = "AptCommandError"; | |
this.rawOut = rawOut; | |
} | |
} | |
class AptPackageError extends AptCommandError { | |
/** | |
* Package name related to the error. | |
* @type {string} | |
*/ | |
packageName; | |
/** | |
* @param {string} message | |
* @param {string} packageName | |
* @param {string[]|null} rawOut | |
*/ | |
constructor(message, packageName, rawOut = null) { | |
super(message, rawOut); | |
this.name = "AptPackageError"; | |
this.packageName = packageName; | |
} | |
} | |
class AptPackageAlreadyInstalledError extends AptPackageError { | |
/** | |
* Version of the package that is already installed. | |
* @type {string} | |
*/ | |
packageVersion; | |
/** | |
* | |
* @param {string} packageName | |
* @param {string} packageVersion | |
* @param {string[]|null} rawOut | |
*/ | |
constructor(packageName, packageVersion, rawOut = null) { | |
super(`${packageName} is already installed (${packageVersion}).`, packageName, rawOut); | |
this.packageVersion = packageVersion; | |
} | |
} | |
class AptUnableToLocatePackageError extends AptPackageError { | |
/** | |
* @param {string} packageName | |
* @param {string[]|null} rawOut | |
*/ | |
constructor(packageName, rawOut = null) { | |
super(`Unable to locate package ${packageName}`, packageName, rawOut); | |
this.name = "AptUnableToLocatePackageError"; | |
} | |
} | |
class AptDpkgInterruptedError extends AptCommandError { | |
/** | |
* @param {string} message | |
* @param {string[]|null} rawOut | |
*/ | |
constructor(message, rawOut) { | |
super(message, rawOut); | |
this.name = "AptDpkgInterruptedError"; | |
} | |
} | |
class AptManager { | |
/** | |
* The command remote used to fetch apt package info. | |
* @type {BaseCmdRemote} | |
*/ | |
cmd; | |
/** | |
* Locations of required binaries | |
* @type {{ dpkgQuery: string, apt: string, addAptRepository: string }} | |
*/ | |
bins; | |
/** | |
* @param {BaseCmdRemote} cmd | |
* @param {{ dpkgQuery?: string, apt?: string, addAptRepository?: string }} bins | |
*/ | |
constructor(cmd, { | |
dpkgQuery = "/usr/bin/dpkg-query", | |
apt = "/usr/bin/apt", | |
addAptRepository = "/usr/bin/add-apt-repository" | |
}) { | |
this.cmd = cmd; | |
this.bins = {dpkgQuery, apt, addAptRepository} | |
} | |
/** | |
* Checks whether a package is installed. | |
* @param {string} packageName | |
* @returns {Promise<boolean>} | |
*/ | |
async isInstalled(packageName) { | |
const cmd = `${this.bins.dpkgQuery} --show --showformat='\${db:Status-Status}\\n' ${packageName}` | |
return (await this.cmd.execute(cmd)).trim() === "installed"; | |
} | |
/** | |
* | |
* @param command | |
* @param {boolean?} throwAlreadyInstalled | |
* @returns {Promise<string[]>} | |
*/ | |
async executeAptCommand(command, throwAlreadyInstalled = false) { | |
const noPkgRegex = /Unable to locate package (.*)/; | |
const alreadyInstalledRegex = /(.*) is already the newest version \((.*)\)\./; | |
const dpkgErrorRegex = /dpkg was interrupted, you must manually run 'dpkg --configure -a' to correct the problem./; | |
return new Promise(async (resolve, reject) => { | |
const cmd = `DEBIAN_FRONTEND=noninteractive ${this.bins.apt} -o=Dpkg::Use-Pty=0 ${command} -y 2>> /dev/stdout`; | |
const stream = await this.cmd.executeWithStream(cmd); | |
let out = []; | |
stream.on("data", (chunk) => { | |
// Make sure that the chunk is a string. | |
chunk = String(chunk).trim(); | |
const skip = [ | |
"WARNING:", | |
"apt", | |
"does not have a stable CLI interface.", | |
"Use with caution in scripts.", | |
"WARNING: apt does not have a stable CLI interface. Use with caution in scripts.", | |
"E", | |
":" | |
]; | |
// Empty-lines, new-lines and skip | |
if (chunk === "\n" || chunk.trim().length < 1 || skip.includes(chunk)) | |
return; | |
out.push(chunk); | |
console.log(`out: "${chunk}"`) | |
let match; | |
if ((match = chunk.match(noPkgRegex))) { | |
reject(new AptUnableToLocatePackageError(match[1], out)) | |
} | |
if (throwAlreadyInstalled && (match = chunk.match(alreadyInstalledRegex))) { | |
reject(new AptPackageAlreadyInstalledError(match[1], match[2], out)) | |
} | |
if ((match = chunk.match(dpkgErrorRegex))) { | |
reject(new AptDpkgInterruptedError(match[0], out)); | |
} | |
}) | |
stream.on("error", reject) | |
stream.on("end", () => resolve(out)) | |
}) | |
} | |
/** | |
* Installs a package. | |
* | |
* @param {string} packageName; | |
* @param {boolean?} throwAlreadyInstalled | |
* @return {Promise<string[]>} the apt output | |
*/ | |
async install(packageName, throwAlreadyInstalled = false) { | |
return this.executeAptCommand(`install ${packageName} -y`, throwAlreadyInstalled); | |
} | |
/** | |
* Purges a package. | |
* | |
* @param {string} packageName | |
* @return {Promise<string[]>} the apt output | |
*/ | |
async purge(packageName) { | |
return this.executeAptCommand(`purge ${packageName} -y`); | |
} | |
/** | |
* Check whether software-properties-common and its dependencies are installed. | |
* @returns {Promise<void>} | |
*/ | |
async checkForSpc() { | |
for (const pkg of ["ca-certificates", "apt-transport-https", "software-properties-common"]) { | |
if (!(await this.isInstalled(pkg))) { | |
await this.install(pkg) | |
} | |
} | |
} | |
/** | |
* Add a repository using apt-add-repository. | |
* @param {string} repository | |
* @param {boolean?} checkForSpc | |
* @returns {Promise<string>} apt command output | |
*/ | |
async addRepository(repository, checkForSpc = true) { | |
if (checkForSpc) await this.checkForSpc(); | |
return await this.cmd.execute(`${this.bins.addAptRepository} ${repository} -y`); | |
} | |
/** | |
* Get all repositories. | |
* @return {Promise<{ repository: string, branches: string[], rawEntry: string }[]>} | |
*/ | |
async getRepositories() { | |
const ex = /deb (.*)/; | |
return (await this.cmd.execute("find /etc/apt/ -name *.list | xargs cat | grep ^[[:space:]]*deb")) | |
.split("\n") | |
.filter((line) => line.trim().length >= 1 && ex.test(line)) | |
.map((raw) => { | |
/** | |
* @type {RegExpExecArray} | |
*/ | |
const match = raw.match(ex); | |
const arr = match[1].split(" "); | |
return { | |
repository: arr.shift(), | |
branches: arr, | |
rawEntry: raw | |
} | |
}) | |
} | |
/** | |
* Executes the "update" command. | |
* @returns {Promise<string[]>} | |
*/ | |
async update() { | |
return await this.executeAptCommand("update -y"); | |
} | |
} | |
module.exports = { | |
AptManager, | |
AptCommandError, | |
AptPackageError, | |
AptPackageAlreadyInstalledError, | |
AptUnableToLocatePackageError, | |
AptDpkgInterruptedError | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment