MacVisor Beta

Using the CLI

Every mvz command by task (creating VMs, lifecycle, shells and containers, templates and disks, networking, housekeeping) with scripting examples.

mvz is MacVisor's command-line tool. It talks to the same background service as the app, so both see one library and one set of running VMs. Everything the app does, a script can do.

Getting the CLI

The network helper links both names into /usr/local/bin at first-run setup and repoints them when the app moves or updates:

/usr/local/bin/mvz      → /Applications/MacVisor.app/Contents/Helpers/macvisor
/usr/local/bin/macvisor → /Applications/MacVisor.app/Contents/Helpers/macvisor

If you declined the helper, run /Applications/MacVisor.app/Contents/Helpers/macvisor, or link it later from Settings → Setup → Link Command Line Tool.

mvz help lists every command; mvz help <command> shows its options and examples. mvz version prints the MacVisor version.

Naming things

<vm> is a VM's name, its id, or the first 4 or more characters of the id. Commands that only look, such as show and ip, also take part of a name; commands that change a VM, such as start, set, and rm, need the whole name or an id. A selector that matches more than one VM is refused, and the matches are listed. A snapshot is named by its id, the first 4 or more characters of it, or its exact name. Names are unique on a Mac: create refuses a name that exists, set <vm> --name renames.

--json for scripts

Most commands take --json:

mvz list --json
mvz show dev --json
mvz ip dev --wait            # prints just the IPv4 address

Commands that change something print their result as JSON with --json too, among them rm, images add and images rm, and add and remove under ports, disks, and nics.

Exit status

CodeMeaning
0Success
1Failure, including a VM that is busy or still installing macOS, and a service that restarted mid-request
2Usage error, including an option the command doesn't take
3Not found: no such VM, template, snapshot, or network
4The service is unreachable: not running, or not registered
5Timed out

Status 4 means nothing answered at all: check mvz service status, or open the app, which starts the service and repairs a broken LaunchAgent; see Troubleshooting. Status 5 comes from wait, ip --wait, from shell and exec when SSH has not answered within two minutes (ten for a macOS guest), and from any command the service doesn't answer in time. A busy service may still be doing what you asked, so check mvz tasks before trying again; the same goes for status 1 with "MacVisorService stopped while handling the request".

Every command refuses an option it doesn't take, so a typo such as start dev --headles fails with status 2 instead of opening a window.

Without a license on this Mac, create, clone, import, and images pull fail with a message saying so; everything else works and existing VMs are unaffected. The background service keeps the license and makes the weekly check with Polar whether or not the app is open, so a Mac you drive only with mvz can be activated and stays licensed:

mvz license                      # status: edition, key, last check
mvz license activate [<key>|-]   # asks in a terminal; - reads standard input
mvz license refresh              # check with Polar now
mvz license deactivate [--yes]   # release this Mac so the key can move

Every form takes --json. See Activate your license.

Shell completion

mvz completion <zsh|bash|fish>

Add eval "$(mvz completion zsh)" to ~/.zshrc, eval "$(mvz completion bash)" to ~/.bashrc, or mvz completion fish | source to ~/.config/fish/config.fish. The scripts complete both mvz and macvisor.

Creating VMs

From a Linux cloud image

mvz create <image> <name> [options]

<image> is a catalogue entry (ubuntu, debian-13, fedora…; mvz images lists them), a cloud-image template, a disk image file (qcow2, raw, or .xz), or an https URL. The image becomes a template on first use and the VM is a linked clone of it, set up by cloud-init on first boot. Cloud images lists the catalogue and what cloud-init sets up.

OptionEffect
--cpus <n>Virtual CPUs (default 4)
--memory <GiB>Memory (default 4)
--disk <GiB>Disk size, never smaller than the image (default 64)
--ssh-key <file>Authorise this public key; repeatable (default: every key in ~/.ssh and your SSH agents)
--no-ssh-keysAuthorise no keys, not even MacVisor's own
--user-data <file>Your own #cloud-config or script, applied after MacVisor's
--runtime <name>containerd, docker, podman, k3s, or none (default), where the image offers it; see Runtimes by distribution
--homeShare your home folder read-only, at the same path (default: not shared)
--project <dir>Share this folder read-write, at the same path
--rosetta, --no-rosettaRun x86-64 Linux programs with Rosetta (default: when installed)
--no-forwardDon't forward the guest's ports to this Mac automatically
--network <id|nat>A custom network's id, or nat (default: Default MacVisor Network)
--no-agentDon't install the MacVisor guest agent
--nested, --no-nestedNested virtualization in the guest (default: on, on an M3 or later Mac)
--balloon, --no-balloonA memory balloon device (default: on)
--location <id>Storage location (default: the default location; see locations)
--jsonPrint the result as JSON
mvz create ubuntu dev
mvz create ubuntu dev --runtime containerd --home
mvz create fedora-44 build --cpus 8 --memory 16 --runtime podman
mvz create debian dev2 --project ~/src/app --user-data cloud.yaml
mvz create ubuntu k8s --runtime k3s --memory 8

