Skip to content

Contributing

Rod Christiansen edited this page Sep 3, 2026 · 2 revisions

Contributing

How the repository is laid out, how to build and test it, and what a good change or bug report looks like. Read this before opening a pull request or filing an issue.

Repository layout

This is what is actually in the tree:

Sources/
  cli/                    bootstrapmate.swift — the whole command-line tool
  core/
    Managers/             ConfigManager, ManifestManager, IAOrchestrator, PackageManager,
                          ScriptManager, NetworkManager, SignatureVerifier, DialogManager,
                          StatusManager, ReportManager, SessionManager, CleanupManager,
                          TaskManager, BootstrapConfig
    Utilities/            BootstrapMateConstants, Logger, ManagementDetector,
                          ManifestDecoder, ManifestGenerator
    XPC/                  HelperXPCProtocol
  app/                    BootstrapMateApp.swift
    Services/             XPCClient
    ViewModels/           SettingsViewModel
    Views/                ContentView, RunView, LogView, ConsoleView, SettingsView
  helper/                 main.swift, HelperCommandRunner.swift
Tests/
  BootstrapMateCoreTests/ BootstrapMateCoreTests.swift
packaging/
  LaunchDaemons/          com.github.bootstrapmate.plist,
                          com.github.bootstrapmate.helper.plist
  resources/              Info.plist.template
  scripts/                postinstall
resources/                BootstrapMate.icon/, BootstrapMate.png, setup-notarization.sh
examples/                 manifest.json, manifest.yaml, config.mobileconfig,
                          preflight.sh, setup-credentials.sh
Makefile
Package.swift
Package.resolved

README.md still documents an older layout with Sources/BootstrapMateCore/ and Sources/BootstrapMateCLI/, and names files in examples/ that do not exist under those names. Trust the tree, not the README. If you move something, fix the README in the same pull request.

Four SPM products are built from those sources: the bootstrapmate executable (target BootstrapMateCLI), the BootstrapMateApp SwiftUI executable, the BootstrapMateHelper privileged daemon, and the BootstrapMateCore library that the other three share. Only BootstrapMateCore depends on a third-party package, Yams; the CLI additionally depends on swift-argument-parser.

Building and running the tests

swift build
swift test
swift build -c release --arch arm64 --arch x86_64

None of that needs signing credentials. The full signed and notarized package build is a separate path — see Building and Signing.

Tests use swift-testing, not XCTest: import Testing, @Suite, @Test and #expect. There are five suites in the single test file — BootstrapMateCore Tests, ReportManager Tests, SignatureVerifier Tests, ManifestDecoder Tests and Logger Tests. The test target depends on BootstrapMateCore only, so the CLI, the GUI and the helper have no test coverage; a change in those targets is verified by building and by running it.

CI runs on every pull request against main: dependency resolution, a debug build, swift test, a universal release build, and an unsigned pkgbuild smoke test. It must be green before a merge.

Conventions visible in the tree

  • The package is Swift 6 (swift-tools-version:6.0) and targets macOS 15 in the manifest.
  • Behaviour lives in Sources/core, not in the executables. Sources/cli/bootstrapmate.swift parses arguments and drives the orchestrator; the GUI never installs anything itself, it asks the helper over XPC. New provisioning logic belongs in a core manager.
  • Managers are named <Area>Manager and live one type per file under Managers/. Each exposes a nonisolated(unsafe) public static let shared singleton that callers use rather than constructing an instance.
  • Paths, identifiers and defaults are centralised in BootstrapMateConstants. Do not hardcode a path in a manager.
  • All output goes through Logger so that it is timestamped, levelled and written to the run log. See Logging and Reporting.
  • The manifest models are plain Codable structs with no CodingKeys. The Swift property name is the wire format. Renaming a property silently changes the key administrators must write in their manifests, so treat any rename there as a breaking change and update Manifest Reference and the files in examples/ with it.
  • Adding a preference key means touching ConfigManager for the read, and then actually threading the value to the code that uses it. Several keys are read into the config today and never consumed; do not add another one. Preferences marks which those are.

Branches and pull requests

Work on a branch and open a pull request against main; the history is merged pull requests, with branches named for the change, such as fix/userland-resilience. Direct pushes to main are not the norm.

Keep a pull request to one change. The commit subject is a plain, present-tense sentence saying what the change does, for example "Run userland regardless of setupassistant item failures" — no bracketed tags, no type prefixes on new commits.

If you are working on several things at once, the repository's convention is git worktrees placed inside the repository at ./.worktrees/<name>, not as sibling directories.

Releases are cut by pushing a v<version> tag; see Release Notes for the scheme. Do not tag as part of a feature pull request.

Reporting a bug

Provisioning failures are almost always diagnosable from the run log, so include it. A good report has:

  • The installed version, read from the bundle rather than from --version, which reports the current time rather than the build:

    defaults read /Applications/Utilities/BootstrapMate.app/Contents/Info CFBundleShortVersionString
  • The macOS version and architecture of the affected Mac, and whether the run happened during Setup Assistant, after login, or was started by hand.

  • The relevant part of the run log from /Library/Managed Bootstrap/logs/. One file is written per run; take the one matching the failed run, and include the whole section around the failure rather than a single line — the line before an error usually says which item was being processed. Logging and Reporting explains the format and which greps answer which question.

  • /Library/Managed Bootstrap/status.json, which records the stage, exit code and last error per phase.

  • /tmp/bootstrapmate-postinstall.log if the problem is that nothing ran at all — that is where the package install and the launchctl load leave their evidence.

  • The manifest item that failed, with its type, file, url and hash fields, and the exact flags or managed-preference keys in effect.

Redact before you post. Issues on this repository are public: replace internal hostnames and manifest URLs with https://example.com/ equivalents, and remove serial numbers, user names and Team IDs. The log's structure is what matters, not the values.

Check Troubleshooting and Gotchas first. Several behaviours that look like bugs are known and documented there, including preference keys that are read but never acted on, and a missing LaunchDaemon plist after a completed run.

See also

Clone this wiki locally