How to create a UBOS development VM for UTM on x86_64 Apple computers

/docs/linux/developer/create-developer-vm/x86_64-utm/

Install UTM

  1. Download and install UTM if you have not done so already.

Obtain an Arch Linux x86_64 image

  1. Download an ISO from a mirror listed on https://archlinux.org/download/ (e.g. archlinux-2024.03.01-x86_64.iso)

Warning

Do not use the UTM-provided Arch Linux ARM image. It would run slowly in emulation mode on an x86_64 computer, and in our experience, has reliability issues.

Run the downloaded Arch Linux VM as the virtual machine to create the development VM with

  1. In UTM, create a new virtual machine:

    • Click “Create a New Virtual Machine”.

    • Select “Virtualize” (not “Emulate”).

    • In “Operating System”, select “Linux”.

    • In “Linux”, in the section “Boot ISO Image”, browse to your downloaded ISO file and select that. Leave the options unchecked. Click “Continue”.

    • In “Hardware”, accept the default and click “Continue.”

    • In “Storage”, enter 60GB as the “size of the drive”. Click “Continue”.

    • In “Shared Directory”, click “Continue”.

    • In “Summary”:

      • Change the name to “ubosdev_x86_64-utm-YYYYMMDD-I” where YYYYMMDD is the date, and I is a number starting with 1. (Don’t add a UBOS channel to the name, as this is Arch.)

      • Select “Open VM Settings”

      • Click “Save”.

    • In the now-open settings dialog:

      • In the outline on the left, select the second “IDE Drive” (the one whose Image Type is “Disk Image”, not “CD/DVD”) and in the popup you get by clicking right, click “Delete”. The wizard creates a virtual IDE drive, but we want something else.

      • In the outline on the left, section “Drives”, click “New”, and then Interface “VirtIO” and size 60GB. Click “Create”.

      • Click “Save”.

  2. Now select “ubosdev_x86_64-utm-YYYYMMDD-I” in the sidebar, and start it by clicking the run icon (">”). Accept the defaults in the boot loader (or just wait) and wait until the boot sequence ends and the root shell appears.

Install Arch on the empty disk and configure it

  1. Update the bootstrap VM and install some packages we need:

    # pacman -Sy
    # pacman -S archlinux-keyring
    # pacman -Su
    # pacman -S btrfs-progs gptfdisk parted dosfstools arch-install-scripts vi
    
  2. Zero out the first bytes on the disk for extra robustness:

    # dd if=/dev/zero of=/dev/vda bs=1M count=8 conv=notrunc
    
  3. Clear the partition table:

    # sgdisk --clear /dev/vda
    
  4. Create the partitions (UEFI, /boot and /) and change them to the right types:

    # sgdisk --new=1::+1M /dev/vda
    # sgdisk --new=2::+512M /dev/vda
    # sgdisk --new=3:: /dev/vda
    # sgdisk --typecode=1:EF02 /dev/vda
    # sgdisk --typecode=2:EF00 /dev/vda
    
  5. Make sure changes are in effect:

    # sync
    # partprobe /dev/vda
    
  6. Create filesystems for partitions other than the UEFI partition:

    # mkfs.vfat  /dev/vda2
    # mkfs.btrfs /dev/vda3
    
  7. Mount the partitions so we can install:

    # mount /dev/vda3 /mnt
    # mkdir /mnt/boot
    # mount /dev/vda2 /mnt/boot
    
  8. Perform the actual install:

    # pacstrap /mnt base
    
  9. Generate the right fstab:

    # genfstab -U -p /mnt >> /mnt/etc/fstab
    
  10. Chroot into your future root disk and continue the installation:

    # arch-chroot /mnt
    
    1. Add the UBOS keyring so we can install our own packages:

      # curl -O https://depot.ubosfiles.net/yellow/$(uname -m)/os/ubos-keyring-0.9-2-any.pkg.tar.zst
      # pacman -U ubos-keyring-0.9-2-any.pkg.tar.zst
      # rm ubos-keyring-0.9-2-any.pkg.tar.zst
      
    2. Add the UBOS tools repo:

      # echo '' >> /etc/pacman.conf
      # echo '[ubos-tools-arch]' >> /etc/pacman.conf
      # echo 'Server = https://depot.ubosfiles.net/yellow/$arch/ubos-tools-arch' >> /etc/pacman.conf'
      
    3. Install more packages:

      # pacman -Sy
      # pacman -S linux mkinitcpio sudo vim btrfs-progs spice-vdagent qemu-guest-agent \
        gdm gnome-console gnome-control-center gnome-session gnome-settings-daemon \
        gnome-shell gnome-keyring nautilus \
        ubos-tools-arch
      

      If asked which alternatives to install, choose the defaults.

    4. Create a ramdisk:

      # mkinitcpio -p linux
      
    5. Configure the boot loader:

      # bootctl --path /boot install
      
    6. Install a locale:

      # perl -pi -e 's!#en_US.UTF-8 UTF-8!en_US.UTF-8 UTF-8!' /etc/locale.gen
      # locale-gen
      
    7. Set up networking:

      # echo '[Match]' > /etc/systemd/network/wired.network
      # echo 'Name=en*' >> /etc/systemd/network/wired.network
      # echo '' >> /etc/systemd/network/wired.network
      # echo '[Network]' >> /etc/systemd/network/wired.network
      # echo 'DHCP=ipv4' >> /etc/systemd/network/wired.network
      # echo 'IPv4Forwarding=1' >> /etc/systemd/network/wired.network
      # echo 'IPv6Forwarding=1' >> /etc/systemd/network/wired.network
      
      # systemctl enable systemd-networkd systemd-resolved systemd-timesyncd
      
    8. Create a user with the right permissions and no password:

      # useradd -m ubosdev
      # chmod 755 ~ubosdev
      # passwd -d ubosdev
      # echo ubosdev ALL = NOPASSWD: ALL > /etc/sudoers.d/ubosdev
      # chmod 600 /etc/sudoers.d/ubosdev
      
    9. No root password:

      # passwd -d root
      
    10. Exit from the arch-chroot shell with ^D.

  11. Remainder of networking setup:

    # rm /mnt/etc/resolv.conf
    # ln -s /run/systemd/resolve/stub-resolv.conf /mnt/etc/resolv.conf
    
  12. Configure UEFI:

    • Loader configuration:

      # echo timeout 4 > /mnt/boot/loader/loader.conf
      # echo default arch >> /mnt/boot/loader/loader.conf
      
    • Boot entry configuration:

      # echo title Arch > /mnt/boot/loader/entries/arch.conf
      # echo linux /vmlinuz-linux >> /mnt/boot/loader/entries/arch.conf
      # echo initrd /initramfs-linux.img >> /mnt/boot/loader/entries/arch.conf
      # echo options root=PARTUUID=$(lsblk -o PARTUUID /dev/vda3 | tail -1 ) rw >> /mnt/boot/loader/entries/arch.conf
      
  13. Power off the virtual machine:

    # systemctl poweroff
    
  14. Select VM “ubosdev” in the outline, and the at the bottom in the right pane, in the CD/DVD popup menu, select “Clear”.

Clean up the VM

  1. UTM has no direct functionality to do this, so we have to do it manually.

    • In UTM, select the “ubosdev_x86_64-utm-YYYYMMDD-I” virtual machine. In the popup, select “Show in Finder”.

    • Quit UTM.

    • In the Finder, keep “ubosdev_x86_64-utm-YYYYMMDD-I.utm” selected and in the popup, select “Show package contents”.

    • In the Finder, open “config.plist” with a text editor.

    • In the XML file, find the entry <key>Drive</key> and the two-element array that follows right after. They are likely towards the beginning of the file.

    • This array contains two dicts, the first representing the virtual CD/DVD drive, and the second the new disk.

    • In the text editor, delete the first dict (representing the CD/DVD) in its entirety.

    • Save “config.plist” and close the text editor.

Remaining configuration

  1. Run UTM again.

  2. Start the “ubosdev_x86_64-utm-YYYYMMDD-I” VM again and wait until the login prompt appears.

  3. At the console, log in as ubosdev. There is no password.

  4. Fix the locale (command won’t run earlier)

    % sudo localectl set-locale LANG=en_US.UTF-8
    
  5. Enable Gnome:

    % sudo systemctl enable gdm
    
  6. Power off the virtual machine:

    % sudo systemctl poweroff
    

Add virtual graphics for the “ubosdev” VM

  1. In UTM, select the “ubosdev_x86_64-utm-YYYYMMDD-I” VM, and in the popup, “Edit”.

  2. Under “Devices”, click “New…” and “Display”.

  3. Click “Save”.

Now your virtual machine is in the same state as the pre-configured development VM described in ../setup/.

Create a file for distribution

  1. In UTM, select the “ubosdev” VM and in the popup, “Share…”

  2. Save the file with a name that also contains the processor architecture, the current date and an index: ubosdev_x86_64-utm-YYYYMMDD-I.utm, e.g. ubosdev_x86_64-utm-20240405-1.utm. (Don’t add a UBOS channel to the name, as this is Arch.)

  3. The .utm file is actually a directory. Put it into a zip file:

    % zip -r ubosdev_x86_64-utm-YYYYMMDD-I.utm.zip ubosdev_x86_64-utm-YYYYMMDD-I.utm