Nothing is installed or shared unless you ask: without --runtime the VM has no container runtime, and without --home or --project it has no shared folders. create doesn't remember your choices the way the New VM form does, so give the options every time. --no-home, from scripts written when the home folder was shared by default, is still accepted and does nothing.

create does not start the VM. mvz shell <vm> starts it on demand, or use start and wait.

The guest account has no password, so an SSH key is the only way in. When create finds no key in ~/.ssh or an SSH agent, it asks whether to make ~/.ssh/id_ed25519 (no passphrase), the key ssh uses by default. Without one (you said no, or create ran without a terminal or with --json), it authorises MacVisor's own key, ~/.macvisor/ssh/id_ed25519, which shell and exec use; plain ssh and editors don't. With --no-ssh-keys nothing can sign in unless your --user-data sets that up.

Every runtime works on every catalogue image except k3s on Oracle Linux, Docker Engine on Oracle Linux 10, and Podman and k3s on Alpine. create refuses those, and the message lists the runtimes the image offers.

From a macOS installer

mvz create <macos-installer|file.ipsw> <name> [--cpus <n>] [--memory <GiB>] [--disk <GiB>] [--user <name> [--ssh]]

A catalogue installer (macos-27, macos-26, macos-15, macos-14, or macos-latest for the newest this Mac supports) is downloaded from Apple once and installed into a new VM: 8 GiB of memory by default, 20–30 minutes, visible in mvz tasks. A local .ipsw works too. Give macOS guests 4 or more CPUs. create returns once the install has started; mvz wait <vm> --ssh follows it, then starts the VM and waits for SSH (see Waiting for a VM).

macOS 27 guests skip Setup Assistant: the account is mac with password macvisor unless --user says otherwise. With --ssh, MacVisor then installs the agent and authorises your SSH keys over SSH, which needs the Local Network permission. Older guests go through Setup Assistant by hand. See Create VMs and templates.

OptionEffect
--user <name>The account automatic setup creates (default mac, password macvisor)
--password-file <file>Read that account's password from a file instead of asking
--no-setupDon't set up an account; go through Setup Assistant
--sshTurn on Remote Login, for shell and exec
--no-autologinDon't sign in automatically
mvz create macos-26 tahoe
mvz create macos-latest ci --user builder --ssh
mvz create ~/Downloads/UniversalMac_27.0_Restore.ipsw seq

From a template

mvz create <template> <name>
mvz template <vm> [on|off]

A template you converted from a VM is copied as it is; the create options don't apply. template <vm> on turns a shut-down VM into a template, off makes it runnable again. on is refused for a suspended VM, whose disk was never written out: resume it and shut it down, or discard its saved state, first. See Create VMs and templates.

Images and installers

mvz images                                   # templates, Linux images and macOS installers (alias: templates)
mvz images pull <image> [--location <id>]    # fetch ahead of first use
mvz images add <file>                        # qcow2, raw, .xz, or .ipsw
mvz images rm <template|installer>
  • pull fetches a catalogue entry, URL, or disk image as a template, or a macOS installer for later. For "latest" entries (Ubuntu, Debian, Rocky Linux, AlmaLinux) it fetches a newer vendor build if there is one; create just uses the template it has.
  • add turns a disk image you already have into a template; an .ipsw joins the installers after a check that it is a restore image.
  • rm trashes a template (refused while VMs are made from it) or, given an installer id such as macos-26, the IPSW.
  • An installer you pull or add stays until you rm it. One that create downloaded goes to the Trash once a newer build of the same macOS version is here.
  • Interrupted or cancelled downloads, and ones that ran out of disk space, keep what they got; the next pull or create carries on.
mvz images pull debian-13
mvz images pull macos-26
mvz images add ~/Downloads/UniversalMac_26.6.2_25G83_Restore.ipsw
mvz images rm "Fedora 43"
mvz images rm macos-26

Where VMs live

