# Mac Mini Provisioning & Maintenance

This document covers usage of `scripts/setupMacosMini.sh` for CI Mac minis (installing toolchains, enforcing versions, creating simulators) and available environment flags.

## Phases

-   **init**: Create and/or regenerate `~/.bashrc` and `~/.bash_profile` with required environment blocks (Homebrew, NVM, RVM, Android SDK, SDKMAN, Xcode path). Backs up existing files before regenerating. Fixed: Xcode version detection now properly expands variables to prevent syntax errors.
-   **install**: Idempotently install system-level dependencies (Homebrew packages including aria2, ncdu; Node@20 via Homebrew, Ruby, Java, Android SDK pieces, Xcode, iOS runtime, baseline simulators). Performs macOS version enforcement first.
-   **setup**: Uses version managers (nvm, rvm, sdkman) to install and set specific language/runtime versions after environment sourcing.
-   **clean [--force]**: Displays (or removes with `--force`) large Xcode / build caches.
-   **about**: Prints a formatted status table showing installed vs. expected versions. Supports JSON output with `SIM_SUMMARY_JSON=1`.
-   **about:enforce**: Runs `about` after attempting to enforce Node and Ruby versions via version managers.
-   **about:quick**: Quick version check without loading tool environments (useful for debugging).

## Version Source

Expected toolchain versions are centrally managed in `./.ci-versions` and sourced by provisioning scripts. The `about` command compares installed versions and treats the listed values as minimum required (installed >= expected shows ✅).

## macOS Version Enforcement

The `install` phase calls `ensure_macos_version` which:

-   Does nothing if current macOS version meets or exceeds the minimum required version.
-   For minor/patch updates (same major version): Automatically applies updates and restarts the system. Handles SSH sessions gracefully by scheduling restart with a 1-minute delay.
-   For major upgrades: Fetches full installer and performs upgrade automatically if current version is below minimum (requires >=30GB free space).
-   **SSH Detection**: Script detects SSH sessions and provides appropriate restart instructions (`AGENT=XX yarn macmini:wait:install` to wait for reboot).
-   After restart, re-run the `install` phase to continue where it left off.

## iOS Runtime & Simulators

-   Runtime: `EXPECTED_IOS_RUNTIME_NAME` (e.g. `iOS 18.2`). Installation retried up to 3x via `xcodes runtimes install` unless blocked by authorization.
-   Skip runtime install: `SKIP_RUNTIME_INSTALL=1`.
-   Baseline simulators are created unless `CREATE_DEFAULT_SIMULATORS=0`.
-   Override baseline list: `SIM_DEVICES="iPhone 16;iPhone SE (3rd generation);iPad (10th generation)"` (semicolon separated).
-   A marker file `~/.sim_devices_<runtime>.stamp` caches a hash of requested devices + runtime so repeated runs skip creation if unchanged.

## Environment Flags Summary

| Flag                        | Default                             | Purpose                                                                           |
| --------------------------- | ----------------------------------- | --------------------------------------------------------------------------------- |
| `MACMINI_DEBUG`             | 0                                   | Enable detailed debug output with `set -x` and additional logging.                |
| `SKIP_RUNTIME_INSTALL`      | 0                                   | Skip iOS runtime install attempts.                                                |
| `CREATE_DEFAULT_SIMULATORS` | 1                                   | Disable (set to 0) to skip simulator creation logic.                              |
| `SIM_DEVICES`               | (uses categories)                   | Semicolon-separated custom simulator names (e.g., "iPhone 16;iPad Pro").          |
| `SIM_SUMMARY_JSON`          | 0                                   | When 1, `about` prints a JSON object after the table.                             |
| `SIM_DEVICE_CATEGORIES`     | iphone_latest;iphone_se;ipad_latest | Category set resolved dynamically to device names unless `SIM_DEVICES` provided.  |
| `SIM_CATEGORY_DEBUG`        | 0                                   | When 1, prints category->device resolution mapping.                               |
| `FORCE_BASHRC`              | 0                                   | Force sourcing of `~/.bashrc` in `about` command (normally loads tools directly). |

**Notes**:

-   `ALLOW_MACOS_AUTO_PATCH` and `ALLOW_MAJOR_MACOS_UPGRADE` flags removed - script now automatically handles OS updates when below minimum version.
-   macOS updates happen automatically during `install` phase if current version is below minimum.

## Version Auto-Correction

The `about` command automatically detects and corrects version mismatches for:

- **Node.js**: If wrong major version, attempts to activate NVM and install/use expected version
- **Ruby**: If version mismatch, activates RVM default version
- **Java**: If version mismatch, activates SDKMAN default version

This ensures that even if the wrong version is in PATH initially, the about command will attempt to correct it before displaying status.

## Node Version Issues (Common)

If `about` shows an unexpected Node version it usually means:

1. `~/.bashrc` was never generated - run `init` phase
2. The shell hasn't sourced the environment - run `source ~/.bashrc`
3. NVM hasn't installed the expected version - run `setup` phase

**Fix steps**:

