From 3eb91f4708bd06e0f91bc12384f73b4868c5c3c4 Mon Sep 17 00:00:00 2001 From: Denis Knauf Date: Wed, 25 Apr 2018 16:49:07 +0200 Subject: [PATCH] to lvm, xfs project. raspbian ready --- armbian2lvm_xfs.rb | 155 +++++++++++++++ lib/to_lvm_xfs.rb | 1 + lib/to_lvm_xfs/base.rb | 380 +++++++++++++++++++++++++++++++++++++ lib/to_lvm_xfs/exts.rb | 38 ++++ lib/to_lvm_xfs/raspbian.rb | 160 ++++++++++++++++ lib/to_lvm_xfs/sh.rb | 246 ++++++++++++++++++++++++ lib/to_lvm_xfs/structs.rb | 49 +++++ raspbian.rb | 7 + 8 files changed, 1036 insertions(+) create mode 100755 armbian2lvm_xfs.rb create mode 100644 lib/to_lvm_xfs.rb create mode 100644 lib/to_lvm_xfs/base.rb create mode 100644 lib/to_lvm_xfs/exts.rb create mode 100644 lib/to_lvm_xfs/raspbian.rb create mode 100644 lib/to_lvm_xfs/sh.rb create mode 100644 lib/to_lvm_xfs/structs.rb create mode 100755 raspbian.rb diff --git a/armbian2lvm_xfs.rb b/armbian2lvm_xfs.rb new file mode 100755 index 0000000..5283262 --- /dev/null +++ b/armbian2lvm_xfs.rb @@ -0,0 +1,155 @@ +#!/usr/bin/env ruby + +require 'pathname' +$: << Pathname.new($0).expand_path.dirname+'lib' +require 'base' +require 'active_support/all' + +Sector = Sectors = 512 + +class Armbian2LVM_XFS < Base + def initialize vgname = nil + super + chs = [?a..?z, ?A..?Z].map(&:to_a).flatten + @vgname = vgname || "armbian_#{5.times.map{chs[rand(chs.length)]}.join}" + end + + def remove_all_partitions_from image + o = `parted -ms #{image.to_s.shellescape} print` + d "Cannot determine partitions in dest-image #{image}", 0 == $? + o.split( "\n")[2..-1].each do + sh.parted image, :rm, 1 + end + end + + def run + mkmppaths + + d "Base image does not exist", base.image.exist? + base_parts = kpartx base.image + d "two (root-)partition in base expected, got: #{base_parts.inspect}", 1 == base_parts.length + + mount -:oro, base_parts[0], base.root + + # creates an image + dest.image.open 'w' do |f| + #f.pos = 1*Sector + ## copy first 4mb from base-image because of u-boot + #f.write base.image.read( 4.megabytes-Sector, Sector) + f.pos = 1.6.gigabytes-1 + f.putc 0.chr + end + # partitions for: /boot (128MiBi), LVM + sh.parted dest.image, *%w[-- + mklabel msdos + mkpart primary ext2 4MB 132MB + mkpart primary ext2 132MB -1s + set 2 LVM on + print] + + sh.bash -:ec, <<-EOF.gsub( /^\t{3}/, '').sub( /\n$/m, '') + BRANCH=dev + source ./build/config/sources/odroidxu4.conf + write_uboot_platform base/root/usr/lib/linux-u-boot-dev-odroidxu4_5.34.171103_armhf dest/base + EOF + + dest_parts = kpartx dest.image + d "two partitions in destination expected", 2 == dest_parts.length + vgpath = Pathname.new( '/dev') + vgname + sh.pvcreate dest_parts[1] + sh.vgcreate vgname, dest_parts[1] + sh.lvcreate -:nroot, '-L1.1G', vgname + sh.lvcreate -:nhome, '-L100M', vgname + sh.vgchange -:ae, vgname + sh.mkext4fs dest_parts[0] + sh.mkxfs vgpath+'root' + sh.mkxfs vgpath+'home' + + mount vgpath+'root', dest.root + addmp = {} + %i[home boot].each do |n| + d = addmp[n] = dest.root+n.to_s + d.mkdir + end + mount vgpath+'home', addmp[:home] + mount dest_parts[0], addmp[:boot] + + sh.rsync_all "#{base.root}/", dest.root + + (addmp[:boot]+'boot').make_symlink '.' # boot -> . + (addmp[:boot]+'boot.ini').open 'r+' do |f| + replace = { + rootdev: "setenv rootdev \"#{vgpath+'root'}\"", + rootfstype: "setenv rootfstype \"xfs\"", + } + lines = + f.each_line.map do |l| + l.chomp! + case l + when /^setenv rootdev / + replace.delete :rootdev + when /^setenv rootfstype / + replace.delete :rootfstype + else + l.gsub ' /boot/', ' /' + end + end + f.truncate 0 + f.pos = 0 + f.puts *lines + d "boot.ini had no entry for: #{replace.keys}", replace.empty? + end + + (dest.root+'etc'+'fstab').open 'r+' do |f| + replace = { + '/' => "#{vgpath+'root'} / xfs defaults,noatime 0 0", + '/boot' => "#{dest_parts[0]} /boot ext4 defaults,noatime 0 0", + '/home' => "#{vgpath+'home'} /home xfs defaults,noatime 0 0", + } + lines = + f.each_line.flat_map do |l| + mp = l.split( /\s+/)[1] + replace.delete( mp) || l + end + lines += replace.values + f.truncate 0 + f.pos = 0 + f.puts lines + end + + (dest.root+'etc'+'initramfs-tools'+'initramfs.conf').open 'r+' do |f| + lines = + f.each_line.flat_map do |l| + l.chomp! + case l + when /^COMPRESS=/ + 'COMPRESS=xz' + when /^# *COMPRESS=/ + [l, 'COMPRESS=xz'] + else + l + end + end + f.truncate 0 + f.pos = 0 + f.puts lines + end + + sh.tar -:C, dest.root, -:xf, 'files.tar' if File.exists? 'files.tar' + + rescue ProgrammError + err $! + raise + ensure + STDERR.puts "="*80 + umount_all ignore_exceptions: true + sh.vgchange -:an, vgname rescue Object + sh.sync rescue Object + departx dest.image rescue Object + departx base.image rescue Object + sh.sync rescue Object + STDERR.puts "="*80 + end +end + +Armbian2LVM_XFS.new(*ARGV).run diff --git a/lib/to_lvm_xfs.rb b/lib/to_lvm_xfs.rb new file mode 100644 index 0000000..d797602 --- /dev/null +++ b/lib/to_lvm_xfs.rb @@ -0,0 +1 @@ +require 'to_lvm_xfs/base' diff --git a/lib/to_lvm_xfs/base.rb b/lib/to_lvm_xfs/base.rb new file mode 100644 index 0000000..821f277 --- /dev/null +++ b/lib/to_lvm_xfs/base.rb @@ -0,0 +1,380 @@ +require 'pathname' +require 'shellwords' +require 'optparse' +require 'securerandom' +require 'to_lvm_xfs/exts' +require 'to_lvm_xfs/sh' +require 'to_lvm_xfs/structs' + +class ProgrammError < RuntimeError +end + +class Base + def self.run *args, &exe + new( *args).instance_eval &exe + end + + def d msg, eq + raise ProgrammError, msg, caller[1..-1] unless eq + end + + def err *args + STDERR.puts "\e[31;1m#{args.join ' '}\e[0m" + end + + def msg first, *args + STDERR.puts "\e[33m#{first}\e[35m #{args.join ' '}\e[0m" + end + + def kpartx image, read_only: nil + sh.kpartx "-sa#{read_only ? :r : ''}", image + lpartx image + end + + def lpartx image + mapper = Pathname.new '/dev/mapper' + lines = sh.kpartx( -:al, image, return: :lines)[0..-1] + if lines.grep( /^loop deleted/).empty? + lines.map do |line| + d "cannot extract partition «#{line}»", line =~ /^([^ :]+) : / + mapper + $1 + end + else + [] + end + end + + def lsblk path, *output + output = %w[NAME TYPE MOUNTPOINT] if output.empty? + output = output.map {|o| o.to_s } + lines = + sh.lsblk %w[--output], output.join(','), path + op = output.map {|o| o.downcase.to_sym } + lines.map {|l| Hash[*op.zip(l.split( /\s+/)).flatten]} + end + + def departx image + sh.kpartx -:sd, image + end + + def kpartxed? image + lines = sh.kpartx -:l, image, return: :lines + not lines.grep( /^loop deleted/).empty? + end + + + def get_vgname_of_pv *a, &exe + return to_enum( __method__, *a) unless block_given? + sh.pvs( --:options, :vg_name, *a, return: :lines).each do |l| + yield l.chomp.sub( /^\s*/, '') + end + end + + def remove_all_partitions_from image + sh[].parted( -:ms, image, :print).each do + sh.parted image, :rm, 1 + end + end + + def manipulate_file file, &exe + flags = 'r+' + msg :manipulate, file + File.open file.to_s, flags do |file| + lines = [] + exe.call file, &lines.method(:push) + file.truncate 0 + file.pos = 0 + lines.each &file.method(:puts) + end + end + + def capsulated_rescue + yield + rescue Object => e + STDERR.puts "\e[31;1m#{e} (#{e.class}):" + e.backtrace.each {|s| STDERR.puts " #{s}" } + STDERR.print "\e[0m" + end + + attr_reader :sh, :mounted, :looped, :base, :dest, :vgname + def initialize *args + OptionParser.new do |opts| + getopts opts + opts.parse! args + end + + if :ask == @password + v = b = nil + require 'io/console' + STDERR.print "Type Password. " + v = STDIN.getpass + STDERR.print "Type password again. " + b = STDIN.getpass + d "Password missmatch", v == b + d "Password is empty", !v.empty? + @password = v.crypt "$6$#{SecureRandom.urlsafe_base64 6}$" + end + + @sh = Sh.new immediately: true, expect_status: 0 + #x = sh.system :echo, :hallo, :welt, as_io: true do |io| + # STDERR.puts io.inspect + # STDERR.puts '---',io.readlines,'---' + #end + #STDERR.puts x.inspect + #exit 1 + @mounted, @activated_vgs = [], [] + @looped = Hash.new {|h,k| h[k] = [] } + @base = Image.new self, 'base', image: @baseimage + @dest = Image.new self, 'dest', image: @destination + + STDERR.print </, hostname } + end + msg :write, hostname, :to, 'etc/hostname' + dest.root.join( 'etc/hostname').open 'w' do |f| + f.puts hostname + end + end + + def install_packages_from_dir *paths + paths.each do |pkgs| + next unless pkgs.directory? + pkgs.each_child do |pn| + next if /\A\./ =~ pn.basename.to_s # no dot-files. + install_package pn + end + end + end + + def install_package path + case path.basename.to_s + when /\.deb\z/ + install_deb path + when /\.tar\z/, /\.t(?:ar\.|)(?:xz|bzip2|gz|lzma|Z)\z/ + sh.tar -:C, dest.root, -:xf, path + else + warn :ignore, path + end + end + + def install_deb path + #sh.dpkg '--unpack', '--force-architecture', pn, chroot: dest.root + adeb = Pathname.new( 'var/cache/apt/archives')+path.basename.to_s + dest.root.join( adeb.to_s).copy path + sh.dpkg -:i, adeb, chroot: dest.root + #msg :unpack, path + # sometimes, you have to use system to do something. but it is ok, if you know, how to do it. + # this command should be safe. + #d "unpacking failed: #{path}", + # system( "ar p #{path.to_s.shellescape} data.tar.xz | tar -JxC #{dest.root.to_s.shellescape}") + end +end diff --git a/lib/to_lvm_xfs/exts.rb b/lib/to_lvm_xfs/exts.rb new file mode 100644 index 0000000..6203800 --- /dev/null +++ b/lib/to_lvm_xfs/exts.rb @@ -0,0 +1,38 @@ +require 'pathname' + +class Pathname + alias +@ to_s + + def chdir &exe + Dir.chdir self.to_s, &exe + end + alias cd chdir + + # copies content of src in self as destination + def copy src, **opts + FileUtils.copy_file src.to_s, self.to_s, **opts + end + + def replace_i + open 'r+' do |f| + lines = yield f + f.truncate 0 + f.pos = 0 + f.puts lines + end + end +end + +class Symbol + alias +@ to_s + def -@ + :"-#{self}" + end +end + +class File + def reset + f.truncate 0 + f.pos = 0 + end +end diff --git a/lib/to_lvm_xfs/raspbian.rb b/lib/to_lvm_xfs/raspbian.rb new file mode 100644 index 0000000..e24d385 --- /dev/null +++ b/lib/to_lvm_xfs/raspbian.rb @@ -0,0 +1,160 @@ +require 'to_lvm_xfs' + +class Raspibian < Base + def initialize *args + @vgname = "raspi_#{SecureRandom.urlsafe_base64 5}" + super *args + end + + def build + ENV['LANG'] = 'C' + mkmppaths + + lsblk( dest.image).each do |l| + d "Device #{l[:name]} mounted at #{l[:mountpoint]}", ! l[:mountpoint] + end + + d "Base image does not exist", base.image.exist? + base_parts = kpartx base.image + d "two partitions in base expected, got: #{base_parts.inspect}", 2 == base_parts.length + mount base_parts[1], base.root, -:oro + mount base_parts[0], base.root+'boot', -:oro + + dest.image.open 'w' do |f| + f << 0.chr*4096 + f.pos = 4.8*1024*1024*1024-1 + f.putc 0.chr + end + sh.parted dest.image, *%w[-- + mklabel msdos + mkpart primary fat32 4MB 132MB + mkpart primary ext2 132MB -1s + set 2 LVM on + print] + + *dest_parts = + begin + lsblk( dest.image).select do |l| + STDERR.puts l + sh.dmsetup :remove, File.basename(l[:name]) if 'lvm' == l[:type] + l[:name].start_with?( dest.image.to_s) and 'part' == l[:type] + end.map {|l| Pathname.new l[:name] }.sort + rescue Sh::ProcessError + kpartx dest.image + end + d "two partitions in destination expected", 2 == dest_parts.length + dest_parts[0].open( 'w') {|f| f << 0.chr*4*1024*1024 } + dest_parts[1].open( 'w') {|f| f << 0.chr*4*1024*1024 } + sh.vgscan '--cache' + vgpath = Pathname.new( '/dev') + vgname + sh.pvcreate '-ff', dest_parts[1] + sh.vgcreate vgname, dest_parts[1] + sh.lvcreate -:nroot, '-L4.2G', vgname + sh.lvcreate -:nhome, '-L100M', vgname + sh.vgchange -:ae, vgname + sh.mkvfat -:nboot, dest_parts[0] + sh.mkxfs -:Lroot, vgpath+'root' + sh.mkxfs -:Lhome, vgpath+'home' + mount vgpath+'root', dest.root + addmp = {} + %i[home boot].each do |n| + d = addmp[n] = dest.root+n.to_s + d.mkdir + end + mount vgpath+'home', addmp[:home] + mount dest_parts[0], addmp[:boot] + + sh.rsync_all "#{base.root}/", dest.root + #sh.rsync *%w[kernel7.img initrd7.img], addmp[:boot] + + rename_user + + msg :patch, 'etc/fstab' + (dest.root+'etc'+'fstab').replace_i do |f| + replace = { + '/' => "UUID=#{sh.fs_uuid vgpath+'root'} / xfs defaults,noatime 0 0", + '/boot' => "UUID=#{sh.fs_uuid dest_parts[0]} /boot vfat defaults 1 1", + '/home' => "UUID=#{sh.fs_uuid vgpath+'home'} /home xfs defaults,noatime 1 1", + } + f.each_line.flat_map do |l| + mp = l.split( /\s+/)[1] + replace.delete( mp) || l + end + replace.values + end + + msg :patch, 'boot/config.txt' + (addmp[:boot]+'config.txt').replace_i do |f| + replace = { + initramfs: 'initramfs initrd7.img followkernel', + } + f.each_line.flat_map do |l| + l.chomp! + case l + when /^initramfs / + replace.delete :initramfs + else l + end + end + replace.values + end + + msg :touch, 'boot/ssh' + (addmp[:boot]+'ssh').open('w') {|f|} + + msg :patch, 'boot/cmdline.txt' + (addmp[:boot]+'cmdline.txt').replace_i do |f| + lines = f.readlines + d "Only one line in cmdline.txt expected", 1 == lines.length + opts = {} + lines[0].split( ' ').each do |line| + /^([^=]*)(?:=(.*))?/ =~ line + opts[$1.to_sym] = $2 + end + opts[:root] = vgpath+'root' + opts[:rootfstype] = :xfs + opts.delete :init + opts.map {|k,v| v ? "#{k}=#{v}" : "#{k}" }.join(' ') + end + + msg :patch, 'etc/initramfs-tools/initramfs.conf' + dest.root.join( 'etc/initramfs-tools/initramfs.conf').replace_i do |f| + f.each_line.flat_map do |l| + l.chomp! + case l + when /^COMPRESS=/ + 'COMPRESS=xz' + when /^# *COMPRESS=/ + [l, 'COMPRESS=xz'] + else + l + end + end + end + + set_hostname + + msg :unlinking, 'etc/rc*.d/*resize2fs_once' + (dest.root+'etc').chdir do + Pathname.glob( 'rc*.d/*resize2fs_once').each do |fn| + fn.unlink + end + end + + qemu_bin = dest.root.join 'usr/bin/qemu-arm-static' + msg :copy, "/usr/bin/qemu-arm-static" + qemu_bin.copy '/usr/bin/qemu-arm-static', preserve: true + + ish = sh.chroot dest.root + ish.apt :update + ish.apt :upgrade, -:y + ish.apt :update + ish.apt :install, -:y, :lvm2, :xfsprogs + + install_packages_from_dir( + Pathname.new($0).expand_path.dirname+'raspbian-files', + Pathname.new('files') + ) + + # generates implicite initramfs + ish.system *%w[dpkg-reconfigure raspberrypi-kernel] + end +end diff --git a/lib/to_lvm_xfs/sh.rb b/lib/to_lvm_xfs/sh.rb new file mode 100644 index 0000000..01f9ae0 --- /dev/null +++ b/lib/to_lvm_xfs/sh.rb @@ -0,0 +1,246 @@ +require 'forwardable' +require 'shellwords' +require 'socket' + +class Sh + class ForkError < RuntimeError + end + class ForkFailed < ForkError + end + class ProcessError < ForkError + end + + class Fork + extend Forwardable + def_delegators :@status, :exitstatus + attr_reader :pid, :status + + def initialize &exe + #@io, s = UNIXSocket.pair + @pid = + Process.fork do + Thread.list.each do |th| + th.kill unless Thread.current == th + end + #STDOUT.reopen s.dup + #STDIN.reopen s + #STDOUT.close_read + #STDIN.close_write + yield + end + raise ForkFailed unless @pid + #s.close + end + + def wait + _, @status = Process.waitpid2 @pid + @status + end + + def kill signal = nil + Process.kill signal||9, @pid + end + + def ok?() 0 == @status.exitstatus end + def failed?() 0 != @status.exitstatus end + end + + class IOFork < Fork + extend Forwardable + def_delegators :@io, :close, :closed?, :close_read, :closed_read?, :close_write, :closed_write? + def_delegators :@io, :getbyte, :getc, :getch, :getpass, :gets + def_delegators :@io, :read, :read_nonblock, :readbyte, :readchar, :readline, :readlines, :readpartial, :sysread + def_delegators :@io, :syswrite, :write, :write_nonblock + def_delegators :@io, :putc, :puts, :print, :printf + def_delegators :@io, :each_line + attr_reader :io + + def initialize &exe + @io, s = UNIXSocket.pair + r = nil + super do + @io.close + STDOUT.reopen s + STDIN.reopen s + #STDOUT.close_read + #STDIN.close_write + r = yield + end + s.close + r + end + end + + class Command + attr_reader :shell, :command, :args, :opts + + def initialize shell, command, *args, **opts + @shell, @command, @args, @opts = shell, command, args, opts + end + + def fork **opts + opts = @opts.merge opts + cl = + case opts[:mode] + when :io then IOFork + when Class then opts[:mode] + else Fork + end + cl.new do + Dir.chdir @shell.opts[:pwd] if @shell.opts[:pwd] + cmd = @command.to_s + $0 = cmd + #STDERR.printf "\n|\n| %p\n|\n", cmd: cmd, args: @args + if opts.has_key? :stdio + stdin, stdout, stderr = *opts[:stdio] + stdin ? STDIN.reopen( stdin) : STDIN.close + STDOUT.reopen stdout + STDERR.reopen stderr if stderr + end + if opts[:chroot] + STDERR.printf "\e[1;34mchroot(%s)\e[0m ", opts[:chroot].to_s + Dir.chroot opts[:chroot].to_s + end + #STDERR.puts "\e[0m#{opts[:return] ? '<=' : '#'} \e[33m#{cmd.shellescape} \e[35m#{usable_args.shelljoin}\e[0m" + STDERR.printf "\e[0m%s \e[33m%s \e[35m%s\e[0m\n", + opts[:return] ? '<=' : '#', + cmd.shellescape, + usable_args.shelljoin + exec cmd, *usable_args + raise ForkFailed, "Cannot execute" + end + rescue ForkFailed => e + raise ForkFailed, "<<#{self}>> cannot be forked" + end + + def usable_args + @uargs ||= @args.flatten.map &:to_s + end + + def to_s + "#{@command.to_s.shellescape}" + (usable_args.empty? ? '' : " #{usable_args.shelljoin}") + end + + def run **opts, &exe + opts = @opts.merge opts + case opts[:return] + when :lines + opts[:mode], exe = :io, lambda( &:readlines) + when :string + opts[:mode], exe = :io, lambda( &:read) + when :line + opts[:mode], exe = :io, lambda{|f|f.read.chomp} + end + r = f = fork **opts + begin + r = exe.call f if exe + rescue Object + f.kill + raise $! + ensure + f.close if f.respond_to? :close + f.wait + end + unless opts[:expect_status].nil? or opts[:expect_status] == f.exitstatus + raise ProcessError, "«#{self}» exited: #{f.status}" + end + r + end + + def run! **opts, &exe + run expect_status: 0, **opts, &exe + end + alias call run! + alias !@ run! + end + + attr_reader :opts + + def initialize dir = nil, **opts + opts = opts.merge dir: dir if dir + @opts = opts.dup + (@opts[:commands]||={}).each do |name, exe| + define_singleton_method name, &exe + end + @opts.freeze + end + + def chdir dir + Sh.new dir, **opts + end + alias cd chdir + def newopts **opts + Sh.new **@opts.merge( opts) + end + + def chroot dir + newopts chroot: dir + end + + def return_string + newopts mode: nil, return: :string + end + + alias _ return_string + def return_lines + newopts mode: nil, return: :lines + end + alias [] return_lines + + def with_io + newopts mode: :io + end + alias io with_io + + def system command, *args, immediately: nil, **opts, &exe + cmd = Command.new self, command, *args, **@opts.merge( opts) + if immediately or @opts[:immediately] or block_given? + cmd.run &exe + else + cmd + end + end + + def define_command name, &exe + @opts[:commands][name] = exe + define_singleton_method name, &exe + end + + def def_system_command name, cmd = nil + cmd ||= name + define_command name do |*args, **opts, &exe| + system cmd, *args, **opts, &exe + end + #e = <<-EOF + # def #{name}(*args, **opts, &exe) + # Command.new self, #{cmd.to_s.inspect}, *args, **opts + # end + #EOF + #eval e, nil, __FILE__, __LINE__ - 4 + end + + def def_system_commands *names + names.each {|cmd| def_system_command cmd } + end + + def alias_command name, cmd, *args, **opts, &exe + opts = @opts.merge opts + define_command name do |*as, **os, &e| + *as = exe.call( *as) if exe + system cmd, *args, *as, **opts, **os + end + end +end + +#f = Sh::IOFork.new do +# STDERR.puts f.inspect +# STDOUT.puts "hallo welt" +# sleep 5 +#end +# +#STDERR.puts "> #{f.inspect}" +#f.each_line do |l| +# STDERR.puts "> #{l}" +#end +#f.wait +#exit 1 diff --git a/lib/to_lvm_xfs/structs.rb b/lib/to_lvm_xfs/structs.rb new file mode 100644 index 0000000..a517e91 --- /dev/null +++ b/lib/to_lvm_xfs/structs.rb @@ -0,0 +1,49 @@ +require 'to_lvm_xfs/exts' + +class Image + attr_reader :base, :dir, :image, :root + def initialize base, dir, image: nil, root: nil + @base = base + @dir = + case dir + when Pathname then dir + when String then Pathname.new dir + else raise ArgumentError, "Pathname for dir expected" + end + @image = + case image + when Pathname then image + when String then Pathname.new image + when nil then @dir+'image' + else raise ArgumentError, "Pathname for image expected" + end + @image = @image.readlink.expand_path @image.expand_path.dirname while @image.symlink? + @root = + case root + when Pathname then root + when String then Pathname.new root + when nil then @dir+'root' + else raise ArgumentError, "Path for root must be Pathname, String or MP, if given" + end + end + def to_s() @dir.to_s end + + def kpartx() @base.kpartx @image end + def departx() @base.departx @image end + def lpartx() @base.lpartx @image end + def kpartxed?() @base.kpartxed? @image end +end + +class MP + attr_reader :base, :dev, :mp, :opts + def initialize base, dev, mp, *opts + @base, @dev, @mp, opts = base, dev, mp, opts + end + + def mount() @base.mount dev, mp, *opts end + def umount() @base.umount mp end + def uuid() @uuid ||= @base.sh.fs_uuid @dev end + def fstype() @fstype ||= @base.sh.fs_type @dev end + def clear_cache() @uuid = @fstype = nil end +end + diff --git a/raspbian.rb b/raspbian.rb new file mode 100755 index 0000000..4d9c5c9 --- /dev/null +++ b/raspbian.rb @@ -0,0 +1,7 @@ +#!/usr/bin/env ruby + +require 'pathname' +$: << Pathname.new($0).expand_path.dirname+'lib' +require 'to_lvm_xfs/raspbian' + +Raspian.new(*ARGV).run