mvz locations                                # list storage locations
mvz move <vm> <location>                     # a running VM is suspended, moved and resumed

create, images pull, clone, and import take --location <id>. See Storage and sharing.

Lifecycle

mvz list                                     # alias: ls
mvz show <vm>                                # alias: info
mvz start <vm> [--headless] [--recovery] [--discard-state]
mvz stop <vm>                                # ask the guest to shut down
mvz poweroff <vm>                            # turn it off at once
mvz restart <vm>
mvz pause <vm>                               # freeze it in memory
mvz resume <vm> [--discard-state]            # a paused or suspended VM
mvz suspend <vm>                             # save its state to disk and stop
mvz discard-state <vm>                       # drop a suspended VM's saved state
mvz open <vm>                                # show a running VM's window
mvz autostart <vm> [on|off]                  # start it when you log in
mvz rm <vm>                                  # move a VM or template to the Trash (alias: delete)
  • start --headless opens no window; --recovery starts a macOS VM in recoveryOS. --recovery is refused for a suspended VM: resume it and shut it down, or discard the saved session, first.
  • A suspended VM resumes its session. macOS allows that only while you are logged in at the Mac with the screen unlocked; over SSH with the screen locked, start and resume say so and keep the saved session, so unlock the Mac (or use Screen Sharing) and resume again. If a resume fails for another reason, show says why and --discard-state boots from the disk. See Run and control VMs.
  • autostart on has the background service start the VM at each login, whether or not the app opens. Whether it then boots fresh or resumes depends on what VMs do at logout: mvz service at-logout, under Housekeeping.
  • rm powers a running VM off first and is refused while other VMs read its disk; delete linked clones before their source.

Waiting for a VM

mvz wait <vm> [--ssh] [--timeout <seconds>]
mvz ip <vm> [--wait]
  • wait returns once the VM runs, its agent answers, and, for a cloud-image VM, cloud-init has finished. --ssh returns as soon as SSH answers, which can be before cloud-init is done; gate on exec --wait if you need that. wait does not start the VM; it exits 5 after --timeout (default 600 s).
  • A VM that is installing macOS is the exception: wait follows the install, starts the VM without a window once it is done, and then waits as usual. It exits 1 if the install fails. A --timeout you give covers the install too; without one, wait allows 600 seconds from the end of the install.
  • ip shows a VM's addresses. --wait waits up to 10 minutes for an IPv4 address and prints only it; a VM that doesn't exist (status 3) or a service that can't be reached (status 4) fails at once.

Console, screenshot and NVRAM

mvz console <vm>                             # serial console; Control-] detaches
mvz screenshot <vm> <file.jpg>
mvz nvram <vm> [set <name> <value> | rm <name>]
  • console needs Serial Console on in VM Settings → Advanced.
  • screenshot needs the VM's window to have been shown since it started (open).
  • nvram reads or changes a shut-down macOS VM's NVRAM variables (mvz nvram tahoe set boot-args -v). A wrong value can keep the guest from booting. macOS 27 currently refuses the private interface this uses, and MacVisor says so.

Working inside a VM

shell and exec

mvz shell <vm> [--workdir <dir>] [--wait] [--user <name>] [<command>…]     # alias: ssh
mvz exec <vm> [--workdir <dir>] [--wait] [--user <name>] [--] <command>…

