Add auto-update system for DashCaddy instances

- self-updater.js: polls for new versions, downloads/verifies tarballs,
  triggers host-side rebuild via systemd path unit
- dashcaddy-update.sh + systemd units: host-side container rebuild with
  automatic rollback on health check failure
- 7 new /api/v1/system/* endpoints for version info, update check/apply,
  rollback, and update history
- Frontend: DashCaddy tab in Updates modal with version display,
  changelog, update button, rollback, and notification dot
- install.sh: updater service installation, volume mounts, env vars
- build-release.sh + webhook-handler.js: release server pipeline
  (Gitea webhook → build tarball → deploy to get.dashcaddy.net)
- Dockerfile: DASHCADDY_COMMIT build arg → VERSION file
- Version bump to 1.1.0

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-07 03:11:35 -08:00
parent 9a0abc02d1
commit ffa6966fd3
14 changed files with 1395 additions and 4 deletions

View File

@@ -49,6 +49,7 @@ const resourceMonitor = require('./resource-monitor');
const backupManager = require('./backup-manager');
const healthChecker = require('./health-checker');
const updateManager = require('./update-manager');
const selfUpdater = require('./self-updater');
const StateManager = require('./state-manager');
const auditLogger = require('./audit-logger');
const portLockManager = require('./port-lock-manager');
@@ -1160,7 +1161,7 @@ Object.assign(ctx, {
app, siteConfig, servicesStateManager, configStateManager,
credentialManager, authManager, licenseManager,
healthChecker, updateManager, backupManager, resourceMonitor,
auditLogger, portLockManager,
auditLogger, portLockManager, selfUpdater,
APP_TEMPLATES, TEMPLATE_CATEGORIES, DIFFICULTY_LEVELS, RECIPE_TEMPLATES, RECIPE_CATEGORIES,
asyncHandler, errorResponse, ok, fetchT, log, logError, safeErrorMessage,
buildDomain, buildServiceUrl, getServiceById, readConfig, saveConfig, addServiceToConfig,
@@ -1863,6 +1864,26 @@ const server = app.listen(PORT, '0.0.0.0', () => {
} catch (error) {
log.error('server', 'Update manager failed to start', { error: error.message });
}
try {
selfUpdater.start();
log.info('server', 'Self-updater started', { interval: selfUpdater.config.checkInterval, url: selfUpdater.config.updateUrl });
// Check for post-update result (did a previous update succeed or roll back?)
selfUpdater.checkPostUpdateResult().then(result => {
if (result) {
log.info('server', 'Post-update result', result);
if (typeof ctx.notification?.send === 'function') {
ctx.notification.send('system.update',
result.success ? 'DashCaddy Updated' : 'DashCaddy Update Failed',
result.success ? `Updated to v${result.version}` : `Update failed: ${result.error || 'Unknown'}. Rolled back.`,
result.success ? 'info' : 'error'
);
}
}
}).catch(() => {});
} catch (error) {
log.error('server', 'Self-updater failed to start', { error: error.message });
}
// Tailscale API sync (if OAuth configured)
if (tailscaleConfig.oauthConfigured) {
@@ -1881,6 +1902,7 @@ function shutdown(signal) {
backupManager.stop();
healthChecker.stop();
updateManager.stop();
selfUpdater.stop();
stopTailscaleSyncTimer();
server.close(() => {
log.info('shutdown', 'HTTP server closed');