installer

package
v0.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 3, 2026 License: MIT Imports: 9 Imported by: 0

README

installer

installer is the core library behind bakepkg. It provides a fluent Go API for programmatically building, signing, and notarizing macOS installer packages (.pkg).

Features

  • Fluent Builder API: Easily compose and configure your macOS packages in Go.
  • Distribution UI Support: First-class support for Welcome screens, READMEs, Licenses, and Background images (including Dark Mode backgrounds).
  • Integrated Signing: Automatically signs embedded executables and the final installer package.
  • Modern Notarization: Directly integrates with Apple's Notary API using JWT credentials.
  • Automatic Sanitization: Automatically strips Gatekeeper's com.apple.quarantine extended attributes.
  • Smart Component Mapping: Generates Component.plist configurations for macOS App Bundles.
  • Simulation Mode: Supports a "dry-run" mode that prints the commands to be executed without modifying the system.

Usage

import "al.essio.dev/cmd/bakepkg/installer"

builder := installer.New().
    WithIdentifier("com.example.app").
    WithVersion("1.0.0").
    WithInstallLocation(installer.InstallLocationApplications).
    AddFile("build/MyApp.app", "/Applications/MyApp.app").
    WithSimulate(true) // Enable simulation mode

err := builder.Build("output/MyApp.pkg")

Requirements

The library utilizes native macOS tooling under the hood. The following commands must be available in your $PATH:

  • pkgbuild
  • productbuild
  • productsign
  • codesign
  • stapler
  • xattr

Documentation

Overview

Package bakepkg provides a fluent API for building macOS installer packages (.pkg). It wraps native macOS tooling like pkgbuild, productbuild, and productsign, while adding advanced features like Gatekeeper quarantine stripping and Apple Notary API integration.

Package installer provides a fluent API for building macOS installer packages (.pkg). It wraps native macOS tooling like pkgbuild, productbuild, and productsign, while adding advanced features like Gatekeeper quarantine stripping and Apple Notary API integration.

The library handles two main types of packages:

  1. Flat Packages: A basic .pkg file that installs files to /Library/<Name>/<Version>/.
  2. Distribution Packages: A more complex .pkg that can include a custom user interface (Welcome, Readme, License), domain choices for per-user installation, and multiple internal component packages.

Script Generation:

Install scripts (postinstall, uninstall.sh) are auto-generated from templates. The postinstall script writes /etc/paths.d and /etc/manpaths.d entries so binaries and man pages are discoverable. If SymlinkBinaries is set, symlinks are created in /usr/local/bin/. An uninstall.sh script is included in the payload for clean removal.

When config files are detected (etc/ directory in payload), preupgrade and postupgrade scripts are also generated to back up and restore configuration during upgrades.

Basic Usage (Flat Package):

builder := installer.New().
    WithIdentifier("com.example.tool").
    WithName("MyTool").
    WithVersion("1.0.0").
    AddFile("build/mytool", "bin/mytool")

err := builder.Build("MyTool.pkg")

Advanced Usage (Distribution Package with UI and Signing):

builder := installer.New().
    WithIdentifier("com.example.tool").
    WithName("MyTool").
    WithVersion("1.0.0").
    AddFile("build/mytool", "bin/mytool").
    WithSingleUser(true).
    WithSymlinkBinaries(true).
    WithDistributionUI(installer.Distribution{
        Readme: "docs/README.md",
        License: "docs/LICENSE.txt",
    }).
    WithSigning(installer.Signing{
        Identity: "Developer ID Installer: Example (XYZ)",
        Notarize: true,
    })

err := builder.Build("MyTool-Signed.pkg")

Quarantine Stripping:

When files are downloaded or moved, macOS often attaches a "com.apple.quarantine" extended attribute. If these files are bundled into a package without stripping this attribute, the installed application may trigger "App is damaged" errors. This builder automatically runs 'xattr -d com.apple.quarantine' on all staged files to prevent this issue.

Index

Examples

Constants

View Source
const (
	InstallLocationLibrary     = "/Library"
	InstallLocationUserLibrary = "~/Library"
)

Variables

This section is empty.

Functions

This section is empty.

Types

type Builder

type Builder struct {
	// contains filtered or unexported fields
}

Builder orchestrates the creation of a macOS package. It uses a fluent interface for configuration.

func New

func New() *Builder

New creates a new Builder instance with default options.

func (*Builder) AddFile

func (b *Builder) AddFile(src, dst string) *Builder

