Skip to content

create-ubuntu-cloud-template: fix snippet logins, failure cleanup and profile bugs - #27

Merged
modem7 merged 11 commits into
masterfrom
feature/cloud-template-optimise
Sep 26, 2026
Merged

modem7 merged 11 commits into
masterfrom
feature/cloud-template-optimise

Conversation

@modem7

@modem7 modem7 commented Sep 26, 2026 •

Copy link
Copy Markdown
Owner

Summary

Fixes several bugs in create-ubuntu-cloud-template.sh, makes it faster and safer, and restructures the comments and help text so they're easier to scan (AuDHD-friendly).

Bug fixes

  • Snippet attached as user= broke logins on clones. In Proxmox, custom user-data replaces the generated user-data (confirmed in PVE/QemuServer/Cloudinit.pm), so clones lost the user, password, SSH keys and hostname. It is now attached as vendor=, which merges with the Cloud-Init tab.
  • Failure cleanup never ran inside functions. Without set -E the ERR trap isn't inherited, so failures left half-built VMs behind. An EXIT trap now handles errors, die and Ctrl+C.
  • --force-overwrite destroyed the old template first. It was destroyed before the summary prompt and before the download, so cancelling or a failed download left you with no template. You now confirm early, but the destroy happens only just before qm create, after the safety checks run again.
  • Pressing Enter could confirm an overwrite. For a template with no name, an empty Enter matched the empty name. Unnamed VMs are now confirmed by ID.
  • Invalid names and tags failed late. A template name that isn't DNS-style (e.g. ubuntu_noble) or a bad tag was only rejected by qm create, after the download or after the old template was destroyed. Both are now checked up front.
  • SNIPPETS_STOR from the profile was reset to empty after loading, so snippets never worked in unattended runs. Declining the snippet interactively now also clears it from the profile.
  • The saved template name was overwritten by _set_distro_vars.
  • A profile could set FORCE_OVERWRITE / UNATTENDED / I_KNOW, which broke the "command line only" guarantee.
  • GitHub users with more than one key crashed the comment check.
  • Unattended runs stopped at a prompt when the cached image was out of date.
  • The disk was attached by guessing vm-ID-disk-0, which could attach an old leftover disk. The disk now uses import-from.
  • The VM ID check used qm list, which misses containers and VMs on other nodes. It now reads /etc/pve/.vmlist.
  • Storage filtering: plain iSCSI (which can't create disks) is no longer offered, and BTRFS gets raw instead of the unsupported qcow2.
  • The checksum lookup matched by substring. It now matches the exact filename.
  • The final message said "not a template" even after converting.
  • The VM notes now match whether a snippet was actually attached, and the re-templating commands in them can be pasted and run.
  • Numbers with a leading zero such as 08 or 010 are now read as base 10, not octal.
  • Package lists with spaces (curl, git, as saved by the old script) are now handled.

Improvements

  • CPU and memory are now asked for instead of being fixed in the script:

    • Sockets: only asked on hosts with more than one NUMA node. NUMA is switched on for the VM when sockets > 1.
    • Cores: limited to the host's CPU threads.
    • Memory: limited to the host's RAM.
    • Ballooning: a yes/no, then a minimum memory. No sets balloon=0.
    • All four are saved to profiles (SOCKETS, CORES, MEM, BALLOON) and checked before unattended runs. They appear in the summary and the VM notes.
  • Network bridge is now asked for, from the bridges on the host (Linux bridges, SDN vnets, vmbr* Open vSwitch bridges). Before, it was fixed at vmbr1, which doesn't exist on most installs. Proxmox only checks the bridge when a VM starts, so every clone would have failed to boot. The script default is now vmbr0.

  • Checked against the Proxmox docs:

    • Every qm / pvesm option matches the qm(1) manual, the qemu-server Drive.pm schema and the Cloud-Init wiki.
    • The EFI disk now uses the documented STORAGE:1,efitype=4m,... form instead of STORAGE:0,...,size=1M. STORAGE:0 is only for import-from, and the EFI size is ignored.
    • The snippet prompt now notes that snippet storage must be reachable from every node a clone may run on (a Cloud-Init wiki requirement).
  • Disk cache: none for all ZFS storage types (zfspool and ZFS over iSCSI), writethrough for everything else. It's shown in the summary and the VM notes.

  • Header: author, MIT licence (Copyright (c) 2026 Alex Lane), SPDX identifier, and repo, latest-version, issues and support links, so a copy shared outside the repo still points home.

  • The Ubuntu menu shows version numbers. It is sorted by release and labels LTS, end-of-life, Ubuntu Pro-only and development releases. The data comes from Ubuntu's meta-release lists (the same ones do-release-upgrade uses), and the menu falls back to plain codenames if they can't be fetched:

      1) bionic     18.04 LTS  (Ubuntu Pro only)
      4) noble      24.04 LTS
      5) questing   25.10      (end of life)
      7) stonking   26.10      (in development)
    

    The VM notes and summary now say e.g. Ubuntu 24.04 LTS (Noble).

  • One virt-customize call instead of two, and Ubuntu versions are checked in parallel.

  • SSH keys are checked with ssh-keygen, so a private key can't be saved or injected.

  • /etc/machine-id is blanked in the image so clones don't share DHCP leases.

  • Profile values are escaped when saved, and profiles are always chmod 600, including ones written by older versions.

  • Password input is hidden and confirmed, and a generated password is shown at the end.

  • VLAN, disk size, bridge, VM ID, template name, tags, CPU, memory and menu choices are validated. In unattended runs, profile values are checked before any work starts.

  • The script checks it is running as root and no longer needs python3.