shell runs ssh with your keys and the VM's pinned host key, through the agent's virtual socket (or the VM's IP without the agent). It starts the VM if needed and connects as soon as SSH answers, even while cloud-init is still working; --wait waits for cloud-init first. The shell opens in the guest folder matching your current one when that folder is shared. exec is shell with a command; put -- before a command with options of its own.

OptionEffect
--workdir <dir>Start in, or run in, this guest folder
--waitWait for cloud-init to finish first
--no-waitDon't wait (the default)
--user <name>Sign in, or run, as this guest account

A macOS guest needs Remote Login on (--ssh at creation, or the guest's Sharing settings). The first shell asks for the account's password once and authorises your key; a macOS 27 guest with automatic setup has already done that. While macOS is still installing, shell says so and exits with status 1; mvz wait <vm> --ssh waits for the install and the first boot.

mvz shell dev
mvz shell dev uptime
mvz exec dev -- nerdctl run --rm alpine uname -m
mvz exec dev --workdir /tmp -- ls -la

From Terminal, shell and exec are not affected by the Local Network permission the app and VM runner need; Terminal carries its own.

Reaching VMs by name

mvz ssh-config [--install | --uninstall | --print]

MacVisor keeps ~/.macvisor/ssh/config current for every cloud-image and macOS VM, host keys pinned. --install adds one Include line to ~/.ssh/config, after which ssh dev.mvz (or dev.macvisor), scp, and VS Code Remote-SSH work. A ~/.ssh/config that is a symlink, as in a dotfiles repo, stays one: --install and --uninstall edit the file it points to. A config MacVisor can't read and write, or that isn't UTF-8 text, is left as it is, and the command says which line to add or remove yourself. <vm>.mvz and <vm>.macvisor names resolve on the Mac without this; see Networking.

Files

mvz send <vm> <path>…

Copies files or folders into a VM through the agent; they land on the guest's desktop, or in the home folder. Long transfers are tasks. For ongoing access, use a shared folder (--project).

Port forwards

mvz ports list <vm>
mvz ports add <vm> --host-port <port> --guest-port <port> [--udp] [--host <address>] [--name <name>]
mvz ports remove <vm> <forward-id|host-port>

Forwards listen on 127.0.0.1 unless --host says otherwise, go through the agent, and apply immediately. Cloud-image VMs also forward the guest's ports automatically. See Networking.

mvz ports add dev --host-port 8080 --guest-port 80 --name web
mvz ports add dev --host-port 5353 --guest-port 53 --udp

Docker, Podman and Kubernetes

mvz docker <vm> [--use | --remove]
mvz kubeconfig <vm> [--save]
  • docker forwards a running VM's Docker API (Docker Engine or Podman's) to a socket on this Mac through the agent and creates a docker context for it; create does this for Docker and Podman VMs. --use makes it the current context, --remove deletes it.
  • kubeconfig prints a k3s VM's kubeconfig, or --save writes it to ~/.kube/macvisor-<name>.yaml. The API server is forwarded to 127.0.0.1:6443.
mvz create ubuntu web --runtime docker && docker --context macvisor-web ps
mvz docker web --use
mvz create ubuntu k8s --runtime k3s --memory 8
mvz kubeconfig k8s --save && KUBECONFIG=~/.kube/macvisor-k8s.yaml kubectl get nodes

Templates, clones, snapshots and disks

Clone

mvz clone <vm> <name> [--linked | --snapshot <id>] [--location <id>]

A full copy is near-instant on the same APFS volume. --linked shares the source's disk and stores only the clone's changes (source stopped). --snapshot <id> copies the VM as it was then; a clone of a live snapshot starts from the snapshot's disk, as after a power cut, and the running session stays with the original VM. See Linked clones.

A copy gives up what would clash with the VM it came from: start at login is off, port forwards on a host port another VM uses are turned off, disk images another VM writes to are attached read-only, and USB devices another VM captures are dropped. Without --snapshot, clone lists what it changed.

mvz clone dev dev2
mvz clone dev scratch --linked
mvz clone dev before-upgrade --snapshot 1a2b3c4d

Flatten and trim

mvz flatten <vm>                             # make a layered disk one self-contained file
mvz trim <vm>                                # return a running Linux VM's unused disk space to the Mac

flatten copies the blocks a linked clone reads from its base into its own disk. The VM must be stopped with no snapshots; this is also what makes a linked clone exportable.

Snapshots

mvz snapshots list <vm>
mvz snapshots take <vm> [--name <name>] [--notes <text>] [--disk-only]
mvz snapshots revert <vm> <snapshot-id> [--resume]
mvz snapshots delete <vm> <snapshot-id>
mvz snapshots export <vm> <snapshot-id> <folder>

A running VM's snapshot includes its memory unless --disk-only. <snapshot-id> is the id, the first 4 or more characters of it, or the snapshot's exact name. revert --resume continues the saved session, falling back to a boot from the snapshot's disk if it can't. See Snapshots and recovery.

mvz snapshots take dev --name "Before update" --notes "Known-good state"
mvz snapshots revert dev 1a2b3c4d --resume

Disks and settings

mvz set <vm> [--name <name>] [--cpus <n>] [--memory <GiB>] [--disk <GiB>] [--network <network>] [--iso <file|none>]
            [--nested on|off] [--balloon on|off] [--balloon-target <GiB>] [--clipboard both|to-vm|from-vm|off] [--discard-state]
mvz disks list <vm>
mvz disks add <vm> --size <GiB>
mvz disks remove <vm> <disk-id>
  • set with no options shows the settings. CPU, memory, network, --nested, and --balloon apply at the next start. --disk grows the boot disk of a stopped VM (a cloud-image Linux VM grows its filesystem at the next boot); it never shrinks one. --iso attaches a disc image, none detaches it.
  • A suspended VM resumes with the settings it was saved with. CPU, memory, network, nested virtualization, balloon and disc-image changes are saved and apply once the VM is shut down and started again. Only a bigger disk would meet the saved session at resume, so set refuses --disk: add --discard-state to drop the session and make the change, or resume the VM and shut it down first. With --discard-state, the other changes apply at the next start too.
  • --balloon-target changes a running VM's memory target at once, in GiB up to 1024. It stays between a floor (a quarter of the VM's memory, at least 1 GiB, or 4 GiB for macOS) and the VM's memory; the output gives the target applied.
  • --clipboard sets which way the clipboard is shared: both (the default), to-vm (Mac to VM only), from-vm (VM to Mac only), or off; set <vm> shows the current one. It applies at once, even to a running VM, except on a Linux guest using spice-vdagent, where a change to or from both applies at the next start. See Guest tools.
  • disks add and disks remove need the VM shut down, with no suspended session. A new disk is blank.
