Note
This repo is based on jborean93/ansible-windows, but has been reworked for Vagrant with the libvirt provider.
This repo contains Ansible roles, playbooks and a libvirt-backed Vagrant workflow to build and manage Windows and Linux VMs (weighted more towards mssql infrastructure).
It can also be used as a general-purpose infrastructure repository when Vagrant provisioning is omitted.
- Vagrant
- libvirt
- the
vagrant-libvirtplugin - Python 3 on the host system running Vagrant
- Ansible on the host system running Vagrant, either from your existing setup or from the optional repo-local host virtualenv
Note
If you already have a working host Ansible setup, you can keep using it and skip this step.
This repo includes a minimal host bootstrap that creates .venv,
installs the pinned Ansible and Python packages from
requirements-host.txt, and installs the required
collections from requirements-host.yml:
./bootstrap/setup-host-venv.sh
source .venv/bin/activateThis host .venv is only for bootstrapping and host-side execution. The repo's
primary Ansible toolchain still lives inside the control node VM at
/home/ansible/controller-venv.
To use that in-VM toolchain after the control node is provisioned:
vagrant ssh ansible-con-01
enter-ansible-enventer-ansible-env switches to the controller admin user and activates the
control-node virtualenv for that shell.
This repo needs Ansible on the host to drive vagrant up --provision and any
direct host-side ansible-playbook runs.
This repo provisions the lab from the dynamic inventory in inventory/inventory.py, which resolves host data from inventory/hosts.yml, inventory/hosts.d, and inventory/group_vars. Vagrant uses that resolved inventory to run playbooks/main.yml against the selected hosts with the appropriate Ansible host limit.
The provisioning flow covers:
- A Linux control node for Ansible and module development work
- Standalone Linux hosts
- A Windows domain controller
- Windows domain member hosts
- AD CS with machine auto-enrollment
- WinRM HTTPS listener certificates issued by the lab CA
- Optional Guest tools and OpenSSH on Windows boxes
The Linux control node is the intended working environment for the repo's Ansible toolchain. It builds its own Python and virtualenv, then installs Ansible and the required Windows connection dependencies there instead of depending on the base VM Python environment.
Note
If you only want to spin up standalone Vagrant hosts and do not need the
Ansible control-node workflow or the lab domain setup, use --no-provision
with vagrant up. This skips guest provisioning, but Vagrant still ensures
the selected host's libvirt networks exist on the host first.
The inventory is organized by host role:
control_node- the Linux Ansible control nodelinux_hosts- optional non-controller Linux hosts and the parent group for Linux MSSQL validation hostsdomain_controller- the Windows domain controllerdomain_members- Windows domain-joined member hosts, currently Windows MSSQL test hostsmssql_windows_matrix- standalone Windows MSSQL install validation matrixmssql_linux_matrix- Linux MSSQL install validation matrix, withmssql_linux_apt,mssql_linux_dnf, andmssql_linux_unsupportedsubgroupsmssql_windows_ag- Windows SQL Server availability group test nodes using WSFCmssql_linux_ag- Linux SQL Server availability group test nodes
Core hosts live in inventory/hosts.yml. MSSQL host definitions live in
inventory/hosts.d/mssql_linux.yml and inventory/hosts.d/mssql_windows.yml, and inventory.py exposes the inventory to Ansible and Vagrant.
Shared lab settings live in group vars:
- all - domain, Kerberos, DNS, certificates, provider defaults
- linux.yml - Linux connection settings and Linux libvirt defaults
- windows.yml - PSRP connection settings and optional Windows extras
- control_node.yml - control-node Python, Ansible, and admin-user settings
- mssql_windows_ag.yml - MSSQL cluster, availability groups topology settings
- mssql_linux_ag.yml - Linux MSSQL availability groups topology settings (only placeholder at the moment)
Two singleton groups are assumed by the playbooks:
control_nodedomain_controller
The playbooks assert that each of those groups contains exactly one host at the moment.
The roles are grouped by responsibility. The per-role README files document their variables and caveats in more detail.
These roles prepare the Linux control node used to run Ansible, PSRP, WinRM, and repo-local tooling.
- linux_base - installs baseline Linux packages, creates the controller admin user and group, installs the authorized key, and grants passwordless sudo
- linux_kerberos_client - installs Kerberos and DNS client pieces and renders the local Kerberos and resolver configuration needed to talk to the AD lab
- controller_python - builds the configured Python from source, validates the interpreter version, and creates the controller virtualenv
- controller_ansible -
installs Ansible plus the Windows controller dependencies into that virtualenv
and adds the
enter-ansible-envshell helper
These roles are normally driven together by controller-node.yml.
These roles manage libvirt host-side prerequisites.
- libvirt_host_network - reads the configured lab networks from inventory, generates libvirt network XML, defines and starts the routed lab networks, and optionally manages firewalld plus scoped nftables outbound NAT
- libvirt_host_share - validates host share records, resolves the effective libvirt domain name, renders virtiofs filesystem XML, and attaches the shares to the persistent and optionally live domain definition
These roles are typically driven by:
These roles build the Windows lab platform itself before SQL Server or WSFC work starts.
- windows_domain_controller - points DNS at localhost, promotes the host to a domain controller, creates the initial domain admin, and validates that identity
- windows_domain_member - points member hosts at the DC for DNS, joins them to the domain, reboots when needed, and validates domain logon
- windows_adcs - installs the CA, publishes the auto-enrollment template, configures the GPO, and exports the CA chain
- windows_winrm_cert - refreshes policy, triggers certificate enrollment, locates the correct issued certificate, and rebinds the WinRM HTTPS listener when needed
- windows_common - provides repo-local helper modules used by the Windows roles so connection, certificate, and SQL flows do not have to duplicate inline PowerShell logic
These roles are split across:
These roles are the SQL Server and WSFC-focused part of the repo.
- windows_sql_server -
manages SQL Server setup by handling service accounts first and then driving
Microsoft
setup.exewith repo-local modules and inventory-backed SQL settings - windows_failover_cluster - exposes operation-specific WSFC entrypoints such as prerequisites, file share witness, and cluster creation instead of forcing everything through one large cluster task file
Primary playbooks:
These roles are optional and are not treated as baseline requirements for every Windows host.
- windows_guest_tools - installs WinFSP and virtio guest tools, then configures virtiofs tag-to-drive mounts when host shares are present
- windows_openssh - installs the OpenSSH Server capability and manages the service, firewall rule, and default shell
Utility playbooks:
The control-node playbook is controller-node.yml. It runs:
which produces:
- A Linux admin user (with passwordless sudo)
- Kerberos and DNS configuration so the control node can resolve and reach the AD lab
- A Python runtime built from source (to avoid distro-specific package related complexities)
- A control-node virtualenv at
/home/ansible/controller-venv ansible,pypsrp,pywinrm,requests-kerberos,requests-credssp, andgssapiinstalled inside that virtualenv/usr/local/bin/enter-ansible-env, which switches to the controller admin user and activates the control-node virtualenv
Current control-node defaults from control_node.yml:
- Python line:
3.11 - source build version:
3.11.1 - virtualenv path:
/home/ansible/controller-venv - Ansible package:
ansible==12.3.0
These are defaults in inventory. Modify them accordingly if you want a different control-node toolchain.
The Windows domain lifecycle is split into separate playbooks:
- windows-domain-controller.yml promotes the controller and creates the initial domain admin
- windows-domain-members.yml joins member hosts to the domain and validates domain logon
- windows-certificates.yml configures AD CS and updates WinRM HTTPS listeners for the controller and members
The Windows flow uses:
- windows_domain_controller promote the DC and create the initial domain admin
- windows_domain_member point DNS at the DC, join the domain, reboot if needed
- windows_adcs install the CA, publish the machine template, configure the auto-enrollment GPO, export the CA chain
- windows_winrm_cert enroll/select the correct machine certificate and rebind the WinRM HTTPS listener
When AD CS is enabled, windows-certificates.yml also writes ca_chain.pem in the repo root. The Windows connection defaults use that file to switch PSRP from certificate validation ignore during bootstrap to validated HTTPS once the lab CA chain is available. After the chain exists locally, PSRP connects to the Windows host FQDN instead of the inventory IP so the WinRM listener certificate can be validated against its DNS SAN.
Additional utility playbooks are available:
- windows-guest-tools.yml install SPICE, WinFSP, and VirtIO guest tools through windows_guest_tools
- windows-openssh.yml install and configure the Windows OpenSSH Server capability through windows_openssh
When running main.yml, the guest-tools and OpenSSH utility
imports are tagged never. Run them explicitly with --tags windows_guest_tools
or --tags windows_openssh when you want those utility flows included.
Provider settings are inventory-driven. The Vagrant provider helper currently accepts: cpus, memory_mb, cpu_mode, nic_model, video_type, graphics, memory_backing, sync_folders
Additional options can be included by extending ProviderOptions::ALLOWED in provider_options.rb.
For example, to add support for CPU topology
ALLOWED = %w[
...
...
cpu_topology
].freezeThen update ProviderOptions.apply_provider_options and append something like
if opts['cpu_topology'].is_a?(Hash)
topo = opts['cpu_topology']
lv.cputopology(
sockets: Integer(topo['sockets']),
cores: Integer(topo['cores']),
threads: Integer(topo['threads'])
)
endAnd define it in the inventory
provider_options:
...
cpu_mode: host-model
cpu_topology:
sockets: 2
cores: 2
threads: 1
...Better validation, error handling etc can be added as needed. The idea is to keep things flexible and let the inventory drive what gets exposed.
Note
Vagrant libvirt documentation reference can be found here
Global defaults come from inventory/group_vars/all, with Linux and Windows overlays from linux.yml and windows.yml. Host-level provider_options override those merged defaults.
Linux sync-folder defaults currently enable read-write virtiofs for the repo root at /vagrant.
Host networking comes from the resolved inventory interface data in inventory/inventory.py.
When you run vagrant up <host>, vagrant reload <host>, or vagrant resume <host>,
Vagrant first ensures the libvirt networks attached to that host exist on the
host system. For example:
ansible-con-01ensureslab-mainwin-cluster-n01ensuresmssql-mainwin-cluster-n02ensuresmssql-dr
The host-side network definitions are managed by libvirt-host-network.yml.
Bring up a specific host:
vagrant up ansible-con-01 --provisionBring up multiple hosts:
vagrant up domain-con-01 win-cluster-n01 --provisionSelect targets with VAGRANT_GROUP:
VAGRANT_GROUP=linux vagrant up --provision
VAGRANT_GROUP=control_node vagrant up --provision
VAGRANT_GROUP=domain_controller,domain_members vagrant up --provisionPass raw Ansible flags with ANSIBLE_EXTRA_ARGS:
ANSIBLE_EXTRA_ARGS="-vv --forks=2" vagrant up domain-con-01 --provisionShow the custom Vagrantfile help:
vagrant --custom-help