diff --git a/packages/installer/src/calls/packageInstall.ts b/packages/installer/src/calls/packageInstall.ts index bd99fe9f7..944fe4fcb 100644 --- a/packages/installer/src/calls/packageInstall.ts +++ b/packages/installer/src/calls/packageInstall.ts @@ -10,7 +10,8 @@ import { rollbackPackages, writeAndValidateFiles, postInstallClean, - afterInstall + afterInstall, + checkInstallRequirements } from "../installer/index.js"; import { logs, getLogUi, logUiClear } from "@dappnode/logger"; import { Routes } from "@dappnode/types"; @@ -53,6 +54,7 @@ export async function packageInstall( if (!release.signedSafe && !options.BYPASS_SIGNED_RESTRICTION) { throw Error(`Package ${release.dnpName} is from untrusted origin and is not signed`); } + if (!release.isCore) await checkInstallRequirements({ manifest: release.manifest }); } // Gather all data necessary for the install diff --git a/packages/installer/src/installer/checkInstallRequirements.ts b/packages/installer/src/installer/checkInstallRequirements.ts new file mode 100644 index 000000000..7e332bdde --- /dev/null +++ b/packages/installer/src/installer/checkInstallRequirements.ts @@ -0,0 +1,60 @@ +import { listPackages, getDockerVersion } from "@dappnode/dockerapi"; +import { params } from "@dappnode/params"; +import { Manifest, InstalledPackageData } from "@dappnode/types"; +import { valid, gt } from "semver"; + +/** + * Get the install requirements and throw an error if they are not met + */ +export async function checkInstallRequirements({ manifest }: { manifest: Manifest }): Promise { + const installedPackages = await listPackages(); + const packagesRequiredToBeUninstalled = getRequiresUninstallPackages({ manifest, installedPackages }); + const requiresCoreUpdate = getRequiresCoreUpdate({ manifest, installedPackages }); + const requiresDockerUpdate = await getRequiresDockerUpdate({ manifest }); + + const errors: string[] = []; + if (packagesRequiredToBeUninstalled.length > 0) + errors.push(`The following packages must be uninstalled: ${packagesRequiredToBeUninstalled.join(", ")}`); + if (requiresCoreUpdate) errors.push("The core package must be updated"); + if (requiresDockerUpdate) errors.push("Docker must be updated"); + if (errors.length > 0) + throw new Error(`The package cannot be installed because of the following requirements: +${errors.join("\n")}`); +} + +function getRequiresUninstallPackages({ + manifest, + installedPackages +}: { + manifest: Manifest; + installedPackages: InstalledPackageData[]; +}): string[] { + const { notInstalledPackages } = manifest.requirements || {}; + if (!notInstalledPackages || notInstalledPackages.length === 0) return []; + return notInstalledPackages.filter((dnpName) => installedPackages.find((dnp) => dnp.dnpName === dnpName)); +} + +function getRequiresCoreUpdate({ + manifest, + installedPackages +}: { + manifest: Manifest; + installedPackages: InstalledPackageData[]; +}): boolean { + const coreDnp = installedPackages.find((dnp) => dnp.dnpName === params.coreDnpName); + if (!coreDnp) return false; + const coreVersion = coreDnp.version; + const minDnVersion = manifest.requirements ? manifest.requirements.minimumDappnodeVersion : ""; + return Boolean(minDnVersion && valid(minDnVersion) && valid(coreVersion) && gt(minDnVersion, coreVersion)); +} +async function getRequiresDockerUpdate({ manifest }: { manifest: Manifest }): Promise { + const minDockerVersion = manifest.requirements?.minimumDockerVersion; + if (!minDockerVersion) return false; + const currentDockerVersion = await getDockerVersion(); + return Boolean( + minDockerVersion && + valid(minDockerVersion) && + valid(currentDockerVersion) && + gt(minDockerVersion, currentDockerVersion) + ); +} diff --git a/packages/installer/src/installer/index.ts b/packages/installer/src/installer/index.ts index 52727c69f..7f31a81de 100644 --- a/packages/installer/src/installer/index.ts +++ b/packages/installer/src/installer/index.ts @@ -7,3 +7,4 @@ export * from "./rollbackPackages.js"; export * from "./runPackages.js"; export * from "./restartPatch.js"; export * from "./writeAndValidateFiles.js"; +export * from "./checkInstallRequirements.js";