https://github.com/cdzombak/mac-install
Idempotent software suite installer for macOS
https://github.com/cdzombak/mac-install
macos
Last synced: 8 months ago
JSON representation
Idempotent software suite installer for macOS
- Host: GitHub
- URL: https://github.com/cdzombak/mac-install
- Owner: cdzombak
- License: mit
- Created: 2025-07-15T18:57:19.000Z (11 months ago)
- Default Branch: main
- Last Pushed: 2025-09-26T19:37:23.000Z (9 months ago)
- Last Synced: 2025-10-23T21:41:33.569Z (8 months ago)
- Topics: macos
- Language: Go
- Homepage:
- Size: 193 KB
- Stars: 11
- Watchers: 0
- Forks: 1
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# `mac-install`
A program for installing and configuring a software suite on macOS. This tool ensures consistent, repeatable, and maintainable setup processes while reducing manual intervention.
## Features
- **Idempotent Operations**: Safe to run multiple times without side effects
- **Multi-Source Installation**: Supports Homebrew, Mac App Store, Go modules, npm, gem, and custom scripts
- **Required vs Optional Groups**: Some groups always install, others prompt the user
- **State Persistence**: Remembers user choices to avoid re-prompting
- **Automated Configuration**: Applies post-install configurations automatically
- **Manual Task Tracking**: Generates checklist for required manual steps
- **Internal Artifact Management**: Automatically installs Homebrew and dependencies when needed
## Installation
### Homebrew
```shell
brew install cdzombak/oss/mac-install
```
### Manual from build artifacts
Pre-built binaries for macOS on various architectures are downloadable from each [GitHub Release](https://github.com/cdzombak/mac-install/releases).
## Quick Start
1. **Create a configuration file** (see `install.example.yaml` for a complete example):
```yaml
checklist: $HOME/SystemSetup.md
install_groups:
- group: Essential Tools
software:
- name: Git
artifact: $BREW/bin/git
install:
- brew: git
```
💡 **Pro tip**: Use the included YAML schema (`schema.yaml`) for autocompletion and validation in your editor. The project includes VS Code settings for automatic schema detection. See [SCHEMA.md](SCHEMA.md) for detailed setup instructions for various editors.
2. **Run the installer**:
```bash
./mac-install -config install.example.yaml
```
To skip all optional sections:
```bash
./mac-install -config install.example.yaml -skip-optional
```
To install only a single piece of software:
```bash
./mac-install -config install.example.yaml -only "Autodesk"
```
## Configuration Format
The configuration is defined in YAML format with the following structure. See [`@cdzombak/dotfiles/mac/install.yaml`](https://github.com/cdzombak/dotfiles/blob/master/mac/install.yaml) for a real-world example.
### Root Level
- `checklist`: Path to the checklist file where manual setup steps are written
- `install_groups`: Array of software groups to install
### Install Groups
Each group contains:
- `group`: Human-readable group name (e.g., "Core Tools", "Development")
- `optional`: Boolean indicating whether to prompt for each software item (optional, defaults to true)
- `software`: Array of software definitions
### Software Definitions
Each software item must have:
- `artifact`: Path to the installed artifact (file/app that indicates successful installation)
Optional fields:
- `name`: Human-readable software name (defaults to artifact display name if not provided)
- `note`: Optional note displayed to the user when prompting for installation (useful for warnings, size information, etc.)
- `persist`: Boolean indicating whether to remember user's choice not to install (defaults to false)
- `install`: Array of installation steps
- `configure`: Array of configuration steps
- `checklist`: Array of manual post-installation steps
**Note:** Artifact paths support asterisk (`*`) wildcards for version-agnostic matching. See [Wildcard Support](#wildcard-support) section for details.
### Installation Methods
The `install` section supports these methods:
- `brew: package-name` - Install via Homebrew
- `cask: package-name` - Install GUI app via Homebrew Cask
- `mas: app-id` - Install from Mac App Store. Accepts either an app ID (e.g., `"1502933106"`) or an App Store URL (e.g., `"https://apps.apple.com/us/app/meshman-3d-viewer-pro/id1502933106?mt=12"`). **NOTE:** The value must be enclosed in quotes.
- `npm: package-name` - Install global npm package
- `gem: package-name` - Install Ruby gem
- `gomod: package-name` - Install Go module via Homebrew
- `pipx: package-name` - Install Python package via pipx
- `dl: url` - Download file from URL and save directly to artifact path
- `run: command` - Execute shell command
- `script: /path/to/script.sh` - Run shell script
- `archive: url` + `file: filename` - Download and extract archive (.dmg, .zip, .tar.gz), then copy specified file to /Applications
- `archive: url` (without `file`) - Download and extract all files from archive to the directory containing the artifact
**Note:** Archive type is automatically detected from the URL (e.g., URLs containing `.dmg`, `.zip`, `.tar.gz`), HTTP Content-Type headers, or from the downloaded file extension. Supported formats include DMG (disk images), ZIP archives, and TAR.GZ compressed archives.
### Configuration Methods
The `configure` section supports:
- `ignore_errors: true` - Ignore errors in subsequent configuration steps
- `run: command` - Execute shell command
- `script: /path/to/script.sh` - Run shell script
### Automatic Application Launch
When installing a `.app` application that has `run` or `script` configuration steps, the application will be automatically opened before configuration begins. This ensures apps that need to be running for configuration are launched automatically.
**Conditions for automatic launch:**
1. Software was just installed (not already present)
2. Artifact path ends with `.app`
3. Configuration steps include at least one `run` or `script` command
The system waits 2 seconds after opening the application before proceeding with configuration to allow the app to start up properly.
**Note:** If the application cannot be opened, a warning is displayed but the installation process continues.
### Variable Expansion
The following variables are automatically expanded:
- `$HOME`: User's home directory
- `$BREW`: Homebrew prefix (typically `/opt/homebrew` or `/usr/local`)
- `$ENV_VARIABLE_NAME`: Environment variables using the `$ENV_` prefix (e.g., `$ENV_ASDF_PY` expands to the value of the `ASDF_PY` environment variable)
**Note:** If an environment variable referenced with `$ENV_` is not set, the configuration loading will fail with an error message.
### Wildcard Support
Artifact paths support asterisk (`*`) wildcards for version-agnostic matching. This is useful when application names include version numbers that may change over time.
**Examples:**
- `/Applications/OpenSCAD*.app` matches both `OpenSCAD.app` and `OpenSCAD-2021.01.app`
- `$HOME/Library/Application Support/MyApp*/config.json` matches version-specific directories
- `$BREW/bin/tool-*` matches versioned command-line tools
Wildcards use Go's `filepath.Glob` pattern matching and will return true if at least one matching file or directory is found.
## Usage
### Command Line Options
- `-config `: Path to configuration YAML file (default: `./install.yaml`)
- `-skip-optional`: Skip all optional sections - no installation, configuration, or checklist actions are taken for items in optional groups
- `-only `: Install only a single piece of software matching this name. Searches both user-chosen names and artifact basenames. If multiple matches are found, lists candidates and exits with error. Cannot be used with `-skip-optional`.
### Examples
#### Required vs Optional Groups
```yaml
# Required group - installs automatically
- group: Essential Tools
optional: false
software:
- name: Homebrew
artifact: /opt/homebrew/bin/brew
# Homebrew is automatically installed as an internal artifact
# Optional group - prompts user
- group: Optional Development Tools
optional: true # This is the default
software:
- name: Visual Studio Code
artifact: /Applications/Visual Studio Code.app
install:
- cask: visual-studio-code
```
#### Basic Software Installation
```yaml
- name: Visual Studio Code
artifact: /Applications/Visual Studio Code.app
install:
- cask: visual-studio-code
configure:
- run: code --install-extension ms-vscode.vscode-json
checklist:
- Sign in to Settings Sync
- Configure preferred themes
```
#### Software with User Notes
```yaml
- name: Xcode
artifact: /Applications/Xcode.app
note: This is a large download and may take 30+ minutes
install:
- mas: "497799835"
checklist:
- Accept Xcode license agreement
- Install additional components when prompted
```
#### Multiple Installation Methods
```yaml
- name: Node.js
artifact: $BREW/bin/node
install:
- brew: node
- npm: npm@latest
configure:
- run: npm config set init-license "MIT"
```
#### Error Handling in Configuration
```yaml
- name: Docker Desktop
artifact: /Applications/Docker.app
install:
- cask: docker
configure:
- ignore_errors: "true"
- run: docker --version # May fail if Docker isn't running
```
#### Manual Installation Only
```yaml
- name: Custom Software
artifact: /Applications/Custom.app
# No install steps - will prompt to add manual installation to checklist
checklist:
- Download from vendor website
- Install manually
```
#### Persist User Choices
```yaml
# Software that remembers user's choice not to install
- name: Optional Tool (Remembered)
artifact: /Applications/OptionalTool.app
persist: true # Remember choice - won't ask again if user says no
install:
- cask: optional-tool
# Software that asks every time
- name: Optional Tool (Always Ask)
artifact: /Applications/AlwaysAsk.app
persist: false # Default - will ask on every run
install:
- cask: always-ask-tool
```
#### Archive Installation
```yaml
# Install specific file from a DMG archive
- name: Custom Application
artifact: /Applications/CustomApp.app
install:
- archive: https://example.com/releases/CustomApp.dmg
file: CustomApp.app # File/directory to copy from the archive
checklist:
- Launch CustomApp and complete setup
- Enter license key if required
# Extract all files from ZIP archive to target directory
- name: Font Collection
artifact: /Library/Fonts/CustomFont.ttf
install:
- archive: https://fonts.example.com/font-pack.zip
# No 'file' parameter - extracts all files to /Library/Fonts/
checklist:
- Verify fonts appear in Font Book
- Test fonts in applications
# TAR.GZ archive with specific binary extraction
- name: Command Line Tool
artifact: /usr/local/bin/tool
install:
- archive: https://github.com/vendor/tool/releases/download/v1.0/tool.tar.gz
file: tool # Binary file to copy
```
#### Wildcard Artifact Paths
```yaml
# Version-agnostic application matching
- name: OpenSCAD
artifact: /Applications/OpenSCAD*.app # Matches OpenSCAD.app, OpenSCAD-2021.01.app, etc.
install:
- cask: openscad
checklist:
- Configure 3D rendering preferences
- Import custom libraries
# Versioned configuration files
- name: Development Config
artifact: $HOME/.config/myapp-*/settings.json
install:
- dl: https://example.com/config/settings.json
```
## Program Behavior
### Installation Workflow
1. **Internal Artifacts**: Automatically installs Homebrew and dependencies if any software requires them
2. **Group Processing**: Processes each software group in order
3. **Artifact Check**: Verifies if the target artifact already exists
4. **Skip or Install**:
- If exists: Reports "already installed", checks for missing checklist items, and skips to configuration
- If missing: For optional groups, prompts user; for required groups, installs automatically
5. **Configuration**: Applies post-install configurations if artifact exists
6. **Checklist Update**: Adds manual steps to checklist for newly installed software or existing software with missing checklist items
### User Interaction
- For optional groups only: prompts "Install [software]? (y/N)" in colored cyan text
- Required groups (optional: false) install automatically without prompting
- User choices are persisted in `~/.config/dotfiles/software/` as flag files only when `persist: true`
- When `persist: false` (default), software will be prompted about on every run
- Subsequent runs respect previous choices and don't re-prompt for persisted software
- Colored output provides visual feedback (green=success, yellow=warning, red=error, blue=info)
### State Management
- Exclusion flags stored as files named `no-[normalized-software-name]` only when `persist: true`
- State directory: `~/.config/dotfiles/software/`
- Filename normalization: lowercase, spaces→hyphens, slashes→hyphens, `.app` suffix removed
- Software with `persist: false` (default) will not create state files and will be prompted about every run
**Examples of state file names:**
- "Visual Studio Code" → `no-visual-studio-code`
- "1Password 7 - Password Manager.app" → `no-1password-7---password-manager`
- "My App/Tool" → `no-my-app-tool`
### Checklist Generation
- Manual steps written to specified checklist file (typically `~/SystemSetup.md`)
- Uses Markdown format with checkboxes
- Headers based on artifact display names
- Idempotent: won't create duplicate entries
- Includes Homebrew caveats when applicable
- **Automatically creates checklist entries for already-installed software** if the header is missing
### Error Handling
- Program exits with failure if any installation or configuration step fails
- Idempotent design allows safe re-running to resolve errors
- Configuration steps can be set to ignore errors with `ignore_errors: true`