semaphore/bulk_vm_lifecycle.py script drives the management UI REST API to act on groups of macOS VMs that share a common name prefix. Use it when you need to provision, configure, manage, or tear down many VMs at once without running a separate management UI task for each one.
For individual VM operations, see the Day-2 operations guide.
Before you begin
- Your MacStadium VDI deployment is complete and the management UI is configured. This page assumes you have a working deployment with active VMs.
uvis installed on the workstation where you’ll run the script.- You have admin credentials for the management UI.
- To use
install-citrix, you’ll need a download URL for the Citrix VDA.dmg(for example, an S3 presigned URL) and your domain’s hostname suffix.
Configure credentials
The script reads credentials from flags, environment variables, or asemaphore/.env file. The env file approach keeps credentials out of your shell history:
--semaphore-url, --semaphore-admin, and --semaphore-password.
Deploy a group of VMs
Thedeploy subcommand provisions VMs in parallel, each named with your prefix followed by a random 8-character hex suffix (for example, demo-a1b2c3d4). It writes the generated names to a manifest at semaphore/.bulk_vms_[PREFIX].json for use by later subcommands.
deploy again with the same prefix merges new names into the existing manifest rather than overwriting it.
List the group
Thelist subcommand runs the List VMs template with vm_name set to your prefix. The underlying playbook treats vm_name as a regex anchor, returning every VM whose name starts with the prefix.
Start and stop the group
Themanage subcommand sets every VM matching the prefix to running, stopped, or absent. Each call is a single management UI task: the playbook loops over all matching VMs inside one Ansible run.
To stop the group:
Provision a user across the group
Theprovision-user subcommand reads the manifest and submits one parallel task per VM. The underlying playbook requires an exact VM name match, so the manifest must exist before you run this subcommand.
--vm-names [VM_NAME_1],[VM_NAME_2] instead.
Install the Citrix VDA across the group
Theinstall-citrix subcommand reads the manifest and submits one parallel task per VM, installing the Citrix on each one.
Run install-citrix
[VM_NAME][HOSTNAME_SUFFIX], installs the VDA, and reboots the VM to complete installation.Each VM reboots at the end of its task. If your hosts can’t handle every VM rebooting simultaneously, lower
--concurrency to spread the load. The default task timeout is 1800 seconds; raise it with --task-timeout [SECONDS] if your installer download is slow.Register each VM with the Delivery Controller
After installation, register each VM using the
VDI | Register Citrix VDA template in the management UI. The script doesn’t handle enrollment tokens, which are issued per machine and must be applied individually.See Citrix DaaS configuration for registration steps.Delete the group
Thedelete subcommand is a convenience wrapper for manage --state absent. It prompts for confirmation before submitting and removes the manifest once every task succeeds. If any task fails, the manifest is kept so you can re-run.
--yes to skip the confirmation prompt in non-interactive environments such as CI pipelines.
Common flags
The following flags apply to every subcommand.| Flag | Default | Purpose |
|---|---|---|
--semaphore-url | http://localhost:3000 or $SEMAPHORE_URL | Management UI base URL |
--semaphore-admin | $SEMAPHORE_ADMIN or admin | Admin username |
--semaphore-password | $SEMAPHORE_ADMIN_PASSWORD or changeme | Admin password |
--project-name | Orka Engine Orchestration | Project that owns the templates |
--wait / --no-wait | --wait | Poll until terminal state, or return immediately after submission |
--poll-interval | 3.0 | Seconds between status polls |
--task-timeout | 1800.0 | Per-task timeout in seconds |
--concurrency | 5 | Parallel task submissions for deploy, provision-user, and install-citrix |
The VM manifest
Whendeploy runs, it writes a manifest at semaphore/.bulk_vms_[PREFIX].json recording the VM names it created. The provision-user and install-citrix subcommands read this file to fan out per-VM tasks, because their underlying playbooks require exact name matches.
manage and delete calls fall back to server-side prefix matching when no manifest is present. The delete subcommand removes the manifest automatically once every task succeeds.
Operational notes
Prefix constraints
Prefixes must start and end with a lowercase letter or digit and contain only lowercase letters, digits, and hyphens. The maximum length is 32 characters. Prefixes that don’t meet these constraints will produce invalid VM names and cause the deploy task to fail.Concurrency
deploy, provision-user, and install-citrix parallelize through a thread pool. The default --concurrency 5 works well for most deployments. Lower it if the management UI or the underlying hosts become saturated.
manage and delete are single management UI tasks. Their runtime scales with the number of matched VMs because the playbook loops over them inside one Ansible run.
Scheduling cleanup
For routine teardown, you can automatedelete in a cron job or CI pipeline. The following cron entry runs a delete every Friday at 10 PM:

