Fix auto-update pipeline bugs discovered in e2e testing
- Fix container-to-host path mapping in trigger.json (stagingDir was using container path /app/updates/ instead of host path /opt/dashcaddy/updates/) - Fix download race condition: primary download's async unlink could delete mirror download's file — use unlinkSync before retry - Fix DASHCADDY_COMMIT build arg not passed to docker compose build (was set as env var, now uses --build-arg) - Remove MakeDirectory=yes from systemd path unit (was creating trigger.json as directory instead of file) - Remove unused 'tar' npm module import - Add mirror fallback for tarball downloads (not just version checks) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -42,6 +42,9 @@ class SelfUpdater extends EventEmitter {
|
||||
updateUrl: options.updateUrl || DEFAULTS.UPDATE_URL,
|
||||
mirrorUrl: options.mirrorUrl || DEFAULTS.MIRROR_URL,
|
||||
updatesDir: options.updatesDir || DEFAULTS.UPDATES_DIR,
|
||||
// hostUpdatesDir is the HOST path that maps to updatesDir inside the container.
|
||||
// Used when writing trigger.json so the host-side script can find staging files.
|
||||
hostUpdatesDir: options.hostUpdatesDir || (isWindows ? options.updatesDir || DEFAULTS.UPDATES_DIR : '/opt/dashcaddy/updates'),
|
||||
apiSourceDir: options.apiSourceDir || DEFAULTS.API_SOURCE_DIR,
|
||||
frontendDir: options.frontendDir || DEFAULTS.FRONTEND_DIR,
|
||||
maxBackups: parseInt(options.maxBackups || DEFAULTS.MAX_BACKUPS, 10),
|
||||
@@ -102,12 +105,14 @@ class SelfUpdater extends EventEmitter {
|
||||
this.status = 'checking';
|
||||
try {
|
||||
let remote;
|
||||
let sourceUrl = this.config.updateUrl;
|
||||
try {
|
||||
remote = await this._fetchJson(`${this.config.updateUrl}/version.json`);
|
||||
} catch (primaryErr) {
|
||||
console.warn('[SelfUpdater] Primary server failed:', primaryErr.message, '— trying mirror');
|
||||
try {
|
||||
remote = await this._fetchJson(`${this.config.mirrorUrl}/version.json`);
|
||||
sourceUrl = this.config.mirrorUrl;
|
||||
} catch (mirrorErr) {
|
||||
this.status = 'idle';
|
||||
this.lastCheckTime = Date.now();
|
||||
@@ -120,7 +125,7 @@ class SelfUpdater extends EventEmitter {
|
||||
const available = this._isNewer(local, remote);
|
||||
|
||||
this.lastCheckTime = Date.now();
|
||||
this.lastCheckResult = { available, local, remote };
|
||||
this.lastCheckResult = { available, local, remote, sourceUrl };
|
||||
this.status = 'idle';
|
||||
|
||||
if (available) {
|
||||
@@ -147,13 +152,21 @@ class SelfUpdater extends EventEmitter {
|
||||
const stagingDir = path.join(this.config.updatesDir, 'staging');
|
||||
|
||||
try {
|
||||
// 1. Download
|
||||
// 1. Download (try primary, fallback to mirror)
|
||||
this.status = 'downloading';
|
||||
this.emit('update-progress', { step: 'downloading', version: remoteInfo.version });
|
||||
|
||||
const tarballUrl = `${this.config.updateUrl}/${remoteInfo.tarball}`;
|
||||
const tarballPath = path.join(this.config.updatesDir, remoteInfo.tarball);
|
||||
await this._downloadFile(tarballUrl, tarballPath);
|
||||
const primaryUrl = `${this.config.updateUrl}/${remoteInfo.tarball}`;
|
||||
const mirrorUrl = `${this.config.mirrorUrl}/${remoteInfo.tarball}`;
|
||||
try {
|
||||
await this._downloadFile(primaryUrl, tarballPath);
|
||||
} catch (dlErr) {
|
||||
console.warn('[SelfUpdater] Primary download failed:', dlErr.message, '— trying mirror');
|
||||
// Ensure file is fully cleaned up before mirror attempt
|
||||
try { fs.unlinkSync(tarballPath); } catch (_) {}
|
||||
await this._downloadFile(mirrorUrl, tarballPath);
|
||||
}
|
||||
|
||||
// 2. Verify SHA-256
|
||||
const hash = await this._computeSha256(tarballPath);
|
||||
@@ -184,12 +197,14 @@ class SelfUpdater extends EventEmitter {
|
||||
this.status = 'waiting';
|
||||
this.emit('update-progress', { step: 'triggering-rebuild', version: remoteInfo.version });
|
||||
|
||||
// Convert container path to host path for trigger.json
|
||||
const hostApiSrc = apiSrc.replace(this.config.updatesDir, this.config.hostUpdatesDir);
|
||||
const trigger = {
|
||||
action: 'update',
|
||||
version: remoteInfo.version,
|
||||
commit: remoteInfo.commit,
|
||||
fromVersion: local.version,
|
||||
stagingDir: apiSrc,
|
||||
stagingDir: hostApiSrc,
|
||||
apiSourceDir: this.config.apiSourceDir,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
@@ -285,11 +300,12 @@ class SelfUpdater extends EventEmitter {
|
||||
}
|
||||
|
||||
const local = this.getLocalVersion();
|
||||
const hostBackupDir = backupDir.replace(this.config.updatesDir, this.config.hostUpdatesDir);
|
||||
const trigger = {
|
||||
action: 'rollback',
|
||||
version: version,
|
||||
fromVersion: local.version,
|
||||
stagingDir: backupDir,
|
||||
stagingDir: hostBackupDir,
|
||||
apiSourceDir: this.config.apiSourceDir,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
@@ -508,6 +524,7 @@ const selfUpdater = new SelfUpdater({
|
||||
updateUrl: process.env.DASHCADDY_UPDATE_URL,
|
||||
mirrorUrl: process.env.DASHCADDY_MIRROR_URL,
|
||||
updatesDir: process.env.DASHCADDY_UPDATES_DIR,
|
||||
hostUpdatesDir: process.env.DASHCADDY_HOST_UPDATES_DIR,
|
||||
apiSourceDir: process.env.DASHCADDY_API_SOURCE_DIR,
|
||||
frontendDir: process.env.DASHCADDY_FRONTEND_DIR,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user