Ansible-managed Ubuntu Linux infrastructure for a test/dev Acumatica ERP lab. One Ubuntu Linux host runs KVM. Each Acumatica instance is a Windows Server VM cloned from a golden image. This README is the operator manual.
Commands below write the host's SSH alias as <host> — substitute the name you
gave it in ~/.ssh/config.
- Configures the Ubuntu Linux host as a KVM hypervisor with ZFS storage, libvirt NAT, SMB shares, and a Tailscale subnet router.
- Clones a Windows Server golden image into one VM per Acumatica instance.
- Installs SQL Server and Acumatica ERP into each instance, unattended.
Everything runs through Ansible from a control machine. All host changes go through Ansible — do not hand-edit the host directly.
Run Ansible from a control machine — a workstation (macOS or Linux) or a CI runner such as a self-hosted GitHub Actions runner. You need:
- SSH access to the Ubuntu Linux host — see Remote host.
- Ansible installed locally (for example
brew install ansibleon macOS, orpipx install ansible/ your distro's package on Linux). - Collection dependencies:
make deps(runsansible-galaxy collection install).
Confirm connectivity before applying anything:
make ping # ansible <host> -m ping
Connect with ssh <host>, where <host> is an alias in your ~/.ssh/config.
The lab resolves it to a Tailscale MagicDNS name. Host runs Ubuntu 26.04 LTS.
- Do all work as
kb(uid/gid 1001).ssh <host>logs in askb. - Use
sudo -nfor privileged steps. Bothkbandsysophave passwordless sudo (/etc/sudoers.d/90-nopasswd). sysop(uid 1000) is the break-glass admin account only. Root SSH is disabled.- kb's login shell is fish (via the
fish_shellrole).ssh <host> '<cmd>'is interpreted by fish. POSIX-isms fail —x=yassignments,$(...)in double quotes, or a stray$(for example in a sed/awk script). For POSIX syntax runssh <host> bash -c '...'.
Pool is upool. Acumatica-related datasets:
upool/vms— VM disks, one zvol per VM (upool/vms/<name>, plus-dataand-backupzvols). Snapshotted daily by one atomiczfs snapshot -r(vm_snap_keepkept) as a crash-consistent whole-VM rollback net.upool/distr→/upool/distr— ISO images and installers (Acumatica MSI, SQL Server ISO). SMB share\\<host>\distr.upool/backups/mssql→/upool/backups/mssql— SQL Server backups. SMB share\\<host>\mssql-backups.
All targets wrap ansible-playbook site.yml. Run from the repo root.
make site— apply the full stack: the Ubuntu Linux host, then clone and provision everyacuinstance.make ping— Ansible connectivity test against the Ubuntu Linux host.make lint—ansible-lintover the ansible tree.make deps— install collection dependencies.make help— list all targets.
LIMIT=<vm> scopes a target to one instance. For example:
make vm LIMIT=acu-dev1 # clone + DHCP/DNS + rename one VM
make mssql LIMIT=acu-dev1 # data disk + SQL Server for one VM
make acumatica LIMIT=acu-dev1 # Acumatica MSI + IIS/ac.exe for one VM
Single-role targets for the host stack: make kvm, make storage,
make network, make fileserver, make fish.
Instances are inventory-driven. Adding a host is all it takes.
-
Add the VM under the
acugroup inansible/inventory/hosts.ymlwith its reserved IP:acu: hosts: acu-tst1: { vm_ip: 192.168.122.12 } -
Apply the stack:
make site # clone -> rename -> mssql -> acumatica make vm LIMIT=acu-tst1 # or just the clone + lease/DNS + rename step
The MAC is derived from vm_ip's last octet, so the static DHCP lease and the
<name>.vm.internal DNS record exist before the VM first boots. The site lands
at http://<vm>.vm.internal/ — first login admin/setup (password change
forced).
Golden-image build, instance internals, and Tailscale access are documented in
docs/windows-vms.md. The backup strategy — SQL-native
.bak backups plus ZFS zvol snapshots — is in
docs/backup-strategy.md.
site.yml runs three plays: the Ubuntu Linux host stack, guest VM cloning, then
the Windows guests. Roles in order:
- kvm — hypervisor plus
win-vm-create,win-vm-rm, andqga-exechelper scripts. Golden-image build, teardown, guest-agent rescue. - storage — ZFS datasets plus the snapshot schedules (daily recursive snapshot of the VM zvols and daily snapshot of the MSSQL backup dataset).
- network — libvirt NAT, static DHCP leases, Tailscale subnet router,
split-DNS dnsmasq. VMs resolve as
<vm>.vm.internalon the tailnet. - fileserver — SMB shares over
/upool:distr,mssql-backups. - vm_clone — inventory-driven guest provisioning. An
acuhost withvm_ipgets a derived MAC, a ZFS clone ofws2025-base, and a first-boot rename.make vm LIMIT=<vm>. - mssql — per-instance Windows layer: a data-disk zvol plus unattended SQL
Server. Runs against inventory group
acuover SSH to Administrator (ansible_shell_type: powershell); host-side steps delegate to the host.make mssql LIMIT=<vm>. - acumatica — Acumatica ERP: installer MSI from the distr share into the
guest, then IIS plus
ac.exeinstance deployment. Produces AcumaticaDB and a site athttp://<vm>/AcumaticaERP.make acumatica LIMIT=<vm>. - fish_shell — fish config for kb.