[grade=B urn:ump:zluxbji5tepzdqybrctqfyjdvbfin2o66atw6rwykg6vdwkzhfsa] backlog v3: P6 Shipdeck era (DC-109-119) cross-platform + barrier-removal lane
- DC-109-112: native runtime, installer remote mode, self-distribution, updater v2 (Shipdeck engine; v0 DoD-verified) - DC-113-115: first-run doctor, auth-gate helper, update nudge - DC-116-119: Podman runtime, service-control abstraction, Mac signing (electron-builder, 5 acceptance gates), 3-OS CI matrix - DC-P6-EVIDENCE.md: verbatim live-state evidence (E1-E6) backing the claims - DC-102 status corrected (mismatched DiskSpaceMonitor annotation)
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
# Evidence file for DC-PRODUCTION-GRADE-BACKLOG.md P6 section (rev14)
|
||||
|
||||
Verbatim excerpts captured 2026-09-14 from the production tree `/opt/dashcaddy`
|
||||
and the live DNS2 deployment, so every live-state claim in the backlog can be
|
||||
checked against this file.
|
||||
|
||||
## E1. Self-updater contract — dashcaddy-api/src/docker/self-updater.js (production tree /opt/dashcaddy)
|
||||
|
||||
Verbatim grep output (`grep -n "UPDATE_URL\|MIRROR_URL\|CHANNEL\|checkInterval\|INTERVAL" ...`):
|
||||
```
|
||||
24: CHECK_INTERVAL: 30 * 60 * 1000, // 30 minutes
|
||||
25: UPDATE_URL: process.env.DASHCADDY_UPDATE_URL || 'https://get.dashcaddy.net/release',
|
||||
26: MIRROR_URL: process.env.DASHCADDY_MIRROR_URL || 'https://get2.dashcaddy.net/release',
|
||||
35: CHANNEL: process.env.DASHCADDY_UPDATE_CHANNEL || 'stable',
|
||||
46: checkInterval: parseInt(options.checkInterval || DEFAULTS.CHECK_INTERVAL, 10),
|
||||
47: updateUrl: options.updateUrl || DEFAULTS.UPDATE_URL,
|
||||
48: mirrorUrl: options.mirrorUrl || DEFAULTS.MIRROR_URL,
|
||||
64: channel: options.channel || process.env.DASHCADDY_UPDATE_CHANNEL || DEFAULTS.CHANNEL,
|
||||
```
|
||||
Revoked kill switch, verbatim (same file, lines 531/535):
|
||||
```js
|
||||
if (remote?.revoked === true) {
|
||||
```
|
||||
```js
|
||||
reason: 'release revoked',
|
||||
```
|
||||
Proves: 30-min poll interval, update + mirror feed URLs, channel selection,
|
||||
and the `revoked: true` kill switch — all in `self-updater.js` of the
|
||||
production tree.
|
||||
|
||||
## E2. Shipdeck v0 DoD — shipdeck/docs/SPEC.md, verbatim
|
||||
|
||||
```
|
||||
## Definition of done (v0) — MET 2026-09-14
|
||||
- [x] `shipdeck deploy` lands a real Go hello-world on samihost end-to-end:
|
||||
build → systemd active → caddy gate (tailnet 200 / public 403) → DNS on
|
||||
DNS2+DNS1 → HTTP health 200 → journal row (epochs 1789360016, …0128,
|
||||
…0610, …0672; rollback …0128→…0016 verified with content diff)
|
||||
- [x] `shipdeck status` all green; `shipdeck rollback` swaps + re-verifies green
|
||||
```
|
||||
|
||||
## E3. Version endpoint — production tree /opt/dashcaddy, verbatim grep output
|
||||
|
||||
PUBLIC_ROUTES membership (`src/utilities/middleware.js`):
|
||||
```
|
||||
488- { path: '/api/v1/monitoring/stats', exact: true, method: 'GET', monitoring: true },
|
||||
489- { path: '/api/v1/health-checks/status', exact: true, method: 'GET', monitoring: true },
|
||||
490: { path: '/api/v1/version', exact: true, method: 'GET' },
|
||||
```
|
||||
Route registration at startup (`src/app.js`, verbatim `sed -n '519,523p'`):
|
||||
```js
|
||||
appName = versionRoute.getName();
|
||||
// Pre-build the version router once at startup and reuse it.
|
||||
const versionRouter = versionRoute.buildRouter();
|
||||
apiRouter.use(versionRouter);
|
||||
log.info('app', `Version endpoint available at /api/v1/version (v${appVersion})`);
|
||||
```
|
||||
(The version HANDLER source lives in the route module wired by
|
||||
`versionRoute.buildRouter()` — `/api/v1/version` returns
|
||||
`{ name, version, node, platform, arch, uptime, instanceId }` from
|
||||
`package.json` read at startup, per the E5 live response below.)
|
||||
|
||||
## E4. Installer build targets — dashcaddy-installer/BUILD_GUIDE.md, verbatim
|
||||
|
||||
```
|
||||
# Windows (creates portable .exe and installer)
|
||||
# macOS zip (from any host; electron-builder's native target)
|
||||
npm run build:mac
|
||||
# macOS real drag-install .dmg — built ON LINUX, no Mac needed
|
||||
# (one-time toolchain: see scripts/build-dmg-linux.sh header)
|
||||
# Linux (creates AppImage and .deb)
|
||||
npm run build:linux
|
||||
```
|
||||
Output artifacts (same file): portable + NSIS .exe (Windows), mac .zip +
|
||||
Linux-built .dmg (macOS, unsigned), AppImage + .deb (Linux).
|
||||
|
||||
## E5. Live deployment state on DNS2 — complete captured output (rev14), capture start 2026-09-14T11:50:10Z, all three exit codes 0
|
||||
|
||||
The block below is the COMPLETE, unedited terminal capture of the three
|
||||
commands (per-command timestamp + exit status embedded). The first command's
|
||||
stdout is the full version.json — no ellipses; the long changelog string is
|
||||
part of the real response.
|
||||
|
||||
```
|
||||
=== E5 CAPTURE 2026-09-14T11:50:10Z — per-command timestamps below ===
|
||||
$ curl -sk --resolve get.dashcaddy.net:443:127.0.0.1 https://get.dashcaddy.net/release/version.json
|
||||
{
|
||||
"version": "1.16.0",
|
||||
"commit": "70e252c",
|
||||
"channel": "stable",
|
||||
"url": "https://get.dashcaddy.net/release/latest.tar.gz",
|
||||
"sha256": "f4dd3a6efe70a99c64b2b2093af13b79943846f83546c76fba9f99b24e489357",
|
||||
"publishedAt": "2026-09-13T12:45:26Z",
|
||||
"changelog": "DashCaddy v1.16.0 \u2014 self-updater hardening + auto-update enabled + docker disk discipline\n\nFIXES\n- self-updater: same-version releases are NEVER \"newer\" again (DC-122). Commit\n labels are opaque build stamps; only a semver bump counts. This bug made\n same-version installs with any commit-string difference report\n \"update available\" forever and would have re-applied stale tarballs in a\n loop once auto-update was enabled.\n- self-updater: _autoCheckAndApply skips re-applying an identical\n version@sha256 within a process lifetime (defense-in-depth against apply\n loops).\n- dashcaddy-update.sh: data-dir cp fallback copied the directory INTO the\n destination (nested data/data), so rollback restored nothing. Now copies\n contents (\"dir/.\") \u2014 backup AND restore paths fixed.\n- dashcaddy-update.sh: frontend is now snapshotted before deploy and restored\n on build-failure and health-check-failure rollbacks. Previously a failed\n update left the NEW frontend paired with the ROLLED BACK API.\n- start.sh: bundle sync is newer-source-only. The old unconditional copy\n reverted self-updater-deployed frontends on every container restart.\n- dashcaddy-update.sh: docker prune now runs on the success path, both\n failure paths, and after rollbacks (shared prune_docker helper).\n\nCHANGES\n- start.sh: DASHCADDY_UPDATE_ENABLED=true \u2014 auto-update is ON (Sami 2026-09-13).\n- start.sh + fallback docker run: json-file log caps (10MB x 3) so container\n logs can never grow unbounded again.\n\nTESTS\n- __tests__/self-updater-isnewer.test.js: 8 regression cases covering the\n same-version commit-mismatch bug, semver ordering, and edge inputs."
|
||||
}curl_exit=0 at 2026-09-14T11:50:10Z
|
||||
|
||||
$ curl -s -m 6 http://127.0.0.1:3001/api/v1/version
|
||||
{"success":true,"name":"dashcaddy-api","version":"1.16.0","node":"v20.11.1","platform":"linux","arch":"x64","uptime":1768.092256197,"instanceId":null}curl_exit=0 at 2026-09-14T11:50:10Z
|
||||
|
||||
$ docker inspect dashcaddy-api --format '{{range .Config.Env}}{{println .}}{{end}}' | grep UPDATE
|
||||
DASHCADDY_UPDATE_ENABLED=true
|
||||
grep_exit=0 at 2026-09-14T11:50:10Z
|
||||
```
|
||||
Proves, AS OF the capture timestamp (a dated deployment snapshot, not
|
||||
necessarily current state at review time): production DNS2 ran DashCaddy
|
||||
v1.16.0, channel `stable`, auto-update ON, feed reachable, and no `revoked`
|
||||
key in the live feed (kill switch not engaged). Re-run the three commands
|
||||
above to refresh.
|
||||
|
||||
## E6. Update-stamp contract — live + source capture, rev14
|
||||
|
||||
(a) Live stamp file — `/var/www/dashcaddy-status/update-stamp.json` (verbatim `cat`):
|
||||
```json
|
||||
{"version":"1.16.0","at":"2026-09-13T12:47:56Z"}
|
||||
```
|
||||
|
||||
(b) The ACTUAL writer: `/opt/dashcaddy/scripts/dashcaddy-update.sh` (host-side
|
||||
helper executed by the self-updater's update flow; the Node self-updater
|
||||
orchestrates, this script writes the stamp — see excerpt, verbatim `sed -n '714,725p'`):
|
||||
```bash
|
||||
cp -rf "$frontend_staging_dir/assets/"* "$frontend_target_dir/assets/" 2>/dev/null || true
|
||||
fi
|
||||
# DC-122: host-side deployment stamp — start.sh treats a stamped, newer
|
||||
# deployment as authoritative and skips its source-bundle sync (this is
|
||||
# the only writer that can reach the web root with real host paths).
|
||||
local esc_ver
|
||||
esc_ver=$(json_escape "$to_version")
|
||||
printf '{"version":"%s","at":"%s"}\n' "$esc_ver" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
||||
> "$frontend_target_dir/update-stamp.json" 2>/dev/null || true
|
||||
fi # DC-122 close: validated frontend-target branch
|
||||
fi
|
||||
|
||||
```
|
||||
(version JSON-escaped, UTC timestamp, written into the validated
|
||||
frontend-target dir; `|| true` only guards the stamp write itself — the
|
||||
deployment result is recorded separately in updates/result.json.)
|
||||
|
||||
(b2) Self-updater call-site — `/opt/dashcaddy/dashcaddy-api/src/docker/self-updater.js`
|
||||
lines 303-313 (verbatim `sed -n '303,313p'`), showing the orchestrator writing
|
||||
trigger.json and deferring stamp writing to the host-side helper:
|
||||
```js
|
||||
await fsp.writeFile(
|
||||
path.join(this.config.updatesDir, 'trigger.json'),
|
||||
JSON.stringify(trigger, null, 2)
|
||||
);
|
||||
|
||||
// DC-122 note: the frontend deployment stamp (update-stamp.json) is
|
||||
// written by the HOST-side dashcaddy-update.sh after it syncs the
|
||||
// frontend — the container has no bind mount for the web root, so
|
||||
// writing the stamp here would silently target the container layer.
|
||||
|
||||
// The host-side systemd service will handle the rest.
|
||||
```
|
||||
So the identity chain is: Node self-updater (orchestrator, writes
|
||||
trigger.json) → host-side `dashcaddy-update.sh` (executed via the systemd path
|
||||
unit; writes update-stamp.json into the web root). The word "writer" in (b)
|
||||
refers to dashcaddy-update.sh specifically. A repo-wide search was NOT run to
|
||||
prove it is the only writer; the excerpt above proves the intended division of
|
||||
labor, not uniqueness.
|
||||
|
||||
(c) Sync-gating side: `/opt/dashcaddy/start.sh` (verbatim `sed -n '150,170p'`) —
|
||||
start.sh treats the stamp as the frontend authority and skips its source-bundle
|
||||
sync while the stamp file's MTIME beats the source tree's mtime — authority is
|
||||
decided by comparing file mtimes, not by parsing version values inside the
|
||||
files (prevents restart-reverts):
|
||||
```
|
||||
|
||||
# Sync the freshly-built dashboard bundle into the static directory Caddy
|
||||
# serves — decided by VERSION METADATA, not file mtimes (mtimes are not
|
||||
# reliable: scp/tar/cp can preserve or shuffle them). DC-122 contract:
|
||||
# - The self-updater writes a STAMP (update-stamp.json) into the live web
|
||||
# root when it deploys a frontend; while that stamp is newer than the
|
||||
# source tree's VERSION file, start.sh must NOT touch the live bundle.
|
||||
# - Normal builds: publishing bumps the source VERSION (mtime = build time)
|
||||
# and clears any stale stamp, so source wins and the sync happens.
|
||||
echo "[start.sh] Syncing dashboard bundle into static dir (metadata-driven)..."
|
||||
mkdir -p /var/www/dashcaddy-status/dist
|
||||
if [ -d /opt/dashcaddy/status/dist ]; then
|
||||
NEEDS_SYNC=1
|
||||
STAMP=/var/www/dashcaddy-status/update-stamp.json
|
||||
SRC_VERSION=/opt/dashcaddy/dashcaddy-api/VERSION
|
||||
if [ -f "$STAMP" ] && [ -f "$SRC_VERSION" ] && [ "$STAMP" -nt "$SRC_VERSION" ]; then
|
||||
# A self-updater deployment is newer than the last source build: hands off.
|
||||
echo "[start.sh] Deployed frontend stamp newer than source VERSION — skipping sync to preserve deployed frontend."
|
||||
NEEDS_SYNC=0
|
||||
fi
|
||||
if [ "$NEEDS_SYNC" = "1" ]; then
|
||||
```
|
||||
(d) Rollback interplay: `dashcaddy-update.sh` restore_frontend() REMOVES the
|
||||
stamp on rollback (verbatim, lines 356-358) so start.sh's source sync resumes
|
||||
authority — the restored frontend is not a self-updater deployment:
|
||||
```bash
|
||||
# Rollback removes the deployment stamp: the restored frontend is NOT a
|
||||
# self-updater deployment, so start.sh's source sync must resume authority.
|
||||
rm -f "${target:?}/update-stamp.json"
|
||||
```
|
||||
|
||||
Proves the update-stamp-on-frontend-deploy behavior cited by DC-112 end to
|
||||
end: writer identity, stamp content (version + UTC timestamp), sync gating,
|
||||
and rollback semantics.
|
||||
|
||||
Reference in New Issue
Block a user