{"id":51822149,"url":"https://github.com/kborovik/acumatica-devops","last_synced_at":"2026-07-22T06:05:49.398Z","repository":{"id":371206692,"uuid":"1297464425","full_name":"kborovik/acumatica-devops","owner":"kborovik","description":"Acumatica Config-as-Code (GitOps) — YAML configuration of DEV company","archived":false,"fork":false,"pushed_at":"2026-07-14T01:10:53.000Z","size":60,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-14T01:24:58.945Z","etag":null,"topics":["acumatica","config-as-code","gitops","python"],"latest_commit_sha":null,"homepage":"https://lab5.ca","language":"Makefile","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kborovik.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-07-11T13:25:27.000Z","updated_at":"2026-07-14T01:10:57.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/kborovik/acumatica-devops","commit_stats":null,"previous_names":["kborovik/acumatica-devops"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/kborovik/acumatica-devops","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kborovik%2Facumatica-devops","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kborovik%2Facumatica-devops/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kborovik%2Facumatica-devops/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kborovik%2Facumatica-devops/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kborovik","download_url":"https://codeload.github.com/kborovik/acumatica-devops/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kborovik%2Facumatica-devops/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35750094,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-07-20T02:08:10.276Z","status":"online","status_checked_at":"2026-07-22T02:00:06.236Z","response_time":124,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["acumatica","config-as-code","gitops","python"],"created_at":"2026-07-22T06:05:48.504Z","updated_at":"2026-07-22T06:05:49.387Z","avatar_url":"https://github.com/kborovik.png","language":"Makefile","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Acumatica DevOps\n\nAnsible-managed Ubuntu Linux infrastructure for a test/dev **Acumatica ERP** lab.\nOne Ubuntu Linux host runs KVM. Each Acumatica instance is a Windows Server VM\ncloned from a golden image. The host also carries the **MailPilot** Ubuntu\nguest(s) — see [docs/mailpilot.md](docs/mailpilot.md). This README is the\noperator manual.\n\nCommands below write the host's SSH alias as `\u003chost\u003e` — substitute the name you\ngave it in `~/.ssh/config`.\n\n## What this repo does\n\n- Configures the Ubuntu Linux host as a KVM hypervisor with ZFS storage, libvirt\n  NAT, SMB shares, and a Tailscale subnet router.\n- Clones a Windows Server golden image into one VM per Acumatica instance.\n- Installs SQL Server and Acumatica ERP into each instance, unattended.\n- Creates one Ubuntu guest per MailPilot instance from the Ubuntu cloud image\n  (zvols + cloud-init) and deploys PostgreSQL 18 + `mailpilot-crm` into it.\n\nEverything runs through Ansible from a control machine. All host changes go\nthrough Ansible — do not hand-edit the host directly.\n\n## Prerequisites\n\nRun Ansible from a control machine — a workstation (macOS or Linux) or a CI\nrunner such as a self-hosted GitHub Actions runner. You need:\n\n- SSH access to the Ubuntu Linux host — see [Remote host](#remote-host).\n- Ansible installed locally (for example `brew install ansible` on macOS, or\n  `pipx install ansible` / your distro's package on Linux).\n- **GNU Make ≥ 3.82** — the Makefile uses `.ONESHELL`. On macOS use Homebrew's\n  `gmake` (`brew install make`); `/usr/bin/make` is 3.81 and will fail the guard.\n  Elsewhere plain `make` is fine when it is GNU Make.\n- Collection dependencies: `gmake deps` (runs `ansible-galaxy collection install`).\n\nConfirm connectivity before applying anything:\n\n    gmake ping        # ansible \u003chost\u003e -m ping\n\n## Remote host\n\nConnect with `ssh \u003chost\u003e`, where `\u003chost\u003e` is an alias in your `~/.ssh/config`.\nThe lab resolves it to a Tailscale MagicDNS name. Host runs Ubuntu 26.04 LTS.\n\n- **Do all work as `kb`** (uid/gid 1001). `ssh \u003chost\u003e` logs in as `kb`.\n- Use `sudo -n` for privileged steps. Both `kb` and `sysop` have passwordless\n  sudo (`/etc/sudoers.d/90-nopasswd`).\n- `sysop` (uid 1000) is the break-glass admin account only. Root SSH is disabled.\n- kb's login shell is **fish** (via the `host_base` role). `ssh \u003chost\u003e '\u003ccmd\u003e'`\n  is interpreted by fish. POSIX-isms fail — `x=y` assignments, `$(...)` in double\n  quotes, or a stray `$` (for example in a sed/awk script). For POSIX syntax run\n  `ssh \u003chost\u003e bash -c '...'`.\n\n## Storage layout (ZFS)\n\nPool is `upool`. Datasets:\n\n- `upool/vms` — VM disks, one zvol per VM disk (`upool/vms/\u003cname\u003e`, plus\n  `-data` and `-backup` zvols) for both the Windows and MailPilot guests.\n  Snapshotted daily by sanoid (one atomic recursive snapshot, `vm_snap_keep`\n  kept) as a crash-consistent whole-VM rollback net.\n- `upool/distr` → `/upool/distr` — ISO images and installers (Acumatica MSI, SQL\n  Server ISO). SMB share `\\\\\u003chost\u003e\\distr`.\n- `upool/backups/mssql` → `/upool/backups/mssql` — SQL Server backups. SMB share\n  `\\\\\u003chost\u003e\\mssql-backups`.\n\n## Everyday commands\n\nAll targets wrap `ansible-playbook site.yml`. Run from the repo root\n(use `gmake` on macOS — see Prerequisites).\n\n- `gmake site` — apply the full stack: the Ubuntu Linux host, every `acu`\n  instance, and every `mailpilot` guest.\n- `gmake ping` — Ansible connectivity test against the Ubuntu Linux host\n  (`LIMIT=mailpilot` for the guests).\n- `gmake lint` — `ansible-lint` over the ansible tree.\n- `gmake deps` — install collection dependencies.\n- `gmake help` — list all targets.\n\n`LIMIT=\u003cvm\u003e` scopes a target to one instance. For example:\n\n    gmake acumatica-vm LIMIT=acu-dev1        # clone + DHCP/DNS + rename one VM\n    gmake acumatica-config LIMIT=acu-dev1    # data disk + SQL Server for one VM\n    gmake acumatica-release LIMIT=acu-dev1   # Acumatica MSI + IIS/ac.exe for one VM\n    gmake mailpilot-vm LIMIT=mailpilot-1     # create the MailPilot Ubuntu guest\n    gmake mailpilot-config LIMIT=mailpilot-1 # configure it (tools, Postgres, CLIs)\n    gmake mailpilot-release                  # deploy/upgrade mailpilot-crm\n\nSingle-role targets for the host stack: `gmake host-kvm`, `gmake host-storage`\n(ZFS datasets + sanoid), `gmake host-network`, `gmake host-smb`, `gmake host-base`.\n`gmake linux-unattended-upgrades` (automatic apt upgrades) and `gmake linux-uv`\n(uv, OS-global) apply to every Linux instance at once — the kronos host and the\nmailpilot guests.\nMailPilot guest specifics — secrets, app config, backups — are in\n[docs/mailpilot.md](docs/mailpilot.md).\n\n## Add an Acumatica instance\n\nInstances are inventory-driven. Adding a host is all it takes.\n\n1. Add the VM under the `acu` group in `ansible/inventory/hosts.yml` with its\n   reserved IP:\n\n       acu:\n         hosts:\n           acu-tst1: { vm_ip: 192.168.122.12 }\n\n2. Apply the stack:\n\n       gmake site                          # clone -\u003e rename -\u003e mssql -\u003e acumatica\n       gmake acumatica-vm LIMIT=acu-tst1   # or just the clone + lease/DNS + rename step\n\nThe MAC is derived from `vm_ip`'s last octet, so the static DHCP lease and the\n`\u003cname\u003e.vm.internal` DNS record exist before the VM first boots. The site lands\nat `http://\u003cvm\u003e.vm.internal/` — first login `admin`/`setup` (password change\nforced).\n\nGolden-image build, instance internals, and Tailscale access are documented in\n[docs/windows-vms.md](docs/windows-vms.md). The backup strategy — SQL-native\n`.bak` backups plus ZFS zvol snapshots — is in\n[docs/backup-strategy.md](docs/backup-strategy.md).\n\n## Role reference\n\n`site.yml` runs the Ubuntu Linux host stack, then the Windows guests, then the\nMailPilot guests. Roles in order:\n\n- **host_kvm** — hypervisor plus `win-vm-create`, `win-vm-rm`, and `qga-exec`\n  helper scripts. Golden-image build, teardown, guest-agent rescue.\n- **host_storage** — ZFS datasets.\n- **host_sanoid** — snapshot schedules: daily atomic recursive snapshot of the VM\n  zvols and daily snapshot of the MSSQL backup dataset (`sanoid.timer`).\n- **host_network** — libvirt NAT, static DHCP leases, Tailscale subnet router,\n  split-DNS dnsmasq. VMs resolve as `\u003cvm\u003e.vm.internal` on the tailnet.\n- **host_smb** — SMB shares over `/upool`: `distr`, `mssql-backups`.\n- **host_base** — base host config for kb (fish shell).\n- **linux_unattended_upgrades** — automatic apt upgrades (security + `-updates`)\n  via `unattended-upgrades`, on every Linux instance (the kronos host and the\n  mailpilot guests). Each reboots itself off-hours (02:00 local,\n  `unattended_upgrade_reboot_time`) when an upgrade requires it. The guest\n  domains are set to libvirt autostart (in `acumatica_vm` / `mailpilot_vm`), so\n  they come back automatically after the kronos host reboots.\n- **linux_uv** — installs uv (astral) OS-global to `/usr/local/bin` on every\n  Linux instance, so `uv`/`uvx` are on every user's PATH. `mailpilot_crm` uses\n  this uv for its `uv tool install`. `gmake linux-uv`.\n- **acumatica_vm** — inventory-driven guest provisioning. An `acu` host with\n  `vm_ip` gets a derived MAC, a ZFS clone of `ws2025-base`, and a first-boot\n  rename. `gmake acumatica-vm LIMIT=\u003cvm\u003e`.\n- **acumatica_mssql** — per-instance Windows layer: a data-disk zvol plus\n  unattended SQL Server. Runs against inventory group `acu` over SSH to\n  Administrator (`ansible_shell_type: powershell`); host-side steps delegate to\n  the host. `gmake acumatica-config LIMIT=\u003cvm\u003e`.\n- **acumatica_erp** — Acumatica ERP: installer MSI from the distr share into the\n  guest, then IIS plus `ac.exe` instance deployment. Produces AcumaticaDB and a\n  site at `http://\u003cvm\u003e/AcumaticaERP`. `gmake acumatica-release LIMIT=\u003cvm\u003e`.\n- **mailpilot_vm** — inventory-driven Ubuntu guest provisioning (group\n  `mailpilot`): OS zvol written from the cloud image, a data zvol, a cloud-init\n  NoCloud seed (hostname + `ubuntu` SSH key + static IP), then\n  `virt-install --import`.\n- **mailpilot_postgresql** — ext4 on the guest data disk (vdb) mounted at\n  `/var/lib/postgresql`, then PostgreSQL 18 from pgdg; optional `pilot` remote\n  and `reporter` read-only roles.\n- **mailpilot_tools / mailpilot_github_cli / mailpilot_google_cli /\n  mailpilot_gpg / mailpilot_nodejs / mailpilot_claude_code /\n  mailpilot_firecrawl_cli** — MailPilot guest OS + operator tooling.\n- **mailpilot_crm** — `mailpilot-crm` from PyPI (uv) as the interactive\n  `ubuntu` user, database bootstrap, `mailpilot.service`. `gmake mailpilot-release`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkborovik%2Facumatica-devops","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkborovik%2Facumatica-devops","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkborovik%2Facumatica-devops/lists"}