Skip to content

Instantly share code, notes, and snippets.

@franzwong
Last active July 16, 2024 03:30
Show Gist options
  • Save franzwong/a349607a02ca1954cd8f787bf78ac5ad to your computer and use it in GitHub Desktop.
Save franzwong/a349607a02ca1954cd8f787bf78ac5ad to your computer and use it in GitHub Desktop.
Start VM with QEMU on MacOS (Apple silicon cpu)

Start VM with QEMU on MacOS

  1. Install qemu
brew install qemu
  1. Download QEMU_EFI.fd

Get one of the .tar.gz from the following link.

https://gist.github.com/theboreddev/5f79f86a0f163e4a1f9df919da5eea20

I pick QEMU_EFI-a096471-edk2-stable202011.tar.gz.

curl -O https://gist.github.com/theboreddev/5f79f86a0f163e4a1f9df919da5eea20/raw/f546faea68f4149c06cca88fa67ace07a3758268/QEMU_EFI-a096471-edk2-stable202011.tar.gz

Decompress to get QEMU_EFI.fd.

tar -xvf QEMU_EFI-a096471-edk2-stable202011.tar.gz
  1. Download Ubuntu cloud image

Download it from Ubuntu.

curl -O https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-arm64.img
  1. Generate SSH key pair
ssh-keygen -t ed25519 -C "my_testing_key" -f "id_ed25519" -N '' <<< y

The above command will generate 2 files id_ed25519 (Private key) and id_ed25519.pub (Public key).

  1. Create user-data file (cloud-init configuration)
public_key=$(cat id_ed25519.pub)

cat << EOF > user-data
#cloud-config
users:
  - name: franz
    shell: /bin/bash
    sudo: "ALL=(ALL) NOPASSWD:ALL"
    primary_group: my_test_group
    groups: sudo
    lock_passwd: true
    ssh_authorized_keys:
      - "${public_key}"

groups:
  - my_test_group

disable_root: true

mounts:
  - ["shared", "/mnt/shared", "9p", "trans=virtio,version=9p2000.L", "0", "0"]

output:
  all: ">> /var/log/cloud-init-output.log"

package_update: true

packages:
  - bindfs

runcmd:
  - groupmod -g 1000 my_test_group
  - ufw default deny incoming
  - ufw default allow outgoing
  - ufw allow 22/tcp
  - ufw enable
  - sed -i -e '/^PermitRootLogin/s/^.*$/PermitRootLogin no/' /etc/ssh/sshd_config
  - sed -i -e '$aAllowUsers franz' /etc/ssh/sshd_config
  - restart ssh
  - bindfs --map=501/1000:@20/@1000 /mnt/shared /mnt/shared

EOF
  1. Create vendor-data file (cloud-init configuration)
touch vendor-data
  1. Create meta-data file (cloud-init configuration)
cat << EOF > meta-data
instance-id: my-test-instance

EOF
  1. Start ad hoc IMDS server

IMDS stands for "Instance Metadata Service". It can expose cloud-init configuration to VM.

(This command blocks. You need to start another terminal session for the remaining steps.)

python3 -m http.server --directory .
  1. Create shared folder
mkdir -p shared
  1. Enlarge cloud image
qemu-img resize noble-server-cloudimg-arm64.img +50G
  1. Start VM with qemu

(This command blocks. You need to start another terminal session for the remaining steps.)

qemu-system-aarch64                                                           \
    -machine type=virt-9.0,accel=hvf                                          \
    -bios QEMU_EFI.fd                                                         \
    -cpu host                                                                 \
    -m 1024                                                                   \
    -nographic                                                                \
    -hda noble-server-cloudimg-arm64.img                                      \
    -virtfs local,path=shared,mount_tag=shared,security_model=mapped-xattr    \
    -smbios type=1,serial=ds='nocloud;s=http://10.0.2.2:8000/'                \
    -netdev user,id=mynet0,hostfwd=tcp::2222-:22                              \
    -device e1000,netdev=mynet0

-m 1024 - Allocate 1024MB memory

-virtfs local,path=shared,mount_tag=shared,security_model=mapped-xattr - Share host folder shared with the VM

-smbios type=1,serial=ds='nocloud;s=http://10.0.2.2:8000/ - Tell cloud-init where IMDS is

-hda noble-server-cloudimg-arm64.img - Use cloud image as virtual hard disk

-netdev user,id=mynet0,hostfwd=tcp::2222-:22 - Map host port 2222 to VM port 22

  1. Connect to VM
ssh -p 2222 -i "id_ed25519" franz@localhost
  1. Wait for cloud-init complete

(Run in VM)

sudo cloud-init status --wait
  1. Check shared folder

(Run in VM)

ls -l -d /mnt/shared

That's it.

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