mvz set dev
mvz set dev --cpus 8 --memory 16
mvz set dev --clipboard to-vm
mvz set dev --disk 128
mvz set dev --disk 128 --discard-state       # a suspended VM: drop its saved session
mvz set <vm> --iso none

Export, import and move

mvz export <vm> <folder>
mvz import <bundle> [--location <id>] [--move]
mvz move <vm> <location>

export copies a VM's bundle to a folder (a linked clone must be flattened first). import adds a .macvisor bundle with a new identity (id, MAC addresses, machine identifier); --move moves instead of copying. move puts a VM in another storage location, suspending and resuming it if it is running; that warm move is refused while the Mac's screen is locked, because the resume needs it unlocked.

A bundle from elsewhere loses the settings that reach outside the VM, and import lists what it changed:

  • shared folders and external disk images, which name another Mac's paths, are removed;
  • USB devices captured automatically are cleared, and start at login is turned off;
  • port forwards and remote access listen on 127.0.0.1 only;
  • an invalid guest account name is fixed;
  • a saved (suspended) session is discarded, because the imported VM has a new identity and starts from its disk;
  • its snapshots become the imported VM's own, and live snapshots lose their memory, keeping their disks.

A bundle whose configuration names disk files outside the bundle is refused, and so is a linked clone whose shared base disk has changed since it was made.

Networking

mvz set <vm> --network nat|bridged|bridged:<interface>|<network>
mvz nics list <vm>
mvz nics add <vm>
mvz nics remove <vm> <nic-id>

--network applies at the next start. Added adapters use NAT; change one in VM Settings → Network. Cloud-image VMs get DHCP on every adapter.

Custom networks

mvz networks
mvz networks create <name> [--host-only] [--subnet <a.b.c.0/24>]
mvz networks rm <network>
mvz networks reserve <network> <vm> <ip>
mvz networks firewall <network> [on|off] [--inbound allow|deny] [--outbound allow|deny] [--allow <rule>]… [--deny <rule>]… [--clear]
  • --subnet is for host-only networks only, and a fixed subnet gets no DHCP from macOS; shared networks cannot pin one on macOS 27. See Addressing.
  • reserve gives a VM a fixed DHCP address; the guest must identify by MAC, which cloud-image VMs do. See DHCP reservations.
  • firewall rules are in|out[:tcp|udp|icmp|any[:<ports>[:<cidr>]]], each part optional from the right. Rules keep the order you type them, --allow and --deny mixed; first match wins, then the direction's default. New rules go after the existing ones; --clear drops those first. off turns the firewall off and keeps the rules. The background service checks and applies the change before the command returns, whether or not the app is open: a rule that can't be enforced as written is refused, and the output lists the numbered rules and says whether they are enforced, and if not, why. mvz networks shows each firewall as on, pending (saved but not in force), or off. See Network firewall.
  • rm is refused while a VM uses the network.
mvz networks create lab
mvz networks create isolated --host-only --subnet 192.168.20.0/24
mvz networks reserve lab dev 192.168.105.50
mvz networks firewall lab on --outbound deny --allow out:tcp:443 --allow out:udp:53
mvz networks firewall lab --clear --deny out:any::10.0.0.0/8 --allow out:tcp:443
mvz networks firewall lab off

In the --clear example the deny comes first, so TCP 443 to 10.x addresses is blocked while 443 anywhere else is allowed.