AddFile adds a file mapping to the package. src is the local file path, and dst is the target path. If dst is relative (doesn't start with /), it is inferred based on Name, Version, and InstallLocation.

func (*Builder) Build

func (b *Builder) Build(output string) error

Build compiles the package according to the builder's configuration and writes it to the specified output path.

Example (Distribution)
package main

import (
	"al.essio.dev/cmd/bakepkg/installer"
	"fmt"
	"os"
)

func main() {
	// Create dummy files and resources for this example
	_ = os.MkdirAll("testdata/resources", 0755)
	_ = os.WriteFile("testdata/app", []byte("binary data"), 0755)
	_ = os.WriteFile("testdata/resources/readme.txt", []byte("readme content"), 0644)
	defer os.RemoveAll("testdata")

	// Configure the builder
	builder := installer.New().
		WithIdentifier("com.example.app").
		WithName("MyApp").
		WithVersion("2.5.0").
		AddFile("testdata/app", "bin/myapp").
		WithDistributionUI(installer.Distribution{
			Readme: "testdata/resources/readme.txt",
		}).
		WithSimulate(true).
		WithLogger(func(format string, args ...any) {
			fmt.Printf(format+"\n", args...)
		})

	// Build the package
	if err := builder.Build("awesome.pkg"); err != nil {
		fmt.Printf("Build failed: %v\n", err)
	}
}
Example (Flat)
package main

import (
	"al.essio.dev/cmd/bakepkg/installer"
	"fmt"
	"os"
)

func main() {
	// Create dummy files to bundle for this example
	_ = os.MkdirAll("testdata", 0755)
	_ = os.WriteFile("testdata/hello", []byte("hello world"), 0755)
	defer os.RemoveAll("testdata")

	// Configure the builder — no explicit install location or scripts needed
	builder := installer.New().
		WithIdentifier("com.example.hello").
		WithName("Hello").
		WithVersion("1.0.0").
		AddFile("testdata/hello", "bin/hello").
		WithSimulate(true).
		WithLogger(func(format string, args ...any) {
			fmt.Printf(format+"\n", args...)
		})

	// Build the package — install location and scripts are auto-generated
	if err := builder.Build("hello.pkg"); err != nil {
		fmt.Printf("Build failed: %v\n", err)
	}
}

func (*Builder) Validate

func (b *Builder) Validate() error

Validate checks if the builder configuration is valid.

func (*Builder) WithDebug

func (b *Builder) WithDebug(debug bool) *Builder

WithDebug sets whether the builder should provide debug output (e.g., raw command output).

func (*Builder) WithDistributionUI

func (b *Builder) WithDistributionUI(ui Distribution) *Builder

WithDistributionUI configures the Distribution XML UI elements (Welcome, Readme, License, Backgrounds).

func (*Builder) WithIdentifier

func (b *Builder) WithIdentifier(id string) *Builder

WithIdentifier sets the package identifier (e.g., com.example.app).

func (*Builder) WithLogger

func (b *Builder) WithLogger(infof func(format string, args ...any)) *Builder

WithLogger sets the logging function for the builder.

func (*Builder) WithName

func (b *Builder) WithName(name string) *Builder

WithName sets the application name (required, used for install prefix and paths.d).

func (*Builder) WithScripts

func (b *Builder) WithScripts(scripts Scripts) *Builder

WithScripts configures custom install scripts for the package. When set, these override the auto-generated scripts.

func (*Builder) WithSigning

func (b *Builder) WithSigning(signing Signing) *Builder

WithSigning configures the Developer ID signing identity and optional Notarization credentials.

func (*Builder) WithSimulate

func (b *Builder) WithSimulate(simulate bool) *Builder

WithSimulate sets whether the builder should simulate the build process without actually executing it.

func (*Builder) WithSingleUser

func (b *Builder) WithSingleUser(singleUser bool) *Builder

WithSingleUser sets whether the package should support per-user installation. When true, a distribution package is generated with domain choices (enable_currentUserHome + enable_localSystem).

func (*Builder) WithSymlinkBinaries

func (b *Builder) WithSymlinkBinaries(symlink bool) *Builder

WithSymlinkBinaries sets whether the postinstall script should create symlinks in /usr/local/bin/ for each detected binary.

func (*Builder) WithVerbose

func (b *Builder) WithVerbose(verbose bool) *Builder

WithVerbose sets whether the builder should provide verbose output.

func (*Builder) WithVersion

func (b *Builder) WithVersion(version string) *Builder

WithVersion sets the package version (e.g., 1.0.0).

type Distribution

type Distribution struct {
	Readme         string
	License        string
	Welcome        string
	Background     string
	BackgroundDark string
}

Distribution defines the UI elements for a Distribution package. Providing any of these will automatically convert a flat package into a Distribution package.

type Options

type Options struct {
	Identifier      string
	Name            string
	Version         string
	InstallLocation string            // computed internally: always /Library
	Files           map[string]string // Source -> Destination
	Scripts         Scripts
	DistributionUI  Distribution
	Signing         Signing
	SingleUser      bool
	SymlinkBinaries bool
	Verbose         bool
	Debug           bool
	Simulate        bool
	Infof           func(format string, args ...any)
}

Options holds the configuration for the macOS package.

type ScriptData

type ScriptData struct {
	Identifier      string
	Name            string
	Version         string
	InstallPrefix   string   // e.g. /Library/MyTool/1.2.0
	Binaries        []string // auto-detected from files → bin/*
	HasManPages     bool
	ManPrefix       string // e.g. /Library/MyTool/1.2.0/share/man
	HasConfig       bool
	ConfigDir       string // e.g. /Library/MyTool/1.2.0/etc
	SymlinkBinaries bool
}

ScriptData holds the template data model for script generation.

type Scripts

type Scripts struct {
	PreInstall  string
	PostInstall string
	PreUpgrade  string
	PostUpgrade string
}

Scripts defines the paths to install scripts. Scripts must be executable and will be bundled into the package.

type Signing

type Signing struct {
	Identity      string
	Entitlements  []string
	Notarize      bool
	IssuerID      string
	KeyID         string
	PrivateKeyB64 string
}

Signing holds the configuration for code-signing and notarizing the package.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL