require 'to_lvm_xfs' class Raspbian < Base def initialize *args @vgname = "raspi_#{SecureRandom.urlsafe_base64 5}" super *args 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| d "File #{dest.image} used as loop-device back-file", +dest.image != +XPathname.new(lo['back-file']) end end when dest.image.blockdev?, dest.image.chardev? lsblk( dest.image).each do |l| d "Device #{l[:name]} mounted at #{l[:mountpoint]}", ! l[:mountpoint] end end sleep 5 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| XPathname.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 = XPathname.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 = {run_udev: dest.root+'run/udev'} %i[home boot dev proc sys].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] mount '/dev', addmp[:dev], --:bind mount 'proc', addmp[:proc], -:tproc mount 'sysfs', addmp[:sys], -:tsysfs sh.rsync_all "#{base.root}/", dest.root install_authorized_keys rename_user (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 (addmp[:boot]+'config.txt').replace_i do |f| replace = { 'pi4' => { initramfs: 'initramfs initrd7l.img followkernel', }, 'pi3' => { initramfs: 'initramfs initrd7.img followkernel', }, '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]+'ssh').write '' (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 (dest.root+'etc').chdir do XPathname.glob( 'rc*.d/*resize2fs_once').each do |fn| fn.unlink end end qemu_bin = dest.root.join 'usr/bin/qemu-arm-static' qemu_bin.copy '/usr/bin/qemu-arm-static', preserve: true preload, preload_x = dest.root+'etc/ld.so.preload', dest.root+'etc/ld.so.preload.tp' preload.rename preload_x ish = sh.chroot( dest.root).chdir( '/') ish.apt :update ish.apt :upgrade, -:y ish.apt :update # 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 :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 :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 set_hostname install_packages_from_dir( XPathname.new( $0).expand_path.dirname + 'raspbian-files', XPathname.new( 'files') ) # generates implicite initramfs ish.system *%w[dpkg-reconfigure raspberrypi-kernel] preload_x.rename preload qemu_bin.unlink end end