Distribute desktop applications

View .md

kanject aws distribute carries a .NET desktop project from a local publish tree to downloadable, checksummed release artifacts across macOS, Windows, and Linux. It owns the edition × RID matrix, destination routing, candidate channel, release feed, and byte-identical promotion.

You'll learn
  • Register a desktop matrix and create a working ZIP/TAR archive without writing a packager
  • Prove version, routes, tools, and finished artifacts locally before adding credentials
  • Stage feed-derived candidates to isolated destinations and promote only approved bytes
  • Graduate to DMG, MSI, and DEB with the correct payload-sign → package → container-sign → verify order
  • Build Linux DEB, RPM, and AppImage packages from a typed packaging block, and know its current limits

Start with a download that works

Distribution reads <Version>, then <VersionPrefix>, from the project or nearest Directory.Build.props; --version is an explicit controlled override. --scaffold-archive creates a checked-in, dependency-free .NET helper and wires packageCommand plus artifactGlobs. Windows cells produce ZIP; macOS/Linux produce permission-preserving TAR.GZ.

bash
# Version the product in its csproj (or nearest Directory.Build.props)# <Version>1.0.0</Version>​# Register the cross-platform desktop matrix and create a working archivekanject add distributable src/Acme.Studio/Acme.Studio.csproj \  --id acme-studio \  --rid osx-arm64 \  --rid win-x64 \  --rid linux-x64 \  --self-contained \  --scaffold-archive

Route cells to destinations

Destinations live in the stage file under distribution.targets[]. A target selects distributable + edition + RID; exact selectors beat *. Desktop downloads normally go to versioned S3 buckets or GitHub Releases. Package repositories should use kanject packages rather than treating CodeArtifact as a generic file store.

bash
# Add destinations through the guided flowkanject configure distribution \  --env prod \  --distributable acme-studio​# Review the resulting selector → destination mapkanject configure distribution list --env prod

Use the safety ladder

The local path deliberately increases authority one step at a time. The first plan is credential-free. The build-only run executes the real artifact phases but cannot upload or write a ledger. Only after both are green should a candidate job receive destination credentials.

bash
# Readiness: version, routes, packaging, signing, local toolskanject doctor --features​# Guaranteed local matrix plan — no credentials or destination callskanject aws distribute \  --env prod \  --distributable acme-studio \  --dry-run --offline​# Execute publish → package → verify → checksum, but upload nothingkanject aws distribute \  --env prod \  --distributable acme-studio \  --no-publish
  • doctor --features — version provenance, routes, helper projects, tool availability, and incomplete sign/verify gates.
  • --dry-run --offline — project, version, matrix, selectors, and routes only; candidate numbering is intentionally unavailable offline.
  • --dry-run — adds destination-aware preflight and may require AWS credentials.
  • --no-publish — runs the real artifact path and leaves each cell under .kanject/dist/<id>/<edition>/<rid>/.

Candidate first, stable after approval

distribute init creates or adopts every declared S3 destination, enforces public-access block, SSE-S3, and versioning, and provisions an isolated -rc sibling. Candidate numbering comes from that candidate feed. Promotion is an S3 server-side copy and release-feed flip—not a rebuild.

bash
# Provision/adopt stable and -rc S3 destinationskanject aws distribute init --env prod --dry-runkanject aws distribute init --env prod​# Build once and stage the next feed-derived candidatekanject aws distribute \  --candidate \  --env prod \  --distributable acme-studio​# After hands-on approval, copy the tested bytes to stablekanject aws distribute promote --env prod

Graduate to native installers

Once the archive path is proven, scaffold the release kit. It is one .NET project with four phases — payload-sign, package, container-sign, verify — that builds a real macOS .app inside a drag-to-Applications DMG, an MSI (WiX v5+), and a DEB (dpkg-deb), with macOS notarization, Windows Authenticode, and independent verification. There are no shell or PowerShell scripts: the kit calls only platform tools that have no .NET equivalent, so a runner needs the .NET SDK and those tools, not PowerShell or Bash. Scaffolding never changes the manifest or reads credentials.

bash
# Copy the release kit: one .NET project for DMG, MSI, and DEBkanject configure distribution scaffold \  --distributable acme-studio​# Fill the generated release.env TODOs, wire the four phase commands# into the manifest, then prove each native cell on its matching OS.kanject aws distribute \  --env prod \  --distributable acme-studio \  --rid <matching-rid> \  --no-publish
bash
kanject aws distribute --env prod --distributable acme-studio --rid osx-arm64 --no-publish

Assemble the .app, sign its payload, build/sign the DMG, notarize, staple, then independently verify with Apple tooling on a macOS runner.

powershell
kanject aws distribute --env prod --distributable acme-studio --rid win-x64 --no-publish

Build the MSI with the pinned WiX tool, Authenticode-sign the payload and finished installer, then inspect the signature on a Windows runner.

bash
kanject aws distribute --env prod --distributable acme-studio --rid linux-x64 --no-publish

Build the DEB with explicit ownership, permissions, desktop metadata, and dependencies; inspect its control data and contents before staging.

Linux packages from a typed block

Since CLI 1.0.32, a distributable can declare a typed packaging block instead of release-kit commands. The bundled dotnet-packaging provider builds DEB and RPM on native linux-x64 or linux-arm64 hosts, and AppImage on linux-x64. The distributable may hold only those Linux RIDs, and AppImage needs exactly linux-x64. The CLI checks each artifact's format itself, writes the checksums, and uploads exactly those files. configure distribution packaging writes the block and pins the tool version without downloading anything; the tool is restored explicitly, like any .NET tool.

bash
# Typed packaging is Linux-only, so it goes on a Linux-only distributablekanject add distributable src/Acme.Tools/Acme.Tools.csproj \  --id acme-tools --rid linux-x64 --self-contained​# Write the typed packaging block and pin the tool (no downloads)kanject configure distribution packaging \  --distributable acme-tools \  --provider dotnet-packaging \  --format linux=deb \  --display-name "Acme Tools" \  --application-id com.acme.tools \  --publisher "Acme, Inc." \  --executable Acme.Tools \  --category Development \  --install-scope per-machine \  --yes --non-interactive​# Readiness: provider, tool pin, hostkanject doctor --features

The pinned DotnetPackaging 10.1.36 has limits that matter for a public release:

  • No publisher in DEB metadata. The tool drops it, so packages show Unknown Maintainer. Every DEB/RPM run prints a warning saying so.
  • No runtime dependencies. The tool can't write Depends: or Requires:, so packages install on hosts that lack your app's native libraries. Declaring dependenciesByFormat against this provider fails (KANCLI537) rather than being silently dropped.
  • Linux only, native hosts only, no signing. A typed cell with a signing block can't publish yet.
Recap
  • --scaffold-archive turns a publish tree into a real first download for every declared RID.
  • doctor --features, offline dry-run, and --no-publish prove progressively stronger claims without destination authority.
  • Candidates live in -rc destinations; promote copies approved bytes to stable without rebuilding.
  • Native installers are matching-OS artifacts with explicit application assembly, two-phase signing, and independent verification; the release kit is one .NET project, no scripts.
  • The typed packaging block builds Linux DEB, RPM, and AppImage today, without publisher metadata or runtime dependencies.
Was this page helpful?