Compare commits

..

No commits in common. "main" and "14.3-RC-2" have entirely different histories.

45 changed files with 184 additions and 3492 deletions

47
.cirrus.yml Normal file
View File

@ -0,0 +1,47 @@
task:
name: "Update Base Image"
only_if: $CIRRUS_CRON != ""
persistent_worker:
labels:
name: dev-mini
env:
TART_REGISTRY_USERNAME: fkorotkov # GitHub supports only PATs
TART_REGISTRY_PASSWORD: ENCRYPTED[!82ed873afdf627284305afef4958c85a8f73127b09978a9786ac521559630ea6c9a5ab6e7f8315abf9ead09b6eff6eae!]
update_script: brew update && brew upgrade
info_script:
- tart --version
- packer --version
build_script:
- packer init templates/base.pkr.hcl
- packer build -var-file="variables.pkrvars.hcl" templates/base.pkr.hcl
push_script:
- tart push ventura-base ghcr.io/cirruslabs/macos-ventura-base:latest
always:
cleanup_script:
- tart delete ventura-base
task:
name: "Release Xcode $CIRRUS_TAG"
only_if: $CIRRUS_TAG != ""
persistent_worker:
labels:
name: dev-mini
env:
TART_REGISTRY_USERNAME: fkorotkov # GitHub supports only PATs
TART_REGISTRY_PASSWORD: ENCRYPTED[!82ed873afdf627284305afef4958c85a8f73127b09978a9786ac521559630ea6c9a5ab6e7f8315abf9ead09b6eff6eae!]
update_script: brew update && brew upgrade
info_script:
- tart --version
- packer --version
pull_base_script:
- tart pull ghcr.io/cirruslabs/macos-ventura-base:latest
- tart clone ghcr.io/cirruslabs/macos-ventura-base:latest ventura-base
build_script:
- packer init templates/xcode.pkr.hcl
- packer build -var-file="variables.pkrvars.hcl" -var xcode_version="$CIRRUS_TAG" templates/xcode.pkr.hcl
push_script:
- tart push ventura-xcode:$CIRRUS_TAG ghcr.io/cirruslabs/macos-ventura-xcode:$CIRRUS_TAG ghcr.io/cirruslabs/macos-ventura-xcode:latest
always:
cleanup_script:
- tart delete ventura-base || true
- tart delete ventura-xcode:$CIRRUS_TAG || true

View File

