From d5a7677e3dd64a2827f1ede5b613e0073d529c5c Mon Sep 17 00:00:00 2001 From: Naveenraj M <22456988+naveenrajm7@users.noreply.github.com> Date: Sat, 21 Dec 2024 06:05:24 -0700 Subject: [PATCH 01/18] virt-initial --- lib/vagrant_utm/cap/mount_options.rb | 56 +++++++++ lib/vagrant_utm/driver/version_4_6.rb | 18 +++ .../add_qemu_additional_args.applescript | 43 +++++++ .../remove_qemu_additional_args.applescript | 46 +++++++ lib/vagrant_utm/synced_folder.rb | 118 ++++++++++++++++++ 5 files changed, 281 insertions(+) create mode 100644 lib/vagrant_utm/cap/mount_options.rb create mode 100644 lib/vagrant_utm/scripts/add_qemu_additional_args.applescript create mode 100644 lib/vagrant_utm/scripts/remove_qemu_additional_args.applescript create mode 100644 lib/vagrant_utm/synced_folder.rb diff --git a/lib/vagrant_utm/cap/mount_options.rb b/lib/vagrant_utm/cap/mount_options.rb new file mode 100644 index 0000000..4e47f0a --- /dev/null +++ b/lib/vagrant_utm/cap/mount_options.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: BUSL-1.1 + +require_relative "../../../synced_folders/unix_mount_helpers" + +module VagrantPlugins + module Utm + module Cap + # Capability for mount options + module MountOptions + extend VagrantPlugins::SyncedFolder::UnixMountHelpers + + # Mount type for VirtFS + UTM_MOUNT_TYPE = "9p" + + # Returns mount options for a utm synced folder + # + # @param [Machine] machine + # @param [String] name of mount + # @param [String] path of mount on guest + # @param [Hash] hash of mount options + def self.mount_options(machine, _name, guest_path, options) + mount_options = options.fetch(:mount_options, []) + detected_ids = detect_owner_group_ids(machine, guest_path, mount_options, options) + mount_uid = detected_ids[:uid] + mount_gid = detected_ids[:gid] + + # VirtFS mount options + mount_options << "trans=virtio" + mount_options << "version=9p2000.L" + mount_options << if mount_opiotns.include?("ro") + "ro" + else + "rw" + end + mount_options << "_netdev" + mount_options << "nofail" + mount_options << "auto" + + mount_options = mount_options.join(",") + [mount_options, mount_uid, mount_gid] + end + + def self.mount_type(_machine) + UTM_MOUNT_TYPE + end + + def self.mount_name(_machine, name, _data) + name.gsub(%r{[\s/\\]}, "_").sub(/^_/, "") + end + end + end + end +end diff --git a/lib/vagrant_utm/driver/version_4_6.rb b/lib/vagrant_utm/driver/version_4_6.rb index dd9816e..e624797 100644 --- a/lib/vagrant_utm/driver/version_4_6.rb +++ b/lib/vagrant_utm/driver/version_4_6.rb @@ -36,6 +36,24 @@ def export(path) command = ["export_vm.applescript", @uuid, path] execute_osa_script(command) end + + def share_folders(folders) + folders.each do |folder| + args = ["--args", + ""] + command = ["add_qemu_additional_args.applescript", @uuid, *args] + execute_osa_script(command) + end + end + + def unshare_folders(folders) + folders.each do |folder| + args = ["--args", + ""] + command = ["remove_qemu_additional_args.applescript", @uuid, *args] + execute_osa_script(command) + end + end end end end diff --git a/lib/vagrant_utm/scripts/add_qemu_additional_args.applescript b/lib/vagrant_utm/scripts/add_qemu_additional_args.applescript new file mode 100644 index 0000000..c9c71f8 --- /dev/null +++ b/lib/vagrant_utm/scripts/add_qemu_additional_args.applescript @@ -0,0 +1,43 @@ +--- +-- add_qemu_additional_args.applescript +-- This script adds qemu arguments to a specified UTM virtual machine. +-- Usage: osascript add_qemu_additional_args.applescript --args ... +-- Example: osascript add_qemu_additional_args.applescript A123 --args "-vnc 127.0.0.1:13" "-vnc..." + +on run argv + set vmId to item 1 of argv # UUID of the VM + + -- Initialize variables + set argsList to {} + set argsFlag to false + + -- Parse the --args arguments + repeat with i from 2 to (count of argv) + set currentArg to item i of argv + if currentArg is "--args" then + set argsFlag to true + else if argsFlag then + set end of argsList to currentArg + end if + end repeat + + tell application "UTM" + -- Get the VM and its configuration + set vm to virtual machine id vmId -- Id is assumed to be valid + set config to configuration of vm + + -- Existing arguments + set qemuAddArgs to qemu additional arguments of config + + -- Create new arguments from argsList and add them to the existing arguments + repeat with arg in argsList + set end of qemuAddArgs to {argument string:arg} + end repeat + + --- set qemu args with new args list + set qemu additional arguments of config to qemuAddArgs + + --- save the configuration (VM must be stopped) + update configuration of vm with config + end tell +end run \ No newline at end of file diff --git a/lib/vagrant_utm/scripts/remove_qemu_additional_args.applescript b/lib/vagrant_utm/scripts/remove_qemu_additional_args.applescript new file mode 100644 index 0000000..63abeeb --- /dev/null +++ b/lib/vagrant_utm/scripts/remove_qemu_additional_args.applescript @@ -0,0 +1,46 @@ +--- +-- remove_qemu_additional_args.applescript +-- This script removes specified qemu arguments from a specified UTM virtual machine. +-- Usage: osascript remove_qemu_additional_args.applescript --args ... +-- Example: osascript remove_qemu_additional_args.applescript A123 --args "-vnc 127.0.0.1:13" "-vnc..." + +on run argv + set vmId to item 1 of argv -- UUID of the VM + + -- Initialize variables + set argsToRemove to {} + set argsFlag to false + + -- Parse the --args arguments + repeat with i from 2 to (count of argv) + set currentArg to item i of argv + if currentArg is "--args" then + set argsFlag to true + else if argsFlag then + set end of argsToRemove to currentArg + end if + end repeat + + tell application "UTM" + -- Get the VM and its configuration + set vm to virtual machine id vmId -- Id is assumed to be valid + set config to configuration of vm + + -- Get the current QEMU additional arguments + set qemuAddArgs to qemu additional arguments of config + + -- Initialize a new list for the updated arguments + set updatedArgs to {} + + -- Iterate through the current arguments and add all except the ones to remove + repeat with arg in qemuAddArgs + if arg is not in argsToRemove then + set end of updatedArgs to arg + end if + end repeat + + -- Update the configuration with the new arguments list + set qemu additional arguments of config to updatedArgs + update configuration of vm with config + end tell +end run \ No newline at end of file diff --git a/lib/vagrant_utm/synced_folder.rb b/lib/vagrant_utm/synced_folder.rb new file mode 100644 index 0000000..2073f38 --- /dev/null +++ b/lib/vagrant_utm/synced_folder.rb @@ -0,0 +1,118 @@ +# frozen_string_literal: true + +module VagrantPlugins + module Utm + # Default Synced folder implementation for UTM + class SyncedFolder < Vagrant.plugin("2", :synced_folder) + def usable?(machine, _raise_errors = false) # rubocop:disable Style/OptionalBooleanParameter + # These synced folders only work if the provider is UTM + if machine.provider_name != :utm + raise Errors::SyncedFolderNonUtm, provider: machine.provider_name.to_s if raise_errors + + return false + end + + true + end + + def prepare(machine, folders, _opts) + share_folders(machine, folders) + end + + def enable(machine, folders, _opts) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength,Metrics/PerceivedComplexity + share_folders(machine, folders) + + # short guestpaths first, so we don't step on ourselves + folders = folders.sort_by do |_id, data| + if data[:guestpath] + data[:guestpath].length + else + # A long enough path to just do this at the end. + 10_000 + end + end + + # Go through each folder and mount + machine.ui.output(I18n.t("vagrant.actions.vm.share_folders.mounting")) + # refresh fstab + fstab_folders = [] # rubocop:disable Lint/UselessAssignment + folders.each do |id, data| + if data[:guestpath] + # Guest path specified, so mount the folder to specified point + machine.ui.detail(I18n.t("vagrant.actions.vm.share_folders.mounting_entry", + guestpath: data[:guestpath], + hostpath: data[:hostpath])) + + # Dup the data so we can pass it to the guest API + data = data.dup + + # Calculate the owner and group + ssh_info = machine.ssh_info + data[:owner] ||= ssh_info[:username] + data[:group] ||= ssh_info[:username] + + # Mount the actual folder + machine.guest.capability( + :mount_virtualbox_shared_folder, + os_friendly_id(id), data[:guestpath], data + ) + else + # If no guest path is specified, then automounting is disabled + machine.ui.detail(I18n.t("vagrant.actions.vm.share_folders.nomount_entry", + hostpath: data[:hostpath])) + end + end + end + + def disable(machine, folders, _opts) + if machine.guest.capability?(:unmount_virtualbox_shared_folder) + folders.each_value do |data| + machine.guest.capability( + :unmount_virtualbox_shared_folder, + data[:guestpath], data + ) + end + end + + # Remove the shared folders from the VM metadata + names = folders.map { |id, _data| os_friendly_id(id) } + driver(machine).unshare_folders(names) + end + + def cleanup(machine, _opts) + driver(machine).clear_shared_folders if machine.id && machine.id != "" + end + + protected + + # This is here so that we can stub it for tests + def driver(machine) + machine.provider.driver + end + + def os_friendly_id(id) + id.gsub(%r{[\s/\\]}, "_").sub(/^_/, "") + end + + # share_folders sets up the shared folder definitions on the + # UTM VM. + # + def share_folders(machine, folders) + defs = [] + + folders.each do |id, data| + hostpath = data[:hostpath] + hostpath = Vagrant::Util::Platform.cygwin_windows_path(hostpath) unless data[:hostpath_exact] + + defs << { + name: os_friendly_id(id), + hostpath: hostpath.to_s, + automount: !!data[:automount] + } + end + + driver(machine).share_folders(defs) + end + end + end +end From c45a5d72fd8e3efa2038e0bf478a9ab47d845f8f Mon Sep 17 00:00:00 2001 From: Naveenraj M <22456988+naveenrajm7@users.noreply.github.com> Date: Sat, 21 Dec 2024 06:23:52 -0700 Subject: [PATCH 02/18] docs: update with new image Add info about new packer utm cloud builder --- README.md | 2 +- docs/boxes/creating_utm_box.md | 13 +++++++------ docs/boxes/utm_box_gallery.md | 15 ++++++++++----- docs/index.md | 10 +++++----- 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index b98722c..25a9095 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ vagrant plugin install vagrant_utm ```ruby Vagrant.configure("2") do |config| - config.vm.box = "utm/debian11" + config.vm.box = "utm/bookworm" end ``` diff --git a/docs/boxes/creating_utm_box.md b/docs/boxes/creating_utm_box.md index e089280..6d1602d 100644 --- a/docs/boxes/creating_utm_box.md +++ b/docs/boxes/creating_utm_box.md @@ -60,17 +60,18 @@ Check the [UTM Guide on Guest Support](https://docs.getutm.app/guest-support/gue By satisfying the [general guidance on creating vagrant boxes](https://developer.hashicorp.com/vagrant/docs/boxes/base) and the above [Virtual Machine](#virtual-machine) requirements you can use your VM with Vagrant UTM plugin. -Apart from manually building the boxes, you can also use the semi-automated way of building these boxes using [packer plugin for UTM](https://github.com/naveenrajm7/packer-plugin-utm). +Apart from manually building the boxes, you can also use the automated (almost) way of building these boxes using [packer plugin for UTM](https://github.com/naveenrajm7/packer-plugin-utm). The packer plugin has the following components: 1. Builder - 1. UTM - Use existing utm file - 2. ISO - Start from scratch using ISO files + 1. UTM - Use existing utm file + 2. ISO - Start from scratch using ISO files + 3. CLOUD - Use existing qcow2 cloud images 2. Post-processor - 1. ZIP - Package UTM VM into zip file - 2. Vagrant - Package UTM VM into vagrant box. + 1. ZIP - Package UTM VM into zip file + 2. Vagrant - Package UTM VM into vagrant box. -Checkout [UTM Box Guide](https://github.com/naveenrajm7/utm-box/blob/main/HowToBuild/DebianUTM.md) to know how to build Box using packer. +Checkout [UTM Box Packer recipe](https://github.com/naveenrajm7/utm-box?tab=readme-ov-file#building-boxes) to know how to build Box using packer. ## Using your own UTM VMs diff --git a/docs/boxes/utm_box_gallery.md b/docs/boxes/utm_box_gallery.md index bd968b7..74a7526 100644 --- a/docs/boxes/utm_box_gallery.md +++ b/docs/boxes/utm_box_gallery.md @@ -10,22 +10,27 @@ nav_order: 1 To work with Vagrant, a base VM (box) must have [certain features](https://developer.hashicorp.com/vagrant/docs/boxes/base), like an ssh user for vagrant to connect. -To help you get started with Vagrant UTM provider, a couple of pre-built VMs that work with Vagrant and are published in [HCP Vagrant registry](https://portal.cloud.hashicorp.com/vagrant/discover/utm). +To help you get started with Vagrant UTM provider, some pre-built VMs that work with Vagrant are published in [HCP Vagrant registry](https://portal.cloud.hashicorp.com/vagrant/discover/utm). {: .important} -All the VMs provided are built from [UTM Gallery VMs](https://mac.getutm.app/gallery/) or ISO in an (semi) automated way using [packer plugin for UTM][packer plugin for UTM]. Please see the [UTM Box Guide][UTM Box Guide] on how these UTM Vagrant boxes were built using packer. +All the VMs provided are built from Cloud Images or ISO files in an (semi) automated way using [packer plugin for UTM][packer plugin for UTM]. Please see the [UTM Box Guide][UTM Box Guide] on how these UTM Vagrant boxes were built using packer. -* Debian 11 (Xfce): +* Debian 12 - Built from cloud image: +```ruby +config.vm.box = "utm/bookworm" +``` + +* Debian 11 (Xfce) - Built from UTM file of UTM gallery: ```ruby config.vm.box = "utm/debian11" ``` -* Ubuntu 24.04 : +* Ubuntu 24.04 - Built from ISO: ```ruby config.vm.box = "utm/ubuntu-24.04" ``` -* Help build more boxes using [packer plugin for UTM][packer plugin for UTM] +* Build your own boxes using [packer plugin for UTM][packer plugin for UTM] diff --git a/docs/index.md b/docs/index.md index 1a1e403..dc956ae 100644 --- a/docs/index.md +++ b/docs/index.md @@ -59,14 +59,14 @@ vagrant plugin install vagrant_utm Option 1: Create a Vagrantfile and initiate the box (OR) ``` -vagrant init utm/debian11 +vagrant init utm/bookworm ``` Option 2: Open the Vagrantfile and replace the contents with the following ```ruby Vagrant.configure("2") do |config| - config.vm.box = "utm/debian11" + config.vm.box = "utm/bookworm" end ``` @@ -81,9 +81,9 @@ Now start using your machine! `vagrant ssh` to log into machine or forward ports to check your website or share folders and start developing. -Check [Commands](commands.md) for all supported Vagrant commands. -Check [Configuration](configuration.md) for more UTM provider config options. - +Check [Commands](commands.md) for all supported Vagrant commands. +Check [Configuration](configuration.md) for more UTM provider config options. +Discover UTM Vagrant boxes at [HCP Vagrant UTM Registry](https://portal.cloud.hashicorp.com/vagrant/discover/utm), which as boxes of popular OS including OpenBSD! ## About the project From c2435c2ca4b0e97333533435990987323d6a9bf0 Mon Sep 17 00:00:00 2001 From: Naveenraj M <22456988+naveenrajm7@users.noreply.github.com> Date: Sun, 19 Jan 2025 19:21:48 -0700 Subject: [PATCH 03/18] test: change testing directory for vagrant plugin --- .gitignore | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 7daed88..39a4541 100644 --- a/.gitignore +++ b/.gitignore @@ -20,5 +20,5 @@ # MacOS .DS_Store -# Test Vagrantfiles -vagrant_play/ \ No newline at end of file +# Test plugin, Vagrantfile +vagrant_test/ \ No newline at end of file From b8ff139c02cd8d3c1019d0e48f319982acb7cb49 Mon Sep 17 00:00:00 2001 From: Naveenraj M <22456988+naveenrajm7@users.noreply.github.com> Date: Sun, 19 Jan 2025 19:23:06 -0700 Subject: [PATCH 04/18] plugin: bundle update --- Gemfile.lock | 115 +++++++++++++++++++++++-------------- lib/vagrant_utm/version.rb | 2 +- 2 files changed, 72 insertions(+), 45 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 8f59464..7d84b99 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -33,7 +33,7 @@ GIT PATH remote: . specs: - vagrant_utm (0.1.2.beta) + vagrant_utm (0.1.3.beta) GEM remote: https://rubygems.org/ @@ -42,25 +42,31 @@ GEM base64 (0.2.0) bcrypt_pbkdf (1.1.1) bcrypt_pbkdf (1.1.1-arm64-darwin) - bigdecimal (3.1.8) + bigdecimal (3.1.9) builder (3.3.0) childprocess (4.1.0) - concurrent-ruby (1.3.3) - date (3.3.4) + concurrent-ruby (1.3.5) + date (3.4.1) diff-lcs (1.5.1) ed25519 (1.3.0) - erubi (1.13.0) - excon (0.111.0) - ffi (1.17.0) - ffi (1.17.0-aarch64-linux-gnu) - ffi (1.17.0-aarch64-linux-musl) - ffi (1.17.0-arm-linux-gnu) - ffi (1.17.0-arm-linux-musl) - ffi (1.17.0-arm64-darwin) - google-protobuf (3.25.3) - google-protobuf (3.25.3-aarch64-linux) - google-protobuf (3.25.3-arm64-darwin) - googleapis-common-protos-types (1.15.0) + erubi (1.13.1) + excon (1.2.3) + faraday (2.12.2) + faraday-net_http (>= 2.0, < 3.5) + json + logger + faraday-net_http (3.4.0) + net-http (>= 0.5.0) + ffi (1.17.1) + ffi (1.17.1-aarch64-linux-gnu) + ffi (1.17.1-aarch64-linux-musl) + ffi (1.17.1-arm-linux-gnu) + ffi (1.17.1-arm-linux-musl) + ffi (1.17.1-arm64-darwin) + google-protobuf (3.25.5) + google-protobuf (3.25.5-aarch64-linux) + google-protobuf (3.25.5-arm64-darwin) + googleapis-common-protos-types (1.18.0) google-protobuf (>= 3.18, < 5.a) grpc (1.56.2) google-protobuf (~> 3.23) @@ -71,42 +77,59 @@ GEM builder (>= 2.1.2) rexml (~> 3.0) hashicorp-checkpoint (0.1.5) + hashie (5.0.0) httpclient (2.8.3) - i18n (1.14.5) + i18n (1.14.7) concurrent-ruby (~> 1.0) - ipaddr (1.2.6) - json (2.9.0) + ipaddr (1.2.7) + json (2.9.1) + jwt (2.10.1) + base64 language_server-protocol (3.17.0.3) listen (3.9.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) little-plugger (1.1.4) log4r (1.1.10) + logger (1.6.5) logging (2.4.0) little-plugger (~> 1.1) multi_json (~> 1.14) - mime-types (3.5.2) + mime-types (3.6.0) + logger mime-types-data (~> 3.2015) - mime-types-data (3.2024.0702) + mime-types-data (3.2025.0107) multi_json (1.15.0) - net-ftp (0.3.7) + multi_xml (0.7.1) + bigdecimal (~> 3.1) + net-ftp (0.3.8) net-protocol time + net-http (0.6.0) + uri net-protocol (0.2.2) timeout net-scp (4.0.0) net-ssh (>= 2.6.5, < 8.0.0) net-sftp (4.0.0) net-ssh (>= 5.0.0, < 8.0.0) - net-ssh (7.2.3) - nori (2.7.0) + net-ssh (7.3.0) + nori (2.7.1) bigdecimal + oauth2 (2.0.9) + faraday (>= 0.17.3, < 3.0) + jwt (>= 1.0, < 3.0) + multi_xml (~> 0.5) + rack (>= 1.2, < 4) + snaky_hash (~> 2.0) + version_gem (~> 1.1) pairing_heap (3.1.0) parallel (1.26.3) - parser (3.3.6.0) + parser (3.3.7.0) ast (~> 2.4.1) racc racc (1.8.1) + rack (3.1.8) rainbow (3.1.1) rake (13.2.1) rb-fsevent (0.11.2) @@ -114,9 +137,8 @@ GEM ffi (~> 1.0) rb-kqueue (0.2.8) ffi (>= 0.5.0) - regexp_parser (2.9.3) - rexml (3.2.9) - strscan + regexp_parser (2.10.0) + rexml (3.4.0) rgl (0.5.10) pairing_heap (>= 0.3.0) rexml (~> 3.2, >= 3.2.4) @@ -125,16 +147,16 @@ GEM rspec-core (~> 3.13.0) rspec-expectations (~> 3.13.0) rspec-mocks (~> 3.13.0) - rspec-core (3.13.0) + rspec-core (3.13.2) rspec-support (~> 3.13.0) - rspec-expectations (3.13.1) + rspec-expectations (3.13.3) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-mocks (3.13.1) + rspec-mocks (3.13.2) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-support (3.13.1) - rubocop (1.69.1) + rspec-support (3.13.2) + rubocop (1.70.0) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) @@ -144,33 +166,38 @@ GEM rubocop-ast (>= 1.36.2, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.36.2) + rubocop-ast (1.37.0) parser (>= 3.3.1.0) ruby-progressbar (1.13.0) rubyntlm (0.6.5) base64 rubyzip (2.3.2) + snaky_hash (2.0.1) + hashie + version_gem (~> 1.1, >= 1.1.1) stream (0.5.5) - strscan (3.1.0) - time (0.3.0) + time (0.4.1) date - timeout (0.4.1) - unicode-display_width (3.1.2) + timeout (0.4.3) + unicode-display_width (3.1.4) unicode-emoji (~> 4.0, >= 4.0.4) unicode-emoji (4.0.4) - vagrant_cloud (3.1.1) - excon (~> 0.73) - log4r (~> 1.1.10) - rexml (~> 3.2.5) + uri (1.0.2) + vagrant_cloud (3.1.2) + excon (~> 1.0) + log4r (~> 1.1) + oauth2 (~> 2.0) + rexml (~> 3.3) + version_gem (1.1.4) wdm (0.1.1) - winrm (2.3.8) + winrm (2.3.9) builder (>= 2.1.2) erubi (~> 1.8) gssapi (~> 1.2) gyoku (~> 1.0) httpclient (~> 2.2, >= 2.2.0.2) logging (>= 1.6.1, < 3.0) - nori (~> 2.0) + nori (~> 2.0, >= 2.7.1) rexml (~> 3.0) rubyntlm (~> 0.6.0, >= 0.6.3) winrm-elevated (1.2.3) diff --git a/lib/vagrant_utm/version.rb b/lib/vagrant_utm/version.rb index ed75c80..c0b56f4 100644 --- a/lib/vagrant_utm/version.rb +++ b/lib/vagrant_utm/version.rb @@ -4,6 +4,6 @@ module VagrantPlugins # Top level module for the Utm provider plugin. module Utm # Current version of the Utm provider plugin. - VERSION = "0.1.2.beta" + VERSION = "0.1.3.beta" end end From fce16b308263b1da85a486663b2e3048de777566 Mon Sep 17 00:00:00 2001 From: Naveenraj M <22456988+naveenrajm7@users.noreply.github.com> Date: Sun, 19 Jan 2025 19:29:15 -0700 Subject: [PATCH 05/18] config: add functional_9pfs To determine if VM supports VirtFS directory sharing, true by default --- lib/vagrant_utm/config.rb | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/lib/vagrant_utm/config.rb b/lib/vagrant_utm/config.rb index ce8972a..e8dec6f 100644 --- a/lib/vagrant_utm/config.rb +++ b/lib/vagrant_utm/config.rb @@ -7,11 +7,6 @@ module VagrantPlugins module Utm # This is the configuration class for the UTM provider. class Config < Vagrant.plugin("2", :config) - # This should be set to the name of the machine in the UTM GUI. - # - # @return [String] - attr_accessor :name - # If true, will check if guest additions are installed and up to # date. By default, this is true. # @@ -23,6 +18,19 @@ class Config < Vagrant.plugin("2", :config) # @return [Array] attr_reader :customizations + # Whether or not this VM has a functional VirtFS 9P filesystem module + # for VirtFS directory sharing to work. + # This defaults to true. If you set this to false, then the "utm" + # synced folder type won't be valid. + # + # @return [Boolean] + attr_accessor :functional_9pfs + + # This should be set to the name of the machine in the UTM GUI. + # + # @return [String] + attr_accessor :name + # The time to wait for the VM to be 'running' after 'started'. # # @return [Integer] @@ -33,6 +41,7 @@ def initialize super @check_guest_additions = UNSET_VALUE @customizations = [] + @functional_9pfs = UNSET_VALUE @name = UNSET_VALUE @wait_time = UNSET_VALUE end @@ -103,6 +112,8 @@ def directory_share_mode=(mode) def finalize! @check_guest_additions = true if @check_guest_additions == UNSET_VALUE + @functional_9pfs = true if @functional_9pfs == UNSET_VALUE + # The default name is just nothing, and we default it @name = nil if @name == UNSET_VALUE @@ -127,6 +138,10 @@ def validate(_machine) { "UTM Provider" => errors } end + + def to_s + "UTM" + end end end end From 9e1578b48f940c46e24b8d6f0c5e87ce80d879c7 Mon Sep 17 00:00:00 2001 From: Naveenraj M <22456988+naveenrajm7@users.noreply.github.com> Date: Sun, 19 Jan 2025 19:30:21 -0700 Subject: [PATCH 06/18] plugin: register sync folder implementation default sync folder for utm plugin --- lib/vagrant_utm/driver/meta.rb | 2 ++ lib/vagrant_utm/driver/version_4_6.rb | 12 ++++++++---- lib/vagrant_utm/plugin.rb | 21 +++++++++++++++++++++ lib/vagrant_utm/synced_folder.rb | 11 +++++------ 4 files changed, 36 insertions(+), 10 deletions(-) diff --git a/lib/vagrant_utm/driver/meta.rb b/lib/vagrant_utm/driver/meta.rb index d59591d..3c0ccdd 100644 --- a/lib/vagrant_utm/driver/meta.rb +++ b/lib/vagrant_utm/driver/meta.rb @@ -88,6 +88,7 @@ def initialize(uuid = nil) # rubocop:disable Metrics/CyclomaticComplexity,Metric def_delegators :@driver, :check_qemu_guest_agent, :clear_forwarded_ports, + :clear_shared_folders, :create_snapshot, :delete, :delete_snapshot, @@ -108,6 +109,7 @@ def initialize(uuid = nil) # rubocop:disable Metrics/CyclomaticComplexity,Metric :restore_snapshot, :set_mac_address, :set_name, + :share_folders, :ssh_port, :start, :start_disposable, diff --git a/lib/vagrant_utm/driver/version_4_6.rb b/lib/vagrant_utm/driver/version_4_6.rb index e624797..021f3f7 100644 --- a/lib/vagrant_utm/driver/version_4_6.rb +++ b/lib/vagrant_utm/driver/version_4_6.rb @@ -13,6 +13,8 @@ def initialize(uuid) @logger = Log4r::Logger.new("vagrant::provider::utm::version_4_6") end + def clear_shared_folders; end + def import(utm) utm = Vagrant::Util::Platform.windows_path(utm) @@ -38,9 +40,12 @@ def export(path) end def share_folders(folders) + # log the folders we are going to share + @logger.debug("Sharing folders: #{folders}") + folders.each do |folder| - args = ["--args", - ""] + args = ["--name", folder[:name], + "--dir", folder[:hostpath]] command = ["add_qemu_additional_args.applescript", @uuid, *args] execute_osa_script(command) end @@ -48,8 +53,7 @@ def share_folders(folders) def unshare_folders(folders) folders.each do |folder| - args = ["--args", - ""] + args = ["--name", folder] command = ["remove_qemu_additional_args.applescript", @uuid, *args] execute_osa_script(command) end diff --git a/lib/vagrant_utm/plugin.rb b/lib/vagrant_utm/plugin.rb index 733e47f..bd7f3d5 100644 --- a/lib/vagrant_utm/plugin.rb +++ b/lib/vagrant_utm/plugin.rb @@ -33,6 +33,12 @@ class Plugin < Vagrant.plugin("2") Config end + # Register the synced folder implementation + synced_folder(:utm) do + require_relative "synced_folder" + SyncedFolder + end + # Register capabilities provider_capability(:utm, :forwarded_ports) do require_relative "cap" @@ -44,6 +50,21 @@ class Plugin < Vagrant.plugin("2") Cap end + synced_folder_capability(:utm, "mount_options") do + require_relative "cap/mount_options" + Cap::MountOptions + end + + synced_folder_capability(:utm, "mount_type") do + require_relative "cap/mount_options" + Cap::MountOptions + end + + synced_folder_capability(:utm, "mount_name") do + require_relative "cap/mount_options" + Cap::MountOptions + end + # Register the command ## Start machine as a snapshot and do not save changes to disk command "disposable" do diff --git a/lib/vagrant_utm/synced_folder.rb b/lib/vagrant_utm/synced_folder.rb index 2073f38..1de0bc6 100644 --- a/lib/vagrant_utm/synced_folder.rb +++ b/lib/vagrant_utm/synced_folder.rb @@ -6,13 +6,12 @@ module Utm class SyncedFolder < Vagrant.plugin("2", :synced_folder) def usable?(machine, _raise_errors = false) # rubocop:disable Style/OptionalBooleanParameter # These synced folders only work if the provider is UTM - if machine.provider_name != :utm - raise Errors::SyncedFolderNonUtm, provider: machine.provider_name.to_s if raise_errors + return false if machine.provider_name != :utm - return false - end + # This only happens with `vagrant package --base`. Sigh. + return true unless machine.provider_config - true + machine.provider_config.functional_9pfs end def prepare(machine, folders, _opts) @@ -22,7 +21,7 @@ def prepare(machine, folders, _opts) def enable(machine, folders, _opts) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength,Metrics/PerceivedComplexity share_folders(machine, folders) - # short guestpaths first, so we don't step on ourselves + # sort guestpaths first, so we don't step on ourselves folders = folders.sort_by do |_id, data| if data[:guestpath] data[:guestpath].length From cdc4bdd726766706b844e53adbb21890dc46b6cd Mon Sep 17 00:00:00 2001 From: Naveenraj M <22456988+naveenrajm7@users.noreply.github.com> Date: Sun, 19 Jan 2025 19:30:41 -0700 Subject: [PATCH 07/18] cap: spell error --- lib/vagrant_utm/cap/mount_options.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vagrant_utm/cap/mount_options.rb b/lib/vagrant_utm/cap/mount_options.rb index 4e47f0a..37724bd 100644 --- a/lib/vagrant_utm/cap/mount_options.rb +++ b/lib/vagrant_utm/cap/mount_options.rb @@ -30,7 +30,7 @@ def self.mount_options(machine, _name, guest_path, options) # VirtFS mount options mount_options << "trans=virtio" mount_options << "version=9p2000.L" - mount_options << if mount_opiotns.include?("ro") + mount_options << if mount_options.include?("ro") "ro" else "rw" From c0d59b43a1396d420b4d20491b9aba24e8014035 Mon Sep 17 00:00:00 2001 From: Naveenraj M <22456988+naveenrajm7@users.noreply.github.com> Date: Sun, 19 Jan 2025 19:30:59 -0700 Subject: [PATCH 08/18] notes: how to update --- notes/README.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/notes/README.md b/notes/README.md index 34298bc..8771746 100644 --- a/notes/README.md +++ b/notes/README.md @@ -34,4 +34,15 @@ GHA will publish gems to GHR and rubygems To update specific gems in the project -`bundle update rubocop` \ No newline at end of file +`bundle update rubocop` + +To update all gems + +`bundle update` + +To update project after a version bump + +``` +Unable to resolve dependency: user requested 'vagrant_utm (= 0.1.1)' +``` +Try changing the version number again From 705e24b3c1dc59fe7e75922bbb1dd608a34af281 Mon Sep 17 00:00:00 2001 From: Naveenraj M <22456988+naveenrajm7@users.noreply.github.com> Date: Sun, 19 Jan 2025 19:48:18 -0700 Subject: [PATCH 09/18] synced_folder : remove configuring share folders in VM at enable step Enable is done after VM boot while it is running. Configuring share folders in UTM requires updating config which requires VM to be stopped. --- lib/vagrant_utm/synced_folder.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/vagrant_utm/synced_folder.rb b/lib/vagrant_utm/synced_folder.rb index 1de0bc6..d406acb 100644 --- a/lib/vagrant_utm/synced_folder.rb +++ b/lib/vagrant_utm/synced_folder.rb @@ -14,13 +14,15 @@ def usable?(machine, _raise_errors = false) # rubocop:disable Style/OptionalBool machine.provider_config.functional_9pfs end + # This is called before VM Boot to prepare the synced folders. + # Add required configs to the VM. def prepare(machine, folders, _opts) share_folders(machine, folders) end + # This is called after VM Boot to mount the synced folders. + # Mount the shared folders inside the VM. def enable(machine, folders, _opts) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength,Metrics/PerceivedComplexity - share_folders(machine, folders) - # sort guestpaths first, so we don't step on ourselves folders = folders.sort_by do |_id, data| if data[:guestpath] From c857235de3770b4a5578702b0dba1a6034eba4af Mon Sep 17 00:00:00 2001 From: Naveenraj M <22456988+naveenrajm7@users.noreply.github.com> Date: Sun, 19 Jan 2025 19:57:53 -0700 Subject: [PATCH 10/18] script: add directory_share_add --- .../scripts/directory_share_add.applescript | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 lib/vagrant_utm/scripts/directory_share_add.applescript diff --git a/lib/vagrant_utm/scripts/directory_share_add.applescript b/lib/vagrant_utm/scripts/directory_share_add.applescript new file mode 100644 index 0000000..892b166 --- /dev/null +++ b/lib/vagrant_utm/scripts/directory_share_add.applescript @@ -0,0 +1,95 @@ +--- +-- directory_share_add.applescript +-- This script adds QEMU arguments for directory sharing in UTM (QEMU) for given id and directory pairs. +-- Usage: osascript directory_share_add.applescript --id --dir --id --dir ... +-- Example: osascript directory_share_add.applescript --id 1 --dir "/path/to/dir1" --id 2 --dir "/path/to/dir2" + +-- Function to create QEMU arguments for directory sharing +on createQemuArgsForDir(dirId, dirPath) + + -- Prepare the QEMU argument strings + set fsdevArgStr to "-fsdev local,id=virtfs" & dirId & ",path=" & dirPath & ",security_model=mapped-xattr" + set deviceArgStr to "-device virtio-9p-pci,fsdev=virtfs" & dirId & ",mount_tag=share" & dirId + + return {fsdevArgStr, deviceArgStr} +end createQemuArgsForDir + +-- Main script +on run argv + -- VM id is assumed to be the first argument + set vmId to item 1 of argv + + -- Initialize variables + set idList to {} -- + set dirList to {} + set idFlag to false + set dirFlag to false + + -- Parse arguments + repeat with i from 2 to (count of argv) + set currentArg to item i of argv + if currentArg is "--id" then + set idFlag to true + set dirFlag to false + else if currentArg is "--dir" then + set dirFlag to true + set idFlag to false + else if idFlag then + set end of idList to currentArg + set idFlag to false + else if dirFlag then + set end of dirList to currentArg + set dirFlag to false + end if + end repeat + + -- Ensure the lists are of the same length + if (count of idList) is not (count of dirList) then + error "The number of IDs and directories must be the same." + end if + + -- Initialize the list of QEMU arguments + set qemuNewArgs to {} + + -- Initialize the directory list + set directoryList to {} + + -- Create QEMU arguments for each directory + repeat with i from 1 to (count of dirList) + set dirPath to item i of dirList + set dirId to item i of idList + set dirURL to POSIX file dirPath + set {fsdevArgStr, deviceArgStr} to createQemuArgsForDir(dirId, dirPath) + + -- add the directory file obj to the list + set end of directoryList to dirURL + -- append the arguments to the list + set end of qemuNewArgs to {fsdevArg:fsdevArgStr, deviceArg:deviceArgStr, dirURL:dirURL} + end repeat + + -- Example usage in UTM + tell application "/Users/naveenrajm/Library/Developer/Xcode/DerivedData/UTM-eazuxysqjkwsochfxxgoouqpkvjr/Build/Products/Debug/UTM.app" + set vm to virtual machine id vmId + set config to configuration of vm + + -- Get the current QEMU additional arguments + set qemuAddArgs to qemu additional arguments of config + + -- Add the new arguments to the existing ones + repeat with arg in qemuNewArgs + set end of qemuAddArgs to {argument string:fsdevArg of arg, file urls:{dirURL of arg}} + set end of qemuAddArgs to {argument string:deviceArg of arg} + end repeat + + -- Update the configuration with the new arguments list + set qemu additional arguments of config to qemuAddArgs + update configuration of vm with config + + -- Get the current directory shares in registry + set reg to registry of vm + -- Add new directory shares to the registry + set reg to reg & directoryList + -- Update registry of vm with new directory shares + update registry of vm with reg + end tell +end run \ No newline at end of file From b7cc1f0a83c6db4507cf5dd2254afc741eb436da Mon Sep 17 00:00:00 2001 From: Naveenraj M <22456988+naveenrajm7@users.noreply.github.com> Date: Sun, 19 Jan 2025 20:08:03 -0700 Subject: [PATCH 11/18] script: use vagrant given name as id for share folder --- lib/vagrant_utm/scripts/directory_share_add.applescript | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/vagrant_utm/scripts/directory_share_add.applescript b/lib/vagrant_utm/scripts/directory_share_add.applescript index 892b166..a1cc2e2 100644 --- a/lib/vagrant_utm/scripts/directory_share_add.applescript +++ b/lib/vagrant_utm/scripts/directory_share_add.applescript @@ -2,14 +2,14 @@ -- directory_share_add.applescript -- This script adds QEMU arguments for directory sharing in UTM (QEMU) for given id and directory pairs. -- Usage: osascript directory_share_add.applescript --id --dir --id --dir ... --- Example: osascript directory_share_add.applescript --id 1 --dir "/path/to/dir1" --id 2 --dir "/path/to/dir2" +-- Example: osascript directory_share_add.applescript --id no1 --dir "/path/to/dir1" --id no2 --dir "/path/to/dir2" -- Function to create QEMU arguments for directory sharing on createQemuArgsForDir(dirId, dirPath) -- Prepare the QEMU argument strings - set fsdevArgStr to "-fsdev local,id=virtfs" & dirId & ",path=" & dirPath & ",security_model=mapped-xattr" - set deviceArgStr to "-device virtio-9p-pci,fsdev=virtfs" & dirId & ",mount_tag=share" & dirId + set fsdevArgStr to "-fsdev local,id=" & dirId & ",path=" & dirPath & ",security_model=mapped-xattr" + set deviceArgStr to "-device virtio-9p-pci,fsdev=" & dirId & ",mount_tag=" & dirId return {fsdevArgStr, deviceArgStr} end createQemuArgsForDir @@ -68,7 +68,7 @@ on run argv end repeat -- Example usage in UTM - tell application "/Users/naveenrajm/Library/Developer/Xcode/DerivedData/UTM-eazuxysqjkwsochfxxgoouqpkvjr/Build/Products/Debug/UTM.app" + tell application "UTM" set vm to virtual machine id vmId set config to configuration of vm From 98c6726409574647814f2f95e7ea7b7ff3ce4b17 Mon Sep 17 00:00:00 2001 From: Naveenraj M <22456988+naveenrajm7@users.noreply.github.com> Date: Sun, 19 Jan 2025 20:11:52 -0700 Subject: [PATCH 12/18] driver: use directory share scripts --- lib/vagrant_utm/driver/version_4_6.rb | 4 ++-- ...hare_add.applescript => add_directory_share.applescript} | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) rename lib/vagrant_utm/scripts/{directory_share_add.applescript => add_directory_share.applescript} (92%) diff --git a/lib/vagrant_utm/driver/version_4_6.rb b/lib/vagrant_utm/driver/version_4_6.rb index 021f3f7..069b010 100644 --- a/lib/vagrant_utm/driver/version_4_6.rb +++ b/lib/vagrant_utm/driver/version_4_6.rb @@ -44,9 +44,9 @@ def share_folders(folders) @logger.debug("Sharing folders: #{folders}") folders.each do |folder| - args = ["--name", folder[:name], + args = ["--id", folder[:name], "--dir", folder[:hostpath]] - command = ["add_qemu_additional_args.applescript", @uuid, *args] + command = ["add_directory_share.applescript", @uuid, *args] execute_osa_script(command) end end diff --git a/lib/vagrant_utm/scripts/directory_share_add.applescript b/lib/vagrant_utm/scripts/add_directory_share.applescript similarity index 92% rename from lib/vagrant_utm/scripts/directory_share_add.applescript rename to lib/vagrant_utm/scripts/add_directory_share.applescript index a1cc2e2..d497aeb 100644 --- a/lib/vagrant_utm/scripts/directory_share_add.applescript +++ b/lib/vagrant_utm/scripts/add_directory_share.applescript @@ -1,8 +1,8 @@ --- --- directory_share_add.applescript +-- add_directory_share.applescript -- This script adds QEMU arguments for directory sharing in UTM (QEMU) for given id and directory pairs. --- Usage: osascript directory_share_add.applescript --id --dir --id --dir ... --- Example: osascript directory_share_add.applescript --id no1 --dir "/path/to/dir1" --id no2 --dir "/path/to/dir2" +-- Usage: osascript add_directory_share.applescript UUID --id --dir --id --dir ... +-- Example: osascript add_directory_share.applescript UUID --id no1 --dir "/path/to/dir1" --id no2 --dir "/path/to/dir2" -- Function to create QEMU arguments for directory sharing on createQemuArgsForDir(dirId, dirPath) From c24f45b93ec5caf8d7ab5b5afcd43f45dffeca53 Mon Sep 17 00:00:00 2001 From: Naveenraj M <22456988+naveenrajm7@users.noreply.github.com> Date: Sun, 19 Jan 2025 22:16:58 -0700 Subject: [PATCH 13/18] share folder: read shared folder to avoid duplicate entry Before every Boot share folder 'prepare' is called. Since we use qemu args to achieve shared folder, we should make sure we are not adding duplicate arguments for same folder Use share 'id' of UTM (vagrant's name ) as folder identifier --- lib/vagrant_utm/driver/version_4_6.rb | 20 +++++++- ...plescript => add_folder_share.applescript} | 14 +++--- .../scripts/read_shared_folders.js | 47 +++++++++++++++++++ 3 files changed, 72 insertions(+), 9 deletions(-) rename lib/vagrant_utm/scripts/{add_directory_share.applescript => add_folder_share.applescript} (90%) create mode 100644 lib/vagrant_utm/scripts/read_shared_folders.js diff --git a/lib/vagrant_utm/driver/version_4_6.rb b/lib/vagrant_utm/driver/version_4_6.rb index 069b010..23cd4b4 100644 --- a/lib/vagrant_utm/driver/version_4_6.rb +++ b/lib/vagrant_utm/driver/version_4_6.rb @@ -39,14 +39,30 @@ def export(path) execute_osa_script(command) end + def read_shared_folders + @logger.debug("Reading shared folders") + script_path = @script_path.join("read_shared_folders.js") + cmd = ["osascript", script_path.to_s, @uuid] + output = execute_shell(*cmd) + result = JSON.parse(output) + return unless result["status"] + + # Return the lits of shared folders names(id) + result["result"] + end + def share_folders(folders) - # log the folders we are going to share + shared_folders = read_shared_folders + @logger.debug("Shared folders: #{shared_folders}") @logger.debug("Sharing folders: #{folders}") folders.each do |folder| + # Skip if the folder is already shared + next if shared_folders.include?(folder[:name]) + args = ["--id", folder[:name], "--dir", folder[:hostpath]] - command = ["add_directory_share.applescript", @uuid, *args] + command = ["add_folder_share.applescript", @uuid, *args] execute_osa_script(command) end end diff --git a/lib/vagrant_utm/scripts/add_directory_share.applescript b/lib/vagrant_utm/scripts/add_folder_share.applescript similarity index 90% rename from lib/vagrant_utm/scripts/add_directory_share.applescript rename to lib/vagrant_utm/scripts/add_folder_share.applescript index d497aeb..cdd7877 100644 --- a/lib/vagrant_utm/scripts/add_directory_share.applescript +++ b/lib/vagrant_utm/scripts/add_folder_share.applescript @@ -77,7 +77,7 @@ on run argv -- Add the new arguments to the existing ones repeat with arg in qemuNewArgs - set end of qemuAddArgs to {argument string:fsdevArg of arg, file urls:{dirURL of arg}} + set end of qemuAddArgs to {argument string:fsdevArg of arg} set end of qemuAddArgs to {argument string:deviceArg of arg} end repeat @@ -85,11 +85,11 @@ on run argv set qemu additional arguments of config to qemuAddArgs update configuration of vm with config - -- Get the current directory shares in registry - set reg to registry of vm - -- Add new directory shares to the registry - set reg to reg & directoryList - -- Update registry of vm with new directory shares - update registry of vm with reg + -- -- Get the current directory shares in registry + -- set reg to registry of vm + -- -- Add new directory shares to the registry + -- set reg to reg & directoryList + -- -- Update registry of vm with new directory shares + -- update registry of vm with reg end tell end run \ No newline at end of file diff --git a/lib/vagrant_utm/scripts/read_shared_folders.js b/lib/vagrant_utm/scripts/read_shared_folders.js new file mode 100644 index 0000000..45b34e6 --- /dev/null +++ b/lib/vagrant_utm/scripts/read_shared_folders.js @@ -0,0 +1,47 @@ +/** + * Reads the shared directory IDs from the QEMU additional arguments of a specified VM in UTM. + * + * This function uses the UTM application to read the QEMU additional arguments + * of a VM identified by the given VM ID and extracts the IDs of shared directories. + * + * @param {string} vmIdentifier - The ID of the VM. + * @returns {string} A JSON string containing the shared directory IDs or an error message. + */ +function run(argv) { + // Check if a VM ID is provided + if (argv.length === 0) { + console.log("Usage: osascript -l JavaScript read_shared_directories.js "); + return JSON.stringify({ status: false, result: "No VM ID provided." }); + } + + const vmIdentifier = argv[0]; + const utm = Application('UTM'); + utm.includeStandardAdditions = true; + + try { + // Attempt to get the VM by ID + const vm = utm.virtualMachines.byId(vmIdentifier); + // Get the config of the VM + const config = vm.configuration(); + // Get the QEMU additional arguments + const qemuArgs = config.qemuAdditionalArguments; + + // Extract shared directory IDs + const sharedDirIds = []; + qemuArgs.forEach(arg => { + const argStr = arg.argumentString; + if (argStr.startsWith("-fsdev")) { + const match = argStr.match(/id=([^,]+)/); + if (match) { + sharedDirIds.push(match[1]); + } + } + }); + + // Return the shared directory IDs + return JSON.stringify({ status: true, result: sharedDirIds }); + } catch (error) { + // Return an error message + return JSON.stringify({ status: false, result: error.message }); + } +} \ No newline at end of file From 5b4349cac89b1588ee5de400a07b8c8129144895 Mon Sep 17 00:00:00 2001 From: Naveenraj M <22456988+naveenrajm7@users.noreply.github.com> Date: Sun, 19 Jan 2025 22:52:55 -0700 Subject: [PATCH 14/18] fix (share folder) : add required function from vagrant This module was outside the vagrant library --- lib/vagrant_utm/cap/mount_options.rb | 2 +- lib/vagrant_utm/util/unix_mount_helpers.rb | 124 +++++++++++++++++++++ 2 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 lib/vagrant_utm/util/unix_mount_helpers.rb diff --git a/lib/vagrant_utm/cap/mount_options.rb b/lib/vagrant_utm/cap/mount_options.rb index 37724bd..0adf535 100644 --- a/lib/vagrant_utm/cap/mount_options.rb +++ b/lib/vagrant_utm/cap/mount_options.rb @@ -3,7 +3,7 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: BUSL-1.1 -require_relative "../../../synced_folders/unix_mount_helpers" +require_relative "../util/unix_mount_helpers" module VagrantPlugins module Utm diff --git a/lib/vagrant_utm/util/unix_mount_helpers.rb b/lib/vagrant_utm/util/unix_mount_helpers.rb new file mode 100644 index 0000000..c226ef2 --- /dev/null +++ b/lib/vagrant_utm/util/unix_mount_helpers.rb @@ -0,0 +1,124 @@ +# frozen_string_literal: true + +# Copied from vagrant/plugins/synced_folder/unix_mount_helpers.rb +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: BUSL-1.1 + +require "shellwords" +require "vagrant/util/retryable" + +module VagrantPlugins + module SyncedFolder + # Contains helper methods for mounting folders on Unix-based systems. + module UnixMountHelpers # rubocop:disable Metrics/ModuleLength + def self.extended(klass) + unless klass.class_variable_defined?(:@@logger) + klass.class_variable_set(:@@logger, Log4r::Logger.new(klass.name.downcase)) # rubocop:disable Style/ClassVars + end + klass.extend Vagrant::Util::Retryable + end + + def detect_owner_group_ids(machine, guest_path, mount_options, options) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity + mount_uid = find_mount_options_id("uid", mount_options) + mount_gid = find_mount_options_id("gid", mount_options) + + if mount_uid.nil? + if options[:owner].to_i.to_s == options[:owner].to_s + mount_uid = options[:owner] + class_variable_get(:@@logger).debug("Owner user ID (provided): #{mount_uid}") + else + output = { stdout: "", stderr: "" } + uid_command = "id -u #{options[:owner]}" + machine.communicate.execute(uid_command, + error_class: Vagrant::Errors::VirtualBoxMountFailed, + error_key: :virtualbox_mount_failed, + command: uid_command, + output: output[:stderr]) { |type, data| output[type] << data if output[type] } + mount_uid = output[:stdout].chomp + class_variable_get(:@@logger).debug("Owner user ID (lookup): #{options[:owner]} -> #{mount_uid}") + end + else + machine.ui.warn "Detected mount owner ID within mount options. (uid: #{mount_uid} guestpath: #{guest_path})" + end + + if mount_gid.nil? + if options[:group].to_i.to_s == options[:group].to_s + mount_gid = options[:group] + class_variable_get(:@@logger).debug("Owner group ID (provided): #{mount_gid}") + else + begin + output = { stdout: "", stderr: "" } + gid_command = "getent group #{options[:group]}" + machine.communicate.execute(gid_command, + error_class: Vagrant::Errors::VirtualBoxMountFailed, + error_key: :virtualbox_mount_failed, + command: gid_command, + output: output[:stderr]) { |type, data| output[type] << data if output[type] } + mount_gid = output[:stdout].split(":").at(2).to_s.chomp + class_variable_get(:@@logger).debug("Owner group ID (lookup): #{options[:group]} -> #{mount_gid}") + rescue Vagrant::Errors::VirtualBoxMountFailed + if options[:owner] == options[:group] # rubocop:disable Metrics/BlockNesting + class_variable_get(:@@logger).debug("Failed to locate group `#{options[:group]}`. Group name matches owner. Fetching effective group ID.") # rubocop:disable Layout/LineLength + output = { stdout: "" } + result = machine.communicate.execute("id -g #{options[:owner]}", + error_check: false) do |type, data| + output[type] << data if output[type] # rubocop:disable Metrics/BlockNesting + end + mount_gid = output[:stdout].chomp if result.zero? # rubocop:disable Metrics/BlockNesting + class_variable_get(:@@logger).debug("Owner group ID (effective): #{mount_gid}") + end + raise unless mount_gid + end + end + else + machine.ui.warn "Detected mount group ID within mount options. (gid: #{mount_gid} guestpath: #{guest_path})" + end + { gid: mount_gid, uid: mount_uid } + end + + def find_mount_options_id(id_name, mount_options) # rubocop:disable Metrics/AbcSize + id_line = mount_options.detect { |line| line.include?("#{id_name}=") } + if id_line + match = id_line.match(/,?#{Regexp.escape(id_name)}=(?\d+),?/) + found_id = match["option_id"] + updated_id_line = [ + match.pre_match, + match.post_match + ].find_all { |string| !string.empty? }.join(",") + if updated_id_line.empty? + mount_options.delete(id_line) + else + idx = mount_options.index(id_line) + mount_options.delete(idx) + mount_options.insert(idx, updated_id_line) + end + end + found_id + end + + def emit_upstart_notification(machine, guest_path) + # Emit an upstart event if we can + machine.communicate.sudo <<-NOTIFICATION.gsub(/^ {12}/, "") + if test -x /sbin/initctl && command -v /sbin/init && /sbin/init 2>/dev/null --version | grep upstart; then + /sbin/initctl emit --no-wait vagrant-mounted MOUNTPOINT=#{guest_path} + fi + NOTIFICATION + end + + def merge_mount_options(base, overrides) # rubocop:disable Metrics/AbcSize + base = base.join(",").split(",") + overrides = overrides.join(",").split(",") + b_kv = Hash[base.map { |item| item.split("=", 2) }] + o_kv = Hash[overrides.map { |item| item.split("=", 2) }] + merged = {}.tap do |opts| + (b_kv.keys + o_kv.keys).uniq.each do |key| + opts[key] = o_kv.fetch(key, b_kv[key]) + end + end + merged.map do |key, value| + [key, value].compact.join("=") + end + end + end + end +end From 93243845f6b15033ab46c83a76e8756c16806eac Mon Sep 17 00:00:00 2001 From: Naveenraj M <22456988+naveenrajm7@users.noreply.github.com> Date: Sun, 19 Jan 2025 23:48:54 -0700 Subject: [PATCH 15/18] util (bug) : fix string error can't modify frozen String: "" replace "" with String.new to make it not a frozen variable --- lib/vagrant_utm/util/unix_mount_helpers.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/vagrant_utm/util/unix_mount_helpers.rb b/lib/vagrant_utm/util/unix_mount_helpers.rb index c226ef2..33d1f1f 100644 --- a/lib/vagrant_utm/util/unix_mount_helpers.rb +++ b/lib/vagrant_utm/util/unix_mount_helpers.rb @@ -27,7 +27,7 @@ def detect_owner_group_ids(machine, guest_path, mount_options, options) # ruboco mount_uid = options[:owner] class_variable_get(:@@logger).debug("Owner user ID (provided): #{mount_uid}") else - output = { stdout: "", stderr: "" } + output = { stdout: String.new, stderr: String.new } # Ensure strings are not frozen uid_command = "id -u #{options[:owner]}" machine.communicate.execute(uid_command, error_class: Vagrant::Errors::VirtualBoxMountFailed, @@ -47,7 +47,7 @@ def detect_owner_group_ids(machine, guest_path, mount_options, options) # ruboco class_variable_get(:@@logger).debug("Owner group ID (provided): #{mount_gid}") else begin - output = { stdout: "", stderr: "" } + { stdout: String.new, stderr: String.new } # Ensure strings are not frozen gid_command = "getent group #{options[:group]}" machine.communicate.execute(gid_command, error_class: Vagrant::Errors::VirtualBoxMountFailed, @@ -59,7 +59,7 @@ def detect_owner_group_ids(machine, guest_path, mount_options, options) # ruboco rescue Vagrant::Errors::VirtualBoxMountFailed if options[:owner] == options[:group] # rubocop:disable Metrics/BlockNesting class_variable_get(:@@logger).debug("Failed to locate group `#{options[:group]}`. Group name matches owner. Fetching effective group ID.") # rubocop:disable Layout/LineLength - output = { stdout: "" } + output = { stdout: String.new } result = machine.communicate.execute("id -g #{options[:owner]}", error_check: false) do |type, data| output[type] << data if output[type] # rubocop:disable Metrics/BlockNesting From 122f45f7048680a666cf3b6948d0f7c6b8fc9853 Mon Sep 17 00:00:00 2001 From: Naveenraj M <22456988+naveenrajm7@users.noreply.github.com> Date: Mon, 20 Jan 2025 01:06:27 -0700 Subject: [PATCH 16/18] config : make virtFS as default share mode We implement UTM directory share for virtFS and hence the default mode should be set accordingly Add TODOs after UTM support --- lib/vagrant_utm/config.rb | 9 +++++++-- lib/vagrant_utm/driver/version_4_6.rb | 6 +++--- lib/vagrant_utm/scripts/add_folder_share.applescript | 2 ++ 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/vagrant_utm/config.rb b/lib/vagrant_utm/config.rb index e8dec6f..3e88efa 100644 --- a/lib/vagrant_utm/config.rb +++ b/lib/vagrant_utm/config.rb @@ -85,6 +85,8 @@ def notes=(notes) customize("pre-boot", ["customize_vm.applescript", :id, "--notes", notes]) end + # TODO: All warning if user sets directory_share_mode, + # because default implementation is 'virtFS' # Shortcut for setting the directory share mode of the virtual machine. # Calls #customize internally. # @@ -110,10 +112,13 @@ def directory_share_mode=(mode) # This is the hook that is called to finalize the object before it # is put into use. def finalize! + # By default, we check for guest additions (qemu-ga) @check_guest_additions = true if @check_guest_additions == UNSET_VALUE - + # Always set the directory share mode to 'virtFS' + # default share folder implementation in utm plugin + self.directory_share_mode = "virtFS" + # By default, we assume the VM supports virtio 9p filesystems @functional_9pfs = true if @functional_9pfs == UNSET_VALUE - # The default name is just nothing, and we default it @name = nil if @name == UNSET_VALUE diff --git a/lib/vagrant_utm/driver/version_4_6.rb b/lib/vagrant_utm/driver/version_4_6.rb index 23cd4b4..aa433cb 100644 --- a/lib/vagrant_utm/driver/version_4_6.rb +++ b/lib/vagrant_utm/driver/version_4_6.rb @@ -13,6 +13,7 @@ def initialize(uuid) @logger = Log4r::Logger.new("vagrant::provider::utm::version_4_6") end + # TODO: Implement clear_shared_folders def clear_shared_folders; end def import(utm) @@ -67,11 +68,10 @@ def share_folders(folders) end end + # TODO: Implement unshare_folders def unshare_folders(folders) folders.each do |folder| - args = ["--name", folder] - command = ["remove_qemu_additional_args.applescript", @uuid, *args] - execute_osa_script(command) + @logger.debug("NOT IMPLEMENTED: unshare_folders(#{folder})") end end end diff --git a/lib/vagrant_utm/scripts/add_folder_share.applescript b/lib/vagrant_utm/scripts/add_folder_share.applescript index cdd7877..c8f882f 100644 --- a/lib/vagrant_utm/scripts/add_folder_share.applescript +++ b/lib/vagrant_utm/scripts/add_folder_share.applescript @@ -77,6 +77,7 @@ on run argv -- Add the new arguments to the existing ones repeat with arg in qemuNewArgs + -- TODO: Add file urls to config after UTM supports it. UTM#6977 set end of qemuAddArgs to {argument string:fsdevArg of arg} set end of qemuAddArgs to {argument string:deviceArg of arg} end repeat @@ -85,6 +86,7 @@ on run argv set qemu additional arguments of config to qemuAddArgs update configuration of vm with config + -- TODO: Enabel registry update after UTM supports it. UTM#6977 -- -- Get the current directory shares in registry -- set reg to registry of vm -- -- Add new directory shares to the registry From 12548852bd3c4a75206ba2ffa7834372f3ade573 Mon Sep 17 00:00:00 2001 From: Naveenraj M <22456988+naveenrajm7@users.noreply.github.com> Date: Mon, 20 Jan 2025 17:40:37 -0700 Subject: [PATCH 17/18] share folder: add clear shared folder implementation - read shared folders (sf) - collect qemu args of sf - remove qemu args of sf --- lib/vagrant_utm/driver/version_4_6.rb | 23 +++++++- .../scripts/read_shared_folders_args.js | 53 +++++++++++++++++++ 2 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 lib/vagrant_utm/scripts/read_shared_folders_args.js diff --git a/lib/vagrant_utm/driver/version_4_6.rb b/lib/vagrant_utm/driver/version_4_6.rb index aa433cb..25cd8a9 100644 --- a/lib/vagrant_utm/driver/version_4_6.rb +++ b/lib/vagrant_utm/driver/version_4_6.rb @@ -13,8 +13,24 @@ def initialize(uuid) @logger = Log4r::Logger.new("vagrant::provider::utm::version_4_6") end - # TODO: Implement clear_shared_folders - def clear_shared_folders; end + # Implement clear_shared_folders + def clear_shared_folders + # Get the list of shared folders + shared_folders = read_shared_folders + # Get the args to remove the shared folders + script_path = @script_path.join("read_shared_folders_args.js") + cmd = ["osascript", script_path.to_s, @uuid, "--ids", shared_folders.join(",")] + output = execute_shell(*cmd) + result = JSON.parse(output) + return unless result["status"] + + # Flatten the list of args and build the command + sf_args = result["result"].flatten + return unless sf_args.any? + + command = ["remove_qemu_additional_args.applescript", @uuid, "--args", *sf_args] + execute_osa_script(command) + end def import(utm) utm = Vagrant::Util::Platform.windows_path(utm) @@ -53,6 +69,9 @@ def read_shared_folders end def share_folders(folders) + # sync folder cleanup will call clear_shared_folders + # This is just a precaution, to make sure we don't + # have duplicate shared folders shared_folders = read_shared_folders @logger.debug("Shared folders: #{shared_folders}") @logger.debug("Sharing folders: #{folders}") diff --git a/lib/vagrant_utm/scripts/read_shared_folders_args.js b/lib/vagrant_utm/scripts/read_shared_folders_args.js new file mode 100644 index 0000000..87df8b9 --- /dev/null +++ b/lib/vagrant_utm/scripts/read_shared_folders_args.js @@ -0,0 +1,53 @@ +/** + * Reads the shared folder QEMU arguments for specified IDs from the QEMU additional arguments of a VM in UTM. + * + * This function uses the UTM application to read the QEMU additional arguments + * of a VM identified by the given VM ID and extracts the arguments for the specified IDs. + * + * @param {string} vmIdentifier - The ID of the VM. + * @param {string} ids - Comma-separated list of directory IDs. + * @returns {string} A JSON string containing the QEMU arguments for the specified IDs or an error message. + */ +function run(argv) { + // Check if a VM ID and IDs are provided + if (argv.length < 2) { + console.log("Usage: osascript -l JavaScript read_shared_folders_args.js --ids ,"); + return JSON.stringify({ status: false, result: "No VM ID or IDs provided." }); + } + + const vmIdentifier = argv[0]; + const idsArgIndex = argv.indexOf("--ids"); + if (idsArgIndex === -1 || idsArgIndex + 1 >= argv.length) { + return JSON.stringify({ status: false, result: "No IDs provided." }); + } + const ids = argv[idsArgIndex + 1].split(","); + + const utm = Application('UTM'); + utm.includeStandardAdditions = true; + + try { + // Attempt to get the VM by ID + const vm = utm.virtualMachines.byId(vmIdentifier); + // Get the config of the VM + const config = vm.configuration(); + // Get the QEMU additional arguments + const qemuArgs = config.qemuAdditionalArguments; + + // Extract QEMU arguments for the specified IDs + const sharedDirArgs = []; + qemuArgs.forEach(arg => { + const argStr = arg.argumentString; + ids.forEach(id => { + if (argStr.includes(`id=${id}`) || argStr.includes(`fsdev=${id}`)) { + sharedDirArgs.push(argStr); + } + }); + }); + + // Return the QEMU arguments for the specified IDs + return JSON.stringify({ status: true, result: sharedDirArgs }); + } catch (error) { + // Return an error message + return JSON.stringify({ status: false, result: error.message }); + } +} \ No newline at end of file From 08dd7dce9127094a9f1e7f2caccc03faba052ed9 Mon Sep 17 00:00:00 2001 From: Naveenraj M <22456988+naveenrajm7@users.noreply.github.com> Date: Mon, 20 Jan 2025 17:41:03 -0700 Subject: [PATCH 18/18] notes: how to fix version mismatch --- notes/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/notes/README.md b/notes/README.md index 8771746..91b7290 100644 --- a/notes/README.md +++ b/notes/README.md @@ -45,4 +45,6 @@ To update project after a version bump ``` Unable to resolve dependency: user requested 'vagrant_utm (= 0.1.1)' ``` -Try changing the version number again + +Due to mismatch versions between global installed version and plugin version in the development setup, since they are same name. +Fix: Uninstall the global version, while using different version of development setup