class SyncDirDef
DEFAULT_EXCLUDE = %w[/proc/* /sys/* /dev/mqueue /dev/hugepages /run/* /var/lib/os-prober/mount /swap /dev/shm/* /var/lib/lxcfs/*]
- attr_accessor :path, :size, :exclude, :size, :srcpath, :fs_features
+ attr_accessor :path, :size, :exclude, :size, :srcpath, :fs_features, :device, :fs_uuid
def initialize(path: '/', size: 8, exclude: DEFAULT_EXCLUDE, srcpath: nil, fs_features: nil)
@path = path
@size = size.to_f
@exclude = (DEFAULT_EXCLUDE + [*exclude]).uniq
@srcpath = srcpath || path
@fs_features = fs_features
+ @device = nil
+ @fs_uuid = nil
end
end
-
class ImageCreator
- attr_accessor :name, :dirs, :src_host
+ attr_accessor :name, :dirs, :src_host, :img_path_base
+ MiB = 1024 ** 2
+ GiB = 1024 ** 3
def initialize(name, dirs, src_host: nil)
@name = name
@dirs = dirs
@src_host = src_host || name
+ @img_path_base = "#{name}_#{Time.now.strftime '%FT%T%z'}"
end
- def size
- @size and return @size
- @size = 3 * (1024**2) # alignment + BIOS boot partition + gap
- dirs.each do |di|
- @size += di.size * (1024**3)
- end
- @size
+ def imgpath(idx)
+ "#{img_path_base}_#{idx}.img"
end
- def imgpath
- @imgpath ||= "#{name}_#{Time.now.strftime '%FT%T%z'}.img"
+ def create_disk
+ dirs.each_with_index do |di, idx|
+ _create_disk imgpath(idx), di, idx != 0
+ end
end
- def create_disk
- raise "Disk image #{imgpath} is already exists!" if File.exists? imgpath
- puts "Creating disk image #{imgpath} (#{'%5.2f' % (size.to_f/(1024**3))} GiB)..."
- last_mb = 1
- File.open(imgpath, "w") do |f|
- f.truncate size
+ def _create_disk path, di, use_gpt = false
+ size_gb = di.size
+ raise "Disk image #{path} is already exists!" if File.exists? path
+ puts "Creating disk image #{path} (#{'%5.2f' % size_gb.to_f} GiB)..."
+ File.open(path, "w") do |f|
+ f.truncate(size_gb * GiB)
end
- system("parted", "-s", imgpath, "mklabel", "gpt") or raise "Failed to create partition label"
- system("parted", "-s", imgpath, "mkpart", "primary", "#{last_mb}MiB", "#{last_mb+1}MiB") or raise "Failed to create boot partition"
- system("parted", "-s", imgpath, "set", "1", "bios_grub", "on") or raise "Failed to set bios boot partition"
- last_mb += 1
- dirs.each do |di|
- system("parted", "-s", imgpath, "mkpart", "primary", "#{last_mb}MiB", "#{di.size * 1024 + last_mb}MiB") or raise "Failed to create partition: #{l}"
- last_mb += di.size * 1024
+ system("parted", "-s", path, "mklabel", use_gpt ? 'gpt' : 'msdos') or raise "Failed to create partition label"
+ system("parted", "-s", path, "mkpart", "primary", "1MiB", "#{size_gb * 1024 - 1}MiB") or raise "Failed to create partition"
+ if !use_gpt
+ system("parted", "-s", path, "set", "1", "boot", "on") or raise "Failed to set bios boot partition"
end
- puts "Image partition created."
- #system "sfdisk", "-l", imgpath
+ puts "Image partition has been created."
end
def with_loopdev &block
begin
- system("kpartx", "-as", imgpath) or raise "Failed to map loop device"
- maplines = `kpartx -l #{Shellwords.escape imgpath}`.split("\n")
devices = []
- maplines.each do |map|
- devices << "/dev/mapper/#{map[/loop\d+p\d+/]}"
+ dirs.each_with_index do |di, idx|
+ system("kpartx", "-as", imgpath(idx)) or raise "Failed to map loop device"
+ di.device = "/dev/mapper/" + `kpartx -l #{Shellwords.escape imgpath(idx)}`.split("\n").first[/loop\d+p\d+/]
+ devices << di.device
end
- yield devices, maplines.first[%r{/dev/loop\d+}]
+ yield devices
ensure
- system "kpartx", "-d", imgpath, err: "/dev/null"
+ dirs.each_with_index do |di, idx|
+ system "kpartx", "-d", imgpath(idx), err: "/dev/null"
+ di.device = nil
+ end
end
end
def create_fs
- with_loopdev do |devices, root|
- devices[1..-1].each_with_index do |(dev, _), index|
+ with_loopdev do |devices|
+ dirs.each_with_index do |di, index|
+ dev = di.device
puts "Creating filesystem on #{dev}..."
cmd = %w(mkfs.ext4 -q)
di = dirs[index]
end
cmd << dev
system(*cmd) or raise "Failed to create file system on #{dev}"
+ system "e2label", dev, di.path == '/' ? 'ROOT' : di.path[1..-1].tr('/', '-')
+ di.fs_uuid = `blkid -o value -s UUID #{di.device}`.chomp("\n")
end
end
end
def sync_dirs
- with_loopdev do |devices, root|
- devices[1..-1].each_with_index do |dev, idx|
+ with_loopdev do |devices|
+ devices.each_with_index do |dev, idx|
di = dirs[idx]
mount_point = "/mnt/ci-#{$$}-#{name}-#{idx}"
system("mkdir", "-p", mount_point)
def fix_boot
puts "Fixing boot environments..."
Dir.mktmpdir("ci-#{$$}-#{name}") do |dir|
- system("cp", "-a", "/usr/lib/grub/i386-pc/boot.img", dir) or raise "Failed to copy boot.img"
- coreimg = "#{dir}/core.img"
- system("grub-mkimage", "-o", coreimg, "-O", "i386-pc", "-p", "(hd0,gpt2)/boot/grub", "biosdisk", "part_gpt", "ext2", "gzio", "xzio", "lzopio") or raise "Failed to create grub core image."
- with_loopdev do |devices, root|
+ with_loopdev do |devices|
puts "Override grub with host version..."
- system("grub-bios-setup", "-d", dir, root) or raise "Failed to run grub-bios-setup"
- rootfs_uuid=`blkid -o value -s UUID #{devices[1]}`.chomp("\n")
+ root_dev = "/dev/#{devices.first[/loop\d+/]}"
+ rootfs_uuid = dirs.find { |d| d.path == '/'}.fs_uuid
puts "New rootfs UUID=#{rootfs_uuid}"
begin
- system("mount", devices[1], dir)
+ system("mount", devices.first, dir) or raise "Failed to mount #{devices.first} to #{dir}"
+ system("mount", "--bind", "/dev", "#{dir}/dev") or raise "Failed to mount /dev to #{dir}/dev"
+ system("mount", "--bind", "/proc", "#{dir}/proc") or raise "Failed to mount /proc to #{dir}/proc"
+
+ dirs[1..-1].each_with_index do |di, idx|
+ system "mkdir", "-p", "#{dir}#{di.path}"
+ system("mount", di.device, "#{dir}#{di.path}") or raise "Failed to mount #{di.device} to #{dir}#{path}"
+ end
+
+ system "rm", "-f", "#{dir}/etc/systemd/system/udev.service", "#{dir}/etc/systemd/system/systemd-udevd.service"
puts "Rewrite fstab..."
- fstab = File.read "#{dir}/etc/fstab"
- fstab.gsub!(%r{^(UUID=|/)\S+(\s+/\s+)}, "UUID=#{rootfs_uuid}\\2")
- fstab.gsub!(%r{^(\S+\s+\S+\s+\S+\s+sw(?=\b))}, '#\1')
- File.write "#{dir}/etc/fstab", fstab
+ File.open "#{dir}/etc/fstab", "w" do |f|
+ dirs.each_with_index do |di, idx|
+ f << %W(UUID=#{di.fs_uuid} #{di.path} ext4 defaults,noatime 0 #{di.path == '/' ? 1 : 2}).join("\t")
+ f << "\n"
+ end
+ end
unless File.exists? "#{dir}/vmlinuz"
system("chroot", dir, "apt-get", "-qy", "update")
system("chroot", dir, "apt-get", "-y", "install", "linux-image-amd64")
end
- puts "Update grub.cfg..."
- system("mkdir", "-p", "#{dir}/boot/grub/i386-pc") or raise "Failed to create grub dir"
- system("cp -a /usr/lib/grub/i386-pc/*.mod #{dir}/boot/grub/i386-pc/") or raise "Failed to copy grub modules"
- if File.exists? "#{dir}/boot/grub/grub.cfg"
- grubconf = File.read "#{dir}/boot/grub/grub.cfg"
- if old_uuid = grubconf[/root=UUID=(\S+)/, 1]
- File.write "#{dir}/boot/grub/grub.cfg", grubconf.gsub(/#{old_uuid}/, rootfs_uuid)
- end
- else
- File.write "#{dir}/boot/grub/grub.cfg", <<-EOC
- set timeout=5
- insmod part_gpt
- insmod ext2
- insmod linux
- search --no-floppy --fs-uuid --set=root #{rootfs_uuid}
- menuentry 'Linux' {
- linux /vmlinuz root=UUID=#{rootfs_uuid} ro
- initrd /initrd.img
- }
- EOC
+ puts "Update grub..."
+ unless File.exists? "#{dir}/boot/grub/grub.cfg"
+ system("chroot", dir, "apt-get", "-qy", "update") or raise "Failed to install grub-pc"
+ system({'DEBIAN_FRONTEND' => 'noninteractive'}, "chroot", dir, "apt-get", "-y", "install", "grub-pc")
+ end
+ File.open "#{dir}/boot/grub/device.map", "w" do |f|
+ f.puts "(hd0)\t#{root_dev}"
end
+ system("chroot", dir, "grub-mkconfig", "-o", "/boot/grub/grub.cfg") or raise "grub-mkconfig fails."
+ system(*%W(grub-install --no-floppy --grub-mkdevicemap=#{dir}/boot/grub/device.map --root-directory=#{dir} #{root_dev})) or raise "grub-install failed."
ensure
+ system("umount", "#{dir}/dev")
+ system("umount", "#{dir}/proc")
+ dirs.reverse[0..-2].each do |di, idx|
+ system("umount", "#{dir}#{di.path}")
+ end
system("umount", dir)
end
end