Embedding (--build)¶
perch --build -f commands.perch -o myapp produces a single, self-contained binary that boots straight into your commands โ no perch install, no Go toolchain, no .perch file required on the target machine.
What you get¶
perch --build -o ./myapp
./myapp --help # โ lists commands from commands.perch
./myapp --version # โ version from commands.perch
./myapp <command> [args] # โ dispatches into the embedded program
scp ./myapp remote:~/ # โ works on any same-OS, same-arch box
The output binary is functionally a private fork of perch with your program baked in. The -f flag is ignored โ the embedded program always wins.
On-disk format¶
A "perch fat binary" is layered:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ stock perch executable โ โ original bytes, unmodified
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ program.json โ โ marshaled domain.Program
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ length (8 bytes big-endian) โ โ byte length of the JSON section
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ magic = "PRCHEMB1" โ โ 8 ASCII bytes
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
At startup, perch reads the last 16 bytes of its own executable (os.Executable()). If the magic matches, it seeks back by length + 16, reads the JSON, parses it as a domain.Program, and dispatches against that program. If the magic doesn't match, perch behaves normally and looks for a .perch file.
Why this design¶
- No Go toolchain required. The fat binary is just a copy + append; no compiler invocation. Build time is tens of milliseconds.
- Trivially distributable. It's one file. Drop it on a server. Email it to a coworker. SCP it to a Pi.
- Source-available. The host perch is just regular perch; anyone can read the embedded JSON section to audit what the binary will do.
- Idempotent rebuilds. Running
--buildon a binary that already has an embedded program strips the old footer first, so accumulating layers can't happen.
Limitations (current)¶
- Same OS/arch only. The output inherits the host's OS and architecture. Cross-compile (
--build --target linux-arm64) is on the roadmap; the implementation needs per-target stubs. - Static op catalog. Embedded binaries can only call the ops their host perch compiled in. If you
--buildfrom perch v0.1.0, the resulting binary doesn't have ops added in v0.2.0. Rebuild after upgrading. - No live reload. The program JSON is frozen at build time. Changes to
commands.perchrequire a fresh--build.
Inspecting an embedded binary¶
You can dump the embedded program JSON without running anything:
# Find the footer
SIZE=$(wc -c < ./myapp)
LEN=$(tail -c 16 ./myapp | head -c 8 | xxd -p | awk '{print strtonum("0x" $0)}')
# Extract the JSON
tail -c $((LEN + 16)) ./myapp | head -c "$LEN" | jq .
Output is the full parsed program: globals, commands, ops, the works.
Security considerations¶
- The embedded JSON is not signed. If your distribution channel matters, sign the entire binary with
codesign(macOS),signtool(Windows), or your platform's equivalent. - The fat binary trusts the embedded program. Anyone who can
--buildon top of an existing perch can produce an unrelated binary with your name. Treat--buildlike any other build pipeline: the source.perchis the input you must trust. - Capabilities still apply, but the default is ambient. If the embedded
commands.perchdeclares arequiresblock, the embedded binary enforces it exactly likeperch -fwould โ every external op is gated (capability-gating.md). Without arequiresblock, the program runs with ambient access, so an embeddedshell "rm -rf /"will run. Two mitigations: (1) declare arequiresblock in the source so the binary self-limits; (2) the embedded binary still honors operator--no-shell/--no-network/--no-write/--envflags at launch. Either way, review the sourcecommands.perchas you'd review any build input.
Roadmap¶
- Cross-compile by shipping pre-built per-target perch stubs alongside the running binary.
- Code signing helper โ
perch --build --signinvoking the platform's signing tool. - Diffable inspection โ
perch --inspect ./myappto print a structured view of the embedded program. - Lockfile โ pin op-catalog version into the program JSON so rebuilds with a newer perch error rather than silently using new ops.