```bash
./scripts/setupMacosMini.sh init
source ~/.bashrc
./scripts/setupMacosMini.sh setup
./scripts/setupMacosMini.sh about
```

The script installs Node@20 via Homebrew as a baseline, then uses NVM to manage specific versions. The `about` command will attempt to auto-correct version mismatches.

## Typical Provisioning Flows

### Fresh Machine (Local)

```bash
./scripts/setupMacosMini.sh init
./scripts/setupMacosMini.sh install   # may restart if OS updates needed
# If system restarts, re-run after reboot:
./scripts/setupMacosMini.sh install
./scripts/setupMacosMini.sh setup
./scripts/setupMacosMini.sh about
```

### Fresh Machine (Remote via yarn scripts)

```bash
# For agent01 (use AGENT=02, AGENT=03 for other agents)
AGENT=01 yarn macmini:full
# After restart (if needed), wait for reboot then continue:
AGENT=01 yarn macmini:wait:full
```

### Individual Phases (Remote)

```bash
AGENT=01 yarn macmini:init
AGENT=01 yarn macmini:install
# If system restarts:
AGENT=01 yarn macmini:wait:install
# Continue after reboot:
AGENT=01 yarn macmini:setup
AGENT=01 yarn macmini:about
```

### Skip Runtime + Simulators (faster iteration)

```bash
SKIP_RUNTIME_INSTALL=1 CREATE_DEFAULT_SIMULATORS=0 ./scripts/setupMacosMini.sh install
```

### Custom Simulator Set

```bash
# Explicit device names (semicolon-separated)
SIM_DEVICES="iPhone 16 Pro;iPhone 16;iPad Air (11-inch)" ./scripts/setupMacosMini.sh install
```

### Category-based Simulators (default)

```bash
# Default: iphone_latest;iphone_se;ipad_latest
./scripts/setupMacosMini.sh install

# Override categories:
SIM_DEVICE_CATEGORIES="iphone_latest;ipad_latest" ./scripts/setupMacosMini.sh install

# Debug category resolution:
SIM_CATEGORY_DEBUG=1 ./scripts/setupMacosMini.sh install
```

### Status and Debugging

```bash
# Full status report:
./scripts/setupMacosMini.sh about

# JSON output:
SIM_SUMMARY_JSON=1 ./scripts/setupMacosMini.sh about

# Quick version check (no env loading):
./scripts/setupMacosMini.sh about:quick

# Enforce versions before displaying:
./scripts/setupMacosMini.sh about:enforce
```

## Cleaning

The `clean` phase now supports modes and granular flags. By default it performs a LIGHT dry run (no deletion) showing planned targets and sizes.

### Modes

-   `light` (default): Core Xcode caches, simulators, DerivedData, module cache, archives (with retention preview)
-   `deep`: `light` + SwiftPM, Carthage, CocoaPods, Watchman, npm & yarn caches
-   `custom`: Activated when using `--only` paths or selectively adding caches via `--include-npm` / `--include-yarn`

### Key Flags

-   `--force` Actually delete (otherwise dry run)
-   `--mode <light|deep|custom>` Set cleanup scope
-   `--keep-archives <DAYS>` Retain last N days of Xcode Archives (default 14)
-   `--no-simulators` Skip CoreSimulator devices
-   `--no-device-support` Skip Xcode iOS DeviceSupport
-   `--include-npm` Include npm cache in non-deep modes
-   `--include-yarn` Include yarn cache in non-deep modes
-   `--only <ABS_PATH>` Restrict to specific absolute path (repeatable)

### Examples

Dry run (default light):

```bash
./scripts/setupMacosMini.sh clean
# Remote:
AGENT=01 yarn macmini:clean
```

Deep dry run:

```bash
AGENT=01 yarn macmini:clean --mode deep
```

Deep delete (destructive):

```bash
AGENT=01 yarn macmini:clean --mode deep --force
# Shortcut with new script:
AGENT=01 yarn macmini:clean:deep:force
```

Custom single path:

```bash
AGENT=01 yarn macmini:clean --only /Users/jenkinsagent/Library/Developer/Xcode/DerivedData --force
```

Skip simulators & device support:

```bash
AGENT=01 yarn macmini:clean --no-simulators --no-device-support
```

### Targets (depending on mode / flags)

-   Xcode DerivedData
-   Xcode Archives (retention applied before deletion)
-   Xcode iOS DeviceSupport
-   Xcode ModuleCache
-   CoreSimulator Devices
-   Xcode cache
-   SwiftPM cache (deep/custom)
-   Carthage cache (deep/custom)
-   CocoaPods cache (deep/custom)
-   Watchman state (deep/custom)
-   npm cache (deep or --include-npm)
-   yarn cache (deep or --include-yarn)
-   Custom paths via `--only`

All operations are previewed unless `--force` is supplied. Archive retention deletes only archives older than the specified days before other deletions proceed.

## Version Management Tools

### mise.toml Synchronization

