to lvm, xfs project. raspbian ready

master
Denis Knauf 2018-04-25 16:49:07 +02:00
commit 3eb91f4708
8 changed files with 1036 additions and 0 deletions

155
armbian2lvm_xfs.rb Executable file
View File

@ -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

1
lib/to_lvm_xfs.rb Normal file
View File

@ -0,0 +1 @@
require 'to_lvm_xfs/base'

380
lib/to_lvm_xfs/base.rb Normal file
View File

@ -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 <<EOF
Settings:
username: #{@username || '(default)'}
password: #{@password ? '*********' : '(default)'}
baseimage: #{@base.image}
destination: #{@dest.image}
vgname: #{@vgname}
hostname: #{@hostname || '(default)'}
EOF
sh.def_system_commands *%i[echo sed mount umount kpartx sync rsync xz gzip bzip2 zip tar bash dpkg apt]
sh.def_system_commands *%i[dmsetup lvcreate vgcreate pvcreate vgchange mkswap vgscan]
sh.alias_command :pvs, *%w[pvs --noheadings]
sh.alias_command :blkid, 'blkid', return: :line
sh.alias_command :lsblk, *%w[lsblk --noheadings --paths --list], return: :lines
sh.alias_command :parted, *%w[parted -ms]
sh.alias_command :mkxfs, *%w[mkfs.xfs -f]
sh.alias_command :mkext2fs, *%w[mkfs.ext2]
sh.alias_command :mkext4fs, *%w[mkfs.ext4]
sh.alias_command :mkvfat, *%w[mkfs.vfat]
sh.alias_command :rsync_all, *%w[rsync -aHAX]
sh.alias_command :mount_ro, *%w[mount -oro]
sh.alias_command :fs_uuid, *%w[blkid -o value -s UUID], return: :line
sh.alias_command :fs_type, *%w[blkid -o value -s TYPE], return: :line
end
def getopts opts
opts.on '-h', '--help' do
STDERR.puts opts
exit 1
end
opts.on '-bIMAGE', '--baseimage=IMAGE', 'Write image to IMAGE. Device or file' do |v|
@baseimage = v
end
opts.on '-dIMAGE', '--destination=IMAGE', 'Write image to IMAGE. Device or file' do |v|
@destination = v
end
opts.on '-uUSER', '--user=USER', 'Change username of user pi to this USERname' do |v|
@username = v
end
opts.on '-W', '--ask-pass', 'Ask for password for user via CLI.' do
@password = :ask
end
opts.on '-wPASSWORD', '--password=PASSWORD', 'Set password for user' do |v|
@password = v.crypt "$6$#{SecureRandom.urlsafe_base64 6}$"
end
opts.on '-nVGNAME', '--vgname=VGNAME', 'Name for volume group' do |v|
@vgname = v
end
opts.on '-HNAME', '--hostname=NAME', 'Set hostname to NAME' do |v|
@hostname = v
end
opts.on '-aAUTHKEYS', '--auth-keys=AUTHKEYS', 'SSH-Keys for user' do |f|
f = Pathname.new f
d "#{f} not found.", f.exist?
@authorized_keys = f
end
opts.on '-AAUTHKEYS', '--root-auth-keys=AUTHKEYS', 'SSH-Keys for root' do |f|
f = Pathname.new f
d "#{f} not found.", f.exist?
@root_authorized_keys = f
end
end
def run
build
qemu_bin = dest.root + "usr/bin/qemu-arm-static"
if qemu_bin.exist?
msg :remove, "/usr/bin/qemu-arm-static"
qemu_bin.unlink
end
rescue ProgrammError
err $!
raise
ensure
STDERR.puts "="*80
umount_all ignore_exceptions: true
umount dest.root, -:R rescue Object
umount base.root, -:R rescue Object
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
def activate_vg vgname
return nil if @activated_vgs.include? vgname
@activated_vgs.unshift vgname
sh.vgchange -:ae, vgname
end
def deactivate_vg vgname
return nil unless @activated_vgs.include? vgname
sh.vgchange -:an, vgname
@activated_vgs.delete vgname
end
def deactivate_all_vgs ignore_exceptions: nil
until @activated_vgs.empty?
if ignore_exceptions
capsulated_rescue { sh.vgchange -:an, @activated_vgs.pop }
else
sh.umount @activated_vgs.pop
end
end
end
def mount from, to, *opts, &exe
@mounted.push to
sh.mount *opts, from, to
if block_given?
begin
yield
ensure
umount to
end
end
end
def umount mp, *opts
r = sh.umount *opts, mp
@mounted.delete to
r
end
def umount_all ignore_exceptions: nil
until @mounted.empty?
if ignore_exceptions
capsulated_rescue { sh.umount @mounted.pop }
else
sh.umount @mounted.pop
end
end
end
def mkmppaths
[base, dest].each do |mp|
mp.dir.mkpath
mp.root.mkpath
end
end
def ssh_copy_id_local src, home, uid, gid = nil
akf = home+'.ssh/authorized_keys'
akd = akf.dirname
akd.mkdir
akd.chown uid, gid
akd.chmod 0700
akf.copy src
akf.chown uid, gid
akf.chmod 0600
end
def rename_user
ssh_copy_id_local @authorized_keys, dest.root+'home/pi', 1000, 1000 if @authorized_keys
ssh_copy_id_local @root_authorized_keys, dest.root+'root', 0, 0 if @root_authorized_keys
if @username
dest.root.join( 'etc/passwd').replace_i do |f|
f.each_line.flat_map do |l|
user, pwd, uid, gid, name, home, shell = l.split( ':')
user, home = @username, "/home/#{@username}" if 'pi' == user
[user, pwd, uid, gid, name, home, shell].join ':'
end
end
dest.root.join( 'etc/group').replace_i do |f|
f.each_line.flat_map do |l|
group, pwd, gid, users = l.split( ':')
group = @username if 'pi' == group
users = users.split( ',').map {|user| 'pi' == user ? @username : user }
[group, pwd, gid, users.join( ',')].join ':'
end
end
dest.root.join( 'home/pi').rename dest.root.join( 'home', @username)
end
if @password or @username
dest.root.join( 'etc/shadow').replace_i do |f|
f.each_line.flat_map do |l|
user, crypt, lastchange, minage, maxage, warn, inact, expiry, reserved = l.split( ':')
if 'pi' == user
crypt = @password if @password
user = @username if @username
end
[user, crypt, lastchange, minage, maxage, warn, inact, expiry, reserved].join ':'
end
end
end
end
def set_hostname hostname = nil
hostname = @hostname
return if hostname.nil? or hostname.empty?
msg :patch, 'etc/hosts', 'set hostname to', hostname
dest.root.join( 'etc/hosts').replace_i do |f|
f.each_line.map {|l| l.gsub /\<raspberrypi\>/, 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

38
lib/to_lvm_xfs/exts.rb Normal file
View File

@ -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

160
lib/to_lvm_xfs/raspbian.rb Normal file
View File

@ -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

246
lib/to_lvm_xfs/sh.rb Normal file
View File

@ -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

49
lib/to_lvm_xfs/structs.rb Normal file
View File

@ -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

7
raspbian.rb Executable file
View File

@ -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