to lvm, xfs project. raspbian ready
This commit is contained in:
commit
3eb91f4708
155
armbian2lvm_xfs.rb
Executable file
155
armbian2lvm_xfs.rb
Executable 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
1
lib/to_lvm_xfs.rb
Normal file
|
@ -0,0 +1 @@
|
||||||
|
require 'to_lvm_xfs/base'
|
380
lib/to_lvm_xfs/base.rb
Normal file
380
lib/to_lvm_xfs/base.rb
Normal 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
38
lib/to_lvm_xfs/exts.rb
Normal 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
160
lib/to_lvm_xfs/raspbian.rb
Normal 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
246
lib/to_lvm_xfs/sh.rb
Normal 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
49
lib/to_lvm_xfs/structs.rb
Normal 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
7
raspbian.rb
Executable 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
|
Loading…
Reference in a new issue