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