Bean & Bark: ship Roastery Desk

View .md

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.

You'll learn
  • Give a desktop app its own Kanject service, with the prod stage 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:

bash
# In the roastery-desk repo, at its rootkanject init --service beanandbark-roastery-desk --yes
text
── 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.

xml
<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:

bash
kanject add distributable \  src/BeanAndBark.RoasteryDesk/BeanAndBark.RoasteryDesk.csproj \  --id roastery-desk \  --rid osx-arm64 \  --rid win-x64 \  --rid linux-x64 \  --self-contained \  --scaffold-archive​kanject configure distribution \  --env prod \  --distributable roastery-desk \  --provider s3 \  --bucket beanandbark-downloads
text
  +  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?

bash
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.

bash
# 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:

bash
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.

Recap
  • A desktop app gets its own service: kanject init in a repo with no Lambda seeds dev, stage, and prod.
  • A reviewed <Version> plus the RID list defines the product matrix; --scaffold-archive creates the first usable downloads, and configure distribution routes them in prod.
  • Offline dry-run proves local intent, while --no-publish executes the real artifact path without destination authority.
  • The candidate feed owns -rc.N; people test those downloads on matching operating systems, and promote copies the approved bytes to stable without rebuilding.
Before Saturday's handoff2
Why can't candidate planning be fully offline?
What happens during stable promotion?
Try it yourself 2
1 Inspect the three local artifacts
Run --no-publish for a three-RID sample, unpack each archive, and compare executable permissions plus SHA256SUMS coverage.
The archive helper intentionally uses ZIP for Windows and TAR.GZ where Unix permission bits matter.
Show solution
The declared archive and checksum file should be the release surface; the loose publish directory should not appear in artifactGlobs or the upload set. Unix executables should retain their executable bits after extraction.
2 Explain the approval boundary
Write the shortest argument for why a release pipeline should promote an RC rather than rebuild stable installers.
Show solution
Approval refers to evidence about specific tested bytes. A rebuild creates different, untested bytes—even from the same commit—so stable promotion must preserve the candidate payload.
Was this page helpful?