summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorBug Magnet <marco.nikic@mullvad.net>2024-08-12 11:14:40 +0200
committerBug Magnet <marco.nikic@mullvad.net>2024-08-12 11:14:40 +0200
commit08b9431171b3ecc042d8697a582ae380c168cd01 (patch)
tree57460e63ab5f19e86a0f78580f8a10602a15d16d
parent5b035e22e7f846b4a72071b002c159d3a311fa69 (diff)
downloadmullvadvpn-08b9431171b3ecc042d8697a582ae380c168cd01.tar.xz
mullvadvpn-08b9431171b3ecc042d8697a582ae380c168cd01.zip
Add packer scripts to create VMs and install Xcode on them
-rw-r--r--ci/ios/create-vm/README.md66
-rw-r--r--ci/ios/create-vm/install-build-dependencies.pkr.hcl113
-rw-r--r--ci/ios/create-vm/install-vanilla-ventura.pkr.hcl129
-rw-r--r--ci/ios/create-vm/scripts/add-apple-certs.sh9
-rw-r--r--ci/ios/create-vm/scripts/cleanup.sh23
-rw-r--r--ci/ios/create-vm/scripts/install-brew-dependencies.sh14
-rw-r--r--ci/ios/create-vm/scripts/install-brew.sh20
-rw-r--r--ci/ios/create-vm/scripts/install-go.sh13
-rw-r--r--ci/ios/create-vm/scripts/install-rustup.sh19
-rw-r--r--ci/ios/create-vm/scripts/install-xcode.sh10
-rw-r--r--ci/ios/create-vm/scripts/link-zprofile.sh12
-rw-r--r--ci/ios/create-vm/scripts/run-xcode-first-launch.sh17
-rw-r--r--ci/ios/create-vm/variables.pkrvars.hcl5
13 files changed, 450 insertions, 0 deletions
diff --git a/ci/ios/create-vm/README.md b/ci/ios/create-vm/README.md
new file mode 100644
index 0000000000..04c99059f6
--- /dev/null
+++ b/ci/ios/create-vm/README.md
@@ -0,0 +1,66 @@
+# Creating new macOS VMs to build MullvadVPN iOS in a CI environment
+This guide assumes you are running on macOS.
+## Prerequisites
+In order to create VMs on the fly, we decided to use [tart](https://tart.run/) and [packer](https://developer.hashicorp.com/packer).
+
+The various scripts that run in the VM are written in bash with the help of [shellcheck](shellcheck.net).
+
+## VM requirements
+- You will need at least 60GB of available space on your VM host
+- You will need at least 8GB of available RAM on your VM host
+- You will need at least 4 CPU cores available on your VM host
+
+## How to install Tart
+- brew install `cirruslabs/cli/tart`
+
+## How to install Packer
+- brew tap `hashicorp/tap`
+- brew install `hashicorp/tap/packer`
+
+## How to install shellcheck
+- brew install `shellcheck`
+
+> [!IMPORTANT]
+> # Prerequisite setup before running packer
+> - Get a copy of the Xcode version you want to install on the VM in a xip format
+> - Copy that file into the folder named `vm_shared_folder`
+> - Open the file named `variables.pkrvars.hcl`
+> - Edit the variables named `xcode_version` and `xcode_xip_name`
+
+Here is an example of what to expect
+```bash
+% ls vm_shared_folder
+Xcode_15.0.1.xip
+% head -2 variables.pkrvars.hcl
+xcode_version = "15.1"
+xcode_xip_name = "Xcode_15.1.xip"
+```
+
+### Sanity checks before running packer
+It is a good idea to keep logs, the `logs` folder is provided to that effect.
+Enable packer logs by setting the following environment variables (assuming your are running with `zsh`)
+- export `PACKER_LOG=1`
+- export `PACKER_LOG_PATH="logs/packer_logs.txt"`
+
+> [!NOTE]
+> The logs will be overwritten with each packer command you issue.
+
+You can then check that the templates are valid before running `packer`
+- packer inspect `-var-file="variables.pkrvars.hcl" install-build-dependencies.pkr.hcl`
+- packer validate `-var-file="variables.pkrvars.hcl" install-build-dependencies.pkr.hcl`
+
+You can make sure you are not missing any dependencies with the `init` command
+- packer init `install-vanilla-ventura.pkr.hcl`
+
+### Create the VM image via Packer
+Once your setup is ready, you just need one command to create a VM. And one more to install Xcode on it.
+- packer build `-var-file="variables.pkrvars.hcl" install-vanilla-ventura.pkr.hcl`
+
+### Install Xcode on the VM image via Packer
+- packer build `-var-file="variables.pkrvars.hcl" install-build-dependencies.pkr.hcl`
+
+> [!IMPORTANT]
+> At the time of writing this, `tart` does not support VM snapshotting. This means that any action taken by packer will be **permanent** on the VM.
+
+Make sure to properly clean up the VM before running packer commands again if something went wrong.
+You can look at the `cleanup.sh` script in the `scripts` folder to see what type of cleanup is ran in case things go wrong. \ No newline at end of file
diff --git a/ci/ios/create-vm/install-build-dependencies.pkr.hcl b/ci/ios/create-vm/install-build-dependencies.pkr.hcl
new file mode 100644
index 0000000000..deeacf609f
--- /dev/null
+++ b/ci/ios/create-vm/install-build-dependencies.pkr.hcl
@@ -0,0 +1,113 @@
+packer {
+ required_plugins {
+ tart = {
+ version = ">= 1.2.0"
+ source = "github.com/cirruslabs/tart"
+ }
+ }
+}
+
+variable "shared_folder_path" { type = string }
+
+variable "xcode_version" {
+ type = string
+
+ validation {
+ condition = can(regex("(\\d)+(\\.)?((\\d)+)?(\\.)?((\\d)+)?", var.xcode_version))
+ error_message = "Invalid Xcode version number. Example of a valid number: '15.0.1'."
+ }
+}
+
+variable "vm_name" { type = string }
+
+variable "user_name" { type = string }
+
+variable "xcode_xip_name" {
+ type = string
+
+ validation {
+ condition = can(regex("Xcode_(\\d)+(\\.)?((\\d)+)?(\\.)?((\\d)+)?\\.xip", var.xcode_xip_name))
+ error_message = "Invalid Xcode file name. Example of a valid file name: 'Xcode_15.0.1.xip'."
+ }
+}
+
+source "tart-cli" "tart" {
+ vm_name = "${var.vm_name}"
+ ssh_password = "admin"
+ ssh_username = "admin"
+ ssh_timeout = "120s"
+ disk_size_gb = 80
+}
+
+build {
+ sources = ["source.tart-cli.tart"]
+
+
+ // Create a symlink for bash compatibility
+ provisioner "shell" {
+ script = "scripts/link-zprofile.sh"
+ }
+
+ // Install brew
+ provisioner "shell" {
+ environment_vars = [
+ "USER=${var.user_name}"
+ ]
+ script = "scripts/install-brew.sh"
+ }
+
+
+ // Install required brew dependencies
+ provisioner "shell" {
+ script = "scripts/install-brew-dependencies.sh"
+ }
+
+ // Install rustup
+ provisioner "shell" {
+ script = "scripts/install-rustup.sh"
+ }
+
+ // Install go
+ provisioner "shell" {
+ script = "scripts/install-go.sh"
+ }
+
+ // Copy the local Xcode xip file to the VM
+ provisioner "file" {
+ source = "${var.shared_folder_path}/${var.xcode_xip_name}"
+ destination = "/tmp/${var.xcode_xip_name}"
+ }
+
+ // Install Xcode via xcodes.app
+ provisioner "shell" {
+
+ environment_vars = [
+ "XCODE_VERSION=${var.xcode_version}",
+ "XCODE_XIP_NAME=${var.xcode_xip_name}",
+ "XCODE_SHARED_PATH=/tmp",
+ ]
+ script = "scripts/install-xcode.sh"
+ }
+
+ // Delete the Xcode xip file to save some space
+ provisioner "shell" {
+ inline = [
+ "rm -f /tmp/${var.xcode_xip_name}"
+ ]
+ }
+
+ // Run the xcodebuild first launch prompt to automatically accept terms and conditions, and download the iOS runtime simulator
+ provisioner "shell" {
+ script = "scripts/run-xcode-first-launch.sh"
+ }
+
+ // Add Apple root certs
+ provisioner "shell" {
+ script = "scripts/add-apple-certs.sh"
+ }
+
+ // Remove everything in case of error
+ error-cleanup-provisioner "shell" {
+ script = "scripts/cleanup.sh"
+ }
+}
diff --git a/ci/ios/create-vm/install-vanilla-ventura.pkr.hcl b/ci/ios/create-vm/install-vanilla-ventura.pkr.hcl
new file mode 100644
index 0000000000..9629a4645e
--- /dev/null
+++ b/ci/ios/create-vm/install-vanilla-ventura.pkr.hcl
@@ -0,0 +1,129 @@
+packer {
+ required_plugins {
+ tart = {
+ version = ">= 1.2.0"
+ source = "github.com/cirruslabs/tart"
+ }
+ }
+}
+
+variable "vm_name" { type = string }
+
+source "tart-cli" "tart" {
+ # You can find macOS IPSW URLs on various websites like https://ipsw.me/
+ # and https://www.theiphonewiki.com/wiki/Beta_Firmware/Mac/13.x
+ from_ipsw = "https://updates.cdn-apple.com/2023SummerFCS/fullrestores/042-43686/945D434B-DA5D-48DB-A558-F6D18D11AD69/UniversalMac_13.5.2_22G91_Restore.ipsw"
+ vm_name = "${var.vm_name}"
+ cpu_count = 4
+ memory_gb = 8
+ disk_size_gb = 60
+ ssh_password = "admin"
+ ssh_username = "admin"
+ ssh_timeout = "120s"
+ boot_command = [
+ # hello, hola, bonjour, etc.
+ "<wait60s><spacebar>",
+ # Language: most of the times we have a list of "English"[1], "English (UK)", etc. with
+ # "English" language already selected. If we type "english", it'll cause us to switch
+ # to the "English (UK)", which is not what we want. To solve this, we switch to some other
+ # language first, e.g. "Italiano" and then switch back to "English". We'll then jump to the
+ # first entry in a list of "english"-prefixed items, which will be "English".
+ #
+ # [1]: should be named "English (US)", but oh well 🤷
+ "<wait30s>italiano<esc>english<enter>",
+ # Select Your Country and Region
+ "<wait30s>united states<leftShiftOn><tab><leftShiftOff><spacebar>",
+ # Written and Spoken Languages
+ "<wait10s><leftShiftOn><tab><leftShiftOff><spacebar>",
+ # Accessibility
+ "<wait10s><leftShiftOn><tab><leftShiftOff><spacebar>",
+ # Data & Privacy
+ "<wait10s><leftShiftOn><tab><leftShiftOff><spacebar>",
+ # Migration Assistant
+ "<wait10s><tab><tab><tab><spacebar>",
+ # Sign In with Your Apple ID
+ "<wait10s><leftShiftOn><tab><leftShiftOff><leftShiftOn><tab><leftShiftOff><spacebar>",
+ # Are you sure you want to skip signing in with an Apple ID?
+ "<wait10s><tab><spacebar>",
+ # Terms and Conditions
+ "<wait10s><leftShiftOn><tab><leftShiftOff><spacebar>",
+ # I have read and agree to the macOS Software License Agreement
+ "<wait10s><tab><spacebar>",
+ # Create a Computer Account
+ "<wait10s>admin<tab><tab>admin<tab>admin<tab><tab><tab><spacebar>",
+ # Enable Location Services
+ "<wait10s><leftShiftOn><tab><leftShiftOff><spacebar>",
+ # Are you sure you don't want to use Location Services?
+ "<wait10s><tab><spacebar>",
+ # Select Your Time Zone
+ "<wait10s><tab>UTC<enter><leftShiftOn><tab><leftShiftOff><spacebar>",
+ # Analytics
+ "<wait10s><leftShiftOn><tab><leftShiftOff><spacebar>",
+ # Screen Time
+ "<wait10s><tab><spacebar>",
+ # Siri
+ "<wait10s><tab><spacebar><leftShiftOn><tab><leftShiftOff><spacebar>",
+ # Choose Your Look
+ "<wait10s><leftShiftOn><tab><leftShiftOff><spacebar>",
+ # Enable Voice Over
+ "<wait10s><leftAltOn><f5><leftAltOff><wait5s><enter>",
+ # Now that the installation is done, open "System Settings"
+ "<wait10s><leftAltOn><spacebar><leftAltOff>System Settings<enter>",
+ # Navigate to "Sharing"
+ "<wait10s><leftAltOn>f<leftAltOff>screen sharing<enter>",
+ # Navigate to "Screen Sharing" and enable it
+ "<wait10s><tab><down><spacebar>",
+ # Navigate to "Remote Login" and enable it
+ "<wait10s><tab><tab><tab><tab><tab><tab><spacebar>",
+ # Open "Remote Login" details
+ "<wait10s><tab><spacebar>",
+ # Enable "Full Disk Access"
+ "<wait10s><tab><spacebar>",
+ # Click "Done"
+ "<wait10s><leftShiftOn><tab><leftShiftOff><leftShiftOn><tab><leftShiftOff><spacebar>",
+ # Disable Voice Over
+ "<leftAltOn><f5><leftAltOff>",
+ ]
+
+ // A (hopefully) temporary workaround for Virtualization.Framework's
+ // installation process not fully finishing in a timely manner
+ create_grace_time = "30s"
+}
+
+build {
+ sources = ["source.tart-cli.tart"]
+
+ provisioner "shell" {
+ inline = [
+ // Enable passwordless sudo
+ "echo admin | sudo -S sh -c \"mkdir -p /etc/sudoers.d/; echo 'admin ALL=(ALL) NOPASSWD: ALL' | EDITOR=tee visudo /etc/sudoers.d/admin-nopasswd\"",
+ // Enable auto-login
+ //
+ // See https://github.com/xfreebird/kcpassword for details.
+ "echo '00000000: 1ced 3f4a bcbc ba2c caca 4e82' | sudo xxd -r - /etc/kcpassword",
+ "sudo defaults write /Library/Preferences/com.apple.loginwindow autoLoginUser admin",
+ // Disable screensaver at login screen
+ "sudo defaults write /Library/Preferences/com.apple.screensaver loginWindowIdleTime 0",
+ // Disable screensaver for admin user
+ "defaults -currentHost write com.apple.screensaver idleTime 0",
+ // Prevent the VM from sleeping
+ "sudo systemsetup -setdisplaysleep Off",
+ "sudo systemsetup -setsleep Off",
+ "sudo systemsetup -setcomputersleep Off",
+ // Launch Safari to populate the defaults
+ "/Applications/Safari.app/Contents/MacOS/Safari &",
+ "sleep 30",
+ "kill -9 %1",
+ // Enable Safari's remote automation and "Develop" menu
+ "sudo safaridriver --enable",
+ "defaults write com.apple.Safari.SandboxBroker ShowDevelopMenu -bool true",
+ "defaults write com.apple.Safari IncludeDevelopMenu -bool true",
+ // Disable screen lock
+ //
+ // Note that this only works if the user is logged-in,
+ // i.e. not on login screen.
+ "sysadminctl -screenLock off -password admin",
+ "defaults -currentHost write com.apple.screensaver idleTime 0"
+ ]
+ }
+}
diff --git a/ci/ios/create-vm/scripts/add-apple-certs.sh b/ci/ios/create-vm/scripts/add-apple-certs.sh
new file mode 100644
index 0000000000..be56458eb8
--- /dev/null
+++ b/ci/ios/create-vm/scripts/add-apple-certs.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+# inspired by https://github.com/actions/runner-images/blob/fb3b6fd69957772c1596848e2daaec69eabca1bb/images/macos/provision/configuration/configure-machine.sh#L33-L61
+
+sudo security delete-certificate -Z FF6797793A3CD798DC5B2ABEF56F73EDC9F83A64 /Library/Keychains/System.keychain
+
+curl -o AppleWWDRCAG3.cer https://www.apple.com/certificateauthority/AppleWWDRCAG3.cer
+curl -o DeveloperIDG2CA.cer https://www.apple.com/certificateauthority/DeveloperIDG2CA.cer
+sudo security add-certificates AppleWWDRCAG3.cer
+sudo security add-certificates DeveloperIDG2CA.cer
diff --git a/ci/ios/create-vm/scripts/cleanup.sh b/ci/ios/create-vm/scripts/cleanup.sh
new file mode 100644
index 0000000000..159d2e8e39
--- /dev/null
+++ b/ci/ios/create-vm/scripts/cleanup.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+set -euo pipefail
+
+# shellcheck source=/dev/null
+source ~/.bash_profile
+
+
+# Uninstall rust
+# shellcheck source=/dev/null
+if [[ -f "${HOME}/.cargo/env" ]]
+then
+ source "${HOME}/.cargo/env"
+ yes | rustup self uninstall
+fi
+
+# Uninstall brew (This should also delete all dependencies)
+NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/uninstall.sh)"
+# Clean up folders that were not automatically removed
+sudo rm -rf /opt/homebrew
+
+# Remove the custom profiles
+rm -f ~/.zprofile ~/.profile ~/.bash_profile \ No newline at end of file
diff --git a/ci/ios/create-vm/scripts/install-brew-dependencies.sh b/ci/ios/create-vm/scripts/install-brew-dependencies.sh
new file mode 100644
index 0000000000..abae2923b8
--- /dev/null
+++ b/ci/ios/create-vm/scripts/install-brew-dependencies.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+set -euo pipefail
+
+# shellcheck source=/dev/null
+source ~/.bash_profile
+
+if command -v brew &>/dev/null
+then
+ echo "Installing xcodes"
+ brew install xcodesorg/made/xcodes
+ echo "Installing xcodes"
+ brew install bash
+fi
diff --git a/ci/ios/create-vm/scripts/install-brew.sh b/ci/ios/create-vm/scripts/install-brew.sh
new file mode 100644
index 0000000000..130d0576da
--- /dev/null
+++ b/ci/ios/create-vm/scripts/install-brew.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+set -euo pipefail
+
+if command -v brew &>/dev/null
+then
+ echo >&1 "brew is already installed, nothing to do here"
+ exit 0
+fi
+
+echo >&1 "installing brew"
+NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
+# This is intentionally in single quotes for echo to append properly
+# shellcheck disable=SC2016
+echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.bash_profile
+eval "$(/opt/homebrew/bin/brew shellenv)"
+
+# shellcheck source=/dev/null
+source ~/.bash_profile
+brew update
diff --git a/ci/ios/create-vm/scripts/install-go.sh b/ci/ios/create-vm/scripts/install-go.sh
new file mode 100644
index 0000000000..9576dc2576
--- /dev/null
+++ b/ci/ios/create-vm/scripts/install-go.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+set -euo pipefail
+
+# shellcheck source=/dev/null
+source ~/.bash_profile
+
+if command -v brew &>/dev/null
+then
+ echo >&1 "Installing go@1.20"
+ brew install go@1.20
+ echo "export PATH='/opt/homebrew/opt/go@1.20/bin:$PATH'" >> ~/.bash_profile
+fi \ No newline at end of file
diff --git a/ci/ios/create-vm/scripts/install-rustup.sh b/ci/ios/create-vm/scripts/install-rustup.sh
new file mode 100644
index 0000000000..6e814adc3e
--- /dev/null
+++ b/ci/ios/create-vm/scripts/install-rustup.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+set -euo pipefail
+
+# shellcheck source=/dev/null
+source ~/.bash_profile
+
+if ! command -v rustup &>/dev/null
+then
+ echo >&1 "Installing rustup"
+ # Install rustup and automatically accept the prompt
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | bash -s -- -y
+
+ # shellcheck source=/dev/null
+ source "${HOME}/.cargo/env"
+ echo "source ~/.cargo/env" >> ~/.bash_profile
+
+ echo >&1 "Installing rustup targets"
+ rustup target add aarch64-apple-ios-sim aarch64-apple-ios
+fi \ No newline at end of file
diff --git a/ci/ios/create-vm/scripts/install-xcode.sh b/ci/ios/create-vm/scripts/install-xcode.sh
new file mode 100644
index 0000000000..26102f5328
--- /dev/null
+++ b/ci/ios/create-vm/scripts/install-xcode.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+set -euo pipefail
+
+# shellcheck source=/dev/null
+source ~/.bash_profile
+
+echo >&1 "Installing xcode"
+xcodes install "${XCODE_VERSION}" --path "${XCODE_SHARED_PATH}/${XCODE_XIP_NAME}" --experimental-unxip
+xcodes select "${XCODE_VERSION}"
diff --git a/ci/ios/create-vm/scripts/link-zprofile.sh b/ci/ios/create-vm/scripts/link-zprofile.sh
new file mode 100644
index 0000000000..af085137ab
--- /dev/null
+++ b/ci/ios/create-vm/scripts/link-zprofile.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+set -eu
+
+# The profile link already exists, skip this step
+if [[ -f "$HOME/.profile" ]]
+then
+ exit 0
+fi
+
+touch ~/.zprofile
+ln -s ~/.zprofile ~/.profile \ No newline at end of file
diff --git a/ci/ios/create-vm/scripts/run-xcode-first-launch.sh b/ci/ios/create-vm/scripts/run-xcode-first-launch.sh
new file mode 100644
index 0000000000..9159c90f21
--- /dev/null
+++ b/ci/ios/create-vm/scripts/run-xcode-first-launch.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+set -euo pipefail
+
+# shellcheck source=/dev/null
+source ~/.bash_profile
+
+if command -v xcodebuild &>/dev/null
+then
+ echo >&1 "Running xcodebuild -runFirstLaunch"
+ xcodebuild -runFirstLaunch
+ echo >&1 "Downloading iOS Simulator runtime, this might take a while"
+ xcodebuild -downloadPlatform iOS
+fi
+
+# Xcode is needed in order to run swiftformat or swiftlint
+brew install swiftformat swiftlint \ No newline at end of file
diff --git a/ci/ios/create-vm/variables.pkrvars.hcl b/ci/ios/create-vm/variables.pkrvars.hcl
new file mode 100644
index 0000000000..19d5e50eaf
--- /dev/null
+++ b/ci/ios/create-vm/variables.pkrvars.hcl
@@ -0,0 +1,5 @@
+xcode_version = "15.0.1"
+xcode_xip_name = "Xcode_15.0.1.xip"
+vm_name = "app-build"
+user_name = "admin"
+shared_folder_path = "vm_shared_folder"