11import deepmerge from "deepmerge" ;
22import { orderInstallPackages } from "./orderInstallPackages.js" ;
3- import { ComposeEditor , ComposeFileEditor } from "@dappnode/dockercompose" ;
3+ import {
4+ ComposeEditor ,
5+ ComposeFileEditor ,
6+ parseVolumeMappings ,
7+ stringifyVolumeMappings
8+ } from "@dappnode/dockercompose" ;
49import { getContainersStatus , listPackages } from "@dappnode/dockerapi" ;
510import { parseTimeoutSeconds } from "../utils.js" ;
611import {
@@ -12,9 +17,10 @@ import {
1217 NotificationsConfig ,
1318 NotificationsSettingsAllDnps
1419} from "@dappnode/types" ;
15- import { getBackupPath , getDockerComposePath , getImagePath , getManifestPath } from "@dappnode/utils" ;
20+ import { getBackupPath , getDockerComposePath , getImagePath , getManifestPath , isNotFoundError } from "@dappnode/utils" ;
1621import { gt } from "semver" ;
1722import { logs } from "@dappnode/logger" ;
23+ import { params } from "@dappnode/params" ;
1824
1925interface GetInstallerPackageDataArg {
2026 releases : PackageRelease [ ] ;
@@ -89,6 +95,9 @@ function getInstallerPackageData(
8995 const compose = new ComposeEditor ( release . compose , { dnpName } ) ;
9096 compose . applyUserSettings ( nextUserSet , { dnpName } ) ;
9197
98+ // Persist critical dappmanager env vars and volume paths across updates
99+ persistDappmanagerSettings ( compose , dnpName , isCore ) ;
100+
92101 const dockerTimeout = parseTimeoutSeconds ( release . manifest . dockerTimeout ) ;
93102
94103 return {
@@ -116,6 +125,78 @@ function getInstallerPackageData(
116125 } ;
117126}
118127
128+ /**
129+ * When updating the dappmanager package, certain environment variables and volume
130+ * settings from the currently installed compose must be preserved, even if the new
131+ * compose being installed doesn't define them. This ensures:
132+ * - DISABLE_HOST_SCRIPTS is not lost during upgrades
133+ * - DAPPNODE_CORE_DIR is not lost during upgrades
134+ * - The DNCORE volume bind mount uses the correct host path from DAPPNODE_CORE_DIR
135+ */
136+ function persistDappmanagerSettings ( compose : ComposeEditor , dnpName : string , isCore : boolean ) : void {
137+ if ( dnpName !== params . dappmanagerDnpName ) return ;
138+
139+ // Read the currently installed compose to get persisted env values
140+ let installedCompose : ComposeFileEditor ;
141+ try {
142+ installedCompose = new ComposeFileEditor ( dnpName , isCore ) ;
143+ } catch ( e ) {
144+ if ( ! isNotFoundError ( e ) ) throw e ;
145+ // Fresh install - no existing compose to read from
146+ logs . info ( "No installed dappmanager compose found, skipping settings persistence" ) ;
147+ return ;
148+ }
149+
150+ const envsToPreserve = [ "DISABLE_HOST_SCRIPTS" , "DAPPNODE_CORE_DIR" ] ;
151+ const DNCORE_CONTAINER_PATH = "/usr/src/app/DNCORE" ;
152+
153+ // Collect env values from the installed compose services
154+ const installedEnvs : Record < string , string > = { } ;
155+ for ( const serviceEditor of Object . values ( installedCompose . services ( ) ) ) {
156+ const envs = serviceEditor . getEnvs ( ) ;
157+ for ( const envName of envsToPreserve ) {
158+ if ( envs [ envName ] !== undefined && envs [ envName ] !== "" ) {
159+ installedEnvs [ envName ] = envs [ envName ] ;
160+ }
161+ }
162+ }
163+
164+ if ( Object . keys ( installedEnvs ) . length === 0 ) return ;
165+
166+ logs . info ( "Persisting dappmanager settings from installed compose" , installedEnvs ) ;
167+
168+ // Apply persisted envs and volume mapping to new compose services
169+ for ( const serviceEditor of Object . values ( compose . services ( ) ) ) {
170+ // Merge preserved envs into the new compose (installed values take priority)
171+ const envsToInject : Record < string , string > = { } ;
172+ for ( const envName of envsToPreserve ) {
173+ if ( installedEnvs [ envName ] !== undefined ) {
174+ envsToInject [ envName ] = installedEnvs [ envName ] ;
175+ }
176+ }
177+ if ( Object . keys ( envsToInject ) . length > 0 ) {
178+ serviceEditor . mergeEnvs ( envsToInject ) ;
179+ }
180+
181+ // Update the DNCORE volume host path to match DAPPNODE_CORE_DIR
182+ if ( installedEnvs [ "DAPPNODE_CORE_DIR" ] ) {
183+ const dncoreHostDir = installedEnvs [ "DAPPNODE_CORE_DIR" ] ;
184+ const service = serviceEditor . get ( ) ;
185+ if ( service . volumes ) {
186+ const volumeMappings = parseVolumeMappings ( service . volumes ) ;
187+ const updatedVolumes = volumeMappings . map ( ( vol ) => {
188+ // Match the volume whose container side is /usr/src/app/DNCORE
189+ if ( vol . container === DNCORE_CONTAINER_PATH ) {
190+ return { ...vol , host : dncoreHostDir , name : undefined } ;
191+ }
192+ return vol ;
193+ } ) ;
194+ service . volumes = stringifyVolumeMappings ( updatedVolumes ) ;
195+ }
196+ }
197+ }
198+ }
199+
119200/**
120201 * Migrates the user settings from the old service name to the new service name
121202 *
0 commit comments