Compare commits

..

No commits in common. "main" and "14.3-beta-3" have entirely different histories.

35 changed files with 192 additions and 2579 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,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,381 +0,0 @@
name: Template Builds
on:
pull_request:
paths:
- ".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: 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
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 '^(templates/base\.pkr\.hcl|templates/disable-sip.*\.pkr\.hcl|data/(github_known_hosts|limit\.maxfiles\.plist|setup-info-template\.json|tart-guest-.*\.plist)|scripts/(install-actions-runner|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,25 +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-{tahoe,sequoia,sonoma}-base` — based on `macos-{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
## Release Cadence
To build `macos-monterey-vanilla`:
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 templates/vanilla-monterey.pkr.hcl
```
## Update Cadence
To build `macos-ventura-vanilla`:
Some of the images are regularly getting rebuild in order to update the pre-installed packages.
```bash
packer build templates/vanilla-ventura.pkr.hcl
```
[This workflow](.github/workflows/monthly.yml) defines images that are getting rebuilt monthly on the first Saturday of the month.
Optionally, SIP can be disabled for each image by running the following commands:
```bash
packer build -var vm_name=monterey-vanilla templates/disable-sip.pkr.hcl
```
```bash
packer build -var vm_name=ventura-vanilla templates/disable-sip.pkr.hcl
```
## Building Base Image
```bash
packer build -var-file="variables.pkrvars.hcl" templates/base.pkr.hcl
```
## 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,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,60 +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
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
}
# 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 "${HOME}/Library/Application Support/com.apple.TCC/TCC.db"

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.2.1"
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,135 +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"
]
}
// 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/fullrestores/140-55718/5809AFC6-1923-4590-AAFC-904A0283E659/UniversalMac_27.0_26A5388g_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-83079/25315EF6-AEAB-4588-9774-A3723774C47F/UniversalMac_26.6.1_25G76_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/2023WinterFCS/fullrestores/032-48346/EFF99C1E-C408-4E7A-A448-12E1468AF06C/UniversalMac_13.2.1_22D68_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 {
@ -101,7 +88,7 @@ build {
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\"",
"echo admin | sudo -S sh -c \"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.
@ -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"