docs: COMMERCIAL-INSTALLER-BACKLOG for MSI and notarized macOS

This commit is contained in:
George Lambert 2026-10-13 21:52:19 -04:00
commit 1b357f6e87

View file

@ -0,0 +1,156 @@
# Commercial installer backlog (MSI + notarized macOS .app)
Backlog for **shipping-grade** installers: a full Windows **MSI** and a **codesigned + notarized** macOS **`.app`** (and related distribution). This is inventory of what already exists versus what is still missing.
Day-to-day packaging (tray, menu bar, version stamps, CERT-01, IPP GET vs POST) is documented in **[PACKAGING.md](PACKAGING.md)**. This file does not replace that runbook; it tracks commercial distribution gaps only.
**Product version source of truth:** `internal/job.HelperVersion` (see [PACKAGING.md](PACKAGING.md) § Version source of truth). All commercial artifacts must stamp that value (or an explicit `VERSION=` override), not hard-coded older defaults.
---
## Goals (definition of done)
| Platform | Ship artifact | Done when |
|----------|---------------|-----------|
| **Windows** | Elevated **MSI** (WiX or equivalent) + **Authenticode**-signed payload | Enterprise deployable via Intune/SCCM/GPO; silent install; upgrade/uninstall ARP; printer + helper + tray; signed binaries and MSI |
| **macOS** | **Developer ID** codesigned, **notarized**, **stapled** **`.app`** (and/or signed `.pkg` that installs it) | Gatekeeper clean on fresh Mac (`spctl` assess pass); hardened runtime; no right-click Open workaround; version = `HelperVersion` |
Related but out of scope for “done” above (still backlog-adjacent): Linux `.deb`, auto-update channels, MSIX / Print Support App.
---
## Windows — full MSI
### Exists today
| Item | Location / notes | Status |
|------|------------------|--------|
| **NSIS GUI setup.exe** (elevated product install) | [`platform/windows/installer-windows.nsi`](../platform/windows/installer-windows.nsi) | **Shipped path** for full product (not MSI) |
| Older/simpler NSIS script | [`platform/windows/installer.nsi`](../platform/windows/installer.nsi) | Present; used by cross-build helper |
| Native Windows build → setup.exe | [`platform/windows/build-windows.ps1`](../platform/windows/build-windows.ps1) | Builds `verae-print.exe` + GUI helper, runs `makensis` |
| Cross-host NSIS build | [`platform/windows/build-installer.sh`](../platform/windows/build-installer.sh) | Needs prebuilt binaries from `scripts/build-win-linux.sh` |
| Printer install / uninstall | [`platform/windows/scripts/install-printer.ps1`](../platform/windows/scripts/install-printer.ps1), `uninstall-printer.ps1` | Used by NSIS product |
| Tray NotifyIcon + GET `/status` | [`platform/windows/scripts/verae-tray.ps1`](../platform/windows/scripts/verae-tray.ps1) | User-level; see [PACKAGING.md](PACKAGING.md) § Windows tray |
| Startup-folder tray install | [`platform/windows/scripts/install-tray-startup.ps1`](../platform/windows/scripts/install-tray-startup.ps1) | **Not** MSI; no elevation |
| WiX-free Start Menu stub | [`platform/windows/scripts/build-tray-startmenu.ps1`](../platform/windows/scripts/build-tray-startmenu.ps1) | Explicitly “future MSI can supersede” |
| Shared version helper | [`platform/windows/scripts/common.ps1`](../platform/windows/scripts/common.ps1) `Get-VeraePackageVersion` | Aligns tray scripts with `HelperVersion` |
| End-user Windows readme | [`platform/windows/README-WINDOWS.txt`](../platform/windows/README-WINDOWS.txt), [`README.txt`](../platform/windows/README.txt) | Documents NSIS flow; states **no elevated MSI** for tray |
| Packaging policy doc | [PACKAGING.md](PACKAGING.md) | “**No elevated MSI**” for tray presence by design |
| Spec intent (historical) | [`docs/superpowers/specs/2026-10-03-print-to-verae-timestamp.md`](superpowers/specs/2026-10-03-print-to-verae-timestamp.md) | Mentions MSI + Authenticode as product direction |
| Plan checkbox (open) | [`docs/superpowers/plans/2026-10-03-print-to-verae-timestamp.md`](superpowers/plans/2026-10-03-print-to-verae-timestamp.md) | “Tray helper build, WiX MSI, Authenticode signing” still unchecked |
NSIS product already covers much of the *install experience* (files under Program Files, key setup UI, IPP + Local Port printers, helper task, Start Menu, uninstall, diagnostics). That is **not** an MSI and is not WiX-based.
### Missing for commercial MSI
| Gap | Why it matters | Suggested work |
|-----|----------------|----------------|
| **No WiX project** (no `.wxs` / `.wixproj`, no `candle`/`light` or WiX v4 `wix build`) | Enterprise wants MSI, not only `setup.exe` | Author WiX that installs the same payload NSIS stages: `verae-print.exe`, helper, scripts, LICENSE, printers custom action |
| **No MSI output artifact** in `dist/` or CI | Nothing to hand Intune/SCCM | Produce `VeraePrintStamp-windows-<arch>-<version>.msi` (+ SHA256) |
| **No Authenticode signing pipeline** in-repo | SmartScreen / enterprise trust | `signtool` (or Azure Trusted Signing) for EXE/DLL **and** MSI; store certs in CI secrets only — never in git |
| **Silent / unattended install contract** | Fleet deploy | Document and implement `msiexec /i … /qn` properties (e.g. skip key UI, `VERAE_SKIP_KEY_SETUP`, install API URL, channel) |
| **Upgrade / repair / ARP metadata** | Clean major-upgrade, Add/Remove Programs | WiX `UpgradeCode`, versioned ProductCode, ARP URL/icon/publisher; migrate from NSIS `UNINST_KEY` |
| **Custom actions parity with NSIS** | Printers, scheduled helper, per-user key | Port `install-printer.ps1`, helper registration, tray Startup/Start Menu from NSIS pages into deferred/impersonated CAs |
| **Per-user vs per-machine story** | Tray is user-level today; MSI is typically machine | Decide: machine MSI + optional per-user tray CA, or dual packages (machine core + user MSIX/stub) |
| **Version drift in build scripts** | `build-windows.ps1` default `0.3.2`; `build-installer.sh` default `0.1.2`; NSIS `!define VERSION "0.3.2"` | Wire all defaults to `HelperVersion` / `Get-VeraePackageVersion` (same as tray smoke) |
| **CI job that builds + signs MSI** | Reproducible release | Windows runner: go test/build → WiX → signtool → upload + checksums |
| **MSIX / Print Support App** (later) | Modern print path in original spec | Explicitly **deferred** unless enterprise asks; not blocking first MSI |
### Windows backlog order (suggested)
1. Freeze install surface from NSIS (file list, registry, printers, helper, tray) as a checklist.
2. WiX authoring that reproduces that surface; dual-ship NSIS until MSI is validated.
3. Authenticode on binaries + MSI.
4. Silent property set + Intune sample command line.
5. Align all version defaults to `HelperVersion`; extend `platform/smoke-packaging-metadata.sh` or a Windows smoke to fail on drift.
6. Deprecate or thin NSIS once MSI is the primary download (keep NSIS only if needed for offline GUI key wizard parity).
---
## macOS — notarized `.app` (and signed `.pkg`)
### Exists today
| Item | Location / notes | Status |
|------|------------------|--------|
| **Signed + notarized `.pkg` builder** | [`platform/macos/pkg/build-pkg.sh`](../platform/macos/pkg/build-pkg.sh) | **Full commercial path for installer package**: universal `verae-print`, codesign hardened runtime, stages `Verae Print Stamp.app`, `pkgbuild` + `productbuild` (Developer ID Installer), `notarytool submit --wait`, `stapler staple`, SHA256SUMS |
| Entitlements | [`platform/macos/pkg/entitlements.plist`](../platform/macos/pkg/entitlements.plist) | network client, user-selected files, allow-jit |
| Postinstall (printer, LaunchAgent, key setup) | [`platform/macos/pkg/scripts/postinstall`](../platform/macos/pkg/scripts/postinstall) | Console-user IPP queue + `mac-key-setup` |
| App bundle **inside** pkg stage | Built by `build-pkg.sh` → `/Applications/Verae Print Stamp.app` | Thin wrapper: runs `verae-print mac-key-setup` or `stamp -open` on dropped PDFs; codesigned in pkg flow |
| Signing env (external) | `$HOME/fie/installers/macos/signing.env` (default); not in repo | API key path / issuer / Developer ID identities — correct secret hygiene |
| IPP LaunchAgent + install scripts | [`platform/macos/ipp/`](../platform/macos/ipp/) | Used by pkg postinstall |
| Menu bar Swift + LaunchAgent | [`platform/macos/menubar/`](../platform/macos/menubar/) | Runtime presence; see [PACKAGING.md](PACKAGING.md) § macOS menu bar |
| **Unsigned** menu-bar `.app` builder | [`platform/macos/menubar/build-menubar-app.sh`](../platform/macos/menubar/build-menubar-app.sh) | Dev/local only; copies entitlements but **does not** codesign |
| Notarization **documentation only** (menubar) | [`platform/macos/menubar/README.txt`](../platform/macos/menubar/README.txt) + [PACKAGING.md](PACKAGING.md) § Codesign + notarization | Manual steps; no automated menubar notarize |
| Packaging smoke | [`platform/smoke-packaging-metadata.sh`](../platform/smoke-packaging-metadata.sh) | Version + “no elevated MSI” + IPP purity — not Gatekeeper |
### Missing for commercial notarized `.app`
| Gap | Why it matters | Suggested work |
|-----|----------------|----------------|
| **No first-class standalone notarized `.app` zip/DMG release** | Many users expect drag-to-Applications `.app`, not only `.pkg` | After codesign: `ditto` zip **or** DMG → `notarytool` → staple **app** (and DMG if used); publish `VeraePrintStamp-<version>.app.zip` / `.dmg` + SHA256 |
| **Menu-bar `.app` not in automated sign/notarize path** | PT-06 presence still unsigned by default | Extend `build-menubar-app.sh` (or share a `sign-and-notarize.sh`) to apply Developer ID + notarytool + staple when `SIGNING_ENV` is set; keep unsigned default for dev |
| **Product `.app` is a thin launcher**, not a full bundled helper | App depends on files under `/Library/Application Support/Verae/PrintStamp/` from the **pkg** | Either (a) keep `.pkg` as primary and document that, or (b) ship a self-contained `.app` with embedded `verae-print`, registration-pdf, IPP assets, and first-run setup |
| **Menubar not installed by pkg postinstall** | Users get printer + key setup but not menu bar unless they run `verae-print menubar --install` | Optional: postinstall or app first-run installs LaunchAgent / offers menu-bar `.app` |
| **Version default drift** | `build-pkg.sh` defaults `VERSION=0.3.4`; should track `HelperVersion` (currently **0.3.6**) | Grep `job.go` like `build-menubar-app.sh`; fail smoke if pkg default ≠ HelperVersion |
| **No DMG branding / layout** (optional) | Nicer download UX | `create-dmg` or `hdiutil` with Applications symlink; notarize DMG |
| **No Sparkle / appcast auto-update** | Commercial expectation for `.app` | Deferred unless product asks; if added, EdDSA keys outside repo |
| **Stapled artifact CI publish** | Repeatable Gatekeeper-clean builds | macOS runner with custom keychain import of Developer ID + notary API key; upload stapled `.pkg` and optional `.app.zip` |
| **Gatekeeper verification in smoke** | Catch regressions | On signed hosts: `spctl -a -vv -t install` on `.pkg`, `spctl --assess --type execute` on `.app`, `stapler validate` |
| **Hardened runtime audit of nested binaries** | Notary rejections | Ensure registration-pdf node bits are not executed under hardened runtime incorrectly; only sign Mach-O helpers that actually run |
| **Bundle ID / naming consistency** | Support and updates | Pkg app: `ai.verae.print-stamp`; menubar: `ai.verae.timestamping.menubar` — document which is “the” commercial app vs accessory |
### macOS backlog order (suggested)
1. Point `build-pkg.sh` `VERSION` at `HelperVersion`; verify stapled `.pkg` on a clean VM (Gatekeeper + printer + key setup).
2. Decide distribution primary: **`.pkg` only** (already closest to done) vs **self-contained notarized `.app`** (+ optional DMG).
3. If `.app` primary: bundle helper + resources inside the app; first-run equals today’s postinstall responsibilities without root where possible.
4. Automate menubar sign/notarize as accessory artifact (or embed menubar in product app).
5. CI: notarize + staple + `spctl` + checksums; never commit `signing.env`.
6. Optional: DMG, Sparkle.
---
## Cross-platform commercial gaps
| Gap | Notes |
|-----|--------|
| **Single release checklist** | One version (`HelperVersion`), Windows MSI + setup.exe (transition), macOS stapled `.pkg` / `.app`, SHA256SUMS, site download links (`site/printit.verae.ai/DOWNLOADS-MANIFEST.txt`) |
| **Secrets policy** | Already correct direction: no certs/passwords in git; Apple `signing.env` and Windows PFX/Trusted Signing only in CI or operator home |
| **Install API / registration** | [`docs/installer-registration-api.md`](installer-registration-api.md) + `internal/installapi` — keep MSI/pkg silent props aligned |
| **Channel / metering defaults** | PrintIt → zapier channel; document MSI/pkg property overrides |
| **Hold ≠ evidence** | Tray/menubar must stay GET-only; commercial installers must not teach IPP POST from presence UI ([PACKAGING.md](PACKAGING.md) § IPP) |
| **Linux commercial** | `platform/linux/deb/build-deb.sh` exists; not part of MSI/app done criteria |
---
## Quick status matrix
| Deliverable | Exists | Commercial-ready |
|-------------|--------|------------------|
| Windows tray / Startup / Start Menu (user) | Yes | Yes for **presence**; not enterprise MSI |
| Windows NSIS `setup.exe` | Yes | **Partial** — full GUI install; needs Authenticode + version align; not MSI |
| Windows **MSI** (WiX) | **No** | **No** |
| Windows Authenticode pipeline | **No** (in-repo) | **No** |
| macOS menu bar LaunchAgent | Yes | Yes (dev/ops) |
| macOS unsigned menubar `.app` | Yes | **No** (Gatekeeper) |
| macOS **signed+notarized `.pkg`** script | Yes | **Near** — needs version sync, CI, clean-VM proof |
| macOS **standalone notarized `.app`** zip/DMG | **Partial** (app built inside pkg only) | **No** as primary download |
| Menubar automated notarization | Docs only | **No** |
---
## References
- **[PACKAGING.md](PACKAGING.md)** — tray, menubar, unsigned `.app`, notarization steps (docs), CERT-01, statuslang, IPP split, smoke tests
- [`platform/windows/`](../platform/windows/) — NSIS + tray scripts
- [`platform/macos/pkg/`](../platform/macos/pkg/) — commercial `.pkg` build
- [`platform/macos/menubar/`](../platform/macos/menubar/) — menu bar + unsigned `.app` + notarize docs
- [`docs/installer-registration-api.md`](installer-registration-api.md) — installer ↔ portal registration API
- [`README.md`](../README.md) — top-level packaging pointers
---
## Maintenance
When closing a row above, update the **Quick status matrix** and add a one-line note under the relevant “Exists” table (path + date). Do not move day-to-day tray/menubar how-to out of [PACKAGING.md](PACKAGING.md) into this backlog.