diff --git a/packages/stakers/src/consensus.ts b/packages/stakers/src/consensus.ts index 7c627acbe0..83d95e32d7 100644 --- a/packages/stakers/src/consensus.ts +++ b/packages/stakers/src/consensus.ts @@ -134,9 +134,9 @@ export class Consensus extends StakerComponent { async persistSelectedConsensusIfInstalled(network: Network): Promise { const currentConsensusDnpName = this.DbHandlers[network].get(); if (currentConsensusDnpName) { - const isInstalled = await this.isPackageInstalled(currentConsensusDnpName); + const pkg = await listPackageNoThrow({ dnpName: currentConsensusDnpName }); - if (!isInstalled) { + if (!pkg) { // update status in db this.DbHandlers[network].set(undefined); return; @@ -144,7 +144,7 @@ export class Consensus extends StakerComponent { const userSettings = await this.getUserSettings(network, currentConsensusDnpName); - await this.setStakerPkgConfig({ dnpName: currentConsensusDnpName, isInstalled, userSettings }); + await this.setStakerPkgConfig({ dnpName: currentConsensusDnpName, pkg, userSettings }); await this.DbHandlers[network].set(currentConsensusDnpName); } diff --git a/packages/stakers/src/execution.ts b/packages/stakers/src/execution.ts index 1550d17c70..13dca05ae4 100644 --- a/packages/stakers/src/execution.ts +++ b/packages/stakers/src/execution.ts @@ -16,7 +16,7 @@ import { StakerComponent } from "./stakerComponent.js"; import { DappnodeInstaller } from "@dappnode/installer"; import * as db from "@dappnode/db"; import { params } from "@dappnode/params"; -import { listPackage } from "@dappnode/dockerapi"; +import { listPackage, listPackageNoThrow } from "@dappnode/dockerapi"; import { logs } from "@dappnode/logger"; import { gt } from "semver"; @@ -104,9 +104,9 @@ export class Execution extends StakerComponent { async persistSelectedExecutionIfInstalled(network: Network): Promise { const currentExecutionDnpName = this.DbHandlers[network].get(); if (currentExecutionDnpName) { - const isInstalled = await this.isPackageInstalled(currentExecutionDnpName); + const pkg = await listPackageNoThrow({ dnpName: currentExecutionDnpName }); - if (!isInstalled) { + if (!pkg) { // update status in db this.DbHandlers[network].set(undefined); return; @@ -114,7 +114,7 @@ export class Execution extends StakerComponent { const userSettings = await this.getUserSettings(network, currentExecutionDnpName); - await this.setStakerPkgConfig({ dnpName: currentExecutionDnpName, isInstalled, userSettings }); + await this.setStakerPkgConfig({ dnpName: currentExecutionDnpName, pkg, userSettings }); await this.DbHandlers[network].set(currentExecutionDnpName); } diff --git a/packages/stakers/src/mevBoost.ts b/packages/stakers/src/mevBoost.ts index 8e38197612..0d6d62a143 100644 --- a/packages/stakers/src/mevBoost.ts +++ b/packages/stakers/src/mevBoost.ts @@ -90,7 +90,7 @@ export class MevBoost extends StakerComponent { await this.setStakerPkgConfig({ dnpName: mevBoostDnpName, - isInstalled: true, + pkg: mevBoostDnp, userSettings }); diff --git a/packages/stakers/src/signer.ts b/packages/stakers/src/signer.ts index 8ec3d3ec85..7717e77a44 100644 --- a/packages/stakers/src/signer.ts +++ b/packages/stakers/src/signer.ts @@ -74,11 +74,11 @@ export class Signer extends StakerComponent { const signerDnp = await listPackageNoThrow({ dnpName: signerDnpName }); const isRunning = signerDnp?.containers.some((container) => container.running); - if (isRunning) { + if (isRunning && signerDnp) { const dnpName = Signer.CompatibleSigners[network].dnpName; const userSettings = this.getUserSettings(network); - await this.setStakerPkgConfig({ dnpName, isInstalled: true, userSettings }); + await this.setStakerPkgConfig({ dnpName, pkg: signerDnp, userSettings }); } } diff --git a/packages/stakers/src/stakerComponent.ts b/packages/stakers/src/stakerComponent.ts index 21e12a7b90..3f286e1ba4 100644 --- a/packages/stakers/src/stakerComponent.ts +++ b/packages/stakers/src/stakerComponent.ts @@ -3,9 +3,10 @@ import { ComposeFileEditor } from "@dappnode/dockercompose"; import { DappnodeInstaller, packageGetData, packageInstall } from "@dappnode/installer"; import { logs } from "@dappnode/logger"; import { InstalledPackageData, StakerItem, UserSettings, Network } from "@dappnode/types"; -import { getIsInstalled, getIsUpdated, getIsRunning, fileToGatewayUrl } from "@dappnode/utils"; +import { getIsInstalled, getIsUpdated, getIsRunning, fileToGatewayUrl, getDockerComposePathSmart } from "@dappnode/utils"; import { lt } from "semver"; import { params } from "@dappnode/params"; +import fs from "fs"; export class StakerComponent { protected dappnodeInstaller: DappnodeInstaller; @@ -95,18 +96,17 @@ protected async getAll({ } if (!newStakerDnpName) return; + // set staker config await this.setStakerPkgConfig({ dnpName: newStakerDnpName, - isInstalled: Boolean(await listPackageNoThrow({ dnpName: newStakerDnpName })), + pkg: await listPackageNoThrow({ dnpName: newStakerDnpName }), userSettings }); } protected async isPackageInstalled(dnpName: string): Promise { - const dnp = await listPackageNoThrow({ dnpName }); - - return Boolean(dnp); + return Boolean(await listPackageNoThrow({ dnpName })); } protected getComposeRootNetworks(network: Network): NonNullable["rootNetworks"] { @@ -129,15 +129,15 @@ protected async getAll({ */ protected async setStakerPkgConfig({ dnpName, - isInstalled, + pkg, userSettings }: { dnpName: string; - isInstalled: boolean; + pkg: InstalledPackageData | null; userSettings: UserSettings; }): Promise { - if (isInstalled) { - await this.setInstalledStakerPkgConfig({ dnpName, userSettings }); + if (pkg) { + await this.setInstalledStakerPkgConfig({ dnpName, pkg, userSettings }); } else { await packageInstall(this.dappnodeInstaller, { name: dnpName, @@ -148,25 +148,38 @@ protected async getAll({ private async setInstalledStakerPkgConfig({ dnpName, + pkg, userSettings }: { dnpName: string; + pkg: InstalledPackageData; userSettings: UserSettings; }): Promise { if (userSettings) { - const composeEditor = new ComposeFileEditor(dnpName, false); + const composePath = getDockerComposePathSmart(dnpName); + const composeBefore = fs.existsSync(composePath) ? fs.readFileSync(composePath, "utf-8") : ""; + const composeEditor = new ComposeFileEditor(dnpName, false); composeEditor.applyUserSettings(userSettings, { dnpName }); - // it must be called write after applying user settings otherwise the new settings will be lost and therefore the compose up will not have effect composeEditor.write(); + + const composeAfter = fs.readFileSync(composePath, "utf-8"); + if (composeBefore !== composeAfter) { + logs.warn(`setInstalledStakerPkgConfig: compose file changed for ${dnpName}`); + } } - // start all containers - await dockerComposeUpPackage({ - composeArgs: { dnpName }, - upAll: true - // dockerComposeUpOptions: { noRecreate: true } - }); + // Only start containers if at least one is not running. + // Calling docker compose up unconditionally can cause unnecessary container + // recreation when compose normalization produces non-meaningful file diffs + // (e.g. key reordering, quote changes) that Docker interprets as config changes. + if (pkg.containers.some((c) => !c.running)) { + logs.info(`At least one container of ${dnpName} is not running, recreating containers to apply changes`); + await dockerComposeUpPackage({ + composeArgs: { dnpName }, + upAll: true + }); + } } /**