the runbook
From idea to a product people can buy.
The exact path Rustify uses to turn a native Rust app into a monetized, licensed, notarized download — the same one that took our own apps from scratch to a live storefront. It's two parts: open your accounts once, then repeat a thin per-product loop.
rustify.toml · secrets.env .env
One rustify.toml is the source of truth; secrets stay in
gitignored env files and get fanned out to where they're consumed.
part 0 · do once
Open your accounts — once.
Account- and identity-level setup, shared by every product you ship. You bring your own Paddle and Apple accounts — Rustify never touches your money or your signing keys. Do this once; reuse it forever.
| Service | What you get | Powers |
|---|---|---|
| Keygen | account · admin token · account id (public) | in-app validation + minting keys |
| Paddle | account · API key · client-side token | checkout + the webhook's email lookup |
| Cloudflare | account · API token (Pages + Zone→DNS + R2, all Edit) · account id | deploy landings, custom domains, R2 downloads |
| Apple | Developer Program membership | the code-signing identity |
| Postmark | account · server token · account token | emailing license keys (optional) |
| A domain | registered, DNS zone in your Cloudflare account | every product's subdomain |
Local tooling — install once
# scaffold (step 1) + package the .app (release)
cargo install cargo-generate cargo-packager Plus node/npm + wrangler (landing deploy) and curl + jq (every script).
Apple signing
Install your Developer ID Application cert in the keychain and store a notarytool profile once. Every product signs with the same identity.
Paddle go-live
Complete seller verification, add payout details, set a default payment link. Account-level — required before any live checkout.
Cloudflare downloads
Create one R2 bucket for your .dmgs, bind a downloads.yourdomain custom domain, add a cache-bypass rule for /*.dmg. All products publish here.
Verify your sender
Run setup-email.sh once — it writes DKIM + Return-Path into Cloudflare and verifies in Postmark. Then every product emails keys from your domain.
Two gotchas that cost real time: the Cloudflare permission you
want is Zone → DNS → Edit, not the similar-looking "Account
DNS Settings" (which can't write records). And in Paddle, "Notification settings"
is a different permission from "Notifications".
part 1 · per product
Then repeat the loop.
Reuses everything above — same accounts, same cert, same verified sender,
same downloads bucket. A new product creates resources inside those
accounts; it never needs new ones. Every script takes DRY_RUN=1.
-
Scaffold the app
app automatedA buildable app pre-wired to the licensing SDK, the release pipeline, and its own scripts/.
cargo generate --git https://github.com/hery/rustify templates/egui -
Build your product
app manualYour idea. One binary runs locked (free tier) and unlocks at runtime when a license validates — gate paid code behind unlocked().
-
Stand up the landing
landing setupCopy web/ into a landing repo, edit site.config.ts (name, price, domain), copy .env.example → .env.
-
Fill config
app manualSet rustify.toml (name, bundle id) and secrets.env — paste your existing Part 0 tokens, set the price + targets. No new accounts.
-
Create this product's resources
app automatedA new Keygen product → policy → token and Paddle product → price → webhook, created inside your existing accounts.
./scripts/provision-resources.sh -
Wire the Paddle client token
manualPaste your account-level live_… token into the landing .env. Same token works for every product.
-
Fan secrets out
app automatedPush webhook secrets + PUBLIC_PADDLE_* to this product's Cloudflare Pages project and its GitHub repo.
./scripts/provision.sh -
Deploy the storefront
landing automatedBuild + deploy to Cloudflare Pages, register the custom domain, write the CNAME. The webhook deploys with it.
bash /path/to/rustify/web/deploy.sh -
Build & publish the app
app automatedBuild → codesign → notarize → staple → publish App.dmg to R2, signed with your Part 0 cert.
./scripts/release.sh -
Verify it's live
app automatedA read-only doctor: landing 200/TLS, checkout inlined, webhook rejects unsigned, Keygen reachable, price active.
./scripts/preflight.sh -
Approve the domain in Paddle
manualThe one per-product compliance gate: submit this product's domain for website approval. Until it clears, live checkout is blocked.
the honest part
What stays manual — and why.
One-time (Part 0)
Opening accounts, issuing tokens, installing your signing cert, Paddle seller verification + payout. Identity and billing — only you can.
Per product (Part 1)
Writing your app, and website approval for the new domain. That's the whole manual layer per product — everything else is a script.
Rustify never centralizes the two things that would make it a regulated liability — being the Merchant-of-Record (your Paddle) and custodying signing keys (your keychain/CI). Those gates are manual on purpose.