| name | xcode-sync |
| description | Xcode fleet sync: signed archives, compatibility, install, selection, first launch. |
Xcode Sync
Synchronize exact Xcode builds across Peter's supported Macs. Use $remote-mac for fleet topology and SSH rules.
Inventory
- Read
~/Projects/manager/computers.yaml; use live tailscale status --json for reachability/IPs.
- Exclude handed-off and unknown hosts. Verify
hostname, user, macOS, architecture, and hardware UUID before writes.
- Deduplicate Tailscale nodes by hardware UUID; one Mac may have multiple live node records.
- Run
scripts/xcode-host-inventory.sh locally or remotely:
skills/xcode-sync/scripts/xcode-host-inventory.sh
ssh -o RequestTTY=no -o RemoteCommand=none HOST 'bash -s' \
< skills/xcode-sync/scripts/xcode-host-inventory.sh
Treat unreachable hosts as pending, not synchronized. Try live Tailscale IP, Tailscale SSH, then mDNS/LAN only when network topology permits.
Inspect source
Prefer the user's downloaded .xip; do not redownload it.
pkgutil --check-signature "$archive"
shasum -a 256 "$archive"
stage=$(mktemp -d /tmp/xcode.XXXXXX)
cleanup() { rm -rf "$stage"; }
trap cleanup EXIT
(cd "$stage" && xip --expand "$archive")
set -- "$stage"/Xcode*.app
[[ $# == 1 && -d "$1" ]]
app=$1
plutil -extract CFBundleShortVersionString raw -o - "$app/Contents/Info.plist"
plutil -extract ProductBuildVersion raw -o - "$app/Contents/version.plist"
plutil -extract LSMinimumSystemVersion raw -o - "$app/Contents/Info.plist"
DEVELOPER_DIR="$app/Contents/Developer" xcodebuild -version
cleanup
trap - EXIT
Require Apple Software signature. Compare ProductBuildVersion, not version label alone: two archives named Xcode 26.6 may contain different builds. Do not use DTXcodeBuild as the sync key; it can differ from the build reported by xcodebuild -version.
Compatibility
- Require Apple silicon for an Apple-silicon-only archive.
- Require host macOS >=
LSMinimumSystemVersion.
- Fleet rule: install Xcode 26.6 only on macOS 26 Tahoe, version 26.2 or newer. Skip macOS 27 Golden Gate even if the bundle launches.
- Apply explicit user exclusions after technical checks.
Never change a host OS to make an Xcode build eligible unless explicitly requested.
Transfer and install
- Transfer the signed archive, not an expanded app; preserve resumability:
rsync -a --partial --progress -e 'ssh -o RequestTTY=no -o RemoteCommand=none' \
"$archive" HOST:Downloads/
- Verify the remote SHA-256 and signature before expansion.
- Expand on the destination. Use a single-quoted remote script or
ssh HOST 'bash -s'; never let the local shell expand remote $variables or $(commands).
- Keep this app policy:
- current stable:
/Applications/Xcode.app
- newest prerelease, beta or RC:
/Applications/Xcode-beta.app
- previous-major stable:
/Applications/Xcode-previous.app, only for three months after a new stable major ships unless the user sets another window
- Replace same-major point releases and same-channel prereleases; do not preserve them. An RC replaces the beta slot. Validate the staged app, move the old app to a temporary rollback path, install and verify the new app, then delete the rollback copy. Restore the old app on failure.
- When stable advances to a new major, rotate transactionally: move any existing
Xcode-previous.app to a temporary rollback path, move the former stable to Xcode-previous.app, install and verify the new stable, then delete the older rollback copy. Restore both channel paths on failure. Record the new previous-major removal date in the task report.
- Stop on unexpected destination collisions. Never delete an app outside these known channels without explicit confirmation.
- Preserve
xcode-select unless the user requests a switch. Replacing the app at the already-selected path preserves selection.
Use writable /Applications directly. Otherwise use passwordless sudo -n; if admin approval is required, show a local macOS authorization prompt or report the exact pending step. Do not bypass receipts or license state.
First launch and verification
For every installed app:
DEVELOPER_DIR="$app/Contents/Developer" xcodebuild -version
codesign --verify --deep --strict "$app"
DEVELOPER_DIR="$app/Contents/Developer" xcodebuild -checkFirstLaunchStatus
If first-launch status is nonzero:
sudo env DEVELOPER_DIR="$app/Contents/Developer" xcodebuild -license accept
sudo env DEVELOPER_DIR="$app/Contents/Developer" xcodebuild -runFirstLaunch
Recheck until status 0. If sudo/admin UI is unavailable, the app is installed but not ready; report that distinction.
Finish with a host matrix: macOS, desired version/build, installed path, selected path, signature, first-launch state, previous-major removal date, and skip/failure reason. Keep source archives unless deletion is explicitly requested.