@ -1,119 +0,0 @@
name: Base Images
on:
push:
paths:
- ".github/workflows/base.yml"
- "data/github_known_hosts"
- "data/limit.maxfiles.plist"
- "data/tart-guest-*.plist"
- "scripts/automationmodetool.expect"
- "scripts/install-actions-runner.sh"
- "scripts/update-tcc-database.sh"
- "templates/base.pkr.hcl"
- "templates/disable-sip*.pkr.hcl"
workflow_dispatch:
inputs:
macos_version:
description: "macOS base image to build"
required: true
default: all
type: choice
options:
- all
- golden-gate
- tahoe
- sequoia
- sonoma
permissions:
contents: read
packages: write
concurrency:
group: tart-image-builds
cancel-in-progress: false
env:
TART_REGISTRY_HOSTNAME: ghcr.io
TART_REGISTRY_USERNAME: ${{ github.actor }}
TART_REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
jobs:
update-base:
name: Update Base Image (${{ matrix.macos_version }})
if: >-
${{
github.event_name == 'workflow_dispatch' ||
github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
}}
runs-on: [self-hosted, macOS, ARM64]
timeout-minutes: 180
strategy:
fail-fast: false
max-parallel: 1
matrix:
include:
- macos_version: sonoma
disable_sip_template: disable-sip.pkr.hcl
- macos_version: sequoia
disable_sip_template: disable-sip-with-username.pkr.hcl
- macos_version: tahoe
disable_sip_template: disable-sip-with-username.pkr.hcl
- macos_version: golden-gate
disable_sip_template: disable-sip-with-username.pkr.hcl
env:
DISABLE_SIP_TEMPLATE: ${{ matrix.disable_sip_template }}
MACOS_VERSION: ${{ matrix.macos_version }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Select image
id: select
env:
INPUT_MACOS_VERSION: ${{ inputs.macos_version || 'all' }}
run: |
set -euo pipefail
build=false
if [[ "$INPUT_MACOS_VERSION" == "all" || "$INPUT_MACOS_VERSION" == "$MACOS_VERSION" ]]; then
build=true
fi
echo "build=$build" >> "$GITHUB_OUTPUT"
- name: Tool versions
if: steps.select.outputs.build == 'true'
run: |
tart --version
packer --version
- name: Pull vanilla image
if: steps.select.outputs.build == 'true'
run: |
tart pull "ghcr.io/cirruslabs/macos-$MACOS_VERSION-vanilla:latest"
tart clone "ghcr.io/cirruslabs/macos-$MACOS_VERSION-vanilla:latest" "$MACOS_VERSION-base"
- name: Disable SIP
if: steps.select.outputs.build == 'true'
run: |
packer init "templates/$DISABLE_SIP_TEMPLATE"
packer build -var "vm_name=$MACOS_VERSION-base" "templates/$DISABLE_SIP_TEMPLATE"
- name: Build base image
if: steps.select.outputs.build == 'true'
run: |
packer init templates/base.pkr.hcl
packer build -var "vm_name=$MACOS_VERSION-base" templates/base.pkr.hcl
- name: Push base image
if: steps.select.outputs.build == 'true'
run: |
tart push "$MACOS_VERSION-base" "ghcr.io/cirruslabs/macos-$MACOS_VERSION-base:latest"
- name: Cleanup
if: always() && steps.select.outputs.build == 'true'
run: |
tart delete "$MACOS_VERSION-base" || true

View File

@ -1,203 +0,0 @@
name: Monthly Images
on:
schedule:
- cron: "0 8 * * 6"
workflow_dispatch:
permissions:
contents: read
packages: write
concurrency:
group: tart-image-builds
cancel-in-progress: false
env:
TART_REGISTRY_HOSTNAME: ghcr.io
TART_REGISTRY_USERNAME: ${{ github.actor }}
TART_REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
jobs:
should-run:
name: Check Monthly Cadence
runs-on: [self-hosted, macOS, ARM64]
outputs:
build: ${{ steps.cadence.outputs.build }}
steps:
- name: Check first Saturday
id: cadence
env:
EVENT_NAME: ${{ github.event_name }}
run: |
set -euo pipefail
build=false
if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then
build=true
else
day="$(date -u +%d)"
if (( 10#$day <= 7 )); then
build=true
fi
fi
echo "build=$build" >> "$GITHUB_OUTPUT"
if [[ "$build" != "true" ]]; then
echo "Not the first Saturday of the month; skipping image rebuilds."
fi
update-base:
name: Update Base Images (${{ matrix.macos_version }})
needs: should-run
if: needs.should-run.outputs.build == 'true'
runs-on: [self-hosted, macOS, ARM64]
timeout-minutes: 180
strategy:
fail-fast: false
max-parallel: 1
matrix:
include:
- macos_version: sonoma
disable_sip_template: disable-sip.pkr.hcl
- macos_version: sequoia
disable_sip_template: disable-sip-with-username.pkr.hcl
- macos_version: tahoe
disable_sip_template: disable-sip-with-username.pkr.hcl
env:
MACOS_VERSION: ${{ matrix.macos_version }}
DISABLE_SIP_TEMPLATE: ${{ matrix.disable_sip_template }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Tool versions
run: |
tart --version
packer --version
- name: Pull vanilla image
run: |
tart pull "ghcr.io/cirruslabs/macos-$MACOS_VERSION-vanilla:latest"
tart clone "ghcr.io/cirruslabs/macos-$MACOS_VERSION-vanilla:latest" "$MACOS_VERSION-base"
- name: Disable SIP
run: |
packer init "templates/$DISABLE_SIP_TEMPLATE"
packer build -var "vm_name=$MACOS_VERSION-base" "templates/$DISABLE_SIP_TEMPLATE"
- name: Build base image
run: |
packer init templates/base.pkr.hcl
packer build -var "vm_name=$MACOS_VERSION-base" templates/base.pkr.hcl
- name: Push base image
run: |
tart push "$MACOS_VERSION-base" "ghcr.io/cirruslabs/macos-$MACOS_VERSION-base:latest"
- name: Cleanup
if: always()
run: |
tart delete "$MACOS_VERSION-base" || true
update-xcode:
name: Update Xcode Images (${{ matrix.macos_version }} ${{ matrix.xcode_version }})
needs:
- should-run
- update-base
if: needs.should-run.outputs.build == 'true'
runs-on: [self-hosted, macOS, ARM64]
timeout-minutes: 180
strategy:
fail-fast: false
max-parallel: 1
matrix:
include:
- macos_version: tahoe
xcode_version: "26.2"
xcode_components: '"MetalToolchain"'
latest: true
- macos_version: tahoe
xcode_version: "26.1.1"
xcode_components: '"MetalToolchain"'
latest: false
- macos_version: tahoe
xcode_version: "26.0.1"
xcode_components: '"MetalToolchain"'
latest: false
- macos_version: sequoia
xcode_version: "16.4"
xcode_components: ""
latest: true
- macos_version: sequoia
xcode_version: "16.3"
xcode_components: ""
latest: false
- macos_version: sequoia
xcode_version: "16.2"
xcode_components: ""
latest: false
- macos_version: sequoia
xcode_version: "16.1"
xcode_components: ""
latest: false
- macos_version: sequoia
xcode_version: "16"
xcode_components: ""
latest: false
- macos_version: sonoma
xcode_version: "16.1"
xcode_components: ""
latest: true
- macos_version: sonoma
xcode_version: "16"
xcode_components: ""
latest: false
- macos_version: sonoma
xcode_version: "15.4"
xcode_components: ""
latest: false
env:
MACOS_VERSION: ${{ matrix.macos_version }}
XCODE_COMPONENTS: ${{ matrix.xcode_components }}
XCODE_VERSION: ${{ matrix.xcode_version }}
LATEST: ${{ matrix.latest }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Tool versions
run: |
tart --version
packer --version
- name: Pull base image
run: |
tart pull "ghcr.io/cirruslabs/macos-$MACOS_VERSION-base:latest"
- name: Build Xcode image
run: |
packer init templates/xcode.pkr.hcl
packer build \
-var "macos_version=$MACOS_VERSION" \
-var "xcode_version=[\"$XCODE_VERSION\"]" \
-var "xcode_components=[$XCODE_COMPONENTS]" \
templates/xcode.pkr.hcl
- name: Push Xcode image
run: |
if [[ "$LATEST" == "true" ]]; then
tart push "$MACOS_VERSION-xcode:$XCODE_VERSION" \
"ghcr.io/cirruslabs/macos-$MACOS_VERSION-xcode:$XCODE_VERSION" \
"ghcr.io/cirruslabs/macos-$MACOS_VERSION-xcode:latest"
else
tart push "$MACOS_VERSION-xcode:$XCODE_VERSION" \
"ghcr.io/cirruslabs/macos-$MACOS_VERSION-xcode:$XCODE_VERSION"
fi
- name: Cleanup
if: always()
run: |
tart delete "$MACOS_VERSION-xcode:$XCODE_VERSION" || true

View File

@ -1,167 +0,0 @@
name: Release Images
on:
release:
types:
- published
workflow_dispatch:
inputs:
xcode_version:
description: "Xcode version/tag to release"
required: true
type: string
permissions:
contents: read
packages: write
concurrency:
group: tart-image-builds
cancel-in-progress: false
env:
TART_REGISTRY_HOSTNAME: ghcr.io
TART_REGISTRY_USERNAME: ${{ github.actor }}
TART_REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
XCODE_RELEASE_VERSION: ${{ github.event.release.tag_name || inputs.xcode_version }}
jobs:
release-xcode:
name: Release Xcode ${{ github.event.release.tag_name || inputs.xcode_version }} (${{ matrix.macos_version }})
runs-on: [self-hosted, macOS, ARM64]
timeout-minutes: 180
strategy:
fail-fast: false
max-parallel: 1
matrix:
macos_version:
- tahoe
- sequoia
env:
MACOS_VERSION: ${{ matrix.macos_version }}
XCODE_COMPONENTS: '"MetalToolchain"'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Validate release version
run: |
set -euo pipefail
if [[ -z "$XCODE_RELEASE_VERSION" ]]; then
echo "XCODE_RELEASE_VERSION is required."
exit 1
fi
- name: Tool versions
run: |
tart --version
packer --version
- name: Pull base image
run: |
tart pull "ghcr.io/cirruslabs/macos-$MACOS_VERSION-base:latest"
- name: Build release image
run: |
packer init templates/xcode.pkr.hcl
packer build \
-var "macos_version=$MACOS_VERSION" \
-var "xcode_version=[\"$XCODE_RELEASE_VERSION\"]" \
-var "xcode_components=[$XCODE_COMPONENTS]" \
templates/xcode.pkr.hcl
- name: Push release image
run: |
if [[ "$XCODE_RELEASE_VERSION" == *beta* ]]; then
tart push "$MACOS_VERSION-xcode:$XCODE_RELEASE_VERSION" \
"ghcr.io/cirruslabs/macos-$MACOS_VERSION-xcode:$XCODE_RELEASE_VERSION"
else
tart push "$MACOS_VERSION-xcode:$XCODE_RELEASE_VERSION" \
"ghcr.io/cirruslabs/macos-$MACOS_VERSION-xcode:$XCODE_RELEASE_VERSION" \
"ghcr.io/cirruslabs/macos-$MACOS_VERSION-xcode:latest"
fi
- name: Cleanup
if: always()
run: |
tart delete "$MACOS_VERSION-xcode:$XCODE_RELEASE_VERSION" || true
release-runner:
name: Update Runner Image (${{ matrix.macos_version }})
# Keep runner refreshes independent from the release-xcode matrix so one
# Xcode image failure does not skip the runner matrix.
runs-on: [self-hosted, macOS, ARM64]
timeout-minutes: 180
strategy:
fail-fast: false
max-parallel: 1
matrix:
include:
- macos_version: sequoia
xcode_versions: '"26.0.1",16.4,16.3,16.2,16.1,16'
additional_ios_builds: "18.5,18.4,18.2,17.5"
additional_tvos_builds: "17.5"
xcode_components: '"MetalToolchain"'
disk_size: 380
- macos_version: tahoe
xcode_versions: '"26.6","27-beta-4","26.5","27-beta","26.4.1","26.3","26.2","26.1.1","26.0.1"'
additional_ios_builds: "18.6"
additional_tvos_builds: ""
xcode_components: '"MetalToolchain"'
disk_size: 520
env:
ADDITIONAL_IOS_BUILDS: ${{ matrix.additional_ios_builds }}
ADDITIONAL_TVOS_BUILDS: ${{ matrix.additional_tvos_builds }}
DISK_SIZE: ${{ matrix.disk_size }}
MACOS_VERSION: ${{ matrix.macos_version }}
XCODE_COMPONENTS: ${{ matrix.xcode_components }}
XCODE_VERSIONS: ${{ matrix.xcode_versions }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Tool versions
run: |
tart --version
packer --version
- name: Pull base image
run: |
tart pull "ghcr.io/cirruslabs/macos-$MACOS_VERSION-base:latest"
- name: Build runner image
run: |
packer init templates/xcode.pkr.hcl
packer build \
-var tag=runner \
-var "disk_size=$DISK_SIZE" \
-var disk_free_mb=100000 \
-var "macos_version=$MACOS_VERSION" \
-var "xcode_version=[$XCODE_VERSIONS]" \
-var "additional_ios_builds=[$ADDITIONAL_IOS_BUILDS]" \
-var "additional_tvos_builds=[$ADDITIONAL_TVOS_BUILDS]" \
-var "xcode_components=[$XCODE_COMPONENTS]" \
-var "expected_runtimes_file=data/expected.$MACOS_VERSION.runtimes.txt" \
templates/xcode.pkr.hcl
- name: Finalize runner image
run: |
if [[ -f "scripts/finalize-$MACOS_VERSION.sh" ]]; then
packer build \
-var "vm_name=$MACOS_VERSION-xcode:runner" \
-var "script_path=scripts/finalize-$MACOS_VERSION.sh" \
templates/exex-script.pkr.hcl
else
echo "Skipping prepare script for $MACOS_VERSION"
fi
- name: Push runner image
run: |
tart push "$MACOS_VERSION-xcode:runner" "ghcr.io/cirruslabs/macos-runner:$MACOS_VERSION"
- name: Cleanup
if: always()
run: |
tart delete "$MACOS_VERSION-xcode:runner" || true

View File

@ -1,394 +0,0 @@
name: Template Builds
on:
pull_request:
paths:
- ".github/workflows/base.yml"
- ".github/workflows/monthly.yml"
- ".github/workflows/release.yml"
- ".github/workflows/template-validation.yml"
- "data/**"
- "scripts/**"
- "templates/**"
permissions:
contents: read
packages: read
concurrency:
group: template-builds-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
env:
FASTLANE_SESSION: ${{ secrets.FASTLANE_SESSION }}
FASTLANE_USER: ${{ secrets.FASTLANE_USER }}
HOMEBREW_NO_AUTO_UPDATE: 1
HOMEBREW_NO_INSTALL_CLEANUP: 1
PACKER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TART_REGISTRY_HOSTNAME: ghcr.io
TART_REGISTRY_USERNAME: ${{ github.actor }}
TART_REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
jobs:
packer-validate:
name: Packer Validate
runs-on: macos-15
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Packer
uses: hashicorp/setup-packer@v3
- name: Install validation dependencies
run: |
brew install ansible
- name: Validate Tart Metal capabilities
run: |
bash -n scripts/install-tart-metal-capabilities.sh scripts/test-tart-metal-capabilities.sh
bash scripts/test-tart-metal-capabilities.sh
- name: Validate TCC database provisioning
run: |
bash -n scripts/update-tcc-database.sh scripts/test-update-tcc-database.sh
bash scripts/test-update-tcc-database.sh
- name: Prepare validation inputs
run: |
mkdir -p "$HOME/XcodesCache"
touch "$HOME/XcodesCache/Xcode_26.6.xip"
- name: Validate templates
run: |
set -euo pipefail
validate() {
local template="$1"
shift
packer init "$template"
packer validate "$@" "$template"
}
for template in templates/vanilla-*.pkr.hcl; do
validate "$template"
done
validate templates/base.pkr.hcl \
-var vm_name=template-validation-base
validate templates/disable-sip.pkr.hcl \
-var vm_name=template-validation-disable-sip
validate templates/disable-sip-with-username.pkr.hcl \
-var vm_name=template-validation-disable-sip-user
validate templates/exex-script.pkr.hcl \
-var vm_name=template-validation-exec \
-var script_path=scripts/finalize-tahoe.sh
validate templates/resolve-macos-number.pkr.hcl \
-var vm_base_name=template-validation-base \
-var vm_name=template-validation-resolve \
-var resolve_file=macos-version.txt
validate templates/xcode.pkr.hcl \
-var macos_version=tahoe \
-var 'xcode_version=["26.6"]' \
-var expected_runtimes_file=data/expected.tahoe.runtimes.txt
build-vanilla:
name: Build Vanilla Image (${{ matrix.macos_version }})
needs: packer-validate
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: [self-hosted, macOS, ARM64]
timeout-minutes: 180
strategy:
fail-fast: false
max-parallel: 1
matrix:
macos_version:
- golden-gate
- tahoe
- sequoia
- sonoma
- monterey
env:
MACOS_VERSION: ${{ matrix.macos_version }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Select image
id: select
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set -euo pipefail
git diff --name-only "$BASE_SHA" "$HEAD_SHA" > changed-files.txt
build=false
if grep -Fxq "templates/vanilla-$MACOS_VERSION.pkr.hcl" changed-files.txt; then
build=true
fi
echo "build=$build" >> "$GITHUB_OUTPUT"
- name: Tool versions
if: steps.select.outputs.build == 'true'
run: |
tart --version
packer --version
- name: Build vanilla image
if: steps.select.outputs.build == 'true'
run: |
packer init "templates/vanilla-$MACOS_VERSION.pkr.hcl"
packer build "templates/vanilla-$MACOS_VERSION.pkr.hcl"
- name: Cleanup
if: always() && steps.select.outputs.build == 'true'
run: |
tart delete "$MACOS_VERSION-vanilla" || true
build-base:
name: Build Base Image (${{ matrix.macos_version }})
needs: packer-validate
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: [self-hosted, macOS, ARM64]
timeout-minutes: 180
strategy:
fail-fast: false
max-parallel: 1
matrix:
include:
- macos_version: sonoma
disable_sip_template: disable-sip.pkr.hcl
- macos_version: sequoia
disable_sip_template: disable-sip-with-username.pkr.hcl
- macos_version: tahoe
disable_sip_template: disable-sip-with-username.pkr.hcl
- macos_version: golden-gate
disable_sip_template: disable-sip-with-username.pkr.hcl
env:
DISABLE_SIP_TEMPLATE: ${{ matrix.disable_sip_template }}
MACOS_VERSION: ${{ matrix.macos_version }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Select image
id: select
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set -euo pipefail
git diff --name-only "$BASE_SHA" "$HEAD_SHA" > changed-files.txt
build=false
if grep -Eq '^(\.github/workflows/base\.yml|templates/base\.pkr\.hcl|templates/disable-sip.*\.pkr\.hcl|data/(github_known_hosts|limit\.maxfiles\.plist|setup-info-template\.json|tart-guest-.*\.plist|tart-metal-capabilities/)|scripts/(automationmodetool\.expect|(install-actions-runner|install-tart-metal-capabilities|update-tcc-database)\.sh)|ansible/)' changed-files.txt; then
build=true
fi
echo "build=$build" >> "$GITHUB_OUTPUT"
- name: Tool versions
if: steps.select.outputs.build == 'true'
run: |
tart --version
packer --version
- name: Pull vanilla image
if: steps.select.outputs.build == 'true'
run: |
tart pull "ghcr.io/cirruslabs/macos-$MACOS_VERSION-vanilla:latest"
tart clone "ghcr.io/cirruslabs/macos-$MACOS_VERSION-vanilla:latest" "$MACOS_VERSION-base"
- name: Disable SIP
if: steps.select.outputs.build == 'true'
run: |
packer init "templates/$DISABLE_SIP_TEMPLATE"
packer build -var "vm_name=$MACOS_VERSION-base" "templates/$DISABLE_SIP_TEMPLATE"
- name: Build base image
if: steps.select.outputs.build == 'true'
run: |
packer init templates/base.pkr.hcl
packer build -var "vm_name=$MACOS_VERSION-base" templates/base.pkr.hcl
- name: Cleanup
if: always() && steps.select.outputs.build == 'true'
run: |
tart delete "$MACOS_VERSION-base" || true
build-runner:
name: Build Runner Image (${{ matrix.macos_version }})
needs: packer-validate
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: [self-hosted, macOS, ARM64]
timeout-minutes: 180
strategy:
fail-fast: false
max-parallel: 1
matrix:
include:
- macos_version: tahoe
xcode_versions: '"26.6","27-beta-2","26.5","27-beta","26.4.1","26.3","26.2","26.1.1","26.0.1"'
additional_ios_builds: "18.6"
additional_tvos_builds: ""
xcode_components: '"MetalToolchain"'
disk_size: 520
- macos_version: sequoia
xcode_versions: '"26.0.1",16.4,16.3,16.2,16.1,16'
additional_ios_builds: "18.5,18.4,18.2,17.5"
additional_tvos_builds: "17.5"
xcode_components: '"MetalToolchain"'
disk_size: 380
env:
ADDITIONAL_IOS_BUILDS: ${{ matrix.additional_ios_builds }}
ADDITIONAL_TVOS_BUILDS: ${{ matrix.additional_tvos_builds }}
DISK_SIZE: ${{ matrix.disk_size }}
MACOS_VERSION: ${{ matrix.macos_version }}
XCODE_COMPONENTS: ${{ matrix.xcode_components }}
XCODE_VERSIONS: ${{ matrix.xcode_versions }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Select image
id: select
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set -euo pipefail
git diff --name-only "$BASE_SHA" "$HEAD_SHA" > changed-files.txt
build=false
if grep -Fxq ".github/workflows/release.yml" changed-files.txt; then
build=true
elif grep -Fxq "data/expected.$MACOS_VERSION.runtimes.txt" changed-files.txt; then
build=true
elif grep -Fxq "scripts/finalize-$MACOS_VERSION.sh" changed-files.txt; then
build=true
elif grep -Eq '^(templates/xcode\.pkr\.hcl|data/setup-info-template\.json|scripts/install-actions-runner\.sh)$' changed-files.txt; then
build=true
fi
echo "build=$build" >> "$GITHUB_OUTPUT"
- name: Tool versions
if: steps.select.outputs.build == 'true'
run: |
tart --version
packer --version
- name: Pull base image
if: steps.select.outputs.build == 'true'
run: |
tart pull "ghcr.io/cirruslabs/macos-$MACOS_VERSION-base:latest"
- name: Prepare Xcode archives
if: steps.select.outputs.build == 'true'
run: |
set -euo pipefail
source ~/.zprofile || true
if ! command -v xcodes >/dev/null; then
brew install xcodes
fi
mkdir -p "$HOME/XcodesCache"
IFS=',' read -ra versions <<< "$XCODE_VERSIONS"
for raw_version in "${versions[@]}"; do
version="${raw_version//\"/}"
target="$HOME/XcodesCache/Xcode_${version}.xip"
if [[ -f "$target" ]]; then
echo "Using cached Xcode $version at $target"
continue
fi
echo "Downloading Xcode $version"
if [[ -z "${FASTLANE_SESSION:-}" ]]; then
echo "::error::Missing $target and FASTLANE_SESSION is not configured. Pre-cache the Xcode archive on the runner or add Apple Developer auth secrets."
exit 1
fi
download_args=(download "$version" --directory "$HOME/XcodesCache" --use-fastlane-auth)
if [[ -n "${FASTLANE_USER:-}" ]]; then
download_args+=(--fastlane-user "$FASTLANE_USER")
fi
xcodes "${download_args[@]}"
candidate=""
case "$version" in
27-beta-2)
candidate="$HOME/XcodesCache/Xcode_27_beta_2.xip"
;;
27-beta)
candidate="$HOME/XcodesCache/Xcode_27_beta.xip"
;;
*)
candidate="$(find "$HOME/XcodesCache" -maxdepth 1 -type f -name "Xcode_${version}*.xip" -print -quit)"
;;
esac
if [[ -n "$candidate" && -f "$candidate" && "$candidate" != "$target" ]]; then
mv "$candidate" "$target"
fi
test -f "$target"
done
- name: Build runner image
if: steps.select.outputs.build == 'true'
run: |
packer init templates/xcode.pkr.hcl
packer build \
-var tag=runner \
-var "disk_size=$DISK_SIZE" \
-var disk_free_mb=100000 \
-var "macos_version=$MACOS_VERSION" \
-var "xcode_version=[$XCODE_VERSIONS]" \
-var "additional_ios_builds=[$ADDITIONAL_IOS_BUILDS]" \
-var "additional_tvos_builds=[$ADDITIONAL_TVOS_BUILDS]" \
-var "xcode_components=[$XCODE_COMPONENTS]" \
-var "expected_runtimes_file=data/expected.$MACOS_VERSION.runtimes.txt" \
templates/xcode.pkr.hcl
- name: Finalize runner image
if: steps.select.outputs.build == 'true'
run: |
if [[ -f "scripts/finalize-$MACOS_VERSION.sh" ]]; then
packer build \
-var "vm_name=$MACOS_VERSION-xcode:runner" \
-var "script_path=scripts/finalize-$MACOS_VERSION.sh" \
templates/exex-script.pkr.hcl
else
echo "Skipping prepare script for $MACOS_VERSION"
fi
- name: Cleanup
if: always() && steps.select.outputs.build == 'true'
run: |
tart delete "$MACOS_VERSION-xcode:runner" || true

View File

@ -1,129 +0,0 @@
name: Vanilla Images
on:
push:
paths:
- "templates/vanilla-*.pkr.hcl"
workflow_dispatch:
inputs:
macos_version:
description: "macOS vanilla image to build"
required: true
default: all
type: choice
options:
- all
- golden-gate
- tahoe
- sequoia
- sonoma
- monterey
permissions:
contents: read
packages: write
concurrency:
group: tart-image-builds
cancel-in-progress: false
env:
TART_REGISTRY_HOSTNAME: ghcr.io
TART_REGISTRY_USERNAME: ${{ github.actor }}
TART_REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
jobs:
update-vanilla:
name: Update Vanilla Image (${{ matrix.macos_version }})
if: >-
${{
github.event_name == 'workflow_dispatch' ||
github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
}}
runs-on: [self-hosted, macOS, ARM64]
timeout-minutes: 180
strategy:
fail-fast: false
max-parallel: 1
matrix:
macos_version:
- golden-gate
- tahoe
- sequoia
- sonoma
- monterey
env:
MACOS_VERSION: ${{ matrix.macos_version }}
RESOLVE_VM_NAME: resolve-macos-number-${{ github.run_id }}-${{ github.run_attempt }}-${{ matrix.macos_version }}
RESOLVE_FILE: resolve-macos-number-${{ github.run_id }}-${{ github.run_attempt }}-${{ matrix.macos_version }}.txt
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Select image
id: select
env:
BEFORE_SHA: ${{ github.event.before }}
EVENT_NAME: ${{ github.event_name }}
INPUT_MACOS_VERSION: ${{ inputs.macos_version || 'all' }}
run: |
set -euo pipefail
build=false
if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then
if [[ "$INPUT_MACOS_VERSION" == "all" || "$INPUT_MACOS_VERSION" == "$MACOS_VERSION" ]]; then
build=true
fi
else
if [[ "$BEFORE_SHA" =~ ^0+$ ]]; then
git diff-tree --no-commit-id --name-only -r "$GITHUB_SHA" > changed-files.txt
else
git diff --name-only "$BEFORE_SHA" "$GITHUB_SHA" > changed-files.txt
fi
if grep -Fxq "templates/vanilla-$MACOS_VERSION.pkr.hcl" changed-files.txt; then
build=true
fi
fi
echo "build=$build" >> "$GITHUB_OUTPUT"
- name: Tool versions
if: steps.select.outputs.build == 'true'
run: |
tart --version
packer --version
- name: Build vanilla image
if: steps.select.outputs.build == 'true'
run: |
packer init "templates/vanilla-$MACOS_VERSION.pkr.hcl"
packer build "templates/vanilla-$MACOS_VERSION.pkr.hcl"
- name: Resolve macOS version
id: resolve
if: steps.select.outputs.build == 'true'
run: |
packer build \
-var "vm_base_name=$MACOS_VERSION-vanilla" \
-var "vm_name=$RESOLVE_VM_NAME" \
-var "resolve_file=$RESOLVE_FILE" \
templates/resolve-macos-number.pkr.hcl
echo "macos_number=$(cat "$RESOLVE_FILE")" >> "$GITHUB_OUTPUT"
rm -f "$RESOLVE_FILE"
- name: Push vanilla image
if: steps.select.outputs.build == 'true'
run: |
tart push "$MACOS_VERSION-vanilla" \
"ghcr.io/cirruslabs/macos-$MACOS_VERSION-vanilla:latest" \
"ghcr.io/cirruslabs/macos-$MACOS_VERSION-vanilla:${{ steps.resolve.outputs.macos_number }}"
- name: Cleanup
if: always() && steps.select.outputs.build == 'true'
run: |
tart delete "$MACOS_VERSION-vanilla" || true
tart delete "$RESOLVE_VM_NAME" || true

View File

@ -1,25 +0,0 @@
## Building Vanilla Image
To build `macos-sonoma-vanilla`:
```bash
packer build templates/vanilla-sonoma.pkr.hcl
```
Optionally, SIP can be disabled for each image by running the following commands:
```bash
packer build -var vm_name=sonoma-vanilla templates/disable-sip.pkr.hcl
```
## Building Base Image
```bash
packer build -var vm_name=sonoma-vanilla templates/base.pkr.hcl
```
## Building Xcode Image
```bash
packer build -var macos_version=sonoma -var xcode_version="[15.4]" templates/xcode.pkr.hcl
```

View File

@ -1,54 +1,45 @@
## macOS Packer Templates for Tart
## macOS Packer Templates for Cirrus CI
Repository with Packer templates to build macOS [Tart](https://tart.run/) virtual machines to use with self-hosted
GitHub Actions runners, [Cirrus Runners](https://cirrus-runners.app/) or [any other automation](https://tart.run/integrations/cirrus-cli/).
Repository with Packer templates to build [Tart VMs](https://github.com/cirruslabs/tart) to use with [Cirrus CI](https://cirrus-ci.org/guide/macOS/).
The following image variants are currently available:
* `macos-{monterey,ventura}-vanilla` image has nothing pre-installed
* `macos-{monterey,ventura}-base` image has only `brew` pre-installed
* `macos-{monterey,ventura}-xcode:N` image is based on `macos-{monterey,ventura}-base` image and has `Xcode N` with [`Flutter`](https://flutter.dev/) pre-installed
* `macos-{golden-gate,tahoe,sequoia,sonoma}-vanilla` — a vanilla macOS installation with helpful tweaks such as auto-login, but no additional software preinstalled
* `macos-{golden-gate,tahoe,sequoia,sonoma}-base` — based on `macos-{golden-gate,tahoe,sequoia,sonoma}-vanilla` image, it comes with `brew` and [other useful software](https://github.com/cirruslabs/macos-image-templates/blob/main/templates/base.pkr.hcl) pre-installed, but without Xcode
* `macos-{tahoe,sequoia,sonoma}-xcode:N` — based on `macos-{tahoe,sequoia,sonoma}-base` image and has `Xcode N` with [`Flutter`](https://flutter.dev/) pre-installed
* `macos-runner:{tahoe,sequoia,sonoma}` — a variant of `xcode:N` with several versions of `Xcode` pre-installed and [`xcodes` tool](https://github.com/XcodesOrg/xcodes) to switch between them.
See a full list of VMs available on Cirrus CI [here](https://github.com/orgs/cirruslabs/packages?tab=packages&q=macos-).
See a full list of VMs available [here](https://github.com/orgs/cirruslabs/packages?tab=packages&q=macos-).
## Building Vanilla Image
## Metal capabilities
To build `macos-monterey-vanilla`:
Base images include the experimental [Tart Metal shim](data/tart-metal-capabilities),
based on [the Lume team's work](https://github.com/trycua/cua/blob/main/blog/gpu-passthrough-macos-vms.md).
It is installed at `/usr/local/lib/TartMetalCapabilities.dylib` but is not
enabled by default, including for the guest agent or `tart exec`.
Before starting the VM, enable unrestricted virtual-GPU features on the host
as the user running Tart:
```shell
defaults write com.apple.gpusw.ParavirtualizedGraphics ForceUnrestrictedDeviceFeatureLevel -bool true
```bash
packer build templates/vanilla-monterey.pkr.hcl
```
Restart an already-running VM after changing that preference. To enable the
shim for one command with Apple family 9 and 64 KiB of threadgroup memory:
To build `macos-ventura-vanilla`:
```shell
tart exec my-vm /usr/bin/env \
DYLD_INSERT_LIBRARIES=/usr/local/lib/TartMetalCapabilities.dylib \
TART_METAL_APPLE_FAMILY_MAX=1009 \
/path/to/workload
```bash
packer build templates/vanilla-ventura.pkr.hcl
```
Omit these environment variables to run normally. Each opted-in process gets
its own settings; `TART_METAL_APPLE_FAMILY_MAX=0` disables the capability
override. Protected executables may reject injection, and GPU support depends
on the host, guest, and workload.
Optionally, SIP can be disabled for each image by running the following commands:
## Release Cadence
```bash
packer build -var vm_name=monterey-vanilla templates/disable-sip.pkr.hcl
```
Once a new version of Xcode is released, we will initiate a GitHub release which will automatically build and push
a new version of the `macos-{tahoe,sequoia}-xcode:N`. This generally happens the next weekend after a release.
Please watch this repository releases to get notified about new images.
```bash
packer build -var vm_name=ventura-vanilla templates/disable-sip.pkr.hcl
```
## Update Cadence
## Building Base Image
Some of the images are regularly getting rebuild in order to update the pre-installed packages.
```bash
packer build -var-file="variables.pkrvars.hcl" templates/base.pkr.hcl
```
[This workflow](.github/workflows/monthly.yml) defines images that are getting rebuilt monthly on the first Saturday of the month.
## Building Xcode Image
```bash
packer build -var-file="variables.pkrvars.hcl" templates/xcode.pkr.hcl
```

View File

@ -1,5 +0,0 @@
- hosts: default
roles:
- system-updater
vars:
ansible_password: admin

View File

@ -1,37 +0,0 @@
- name: Perform first "softwareupdate" invocation
include_tasks: softwareupdate.yml
# Needed after a major macOS update, otherwise things like
# Command Line Tools won't be updated
- name: Perform second "softwareupdate" invocation
include_tasks: softwareupdate.yml
# This one looks weird, but unfortunately there's no other way around, because Homebrew
# is not designed to run as root (see https://gist.github.com/irazasyed/7732946
# for more details).
- name: fix up /usr/local permissions for Homebrew
file:
path: /usr/local/share/man
state: directory
owner: "{{ ansible_user_id }}"
recurse: yes
become: yes
- name: "ensure that there are no more software updates available: check for available updates"
command:
cmd: "softwareupdate --all --list"
register: software_updates_result
- name: "ensure that there are no more software updates available: parse available updates"
set_fact:
software_updates: "{{ software_updates_result.stdout | regex_findall('\\* Label: (.*)\\n\\tTitle: (.*), Version: (.*), Size: (.*), Recommended: (.*), Action: (.*), .*') | map('zip', ['label', 'title', 'version', 'size', 'recommended', 'action']) | map('map', 'reverse') | map('community.general.dict') }}"
- name: "ensure that there are no more software updates available: print available updates"
debug:
var: software_updates
- name: "ensure that there are no more software updates available: fail if some updates were not installed"
fail:
msg: "Found unapplied update: {{ item.label }}"
loop: "{{ software_updates }}"
when: "not item.label.startswith('macOS') or item.version.split('.')[0] == ansible_facts['distribution_version'].split('.')[0]"

View File

@ -1,43 +0,0 @@
- name: check for available updates
command:
cmd: "softwareupdate --all --list"
register: software_updates_result
- name: parse available updates
set_fact:
software_updates: "{{ software_updates_result.stdout | regex_findall('\\* Label: (.*)\\n\\tTitle: (.*), Version: (.*), Size: (.*), Recommended: (.*), Action: (.*), .*') | map('zip', ['label', 'title', 'version', 'size', 'recommended', 'action']) | map('map', 'reverse') | map('community.general.dict') }}"
- name: print available updates
debug:
var: software_updates
# It seems that we must always pass "--restart" command-line argument to "softwareupdate",
# otherwise on the OS update the "softwareupdate" will be stuck at "Downloaded: macOS [...]"
- name: install available update
command:
cmd: "softwareupdate --install --agree-to-license --force --restart --user admin --stdinpass {{ stdinpass | default('') }} '{{ item.label }}'"
stdin: "{{ ansible_password }}"
register: update_result
# Work around the following:
# > Data could not be sent to remote host [...].
# > Make sure this host can be reached over ssh:
# > ssh: connect to host [...] port 22: Connection refused.
ignore_unreachable: yes
# Ignore SIGTERM/SIGKILL sent "softwareupdate" process
# when the system reboots due to --restart and any other errors,
# since we'll check whether the update was installed in main.yml
# anyway.
ignore_errors: yes
become: yes
loop: "{{ software_updates }}"
when: "not item.label.startswith('macOS') or item.version.split('.')[0] == ansible_facts['distribution_version'].split('.')[0]"
# Wait for the connection since the previous command could restart the host
- name: wait for connection
wait_for_connection:
# We need to wait long enough for the "softwareupdate" to initiate the reboot,
# otherwise it's possible that we'll interrupt the process by running
# the commands below on a non-restarted system.
delay: 60
timeout: 1800
when: update_result is defined and not update_result.skipped | default(false)

View File

@ -1,20 +0,0 @@
== Runtimes ==
iOS 17.5 (17.5 - 21F79) - com.apple.CoreSimulator.SimRuntime.iOS-17-5
iOS 18.0 (18.0 - 22A3351) - com.apple.CoreSimulator.SimRuntime.iOS-18-0
iOS 18.1 (18.1 - 22B81) - com.apple.CoreSimulator.SimRuntime.iOS-18-1
iOS 18.2 (18.2 - 22C150) - com.apple.CoreSimulator.SimRuntime.iOS-18-2
iOS 18.3 (18.3.1 - 22D8075) - com.apple.CoreSimulator.SimRuntime.iOS-18-3
iOS 18.4 (18.4 - 22E238) - com.apple.CoreSimulator.SimRuntime.iOS-18-4
iOS 18.5 (18.5 - 22F77) - com.apple.CoreSimulator.SimRuntime.iOS-18-5
iOS 18.6 (18.6 - 22G86) - com.apple.CoreSimulator.SimRuntime.iOS-18-6
iOS 26.0 (26.0.1 - 23A8464) - com.apple.CoreSimulator.SimRuntime.iOS-26-0
tvOS 17.5 (17.5 - 21L569) - com.apple.CoreSimulator.SimRuntime.tvOS-17-5
tvOS 18.4 (18.4 - 22L254) - com.apple.CoreSimulator.SimRuntime.tvOS-18-4
tvOS 18.5 (18.5 - 22L572) - com.apple.CoreSimulator.SimRuntime.tvOS-18-5
tvOS 26.0 (26.0 - 23J352) - com.apple.CoreSimulator.SimRuntime.tvOS-26-0
watchOS 11.4 (11.4 - 22T250) - com.apple.CoreSimulator.SimRuntime.watchOS-11-4
watchOS 11.5 (11.5 - 22T572) - com.apple.CoreSimulator.SimRuntime.watchOS-11-5
watchOS 26.0 (26.0 - 23R353) - com.apple.CoreSimulator.SimRuntime.watchOS-26-0
visionOS 2.4 (2.4 - 22O237) - com.apple.CoreSimulator.SimRuntime.xrOS-2-4
visionOS 2.5 (2.5 - 22O473) - com.apple.CoreSimulator.SimRuntime.xrOS-2-5
visionOS 26.0 (26.0 - 23M336) - com.apple.CoreSimulator.SimRuntime.xrOS-26-0

View File

@ -1,9 +0,0 @@
== Runtimes ==
iOS 18.0 (18.0 - 22A3351) - com.apple.CoreSimulator.SimRuntime.iOS-18-0
iOS 18.1 (18.1 - 22B81) - com.apple.CoreSimulator.SimRuntime.iOS-18-1
tvOS 18.0 (18.0 - 22J356) - com.apple.CoreSimulator.SimRuntime.tvOS-18-0
tvOS 18.1 (18.1 - 22J578) - com.apple.CoreSimulator.SimRuntime.tvOS-18-1
watchOS 11.0 (11.0 - 22R349) - com.apple.CoreSimulator.SimRuntime.watchOS-11-0
watchOS 11.1 (11.1 - 22R581) - com.apple.CoreSimulator.SimRuntime.watchOS-11-1
visionOS 2.0 (2.0 - 22N318) - com.apple.CoreSimulator.SimRuntime.xrOS-2-0
visionOS 2.1 (2.1 - 22N580) - com.apple.CoreSimulator.SimRuntime.xrOS-2-1

View File

@ -1,15 +0,0 @@
== Runtimes ==
iOS 18.6 (18.6 - 22G86) - com.apple.CoreSimulator.SimRuntime.iOS-18-6
iOS 26.0 (26.0.1 - 23A8464) - com.apple.CoreSimulator.SimRuntime.iOS-26-0
iOS 26.1 (26.1 - 23B86) - com.apple.CoreSimulator.SimRuntime.iOS-26-1
iOS 26.3 (26.3.1 - 23D8133) - com.apple.CoreSimulator.SimRuntime.iOS-26-3
iOS 26.4 (26.4.1 - 23E254a) - com.apple.CoreSimulator.SimRuntime.iOS-26-4
iOS 26.5 (26.5 - 23F77) - com.apple.CoreSimulator.SimRuntime.iOS-26-5
iOS 27.0 (27.0 - 24A5355p) - com.apple.CoreSimulator.SimRuntime.iOS-27-0
iOS 27.0 (27.0 - 24A5370g) - com.apple.CoreSimulator.SimRuntime.iOS-27-0
tvOS 26.5 (26.5 - 23L470) - com.apple.CoreSimulator.SimRuntime.tvOS-26-5
tvOS 27.0 (27.0 - 24J5305f) - com.apple.CoreSimulator.SimRuntime.tvOS-27-0
watchOS 26.5 (26.5 - 23T570) - com.apple.CoreSimulator.SimRuntime.watchOS-26-5
watchOS 27.0 (27.0 - 24R5305f) - com.apple.CoreSimulator.SimRuntime.watchOS-27-0
visionOS 26.5 (26.5 - 23O470) - com.apple.CoreSimulator.SimRuntime.xrOS-26-5
visionOS 27.0 (27.0 - 24M5306g) - com.apple.CoreSimulator.SimRuntime.xrOS-27-0

View File

@ -1,3 +0,0 @@
github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl
github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=
github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=

View File

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>limit.maxfiles</string>
<key>ProgramArguments</key>
<array>
<string>launchctl</string>
<string>limit</string>
<string>maxfiles</string>
<string>65536</string>
<string>524288</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>ServiceIPC</key>
<false/>
</dict>
</plist>

View File

@ -1,52 +0,0 @@
[
{
"group": "Runner Detail",
"detail": [
{
"name": "OS Information",
"script": "echo \"macOS $(sw_vers -productVersion) ($(sw_vers -buildVersion))\""
},
{
"name": "Build Date",
"script": "date +\"%Y-%m-%d\""
}
]
},
{
"group": "Software Detail",
"detail": [
{
"name": "Xcode Versions",
"script": "xcodes installed --no-color || echo \"Xcode not installed\""
},
{
"name": "Python Version",
"script": "python3 --version"
},
{
"name": "Node Version",
"script": "node --version"
},
{
"name": "Ruby Version",
"script": "ruby --version"
},
{
"name": "Bundler Version",
"script": "bundler --version"
},
{
"name": "CocoaPods Version",
"script": "pod --version"
},
{
"name": "Fastlane Version",
"script": "fastlane --version"
},
{
"name": "SwiftLint Version",
"script": "swiftlint --version"
}
]
}
]

View File

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>org.cirruslabs.tart-guest-agent</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/tart-guest-agent</string>
<string>--run-agent</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/bin:/usr/bin:/usr/sbin:/usr/local/bin:/opt/homebrew/bin</string>
<key>TERM</key>
<string>xterm-256color</string>
</dict>
<key>WorkingDirectory</key>
<string>/Users/admin</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/tmp/tart-guest-agent.log</string>
<key>StandardErrorPath</key>
<string>/tmp/tart-guest-agent.log</string>
</dict>
</plist>

View File

@ -1,28 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>org.cirruslabs.tart-guest-daemon</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/tart-guest-agent</string>
<string>--run-daemon</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/bin:/usr/bin:/usr/sbin:/usr/local/bin:/opt/homebrew/bin</string>
</dict>
<key>WorkingDirectory</key>
<string>/var/empty</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/tmp/tart-guest-daemon.log</string>
<key>StandardErrorPath</key>
<string>/tmp/tart-guest-daemon.log</string>
</dict>
</plist>

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2026 Cua AI, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,31 +0,0 @@
# Tart Metal capabilities
This is a vendored adaptation of the minimal [Cua/Lume Metal capability shim](https://github.com/trycua/cua/tree/3c1acf27748c3e0f8ff71cd0c9ab072b1e160997/libs/lume/metal-capability-shim)
at revision `3c1acf27748c3e0f8ff71cd0c9ab072b1e160997`. The full shim source and
Metal capability probe are copied here. The source's `Lume` identifiers,
diagnostics, and `LUME_METAL_*` settings are renamed to `Tart` and `TART_METAL_*`.
The original Cua copyright and [MIT license](LICENSE) are retained. The upstream
source SHA-256 before renaming is
`e1371b1e579bca895e6b3a2b581b9f328203d9786bf809912a4a5d1672010cce`.
The shim changes only Apple-family capability answers and selected memory
limits. It does not advertise additional Common, Mac, or Metal families. It
uses private, version-sensitive behavior; reported capabilities are not a
guarantee that every corresponding GPU operation is supported.
Configuration is read once, when each process loads the library:
| Variable | Behavior |
| --- | --- |
| `TART_METAL_APPLE_FAMILY_MAX` | Required Apple-family ceiling, from 1001 through 1999. Missing, zero, or invalid values disable the shim. |
| `TART_METAL_MAX_THREADGROUP_MEMORY` | Memory floor in bytes; defaults to 65536. |
| `TART_METAL_RECOMMENDED_WORKING_SET_SIZE` | Optional working-set floor in bytes; unchanged when unset. |
The old `LUME_METAL_*` names are not recognized. Base images install the library
without enabling it for the guest agent or other processes. See the repository's
[Metal capabilities instructions](../../README.md#metal-capabilities) for
explicit per-command activation and host setup.
From the repository root, run `bash scripts/test-tart-metal-capabilities.sh` to
build and test without installing anything on the host. The installer accepts
`DESTDIR` for staging and `TART_METAL_SOURCE_DIR` for Packer's uploaded sources.

View File

@ -1,207 +0,0 @@
// Copyright 2026 Cua AI, Inc.
// SPDX-License-Identifier: MIT
// Adapted from the Cua/Lume team's process-scoped Metal capability shim.
// Credit to the Lume folks for the idea and original implementation:
// https://github.com/trycua/cua/blob/3c1acf27748c3e0f8ff71cd0c9ab072b1e160997/blog/gpu-passthrough-macos-vms.md
#import <Foundation/Foundation.h>
#import <Metal/Metal.h>
#import <objc/runtime.h>
#include <errno.h>
#include <stdlib.h>
typedef unsigned long long TartU64;
typedef struct {
BOOL enabled;
NSUInteger appleFamilyMax;
NSUInteger maxThreadgroupMemory;
BOOL hasRecommendedWorkingSetSize;
NSUInteger recommendedWorkingSetSize;
} TartMetalConfiguration;
static TartMetalConfiguration gConfiguration = {0};
static IMP gOriginalInitGPUFamilySupport = NULL;
static IMP gOriginalMaxThreadgroupMemoryLength = NULL;
static IMP gOriginalRecommendedMaxWorkingSetSize = NULL;
static IMP gOriginalSupportsFamily = NULL;
static BOOL gDeviceHooksInstalled = NO;
static BOOL parseUnsignedEnvironmentValue(const char *name, TartU64 *value) {
const char *rawValue = getenv(name);
if (!rawValue || !*rawValue) return NO;
errno = 0;
char *end = NULL;
unsigned long long parsed = strtoull(rawValue, &end, 0);
if (errno != 0 || end == rawValue || !end || *end != '\0') return NO;
*value = parsed;
return YES;
}
static BOOL loadConfiguration(void) {
TartU64 appleFamilyMax = 0;
if (!parseUnsignedEnvironmentValue(
"TART_METAL_APPLE_FAMILY_MAX",
&appleFamilyMax
) || appleFamilyMax < 1001 || appleFamilyMax >= 2000) {
return NO;
}
TartU64 maxThreadgroupMemory = 65536;
const char *rawThreadgroupMemory = getenv("TART_METAL_MAX_THREADGROUP_MEMORY");
if (rawThreadgroupMemory && *rawThreadgroupMemory &&
!parseUnsignedEnvironmentValue("TART_METAL_MAX_THREADGROUP_MEMORY", &maxThreadgroupMemory)) {
return NO;
}
TartU64 recommendedWorkingSetSize = 0;
const char *rawWorkingSetSize = getenv("TART_METAL_RECOMMENDED_WORKING_SET_SIZE");
BOOL hasRecommendedWorkingSetSize = rawWorkingSetSize && *rawWorkingSetSize;
if (hasRecommendedWorkingSetSize &&
!parseUnsignedEnvironmentValue(
"TART_METAL_RECOMMENDED_WORKING_SET_SIZE",
&recommendedWorkingSetSize
)) {
return NO;
}
gConfiguration.enabled = YES;
gConfiguration.appleFamilyMax = (NSUInteger)appleFamilyMax;
gConfiguration.maxThreadgroupMemory = (NSUInteger)maxThreadgroupMemory;
gConfiguration.hasRecommendedWorkingSetSize = hasRecommendedWorkingSetSize;
gConfiguration.recommendedWorkingSetSize = (NSUInteger)recommendedWorkingSetSize;
return YES;
}
static NSUInteger hookMaxThreadgroupMemoryLength(id self, SEL selector) {
NSUInteger original = gOriginalMaxThreadgroupMemoryLength
? ((NSUInteger(*)(id, SEL))(void *)gOriginalMaxThreadgroupMemoryLength)(self, selector)
: 0;
return original < gConfiguration.maxThreadgroupMemory
? gConfiguration.maxThreadgroupMemory
: original;
}
static NSUInteger hookRecommendedMaxWorkingSetSize(id self, SEL selector) {
NSUInteger original = gOriginalRecommendedMaxWorkingSetSize
? ((NSUInteger(*)(id, SEL))(void *)gOriginalRecommendedMaxWorkingSetSize)(self, selector)
: 0;
return original < gConfiguration.recommendedWorkingSetSize
? gConfiguration.recommendedWorkingSetSize
: original;
}
static BOOL hookSupportsFamily(id self, SEL selector, NSUInteger family) {
BOOL original = gOriginalSupportsFamily
? ((BOOL(*)(id, SEL, NSUInteger))(void *)gOriginalSupportsFamily)(self, selector, family)
: NO;
BOOL isConfiguredAppleFamily = family >= 1001 &&
family <= gConfiguration.appleFamilyMax;
return original || isConfiguredAppleFamily;
}
static BOOL replaceMethod(
Class deviceClass,
NSString *selectorName,
IMP replacement,
IMP *original
) {
SEL selector = NSSelectorFromString(selectorName);
Method method = class_getInstanceMethod(deviceClass, selector);
if (!method) return NO;
*original = method_setImplementation(method, replacement);
return *original != NULL;
}
static void installDeviceHooks(id device) {
@synchronized([device class]) {
if (gDeviceHooksInstalled) return;
Class deviceClass = [device class];
Method maxThreadgroupMemory = class_getInstanceMethod(
deviceClass,
NSSelectorFromString(@"maxThreadgroupMemoryLength")
);
Method supportsFamily = class_getInstanceMethod(
deviceClass,
NSSelectorFromString(@"supportsFamily:")
);
Method recommendedWorkingSetSize = gConfiguration.hasRecommendedWorkingSetSize
? class_getInstanceMethod(
deviceClass,
NSSelectorFromString(@"recommendedMaxWorkingSetSize")
)
: NULL;
if (!maxThreadgroupMemory || !supportsFamily ||
(gConfiguration.hasRecommendedWorkingSetSize && !recommendedWorkingSetSize)) {
NSLog(@"[TartMetalCapabilities] Required device methods are unavailable; leaving stock capabilities unchanged");
return;
}
BOOL installed = replaceMethod(
deviceClass,
@"maxThreadgroupMemoryLength",
(IMP)hookMaxThreadgroupMemoryLength,
&gOriginalMaxThreadgroupMemoryLength
);
installed = installed && replaceMethod(
deviceClass,
@"supportsFamily:",
(IMP)hookSupportsFamily,
&gOriginalSupportsFamily
);
if (installed && gConfiguration.hasRecommendedWorkingSetSize) {
installed = replaceMethod(
deviceClass,
@"recommendedMaxWorkingSetSize",
(IMP)hookRecommendedMaxWorkingSetSize,
&gOriginalRecommendedMaxWorkingSetSize
);
}
if (!installed) {
NSLog(@"[TartMetalCapabilities] Capability hook installation was incomplete");
return;
}
gDeviceHooksInstalled = YES;
NSLog(
@"[TartMetalCapabilities] Enabled for %@ (appleFamilyMax=%llu maxThreadgroupMemory=%llu)",
[NSProcessInfo processInfo].processName,
(TartU64)gConfiguration.appleFamilyMax,
(TartU64)gConfiguration.maxThreadgroupMemory
);
}
}
static void hookInitGPUFamilySupport(id self, SEL selector) {
installDeviceHooks(self);
((void(*)(id, SEL))(void *)gOriginalInitGPUFamilySupport)(self, selector);
}
__attribute__((constructor))
static void initializeTartMetalCapabilities(void) {
@autoreleasepool {
if (!loadConfiguration()) return;
Class deviceClass = NSClassFromString(@"_MTLDevice");
if (!deviceClass) return;
Method method = class_getInstanceMethod(
deviceClass,
NSSelectorFromString(@"initGPUFamilySupport")
);
if (!method) return;
gOriginalInitGPUFamilySupport = method_setImplementation(
method,
(IMP)hookInitGPUFamilySupport
);
}
}

View File

@ -1,66 +0,0 @@
#import "../Sources/TartMetalCapabilities.m"
#include <assert.h>
#include <stdio.h>
#include <string.h>
static void resetConfiguration(void) {
unsetenv("TART_METAL_APPLE_FAMILY_MAX");
unsetenv("TART_METAL_MAX_THREADGROUP_MEMORY");
unsetenv("TART_METAL_RECOMMENDED_WORKING_SET_SIZE");
unsetenv("LUME_METAL_APPLE_FAMILY_MAX");
gConfiguration = (TartMetalConfiguration){0};
}
int main(int argc, const char *argv[]) {
// In this mode, check the configuration loaded by the real constructor
// before main(), as it would be for a newly executed workload.
if (argc == 2) {
NSUInteger expected = (NSUInteger)strtoull(argv[1], NULL, 10);
assert(gConfiguration.enabled == (expected != 0));
assert(gConfiguration.appleFamilyMax == expected);
return 0;
}
resetConfiguration();
assert(!loadConfiguration());
setenv("LUME_METAL_APPLE_FAMILY_MAX", "1009", 1);
assert(!loadConfiguration());
const char *invalidFamilies[] = {"", "0", "1000", "2000", "-1", "1009x"};
for (size_t i = 0; i < sizeof(invalidFamilies) / sizeof(invalidFamilies[0]); i++) {
resetConfiguration();
setenv("TART_METAL_APPLE_FAMILY_MAX", invalidFamilies[i], 1);
assert(!loadConfiguration());
assert(!gConfiguration.enabled);
}
resetConfiguration();
setenv("TART_METAL_APPLE_FAMILY_MAX", "1009", 1);
assert(loadConfiguration());
assert(gConfiguration.appleFamilyMax == 1009);
assert(gConfiguration.maxThreadgroupMemory == 65536);
assert(!gConfiguration.hasRecommendedWorkingSetSize);
resetConfiguration();
setenv("TART_METAL_APPLE_FAMILY_MAX", "1999", 1);
setenv("TART_METAL_MAX_THREADGROUP_MEMORY", "32768", 1);
setenv("TART_METAL_RECOMMENDED_WORKING_SET_SIZE", "1073741824", 1);
assert(loadConfiguration());
assert(gConfiguration.appleFamilyMax == 1999);
assert(gConfiguration.maxThreadgroupMemory == 32768);
assert(gConfiguration.hasRecommendedWorkingSetSize);
assert(gConfiguration.recommendedWorkingSetSize == 1073741824);
resetConfiguration();
setenv("TART_METAL_APPLE_FAMILY_MAX", "1009", 1);
setenv("TART_METAL_MAX_THREADGROUP_MEMORY", "invalid", 1);
assert(!loadConfiguration());
unsetenv("TART_METAL_MAX_THREADGROUP_MEMORY");
setenv("TART_METAL_RECOMMENDED_WORKING_SET_SIZE", "invalid", 1);
assert(!loadConfiguration());
resetConfiguration();
puts("configuration: OK");
return 0;
}

View File

@ -1,58 +0,0 @@
#include <assert.h>
#include <mach-o/dyld.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
static pid_t startChild(const char *executable, const char *library, const char *family) {
pid_t child = fork();
assert(child >= 0);
if (child == 0) {
char *libraryAssignment = NULL;
char *familyAssignment = NULL;
assert(asprintf(&libraryAssignment, "DYLD_INSERT_LIBRARIES=%s", library) >= 0);
assert(asprintf(&familyAssignment, "TART_METAL_APPLE_FAMILY_MAX=%s", family) >= 0);
execl("/usr/bin/env", "env", libraryAssignment, familyAssignment,
executable, family, "child", NULL);
_exit(1);
}
return child;
}
int main(int argc, char **argv) {
assert(argc >= 2);
const char *library = getenv("DYLD_INSERT_LIBRARIES");
const char *family = getenv("TART_METAL_APPLE_FAMILY_MAX");
const char *memory = getenv("TART_METAL_MAX_THREADGROUP_MEMORY");
int loaded = 0;
for (uint32_t i = 0; i < _dyld_image_count(); i++) {
if (strstr(_dyld_get_image_name(i), "/TartMetalCapabilities.dylib")) loaded = 1;
}
if (strcmp(argv[1], "stock") == 0) {
assert(!library && !family && !memory && !loaded);
puts("stock process without injection: OK");
return 0;
}
assert(library && family && memory);
assert(strcmp(family, argv[1]) == 0);
assert(strcmp(memory, "65536") == 0);
assert(loaded);
if (argc == 3) return 0;
// Concurrent workloads must get independent overrides while their parent
// retains its explicit opt-in. Exercise the documented /usr/bin/env path.
pid_t children[] = {
startChild(argv[0], library, "1008"),
startChild(argv[0], library, "0"),
};
for (size_t i = 0; i < sizeof(children) / sizeof(children[0]); i++) {
int status = 0;
assert(waitpid(children[i], &status, 0) == children[i]);
assert(WIFEXITED(status) && WEXITSTATUS(status) == 0);
}
assert(strcmp(getenv("TART_METAL_APPLE_FAMILY_MAX"), argv[1]) == 0);
puts("per-process injection and overrides: OK");
return 0;
}

View File

@ -1,42 +0,0 @@
#import <Foundation/Foundation.h>
#import <Metal/Metal.h>
#include <errno.h>
#include <stdlib.h>
static BOOL parseFamily(const char *raw, NSUInteger *family) {
errno = 0;
char *end = NULL;
unsigned long long parsed = strtoull(raw, &end, 0);
if (errno != 0 || end == raw || !end || *end != '\0') return NO;
*family = (NSUInteger)parsed;
return YES;
}
int main(int argc, const char *argv[]) {
@autoreleasepool {
NSUInteger family = 1009;
if (argc > 1 && !parseFamily(argv[1], &family)) {
fprintf(stderr, "invalid family: %s\n", argv[1]);
return 2;
}
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
if (!device) {
fprintf(stderr, "Metal device unavailable\n");
return 1;
}
printf("device=%s\n", device.name.UTF8String);
printf("family=%llu\n", (unsigned long long)family);
printf(
"supports_family=%s\n",
[device supportsFamily:(MTLGPUFamily)family] ? "true" : "false"
);
printf(
"max_threadgroup_memory=%llu\n",
(unsigned long long)device.maxThreadgroupMemoryLength
);
}
return 0;
}

View File

@ -1,6 +0,0 @@
#!/usr/bin/expect -f
spawn automationmodetool enable-automationmode-without-authentication
expect "Enter the password for user 'admin':"
send "admin\n"
expect "Setting up machine to allow Automation Mode without requiring user authentication... succeeded."

View File

@ -1,72 +0,0 @@
#!/bin/bash
source ~/.zprofile
# Set shell options to enable fail-fast behavior
#
# * -e: fail the script when an error occurs or command fails
# * -u: fail the script when attempting to reference unset parameters
# * -o pipefail: by default an exit status of a pipeline is that of its
# last command, this fails the pipe early if an error in
# any of its commands occurs
#
set -euo pipefail
# Wait until `xcrun simctl list devices -v` no longer reports any devices as "unavailable".
#
# Exit codes:
# 0 - Success: no devices are marked as unavailable within the timeout window
# 1 - Failure: timed out waiting for devices to become available
# 2 - Failure: prerequisites missing (e.g., xcrun not found)
DEFAULT_TIMEOUT_MINUTES=60
TIMEOUT_MINUTES=${1:-$DEFAULT_TIMEOUT_MINUTES}
if ! [[ "$TIMEOUT_MINUTES" =~ ^[0-9]+$ ]]; then
echo "[wait-simulators] ERROR: TIMEOUT_MINUTES must be an integer number of minutes (got: '$TIMEOUT_MINUTES')." >&2
exit 2
fi
SECONDS_TOTAL=$(( TIMEOUT_MINUTES * 60 ))
DEADLINE=$(( $(date +%s) + SECONDS_TOTAL ))
SLEEP_SECONDS=15
# Print a one-line status snapshot of current unavailable devices (if any)
print_status() {
if xcrun simctl list devices -v | grep -qi "unavailable"; then
echo "[wait-simulators] Still seeing 'unavailable' devices at $(date '+%Y-%m-%d %H:%M:%S')"
# Show a concise list of unavailable lines for debugging
xcrun simctl list devices -v | grep -i "unavailable" | sed 's/^/[wait-simulators] /'
else
echo "[wait-simulators] No 'unavailable' devices detected at $(date '+%Y-%m-%d %H:%M:%S')"
fi
}
trap 'echo "[wait-simulators] Interrupted" >&2; exit 130' INT TERM
echo "[wait-simulators] Waiting up to ${TIMEOUT_MINUTES} minute(s) for simulators to become available..."
while true; do
if ! xcrun simctl list devices -v | grep -qi "unavailable"; then
echo "[wait-simulators] All simulators are available."
exit 0
fi
NOW=$(date +%s)
if (( NOW >= DEADLINE )); then
echo "[wait-simulators] TIMEOUT after ${TIMEOUT_MINUTES} minute(s). Some simulators remain 'unavailable'." >&2
echo "[wait-simulators] Final snapshot of unavailable devices:" >&2
xcrun simctl list devices -v | grep -i "unavailable" | sed 's/^/[wait-simulators] /' >&2
exit 1
fi
print_status
REMAIN=$(( DEADLINE - NOW ))
# Sleep in chunks to allow quicker exit when they become available
SLEEP=$SLEEP_SECONDS
if (( REMAIN < SLEEP_SECONDS )); then SLEEP=$REMAIN; fi
sleep "$SLEEP"
# Loop and re-check
done

View File

@ -1,20 +0,0 @@
#!/bin/bash
# Set shell options to enable fail-fast behavior
#
# * -e: fail the script when an error occurs or command fails
# * -u: fail the script when attempting to reference unset parameters
# * -o pipefail: by default an exit status of a pipeline is that of its
# last command, this fails the pipe early if an error in
# any of its commands occurs
#
set -euo pipefail
source ~/.zprofile
brew install jq
DOWNLOAD_URL=$(curl -sS 'https://api.github.com/repos/actions/runner/releases/latest' | jq --raw-output '.assets[] | select(.name | test("actions-runner-osx-arm64-[0-9.]+.tar.gz")) | .browser_download_url')
rm -rf actions-runner && mkdir actions-runner && cd actions-runner
wget -O - "${DOWNLOAD_URL}" | tar xz

View File

@ -1,37 +0,0 @@
#!/bin/bash
set -euo pipefail
# Packer supplies the uploaded source directory. Local builds use the vendored
# copy next to this script, so building an image needs no upstream download.
source_dir=${TART_METAL_SOURCE_DIR:-"$(cd "$(dirname "${BASH_SOURCE[0]}")/../data/tart-metal-capabilities" && pwd)"}
work_dir=$(mktemp -d)
trap 'rm -rf "$work_dir"' EXIT
test -f "$source_dir/LICENSE"
# One path must work for the native guest agent, arm64e processes, and Rosetta
# children. Keep the deployment target compatible with the oldest template.
xcrun clang \
-arch arm64 -arch arm64e -arch x86_64 \
-O3 -Wall -Wextra -Werror -fobjc-arc -fblocks -fvisibility=hidden \
-dynamiclib -install_name /usr/local/lib/TartMetalCapabilities.dylib \
-mmacosx-version-min=12.0 -framework Foundation -framework Metal \
"$source_dir/Sources/TartMetalCapabilities.m" -o "$work_dir/TartMetalCapabilities.dylib"
codesign --force --sign - "$work_dir/TartMetalCapabilities.dylib"
# Xcode 27's lipo rejects multiple architectures in one -verify_arch call.
for architecture in arm64 arm64e x86_64; do
lipo "$work_dir/TartMetalCapabilities.dylib" -verify_arch "$architecture"
done
codesign --verify --strict "$work_dir/TartMetalCapabilities.dylib"
# DESTDIR allows the exact installer to be exercised without modifying the host.
install_root=${DESTDIR:-}
install_command=(sudo install -o root -g wheel)
if [[ -n "$install_root" ]]; then
install_command=(install)
fi
"${install_command[@]}" -d -m 0755 "$install_root/usr/local/lib" \
"$install_root/usr/local/share/licenses/tart-metal-capabilities"
"${install_command[@]}" -m 0644 "$work_dir/TartMetalCapabilities.dylib" \
"$install_root/usr/local/lib/TartMetalCapabilities.dylib"
"${install_command[@]}" -m 0644 "$source_dir/LICENSE" \
"$install_root/usr/local/share/licenses/tart-metal-capabilities/LICENSE"

View File

@ -1,60 +0,0 @@
#!/bin/bash
set -euo pipefail
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
source_dir="$script_dir/../data/tart-metal-capabilities"
work_dir=$(mktemp -d)
trap 'rm -rf "$work_dir"' EXIT
DESTDIR="$work_dir/root" TART_METAL_SOURCE_DIR="$source_dir" \
bash "$script_dir/install-tart-metal-capabilities.sh"
library="$work_dir/root/usr/local/lib/TartMetalCapabilities.dylib"
cmp "$source_dir/LICENSE" \
"$work_dir/root/usr/local/share/licenses/tart-metal-capabilities/LICENSE"
xcrun clang -O2 -Wall -Wextra -Werror -fobjc-arc \
-framework Foundation -framework Metal \
"$source_dir/Tests/configuration.m" -o "$work_dir/configuration"
/usr/bin/env -u TART_METAL_APPLE_FAMILY_MAX \
-u TART_METAL_MAX_THREADGROUP_MEMORY -u TART_METAL_RECOMMENDED_WORKING_SET_SIZE \
"$work_dir/configuration"
for family in 1009 1008 0; do
/usr/bin/env -u TART_METAL_MAX_THREADGROUP_MEMORY \
-u TART_METAL_RECOMMENDED_WORKING_SET_SIZE \
TART_METAL_APPLE_FAMILY_MAX="$family" "$work_dir/configuration" "$family"
done
xcrun clang -O2 -Wall -Wextra -Werror -arch arm64 -arch x86_64 \
-mmacosx-version-min=12.0 "$source_dir/Tests/exec-environment.c" \
-o "$work_dir/exec-environment"
run_stock() {
"$@" /usr/bin/env -u DYLD_INSERT_LIBRARIES -u TART_METAL_APPLE_FAMILY_MAX \
-u TART_METAL_MAX_THREADGROUP_MEMORY -u TART_METAL_RECOMMENDED_WORKING_SET_SIZE \
"$work_dir/exec-environment" stock
}
run_injected() {
"$@" /usr/bin/env DYLD_INSERT_LIBRARIES="$library" \
TART_METAL_APPLE_FAMILY_MAX=1009 TART_METAL_MAX_THREADGROUP_MEMORY=65536 \
"$work_dir/exec-environment" 1009
}
run_stock
run_injected
if [[ $(uname -m) == arm64 ]]; then
if arch -x86_64 /usr/bin/true; then
run_stock arch -x86_64
run_injected arch -x86_64
else
echo "Rosetta runtime check skipped: Rosetta is not installed"
fi
fi
for variant in agent daemon; do
plist="$script_dir/../data/tart-guest-$variant.plist"
plutil -lint "$plist"
agent_environment=$(plutil -extract EnvironmentVariables xml1 -o - "$plist")
if printf '%s\n' "$agent_environment" \
| grep -Eq '<key>(DYLD_INSERT_LIBRARIES|TART_METAL_[^<]+)</key>'; then
echo "Unexpected default Metal injection in $plist" >&2
exit 1
fi
done

View File

@ -1,170 +0,0 @@
#!/bin/bash
set -euo pipefail
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
export TCC_TEST_ROOT
TCC_TEST_ROOT=$(mktemp -d)
trap 'rm -rf "$TCC_TEST_ROOT"' EXIT
export TCC_TEST_SQLITE
TCC_TEST_SQLITE=$(command -v sqlite3)
export TCC_TEST_SYSTEM_DB='/Library/Application Support/com.apple.TCC/TCC.db'
export TCC_TEST_LEGACY_DB="$HOME/Library/Application Support/com.apple.TCC/TCC.db"
export TCC_TEST_PROTECTED_DB='/private/var/containers/Data/ProtectedSystem/TEST-USER/Data/Library/Application Support/com.apple.TCC/TCC.db'
export TCC_TEST_AGENT='/opt/homebrew/Cellar/tart-guest-agent/0.13.0/bin/tart-guest-agent'
# Run the real provisioning script against temporary SQLite databases. Every
# privileged operation is intercepted; these tests never access the host's TCC.
source() {
[[ $# == 1 && "$1" == "$HOME/.zprofile" ]]
}
sw_vers() {
[[ $# == 1 && "$1" == -productVersion ]] || return 1
printf '%s\n' "$TCC_TEST_MACOS_VERSION"
}
id() {
[[ $# == 1 && "$1" == -u ]] || return 1
printf '501\n'
}
realpath() {
[[ $# == 1 && "$1" == /opt/homebrew/bin/tart-guest-agent ]] || return 1
[[ "$TCC_TEST_AGENT_EXISTS" == 1 ]] || return 1
printf '%s\n' "$TCC_TEST_AGENT"
}
sudo() {
local subcommand="$1" database
shift
case "$subcommand" in
lsof)
[[ "$TCC_TEST_EXPECT_LSOF" == 1 && "$*" == '-a -u 501 -c tccd -Fn' ]] || return 1
printf '%s\n' "$TCC_TEST_OPEN_FILES"
return "$TCC_TEST_LSOF_STATUS"
;;
test)
[[ $# == 2 && "$1" == -f ]] || return 1
case "$2" in
"$TCC_TEST_LEGACY_DB") [[ "$TCC_TEST_LEGACY_EXISTS" == 1 ]] ;;
"$TCC_TEST_PROTECTED_DB") [[ "$TCC_TEST_PROTECTED_EXISTS" == 1 ]] ;;
*) return 1 ;;
esac
;;
stat)
[[ $# == 3 && "$1" == -f && "$2" == %u && "$3" == "$TCC_TEST_PROTECTED_DB" ]] || return 1
printf '%s\n' "$TCC_TEST_OWNER"
;;
sqlite3)
[[ $# == 1 ]] || return 1
case "$1" in
"$TCC_TEST_SYSTEM_DB") database="$TCC_TEST_ROOT/system.db" ;;
"$TCC_TEST_LEGACY_DB"|"$TCC_TEST_PROTECTED_DB") database="$TCC_TEST_ROOT/user.db" ;;
*) echo "Unexpected database: $1" >&2; return 1 ;;
esac
printf '%s\n' "$1" >> "$TCC_TEST_ROOT/writes"
"$TCC_TEST_SQLITE" "$database"
;;
*) echo "Unexpected sudo command: $subcommand" >&2; return 1 ;;
esac
}
export -f source sw_vers id realpath sudo
reset_case() {
export TCC_TEST_MACOS_VERSION=26.6.2 TCC_TEST_AGENT_EXISTS=1
export TCC_TEST_LEGACY_EXISTS=1 TCC_TEST_PROTECTED_EXISTS=1 TCC_TEST_OWNER=501
export TCC_TEST_EXPECT_LSOF=0 TCC_TEST_LSOF_STATUS=0 TCC_TEST_OPEN_FILES=''
: > "$TCC_TEST_ROOT/writes"
local database
for database in system user; do
"$TCC_TEST_SQLITE" "$TCC_TEST_ROOT/$database.db" <<'SQL'
DROP TABLE IF EXISTS access;
CREATE TABLE access (
service TEXT NOT NULL,
client_type INTEGER NOT NULL,
client TEXT NOT NULL,
auth_value INTEGER NOT NULL,
auth_reason INTEGER NOT NULL,
auth_version INTEGER NOT NULL,
indirect_object_identifier_type INTEGER,
indirect_object_identifier TEXT NOT NULL,
PRIMARY KEY (service, client, client_type, indirect_object_identifier)
);
SQL
done
}
golden_gate_case() {
reset_case
export TCC_TEST_MACOS_VERSION=27.0 TCC_TEST_EXPECT_LSOF=1
# Include an obsolete legacy copy, the system database, a WAL, and duplicate
# descriptors. Only the current user's active protected database may win.
TCC_TEST_OPEN_FILES=$(printf 'p123\nn%s\nn%s-wal\nn%s\nn%s\nn%s\n' \
"$TCC_TEST_SYSTEM_DB" "$TCC_TEST_PROTECTED_DB" "$TCC_TEST_LEGACY_DB" \
"$TCC_TEST_PROTECTED_DB" "$TCC_TEST_PROTECTED_DB")
export TCC_TEST_OPEN_FILES
}
assert_equal() {
if [[ "$1" != "$2" ]]; then
printf 'Expected: %s\nActual: %s\n' "$1" "$2" >&2
exit 1
fi
}
expect_success() {
local user_database="$1" database expected_writes
bash "$script_dir/update-tcc-database.sh"
bash "$script_dir/update-tcc-database.sh"
expected_writes=$(printf '%s\n%s\n%s\n%s' \
"$TCC_TEST_SYSTEM_DB" "$user_database" "$TCC_TEST_SYSTEM_DB" "$user_database")
assert_equal "$expected_writes" "$(< "$TCC_TEST_ROOT/writes")"
for database in system user; do
assert_equal 18 "$("$TCC_TEST_SQLITE" "$TCC_TEST_ROOT/$database.db" 'SELECT count(*) FROM access WHERE auth_value=2;')"
assert_equal 4 "$("$TCC_TEST_SQLITE" "$TCC_TEST_ROOT/$database.db" "SELECT count(*) FROM access WHERE client='$TCC_TEST_AGENT' AND client_type=1;")"
assert_equal 1 "$("$TCC_TEST_SQLITE" "$TCC_TEST_ROOT/$database.db" "SELECT count(*) FROM access WHERE client='org.python.python' AND service='kTCCServiceMicrophone';")"
done
}
expect_failure() {
if bash "$script_dir/update-tcc-database.sh" > "$TCC_TEST_ROOT/output" 2>&1; then
echo 'Expected provisioning to fail' >&2
exit 1
fi
if [[ -n "$1" ]] && ! grep -Fq "$1" "$TCC_TEST_ROOT/output"; then
cat "$TCC_TEST_ROOT/output" >&2
exit 1
fi
assert_equal '' "$(< "$TCC_TEST_ROOT/writes")"
}
reset_case
expect_success "$TCC_TEST_LEGACY_DB"
reset_case
TCC_TEST_MACOS_VERSION=12.7.6
expect_success "$TCC_TEST_LEGACY_DB"
golden_gate_case
expect_success "$TCC_TEST_PROTECTED_DB"
reset_case
TCC_TEST_LEGACY_EXISTS=0
expect_failure 'User TCC database does not exist'
golden_gate_case
TCC_TEST_LSOF_STATUS=1
expect_failure 'Unable to inspect the user TCC daemon'
golden_gate_case
TCC_TEST_OPEN_FILES="n$TCC_TEST_SYSTEM_DB"
expect_failure 'Unable to find the active user TCC database'
golden_gate_case
TCC_TEST_OPEN_FILES="$TCC_TEST_OPEN_FILES"$'\n'"n${TCC_TEST_PROTECTED_DB/TEST-USER/OTHER-USER}"
expect_failure 'Found multiple active user TCC databases'
golden_gate_case
TCC_TEST_PROTECTED_EXISTS=0
expect_failure 'User TCC database does not exist'
golden_gate_case
TCC_TEST_OWNER=502
expect_failure 'Unexpected owner for user TCC database'
golden_gate_case
TCC_TEST_AGENT_EXISTS=0
expect_failure ''
reset_case
TCC_TEST_MACOS_VERSION=invalid
expect_failure 'Unexpected macOS version'
echo 'TCC database tests passed'

View File

@ -1,112 +0,0 @@
#!/bin/bash
source ~/.zprofile
# Set shell options to enable fail-fast behavior
#
# * -e: fail the script when an error occurs or command fails
# * -u: fail the script when attempting to reference unset parameters
# * -o pipefail: by default an exit status of a pipeline is that of its
# last command, this fails the pipe early if an error in
# any of its commands occurs
#
set -euo pipefail
resolve_user_tcc_database() {
local macos_version macos_major user_id open_files line candidate database=""
macos_version="$(sw_vers -productVersion)"
macos_major="${macos_version%%.*}"
case "$macos_major" in
''|*[!0-9]*) echo "Unexpected macOS version: $macos_version" >&2; return 1 ;;
esac
if [[ "$macos_major" -lt 27 ]]; then
database="${HOME}/Library/Application Support/com.apple.TCC/TCC.db"
else
# macOS 27 moved the user database into a per-user ProtectedSystem
# container. Inspect the daemon's open files to avoid using a stale copy.
# https://developer.apple.com/documentation/macos-release-notes/macos-27-release-notes#TCC
user_id="$(id -u)"
if ! open_files="$(sudo lsof -a -u "$user_id" -c tccd -Fn)"; then
echo "Unable to inspect the user TCC daemon for UID $user_id" >&2
return 1
fi
while IFS= read -r line; do
case "$line" in
n/private/var/containers/Data/ProtectedSystem/*/Data/Library/Application\ Support/com.apple.TCC/TCC.db)
candidate="${line#n}"
if [[ -n "$database" && "$database" != "$candidate" ]]; then
echo "Found multiple active user TCC databases for UID $user_id" >&2
return 1
fi
database="$candidate"
;;
esac
done <<< "$open_files"
if [[ -z "$database" ]]; then
echo "Unable to find the active user TCC database for UID $user_id" >&2
return 1
fi
fi
if ! sudo test -f "$database"; then
echo "User TCC database does not exist: $database" >&2
return 1
fi
if [[ "$macos_major" -ge 27 && "$(sudo stat -f %u "$database")" != "$user_id" ]]; then
echo "Unexpected owner for user TCC database: $database" >&2
return 1
fi
printf '%s\n' "$database"
}
update_tcc_database() {
local tart_guest_agent_path
tart_guest_agent_path="$(realpath /opt/homebrew/bin/tart-guest-agent)"
sudo sqlite3 "$1" <<-EOF
INSERT OR REPLACE
INTO access (
service,
client_type,
client,
auth_value,
auth_reason,
auth_version,
indirect_object_identifier_type,
indirect_object_identifier
) VALUES
-- Indirect osascript invocation via SSH
('kTCCServiceAccessibility', 1, '/usr/libexec/sshd-keygen-wrapper', 2, 0, 1, NULL, 'UNUSED'),
('kTCCServiceScreenCapture', 1, '/usr/libexec/sshd-keygen-wrapper', 2, 0, 1, NULL, 'UNUSED'),
('kTCCServicePostEvent', 1, '/usr/libexec/sshd-keygen-wrapper', 2, 0, 1, NULL, 'UNUSED'),
('kTCCServiceAppleEvents', 1, '/usr/libexec/sshd-keygen-wrapper', 2, 0, 1, 0, 'com.apple.systemevents'),
('kTCCServiceAppleEvents', 1, '/usr/libexec/sshd-keygen-wrapper', 2, 0, 1, 0, 'com.apple.Safari'),
-- Direct osascript invocation
('kTCCServiceAccessibility', 1, '/usr/bin/osascript', 2, 0, 1, NULL, 'UNUSED'),
('kTCCServiceScreenCapture', 1, '/usr/bin/osascript', 2, 0, 1, NULL, 'UNUSED'),
('kTCCServicePostEvent', 1, '/usr/bin/osascript', 2, 0, 1, NULL, 'UNUSED'),
('kTCCServiceAppleEvents', 1, '/usr/bin/osascript', 2, 0, 1, 0, 'com.apple.systemevents'),
('kTCCServiceAppleEvents', 1, '/usr/bin/osascript', 2, 0, 1, 0, 'com.apple.Safari'),
-- Direct Python invocation
('kTCCServiceAccessibility', 0, 'org.python.python', 2, 0, 1, NULL, 'UNUSED'),
('kTCCServiceScreenCapture', 0, 'org.python.python', 2, 0, 1, NULL, 'UNUSED'),
('kTCCServiceMicrophone', 0, 'org.python.python', 2, 0, 1, NULL, 'UNUSED'),
('kTCCServicePostEvent', 0, 'org.python.python', 2, 0, 1, NULL, 'UNUSED'),
-- Commands invoked through the Tart Guest Agent
('kTCCServiceAccessibility', 1, '${tart_guest_agent_path}', 2, 0, 1, NULL, 'UNUSED'),
('kTCCServiceScreenCapture', 1, '${tart_guest_agent_path}', 2, 0, 1, NULL, 'UNUSED'),
('kTCCServiceMicrophone', 1, '${tart_guest_agent_path}', 2, 0, 1, NULL, 'UNUSED'),
('kTCCServicePostEvent', 1, '${tart_guest_agent_path}', 2, 0, 1, NULL, 'UNUSED');
EOF
}
# Resolve the user database before making either update. Never create an empty
# database at an obsolete path or silently omit user-level grants.
user_tcc_database="$(resolve_user_tcc_database)"
# Update TCC.db for all users
update_tcc_database "/Library/Application Support/com.apple.TCC/TCC.db"
# Update TCC.db for the current user
update_tcc_database "$user_tcc_database"

View File

@ -1,18 +1,23 @@
packer {
required_plugins {
tart = {
version = ">= 1.12.0"
version = ">= 0.5.4"
source = "github.com/cirruslabs/tart"
}
}
}
variable "vm_name" {
type = string
variable "macos_version" {
type = string
}
variable "gha_version" {
type = string
}
source "tart-cli" "tart" {
vm_name = "${var.vm_name}"
vm_base_name = "ghcr.io/cirruslabs/macos-${var.macos_version}-vanilla:13.3"
vm_name = "${var.macos_version}-base"
cpu_count = 4
memory_gb = 8
disk_size_gb = 50
@ -24,22 +29,20 @@ source "tart-cli" "tart" {
build {
sources = ["source.tart-cli.tart"]
provisioner "file" {
source = "data/limit.maxfiles.plist"
destination = "~/limit.maxfiles.plist"
}
provisioner "shell" {
inline = [
"echo 'Configuring maxfiles...'",
"sudo mv ~/limit.maxfiles.plist /Library/LaunchDaemons/limit.maxfiles.plist",
"sudo chown root:wheel /Library/LaunchDaemons/limit.maxfiles.plist",
"sudo chmod 0644 /Library/LaunchDaemons/limit.maxfiles.plist",
"echo 'Disabling spotlight...'",
"sudo mdutil -a -i off",
]
}
# setup DNS
provisioner "shell" {
inline = [
"networksetup -setdnsservers Ethernet 8.8.8.8 8.8.4.4 1.1.1.1",
]
}
# Create a symlink for bash compatibility
provisioner "shell" {
inline = [
@ -48,6 +51,15 @@ build {
]
}
provisioner "shell" {
inline = [
"cd $HOME",
"mkdir actions-runner && cd actions-runner",
"curl -O -L https://github.com/actions/runner/releases/download/v${var.gha_version}/actions-runner-osx-arm64-${var.gha_version}.tar.gz",
"tar xzf ./actions-runner-osx-arm64-${var.gha_version}.tar.gz",
"rm actions-runner-osx-arm64-${var.gha_version}.tar.gz",
]
}
provisioner "shell" {
inline = [
"/bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"",
@ -55,151 +67,27 @@ build {
"echo 'eval \"$(/opt/homebrew/bin/brew shellenv)\"' >> ~/.zprofile",
"echo \"export HOMEBREW_NO_AUTO_UPDATE=1\" >> ~/.zprofile",
"echo \"export HOMEBREW_NO_INSTALL_CLEANUP=1\" >> ~/.zprofile",
]
}
provisioner "shell" {
inline = [
"source ~/.zprofile",
"brew --version",
"brew update",
"brew install wget unzip zip ca-certificates cmake gcc git-lfs jq yq gh gitlab-runner",
"brew install buildkite/buildkite/buildkite-agent@3",
"brew install equinix-labs/otel-cli/otel-cli",
"brew install curl || true", // doesn't work on Monterey
"brew install --cask git-credential-manager",
"brew install wget cmake gcc git-lfs jq gh",
"git lfs install",
"sudo softwareupdate --install-rosetta --agree-to-license"
]
}
// Add GitHub to known hosts
// Similar to https://github.com/actions/runner-images/blob/main/images/macos/scripts/build/configure-ssh.sh
provisioner "shell" {
inline = [
"mkdir -p ~/.ssh"
]
}
provisioner "file" {
source = "data/github_known_hosts"
destination = "~/.ssh/known_hosts"
}
// Install the GitHub Actions runner
provisioner "shell" {
script = "scripts/install-actions-runner.sh"
}
// Create a /Users/runner /Users/admin symlink to support certain GitHub Actions
// like ruby/setup-ruby that hard-code the "/Users/runner/hostedtoolcache" path[1]
//
// [1]: https://github.com/ruby/setup-ruby/blob/6bd3d993c602f6b675728ebaecb2b569ff86e99b/common.js#L268
provisioner "shell" {
inline = [
"sudo ln -s /Users/admin /Users/runner"
]
}
provisioner "shell" {
inline = [
"source ~/.zprofile",
"brew install libyaml", # https://github.com/rbenv/ruby-build/discussions/2118
"brew install rbenv",
"echo 'if which rbenv > /dev/null; then eval \"$(rbenv init -)\"; fi' >> ~/.zprofile",
"brew install mise",
"source ~/.zprofile",
"rbenv install 2.7.8", // latest 2.x.x before EOL
"rbenv install -l | grep -v - | tail -2 | xargs -L1 rbenv install",
"rbenv global $(rbenv install -l | grep -v - | tail -1)",
"rbenv install 3.0.5",
"rbenv global 3.0.5",
"gem install bundler",
]
}
provisioner "shell" {
inline = [
"source ~/.zprofile",
"brew install node@24",
"echo 'export PATH=\"/opt/homebrew/opt/node@24/bin:$PATH\"' >> ~/.zprofile",
"source ~/.zprofile",
"node --version",
"npm install --global yarn pnpm",
"echo 'export PNPM_HOME=\"$HOME/Library/pnpm\"' >> ~/.zprofile",
"echo 'export PATH=\"$PNPM_HOME:$PATH\"' >> ~/.zprofile",
"source ~/.zprofile",
"yarn --version",
"pnpm --version",
]
}
provisioner "shell" {
inline = [
"sudo safaridriver --enable",
]
}
provisioner "shell" {
inline = [
"source ~/.zprofile",
"brew install awscli"
]
}
# Enable UI automation, see https://github.com/cirruslabs/macos-image-templates/issues/136
provisioner "shell" {
script = "scripts/automationmodetool.expect"
}
// some other health checks
provisioner "shell" {
inline = [
"source ~/.zprofile",
"test -d /Users/runner",
"test -f ~/.ssh/known_hosts"
]
}
// Install the process-scoped Metal shim for opt-in workloads.
provisioner "shell" {
inline = ["mkdir -p ~/tart-metal-capabilities-src"]
}
provisioner "file" {
source = "data/tart-metal-capabilities/"
destination = "~/tart-metal-capabilities-src/"
}
provisioner "shell" {
environment_vars = ["TART_METAL_SOURCE_DIR=/Users/admin/tart-metal-capabilities-src"]
script = "scripts/install-tart-metal-capabilities.sh"
}
provisioner "shell" {
inline = ["rm -rf ~/tart-metal-capabilities-src"]
}
// Guest agent for Tart VMs
provisioner "file" {
source = "data/tart-guest-daemon.plist"
destination = "~/tart-guest-daemon.plist"
}
provisioner "file" {
source = "data/tart-guest-agent.plist"
destination = "~/tart-guest-agent.plist"
}
provisioner "shell" {
inline = [
# Install Tart Guest Agent
"source ~/.zprofile",
"brew install openai/tools/tart-guest-agent",
# Install daemon variant of the Tart Guest Agent
"sudo mv ~/tart-guest-daemon.plist /Library/LaunchDaemons/org.cirruslabs.tart-guest-daemon.plist",
"sudo chown root:wheel /Library/LaunchDaemons/org.cirruslabs.tart-guest-daemon.plist",
"sudo chmod 0644 /Library/LaunchDaemons/org.cirruslabs.tart-guest-daemon.plist",
# Install agent variant of the Tart Guest Agent
"sudo mv ~/tart-guest-agent.plist /Library/LaunchAgents/org.cirruslabs.tart-guest-agent.plist",
"sudo chown root:wheel /Library/LaunchAgents/org.cirruslabs.tart-guest-agent.plist",
"sudo chmod 0644 /Library/LaunchAgents/org.cirruslabs.tart-guest-agent.plist",
]
}
# Update TCC.db and allow automation tools
provisioner "shell" {
script = "scripts/update-tcc-database.sh"
}
}

View File

@ -1,39 +0,0 @@
packer {
required_plugins {
tart = {
version = ">= 1.12.0"
source = "github.com/cirruslabs/tart"
}
}
}
variable "vm_name" {
type = string
}
source "tart-cli" "tart" {
vm_name = "${var.vm_name}"
recovery = true
cpu_count = 4
memory_gb = 8
disk_size_gb = 50
communicator = "none"
boot_command = [
# Skip over "Macintosh" and select "Options"
# to boot into macOS Recovery
"<wait60s><right><right><enter>",
# Open Terminal
"<wait10s><leftAltOn>T<leftAltOff>",
# Disable SIP
"<wait10s>csrutil disable<enter>",
"<wait10s>y<enter>",
"<wait10s>admin<enter>",
"<wait10s>admin<enter>",
# Shutdown
"<wait10s>halt<enter>"
]
}
build {
sources = ["source.tart-cli.tart"]
}

View File

@ -1,7 +1,7 @@
packer {
required_plugins {
tart = {
version = ">= 1.12.0"
version = ">= 0.5.4"
source = "github.com/cirruslabs/tart"
}
}
@ -22,8 +22,11 @@ source "tart-cli" "tart" {
# Skip over "Macintosh" and select "Options"
# to boot into macOS Recovery
"<wait60s><right><right><enter>",
# Select default language
"<wait10s><enter>",
# Open Terminal
"<wait10s><leftAltOn>T<leftAltOff>",
"<wait10s><leftCtrlOn><f2><leftCtrlOff>",
"<right><right><right><right><down><down><down><enter>",
# Disable SIP
"<wait10s>csrutil disable<enter>",
"<wait10s>y<enter>",

View File

@ -1,39 +0,0 @@
packer {
required_plugins {
tart = {
version = ">= 1.12.0"
source = "github.com/cirruslabs/tart"
}
}
}
variable "vm_name" {
type = string
}
variable "script_path" {
type = string
description = "Path to a local script that should be uploaded and executed inside the VM."
}
variable "pause_before" {
type = string
default = "60s"
description = "How long Packer should wait before running the uploaded script."
}
source "tart-cli" "tart" {
vm_name = "${var.vm_name}"
ssh_password = "admin"
ssh_username = "admin"
ssh_timeout = "120s"
}
build {
sources = ["source.tart-cli.tart"]
provisioner "shell" {
script = var.script_path
pause_before = var.pause_before
}
}

View File

@ -1,48 +0,0 @@
packer {
required_plugins {
tart = {
version = ">= 1.12.0"
source = "github.com/cirruslabs/tart"
}
}
}
variable "vm_base_name" {
type = string
}
variable "vm_name" {
type = string
}
variable "resolve_file" {
type = string
}
source "tart-cli" "tart" {
vm_base_name = "${var.vm_base_name}"
vm_name = "${var.vm_name}"
cpu_count = 4
memory_gb = 8
ssh_password = "admin"
ssh_username = "admin"
ssh_timeout = "120s"
}
build {
sources = ["source.tart-cli.tart"]
provisioner "shell" {
inline = [
# Use "-productVersion" instead of "--productVersion"
# to support old-style syntax used on macOS Monterey
"sw_vers -productVersion > /tmp/sw-vers-product-version.txt",
]
}
provisioner "file" {
source = "/tmp/sw-vers-product-version.txt"
destination = "${var.resolve_file}"
direction = "download"
}
}

View File

@ -1,107 +0,0 @@
packer {
required_plugins {
tart = {
version = ">= 1.16.0"
source = "github.com/cirruslabs/tart"
}
ansible = {
version = "~> 1"
source = "github.com/hashicorp/ansible"
}
}
}
source "tart-cli" "tart" {
from_ipsw = "https://updates.cdn-apple.com/2026SummerSeed/7b1c2bd9-7617-426d-92e5-ef204407ffaa/UniversalMac_27.0_26A5416b_Restore.ipsw"
vm_name = "golden-gate-vanilla"
cpu_count = 4
memory_gb = 8
disk_size_gb = 50
disk_format = "asif"
ssh_password = "admin"
ssh_username = "admin"
ssh_timeout = "180s"
// Requires Tart 2.33.0+ and macOS 27+ on both the host and guest VM
run_extra_args = [
"--provisioning-opts=${join(",", [
"fullName=Managed via Tart",
"username=admin",
"password=admin",
"logsInAutomatically=true",
"enablesRemoteLogin=true",
])}",
]
boot_command = [
# Wait for first-boot provisioning to finish automatic login
"<wait120s>",
# Enable Keyboard navigation
# This is so that we can navigate the System Settings app using the keyboard
"<wait10s><leftAltOn><spacebar><leftAltOff>Terminal<wait10s><enter>",
"<wait10s><wait10s>defaults write NSGlobalDomain AppleKeyboardUIMode -int 3<enter>",
# Disable Gatekeeper (1/2)
"<wait10s>sudo spctl --global-disable<enter>",
"<wait10s>admin<enter>",
# Disable Gatekeeper (2/2)
# On Tahoe opening System Settings through Spotlight is not very reliable, sometimes opens System information
"<wait10s>open '/System/Applications/System Settings.app'<enter>",
# Wait for System Settings to fully open before navigating with the keyboard
"<wait120s>",
"<wait10s><leftCtrlOn><f2><leftCtrlOff><right><right><right><down>Privacy & Security<enter>",
"<wait10s><leftShiftOn><tab><tab><tab><tab><tab><tab><leftShiftOff>",
"<wait10s><down><wait1s><down><wait1s><enter>",
"<wait10s>admin<enter>",
"<wait10s><leftShiftOn><tab><leftShiftOff><wait1s><spacebar>",
# Quit System Settings
"<wait10s><leftAltOn>q<leftAltOff>",
]
// A (hopefully) temporary workaround for Virtualization.Framework's
// installation process not fully finishing in a timely manner
create_grace_time = "30s"
// Keep the recovery partition, otherwise it's not possible to "softwareupdate"
recovery_partition = "keep"
}
build {
sources = ["source.tart-cli.tart"]
provisioner "shell" {
inline = [
// Enable passwordless sudo
"echo admin | sudo -S sh -c \"mkdir -p /etc/sudoers.d/; echo 'admin ALL=(ALL) NOPASSWD: ALL' | EDITOR=tee visudo /etc/sudoers.d/admin-nopasswd\"",
// Enable Screen Sharing for "tart run --vnc"
"sudo launchctl enable system/com.apple.screensharing",
// Use the same timezone as the previous Setup Assistant flow
"sudo systemsetup -settimezone GMT 2>/dev/null",
// Disable screensaver at login screen
"sudo defaults write /Library/Preferences/com.apple.screensaver loginWindowIdleTime 0",
// Disable screensaver for admin user
"defaults -currentHost write com.apple.screensaver idleTime 0",
// Prevent the VM from sleeping
"sudo systemsetup -setsleep Off 2>/dev/null",
// Launch Safari to populate the defaults
"/Applications/Safari.app/Contents/MacOS/Safari &",
"SAFARI_PID=$!",
"disown",
"sleep 30",
"kill -9 $SAFARI_PID",
// Enable Safari's remote automation
"sudo safaridriver --enable",
// Disable screen lock
//
// Note that this only works if the user is logged-in,
// i.e. not on login screen.
"sysadminctl -screenLock off -password admin",
]
}
provisioner "shell" {
inline = [
# Ensure that Gatekeeper is disabled
"spctl --status | grep -q 'assessments disabled'",
# Ensure that FileVault remains disabled by default
"sudo fdesetup status | grep -q 'FileVault is Off'",
]
}
}

View File

@ -1,37 +1,28 @@
packer {
required_plugins {
tart = {
version = ">= 1.2.0"
version = ">= 0.5.4"
source = "github.com/cirruslabs/tart"
}
ansible = {
version = "~> 1"
source = "github.com/hashicorp/ansible"
}
}
}
source "tart-cli" "tart" {
# You can find macOS IPSW URLs on various websites like https://ipsw.me/
from_ipsw = "https://updates.cdn-apple.com/2022FallFCS/fullrestores/012-66032/8D8D90C6-A876-4FFF-BBF4-D158939B3841/UniversalMac_12.6.1_21G217_Restore.ipsw"
# and https://www.theiphonewiki.com/wiki/Beta_Firmware/Mac/13.x
from_ipsw = "https://updates.cdn-apple.com/2022FallFCS/fullrestores/012-40537/0EC7C669-13E9-49FB-BD64-9EECC1D174B2/UniversalMac_12.6_21G115_Restore.ipsw"
vm_name = "monterey-vanilla"
cpu_count = 4
memory_gb = 8
disk_size_gb = 50
disk_size_gb = 40
ssh_password = "admin"
ssh_username = "admin"
ssh_timeout = "120s"
boot_command = [
# hello, hola, bonjour, etc.
"<wait60s><spacebar>",
# Language: most of the times we have a list of "English"[1], "English (UK)", etc. with
# "English" language already selected. If we type "english", it'll cause us to switch
# to the "English (UK)", which is not what we want. To solve this, we switch to some other
# language first, e.g. "Italiano" and then switch back to "English". We'll then jump to the
# first entry in a list of "english"-prefixed items, which will be "English".
#
# [1]: should be named "English (US)", but oh well 🤷
"<wait30s>italiano<esc>english<enter>",
# Language
"<wait30s><enter>",
# Select Your Country and Region
"<wait30s>united states<leftShiftOn><tab><leftShiftOff><spacebar>",
# Written and Spoken Languages
@ -83,9 +74,6 @@ source "tart-cli" "tart" {
// A (hopefully) temporary workaround for Virtualization.Framework's
// installation process not fully finishing in a timely manner
create_grace_time = "30s"
// Keep the recovery partition, otherwise it's not possible to "softwareupdate"
recovery_partition = "keep"
}
build {
@ -102,20 +90,18 @@ build {
"sudo defaults write /Library/Preferences/com.apple.loginwindow autoLoginUser admin",
// Disable screensaver at login screen
"sudo defaults write /Library/Preferences/com.apple.screensaver loginWindowIdleTime 0",
// Disable screensaver for admin user
"defaults -currentHost write com.apple.screensaver idleTime 0",
// Prevent the VM from sleeping
"sudo systemsetup -setdisplaysleep Off 2>/dev/null",
"sudo systemsetup -setsleep Off 2>/dev/null",
"sudo systemsetup -setcomputersleep Off 2>/dev/null",
"sudo systemsetup -setdisplaysleep Off",
"sudo systemsetup -setsleep Off",
"sudo systemsetup -setcomputersleep Off",
// Launch Safari to populate the defaults
"/Applications/Safari.app/Contents/MacOS/Safari &",
"SAFARI_PID=$!",
"disown",
"sleep 30",
"kill -9 $SAFARI_PID",
// Enable Safari's remote automation
"sleep 3",
"kill -9 %1",
// Enable Safari's remote automation and "Develop" menu
"sudo safaridriver --enable",
"defaults write com.apple.Safari.SandboxBroker ShowDevelopMenu -bool true",
"defaults write com.apple.Safari IncludeDevelopMenu -bool true",
// Disable screen lock
//
// Note that this only works if the user is logged-in,
@ -123,26 +109,4 @@ build {
"sysadminctl -screenLock off -password admin",
]
}
provisioner "shell" {
inline = [
# Install command-line tools
"touch /tmp/.com.apple.dt.CommandLineTools.installondemand.in-progress",
"softwareupdate --list | sed -n 's/.*Label: \\(Command Line Tools for Xcode-.*\\)/\\1/p' | xargs -I {} softwareupdate --install '{}'",
"rm /tmp/.com.apple.dt.CommandLineTools.installondemand.in-progress",
]
}
provisioner "ansible" {
playbook_file = "ansible/playbook-system-updater.yml"
extra_arguments = [
"-vvv",
"--extra-vars", "stdinpass=admin",
]
ansible_env_vars = [
"ANSIBLE_TRANSPORT=paramiko",
"ANSIBLE_HOST_KEY_CHECKING=False",
]
use_proxy = false
}
}

View File

@ -1,177 +0,0 @@
packer {
required_plugins {
tart = {
version = ">= 1.16.0"
source = "github.com/cirruslabs/tart"
}
ansible = {
version = "~> 1"
source = "github.com/hashicorp/ansible"
}
}
}
source "tart-cli" "tart" {
// will be update to 15.7.2
from_ipsw = "https://updates.cdn-apple.com/2025SummerFCS/fullrestores/093-10809/CFD6DD38-DAF0-40DA-854F-31AAD1294C6F/UniversalMac_15.6.1_24G90_Restore.ipsw"
vm_name = "sequoia-vanilla"
cpu_count = 4
memory_gb = 8
disk_size_gb = 50
ssh_password = "admin"
ssh_username = "admin"
ssh_timeout = "180s"
boot_command = [
# hello, hola, bonjour, etc.
"<wait60s><spacebar>",
# Language: most of the times we have a list of "English"[1], "English (UK)", etc. with
# "English" language already selected. If we type "english", it'll cause us to switch
# to the "English (UK)", which is not what we want. To solve this, we switch to some other
# language first, e.g. "Italiano" and then switch back to "English". We'll then jump to the
# first entry in a list of "english"-prefixed items, which will be "English".
#
# [1]: should be named "English (US)", but oh well 🤷
"<wait30s>italiano<esc>english<enter>",
# Select Your Country or Region
"<wait30s><click 'Select Your Country or Region'><wait5s>united states<leftShiftOn><tab><leftShiftOff><spacebar>",
# Transfer Your Data to This Mac
"<wait10s><tab><tab><tab><spacebar><tab><tab><spacebar>",
# Written and Spoken Languages
"<wait10s><leftShiftOn><tab><leftShiftOff><spacebar>",
# Accessibility
"<wait10s><leftShiftOn><tab><leftShiftOff><spacebar>",
# Data & Privacy
"<wait10s><leftShiftOn><tab><leftShiftOff><spacebar>",
# Create a Mac Account
"<wait10s>Managed via Tart<tab>admin<tab>admin<tab>admin<tab><tab><spacebar><tab><tab><spacebar>",
# Enable Voice Over
"<wait120s><leftAltOn><f5><leftAltOff>",
# Sign In with Your Apple ID
"<wait10s><leftShiftOn><tab><leftShiftOff><spacebar>",
# Are you sure you want to skip signing in with an Apple ID?
"<wait10s><tab><spacebar>",
# Terms and Conditions
"<wait10s><leftShiftOn><tab><leftShiftOff><spacebar>",
# I have read and agree to the macOS Software License Agreement
"<wait10s><tab><spacebar>",
# Enable Location Services
"<wait10s><leftShiftOn><tab><leftShiftOff><spacebar>",
# Are you sure you don't want to use Location Services?
"<wait10s><tab><spacebar>",
# Select Your Time Zone
"<wait10s><tab><tab>UTC<enter><leftShiftOn><tab><tab><leftShiftOff><spacebar>",
# Analytics
"<wait10s><leftShiftOn><tab><leftShiftOff><spacebar>",
# Screen Time
"<wait10s><tab><spacebar>",
# Siri
"<wait10s><tab><spacebar><leftShiftOn><tab><leftShiftOff><spacebar>",
# Choose Your Look
"<wait10s><leftShiftOn><tab><leftShiftOff><spacebar>",
# Update Mac Automatically
"<wait10s><tab><spacebar>",
# Welcome to Mac
"<wait10s><spacebar>",
# Disable Voice Over
"<leftAltOn><f5><leftAltOff>",
# Enable Keyboard navigation
# This is so that we can navigate the System Settings app using the keyboard
"<wait10s><leftAltOn><spacebar><leftAltOff>Terminal<enter>",
"<wait10s>defaults write NSGlobalDomain AppleKeyboardUIMode -int 3<enter>",
"<wait10s><leftAltOn>q<leftAltOff>",
# Now that the installation is done, open "System Settings"
"<wait10s><leftAltOn><spacebar><leftAltOff>System Settings<enter>",
# Navigate to "Sharing"
"<wait10s><leftCtrlOn><f2><leftCtrlOff><right><right><right><down>Sharing<enter>",
# Navigate to "Screen Sharing" and enable it
"<wait10s><tab><tab><tab><tab><tab><tab><tab><spacebar>",
# Navigate to "Remote Login" and enable it
"<wait10s><tab><tab><tab><tab><tab><tab><tab><tab><tab><tab><tab><tab><spacebar>",
# Quit System Settings
"<wait10s><leftAltOn>q<leftAltOff>",
# Disable Gatekeeper (1/2)
"<wait10s><leftAltOn><spacebar><leftAltOff>Terminal<enter>",
"<wait10s>sudo spctl --global-disable<enter>",
"<wait10s>admin<enter>",
"<wait10s><leftAltOn>q<leftAltOff>",
# Disable Gatekeeper (2/2)
"<wait10s><leftAltOn><spacebar><leftAltOff>System Settings<enter>",
"<wait10s><leftCtrlOn><f2><leftCtrlOff><right><right><right><down>Privacy & Security<enter>",
"<wait10s><leftShiftOn><tab><leftShiftOff><leftShiftOn><tab><leftShiftOff><leftShiftOn><tab><leftShiftOff><leftShiftOn><tab><leftShiftOff><leftShiftOn><tab><leftShiftOff><leftShiftOn><tab><leftShiftOff><leftShiftOn><tab><leftShiftOff>",
"<wait10s><down><wait1s><down><wait1s><enter>",
"<wait10s>admin<enter>",
"<wait10s><leftShiftOn><tab><leftShiftOff><wait1s><spacebar>",
# Quit System Settings
"<wait10s><leftAltOn>q<leftAltOff>",
]
// A (hopefully) temporary workaround for Virtualization.Framework's
// installation process not fully finishing in a timely manner
create_grace_time = "30s"
// Keep the recovery partition, otherwise it's not possible to "softwareupdate"
recovery_partition = "keep"
}
build {
sources = ["source.tart-cli.tart"]
provisioner "shell" {
inline = [
// Enable passwordless sudo
"echo admin | sudo -S sh -c \"mkdir -p /etc/sudoers.d/; echo 'admin ALL=(ALL) NOPASSWD: ALL' | EDITOR=tee visudo /etc/sudoers.d/admin-nopasswd\"",
// Enable auto-login
//
// See https://github.com/xfreebird/kcpassword for details.
"echo '00000000: 1ced 3f4a bcbc ba2c caca 4e82' | sudo xxd -r - /etc/kcpassword",
"sudo defaults write /Library/Preferences/com.apple.loginwindow autoLoginUser admin",
// Disable screensaver at login screen
"sudo defaults write /Library/Preferences/com.apple.screensaver loginWindowIdleTime 0",
// Disable screensaver for admin user
"defaults -currentHost write com.apple.screensaver idleTime 0",
// Prevent the VM from sleeping
"sudo systemsetup -setsleep Off 2>/dev/null",
// Launch Safari to populate the defaults
"/Applications/Safari.app/Contents/MacOS/Safari &",
"SAFARI_PID=$!",
"disown",
"sleep 30",
"kill -9 $SAFARI_PID",
// Enable Safari's remote automation
"sudo safaridriver --enable",
// Disable screen lock
//
// Note that this only works if the user is logged-in,
// i.e. not on login screen.
"sysadminctl -screenLock off -password admin",
]
}
provisioner "shell" {
inline = [
# Ensure that Gatekeeper is disabled
"spctl --status | grep -q 'assessments disabled'"
]
}
provisioner "shell" {
inline = [
# Install command-line tools
"touch /tmp/.com.apple.dt.CommandLineTools.installondemand.in-progress",
"softwareupdate --list | sed -n 's/.*Label: \\(Command Line Tools for Xcode-.*\\)/\\1/p' | xargs -I {} softwareupdate --install '{}'",
"rm /tmp/.com.apple.dt.CommandLineTools.installondemand.in-progress",
]
}
provisioner "ansible" {
playbook_file = "ansible/playbook-system-updater.yml"
extra_arguments = [
"-vvv",
]
ansible_env_vars = [
"ANSIBLE_TRANSPORT=paramiko",
"ANSIBLE_HOST_KEY_CHECKING=False",
]
use_proxy = false
}
}

View File

@ -1,147 +0,0 @@
packer {
required_plugins {
tart = {
version = ">= 1.12.0"
source = "github.com/cirruslabs/tart"
}
ansible = {
version = "~> 1"
source = "github.com/hashicorp/ansible"
}
}
}
source "tart-cli" "tart" {
// will be update to 14.8.2
from_ipsw = "https://updates.cdn-apple.com/2024SummerFCS/fullrestores/062-52859/932E0A8F-6644-4759-82DA-F8FA8DEA806A/UniversalMac_14.6.1_23G93_Restore.ipsw"
vm_name = "sonoma-vanilla"
cpu_count = 4
memory_gb = 8
disk_size_gb = 50
ssh_password = "admin"
ssh_username = "admin"
ssh_timeout = "180s"
boot_command = [
# hello, hola, bonjour, etc.
"<wait60s><spacebar>",
# Language: most of the times we have a list of "English"[1], "English (UK)", etc. with
# "English" language already selected. If we type "english", it'll cause us to switch
# to the "English (UK)", which is not what we want. To solve this, we switch to some other
# language first, e.g. "Italiano" and then switch back to "English". We'll then jump to the
# first entry in a list of "english"-prefixed items, which will be "English".
#
# [1]: should be named "English (US)", but oh well 🤷
"<wait30s>italiano<esc>english<enter>",
# Select Your Country and Region
"<wait30s>united states<leftShiftOn><tab><leftShiftOff><spacebar>",
# Written and Spoken Languages
"<wait10s><leftShiftOn><tab><leftShiftOff><spacebar>",
# Accessibility
"<wait10s><leftShiftOn><tab><leftShiftOff><spacebar>",
# Data & Privacy
"<wait10s><leftShiftOn><tab><leftShiftOff><spacebar>",
# Migration Assistant
"<wait10s><tab><tab><tab><spacebar>",
# Sign In with Your Apple ID
"<wait10s><leftShiftOn><tab><leftShiftOff><leftShiftOn><tab><leftShiftOff><spacebar>",
# Are you sure you want to skip signing in with an Apple ID?
"<wait10s><tab><spacebar>",
# Terms and Conditions
"<wait10s><leftShiftOn><tab><leftShiftOff><spacebar>",
# I have read and agree to the macOS Software License Agreement
"<wait10s><tab><spacebar>",
# Create a Computer Account
"<wait10s>admin<tab><tab>admin<tab>admin<tab><tab><tab><spacebar>",
# Enable Location Services
"<wait30s><leftShiftOn><tab><leftShiftOff><spacebar>",
# Are you sure you don't want to use Location Services?
"<wait10s><tab><spacebar>",
# Select Your Time Zone
"<wait10s><tab>UTC<enter><leftShiftOn><tab><leftShiftOff><spacebar>",
# Analytics
"<wait10s><leftShiftOn><tab><leftShiftOff><spacebar>",
# Screen Time
"<wait10s><tab><spacebar>",
# Siri
"<wait10s><tab><spacebar><leftShiftOn><tab><leftShiftOff><spacebar>",
# Choose Your Look
"<wait10s><leftShiftOn><tab><leftShiftOff><spacebar>",
# Enable Voice Over
"<wait10s><leftAltOn><f5><leftAltOff><wait5s>v",
# Now that the installation is done, open "System Settings"
"<wait10s><leftAltOn><spacebar><leftAltOff>System Settings<enter>",
# Navigate to "Sharing"
"<wait10s><leftAltOn>f<leftAltOff>sharing<enter>",
# Navigate to "Screen Sharing" and enable it
"<wait10s><tab><tab><tab><tab><tab><spacebar>",
# Navigate to "Remote Login" and enable it
"<wait10s><tab><tab><tab><tab><tab><tab><tab><tab><tab><tab><tab><tab><spacebar>",
# Disable Voice Over
"<leftAltOn><f5><leftAltOff>",
]
// A (hopefully) temporary workaround for Virtualization.Framework's
// installation process not fully finishing in a timely manner
create_grace_time = "30s"
// Keep the recovery partition, otherwise it's not possible to "softwareupdate"
recovery_partition = "keep"
}
build {
sources = ["source.tart-cli.tart"]
provisioner "shell" {
inline = [
// Enable passwordless sudo
"echo admin | sudo -S sh -c \"mkdir -p /etc/sudoers.d/; echo 'admin ALL=(ALL) NOPASSWD: ALL' | EDITOR=tee visudo /etc/sudoers.d/admin-nopasswd\"",
// Enable auto-login
//
// See https://github.com/xfreebird/kcpassword for details.
"echo '00000000: 1ced 3f4a bcbc ba2c caca 4e82' | sudo xxd -r - /etc/kcpassword",
"sudo defaults write /Library/Preferences/com.apple.loginwindow autoLoginUser admin",
// Disable screensaver at login screen
"sudo defaults write /Library/Preferences/com.apple.screensaver loginWindowIdleTime 0",
// Disable screensaver for admin user
"defaults -currentHost write com.apple.screensaver idleTime 0",
// Prevent the VM from sleeping
"sudo systemsetup -setdisplaysleep Off 2>/dev/null",
"sudo systemsetup -setsleep Off 2>/dev/null",
"sudo systemsetup -setcomputersleep Off 2>/dev/null",
// Launch Safari to populate the defaults
"/Applications/Safari.app/Contents/MacOS/Safari &",
"SAFARI_PID=$!",
"disown",
"sleep 30",
"kill -9 $SAFARI_PID",
// Enable Safari's remote automation
"sudo safaridriver --enable",
// Disable screen lock
//
// Note that this only works if the user is logged-in,
// i.e. not on login screen.
"sysadminctl -screenLock off -password admin",
]
}
provisioner "shell" {
inline = [
# Install command-line tools
"touch /tmp/.com.apple.dt.CommandLineTools.installondemand.in-progress",
"softwareupdate --list | sed -n 's/.*Label: \\(Command Line Tools for Xcode-.*\\)/\\1/p' | xargs -I {} softwareupdate --install '{}'",
"rm /tmp/.com.apple.dt.CommandLineTools.installondemand.in-progress",
]
}
provisioner "ansible" {
playbook_file = "ansible/playbook-system-updater.yml"
extra_arguments = [
"-vvv",
]
ansible_env_vars = [
"ANSIBLE_TRANSPORT=paramiko",
"ANSIBLE_HOST_KEY_CHECKING=False",
]
use_proxy = false
}
}

View File

@ -1,163 +0,0 @@
packer {
required_plugins {
tart = {
version = ">= 1.16.0"
source = "github.com/cirruslabs/tart"
}
ansible = {
version = "~> 1"
source = "github.com/hashicorp/ansible"
}
}
}
source "tart-cli" "tart" {
from_ipsw = "https://updates.cdn-apple.com/2026SummerFCS/fullrestores/140-75212/A2A24B94-1FC1-45A3-93F7-C51B02AF1F4D/UniversalMac_26.6.2_25G83_Restore.ipsw"
vm_name = "tahoe-vanilla"
cpu_count = 4
memory_gb = 8
disk_size_gb = 50
ssh_password = "admin"
ssh_username = "admin"
ssh_timeout = "180s"
boot_command = [
# hello, hola, bonjour, etc.
"<wait60s><spacebar>",
# Language: most of the times we have a list of "English"[1], "English (UK)", etc. with
# "English" language already selected. If we type "english", it'll cause us to switch
# to the "English (UK)", which is not what we want. To solve this, we switch to some other
# language first, e.g. "Italiano" and then switch back to "English". We'll then jump to the
# first entry in a list of "english"-prefixed items, which will be "English".
#
# [1]: should be named "English (US)", but oh well 🤷
"<wait30s>italiano<esc>english<enter>",
# Select Your Country or Region
"<wait60s><click 'Select Your Country or Region'><wait5s>united states<leftShiftOn><tab><leftShiftOff><spacebar>",
# Transfer Your Data to This Mac
"<wait10s><tab><tab><tab><spacebar><tab><tab><spacebar>",
# Written and Spoken Languages
"<wait10s><leftShiftOn><tab><leftShiftOff><spacebar>",
# Accessibility
"<wait10s><leftShiftOn><tab><leftShiftOff><spacebar>",
# Data & Privacy
"<wait10s><leftShiftOn><tab><leftShiftOff><spacebar>",
# Create a Mac Account
"<wait10s><tab><tab><tab><tab><tab><tab>Managed via Tart<tab>admin<tab>admin<tab>admin<tab><tab><spacebar><tab><tab><spacebar>",
# Enable Voice Over
"<wait120s><leftAltOn><f5><leftAltOff>",
# Sign In with Your Apple ID
"<wait10s><leftShiftOn><tab><leftShiftOff><spacebar><up><spacebar>",
# Are you sure you want to skip signing in with an Apple ID?
"<wait10s><tab><spacebar>",
# Terms and Conditions
"<wait10s><leftShiftOn><tab><leftShiftOff><spacebar>",
# I have read and agree to the macOS Software License Agreement
"<wait10s><tab><spacebar>",
# Age Range -> Adult
"<wait10s><tab><tab><tab><spacebar>",
# Enable Location Services
"<wait10s><leftShiftOn><tab><leftShiftOff><spacebar>",
# Are you sure you don't want to use Location Services?
"<wait10s><tab><spacebar>",
# Select Your Time Zone
"<wait10s><tab><tab><tab>UTC<enter><leftShiftOn><tab><leftShiftOff><spacebar>",
# Analytics
"<wait10s><leftShiftOn><tab><leftShiftOff><spacebar>",
# Screen Time
"<wait10s><tab><tab><spacebar>",
# Siri
"<wait10s><tab><spacebar><leftShiftOn><tab><leftShiftOff><spacebar>",
# You Mac is Ready for FileVault
"<wait10s><leftShiftOn><tab><tab><leftShiftOff><spacebar>",
# Mac Data Will Not Be Securely Encrypted
"<wait10s><tab><spacebar>",
# Choose Your Look
"<wait10s><leftShiftOn><tab><leftShiftOff><spacebar>",
# Update Mac Automatically
"<wait10s><tab><tab><spacebar>",
# Welcome to Mac
"<wait30s><spacebar>",
# Disable Voice Over
"<wait10s><leftAltOn><f5><leftAltOff>",
# Enable Keyboard navigation
# This is so that we can navigate the System Settings app using the keyboard
"<wait10s><leftAltOn><spacebar><leftAltOff>Terminal<wait10s><enter>",
"<wait10s><wait10s>defaults write NSGlobalDomain AppleKeyboardUIMode -int 3<enter>",
# Now that the installation is done, open "System Settings"
# On Tahoe opening System Settings through Spotlight is not very reliable, sometimes opens System information
"<wait10s>open '/System/Applications/System Settings.app'<enter>",
"<wait120s>",
# Navigate to "Sharing"
"<wait10s><leftCtrlOn><f2><leftCtrlOff><right><right><right><down>Sharing<enter>",
# Navigate to "Screen Sharing" and enable it
"<wait10s><tab><tab><tab><tab><tab><spacebar>",
# Type in the password to allow enabling Screen Sharing
"<wait10s>admin<enter>",
# Navigate to "Remote Login" and enable it
"<wait10s><tab><tab><tab><tab><tab><tab><tab><tab><tab><tab><tab><tab><spacebar>",
# Quit System Settings
"<wait10s><leftAltOn>q<leftAltOff>",
# Disable Gatekeeper (1/2)
"<wait10s>sudo spctl --global-disable<enter>",
"<wait10s>admin<enter>",
# Disable Gatekeeper (2/2)
# On Tahoe opening System Settings through Spotlight is not very reliable, sometimes opens System information
"<wait10s>open '/System/Applications/System Settings.app'<enter>",
"<wait10s><leftCtrlOn><f2><leftCtrlOff><right><right><right><down>Privacy & Security<enter>",
"<wait10s><leftShiftOn><tab><tab><tab><tab><tab><tab><leftShiftOff>",
"<wait10s><down><wait1s><down><wait1s><enter>",
"<wait10s>admin<enter>",
"<wait10s><leftShiftOn><tab><leftShiftOff><wait1s><spacebar>",
# Quit System Settings
"<wait10s><leftAltOn>q<leftAltOff>",
]
// A (hopefully) temporary workaround for Virtualization.Framework's
// installation process not fully finishing in a timely manner
create_grace_time = "30s"
// Keep the recovery partition, otherwise it's not possible to "softwareupdate"
recovery_partition = "keep"
}
build {
sources = ["source.tart-cli.tart"]
provisioner "shell" {
inline = [
// Enable passwordless sudo
"echo admin | sudo -S sh -c \"mkdir -p /etc/sudoers.d/; echo 'admin ALL=(ALL) NOPASSWD: ALL' | EDITOR=tee visudo /etc/sudoers.d/admin-nopasswd\"",
// Enable auto-login
//
// See https://github.com/xfreebird/kcpassword for details.
"echo '00000000: 1ced 3f4a bcbc ba2c caca 4e82' | sudo xxd -r - /etc/kcpassword",
"sudo defaults write /Library/Preferences/com.apple.loginwindow autoLoginUser admin",
// Disable screensaver at login screen
"sudo defaults write /Library/Preferences/com.apple.screensaver loginWindowIdleTime 0",
// Disable screensaver for admin user
"defaults -currentHost write com.apple.screensaver idleTime 0",
// Prevent the VM from sleeping
"sudo systemsetup -setsleep Off 2>/dev/null",
// Launch Safari to populate the defaults
"/Applications/Safari.app/Contents/MacOS/Safari &",
"SAFARI_PID=$!",
"disown",
"sleep 30",
"kill -9 $SAFARI_PID",
// Enable Safari's remote automation
"sudo safaridriver --enable",
// Disable screen lock
//
// Note that this only works if the user is logged-in,
// i.e. not on login screen.
"sysadminctl -screenLock off -password admin",
]
}
provisioner "shell" {
inline = [
# Ensure that Gatekeeper is disabled
"spctl --status | grep -q 'assessments disabled'"
]
}
}

View File

@ -1,38 +1,28 @@
packer {
required_plugins {
tart = {
version = ">= 1.12.0"
version = ">= 0.5.4"
source = "github.com/cirruslabs/tart"
}
ansible = {
version = "~> 1"
source = "github.com/hashicorp/ansible"
}
}
}
source "tart-cli" "tart" {
# You can find macOS IPSW URLs on various websites like https://ipsw.me/
# and https://www.theiphonewiki.com/wiki/Beta_Firmware/Mac/13.x
from_ipsw = "https://updates.cdn-apple.com/2023FallFCS/fullrestores/042-55833/C0830847-A2F8-458F-B680-967991820931/UniversalMac_13.6_22G120_Restore.ipsw"
from_ipsw = "https://updates.cdn-apple.com/2023WinterSeed/fullrestores/002-75537/8250FA0E-0962-46D6-8A90-57A390B9FFD7/UniversalMac_13.3_22E252_Restore.ipsw"
vm_name = "ventura-vanilla"
cpu_count = 4
memory_gb = 8
disk_size_gb = 50
disk_size_gb = 40
ssh_password = "admin"
ssh_username = "admin"
ssh_timeout = "120s"
boot_command = [
# hello, hola, bonjour, etc.
"<wait60s><spacebar>",
# Language: most of the times we have a list of "English"[1], "English (UK)", etc. with
# "English" language already selected. If we type "english", it'll cause us to switch
# to the "English (UK)", which is not what we want. To solve this, we switch to some other
# language first, e.g. "Italiano" and then switch back to "English". We'll then jump to the
# first entry in a list of "english"-prefixed items, which will be "English".
#
# [1]: should be named "English (US)", but oh well 🤷
"<wait30s>italiano<esc>english<enter>",
# Language
"<wait30s>english<enter>",
# Select Your Country and Region
"<wait30s>united states<leftShiftOn><tab><leftShiftOff><spacebar>",
# Written and Spoken Languages
@ -90,9 +80,6 @@ source "tart-cli" "tart" {
// A (hopefully) temporary workaround for Virtualization.Framework's
// installation process not fully finishing in a timely manner
create_grace_time = "30s"
// Keep the recovery partition, otherwise it's not possible to "softwareupdate"
recovery_partition = "keep"
}
build {
@ -109,47 +96,23 @@ build {
"sudo defaults write /Library/Preferences/com.apple.loginwindow autoLoginUser admin",
// Disable screensaver at login screen
"sudo defaults write /Library/Preferences/com.apple.screensaver loginWindowIdleTime 0",
// Disable screensaver for admin user
"defaults -currentHost write com.apple.screensaver idleTime 0",
// Prevent the VM from sleeping
"sudo systemsetup -setdisplaysleep Off 2>/dev/null",
"sudo systemsetup -setsleep Off 2>/dev/null",
"sudo systemsetup -setcomputersleep Off 2>/dev/null",
"sudo systemsetup -setdisplaysleep Off",
"sudo systemsetup -setsleep Off",
"sudo systemsetup -setcomputersleep Off",
// Launch Safari to populate the defaults
"/Applications/Safari.app/Contents/MacOS/Safari &",
"SAFARI_PID=$!",
"disown",
"sleep 30",
"kill -9 $SAFARI_PID",
// Enable Safari's remote automation
"kill -9 %1",
// Enable Safari's remote automation and "Develop" menu
"sudo safaridriver --enable",
"defaults write com.apple.Safari.SandboxBroker ShowDevelopMenu -bool true",
"defaults write com.apple.Safari IncludeDevelopMenu -bool true",
// Disable screen lock
//
// Note that this only works if the user is logged-in,
// i.e. not on login screen.
"sysadminctl -screenLock off -password admin",
"defaults -currentHost write com.apple.screensaver idleTime 0"
]
}
provisioner "shell" {
inline = [
# Install command-line tools
"touch /tmp/.com.apple.dt.CommandLineTools.installondemand.in-progress",
"softwareupdate --list | sed -n 's/.*Label: \\(Command Line Tools for Xcode-.*\\)/\\1/p' | xargs -I {} softwareupdate --install '{}'",
"rm /tmp/.com.apple.dt.CommandLineTools.installondemand.in-progress",
]
}
provisioner "ansible" {
playbook_file = "ansible/playbook-system-updater.yml"
extra_arguments = [
"-vvv",
]
ansible_env_vars = [
"ANSIBLE_TRANSPORT=paramiko",
"ANSIBLE_HOST_KEY_CHECKING=False",
]
use_proxy = false
}
}

View File

@ -1,128 +1,71 @@
packer {
required_plugins {
tart = {
version = ">= 1.12.0"
version = ">= 0.5.4"
source = "github.com/cirruslabs/tart"
}
}
}
variable "macos_version" {
type = string
type = string
}
variable "xcode_version" {
type = list(string)
type = string
}
variable "additional_ios_builds" {
type = list(string)
default = []
}
variable "additional_tvos_builds" {
type = list(string)
default = []
}
variable "xcode_components" {
type = list(string)
default = []
description = "Additional Xcode components to download."
}
variable "expected_runtimes_file" {
type = string
default = ""
description = "Path to file containing expected simulator runtimes. If empty, runtime verification is skipped."
}
variable "tag" {
type = string
default = ""
}
variable "disk_size" {
type = number
default = 140
}
variable "disk_free_mb" {
type = number
default = 15000
variable "gha_version" {
type = string
}
variable "android_sdk_tools_version" {
type = string
default = "14742923" # https://developer.android.com/studio#command-line-tools-only
type = string
default = "9477386" # https://developer.android.com/studio/#command-tools
}
source "tart-cli" "tart" {
vm_base_name = "ghcr.io/cirruslabs/macos-${var.macos_version}-base:latest"
// use tag or the last element of the xcode_version list
vm_name = "${var.macos_version}-xcode:${var.tag != "" ? var.tag : var.xcode_version[0]}"
vm_base_name = "${var.macos_version}-base"
vm_name = "${var.macos_version}-xcode:${var.xcode_version}"
cpu_count = 4
memory_gb = 8
disk_size_gb = var.disk_size
disk_size_gb = 90
headless = true
ssh_password = "admin"
ssh_username = "admin"
ssh_timeout = "120s"
}
locals {
xcode_install_provisioners = [
for version in reverse(sort(var.xcode_version)) : {
type = "shell"
inline = [
"source ~/.zprofile",
"sudo xcodes install ${version} --experimental-unxip --path /Users/admin/Downloads/Xcode_${version}.xip --select --empty-trash",
// get selected xcode path, strip /Contents/Developer and move to GitHub compatible locations
"INSTALLED_PATH=$(xcodes select -p)",
"CONTENTS_DIR=$(dirname $INSTALLED_PATH)",
"APP_DIR=$(dirname $CONTENTS_DIR)",
"sudo mv $APP_DIR /Applications/Xcode_${version}.app",
"sudo xcode-select -s /Applications/Xcode_${version}.app",
"xcodebuild -downloadPlatform iOS",
"xcodebuild -runFirstLaunch",
"df -h",
]
}
]
}
build {
sources = ["source.tart-cli.tart"]
// re-install the actions runner
provisioner "shell" {
inline = [
"cd $HOME",
"rm -rf actions-runner",
"mkdir actions-runner && cd actions-runner",
"curl -O -L https://github.com/actions/runner/releases/download/v${var.gha_version}/actions-runner-osx-arm64-${var.gha_version}.tar.gz",
"tar xzf ./actions-runner-osx-arm64-${var.gha_version}.tar.gz",
"rm actions-runner-osx-arm64-${var.gha_version}.tar.gz",
]
}
provisioner "shell" {
inline = [
"source ~/.zprofile",
"brew --version",
"brew update",
"brew upgrade",
"brew install codex",
"brew install --cask claude-code",
"brew install --cask amazon-q"
]
}
// Re-install the GitHub Actions runner
provisioner "shell" {
script = "scripts/install-actions-runner.sh"
}
// make sure our workaround from base is still valid
provisioner "shell" {
inline = [
"sudo ln -s /Users/admin /Users/runner || true"
"brew install curl wget unzip zip ca-certificates",
"sudo softwareupdate --install-rosetta --agree-to-license"
]
}
provisioner "shell" {
inline = [
"source ~/.zprofile",
"brew install openjdk@17",
"echo 'export PATH=\"/opt/homebrew/opt/openjdk@17/bin:$PATH\"' >> ~/.zprofile",
"brew install homebrew/cask-versions/temurin11",
"echo 'export ANDROID_HOME=$HOME/android-sdk' >> ~/.zprofile",
"echo 'export ANDROID_SDK_ROOT=$ANDROID_HOME' >> ~/.zprofile",
"echo 'export PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator' >> ~/.zprofile",
@ -133,140 +76,28 @@ build {
"rm android-sdk-tools.zip",
"mv $ANDROID_HOME/cmdline-tools/cmdline-tools $ANDROID_HOME/cmdline-tools/latest",
"yes | sdkmanager --licenses",
"yes | sdkmanager 'platform-tools' 'platforms;android-36' 'build-tools;36.0.0' 'ndk;28.2.13676358'"
"yes | sdkmanager 'platform-tools' 'platforms;android-33' 'build-tools;33.0.1' 'ndk;25.1.8937393'"
]
}
provisioner "shell" {
inline = [
"echo 'export PATH=/usr/local/bin/:$PATH' >> ~/.zprofile",
"source ~/.zprofile",
"brew install xcodes",
"wget --quiet https://github.com/RobotsAndPencils/xcodes/releases/latest/download/xcodes.zip",
"unzip xcodes.zip",
"rm xcodes.zip",
"chmod +x xcodes",
"sudo mkdir -p /usr/local/bin/",
"sudo mv xcodes /usr/local/bin/xcodes",
"xcodes version",
]
}
provisioner "file" {
sources = [for version in var.xcode_version : pathexpand("~/XcodesCache/Xcode_${version}.xip")]
destination = "/Users/admin/Downloads/"
}
provisioner "shell" {
inline = [
"source ~/.zprofile",
"df -h",
]
}
// iterate over all Xcode versions and install them
// select the latest one as the default
dynamic "provisioner" {
for_each = local.xcode_install_provisioners
labels = ["shell"]
content {
inline = provisioner.value.inline
}
}
dynamic "provisioner" {
for_each = length(var.xcode_version) > 2 ? [2] : []
labels = ["shell"]
content {
inline = [
"source ~/.zprofile",
"sudo xcode-select -s /Applications/Xcode_${var.xcode_version[2]}.app/Contents/Developer",
"xcodebuild -downloadAllPlatforms",
]
}
}
dynamic "provisioner" {
for_each = length(var.xcode_version) > 1 ? [1] : []
labels = ["shell"]
content {
inline = [
"source ~/.zprofile",
"sudo xcode-select -s /Applications/Xcode_${var.xcode_version[1]}.app/Contents/Developer",
"xcodebuild -downloadAllPlatforms",
]
}
}
provisioner "shell" {
inline = [
"source ~/.zprofile",
"sudo xcode-select -s /Applications/Xcode_${var.xcode_version[0]}.app/Contents/Developer",
"wget --quiet https://storage.googleapis.com/xcodes-cache/Xcode_${var.xcode_version}.xip",
"xcodes install ${var.xcode_version} --experimental-unxip --path $PWD/Xcode_${var.xcode_version}.xip",
"sudo rm -rf ~/.Trash/*",
"xcodes select ${var.xcode_version}",
"xcodebuild -downloadAllPlatforms",
"xcodebuild -runFirstLaunch",
]
}
provisioner "shell" {
inline = concat(
["source ~/.zprofile"],
[
for runtime in var.additional_ios_builds : "xcodebuild -downloadPlatform iOS -buildVersion ${runtime}"
]
)
}
provisioner "shell" {
inline = concat(
["source ~/.zprofile"],
[
for runtime in var.additional_tvos_builds : "xcodebuild -downloadPlatform tvOS -buildVersion ${runtime}"
]
)
}
provisioner "shell" {
inline = concat(
["source ~/.zprofile"],
[
for component in var.xcode_components : "xcodebuild -downloadComponent ${component}"
]
)
}
provisioner "shell" {
inline = [
"source ~/.zprofile",
"brew install libimobiledevice ideviceinstaller ios-deploy carthage",
"brew install xcbeautify swiftformat swiftlint swiftgen licenseplist",
"brew install mint",
"git clone --depth 1 https://github.com/tuist/homebrew-tuist.git \"$(brew --repository)/Library/Taps/tuist/homebrew-tuist\"",
"rm -rf \"$(brew --repository)/Library/Taps/tuist/homebrew-tuist/Casks\"",
"tuist_version=$(ruby -ne 'if $_ =~ %r{/download/([^/]+)/}; puts $1; exit; end' \"$(brew --repository)/Library/Taps/tuist/homebrew-tuist/Aliases/tuist\") && brew trust --formula \"tuist/tuist/tuist@$tuist_version\" && brew install --formula \"tuist/tuist/tuist@$tuist_version\"",
"gem update",
"gem install fastlane",
"gem install cocoapods",
"gem install xcpretty",
"gem uninstall --ignore-dependencies ffi && gem install ffi -- --enable-libffi-alloc"
]
}
// Copy expected runtimes file if provided
dynamic "provisioner" {
for_each = var.expected_runtimes_file != "" ? [1] : []
labels = ["file"]
content {
source = var.expected_runtimes_file
destination = "/Users/admin/runtimes.expected.txt"
}
}
// Verify simulator runtimes match expected list if file was provided
dynamic "provisioner" {
for_each = var.expected_runtimes_file != "" ? [1] : []
labels = ["shell"]
content {
inline = [
"source ~/.zprofile",
"xcrun simctl list runtimes > /Users/admin/runtimes.actual.txt",
"diff -q /Users/admin/runtimes.actual.txt /Users/admin/runtimes.expected.txt || (echo 'Simulator runtimes do not match expected list' && cat /Users/admin/runtimes.actual.txt && exit 1)",
"rm /Users/admin/runtimes.actual.txt /Users/admin/runtimes.expected.txt"
]
}
}
provisioner "shell" {
inline = [
"source ~/.zprofile",
@ -281,110 +112,34 @@ build {
"flutter precache",
]
}
# useful utils for mobile development
provisioner "shell" {
inline = [
"source ~/.zprofile",
"brew install graphicsmagick imagemagick",
"brew install wix/brew/applesimutils",
"brew install gnupg"
"brew install libimobiledevice ideviceinstaller ios-deploy fastlane carthage",
"sudo gem update",
"sudo gem install cocoapods",
"sudo gem uninstall --ignore-dependencies ffi && sudo gem install ffi -- --enable-libffi-alloc"
]
}
# inspired by https://github.com/actions/runner-images/blob/fb3b6fd69957772c1596848e2daaec69eabca1bb/images/macos/provision/configuration/configure-machine.sh#L33-L61
provisioner "shell" {
inline = [
"source ~/.zprofile",
"sudo security delete-certificate -Z FF6797793A3CD798DC5B2ABEF56F73EDC9F83A64 /Library/Keychains/System.keychain",
"curl -o add-certificate.swift https://raw.githubusercontent.com/actions/runner-images/fb3b6fd69957772c1596848e2daaec69eabca1bb/images/macos/provision/configuration/add-certificate.swift",
"swiftc add-certificate.swift",
"curl -o AppleWWDRCAG3.cer https://www.apple.com/certificateauthority/AppleWWDRCAG3.cer",
"curl -o DeveloperIDG2CA.cer https://www.apple.com/certificateauthority/DeveloperIDG2CA.cer",
"curl -o add-certificate.swift https://raw.githubusercontent.com/actions/runner-images/fb3b6fd69957772c1596848e2daaec69eabca1bb/images/macos/provision/configuration/add-certificate.swift",
"swiftc -suppress-warnings add-certificate.swift",
"sudo ./add-certificate AppleWWDRCAG3.cer",
"sudo ./add-certificate DeveloperIDG2CA.cer",
"rm add-certificate* *.cer"
]
}
provisioner "shell" {
inline = [
"source ~/.zprofile",
"brew doctor",
"flutter doctor"
]
}
// check there is at least 15GB of free space and fail if not
provisioner "shell" {
inline = [
"source ~/.zprofile",
"df -h",
"export FREE_MB=$(df -m | awk '{print $4}' | head -n 2 | tail -n 1)",
"[[ $FREE_MB -gt ${var.disk_free_mb} ]] && echo OK || exit 1"
]
}
// some other health checks
provisioner "shell" {
inline = [
"source ~/.zprofile",
"test -d /Users/runner"
]
}
# Disable apsd[1][2] daemon as it causes high CPU usage after boot
#
# [1]: https://iboysoft.com/wiki/apsd-mac.html
# [2]: https://discussions.apple.com/thread/4459153
provisioner "shell" {
inline = [
"sudo launchctl unload -w /System/Library/LaunchDaemons/com.apple.apsd.plist"
]
}
# Wait for the "update_dyld_sim_shared_cache" process[1][2] to finish
# to avoid wasting CPU cycles after boot
#
# [1]: https://apple.stackexchange.com/questions/412101/update-dyld-sim-shared-cache-is-taking-up-a-lot-of-memory
# [2]: https://stackoverflow.com/a/68394101/9316533
provisioner "shell" {
inline = [
"source ~/.zprofile",
"xcrun simctl runtime dyld_shared_cache update --all || sleep 180",
"xcrun simctl list -v"
]
}
# Compatibility with GitHub Actions Runner Images, where
# /usr/local/bin belongs to the default user. Also see [2].
#
# [1]: https://github.com/actions/runner-images/blob/6bbddd20d76d61606bea5a0133c950cc44c370d3/images/macos/scripts/build/configure-machine.sh#L96
# [2]: https://github.com/actions/runner-images/discussions/7607
provisioner "shell" {
inline = [
"sudo chown admin /usr/local/bin"
]
}
// Install setup-info-generator
provisioner "shell" {
inline = [
"source ~/.zprofile",
"brew install cirruslabs/cli/setup-info-generator"
]
}
// Copy setup info template
provisioner "file" {
source = "data/setup-info-template.json"
destination = "~/setup-info-template.json"
}
// Generate setup info
provisioner "shell" {
inline = [
"source ~/.zprofile",
"cat ~/setup-info-template.json | setup-info-generator > ~/actions-runner/.setup_info",
"rm ~/setup-info-template.json"
]
}
}

3
variables.pkrvars.hcl Normal file
View File

@ -0,0 +1,3 @@
macos_version = "ventura"
gha_version = "2.303.0"
xcode_version = "14.2"