tolvmxfs/lib/to_lvm_xfs/raspbian.rb

270 lines
8.0 KiB
Ruby

require 'to_lvm_xfs'
class Raspbian < Base
Sizes = {
'' => 1,
'b' => 1,
's' => 512,
'k' => 1024,
'm' => 1024*1024,
'g' => 1024*1024*1024,
't' => 1024*1024*1024*1024,
'p' => 1024*1024*1024*1024*1024,
'e' => 1024*1024*1024*1024*1024*1024,
}
Volume = Struct.new :sh, :name, :device, :mountpoint, :builder_mp, :size do
def uuid() sh.fs_uuid device end
def type() sh.fs_type device end
def to_fstab_entry
FSTabMountEntry.new "UUID=#{uuid}", mountpoint, type, 'defaults,noatime', nil, nil
end
end
def initialize *args
@vgname = "raspi_#{SecureRandom.urlsafe_base64 5}"
@volumes = { root: ['/', '4.2G'], home: ['/home', '100M'] }
super *args
@vgpath = XPathname.new( '/dev') + @vgname
STDERR.printf "Volumes:\n"
vols = {}
@volumes.each do |name, (mp, size)|
mp = XPathname.new( mp).cleanpath
vols[mp.to_s] = Volume.new sh, name.to_s, @vgpath+name.to_s, mp, dest.root + mp.to_s[1..-1], size
STDERR.printf "%13s: %s (%s)\n", name, mp, size
end
@volumes = vols
end
def getopts opts
super opts
opts.on '-vNAME', '--volume=NAME', 'Creates additional volume name:mountpoint=size or overwrites defaults.' do |v|
m = %r<\A([0-9a-z_-]+):(/[0-9a-z/_-]*)=([0-9.]+[%a-z]?)\z>i.match v
fail "Volume expected in format: \"name:mountpoint=size\"" unless m
@volumes[m[1].to_sym] = [XPathname.new( m[2]).cleanpath, m[3]]
end
end
def build
ENV['LANG'] = 'C'
mkmppaths
case
when !dest.image.exist?
when dest.image.file?
r = sh.losetup_list
unless r.empty? or r['loopdevices']
r['loopdevices'].each do |lo|
fail "File #{dest.image} used as loop-device back-file" if +dest.image == +XPathname.new( lo['back-file'])
end
end
when dest.image.blockdev?, dest.image.chardev?
lsblk( dest.image).each do |l|
fail "Device #{l[:name]} mounted at #{l[:mountpoint]}" if l[:mountpoint]
end
end
task "Mount base image #{base.image}" do
sh.partx -:u, base.image if base.image.blockdev?
fail "Base image does not exist" unless base.image.exist?
base_parts = kpartx base.image
fail "two partitions in base expected, got: #{base_parts.inspect}" unless 2 == base_parts.length
mount base_parts[1], base.root, -:oro
mount base_parts[0], base.root+'boot', -:oro
end
dest.image.open 'w' do |f|
size =
@volumes.inject 136*1024*1024 do |s, (_n, vol)|
m = /\A([0-9.]+)([bBsSkKmMgGtTpPeE]?)\z/i.match vol.size
fail "invalid size: #{vol.size}" unless m
s + m[1].to_f * Sizes[m[2].downcase]
end
f << 0.chr*4096
f.pos = size - 1
f.putc 0.chr
end
task "Partitioning destination image #{dest.image}" do
sh.parted dest.image, *%w[--
mklabel msdos
mkpart primary fat32 4MB 132MB
mkpart primary ext2 132MB -1s
set 2 LVM on
print]
sh.partx -:u, dest.image if dest.image.blockdev?
end
*dest_parts =
begin
lsblk( dest.image).select do |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| XPathname.new l[:name] }.sort
rescue Sh::ProcessError
kpartx dest.image
end
fail "two partitions in destination expected" unless 2 == dest_parts.length
task "Prepare boot-partition #{dest_parts[0]} and lvm #{dest_parts[1]}" do
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'
sh.pvcreate -:ff, dest_parts[1]
sh.vgcreate vgname, dest_parts[1]
@volumes.each {|_name, vol| sh.lvcreate "-n#{vol.name}", "-L#{vol.size}", vgname }
sh.vgchange -:ae, vgname
sh.mkvfat -:nboot, dest_parts[0]
@volumes.each {|_name, vol| sh.mkxfs "-mreflink=1", "-L#{vol.name}", vol.device }
end
addmp = {run_udev: dest.root+'run/udev'}
task "Mount all filesystems" do
mount @vgpath+'root', dest.root
(%i[boot]).each do |n|
d = addmp[n] = dest.root+n.to_s
d.mkdir unless d.exist?
end
mount dest_parts[0], addmp[:boot]
@volumes.each do |_name, vol|
path = vol.builder_mp
next if dest.root == path || addmp.values.include?( path)
path.mkdir unless path.exist?
mount @vgpath+vol.name, path
end
end
task "Copy raspbian from base to dest" do
sh.rsync_all "#{base.root}/", dest.root
end
task "Mount all special filesystems" do
(%i[dev proc sys]).each do |n|
d = addmp[n] = dest.root+n.to_s
d.mkdir unless d.exist?
end
mount '/dev', addmp[:dev], --:bind
mount 'proc', addmp[:proc], -:tproc
mount 'sysfs', addmp[:sys], -:tsysfs
end
task "Prepare users" do
install_authorized_keys
rename_user
end
task "Prepare /etc and /boot" do
update_fstab @volumes.map { |_name, vol| vol.to_fstab_entry } +
[ FSTabMountEntry.new( "UUID=#{sh.fs_uuid( dest_parts[0])}", '/boot', 'vfat', 'defaults', 1, 1)]
addmp[:boot].join( 'config.txt').replace_i do |f|
replace = {
'pi4' => { initramfs: 'initramfs initrd8.img followkernel', arm_64bit: 'arm_64bit=1' },
'pi3' => { initramfs: 'initramfs initrd8.img followkernel', arm_64bit: 'arm_64bit=1' },
'pi2' => { initramfs: 'initramfs initrd7.img followkernel', },
'pi1' => { initramfs: 'initramfs initrd.img followkernel', },
'pi0' => { initramfs: 'initramfs initrd.img followkernel', },
}
blocks = [nil]
content = Hash.new {|h,block| h[block] = [] }
block = nil
f.each_line do |l|
l.chomp!
case l
when /\A\[([^\]]*)\]\z/
block = $1
blocks.push block
when /\Ainitramfs /
l = replace[block].delete :initramfs
end
content[block].push l
end
replace.each {|block, rpl| content[block] += rpl.values + [''] unless rpl.empty? }
blocks.flat_map {|block| content[block] }
end
addmp[:boot].join( 'ssh').write ''
addmp[:boot].join( 'cmdline.txt').replace_i do |f|
lines = f.readlines
fail "Only one line in cmdline.txt expected" unless 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
dest.root.join( 'etc').chdir do
XPathname.glob( 'rc*.d/*resize2fs_once').each do |fn|
fn.unlink
end
end
set_hostname
end
preload, preload_x = dest.root+'etc/ld.so.preload', dest.root+'etc/ld.so.preload.tp'
task "Prepare to chroot to raspbian" do
@qemu_bin.copy @qemu_bin_src, preserve: true
after { @qemu_bin.unlink }
if preload.exist?
preload.rename preload_x
after { preload_x.rename preload }
end
end
task "update, upgrade and install" do
ish = sh.chroot( dest.root).chdir( '/')
ish.apt :update
ish.apt :upgrade, -:y
ish.apt :update
install_packages_from_dir(
XPathname.new( $0).expand_path.dirname + 'raspbian-files',
XPathname.new( 'files')
)
# We mount /run/udev for lvm-scanning - vgs / vgcfgbackup need it to connect to udev.
addmp[:run_udev].mkdir
mount '/run/udev', addmp[:run_udev], --:bind
# prevent installing exim by installing nullmailer
#ish.apt *%w[install -y lvm2 xfsprogs nullmailer dracut]
#dest.root.join( 'etc/dracut.conf.d/10-denkn.conf').open 'w' do |f|
# f.puts 'add_modules+="lvm"'
# f.puts 'add_drivers+="dm-mod xfs"'
# f.puts 'compress="xz"'
#end
ish.apt *%w[install -y lvm2 xfsprogs initramfs-tools]
dest.root.join( 'etc/initramfs-tools/initramfs.conf').replace_i do |f|
replace = { compress: 'COMPRESS=xz', }
f.each_line.flat_map do |l|
case l.chomp!
when /^COMPRESS=/ then replace.delete :compress
when /^# *COMPRESS=/ then [l, replace.delete( :compress)]
else l
end
end + replace.values
end
# strange error while generating initramfs;
# it want to delete this directory, but it does not exist.
#overlays = dest.root + 'usr/share/rpikernelhack/overlays'
#overlays.mkpath
#overlays.join( '.keep').write ''
# generates implicite initramfs
ish.system *%w[dpkg-reconfigure raspberrypi-kernel]
end
end
end