Skip to content

Wire Darling into the release pipeline#1340

Merged
erikdarlingdata merged 2 commits into
devfrom
feature/1262-darling-release-wiring
Jul 2, 2026
Merged

Wire Darling into the release pipeline#1340
erikdarlingdata merged 2 commits into
devfrom
feature/1262-darling-release-wiring

Conversation

@erikdarlingdata

Copy link
Copy Markdown
Owner

Wires the Darling headless edition (Windows-service collector + WPF viewer + bundled managed PostgreSQL, backend finished in #1338) into the release pipeline in .github/workflows/build.yml. Part of #1262.

Everything mirrors the existing Dashboard/Lite/Installer steps; the heavy/opaque parts (the ~340MB PostgreSQL runtime download, the EDB binaries) are handled deliberately.

What runs on a release now

On top of the existing Dashboard/Lite/Installer flow, a release: published build now also:

  1. Publishes PerformanceMonitor.Darling.Service and PerformanceMonitor.Darling.Viewer framework-dependent (-c Release -o publish/DarlingService / publish/DarlingViewer), exactly like Dashboard/Lite. These publish on every non-doc build too (build verification), same gate as Dashboard/Lite.
  2. Builds pg-runtime.zip via Darling/tools/fetch-pg-runtime.ps1 (pinned EDB PostgreSQL 17.10 + TimescaleDB 2.28.1, SHA256-verified) — release-only, behind an actions/cache.
  3. Uploads + signs the Darling service and viewer through the existing SignPath flow (our own exes/dlls only).
  4. Packages one portable PerformanceMonitorDarling-<version>.zip from the SIGNED binaries, with pg-runtime.zip placed beside the service exe.
  5. Rides the existing checksums + release upload steps unchanged (they already glob releases/*.zip).

Artifact list delta

Release assets gain exactly one file:

  • PerformanceMonitorDarling-<version>.zip (and its line in SHA256SUMS.txt)

No other artifact changes. pg-runtime.zip is not a standalone asset — it ships inside the Darling zip.

Decision: one zip vs two

One zip. The existing artifact set is one-zip-per-product (Dashboard, Lite, and Installer-with-its-SQL-scripts are each a single zip), so Darling — one product with two exes — is one zip too. Layout mirrors the Installer's "primary tool at root + a subfolder" shape:

PerformanceMonitorDarling-<version>.zip
├─ PerformanceMonitor.Darling.Service.exe   (primary deliverable, at root)
├─ pg-runtime.zip                            (beside the service exe)
├─ darling.sample.json                       (already in the service publish output)
├─ <service dlls + runtimes/ + localization dirs>
└─ viewer/
   └─ PerformanceMonitor.Darling.Viewer.exe  (+ its dlls)

pg-runtime.zip at the root is load-bearing: DarlingManagedPostgres looks for it at AppContext.BaseDirectory (verified in DarlingManagedPostgres.cs:98) and extracts it on first run, so the zip is ready to sc create in place with zero extra steps.

Decision: cache keyed on the fetch script's content hash

actions/cache@v4 caches the assembled Darling/artifacts/pg-runtime.zip with key pg-runtime-${{ runner.os }}-${{ hashFiles('Darling/tools/fetch-pg-runtime.ps1') }}.

  • Caching the assembled zip (not the raw downloads) means a re-release with unchanged pins skips both the ~340MB download and the assembly.
  • Keying on the fetch script's own hash is drift-free: the SHA256 pins + URLs live inside that script, so any version/pin bump edits the file, changes the hash, and invalidates the cache automatically — no pin value is duplicated into the workflow.
  • The cache step is early (before signing) and actions/cache saves in its post step, so even if a later step fails the runtime stays cached for the retry.

Signing situation

  • Repo-side (done here): upload + sign steps for the service and viewer, mirroring the Dashboard/Lite/Installer pattern (same org-id, policy-slug, wait-for-completion), reading from publish/DarlingService / publish/DarlingViewer and writing signed/DarlingService / signed/DarlingViewer. The final zip is built from the signed output.
  • pg-runtime.zip is never sent to SignPath. It is placed into the package after signing, so the EDB PostgreSQL binaries inside ship as opaque data and are never touched by our signing flow (per the ground rules).
  • Erik action required (SignPath, outside this repo): the two new artifact-configuration-slug values — DarlingService and DarlingViewer — must be created on signpath.io. That config (which files in the uploaded artifact get signed) is not stored in the repo, so I did not guess it; configure each to sign the apphost exe + PerformanceMonitor.* dlls and leave third-party assemblies alone. Until both slugs exist, the two new Sign steps will fail the release. This is flagged in an ACTION REQUIRED comment in the workflow too.

Velopack deferral

No Velopack for Darling v1 — plain zip only. A Windows service has a different update story than the WPF apps' Setup.exe/Velopack model, and it's deliberately deferred. Noted in a workflow comment and the CHANGELOG.

Version stamping

The service and viewer csprojs had no version. Every other shipped entrypoint (Dashboard, Lite, Installer, Installer.Core) carries <Version>3.1.0</Version> + <AssemblyVersion>3.1.0.0</AssemblyVersion> + <FileVersion>3.1.0.0</FileVersion>, so I added the same triplet to both Darling entrypoints. The zip name itself reuses the Dashboard-derived VERSION (unchanged), so this only affects the binaries' own file metadata. Note for release cuts: these two csprojs now join the set of files to version-bump (the check-version-bump gate still only reads Dashboard.csproj, unchanged).

What I validated locally

  • YAML: actionlint isn't installed, so I parsed all three workflows with PyYAML and asserted structure: all 9 new steps present, no duplicate step ids, the Sign steps reference the correct upload-artifact ids/slugs/output dirs, and the cache step is wired (id: cache-pg-runtime, actions/cache@v4, correct path, and the Build step gated on cache-hit != 'true'). All green.
  • Packaging (executed, not eyeballed): dotnet publish both Darling projects (Release) to a temp dir, staged a simulated signed/ + Darling/artifacts/pg-runtime.zip (copied from a prebuilt runtime — no 340MB re-download), and ran the verbatim Package Darling (signed) + Generate checksums commands. Result: a 133.6 MB PerformanceMonitorDarling-3.1.0.zip and a correct SHA256 line.
  • Observed zip layout (via zipfile.namelist() — 242 entries, all forward-slash separators):
    • root: PerformanceMonitor.Darling.Service.exe, pg-runtime.zip (51.8 MB), darling.sample.json
    • viewer/PerformanceMonitor.Darling.Viewer.exe
    • runtimes/… service assets
    • pg-runtime.zip appears at root only (not under viewer/, not inside the signed upload).
  • Confirmed darling.sample.json is in the service publish output (README claim holds; no csproj fix needed).
  • Tests: dotnet test Darling/Darling.Tests -c Debug → 169 passed, 16 skipped (env-gated DARLING_TEST_PG* E2E), 0 failed.

Could NOT validate (CI-only)

  • The actual SignPath submissions (need the org token + the two new slugs to exist).
  • actions/cache hit/miss + save behavior on the real runner.
  • The live fetch-pg-runtime download/verify path — I did not change that script (pins untouched) and reused a prebuilt zip for layout tests, so there was nothing script-side to re-verify.

Nothing found broken

fetch-pg-runtime.ps1 pins untouched; Darling/artifacts/ is already gitignored; the local package-release.cmd/build-*.cmd helpers are intentionally left out of scope (they don't sign or fetch the runtime, so they can't produce a complete Darling package).

🤖 Generated with Claude Code

erikdarlingdata and others added 2 commits July 2, 2026 17:52
Release builds now publish the Darling headless edition (Windows-service
collector + WPF viewer) and ship it as one portable zip, alongside the
existing Dashboard/Lite/Installer artifacts.

- Publish PerformanceMonitor.Darling.Service and .Viewer framework-dependent,
  mirroring the Dashboard/Lite publish steps.
- Build the bundled pg-runtime.zip (pinned EDB PostgreSQL 17.10 + TimescaleDB
  2.28.1) via Darling/tools/fetch-pg-runtime.ps1, release-only (it downloads
  ~340MB) and cached with actions/cache keyed on the fetch script's own
  content hash, so re-releases with unchanged pins skip the download.
- Package PerformanceMonitorDarling-<version>.zip from the SIGNED binaries:
  service at the archive root with pg-runtime.zip beside its exe (where
  DarlingManagedPostgres extracts it on first run) and the viewer in a
  viewer/ subfolder. darling.sample.json is already in the service publish
  output. Checksums + release upload already glob releases/*.zip.
- Sign our own Darling exes/dlls through the existing SignPath flow; the EDB
  PostgreSQL binaries inside pg-runtime.zip are uploaded to SignPath NEVER
  (the zip is placed after signing) and ship as opaque data. Two new
  artifact-configuration slugs (DarlingService/DarlingViewer) must be created
  on signpath.io before the next release.
- Stamp the service/viewer csprojs with the shared Version/AssemblyVersion/
  FileVersion (3.1.0) to match the other shipped entrypoints.

No Velopack for Darling v1 (a Windows service has a different update story,
deliberately deferred) - plain zip only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…requires pwsh 7

Validation follow-ups to the release wiring:

- The one-zip layout puts the viewer in viewer\ while the operator's
  darling.json lives beside the service exe — the viewer's resolution
  (explicit -> DARLING_CONFIG -> beside binary) would miss it out of the
  box. ViewerSettings now probes the parent directory as a viewer-only
  final fallback, pinned by a packaged-layout resolution test (beside-
  binary still wins; miss-both still reports the viewer's own path).

- fetch-pg-runtime.ps1 now #Requires -Version 7.0: under Windows
  PowerShell 5.1 it runs on .NET Framework, whose
  ZipFile.CreateFromDirectory writes backslash entry separators — a
  non-conformant zip (Info-ZIP mangles it) that byte-differs from the
  CI-built one (shell: pwsh). Caught when unzip refused the locally
  built pg-runtime.zip during validation.

Validated: 186/186 fully gated (PG fixture + SQL2022 + PGRUNTIME), with
the bootstrap E2E consuming a runtime extracted from the artifact zip
itself.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@erikdarlingdata erikdarlingdata merged commit 2ee977e into dev Jul 2, 2026
2 checks passed
@erikdarlingdata erikdarlingdata deleted the feature/1262-darling-release-wiring branch July 2, 2026 22:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant