#!/usr/bin/env ruby require 'hackmac' include Hackmac require 'term/ansicolor' class String include Term::ANSIColor end require 'tabulo' def x(cmd, verbose: true) prompt = cmd =~ /\A\s*sudo/ ? ?# : ?$ output = `#{cmd}` if $?.success? print "#{prompt} #{cmd}".green puts verbose ? "" : " >/dev/null".yellow else print "#{prompt} #{cmd}".red puts verbose ? "" : " >/dev/null".yellow STDERR.puts "command #{cmd.inspect} failed with exit status #{$?.exitstatus}".on_red.white end if verbose print output.italic end output end def clone(from:, to:) print "Cloning from #{from} to #{to} now? (y/n) ".bold.yellow if gets !~ /\Ay/i return end x %{sudo mkdir -v "/Volumes/#{from}"} x %{sudo mkdir -v "/Volumes/#{to}"} x %{sudo diskutil mount -mountPoint "/Volumes/#{from}" "#{from}"} x %{sudo diskutil mount -mountPoint "/Volumes/#{to}" "#{to}"} x %{rsync -nav --exclude ".*" --delete "/Volumes/#{from}/" "/Volumes/#{to}/"} print "This will be copied/deleted. Really do it? (y/n) ".bold.yellow if gets !~ /\Ay/i puts " *** Interrupted.".bold.yellow exit 1 end x %{rsync -av --exclude ".*" --delete "/Volumes/#{from}/" "/Volumes/#{to}/"} end def boot_dev unless @boot_dev value = x(%{bdmesg}, verbose: false).lines. find { |l| l =~ /SelfDevicePath=(.*)\r/ and break $1 } uuid = value.split('\\').last[/(?: h { h.sub(/\A[a-z]/) { $&.upcase }.bold } case command = ARGV.shift when 'help', nil usage when 'mount' #if File.exist?('/Volumes/EFI') # raise "/Volumes/EFI already exists => Sort this out first." #end mdev = device_name(ARGV.shift) x %{sudo diskutil mount "#{mdev}"} # TODO symlink to /Volumes/mdev, mdev should be foo['MountPoint'] from efi boot when /\Aun?mount\z/ mdev = device_name(ARGV.shift) x %{sudo diskutil unmount "#{mdev}"} # TODO remove /Volumes/mdev if it is a symlink when 'clone' from = ARGV.shift or fail "need from argument" from = device_name(from) to = ARGV.shift or fail "need to argument" to = device_name(to) from != to or fail "cloning only allowed from one partition to another" clone from: from, to: to when 'kexts' mdev = device_name(ARGV.shift) x %{sudo diskutil mount "#{mdev}"} on_efi = Dir["/Volumes/#{mdev}/#{$config.kext.efi_path}/*.kext"].map { |path| Kext.new(path: path, config: $config) }.sort_by(&:name) puts 'EFI'.yellow.bold + " (#{mdev})".bold puts Tabulo::Table.new(on_efi, align_header: :left, border: :modern) { |t| t.add_column(:name, header_styler: bold_head) t.add_column(:itself, header: 'Version/Remote', styler: -> v, s { v.version < v.remote_version ? s.red : s.green rescue s.yellow }, formatter: -> e { "%s %s %s" % [ e.version, ({ 0 => ?=, -1 => ?<, 1 => ?> }[e.version <=> e.remote_version] rescue nil), e.remote_version ] }, header_styler: bold_head) }.pack when 'kext' path = ARGV.shift or fail 'need kext dir' puts Kext.new(path: path) when 'list' disks = Disks.new efis = [] disks.as_hash['AllDisksAndPartitions'].each_with_object([]) { |d, ps| uuids = Array(d['Partitions']&.map { |p| p['DiskUUID'] }&.compact) efis.concat uuids. map { |uuid| DiskInfo.new(disk: uuid) }. select { |di| di.FilesystemType == 'msdos' } } efis.each do |e| e.Booted = e.DeviceIdentifier == boot_dev e.Volumes = ContainerDisk.new(disk: e.ParentWholeDisk, limiter: 'internal').VolumesFromDisks end puts Tabulo::Table.new(efis, align_header: :left, border: :modern) { |t| t.add_column(:Booted, styler: -> v, _ { v == ?☑ ? v.green : v.red }, header_styler: bold_head) { |e| e.Booted ? ?☑ : ?☐ } t.add_column(:VolumeName, header_styler: bold_head) t.add_column(:DeviceIdentifier, header_styler: bold_head) t.add_column(:ParentWholeDisk, header_styler: bold_head) t.add_column(:DiskUUID, header_styler: bold_head) t.add_column(:Volumes, header_styler: bold_head) { |e| e.Volumes * ?, } t.add_column(:MountPoint, styler: -> v, _ { v.empty? ? v : v.green }, header_styler: bold_head) }.pack else fail "don't know how to #{command}" end