Compare commits

..

16 Commits

Author SHA1 Message Date
edi-oai d6ee41d6d5
Install Tart Guest Agent from OpenAI tap (#370) 2026-08-14 22:34:20 -04:00
Fedor Kororkov 741758b9ba
Use ASIF for Golden Gate vanilla image (#369) 2026-08-14 14:30:54 -04:00
Jiawen Geng f03344599b
Pre-install pnpm alongside yarn in base image (#357)
* Pre-install pnpm alongside yarn in base image

Install pnpm globally in the Node.js provisioner so base images
include both common package managers.

Assisted-by: Cursor Agent

Co-authored-by: Jiawen Geng <technicalcute@gmail.com>

* Install yarn and pnpm in a single npm command

Assisted-by: Cursor Agent

Co-authored-by: Jiawen Geng <technicalcute@gmail.com>

* Configure PNPM_HOME in the base image shell profile

Assisted-by: Cursor Agent

Co-authored-by: Jiawen Geng <technicalcute@gmail.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-08-14 14:02:34 -04:00
Nikolay Edigaryev 16e9ad7d00 Pass "--provisioning-opts" to "tart run" to simplify installation 2026-08-13 15:04:29 +01:00
Nikolay Edigaryev 85b77e41bc Produce macOS Golden Gate vanilla image 2026-08-13 15:04:29 +01:00
Fedor Kororkov 9be4b9dc86
Fix base image automation microphone permissions (#367) 2026-08-11 21:45:23 -04:00
Fedor Kororkov 46050816d8
Update release.yml 2026-08-07 19:55:39 -04:00
Fedor Kororkov d358da6f50
Use base image Ruby for Xcode builds (#366) 2026-08-07 19:54:31 -04:00
Fedor Kororkov b4926493fb
Update Ruby for Xcode 27 image builds (#365) 2026-08-07 14:24:09 -04:00
Fedor Kororkov c166ec1698
Merge pull request #364 from cirruslabs/dev/fkorotkov/tahoe-26-6-1-automation-permissions
Update Tahoe restore image and automation permissions
2026-08-07 10:38:00 -04:00
Fedor Korotkov 3d6445b446
Update Tahoe restore image and automation permissions 2026-08-07 10:24:00 -04:00
Fedor Kororkov f5a7e1e631
Merge pull request #363 from cirruslabs/dev/fkorotkov/fix-monthly-buildkite-tap-trust
Fix Homebrew 6 trust failure in monthly image builds
2026-08-06 12:20:10 -04:00
Fedor Korotkov 0a9ad1a419
Fix Buildkite formula trust in monthly builds 2026-08-06 08:36:12 -04:00
Fedor Korotkov cd2d1c6698 Decouple runner release builds from Xcode releases 2026-07-05 12:00:02 -04:00
Fedor Korotkov 65519d3874
Remove dependency on release-xcode for runner image 2026-07-05 11:57:09 -04:00
Fedor Korotkov 71b405bd0c
Add Tahoe runner Xcode 26.6 and beta 2 (#356)
* Add Tahoe runner Xcode 26.6 and beta 2

* Add PR template validation workflow

* Install Ansible for template validation

* Build template images in PR workflow

* Authenticate Packer plugin downloads

* Prepare Xcode archives for PR builds

* Support authenticated Xcode archive downloads

* Use Xcode 27 beta 2 version token

* Increase Tahoe runner disk size

* Select Xcode apps by path during runner builds

* Update Homebrew before Tuist install

* Avoid Tuist cask validation during image builds

* Trust Tuist formula during image builds

* Add Tahoe iOS 27 beta runtime expectation
2026-07-04 22:21:50 -04:00
10 changed files with 545 additions and 39 deletions

View File

@ -89,7 +89,8 @@ jobs:
release-runner:
name: Update Runner Image (${{ matrix.macos_version }})
needs: release-xcode
# 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:
@ -104,11 +105,11 @@ jobs:
xcode_components: '"MetalToolchain"'
disk_size: 380
- macos_version: tahoe
xcode_versions: '"26.5","27-beta","26.4.1","26.3","26.2","26.1.1","26.0.1"'
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: 330
disk_size: 520
env:
ADDITIONAL_IOS_BUILDS: ${{ matrix.additional_ios_builds }}
ADDITIONAL_TVOS_BUILDS: ${{ matrix.additional_tvos_builds }}

View File

@ -0,0 +1,381 @@
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

@ -13,6 +13,7 @@ on:
type: choice
options:
- all
- golden-gate
- tahoe
- sequoia
- sonoma
@ -46,6 +47,7 @@ jobs:
max-parallel: 1
matrix:
macos_version:
- golden-gate
- tahoe
- sequoia
- sonoma

View File

@ -5,7 +5,7 @@ GitHub Actions runners, [Cirrus Runners](https://cirrus-runners.app/) or [any ot
The following image variants are currently available:
* `macos-{tahoe,sequoia,sonoma}-vanilla` — a vanilla macOS installation with helpful tweaks such as auto-login, but no additional software preinstalled
* `macos-{golden-gate,tahoe,sequoia,sonoma}-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.

View File

@ -5,12 +5,11 @@ 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
tvOS 26.2 (26.2 - 23K51) - com.apple.CoreSimulator.SimRuntime.tvOS-26-2
tvOS 26.4 (26.4 - 23L243a) - com.apple.CoreSimulator.SimRuntime.tvOS-26-4
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
watchOS 26.2 (26.2 - 23S303) - com.apple.CoreSimulator.SimRuntime.watchOS-26-2
watchOS 26.4 (26.4 - 23T240b) - com.apple.CoreSimulator.SimRuntime.watchOS-26-4
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
visionOS 26.2 (26.2 - 23N301) - com.apple.CoreSimulator.SimRuntime.xrOS-26-2
visionOS 26.4 (26.4.1 - 23O249a) - com.apple.CoreSimulator.SimRuntime.xrOS-26-4
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

@ -13,7 +13,10 @@ source ~/.zprofile
set -euo pipefail
update_tcc_database() {
sudo sqlite3 "$1" <<-'EOF'
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,
@ -36,7 +39,17 @@ update_tcc_database() {
('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');
('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
}

View File

@ -64,7 +64,7 @@ build {
"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",
"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",
@ -121,8 +121,12 @@ build {
"echo 'export PATH=\"/opt/homebrew/opt/node@24/bin:$PATH\"' >> ~/.zprofile",
"source ~/.zprofile",
"node --version",
"npm install --global yarn",
"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" {
@ -164,7 +168,7 @@ build {
inline = [
# Install Tart Guest Agent
"source ~/.zprofile",
"brew install cirruslabs/cli/tart-guest-agent",
"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",

View File

@ -0,0 +1,107 @@
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

@ -6,13 +6,13 @@ packer {
}
ansible = {
version = "~> 1"
source = "github.com/hashicorp/ansible"
source = "github.com/hashicorp/ansible"
}
}
}
source "tart-cli" "tart" {
from_ipsw = "https://updates.cdn-apple.com/2026SpringFCS/fullrestores/122-58869/DFB1CEEF-5619-4591-9924-E20DB2C8FED0/UniversalMac_26.5_25F71_Restore.ipsw"
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

View File

@ -16,39 +16,39 @@ variable "xcode_version" {
}
variable "additional_ios_builds" {
type = list(string)
type = list(string)
default = []
}
variable "additional_tvos_builds" {
type = list(string)
type = list(string)
default = []
}
variable "xcode_components" {
type = list(string)
default = []
type = list(string)
default = []
description = "Additional Xcode components to download."
}
variable "expected_runtimes_file" {
type = string
default = ""
type = string
default = ""
description = "Path to file containing expected simulator runtimes. If empty, runtime verification is skipped."
}
variable "tag" {
type = string
type = string
default = ""
}
variable "disk_size" {
type = number
type = number
default = 140
}
variable "disk_free_mb" {
type = number
type = number
default = 15000
}
@ -146,7 +146,7 @@ build {
}
provisioner "file" {
sources = [ for version in var.xcode_version : pathexpand("~/XcodesCache/Xcode_${version}.xip")]
sources = [for version in var.xcode_version : pathexpand("~/XcodesCache/Xcode_${version}.xip")]
destination = "/Users/admin/Downloads/"
}
@ -161,7 +161,7 @@ build {
// select the latest one as the default
dynamic "provisioner" {
for_each = local.xcode_install_provisioners
labels = ["shell"]
labels = ["shell"]
content {
inline = provisioner.value.inline
}
@ -169,11 +169,11 @@ build {
dynamic "provisioner" {
for_each = length(var.xcode_version) > 2 ? [2] : []
labels = ["shell"]
labels = ["shell"]
content {
inline = [
"source ~/.zprofile",
"sudo xcodes select '${var.xcode_version[2]}'",
"sudo xcode-select -s /Applications/Xcode_${var.xcode_version[2]}.app/Contents/Developer",
"xcodebuild -downloadAllPlatforms",
]
}
@ -181,11 +181,11 @@ build {
dynamic "provisioner" {
for_each = length(var.xcode_version) > 1 ? [1] : []
labels = ["shell"]
labels = ["shell"]
content {
inline = [
"source ~/.zprofile",
"sudo xcodes select '${var.xcode_version[1]}'",
"sudo xcode-select -s /Applications/Xcode_${var.xcode_version[1]}.app/Contents/Developer",
"xcodebuild -downloadAllPlatforms",
]
}
@ -194,7 +194,7 @@ build {
provisioner "shell" {
inline = [
"source ~/.zprofile",
"sudo xcodes select '${var.xcode_version[0]}'",
"sudo xcode-select -s /Applications/Xcode_${var.xcode_version[0]}.app/Contents/Developer",
"xcodebuild -downloadAllPlatforms",
]
}
@ -232,10 +232,9 @@ build {
"brew install libimobiledevice ideviceinstaller ios-deploy carthage",
"brew install xcbeautify swiftformat swiftlint swiftgen licenseplist",
"brew install mint",
"brew tap tuist/tuist",
"brew install --formula tuist",
"rbenv install 3.3.10",
"rbenv global 3.3.10", # fastlane conflicts with 3.4.0+ https://github.com/fastlane/fastlane/issues/29527
"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",
@ -247,7 +246,7 @@ build {
// Copy expected runtimes file if provided
dynamic "provisioner" {
for_each = var.expected_runtimes_file != "" ? [1] : []
labels = ["file"]
labels = ["file"]
content {
source = var.expected_runtimes_file
destination = "/Users/admin/runtimes.expected.txt"
@ -257,7 +256,7 @@ build {
// Verify simulator runtimes match expected list if file was provided
dynamic "provisioner" {
for_each = var.expected_runtimes_file != "" ? [1] : []
labels = ["shell"]
labels = ["shell"]
content {
inline = [
"source ~/.zprofile",