Distribute desktop applications
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.
- 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
packagingblock, 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.
# 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.
# 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.
# 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.
# 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.
# 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 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.
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.
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.
# 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:orRequires:, so packages install on hosts that lack your app's native libraries. DeclaringdependenciesByFormatagainst this provider fails (KANCLI537) rather than being silently dropped. - Linux only, native hosts only, no signing. A typed cell with a
signingblock can't publish yet.
--scaffold-archiveturns a publish tree into a real first download for every declared RID.doctor --features, offline dry-run, and--no-publishprove progressively stronger claims without destination authority.- Candidates live in
-rcdestinations;promotecopies 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
packagingblock builds Linux DEB, RPM, and AppImage today, without publisher metadata or runtime dependencies.