X-Git-Url: http://git.osdn.net/view?a=blobdiff_plain;f=create-image;h=cc76c0fa30b91f009bddb4649d108d9a976634f2;hb=HEAD;hp=04fe0ee74a4c3d62ecfacc533e16b4e783f95149;hpb=fbea085325051b3b6790b723e6496303f859e5c6;p=osdn-codes%2Fimage-creator.git diff --git a/create-image b/create-image index 04fe0ee..cc76c0f 100755 --- a/create-image +++ b/create-image @@ -1,4 +1,10 @@ #!/usr/bin/ruby +# +# Author: Tatsuki Sugiura +# Copyright: Apprits, Inc. +# Lisence: MIT +# +# require 'pp' require 'shellwords' require 'tmpdir' @@ -20,14 +26,15 @@ class SyncDirDef end class ImageCreator - attr_accessor :name, :dirs, :src_host, :img_path_base + attr_accessor :name, :dirs, :src_host, :img_path_base, :run_cmds MiB = 1024 ** 2 GiB = 1024 ** 3 - def initialize(name, dirs, src_host: nil) + def initialize(name, dirs, src_host: nil, run_cmds: nil) @name = name @dirs = dirs @src_host = src_host || name + @run_cmds = run_cmds @img_path_base = "#{name}_#{Time.now.strftime '%FT%T%z'}" end @@ -100,7 +107,9 @@ class ImageCreator begin system("mount", dev, mount_point) or raise "Failed to mount file system #{dev} on #{mount_point}" puts "Copying #{src_host}:#{di.srcpath} to #{dev}..." - system("rsync", "-azHSAX", "--numeric-ids", "--info=progress2", "#{src_host}:#{di.srcpath}/", "#{mount_point}/", *((["--exclude"] * di.exclude.size).zip(di.exclude).flatten)) or raise "rsync fails" + unless system("rsync", "-azHSAX", "--numeric-ids", "--info=progress2", "#{src_host}:#{di.srcpath}/", "#{mount_point}/", *((["--exclude"] * di.exclude.size).zip(di.exclude).flatten)) + warn "rsync exit with error, file transfer may not be completed." + end ensure system("umount", mount_point) File.directory?(mount_point) and @@ -110,14 +119,110 @@ class ImageCreator end end + def prepare_awsenv + puts "Prepareing AWS cloud boot environment..." + with_rootfs do |dir, devices| + system({'DEBIAN_FRONTEND' => 'noninteractive'}, "chroot", dir, *%w(sudo dpkg --remove td-agent smartmontools snmpd lm-sensors arrayprobe megacli megacli-check-change megaclisas megaclisas-status fancontrol)) + + pkgs = %w(apt-transport-https locales-all ntp ntpdate rsync python-apt git subversion less lv cloud-init tmux screen irqbalance) + if File.read("#{dir}/etc/debian_version").to_f >= 9.0 + pkgs << 'cloud-guest-utils' + end + + system("chroot", dir, "apt-get", "-qy", "update") + system({'DEBIAN_FRONTEND' => 'noninteractive'}, "chroot", dir, "apt-get", "-y", "--allow-unauthenticated", "install", *pkgs) + + if File.exists? "#{dir}//var/spool/nullmailer/queue" + puts "Remove nullmailer queue" + system("chroot", dir, "find", "/var/spool/nullmailer/queue", "-type", "f", "-name", '[0-9]*.[0-9]*', "-delete") + end + + unless system("chroot", dir, *%w(grep -rq cdn-aws.deb.debian.org /etc/apt/sources.list /etc/apt/sources.list.d)) + puts "Rewrite apt sources list for AWS CDN" + rel = `chroot #{dir} lsb_release -c -s`.chomp + s = [ + "deb http://cdn-aws.deb.debian.org/debian #{rel} main main contrib non-free", + "deb http://cdn-aws.deb.debian.org/debian #{rel}-updates main main contrib non-free", + ] + if File.exists? "#{dir}/etc/apt/sources.list" + s << File.read("#{dir}/etc/apt/sources.list") + end + File.write "#{dir}/etc/apt/sources.list", s.join("\n") + end + + unless File.read("#{dir}/etc/ntp.conf").include? "169.254.169.123" + puts "Rewrite ntp.conf" + c = File.read("#{dir}/etc/ntp.conf") + c.gsub!(/^(pool|server) /) { "##{$1}" } + c << "\nserver 169.254.169.123 prefer iburst\n" + File.write "#{dir}/etc/ntp.conf", c + end + + File.write "#{dir}/etc/cloud/cloud.cfg.d/50_keep_apt_source.cfg", "apt_preserve_sources_list: true\n" + File.write "#{dir}/etc/cloud/cloud.cfg.d/50_keep_ssh_key.cfg", "ssh_deletekeys: false\n" + end + end + def fix_boot puts "Fixing boot environments..." + with_rootfs do |dir, devices| + puts "Override grub with host version..." + root_dev = "/dev/#{devices.first[/loop\d+/]}" + rootfs_uuid = dirs.find { |d| d.path == '/'}.fs_uuid + puts "New rootfs UUID=#{rootfs_uuid}" + + system "rm", "-f", "#{dir}/etc/systemd/system/udev.service", "#{dir}/etc/systemd/system/systemd-udevd.service", "#{dir}/etc/udev/rules.d/70-persistent-net.rules" + + puts "Rewrite 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 + + system("chroot", dir, "apt-get", "-qy", "update") + if File.read("#{dir}/etc/debian_version").to_f >= 9.0 + # Note: 2019-10-08 時点で Debian9 のカーネルバージョンに対応していないので、エラー回避のために既存のカーネルを全て削除し、強制的に jessie のカーネルをイントールする + system("rm", "-f", "#{dir}/var/lib/dpkg/info/linux-image-#{`uname -r`.chomp}.prerm") + system({'DEBIAN_FRONTEND' => 'noninteractive'}, "chroot", dir, "apt", "remove", "--purge", "-y", "linux-image-*") + system("wget", "-O", "#{dir}/tmp/linux.deb", "http://security-cdn.debian.org/debian-security/pool/updates/main/l/linux/linux-image-3.16.0-10-amd64_3.16.81-1_amd64.deb") or raise "Failed to get jessie kernel" + system("chroot", dir, "dpkg", "-i", "/tmp/linux.deb") + system({'DEBIAN_FRONTEND' => 'noninteractive'}, "chroot", dir, "apt", "install", "-f", "-y") or raise "Failed to install jessie kernel" + system({'DEBIAN_FRONTEND' => 'noninteractive'}, "chroot", dir, "apt", "-y", "install", "isc-dhcp-client") + else + system({'DEBIAN_FRONTEND' => 'noninteractive'}, "chroot", dir, "apt-get", "-y", "--allow-unauthenticated", "install", "linux-image-amd64", "isc-dhcp-client") + end + + puts "Update grub..." + if File.exists? "#{dir}/var/lib/dpkg/info/grub-efi-amd64.list" + system({'DEBIAN_FRONTEND' => 'noninteractive'}, "chroot", dir, "apt", "remove", "--purge", "-y", "grub-efi-amd64") or raise "Failed to purge grub-efi" + end + if !File.exists?("#{dir}/var/lib/dpkg/info/grub-pc.list") || !(File.exists?("#{dir}/usr/sbin/grub-bios-setup") || File.exists?("#{dir}/usr/sbin/grub-setup")) + 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." + + unless Array(run_cmds).empty? + Array(run_cmds).each do |cmd| + system({'DEBIAN_FRONTEND' => 'noninteractive'}, "chroot", dir, *Array(cmd)) or raise "Failed to execute command (#{cmd}): #{$!}" + end + end + + cfg = File.read "#{dir}/boot/grub/grub.cfg" + cfg.gsub! %r{mapper/loop\d+p}, "sda" + File.write "#{dir}/boot/grub/grub.cfg", cfg + end + end + + def with_rootfs(&block) Dir.mktmpdir("ci-#{$$}-#{name}") do |dir| with_loopdev do |devices| - puts "Override grub with host version..." - 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.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" @@ -128,31 +233,8 @@ class ImageCreator 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" + yield(dir, devices) - puts "Rewrite 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..." - unless File.exists? "#{dir}/usr/sbin/grub-mkconfig" - 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") @@ -172,19 +254,19 @@ class ImageCreator "Description" => dir.path == '/' ? 'root' : dir.path[1..-1].tr('/', '-'), "Format" => "raw", "UserBucket" => { - "S3Bucket" => "Change-to-your-buket-name", - "S3Key" => "/src-disks/#{img_path_base}_#{idx}.img" + "S3Bucket" => ENV.fetch("S3_BUCKET", "osdn-base-images"), + "S3Key" => "#{ENV.fetch("S3_KEY_PREFIX", "src-disks/")}#{img_path_base}_#{idx}.img" } }) end File.write "#{img_path_base}.json", JSON.pretty_generate(jdef) end - def run create_disk create_fs sync_dirs + prepare_awsenv fix_boot write_json puts "Image creation has been complated (#{name})" @@ -192,20 +274,35 @@ class ImageCreator end if $0 == __FILE__ + require 'optparse' + + opts = ARGV.getopts('l:', 'limit:', 'skip:') + limit_pat = opts['limit'] || opts['l'] + limit_pat and + limit_pat = Regexp.new(limit_pat) + list = YAML.load_file(ARGV[0] || 'image-list.yml') list.each do |imgdef| name = nil dirs = [] + opts = {} if imgdef.kind_of?(Hash) name = imgdef['name'] (imgdef['dirs'] || {}).each do |path, opts| opts.kind_of?(Hash) or opts = {size: opts} dirs << SyncDirDef.new({path: path}.merge(opts.keys.map(&:to_sym).zip(opts.values).to_h)) end + imgdef.keys.each do |k| + next if %w[dirs name].member?(k) + opts[k.to_sym] = imgdef[k] + end else name = imgdef end + if limit_pat + limit_pat.match?(name) or next + end dirs.empty? and dirs << SyncDirDef.new - ImageCreator.new(name, dirs).run + ImageCreator.new(name, dirs, **opts).run end end