Fork channel

Create a new channel as a copy of main.

Rename channel

Rename main to:

Delete channel

Delete main? This cannot be undone.

mode name
drwxr-xr-x examples/
drwxr-xr-x src/
-rw-r--r-- Cargo.toml
-rw-r--r-- README.md
README

pkgedit

Build macOS flat installer packages (.pkg) from any Unix host — pure Rust, deterministic, no Apple tooling. The installer-side sibling of machoedit / elfedit.

A flat component package is a xar archive of four members:

  • PackageInfo — XML metadata (identifier, version, install-location, scripts);
  • Bom — the Bill Of Materials installer reads to lay down and receipt the files (bomutils-compatible binary format, independent impl);
  • Payload — gzip’d cpio (odc) of the installed tree;
  • Scripts — gzip’d cpio of preinstall/postinstall (optional).

Every field that could vary by host is pinned — uid/gid 0 (root:wheel), mtime 0, fixed member order, SHA-1/CRC-32 hand-rolled (hash) — so the .pkg is byte-identical whether built on the Linux backend or natively on macOS, hence content-addressable like the rest of the store. There is no pkgbuild-on-macOS-vs-bomutils-on-Linux fork: one code path, both hosts.

let pkg = pkgedit::Package::builder("fr.elpe.elpe", "0.1.0", "/opt/elpe")
    .payload_dir("payload")          // payload/bin/elpe -> /opt/elpe/bin/elpe
    .postinstall("#!/bin/sh\nmkdir -p /opt/elpe\n…")
    .build()?;
std::fs::write("elpe.pkg", pkg)?;

or cargo run --example pack -- <payload-dir> elpe.pkg.

Install model

Unsigned is sufficient for the two target channels:

  • Corporate MDM (Jamf/Kandji/Mosyle/Munki/Fleet): the management channel is trusted, so an unsigned .pkg deploys as a policy — no Developer ID, no Gatekeeper prompt, SIP stays on.
  • AWS EC2 Mac: sudo installer -pkg elpe.pkg -target / headless.

/opt is a firmlink on stock macOS, so the store lives at /opt/elpe with no synthetic.conf, dedicated APFS volume, or reboot (unlike the Nix/Determinate approach). The store is user-owned, no daemon (Homebrew model); the sole privileged step is the postinstall mkdir /opt/elpe && chown.

Developer ID signing + notarisation (only needed for open, non-MDM distribution) is done from Linux by the companion script apple-notarize.sh (rcodesign + the App Store Connect API): it mints a Developer ID Application certificate via the ASC API, Dev-ID-signs Mach-O with hardened runtime, and notary-submit --wait --staples — no Mac, no Xcode. Config lives in a apple.env (team id, issuer id, key id, .p8). The one thing it can’t create from Linux is the Developer ID Installer cert (not an ASC API type) — make that once via the web portal to sign the .pkg wrapper; the Mach-O signing + notarisation that a distributable app needs is fully covered.

Scope / limits

  • BOM paths form a real B-tree: leaves of ≤ MAX_LEAF (256) (info1, file) pairs chained via forward/backward, under one branch node with one entry per leaf (the bomutils/mkbom convention — lsbom/installer iterate branch entries to reach every leaf). Large payloads (the full darwin toolchain + deps bundle, tens of thousands of files) pack fine and read back byte-complete under real macOS lsbom/pkgutil; a payload within one leaf points the tree straight at it.
  • Members stored uncompressed at the xar level (the cpio streams are already gzip’d); no per-file xar compression.
  • No signing (see above).