Bean & Bark: ship Roastery Desk
The Orders API is live and the Pricing SDK is shared, but the physical roastery still runs on spreadsheets. Nadia wants Roastery Desk for batch intake, labels, and stock counts. Her MacBook is on the tasting floor, Tunde has a Windows laptop, and the packing station runs Linux. A publish folder on your machine is not a release any of them can install.
- Give a desktop app its own Kanject service, with the
prodstage its releases go through - Declare one versioned desktop product across macOS, Windows, and Linux runtime identifiers
- Create permission-safe first downloads and prove every routed cell without publication authority
- Stage a feed-derived candidate for hands-on testing, then promote it byte-identically
Give Roastery Desk its own service
Roastery Desk lives in its own repository, roastery-desk, with the app at src/BeanAndBark.RoasteryDesk/. It can't borrow the Orders API's manifest: a distributable must sit inside the service it's declared in. So it gets a manifest of its own, made by kanject init at the repository root:
# In the roastery-desk repo, at its rootkanject init --service beanandbark-roastery-desk --yes ── Init complete ───────────────────────────────────────────────────────────────╭───────┬───────────┬─────────┬─────────────────────────────────╮│ Stage │ Region │ Profile │ Resource prefix │├───────┼───────────┼─────────┼─────────────────────────────────┤│ dev │ eu-west-1 │ default │ dev-beanandbark-roastery-desk ││ stage │ eu-west-1 │ default │ stage-beanandbark-roastery-desk ││ prod │ eu-west-1 │ default │ prod-beanandbark-roastery-desk │╰───────┴───────────┴─────────┴─────────────────────────────────╯ With no Lambda in the repository, init seeds the classic three stages — dev, stage and prod — so prod, where releases go, exists from the start. (It also says it writes a schema v1 manifest, since schema v2 describes Lambda entries; distribution works the same on either.)
Name the product version once
Distribution reads the product version from the project or nearest Directory.Build.props. Put it where code review can see it; a candidate will append a feed-derived -rc.N, while the stable release keeps the clean base.
<PropertyGroup> <Version>1.0.0</Version></PropertyGroup> Turn a publish tree into three downloads
--scaffold-archive creates a small checked-in .NET helper and wires it into the manifest. Windows gets ZIP; macOS and Linux get permission-preserving TAR.GZ. The helper writes only declared archives into the output folder, so checksums and uploads do not accidentally include the loose publish tree. configure distribution then routes the app's downloads in the prod stage:
kanject add distributable \ src/BeanAndBark.RoasteryDesk/BeanAndBark.RoasteryDesk.csproj \ --id roastery-desk \ --rid osx-arm64 \ --rid win-x64 \ --rid linux-x64 \ --self-contained \ --scaffold-archivekanject configure distribution \ --env prod \ --distributable roastery-desk \ --provider s3 \ --bucket beanandbark-downloads + added distributable roastery-desk (desktop) to manifest.json + scaffolded archive packagereng/distribution/roastery-desk-archive/roastery-desk.ArchivePackager.csproj (ZIPfor Windows, permission-preserving TAR.GZ for macOS/Linux) → Add its destination under distribution.targets inkanject-cli/stages/dev.json. → Preview the route with kanject aws distribute --env dev --distributableroastery-desk --dry-run. ✓ added s3 target for roastery-desk in stage prod → Preview with kanject aws distribute --env prod --distributable roastery-desk--dry-run. add distributable suggests the default stage, dev; the route goes in prod. It's one wildcard row in stages/prod.json sending every RID to the beanandbark-downloads bucket; exact RID rows can override it later without touching the manifest.
Climb the safety ladder
Before AWS credentials enter the room, ask two progressively stronger questions: is the local plan coherent, and can the real artifact path finish?
kanject doctor --features# Plan every cell without AWS or destination credentialskanject aws distribute \ --env prod \ --distributable roastery-desk \ --dry-run --offline# Produce the real archives and checksums, but upload nothingkanject aws distribute \ --env prod \ --distributable roastery-desk \ --no-publish The offline plan prints one row per cell — the version 1.0.0 and the file it came from, the provider, the destination — and ends "Dry run — planned 3 cell(s); no build, upload, or ledger." The build-only run publishes, archives, verifies, and checksums each cell under .kanject/dist/roastery-desk/..., with no upload or ledger side effect. Unpack each archive on the matching machine and launch it: the arm64 build on Nadia's Mac, the self-contained ZIP on Tunde's Windows laptop without a preinstalled runtime, and the TAR.GZ on the packing station with its executable bit intact.
Stage one candidate for three people
distribute init creates or adopts the stable downloads bucket and its isolated -rc sibling, plus the stage's artifact bucket, where the distribution ledger lives, and enforces public-access block, encryption, and versioning. Candidate numbering comes from the candidate feed, so the pipeline cannot collide by guessing a suffix.
# Create/adopt the downloads buckets, the -rc sibling and the ledger bucketkanject aws distribute init --env prod# Stage the next feed-derived 1.0.0-rc.N releasekanject aws distribute \ --candidate \ --env prod \ --distributable roastery-desk# After the three-machine check, promote the exact candidatekanject aws distribute promote --env prod Nadia, Tunde, and the packing operator download 1.0.0-rc.1 from the candidate channel. The approval records that all three cells passed hands-on checks. promote then performs server-side copies into the stable destinations and flips the stable feed to 1.0.0; no compiler, archive helper, or signing script runs again. Saturday gets the candidate everyone tested.
When an archive is no longer enough
Saturday's internal pilot can use archives. A customer-facing release needs native identity and OS trust. Scaffold the inert release kit only after the archive path is green:
kanject configure distribution scaffold \ --distributable roastery-desk The generated README and RID router carry runnable DMG, MSI, and DEB starters plus payload/container signing and independent verification. They are intentionally not activated for you: bundle identifiers, upgrade codes, entitlements, certificates, notarization credentials, install scope, and Linux metadata are product decisions.
- A desktop app gets its own service:
kanject initin a repo with no Lambda seedsdev,stage, andprod. - A reviewed
<Version>plus the RID list defines the product matrix;--scaffold-archivecreates the first usable downloads, andconfigure distributionroutes them inprod. - Offline dry-run proves local intent, while
--no-publishexecutes the real artifact path without destination authority. - The candidate feed owns
-rc.N; people test those downloads on matching operating systems, andpromotecopies the approved bytes to stable without rebuilding.
--no-publish for a three-RID sample, unpack each archive, and compare executable permissions plus SHA256SUMS coverage.Show solution
artifactGlobs or the upload set. Unix executables should retain their executable bits after extraction.