> ## Documentation Index
> Fetch the complete documentation index at: https://orchard.andrewshawcare.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Create and register custom Orchard provisioning modules

> Use scaffold.sh to generate a new module, write your install logic, and register it with provision.sh so it runs as part of every provisioning run.

Orchard is built around a modular architecture where every provisioning concern — packages, settings, tools — lives in its own directory with a dedicated `install.sh`. Adding your own module follows the same pattern: create a directory, add an `install.sh`, and register it in `provision.sh`. The `scaffold.sh` script handles the directory and file creation for you.

## How modules work

Each module is a subdirectory of the Orchard root. At minimum, every module contains an `install.sh` that performs its work. Modules can also include a `test.bats` file with [Bats](https://github.com/bats-core/bats-core) tests. The main `provision.sh` runs every registered module in order:

```bash provision.sh theme={null}
#!/bin/bash -euo pipefail

working_directory=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)

install_scripts=(
  brew
  brew-packages
  cask-packages
  dockutil
  dock
  mac-app-store
  finder
  keyboard
  mouse
  trackpad
  security-and-privacy
  bats
  claude-code
)

for script in "${install_scripts[@]}"; do
  "${working_directory}/${script}/install.sh"
done

./test.sh
```

To register a new module, add its directory name to the `install_scripts` array. The order matters — modules run top to bottom, so place your module after any dependencies it has.

## Creating a module with scaffold.sh

Run `scaffold.sh` from the Orchard root, passing your module name as the argument:

```bash theme={null}
./scaffold.sh my-module
```

This creates two files:

* `my-module/install.sh` — a minimal shell script with `#!/bin/bash -euo pipefail`, marked executable
* `my-module/test.bats` — a Bats test file with a placeholder failing test to remind you to write real tests

```bash my-module/install.sh theme={null}
#!/bin/bash -euo pipefail
```

```bash my-module/test.bats theme={null}
#!/usr/bin/env bats

@test 'Implement me!' {
  [ 0 -eq 1 ]
}
```

<Note>
  `scaffold.sh` automatically runs `chmod +x` on the generated `install.sh`. If you create an `install.sh` manually without using `scaffold.sh`, you must make it executable yourself: `chmod +x my-module/install.sh`. Otherwise `provision.sh` will fail with a permission error when it tries to run it.
</Note>

## Complete example: git global settings

Here is a walkthrough of creating a module that writes global Git configuration settings.

<Steps>
  <Step title="Generate the module">
    Run `scaffold.sh` from the Orchard root:

    ```bash theme={null}
    ./scaffold.sh git-config
    ```

    This creates `git-config/install.sh` and `git-config/test.bats`.
  </Step>

  <Step title="Write the install logic">
    Open `git-config/install.sh` and add the commands you want to run. For example:

    ```bash git-config/install.sh theme={null}
    #!/bin/bash -euo pipefail

    git config --global core.autocrlf input
    git config --global pull.rebase true
    git config --global init.defaultBranch main
    ```

    Each line runs as a standard shell command. You can use any tool that is available on the system — including packages installed by earlier modules in `provision.sh`.
  </Step>

  <Step title="Register the module in provision.sh">
    Open `provision.sh` and add `git-config` to the `install_scripts` array. Place it after `brew-packages` so that Git is already installed when the module runs:

    ```bash provision.sh theme={null}
    install_scripts=(
      brew
      brew-packages
      cask-packages
      dockutil
      dock
      mac-app-store
      finder
      keyboard
      mouse
      trackpad
      security-and-privacy
      bats
      claude-code
      git-config
    )
    ```
  </Step>

  <Step title="Run provision.sh">
    Execute the provisioner to run your new module along with everything else:

    ```bash theme={null}
    ./provision.sh
    ```

    To run only your new module during development, call its `install.sh` directly:

    ```bash theme={null}
    ./git-config/install.sh
    ```
  </Step>

  <Step title="Replace the placeholder test">
    Open `git-config/test.bats` and replace the failing placeholder with a real assertion. For example:

    ```bash git-config/test.bats theme={null}
    #!/usr/bin/env bats

    @test 'git pull.rebase is set to true' {
      result=$(git config --global pull.rebase)
      [ "$result" = "true" ]
    }
    ```

    Orchard runs all `test.bats` files at the end of `provision.sh` via `test.sh`. A failing placeholder test will cause the overall run to report failure until you replace it.
  </Step>
</Steps>

<CardGroup cols={2}>
  <Card title="Adding packages" icon="box" href="/customization/adding-packages">
    Add Homebrew, cask, or Mac App Store packages to an existing module.
  </Card>

  <Card title="System settings" icon="sliders" href="/customization/system-settings">
    Modify defaults write values to tune macOS behavior.
  </Card>
</CardGroup>
