Documentation
¶
Overview ¶
Package appbundle is the .app directory a macOS program lives in: whether the running process is inside one, and how to assemble one around an executable.
A bare executable is not an application on this system. AppKit reads what a program is from the bundle around it, so a program that wants a menu-bar item, a dock tile, a name in the menu bar, notification permission or a place in Login Items has to be in one. Asked for from outside a bundle, a status item is asked for by nobody: it never appears, and the process ends without complaining.
Everything here is path and file work — no AppKit, no cgo — so it builds and is tested on every platform, which is what makes a bundle assembler useful in a cross-compiling build.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type Bundle ¶
type Bundle struct {
// Path is the .app directory itself.
Path string
// Name is what it is called, without the .app.
Name string
}
Bundle is a .app directory.
func Build ¶
Build assembles the bundle and reports where it put it.
It replaces whatever was there: a bundle assembled over the top of an older one keeps files nothing refers to any more, and those are the ones that go stale without anybody noticing.
func Of ¶
Of reports the bundle an executable path sits in, if it sits in one.
It is a question about a path, not about the machine, so it answers the same way everywhere: a build on another platform can reason about a bundle it is assembling.
func (Bundle) DesignatedRequirement ¶ added in v0.5.0
DesignatedRequirement is the rule macOS uses to decide whether a program is still the one a permission was granted to.
It is worth looking at rather than trusting, because the two shapes it comes in differ in whether the grant survives a rebuild, and they are told apart by reading them: one names a cdhash, the other an identifier and a certificate.
func (Bundle) Sign ¶ added in v0.5.0
Sign code-signs an assembled bundle in place.
It runs codesign, so it works on macOS and nowhere else -- unlike Build, which is file work and assembles a bundle from any platform that can cross-compile for this one. Signing is therefore a separate step rather than a field on Spec: a Linux builder can produce the bundle, and only a Mac can finish it.
type Signer ¶ added in v0.5.0
type Signer struct {
// Identity names the signing certificate, the way `security find-identity
// -p codesigning` prints it -- a name like "Acme Dev", or its SHA-1.
//
// "-" signs ad hoc, which is what the linker already did, and which does
// NOT give a stable identity. It is here because it is occasionally what
// somebody wants, not because it is a lesser version of this.
Identity string
// Identifier is what the signature calls the program, and it belongs in the
// designated requirement. Empty takes the bundle's own identifier, which is
// nearly always right; codesign left to itself would take the executable's
// file name, and for a Go build that is whatever the linker chose.
Identifier string
// Entitlements is a plist file granting the entitlements this program
// claims. Empty asks for none, which is right until something needs one.
Entitlements string
}
Signer is how a bundle is code-signed, and the reason to bother is that a privacy permission is granted to a CODE IDENTITY rather than to a path.
⛔ AN AD-HOC SIGNATURE IS A NEW APPLICATION EVERY TIME THE SOURCE CHANGES. A Go binary comes out of the linker signed ad hoc, and an ad-hoc signature's designated requirement is the hash of the binary itself:
designated => cdhash H"f310919066069e55c6164a1d02e4fc0fc7f11d43"
The bundle identifier is not even in it. So every grant the person made -- screen recording, the camera, the microphone, Accessibility -- is attached to that one build and is gone the moment anything is recompiled. The program then fails at exactly the place it worked yesterday, and the failure looks like a bug in the program rather than a permission that quietly stopped applying. Rebuilding the identical source is fine, because Go builds reproducibly; it is CHANGING it that costs the grant, which is to say every build that was worth making.
Signed with a certificate the requirement names the identifier and the certificate instead, and is the same for every build there will ever be:
designated => identifier "io.github.example.app" and certificate root = H"63463ed1..."
The certificate does not have to be Apple's. A self-signed one made locally is untrusted for Gatekeeper and entirely sufficient here, because what is being asked of it is not "did Apple vouch for this" but "is this the same program the person said yes to".
type Spec ¶
type Spec struct {
// Dir is where the .app is written.
Dir string
// Name is the application's name, and the .app directory's.
Name string
// Identifier is the bundle identifier, in reverse-DNS form.
Identifier string
// Version is what the application reports as its version.
Version string
// Executable is the built program to put inside. It is copied, so the
// caller keeps whatever it built.
Executable string
// Accessory asks for LSUIElement: a program with a menu-bar item and no
// dock tile and no menu of its own, which is what a status-item
// application is.
Accessory bool
// MinimumSystem is the oldest macOS this claims to run on. Empty leaves
// the key out rather than inventing a floor.
MinimumSystem string
// UsageDescriptions are the NS...UsageDescription strings this program
// needs, keyed by the plist key -- "NSCameraUsageDescription" and the like.
//
// ⛔ WITHOUT THE RIGHT ONE, macOS DOES NOT DENY THE PROGRAM, IT ENDS IT.
// Touching a camera, a microphone, the Photos library or a dozen other
// things from a program with no usage description for it is not a refusal a
// caller can handle: TCC terminates the process with "This app has crashed
// because it attempted to access privacy-sensitive data without a usage
// description". A bare binary has no Info.plist at all, which is why a
// program that needs any of these has to be a bundle.
//
// The value is shown to the person in the prompt, so it is a SENTENCE about
// what this program wants it for -- "XR desk shows what the glasses see" --
// and not the name of an API.
UsageDescriptions map[string]string
// Icon is a .icns file's bytes. Empty leaves the bundle without one,
// which is an application drawn as a blank page everywhere it appears.
//
// It is bytes rather than a path because assembling a bundle is the last
// step of a build, and by then the icon is as likely to be embedded in
// the builder as sitting on disk beside it.
Icon []byte
}
Spec is what to assemble.