tools/tart_macos/README.md
Use this skill when preparing the reusable Tart base VM used by frb-dev-env tart ....
The reusable base VM is a clean local VM built by Packer from a pinned Tart OCI artifact:
ghcr.io/cirruslabs/macos-sonoma-xcode:16.1
-> optional local OCI registry mirror
-> packer build ... frb-tart-base-candidate
-> smoke test candidate
-> promote candidate to frb-tart-base
-> tart clone frb-tart-base frb-tart-<worktree-hash>
frb-tart-base is the prepared base image. Do not use it for development, do not run tests in it, and do not manually install tools into it. Treat it as immutable after creation. Per-worktree VMs are cloned from it via APFS copy-on-write and can be dirtied, stopped, deleted, and recreated.
Do not boot frb-tart-base and manually install Flutter, Android SDK packages, CocoaPods updates, Rust targets, or other dependencies into it.
Manual changes make the base hard to reproduce and hard to audit. If the base needs different tools, update the Packer template or provision script next to this skill, document the exact source, rebuild a candidate, smoke test it, and then promote it to frb-tart-base. The base VM should answer both questions: "which OCI artifact did this come from?" and "which provision script changed it?"
Use a pinned macOS/Xcode OCI reference. The current known-good source is:
ghcr.io/cirruslabs/macos-sonoma-xcode:16.1
Avoid latest tags. Avoid broad multi-Xcode runner images unless there is a specific reason; they are larger and less reproducible.
If network access to GHCR is reliable enough, Packer can clone the raw source image directly:
cd tools/tart_macos/packer
packer init .
packer build \
-var 'source_vm=ghcr.io/cirruslabs/macos-sonoma-xcode:16.1' \
-var 'target_vm=frb-tart-base-candidate' \
-var 'flutter_version=3.44.0' \
-var 'host_proxy_url=http://192.168.64.1:7897' \
-var 'allow_insecure=false' \
.
Use this only when the direct download path is acceptable. Packer delegates the source VM clone to Tart, so shell proxy environment variables may not control it the way they control CLI tools like curl or crane.
Leave host_proxy_url empty when the Tart guest has reliable direct network access. When using a host proxy, enable LAN access so the mixed proxy port listens on *:7897, then use http://192.168.64.1:7897 from Tart guests. Verify it from a running Tart VM before a long base build:
tart exec <vm-name> /bin/zsh -lc 'curl -I --proxy http://192.168.64.1:7897 --max-time 10 https://pub.dev'
There are two separate network paths during a base rebuild:
source_vm. Use the local registry path below when tart clone ghcr.io/... is unreliable.scripts/provision-frb-tart-base.sh runs inside the candidate VM. host_proxy_url only applies to this guest provisioning phase, including Flutter, Homebrew, Rust, Cargo, and Dart downloads.Use this path when direct tart clone ghcr.io/... is slow, unreliable, or hard to route through the desired proxy. The idea is:
127.0.0.1:5000.crane copy with explicit proxy environment variables to mirror the remote Tart OCI artifact into the local registry.allow_insecure=true and source_vm=127.0.0.1:5000/... to create frb-tart-base-candidate.frb-tart-base.Use a large disk for registry storage:
mkdir -p /Volumes/MyExternal/temp/frb-mobile-registry
docker run -d --name frb-mobile-registry \
-p 127.0.0.1:5000:5000 \
-v /Volumes/MyExternal/temp/frb-mobile-registry:/var/lib/registry \
registry:2
Verify it:
NO_PROXY=127.0.0.1,localhost,::1 no_proxy=127.0.0.1,localhost,::1 \
curl -fsS http://127.0.0.1:5000/v2/
Use 127.0.0.1, not localhost, to avoid IPv6 or host service surprises.
Mirror with crane. If a proxy is needed, set it explicitly for this command:
PROXY_URL=http://localhost:7897
HTTP_PROXY=$PROXY_URL HTTPS_PROXY=$PROXY_URL \
http_proxy=$PROXY_URL https_proxy=$PROXY_URL \
ALL_PROXY=$PROXY_URL all_proxy=$PROXY_URL \
NO_PROXY=localhost,127.0.0.1,::1 no_proxy=localhost,127.0.0.1,::1 \
crane --insecure copy --jobs 4 \
ghcr.io/cirruslabs/macos-sonoma-xcode:16.1 \
127.0.0.1:5000/cirruslabs/macos-sonoma-xcode:16.1
For long mirrors, run it in screen and write logs to disk:
PROXY_URL=http://localhost:7897
screen -dmS frb-mobile-crane-copy zsh -lc "HTTP_PROXY=$PROXY_URL HTTPS_PROXY=$PROXY_URL http_proxy=$PROXY_URL https_proxy=$PROXY_URL ALL_PROXY=$PROXY_URL all_proxy=$PROXY_URL NO_PROXY=localhost,127.0.0.1,::1 no_proxy=localhost,127.0.0.1,::1 crane --insecure copy --jobs 4 ghcr.io/cirruslabs/macos-sonoma-xcode:16.1 127.0.0.1:5000/cirruslabs/macos-sonoma-xcode:16.1 >> /Volumes/MyExternal/temp/frb-mobile-crane-copy.log 2>&1"
Check that crane only connects to the intended local proxy, if any, and local registry:
PROXY_PORT=7897
lsof -nP -p <crane-pid> | rg "TCP|127\\.0\\.0\\.1|:${PROXY_PORT}|:5000|ghcr|github"
Expected network shape:
127.0.0.1:<ephemeral> -> 127.0.0.1:<selected-proxy-port> for remote GHCR fetches through the selected proxy.127.0.0.1:<ephemeral> -> 127.0.0.1:5000 for local registry writes.Use an empty Docker config if the host credential helper interferes:
mkdir -p /private/tmp/frb-empty-docker-config
DOCKER_CONFIG=/private/tmp/frb-empty-docker-config \
NO_PROXY=localhost,127.0.0.1,::1 no_proxy=localhost,127.0.0.1,::1 \
crane --insecure digest 127.0.0.1:5000/cirruslabs/macos-sonoma-xcode:16.1
Known digest for the current source image:
sha256:f181b76eede9acd7a6db76438a4cf73e76550c3f9e8104aed16347122e132184
Build a candidate base VM from the local registry:
cd tools/tart_macos/packer
packer init .
packer build \
-var 'source_vm=127.0.0.1:5000/cirruslabs/macos-sonoma-xcode:16.1' \
-var 'target_vm=frb-tart-base-candidate' \
-var 'flutter_version=3.44.0' \
-var 'host_proxy_url=http://192.168.64.1:7897' \
-var 'allow_insecure=true' \
.
The Packer template clones the raw source VM, boots it as admin/admin, runs scripts/provision-frb-tart-base.sh, and leaves a stopped local VM named by target_vm. The current provision step initializes Xcode, checks out the requested Flutter SDK version, installs CocoaPods with Homebrew, installs Rust matching the devcontainer, adds the aarch64-apple-ios-sim and x86_64-apple-ios Rust targets for both the pinned toolchain and the stable-aarch64-apple-darwin toolchain used by CargoKit, and pre-caches Flutter iOS and macOS artifacts. If host_proxy_url is set, the provision step receives standard HTTP_PROXY / HTTPS_PROXY / ALL_PROXY / CARGO_HTTP_PROXY environment variables. It also verifies the image contains Xcode and simulator tooling.
Keep flutter_version in sync with FRB_MAIN_FLUTTER_VERSION in .github/workflows/ci.yaml and the Flutter version in .devcontainer/Dockerfile. Do not rely on the source Tart OCI image's preinstalled Flutter version, because Flutter template drift can change generated scaffold files.
Verify the candidate:
tart list
tart get frb-tart-base-candidate --format json
Do not boot and mutate frb-tart-base during verification. If you need to inspect runtime state, clone a disposable probe VM from the candidate:
tart clone frb-tart-base-candidate frb-tart-probe
tart run --no-graphics frb-tart-probe
tart ip frb-tart-probe --wait 180
tart exec frb-tart-probe /bin/zsh -lc 'sw_vers && xcodebuild -version && flutter --version && flutter --version --machine | python3 -c '"'"'import json,sys; assert json.load(sys.stdin)["frameworkVersion"] == "3.44.0"'"'"' && pod --version && rustc --version && cargo --version && rustup target list --installed | grep aarch64-apple-ios-sim && rustup target list --installed | grep x86_64-apple-ios && rustup target list --toolchain stable-aarch64-apple-darwin --installed | grep aarch64-apple-ios-sim && rustup target list --toolchain stable-aarch64-apple-darwin --installed | grep x86_64-apple-ios'
tart stop frb-tart-probe
tart delete frb-tart-probe
After the candidate passes smoke, promote it by moving the old base out of the way and renaming the candidate:
stamp=$(date +%Y%m%d-%H%M%S)
tart rename frb-tart-base "frb-tart-base-before-packer-$stamp"
tart rename frb-tart-base-candidate frb-tart-base
If there is no existing frb-tart-base, only the second rename is needed.
Local VM-to-VM cloning is cheap because Tart uses APFS copy-on-write for local clones. du may count shared blocks and look large, so prefer df available space when measuring real disk pressure.
Even though local clone is cheap, keep a conservative free-space threshold before creating worktree VMs. frb-dev-env tart create requires at least 150GB free by default because base image setup, caches, build products, and simulator state can grow quickly.