Behaviour changes

  • The cached image moved from /tmp to /var/tmp/ubuntu-cloud-template (can be changed with WORK_DIR). Expect one re-download.
  • DISK_STOR_DEFAULT changed from local to local-lvm.
  • The snippet file is now <name>-vendor-data.yaml. An existing snippet is never overwritten.
  • New question: "Upgrade packages on first boot?" It sets the Proxmox ciupgrade option ("Upgrade packages" in the Cloud-Init tab), so the setting is visible and changeable per VM in the GUI. It's saved in profiles as CI_UPGRADE (1/0). The default is off, the same as before, and older profiles keep that behaviour.
  • The snippet is now only for your own extras. It's an optional place for first-boot settings such as packages and runcmd, starting as commented-out examples. It no longer does package upgrades (Proxmox handles that now) or growpart (cloud-init already does it on every boot). ssh_pwauth: true is only switched on when no SSH key was set.
  • cloud-init is now listed in VIRT_PKGS. It's preinstalled in the cloud images, so this is a no-op safeguard.
  • An interactive yes/no to template conversion is saved to the profile, as the original comments intended.
  • With --force-overwrite, if qm create fails after the old template was destroyed, the old template is still gone. That window is now as short as possible: after the download, the customise step and all validation.

Testing

  • bash -n and shellcheck -S warning pass.
  • Dry runs with mocked qm, pvesm, wget, virt-customize and /etc/pve. Scenarios checked:
    • interactive runs, unattended runs and invalid input
    • VM ID clash
    • a profile trying to set FORCE_OVERWRITE
    • failed import, including input running out at the final prompt (the finished template is kept)
    • overwrite, including cancelling at the summary, a failed download, a successful run and a running VM
    • bad name and tags, leading-zero numbers
    • profile round-trip with quotes and $ in values
  • Repeated full-script review passes (not just the diff), fixing until a pass came back clean.
  • Run once on a real Proxmox host (checks import-from, pvesm path for snippets and --run-command).

… and profile bugs

Attach the snippet as vendor-data instead of user-data, so clones keep the Proxmox user, password, SSH keys and hostname. Run cleanup on every failure (set -E plus an EXIT trap). Keep SNIPPETS_STOR and the template name from the profile. Keep safety flags command-line only. Use import-from for the disk, check VM IDs cluster-wide, validate SSH keys, merge the virt-customize calls, and restructure comments and help for readability.
Stop protecting the VM on exit before cleanup runs, so a failure or Ctrl+C at the final prompt no longer destroys the finished template. chmod 600 profiles after writing, since umask only affects new files. Write the VM notes after the snippet step so they match what was attached. Hide plain iSCSI storages, which cannot allocate new disks.
…te confirm

Clear SNIPPETS_STOR when the user declines the snippet, so later unattended runs don't re-enable it. Confirm overwrite of an unnamed VM by its ID, so an empty Enter can no longer match an empty name.
…he image is ready

Confirm the overwrite early but destroy the old template only just before create_vm, re-running the safety checks. Cancelling at the summary, or a failed download or customise, no longer leaves you with no template. Also strip spaces from package lists written by older profiles.
…bers as base 10

Reject invalid template names and tags before any work starts, so qm create can no longer fail after a long download or after --force-overwrite has destroyed the old template. Parse numeric input as base 10 so 08/09 no longer error and 010 is not read as octal. Correct the comment about GitHub key references.
Map codenames to version numbers using Ubuntu's meta-release lists, sort the menu by version, and flag LTS, end-of-life, Ubuntu Pro-only and development releases. Falls back to plain codenames if the lists can't be fetched. OS_NAME now reads e.g. 'Ubuntu 24.04 LTS (Noble)'.
SSH keys from the Cloud-Init tab and root disk growth already work without the snippet, so stop implying otherwise. Drop the redundant growpart block, only enable ssh_pwauth when no SSH key was set, and word the prompt and VM notes to match. List cloud-init in VIRT_PKGS as a safeguard.
…ox ciupgrade

Add a CI_UPGRADE setting (prompted, saved in profiles, default off) that sets Proxmox's 'Upgrade packages' cloud-init option, so upgrades are visible and changeable per VM in the GUI. Remove package_update/upgrade from the snippet, which is now an optional place for your own first-boot config (plus password login when no SSH key is set).
…nce header

Ask for cores and memory with sensible defaults and host limits, ask for sockets on NUMA hosts (NUMA on when sockets > 1), and make ballooning a yes/no with a minimum. Values are saved to profiles and checked in unattended runs. Use cache=none for all ZFS storage types. Add author, MIT licence, repo and support links to the header.
…ofile values, follow qm docs

Ask for the bridge from the ones on this host (the default vmbr1 doesn't exist on most installs, which left clones unable to start), and default to vmbr0. Check NET_BRIDGE, VLAN and DISK_SIZE from profiles before any work, so they can't fail after an overwrite destroyed the old template. Allocate the EFI disk with the documented STORAGE:1 form instead of STORAGE:0,size=1M. Keep the optional release lookup to one quick try, base the VM notes on the snippet file actually attached, and warn that snippet storage must be reachable from every cluster node.
An empty value or a plain NIC such as eth0 passed the existence check. Check membership in the host's bridge list instead.
@modem7
modem7 merged commit edc31ae into master Sep 26, 2026
1 check passed
@modem7
modem7 deleted the feature/cloud-template-optimise branch September 26, 2026 13:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant