fix(updates): route frontend deploy through host-side updater

When DashCaddy is installed without `${DASHBOARD_DIR}:/app/dashboard` bind
mounted into the container (e.g. legacy DNS2 setup where Caddy serves from
/var/www/dashcaddy-status/), the self-updater's in-container copy to
/app/dashboard was a silent no-op — leaving the dashboard stale across
self-updates, which led to CSP-hash mismatches and a broken UI.

- self-updater: new hostFrontendDir option (default `/var/www/dashcaddy-status`
  on Linux, overridable via DASHCADDY_HOST_FRONTEND_DIR). When set, defer the
  frontend copy to the host-side updater by passing frontendStagingDir +
  frontendTargetDir in trigger.json. Now also includes `js/` in the copy list.
- dashcaddy-update.sh: read those new trigger fields and sync the dashboard
  files on the host. Auto-detect fallback for older self-updaters (no fields
  in trigger.json) so a single release upgrade self-heals.
This commit is contained in:
Sami
2026-05-17 01:29:07 -07:00
parent 5b12c5c9c0
commit d7804d6b68
2 changed files with 61 additions and 3 deletions
+22 -3
View File
@@ -51,6 +51,14 @@ class SelfUpdater extends EventEmitter {
hostUpdatesDir: options.hostUpdatesDir || (platformPaths.isWindows ? options.updatesDir || DEFAULTS.UPDATES_DIR : '/opt/dashcaddy/updates'),
apiSourceDir: options.apiSourceDir || DEFAULTS.API_SOURCE_DIR,
frontendDir: options.frontendDir || DEFAULTS.FRONTEND_DIR,
// hostFrontendDir is the path on the HOST where Caddy serves the dashboard
// from. The in-container `frontendDir` is often a path that isn't mounted
// (e.g. /app/dashboard with no bind mount), so writing there is silently
// useless. When this is set, we pass it to the host-side updater script
// and skip the in-container copy entirely.
hostFrontendDir: options.hostFrontendDir
|| process.env.DASHCADDY_HOST_FRONTEND_DIR
|| (platformPaths.isWindows ? null : '/var/www/dashcaddy-status'),
maxBackups: parseInt(options.maxBackups || DEFAULTS.MAX_BACKUPS, 10),
channel: options.channel || process.env.DASHCADDY_UPDATE_CHANNEL || DEFAULTS.CHANNEL,
instanceIdFile: options.instanceIdFile || process.env.DASHCADDY_INSTANCE_ID_FILE || DEFAULTS.INSTANCE_ID_FILE,
@@ -241,11 +249,20 @@ class SelfUpdater extends EventEmitter {
await this._cleanDir(stagingDir);
await this._extractTarball(tarballPath, stagingDir);
// 4. Apply frontend files directly (zero-downtime)
// 4. Locate frontend source. The actual deploy is done either here (if no
// hostFrontendDir is configured, e.g. Windows or unusual setups) or by
// the host-side updater script via trigger.json (preferred path on Linux,
// where Caddy serves from /var/www/... outside the container's filesystem).
const frontendSrc = this._findDir(stagingDir, 'status');
if (frontendSrc) {
let hostFrontendStagingPath = null;
if (frontendSrc && this.config.hostFrontendDir) {
// Defer the copy to the host-side script. Just compute the host path
// for staging so it can find the files.
hostFrontendStagingPath = frontendSrc.replace(this.config.updatesDir, this.config.hostUpdatesDir);
} else if (frontendSrc) {
// No host path configured — copy in-container (legacy / Windows path).
await this._copyDir(frontendSrc, this.config.frontendDir, [
'dist', 'css', 'assets', 'vendor', 'index.html', 'sw.js'
'dist', 'css', 'assets', 'vendor', 'js', 'index.html', 'sw.js'
]);
this.emit('update-progress', { step: 'frontend-updated', version: remoteInfo.version });
}
@@ -265,6 +282,8 @@ class SelfUpdater extends EventEmitter {
fromVersion: local.version,
stagingDir: hostApiSrc,
apiSourceDir: this.config.apiSourceDir,
frontendStagingDir: hostFrontendStagingPath,
frontendTargetDir: this.config.hostFrontendDir || null,
timestamp: new Date().toISOString(),
channel: this.config.channel,
instanceId: this.instanceId,