Skip to content

Instantly share code, notes, and snippets.

@avoidik
Created June 14, 2024 09:35
Show Gist options
  • Save avoidik/2ea19ae9b5f128b1a60c363ba96c5c47 to your computer and use it in GitHub Desktop.
Save avoidik/2ea19ae9b5f128b1a60c363ba96c5c47 to your computer and use it in GitHub Desktop.
Run Ubuntu ARM on Mac M1 or similar

How to run Ubuntu on Mac

We're going to run Ubuntu (ARM) on Mac M1 (ARM) using the native binary translation, thanks to up-to-date version of qemu which has native hardware support.

Prerequisites

Install qemu and required tools (coreutils - we need truncate/gtruncate, dd/gdd CLI tools, samba - to share files between host and guest VM, yq - to insert SSH public key into the cloud-init configuration).

$ brew install qemu coreutils samba yq

Download ISO

$ curl -fsSL https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-arm64.img -o cloudimg.img

and optionally standalone kernel

$ curl -fsSL https://cloud-images.ubuntu.com/noble/current/unpacked/noble-server-cloudimg-arm64-initrd-generic -o initrd
$ curl -fsSL https://cloud-images.ubuntu.com/noble/current/unpacked/noble-server-cloudimg-arm64-vmlinuz-generic -o vmlinuz

Prepare UEFI

Prepare UEFI firmware image

$ gtruncate -s 64m efi.img
$ gdd if=/opt/homebrew/opt/qemu/share/qemu/edk2-aarch64-code.fd of=efi.img conv=notrunc

Prepare UEFI variables image

$ gtruncate -s 64m vars.img
$ gdd if=/opt/homebrew/opt/qemu/share/qemu/edk2-arm-vars.fd of=vars.img conv=notrunc

Prepare cloud-init config

Generate SSH key-pair

$ ssh-keygen -t rsa -b 2048 -q -f id_rsa_qemu -N "" -C ""
$ chmod 0600 id_rsa_qemu

Install required package

$ brew install cdrtools
$ curl -fsSL https://github.com/canonical/cloud-utils/raw/main/bin/cloud-localds -o cloud-localds
$ sed -i 's/genisoimage/mkisofs/g' cloud-localds
$ chmod +x cloud-localds

Prepare the cloud-init configuration by using the misc files network-config.yml, meta-data.yml, and user-data.yml.tpl attached below.

$ PUBKEY="$(cat ./id_rsa_qemu.pub)" yq '.users[0].ssh-authorized-keys[0] = strenv(PUBKEY)' user-data.yml.tpl > user-data.yml
$ ./cloud-localds --disk-format qcow2 --network-config network-config.yml cloud-init.img user-data.yml meta-data.yml

This will assign the generated SSH public key to a user in the cloud-init configuration file.

Install Ubuntu

Prepare system disk image (put as much space as you need)

$ cp cloudimg.img ubuntu.img
$ qemu-img resize ubuntu.img 60G

Prepare Samba shared directory

$ mkdir smb

Start VM

$ qemu-system-aarch64 \
 -m 4G -smp 4 -cpu host -M virt -accel hvf \
 -nographic \
 -drive if=pflash,format=raw,file=efi.img,readonly=on \
 -drive if=pflash,format=raw,file=vars.img \
 -device virtio-net-device,netdev=net \
 -netdev user,id=net,ipv6=off,hostfwd=tcp::2222-:22,smb="$(pwd)/smb" \
 -drive if=none,id=hd0,format=qcow2,file=ubuntu.img,discard=unmap,detect-zeroes=unmap \
 -device virtio-blk-device,drive=hd0 \
 -drive if=none,id=cloud,format=qcow2,file=cloud-init.img,readonly=on \
 -device virtio-blk-device,drive=cloud \
 -device virtio-rng-pci \
 -device virtio-balloon \
 -rtc base=utc,clock=host \
 -kernel vmlinuz \
 -initrd initrd \
 -append 'root=/dev/vdb1 console=ttyS0 mitigations=off quiet nosplash' \
 -monitor telnet::45454,server,nowait -serial mon:stdio

TPM2

If you are looking for the TPM2 software emulation.

$ brew install swtpm

Make sure VM is turned off, then start swtpm as daemon (it will be running in the background)

$ swtpm_setup --create-config-files skip-if-exist
$ swtpm_setup --tpmstate "$(pwd)/tpm" --tpm2 --create-ek-cert --create-platform-cert
$ swtpm socket --tpmstate dir="$(pwd)/tpm" --ctrl type=unixio,path="$(pwd)/tpm/swtpm-sock" --tpm2 --daemon