VLAN interfaces, network-level (vmnet NAT) port forwards, and the per-network DHCP/DNS/NAT/RA switches are set in the app.

Housekeeping

mvz tasks [list] [<vm>]
mvz tasks status <task-id>
mvz tasks cancel <task-id>
mvz service [status]
mvz service logs [--tail <lines>]
mvz service restart
mvz service stop
mvz service install-agent | uninstall-agent | agent-status
mvz service at-logout [shutdown|suspend]     # what running VMs do when the Mac logs out
mvz locations
mvz completion <zsh|bash|fish>
mvz version
mvz help [<command>]
  • Tasks are the long operations the app's task pane shows (downloads, macOS installs, exports, file transfers, flattening), with who started each one. tasks cancel stops one: a cancelled download keeps what it got and stays stopped, even across a service restart, and a cancelled macOS install leaves the VM stopped, for Erase and Install macOS… in the app.
  • service status reports the PID, executable, build, LaunchAgent state, and pendingStorageRoots (locations macOS has not yet let the service read). After an update, if the service still runs an older build than mvz, it says so: run mvz service restart. install-agent, uninstall-agent, and agent-status manage the service's own LaunchAgent, meaning the background service, not the guest agent.
  • service restart and service stop let work in progress (a clone, an export, a download) finish first. If any is running, the command says so and the service restarts or stops by itself when it is done; the launchctl kickstart -k command it prints doesn't wait, and interrupts that work. Running VMs keep running either way.
  • service at-logout shows or sets what running VMs do when the Mac logs out, restarts, or shuts down, the same choice as Settings → General → Logout and Shutdown in the app, for every VM. shutdown, the default, asks each guest to shut down and powers it off if it hasn't within 30 seconds; VMs set to start at login boot fresh. suspend saves each VM's memory to disk, and VMs set to start at login resume; after a macOS update a saved session can't be restored, and such a VM starts from its disk instead. The choice applies to VMs already running, unless an earlier MacVisor started them. See Run and control VMs.
mvz service at-logout                        # show the current choice
mvz service at-logout suspend
mvz service at-logout --json                 # result.atLogout is "shutdown" or "suspend"

Scripting and CI examples

Every command exits non-zero on failure (see exit status), so && chains and set -e work.

A throwaway VM that runs the tests

A MacVisor headless CI pipeline creates a VM from a template, configures it, runs a job, collects results, and cleans up.

mvz create ubuntu ci-42 --project "$PWD"
mvz start ci-42 --headless && mvz wait ci-42 --ssh
mvz exec ci-42 --wait --workdir "$PWD" -- make test
mvz poweroff ci-42 && mvz rm ci-42

--project shares the working tree read-write at the same path, so build paths match on both sides; nothing else is shared and no container runtime is installed. exec runs as your own account. The VM is a linked clone of the Ubuntu template, so creating and deleting it costs no disk space and no download after the first time.

Snapshot, try something, roll back

mvz snapshots take dev --name baseline --disk-only
mvz exec dev -- sudo ./risky-upgrade.sh
mvz snapshots list dev --json
mvz snapshots revert dev <snapshot-id>      # powers off and restores the disk
mvz start dev --headless

To keep the VM running throughout, take the snapshot without --disk-only and revert --resume.

A headless macOS 27 VM with an account and SSH

mvz create macos-27 mac-ci --user builder --ssh --password-file ./builder-password --cpus 6 --memory 12
mvz wait mac-ci --ssh                       # the install (20–30 minutes), then the first boot until SSH answers
mvz exec mac-ci -- sw_vers
mvz autostart mac-ci on

wait follows the install, starts the VM without a window once macOS is installed, and returns when SSH answers; it exits 1 if the install fails. Without --timeout it allows 600 seconds from the end of the install; a --timeout you give covers the install too. The first boot creates the builder account and turns Remote Login on; MacVisor then installs its agent and authorises your SSH keys, so exec and shell need no password. autostart on brings the VM back at every login; see Always-on host.

Reading state from a script

mvz list --json
mvz show dev --json
mvz ip dev --wait                           # just the address, when it exists
mvz service status --json                   # exit 4 if the service is not answering

Still GUI-only

Converting a raw disk to ASIF, shrinking a disk, network-level (vmnet NAT) port forwards, VLAN interfaces, the per-network DHCP/DNS/NAT/RA switches, and creating an empty VM for an ISO install are done in the app. set <vm> --iso attaches installation media to an existing VM.