The project uses both traditional version files (.node-version, .ruby-version, .java-version) for Mac mini provisioning and mise.toml for developers using [mise](https://github.com/jdx/mise). To keep these in sync:

```bash
# Synchronize mise.toml with version files
yarn sync-mise
```

This script:
- Reads versions from .node-version, .ruby-version, and .java-version
- Converts Java versions to mise format (e.g., 17.0.17-amzn → corretto-17.0.17.10.1)
- Updates mise.toml to match
- Creates a timestamped backup
- Verifies all changes

Run this script after updating any .xxx-version files to ensure mise users have the correct versions.

## CI Integration Notes

-   All phases are **idempotent** and safe to re-run.
-   The `install` phase is tolerant of failures where practical (non-fatal warnings).
-   Use `about` in build logs to confirm toolchain versions before starting builds.
-   The script includes **recursion guards** and **sentinel files** to prevent infinite loops.
-   SSH session detection automatically handles system restarts gracefully.
-   Use `AGENT=XX yarn macmini:wait:install` or `AGENT=XX yarn macmini:wait:full` to wait for reboots.

### Available Yarn Scripts

**Important**: All commands require the `AGENT` environment variable to be set. If not provided, an error will be shown.

| Script Pattern                   | Example                          | Description                           |
| -------------------------------- | -------------------------------- | ------------------------------------- |
| `AGENT=XX yarn macmini:init`     | `AGENT=02 yarn macmini:init`     | Initialize environment files          |
| `AGENT=XX yarn macmini:install`  | `AGENT=02 yarn macmini:install`  | Install system dependencies           |
| `AGENT=XX yarn macmini:setup`    | `AGENT=02 yarn macmini:setup`    | Setup runtime versions                |
| `AGENT=XX yarn macmini:about`    | `AGENT=02 yarn macmini:about`    | Show system status                    |
| `AGENT=XX yarn macmini:about:quick` | `AGENT=02 yarn macmini:about:quick` | Quick version check              |
| `AGENT=XX yarn macmini:clean`    | `AGENT=02 yarn macmini:clean`    | Show cache sizes (light mode dry run) |
| `AGENT=XX yarn macmini:clean:force` | `AGENT=02 yarn macmini:clean:force` | Delete light mode targets        |
| `AGENT=XX yarn macmini:clean:deep` | `AGENT=02 yarn macmini:clean:deep` | Show deep mode targets (dry run) |
| `AGENT=XX yarn macmini:clean:deep:force` | `AGENT=02 yarn macmini:clean:deep:force` | Delete deep mode targets |
| `AGENT=XX yarn macmini:full`     | `AGENT=02 yarn macmini:full`     | Run all phases in sequence            |
| `AGENT=XX yarn macmini:wait:install` | `AGENT=02 yarn macmini:wait:install` | Wait for reboot, then run install |
| `AGENT=XX yarn macmini:wait:full` | `AGENT=02 yarn macmini:wait:full` | Wait for reboot, then run full flow |
| `AGENT=XX yarn macmini:wait:about` | `AGENT=02 yarn macmini:wait:about` | Wait for reboot, then show status |

Replace `XX` with `01`, `02`, or `03` for your specific agent.

## Debug Mode

Enable detailed debug output:

```bash
MACMINI_DEBUG=1 ./scripts/setupMacosMini.sh <phase>
# Or via yarn:
AGENT=01 MACMINI_DEBUG=1 yarn macmini:about
```

Debug mode shows:

-   Script entry and exit points
-   Sentinel file operations
-   Recursion guard triggers
-   Environment loading steps
-   Detailed execution flow with `set -x`

## Implementation Details

### Recursion Guards

-   `__MACMINI_ALREADY_RUNNING` environment variable prevents nested invocations
-   Sentinel files (`/tmp/.macmini_about_running`) prevent external re-entry loops
-   Sentinel cleaned up automatically on EXIT trap

### Version Comparison Logic

-   Normalizes versions to 3-part format (X.Y.Z)
-   Strips suffixes like `-amzn` before comparison
-   Uses `sort -V` for semantic version comparison
-   Installed version >= expected version = ✅

### Node Installation Strategy

1. Install Node@20 via Homebrew as baseline
2. Force link Homebrew node@20 (handles keg-only)
3. Use NVM to install and manage specific versions
4. Auto-corrects version mismatches in `about` command

### Ruby Installation Strategy

1. Install Ruby@3.2 via Homebrew (provides build dependencies)
2. Use RVM to compile and manage specific versions
3. Links against Homebrew's openssl, readline, libyaml
4. Disables dtrace and install-doc for faster builds

### Simulator Management

-   Marker files cache simulator creation state using hash of (devices + runtime + categories)
-   Skip creation if marker matches and all devices present
-   Category-based resolution dynamically picks latest devices
-   Supports explicit device type identifiers and heuristic fallbacks

## Future Enhancements (Ideas)

-   Add checksum verification for downloaded Xcode packages
-   Optional `jq` parsing for richer JSON output
-   Automatic pruning of old simulators beyond the baseline list
-   Dedicated log file for provisioning actions
-   Health check command to verify all tools are working correctly
