Three logical changes grouped:
1. Widget bundle rebuild + sami-files logo (from previous session)
- status/dist/{init,core,features,onboarding}.js rebuilt from latest source
- status/sw.js cache bumped to dashcaddy-shell-594ec75648 to force SW refresh
- status/assets/sami-files.png added (Sami Files service card logo)
2. status/build.js: include monitoring-widgets.js in bundle
- The original build.js was missing monitoring-widgets.js from its JS()
bundle list — that's why the System Overview widget never showed up
in the live init.js until we ran the live /var/www/dashcaddy-status/
build.js. Now consistent.
3. dashcaddy-api/scripts/dashcaddy-update.sh restart_container(): preserve
TOTP secret across container recreates
- Was only setting SERVICES_FILE; container fell back to image-local
/app/credentials.json + /app/.encryption-key (auto-generated fresh
every recreate), which broke TOTP for the bind-mounted secret at
/app/data/credentials.json
- Added CREDENTIALS_FILE + ENCRYPTION_KEY_FILE env vars pointing at
/app/data/ so the container reads from the bind-mounted host data dir
- See skill: software-development/dashcaddy/references/totp-and-system-overview-pitfalls.md §9
4. Auto-updater integration (pulled from upstream release):
- dashcaddy-api/VERSION: dev → c64bbe2
- dashcaddy-api/health-checker.js, middleware.js, package.json,
routes/backups.js, src/app.js: new release code (bundled workflows,
/api/auth/ → /api/v1/ back-compat rewrite, backup storage limits)
- Recreate status/js/monitoring-widgets.js with robust services count
(reads from window.APPS, #cards DOM, then live fetch as fallback)
- Add sami-files service to data/services.json (Sami Files card)
- Add sami-files template to app-templates.js under 'Files' category
with full systemd deployment docs and Caddy snippet
- Bundle monitoring-widgets.js into init.js
- Add backup_data_dir() and restore_data_dir() using rsync
- Data backed up to backups/{version}/data-backup/ alongside code
- restore_data_dir() called in all three rollback paths (build fail, restart fail, health check fail)
- Add restart_container() that does rm + run to apply new env vars
- Handle action=rollback explicitly (no new version deployment)
- Uses standalone docker build instead of compose for reliability
- Add start.sh at /opt/dashcaddy/start.sh for reboot survival
The data/ directory (services.json, config.json, credentials,
TOTP config, notifications) was never included in the update
backup. Every update wiped user data — services, licenses,
credentials — requiring manual restore.
Now the host-side updater:
- Backs up data/ alongside code files before any update
- Restores data/ on rollback (build failure, restart failure,
or health-check failure)
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.
- self-updater: per-instance notify secret (auto-generated), notifyAndApply()
triggers an immediate check+apply for the publishing host
- routes: POST /api/system/update-notify (X-DashCaddy-Notify-Secret gated,
added to public-routes allowlist so TOTP doesn't block machine-to-machine)
- dashcaddy-update.sh: include VERSION in backup/deploy/rollback copy lists;
belt-and-suspenders write trigger.json commit to VERSION post-deploy.
Fixes drift where /app/VERSION stayed at the old commit after self-update.
- release.sh: mirror failures are non-fatal+loud; HTTP-verify get2 after
rsync; auto-notify co-located instance via /opt/dashcaddy/updates/notify-secret
(or honour DASHCADDY_NOTIFY_TARGETS for multi-instance setups).
Two bugs in the host-side updater script:
1. The Dockerfile (since f5fe32b) does \`COPY src/ ./src/\`, but the
host script never copies src/ from staging into the api source
directory. Result: every update fails with
"failed to compute cache key: ... '/src': not found".
2. \`cp -rf staging/routes api_source/routes/\` does NOT replace the
destination directory — it copies the source dir INTO the
destination, producing api_source/routes/routes/. Means new route
files end up nested one level deep and never get loaded by
server.js, so updates silently regress route handlers even when
the build succeeds.
Switch to "rm -rf dest && cp -rf src dest" semantics for both routes
and src, in all four touch points (deploy + 3 rollback paths).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`MakeDirectory=yes` on a `PathChanged=` directive whose target is a
file (not a directory) causes systemd to create the watched path as
an empty directory on unit start. The container's self-updater then
crashes with EISDIR every time it tries to writeFile() the trigger,
and the host script never runs.
The parent `/opt/dashcaddy/updates/` is already created by the
installer/Docker volume, so the flag is redundant and only here as
a footgun. Drop it.
Reproducer: enable the unit on a fresh system, watch
`/opt/dashcaddy/updates/trigger.json` get materialized as a directory
within milliseconds of `systemctl start dashcaddy-updater.path`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the in-container self-updater downloads a new version, it writes
trigger.json. The new systemd path unit watches for this file and runs
dashcaddy-update.sh, which backs up current API files, copies the new
ones, rebuilds the container, verifies health, and writes result.json.
Automatic rollback on build or health check failure.
Also fixes undefined `isWindows` variable in self-updater.js and adds
DASHCADDY_HOST_UPDATES_DIR env var to the installer's docker-compose
template for correct container-to-host path translation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 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>
- 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>
Full codebase including API server (32 modules + routes), dashboard frontend,
DashCA certificate distribution, installer script, and deployment skills.