Add these command-line parameters

$ qemu-system-aarch64 \
 -chardev socket,id=chrtpm,path=./tpm/swtpm-sock \
 -tpmdev emulator,id=tpm0,chardev=chrtpm \
 -device tpm-tis-device,tpmdev=tpm0 \
 ...

To terminate TPM2 daemon

$ kill -INT $(pidof swtpm)
instance-id: ubuntuvm
local-hostname: ubuntuvm
version: 2
ethernets:
eth0:
match:
name: eth*
dhcp4: true
dhcp6: false
enp0s1:
match:
name: enp0s1
dhcp4: true
dhcp6: false
#cloud-config
package_update: false
package_upgrade: false
users:
- name: vja
ssh-authorized-keys: []
sudo: ALL=(ALL) NOPASSWD:ALL
groups: adm,cdrom,sudo,dip,plugdev
shell: /bin/bash
bootcmd:
- mkdir -p /mount/qemu
runcmd:
- while fuser /var/{lib/{dpkg,apt/lists},cache/apt/archives}/lock >/dev/null 2>&1; do sleep; done
- DEBIAN_FRONTEND=noninteractive apt update -y > /dev/null
- DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt install -y cifs-utils linux-modules-extra-$(uname -r) > /dev/null
- echo '//10.0.2.4/qemu /mount/qemu cifs guest,uid=vja,gid=vja,iocharset=utf8,file_mode=0644,dir_mode=0755,noperm,_netdev,nofail 0 0' | tee -a /etc/fstab > /dev/null
- mount /mount/qemu
@avoidik
Copy link
Author

avoidik commented Jun 14, 2024

Alpine

There are two options to install Alpine Linux, prepare system disk image at first

$ qemu-img create -f qcow2 alpine.img 10G

Install over Netboot

Download kernel

$ curl -fsSLO https://dl-cdn.alpinelinux.org/alpine/v3.20/releases/aarch64/netboot/initramfs-lts
$ curl -fsSLO https://dl-cdn.alpinelinux.org/alpine/v3.20/releases/aarch64/netboot/vmlinuz-lts

Install over netboot (without ISO)

$ qemu-system-aarch64 \
 -m 4G -smp 4 -cpu host -M virt -accel hvf \
 -nographic \
 -bios edk2-aarch64-code.fd \
 -device virtio-net-device,netdev=net \
 -netdev user,id=net,ipv6=off \
 -drive if=virtio,id=hd0,index=0,format=qcow2,file=alpine.img,media=disk,discard=unmap,detect-zeroes=unmap \
 -rtc base=utc,clock=host \
 -kernel vmlinuz-lts \
 -initrd initramfs-lts \
 -append 'console=tty0 modules=loop,squashfs,sd-mod,usb-storage alpine_repo=https://dl-cdn.alpinelinux.org/alpine/v3.20/main modloop=https://dl-cdn.alpinelinux.org/alpine/v3.20/releases/aarch64/netboot/modloop-lts'

Install using ISO

$ curl -fsSLO https://dl-cdn.alpinelinux.org/alpine/v3.20/releases/aarch64/alpine-standard-3.20.0-aarch64.iso

Install Alpine from ISO

$ qemu-system-aarch64 \
 -m 4G -smp 4 -cpu host -M virt -accel hvf \
 -nographic \
 -bios edk2-aarch64-code.fd \
 -device virtio-net-device,netdev=net \
 -netdev user,id=net,ipv6=off \
 -drive if=virtio,id=sr0,index=1,format=raw,file=alpine-standard-3.20.0-aarch64.iso,media=cdrom,readonly=on \
 -drive if=virtio,id=hd0,index=0,format=qcow2,file=alpine.img,media=disk,discard=unmap,detect-zeroes=unmap \
 -boot c \
 -rtc base=utc,clock=host

Run

Regardless of install option, run Alpine

$ qemu-system-aarch64 \
 -m 4G -smp 4 -cpu host -M virt -accel hvf \
 -nographic \
 -bios edk2-aarch64-code.fd \
 -device virtio-net-device,netdev=net \
 -netdev user,id=net,ipv6=off,hostfwd=tcp::2222-:22 \
 -drive if=virtio,id=hd0,index=0,format=qcow2,file=alpine.img,media=disk,discard=unmap,detect-zeroes=unmap \
 -rtc base=utc,clock=host

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment