Class: Macinbox::Actions::CreateHDDFromImage

Inherits:
Object
  • Object
show all
Defined in:
lib/macinbox/actions/create_hdd_from_image.rb

Instance Method Summary collapse

Constructor Details

#initialize(opts) ⇒ CreateHDDFromImage

Returns a new instance of CreateHDDFromImage.

Raises:



16
17
18
19
20
21
22
23
24
25
# File 'lib/macinbox/actions/create_hdd_from_image.rb', line 16

def initialize(opts)
  @input_image       = opts[:image_path]     or raise ArgumentError.new(":image_path not specified")
  @output_path       = opts[:hdd_path]       or raise ArgumentError.new(":hdd_path not specified")
  @parallels_app     = opts[:parallels_path] or raise ArgumentError.new(":parallels_path not specified")

  @collector         = opts[:collector]      or raise ArgumentError.new(":collector not specified")

  raise Macinbox::Error.new("input image not found")       unless File.exist? @input_image
  raise Macinbox::Error.new("Parallels Desktop not found") unless File.exist? @parallels_app
end

Instance Method Details

#attach_imageObject



49
50
51
52
53
54
55
56
57
58
# File 'lib/macinbox/actions/create_hdd_from_image.rb', line 49

def attach_image
  Logger.info "Attaching the image..." do
    @disk = VirtualDisk.new(@image)
    @collector.on_cleanup { @disk.detach! }
    @image_mountpoint = "#{@temp_dir}/image_mountpoint"
    FileUtils.mkdir @image_mountpoint
    @disk.attach
    @disk.mount(at: @image_mountpoint, owners: true)
  end
end

#convert_imageObject



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/macinbox/actions/create_hdd_from_image.rb', line 123

def convert_image
  Logger.info "Converting the image to HDD format..." do

    disk_info = Task.backtick %W[ /usr/sbin/fdisk #{@disk.device} ]

    geometry_re = /geometry: (\d+)\/(\d+)\/(\d+) \[(\d+) sectors\]/

    match = geometry_re.match(disk_info)

    raise Macinbox::Error.new("failed to determine disk geometry") if match.nil? || match.captures.length != 4

    device_sectors = match.captures[3]

    device_cylinders = match.captures[0]
    device_heads_per_track = match.captures[1]
    device_sectors_per_track = match.captures[2]

    bios_cylinders = 1024
    bios_heads_per_track = device_heads_per_track
    bios_sectors_per_track = device_sectors_per_track

    File.write "#{@temp_dir}/macinbox.vmdk", <<~EOF
      # Disk DescriptorFile
      version=1
      encoding="UTF-8"
      CID=fffffffe
      parentCID=ffffffff
      isNativeSnapshot="no"
      createType="monolithicFlat"

      # Extent description
      RW #{device_sectors} FLAT "#{@disk.device}" 0

      # The Disk Data Base
      #DDB

      ddb.adapterType = "lsilogic"
      ddb.deletable = "true"
      ddb.geometry.biosCylinders = "#{bios_cylinders}"
      ddb.geometry.biosHeads = "#{bios_heads_per_track}"
      ddb.geometry.biosSectors = "#{bios_sectors_per_track}"
      ddb.geometry.cylinders = "#{device_cylinders}"
      ddb.geometry.heads = "#{device_heads_per_track}"
      ddb.geometry.sectors = "#{device_sectors_per_track}"
      ddb.longContentID = "9fa218b506cfe68615c39994fffffffe"
      ddb.uuid = "60 00 C2 99 91 76 dd 77-6e 0d 84 8b b0 24 6e 00"
      ddb.virtualHWVersion = "14"
    EOF

    prl_convert = "#{@parallels_app}/Contents/MacOS/prl_convert"
    task_opts = $verbose ? {} : { :out => File::NULL }
    Task.run %W[ #{prl_convert} #{@temp_dir}/macinbox.vmdk --allow-no-os --dst=#{@temp_dir} ] + [task_opts]
    @disk.eject
  end
end

#copy_input_imageObject



42
43
44
45
46
47
# File 'lib/macinbox/actions/create_hdd_from_image.rb', line 42

def copy_input_image
  Logger.info "Copying the image..." do
    @image = "#{@temp_dir}/macinbox.sparseimage"
    Macinbox::copyfiles(from: @input_image, to: @image)
  end
end

#create_temp_dirObject



37
38
39
40
# File 'lib/macinbox/actions/create_hdd_from_image.rb', line 37

def create_temp_dir
  @temp_dir = Task.backtick %W[ /usr/bin/mktemp -d -t create_hdd_from_image ]
  @collector.add_temp_dir @temp_dir
end

#eject_and_reattach_imageObject



116
117
118
119
120
121
# File 'lib/macinbox/actions/create_hdd_from_image.rb', line 116

def eject_and_reattach_image
  Logger.info "Reattaching the image..." do
    @disk.eject
    @disk.attach
  end
end

#install_parallels_toolsObject



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/macinbox/actions/create_hdd_from_image.rb', line 60

def install_parallels_tools

  Logger.info "Installing the Parallels Tools..." do

    tools_image = "#{@parallels_app}/Contents/Resources/Tools/prl-tools-mac.iso"

    tools_disk = VirtualDisk.new(tools_image)

    @collector.on_cleanup { tools_disk.detach! }

    tools_mountpoint = "#{@temp_dir}/tools_mountpoint"
    FileUtils.mkdir tools_mountpoint

    tools_disk.attach
    tools_disk.mount(at: tools_mountpoint)

    tools_packages_dir = "#{tools_mountpoint}/Install.app/Contents/Resources/Install.mpkg/Contents/Packages"

    tools_packages = [
      "Parallels Tools Audio 10.9.pkg",
      "Parallels Tools Coherence.pkg",
      "Parallels Tools CopyPaste.pkg",
      "Parallels Tools DragDrop.pkg",
      "Parallels Tools HostTime.pkg",
      "Parallels Tools InstallationAgent.pkg",
      "Parallels Tools Network 10.9.pkg",
      "Parallels Tools SharedFolders.pkg",
      "Parallels Tools TimeSync.pkg",
      "Parallels Tools ToolGate 10.9.pkg",
      "Parallels Tools Utilities.pkg",
      "Parallels Tools Video 10.9.pkg"
    ]

    tools_expanded_packages_dir = "#{@temp_dir}/tools_packages"
    FileUtils.mkdir tools_expanded_packages_dir

    tools_packages.each do |package|
      Task.run %W[ /usr/sbin/pkgutil --expand #{tools_packages_dir}/#{package} #{tools_expanded_packages_dir}/#{package} ]
      Task.run %W[ /usr/bin/ditto -x -z #{tools_expanded_packages_dir}/#{package}/Payload #{@image_mountpoint} ]
    end

    prl_nettool_source = "/Library/Parallels Guest Tools/prl_nettool"
    prl_nettool_target = "#{@image_mountpoint}/usr/local/bin/prl_nettool"

    FileUtils.mkdir_p File.dirname(prl_nettool_target)
    FileUtils.ln_s prl_nettool_source, prl_nettool_target

    prl_fsd_plist = "#{@image_mountpoint}/Library/LaunchDaemons/com.parallels.vm.prl_fsd.plist"
    Task.run %W[ /usr/bin/sed -i #{''} s/PARALLELS_ADDITIONAL_ARGS/--share/ #{prl_fsd_plist} ]

    contents = "/Library/Parallels\ Guest\ Tools/dynres --enable-retina\n"
    File.write "#{@image_mountpoint}/private/etc/rc.vagrant", contents, mode: 'a'

    tools_disk.eject
  end

  def eject_and_reattach_image
    Logger.info "Reattaching the image..." do
      @disk.eject
      @disk.attach
    end
  end

  def convert_image
    Logger.info "Converting the image to HDD format..." do

      disk_info = Task.backtick %W[ /usr/sbin/fdisk #{@disk.device} ]

      geometry_re = /geometry: (\d+)\/(\d+)\/(\d+) \[(\d+) sectors\]/

      match = geometry_re.match(disk_info)

      raise Macinbox::Error.new("failed to determine disk geometry") if match.nil? || match.captures.length != 4

      device_sectors = match.captures[3]

      device_cylinders = match.captures[0]
      device_heads_per_track = match.captures[1]
      device_sectors_per_track = match.captures[2]

      bios_cylinders = 1024
      bios_heads_per_track = device_heads_per_track
      bios_sectors_per_track = device_sectors_per_track

      File.write "#{@temp_dir}/macinbox.vmdk", <<~EOF
        # Disk DescriptorFile
        version=1
        encoding="UTF-8"
        CID=fffffffe
        parentCID=ffffffff
        isNativeSnapshot="no"
        createType="monolithicFlat"

        # Extent description
        RW #{device_sectors} FLAT "#{@disk.device}" 0

        # The Disk Data Base
        #DDB

        ddb.adapterType = "lsilogic"
        ddb.deletable = "true"
        ddb.geometry.biosCylinders = "#{bios_cylinders}"
        ddb.geometry.biosHeads = "#{bios_heads_per_track}"
        ddb.geometry.biosSectors = "#{bios_sectors_per_track}"
        ddb.geometry.cylinders = "#{device_cylinders}"
        ddb.geometry.heads = "#{device_heads_per_track}"
        ddb.geometry.sectors = "#{device_sectors_per_track}"
        ddb.longContentID = "9fa218b506cfe68615c39994fffffffe"
        ddb.uuid = "60 00 C2 99 91 76 dd 77-6e 0d 84 8b b0 24 6e 00"
        ddb.virtualHWVersion = "14"
      EOF

      prl_convert = "#{@parallels_app}/Contents/MacOS/prl_convert"
      task_opts = $verbose ? {} : { :out => File::NULL }
      Task.run %W[ #{prl_convert} #{@temp_dir}/macinbox.vmdk --allow-no-os --dst=#{@temp_dir} ] + [task_opts]
      @disk.eject
    end
  end

  def save_image
    Logger.info "Moving the HDD to the destination..." do
      FileUtils.chown_R ENV["SUDO_USER"], nil, "#{@temp_dir}/macinbox.hdd"
      FileUtils.mv "#{@temp_dir}/macinbox.hdd", @output_path
    end
  end

end

#runObject



27
28
29
30
31
32
33
34
35
# File 'lib/macinbox/actions/create_hdd_from_image.rb', line 27

def run
  create_temp_dir
  copy_input_image
  attach_image
  install_parallels_tools
  eject_and_reattach_image
  convert_image
  save_image
end

#save_imageObject



179
180
181
182
183
184
# File 'lib/macinbox/actions/create_hdd_from_image.rb', line 179

def save_image
  Logger.info "Moving the HDD to the destination..." do
    FileUtils.chown_R ENV["SUDO_USER"], nil, "#{@temp_dir}/macinbox.hdd"
    FileUtils.mv "#{@temp_dir}/macinbox.hdd", @output_path
  end
end