Welcome to my Omnissa Horizon session about a Packer + Ansible pipeline that builds the Ubuntu 24.04 LTS Desktop golden image used for a Horizon Instant Clone desktop pool. It automates, step for step, the manual walkthrough at Omnissa Horizon – Ubuntu 24.04 Desktop Instant Clone Template. It’s built and run entirely from the same ansible worker VM (Ubuntu 24.04), driven by scripts/build.sh, and produces a powered-off VM with a snapshot for Horizon to fork Instant Clones from. For more info of setting up the automation platform see: Omnissa Horizon Golden Images – Setting Up the Automation Platform
Rather than inlining every file’s contents here, each phase below explains what actually happens and links straight to the real files on GitHub — so you’re always looking at the current, working version, not a copy that can drift out of date.
AI-assisted development
This OS-Build was done with assistance from Claude by Anthropic, particularly for some of the Packer/Ansible-related tasks etc. While I am not above using AI to reduce the amount of manual work involved, every part of the solution has been implemented, tested, and proven to work in my lab environment before being included in this write-up.
Why
Traditionally, Horizon templates are maintained manually and can be very time-consuming and prone with human errors, therefore automating this task makes a lot of sense.
The Tasks:
- Deploy template
- Patch template
- Join domain
- Upgrade agents
- Take snapshot
- Push image
The tech. used:
- Packer
- Ansible
- vSphere API
- Horizon API
Why Packer here, and not for every OS? Ubuntu’s installer has a genuinely unattended mode — autoinstall — that Packer’s vsphere-iso builder can drive end to end: boot the ISO, feed it an answer file over Packer’s own local HTTP server, wait for the OS to come up, then hand off to Ansible for everything in-guest. The Windows 11 24H2 and Windows Server 2025 RDSH images in this series use the same vsphere-iso builder; what differs is the answer file (an autounattend.xml on a virtual floppy instead of autoinstall over HTTP) and everything that happens after first boot.
Build Outcomes
The final result and goal of this exercise is a pipeline that automatically:
- Builds an Ubuntu 24.04 LTS Desktop VM with an unattended autoinstall
- Applies all pending apt updates
- Installs and verifies VMware Tools (open-vm-tools) and SSH
- Joins the domain
- Installs the Horizon Agent for Linux
- Configures SSSD, NFS home directories, login restriction and sudo
- Prepares the desktop (apps, no auto-updates, no GNOME first-run wizard)
- Creates a snapshot
- Publishes to the Horizon Desktop Pool
Prerequisites
- vCenter access (API credentials with rights to create/clone/snapshot VMs in the target cluster/folder)
- The Ubuntu 24.04 LTS Desktop ISO staged on a datastore or content library — Packer mounts it directly (iso_paths), no download and no checksum needed
- Outbound access to the Ubuntu archive (archive.ubuntu.com) from the build VM’s network segment during the install — this ISO ships no kernel package of its own, so a disk install always fetches one over the network
- An Ansible control node with the shared platform set up (Packer, Ansible, pyvmomi, requests) — see the platform page linked above
- ansible-vault set up for secrets (vCenter credentials, the domain-join account password, the build account password, the Horizon API password)
- A DHCP reservation and DNS A record for the build VM’s name (ubt-2404-tpl) and the pinned MAC/IP the build uses — scripts/build.sh’s pre-flight check refuses to start until the name resolves to the reserved IP
- An Active Directory OU for the Linux computer objects, and a join account with rights to create — and, for the stale-object cleanup, delete — computer objects in it
- An NFS server exporting the users’ home directories, with a UID mapping that matches what SSSD computes on the desktops (nfs_server, nfs_export_path)
- The Omnissa Horizon Agent for Linux tarball, staged into the project’s files/ folder once per agent version — Omnissa gates the download behind a portal login, so this one step can’t be automated away
- An existing Horizon instant-clone desktop pool to publish to — I point mine at a reference/staging pool rather than the live one
Note: Unlike the Windows images in this series, this one is joined to the domain at build time — the domain_join role creates the computer object and keytab that the instant clones rely on. The job’s dependencies in AD are therefore a little larger than for Windows, where Horizon joins each spawned desktop itself.
Repository
Everything below lives under operations/template-automation/ubt_2404_tpl/ in the bjosoren/omnissa repository, plus a handful of shared files under operations/template-automation/automation-platform/ (inventory, group_vars, and the orchestration scripts) that every image template shares. Settings live in inventory/group_vars/ubt_2404_tpl.yml; site-specific values can be kept in a gitignored ubt_2404_tpl.local.yml on the control node, which the shared tooling loads last so it overrides every committed default.
ubt_2404_tpl/
├── ubt_2404_tpl.pkr.hcl # Packer source + build blocks
├── variables.pkr.hcl # every variable Packer itself needs
├── playbook.yml # the Ansible side - one role per manual-post step
├── resolve_vars.yml # resolves group_vars + vault for scripts/build.sh
├── preflight_check_vm.yml # does the build VM already exist?
├── delete_vm.yml # power off + delete a VM by name
├── post_build_snapshot.yml # clone, fix NIC, snapshot
├── set_nic_automatic.py # flips the clone's NIC from Manual to Automatic
├── http/
│ └── user-data.pkrtpl.hcl # autoinstall answer file, as an HCL template
├── files/ # Horizon Agent tarball goes here (not committed)
└── roles/
├── base/ vmware_tools/ horizon_prereqs/
├── domain_join/ horizon_agent/ sssd/ nfs_home/ realm_restrict/
└── apps/ hardening/ gnome_setup/ sudoers/ cleanup/
How the pieces fit together
scripts/build.sh is the entry point — run with no arguments it lists every image it discovers under images/ and prompts you to pick one, or you can pass an image key directly (scripts/build.sh ubt_2404_tpl). An interactive placement picker (scripts/select_placement.py) then lists your real clusters, hosts, datastores, port groups and folders from vCenter, asks which Horizon desktop pool to publish to (enumerated live from Horizon) and whether to delete the build VM afterwards — and can save those answers so a later run replays them unattended with scripts/build.sh ubt_2404_tpl –config NAME. It then checks that the build VM doesn’t already exist in vCenter (preflight_check_vm.yml) and offers to delete a leftover one, verifies DNS, resolves this image’s settings from inventory/group_vars/ubt_2404_tpl.yml plus the shared group_vars/all.yml and the vault into Packer variables (resolve_vars.yml writes them to a root-only temp file that build.sh shreds right after), hashes the build account’s password so it never touches disk in plaintext, runs Packer with -force (which hands off to Ansible for everything in-guest), clones and snapshots the result, and pushes it live via scripts/publish_to_pool.sh. A transcript of every run is written to the project’s logs/ folder for debugging.
Departures from manual
Domain join and the Horizon Agent install are two separate steps, not one wizard. The manual procedure’s easyinstall_viewagent.sh bundles both behind an interactive prompt that, as far as I can tell, has no documented unattended mode — the closest thing, -l advanced, just asks a longer list of interactive questions rather than accepting them as flags. So this project does the domain join directly, then installs the agent with install_viewagent.sh’s own command-line flags, which is built for scripted use.
One final snapshot instead of five. The manual walkthrough snapshots after every step for rollback convenience during interactive work. A failed automated build just gets deleted and re-run, so this project only takes the one snapshot Horizon actually needs.
The build, phase by phase
Phase 1 — Provisioning
Create the virtual machine
Packer’s vsphere-iso builder creates the build VM directly against vCenter: EFI firmware, an ubuntu64Guest guest type, a thin-provisioned disk and a vmxnet3 network adapter with a pinned MAC address. The VM name (ubt-2404-tpl) is fixed and reused on every run, so it keeps the same MAC, IP and AD identity across builds; build.sh passes -force to Packer so the previous VM is destroyed and rebuilt from scratch each time. convert_to_template is deliberately never set — Horizon instant clones run from a snapshot on an ordinary VM, not from a vCenter template. The Ubuntu Desktop ISO is mounted straight from the datastore or content library.
Files used:
- ubt_2404_tpl.pkr.hcl — the Packer source/build definition
- variables.pkr.hcl — every variable declaration (VM sizing, placement, guest network, install media)
Settings: in inventory/group_vars/ubt_2404_tpl.yml
- VM sizing (vm_cpu_count, vm_mem_size_mb, vm_disk_size_mb)
- Placement (vcenter_cluster, vcenter_datastore, vcenter_folder, vcenter_network) — defaults the placement picker can override per build
- mac_address — the pinned MAC the build VM keeps across runs
- Install media (iso_paths)
Omnissa documentation: Create a Virtual Machine and Install Linux
vCenter connection itself (vcenter_server, credentials) comes from the shared group_vars/all.yml and vault.
Install Ubuntu with autoinstall
Packer serves the autoinstall answer file from its own local HTTP server (http_content), and the boot_command opens GRUB’s command line and points the installer at it. The answer file is built with Packer’s own templatefile() function rather than kept as a static file, so the guest-network and identity values all come from one place (variables.pkr.hcl) instead of being duplicated by hand. Keystrokes go over vSphere’s remote-keystroke API, which dropped them at Packer’s default speed on this platform, so boot_keygroup_interval is raised to 1000ms; if the installer ever falls through to the plain interactive live installer with no error, that is the first value to raise.
On this builder, boot_command is strictly keyboard input delivered to vSphere’s own guest keyboard HID API (vm.TypeOnKeyboard) — there is no VNC session behind it here, unlike Packer’s vmware-iso builder. The practical effect: the graphical installer can land on a blank white window on the vCenter console and sit there until something gives it an input event. The boot_command pokes it awake with a handful of <tab> keypresses at increasing wait intervals — a genuine dispatched keystroke rather than a modifier toggle, so it can’t activate anything by itself. If a future installer build ever needs more than that, the documented fallback is a WebMKS-based script that drives a real mouse click through the same vCenter API set_nic_automatic.py already uses, since boot_command itself has no pointer events at all.
The answer file deliberately does not use mode: oem. That mode is what Ubuntu Desktop uses for OEM-style deferred setup — it leaves a GNOME first-boot wizard for whoever unboxes the machine. Providing identity directly does a normal unattended install with no wizard at all, which matters here since removing that wizard is one of the manual procedure’s steps — better to never install it in the first place. It also pins the kernel flavor to generic, applies apt retry/timeout settings, and puts a temporary apt pin in place to work around a real archive-mirror desync that broke curtin’s kernel install — that pin is removed again in the cleanup role so it never ships in the image.
The VM uses a static IP while it builds (so Ansible has a fixed address to connect to); the cleanup role reverts the shipped image to DHCP. A full run takes a while — the Desktop ISO’s autoinstall plus first boot is the slow part, which is why ssh_timeout and ip_wait_timeout are both 45 minutes rather than Packer’s much shorter defaults.
Files used:
- http/user-data.pkrtpl.hcl — the autoinstall answer-file template
- ubt_2404_tpl.pkr.hcl — boot_command, http_content, SSH/IP timeouts
Settings: in inventory/group_vars/ubt_2404_tpl.yml
- guest_hostname, guest_ip_cidr, guest_gateway, guest_dns_servers
- timezone, locale, keyboard_layout
The build account’s password comes from the vault (vault_local_admin_password); build.sh hashes it before it reaches the answer file.
Phase 2 — Base system
Everything from here runs as a single Ansible playbook pass (playbook.yml) over SSH, executed by Packer’s ansible provisioner once the guest is up. It is intentionally boring — one role per step, in the same order as the manual procedure, each role mapping to one numbered step there. The provisioner loads the shared platform vars and vault plus this project’s own group_vars explicitly, so the playbook sees exactly the same values as every other project on the platform.
Base system (manual Step 1)
Replaces the installer’s 127.0.1.1 /etc/hosts entry with the guest’s real IP and FQDN, disables IPv6 (including stripping the link-local address the interface already picked up before Ansible connected), and brings the base install up to date with an apt upgrade.
Files used:
Settings: guest_hostname and domain_fqdn (the latter from the shared group_vars/all.yml).
Omnissa documentation: Prepare a Linux Machine for Remote Desktop Deployment
VMware Tools and SSH (manual Step 2)
Confirms open-vm-tools-desktop is installed and the SSH service is running — the VM can’t be reached or managed properly by Packer, Ansible and vCenter without both.
Files used:
Settings: none.
Horizon Agent prerequisites (manual Step 3)
Installs the dependency packages the Horizon Agent installer expects before it runs. Cross-check the list against Omnissa’s own dependency-package documentation for whichever agent version you stage — it shifts between releases.
Files used:
Settings: none — the package list is fixed in the role; edit it if your agent version needs a different set.
Omnissa documentation: Install Dependency Packages for Horizon Agent | System Requirements for Horizon Agent for Linux
Phase 3 — Domain & Horizon Agent
Domain join, without the wizard (manual Step 4, first half)
Joins the domain directly — same end result as the wizard (a computer object in the right OU and a working /etc/krb5.keytab), but fully scriptable. This project used to join with realmd/adcli and moved to Samba’s net ads join after tracing a persistent, misleading “KDC reply did not match expectations” failure down to a retry path in Ubuntu 24.04’s MIT Kerberos client that insists on a legacy _kerberos-master DNS SRV record Active Directory doesn’t publish by default; net ads join doesn’t share that code path. (Publishing the SRV records in AD DNS would also fix it, with no automation changes, if you’d rather do that.) The role writes an smb.conf declaring the host an ADS member, builds the createcomputer= OU path from computer_ou_dn so that stays the single source of truth, and joins.
Before joining, the role checks computer_ou_dn for a leftover computer object matching the hostname — left behind when an earlier build’s VM was deleted without cleaning up its AD object — and deletes it over LDAPS before proceeding, which used to be a manual chore between test runs. That needs the join account to hold Delete Computer Objects permission on that OU specifically, not just create/join rights. Toggle it off per environment with domain_join_delete_stale_computer_object: false.
Files used:
Settings: in inventory/group_vars/ubt_2404_tpl.yml and the shared all.yml
- computer_ou_dn — the OU the computer object is created in (LDAP DN order)
- domain_fqdn and domain_netbios_name — the NetBIOS name is a separate AD attribute, so confirm yours with net ads lookup rather than deriving it
- domain_join_delete_stale_computer_object — true by default
The join account’s password (vault_linux_domain_join_password) comes from the vault.
Install the Horizon Agent non-interactively (manual Step 4, second half)
Copies the Omnissa-provided tarball to the guest, extracts it, and runs install_viewagent.sh directly rather than the easy-install wizard. -A yes accepts the EULA and is the one flag that’s mandatory for a silent run; add audio, SSO or printing options per Omnissa’s command-line options doc for your agent version — I left that as a variable rather than hard-coding a list, since it shifts between releases. The role reboots afterwards and verifies the agent landed.
Files used:
Settings: in inventory/group_vars/ubt_2404_tpl.yml
- horizon_agent_tarball — filename of the tarball staged in files/
- horizon_agent_install_flags — optional override, “-A yes” by default
Omnissa documentation: Command-line Options for Installing Horizon Agent for Linux | Install Horizon Agent on a Linux Machine
SSSD configuration (manual Step 5)
Overrides what the wizard would have left behind: short usernames instead of fully-qualified ones, a home-directory path that matches the NFS mount point, and — critically — the same UID mapping the NFS server computes for the same user. The same file also does the manual procedure’s Step 7 job, restricting logins to the pool’s AD group, by setting access_provider = simple and simple_allow_groups directly.
Files used:
Settings: linux_login_group — the AD group allowed to log in; the domain values come from the shared group_vars/all.yml.
NFS home directories (manual Step 6)
Installs the NFS client and pam_mount, enables automatic home-directory creation on first login, and adds the NFS home volume to pam_mount.conf.xml (only if it isn’t there already).
Files used:
Settings: nfs_server and nfs_export_path.
Restrict logins to the pool group (manual Step 7)
Kept as a deliberate no-op so anyone matching the manual procedure’s numbered steps against this automation can still find Step 7 and see where its job went: realmd’s realm deny/permit commands only ever wrote sssd.conf’s access_provider and simple_allow_groups keys, and since this project no longer uses realmd, the sssd role writes those keys directly.
Files used:
Settings: none — see the SSSD step above.
Phase 4 — Desktop setup & shutdown
Applications (manual Step 8)
Installs the applications available to every user in the pool (PuTTY, Nemo and a few others) and sets Nemo as the default file manager system-wide, including pre-seeding /etc/skel so new home directories pick it up. This is the one role you’ll likely edit per environment — trim or extend the package list to match what your pool actually needs.
Files used:
Settings: the package list is in the role itself.
Disable automatic updates (manual Step 9)
An instant-clone template must not update itself between snapshots — updates are applied by rebuilding and re-snapshotting through this same pipeline, not by the running clones. Stops and removes unattended-upgrades and disables apt’s daily update and upgrade timers.
Files used:
Settings: none.
Remove the GNOME first-run wizard (manual Step 10)
Removes gnome-initial-setup and pre-seeds the already-done flag into /etc/skel so it’s set for every new home directory automatically.
Files used:
Settings: none.
Grant sudo to the pool group (manual Step 11)
Grants sudo to the pool’s AD group through a validated sudoers.d drop-in rather than editing /etc/sudoers directly — a bad edit there would block itself, while a bad drop-in just gets ignored (the file is checked with visudo -cf before it’s installed).
Files used:
Settings: linux_login_group.
Cleanup (manual Step 12)
Clears /tmp, removes the temporary apt pin the answer file set up for the install, and reverts the guest’s netplan config from the static build-time address back to DHCP — file only, not applied, so Packer’s live SSH session survives — so every desktop cloned from the image gets its address normally. The DHCP stanza also pins dhcp-identifier: mac: systemd-networkd’s default DHCPv4 client identifier is derived from /etc/machine-id, which every clone of this image shares, so without that key two VDIs with genuinely different MAC addresses could still present the same identifier to the DHCP server and collide on one leased IP — forcing MAC-based identification avoids that. The role checks which netplan file currently holds the static config and fails loudly if it isn’t where it expects. This is the last role in the playbook on purpose: powering off is deliberately left to Packer’s own shutdown_command, which waits for the VM to actually reach powered-off before build.sh moves on. Two independent shutdown paths racing each other would only confuse the vsphere-iso builder.
Files used:
Settings: none.
Phase 5 — Publish
These steps run against the vCenter and Horizon APIs only, once Packer’s own build finishes — they never touch the guest OS.
Clone, fix NIC, and snapshot
The build VM itself is never snapshotted directly — it’s cloned to a separate, uniquely-named published VM first (gi-ubt2404-<YYYYMMDD-HHMMSS>, with the time included because this project gets rebuilt more than once a day while iterating). That split came from a real failure: once a build was published, Horizon locked the VM, but it kept holding the same pinned MAC and static IP the next build’s own guest also configured, and the two VMs fighting over one network identity caused intermittent SSH-after-reboot failures. Cloning first keeps the pinned identity on the one reusable build VM only.
It’s a full clone, not a linked one — a linked clone would depend on the build VM’s disk chain, and that VM gets destroyed and rebuilt by packer build -force on every future run. A full clone copies the source adapter’s config verbatim, pinned MAC and Manual address type included, so a small pyVmomi script flips the clone’s NIC to Automatic and makes sure it’s set to connect at power on, with a single in-place reconfigure (an earlier fix that deleted and recreated the adapter with community.vmware reported success but never actually changed the address type, and separately left the recreated NIC disconnected until someone powered it on by hand). The snapshot is then taken on the published clone, not the build VM — it’s what Horizon forks Instant Clones from. If you chose to at the placement prompt, the build VM is deleted afterwards.
Files used:
- post_build_snapshot.yml
- set_nic_automatic.py — the in-place NIC reconfigure
- delete_vm.yml — optional removal of the build VM
Settings: the published VM’s name is generated from the gi-ubt2404 prefix plus a timestamp; placement reuses the same vcenter_* settings from the provisioning phase.
Deploy to the Horizon desktop pool
Pushes the new snapshot out via the Horizon REST API to an instant-clone desktop pool — the same Schedule Push Image operation Horizon Console’s own image-push screen uses: log in, resolve the vCenter, datacenter, base VM, snapshot and pool IDs, and schedule the push. Publishing a new image starts recomposing the desktops in the target pool, so when the pool wasn’t already chosen at the placement prompt, this step stops for a confirmation showing the target pool name — type y to proceed, n (or just Enter) to abort, or a different pool name to push there instead. When the target was picked at the picker’s own Horizon step, or loaded from a saved –config, that choice counts as the confirmation and the prompt is skipped. It can still be run standalone, e.g. to re-push a specific past build without rebuilding: scripts/publish_to_pool.sh <vm-name>.
One honest caveat: Horizon’s REST API field names have shifted between releases before, so check your own Connection Server’s live API reference (usually https://<connection-server>/rest/swagger-ui.html) against what the script sends before trusting it against a production pool.
Files used:
Settings: in inventory/group_vars/ubt_2404_tpl.yml (“Horizon pool publish” section):
- horizon_connection_server
- horizon_pool_name
- horizon_api_username
- horizon_api_domain
The API password (vault_horizon_api_password) comes from the vault.
Production checklist
Before you rely on this image in production, tick off the points below. Each row points back to the chapter that explains it.
| Done | Area | Check | Covered in |
|---|---|---|---|
| ☐ | Platform | The shared automation platform has passed its own production checklist | Platform checklist |
| ☐ | vCenter | Build account can create, clone and snapshot VMs in the target cluster and folder | Prerequisites |
| ☐ | Install media | Ubuntu 24.04 LTS Desktop ISO staged on a datastore or content library (iso_paths) | Provisioning |
| ☐ | Network | Build VM segment reaches archive.ubuntu.com during the install. The ISO has no kernel package of its own | Prerequisites |
| ☐ | DNS / DHCP | DHCP reservation and DNS A record for ubt-2404-tpl, matching the pinned mac_address and guest_ip_cidr. build.sh refuses to start until the name resolves to the reserved IP | Prerequisites, Provisioning |
| ☐ | Vault | vCenter credentials, the domain-join password, the build account password and the Horizon API password are in the vault | Prerequisites |
| ☐ | Active Directory | computer_ou_dn points at the Linux OU. The join account can create and delete computer objects there (needed for the stale-object cleanup) | Domain & Horizon Agent |
| ☐ | Active Directory | domain_netbios_name confirmed with net ads lookup, not derived from the FQDN | Domain & Horizon Agent |
| ☐ | NFS homes | nfs_server and nfs_export_path set. The NFS server’s UID mapping matches what SSSD computes on the desktops | Prerequisites, Domain & Horizon Agent |
| ☐ | Horizon Agent | Tarball for the current agent version staged in files/. horizon_agent_install_flags reviewed (audio, SSO, printing) for that version | Domain & Horizon Agent |
| ☐ | Horizon Agent | Prerequisite package list in the horizon_prereqs role cross-checked against Omnissa’s list for that agent version | Base system |
| ☐ | Access | linux_login_group is the pool’s AD group. It controls both who can log in and who gets sudo | Domain & Horizon Agent, Desktop setup & shutdown |
| ☐ | Applications | Package list in the apps role trimmed or extended to what the pool needs | Desktop setup & shutdown |
| ☐ | Horizon | An existing instant-clone desktop pool. horizon_connection_server, horizon_pool_name and a REST API account with only the rights needed. The REST field names checked against your Connection Server’s API reference | Publish |
| ☐ | Test build | One full build through build.sh, published to a reference or staging pool before the live pool is touched | Publish |
Troubleshooting
When a build fails, open the build transcript first: it shows which role or step stopped and why. Ansible runs over SSH, so the transcript also holds the output of every in-guest step.
Where the logs are
| Log | Location | What it shows |
|---|---|---|
| Build transcript | Control node: images/ubt_2404_tpl/logs/<published VM name>.log | Everything build.sh, Packer and Ansible printed for that run, one file per build. Start here |
| Installer logs | Build VM: /var/log/installer/ | What autoinstall and curtin did, including the kernel install |
| SSSD | Build VM or a clone: /var/log/sssd/ and journalctl -u sssd | Login, group lookup and ID mapping problems |
Common problems
| Symptom | Likely cause and fix | See |
|---|---|---|
| build.sh refuses to start | Pre-flight found a leftover ubt-2404-tpl VM, or the name doesn’t resolve to the reserved IP. Let build.sh delete the old VM, or fix DHCP and DNS | Prerequisites |
| The plain interactive installer opens, no error | Keystrokes were dropped on the way to GRUB. Raise boot_keygroup_interval | Provisioning |
| Install fails while fetching the kernel | The build VM can’t reach archive.ubuntu.com | Prerequisites |
| Packer waits a long time for SSH | Normal: autoinstall plus first boot is slow, and the timeouts are 45 minutes. Watch the VM console in vCenter | Provisioning |
| Join fails with “KDC reply did not match expectations” | The realmd/adcli path. The role uses net ads join to avoid it, or publish the _kerberos-master SRV records in AD DNS | Domain & Horizon Agent |
| Join fails, computer object already exists | The join account lacks Delete Computer Objects on computer_ou_dn, so the stale object wasn’t removed | Domain & Horizon Agent |
| Users can’t log in, or get no home directory | Check linux_login_group, the NFS UID mapping and nfs_server/nfs_export_path, then the SSSD log | Domain & Horizon Agent |
| Cleanup fails on netplan | The static build-time config isn’t in the netplan file the role expects | Desktop setup & shutdown |
| Pool push fails with an API error | Horizon REST field names differ on your Connection Server. Compare with its swagger-ui reference | Publish |
Omnissa Documentation:
- Prepare a Linux Machine for Remote Desktop Deployment
- Create a Virtual Machine and Install Linux
- Install Dependency Packages for Horizon Agent
- System Requirements for Horizon Agent for Linux
- Install Horizon Agent on a Linux Machine
- Command-line Options for Installing Horizon Agent for Linux
Other Sources:
- My GitHub repository: bjosoren/omnissa
- The manual procedure this automates: Omnissa Horizon – Ubuntu 24.04 Desktop Instant Clone Template
- The platform this runs on: Omnissa Horizon Golden Images – Setting Up the Automation Platform
Disclaimer: Every tips/tricks/posting I have published here, is tried and tested in different IT-solutions. It is not guaranteed to work everywhere, but is meant as a tip for other users out there. Remember, Google is your friend and don’t be afraid to steal with pride! Feel free to comment below as needed.