From 75f7c62bd1ae7005896fa9b6b152d2d40c6b58c9 Mon Sep 17 00:00:00 2001 From: Michael Shigorin Date: Mon, 18 Jun 2012 21:00:32 +0300 Subject: [PATCH] initial build-vm feature Yes, mkimage-profiles is now able to build VM disk images. So far the support is pretty basic: - a single hard drive image with a single partition/FS - only stock root password is configurable - LILO is hardwired as a bootloader The resulting images tend to boot under qemu/kvm though. Please see doc/vm.txt for the warning regarding additional privileges and setup required. This was started back in February but I still hoped to avoid sudo/privileged helper (and libguestfs is almost as undistributable as can be)... Thanks: - http://blog.quinthar.com/2008/07/building-1gb-bootable-qemu-image-using.html - Alexey Morarash who reworked that as https://github.com/tuxofil/linsygen - led@, legion@, vitty@, aen@ for providing advice and inspiration --- README | 2 +- bin/tar2vm | 130 ++++++++++++++++++ doc/variables.txt | 5 + doc/vm.txt | 20 +++ .../build-distro/lib/90-build-distro.mk | 1 - features.in/build-vm/README | 3 + features.in/build-vm/config.mk | 3 + features.in/build-vm/image-scripts.d/00root | 7 + .../build-vm/image-scripts.d/03services | 9 ++ .../build-vm/image-scripts.d/04inittab | 4 + features.in/build-vm/image-scripts.d/06syslog | 16 +++ features.in/build-vm/image-scripts.d/07kernel | 13 ++ features.in/build-vm/lib/90-build-vm.mk | 33 +++++ features.in/pack/config.mk | 7 + lib/help.mk | 16 ++- lib/vm.mk | 17 +++ main.mk | 8 +- 17 files changed, 284 insertions(+), 10 deletions(-) create mode 100755 bin/tar2vm create mode 100644 doc/vm.txt create mode 100644 features.in/build-vm/README create mode 100644 features.in/build-vm/config.mk create mode 100755 features.in/build-vm/image-scripts.d/00root create mode 100755 features.in/build-vm/image-scripts.d/03services create mode 100755 features.in/build-vm/image-scripts.d/04inittab create mode 100755 features.in/build-vm/image-scripts.d/06syslog create mode 100755 features.in/build-vm/image-scripts.d/07kernel create mode 100644 features.in/build-vm/lib/90-build-vm.mk create mode 100644 lib/vm.mk diff --git a/README b/README index 10a99ada..7faf3ac0 100644 --- a/README +++ b/README @@ -32,7 +32,7 @@ Most docs are in Russian, welcome to learn it or ask for English. - сборка образа Объекты: -- дистрибутивы и виртуальные окружения: +- дистрибутивы и виртуальные среды/машины: + описываются в conf.d/*.mk или соответственно lib/{distro,ve}.mk + могут основываться на предшественниках, расширяя их + дистрибутивы также: diff --git a/bin/tar2vm b/bin/tar2vm new file mode 100755 index 00000000..f53f7d10 --- /dev/null +++ b/bin/tar2vm @@ -0,0 +1,130 @@ +#!/bin/sh -e + +. shell-error + +if [ $# -lt 2 ]; then + fatal "error: tar2vm needs at least two arguments" +fi + +# this needs env_keep sudo setup to actually work +if [ -n "$GLOBAL_BUILDDIR" ]; then + WORKDIR="$GLOBAL_BUILDDIR/vmroot" +else + WORKDIR="$(mktemp --tmpdir -d vmroot-XXXXX)" +fi + +[ -n "$WORKDIR" ] || fatal "couldn't come up with suitable WORKDIR" + +[ -n "$GLOBAL_DEBUG" ] || message "WORKDIR: $WORKDIR" + +# a tarball containing chroot with a kernel +TAR="$1" +[ -s "$TAR" ] || fatal "source tarball doesn't really exist" + +# a path to the image to be generated +IMG="$2" +[ -d "$(dirname "$IMG")" ] || fatal "target directory doesn't exist" + +# image size in bytes (256M is a fallback) +TARSIZE="$(stat -Lc %s "$TAR")" +DEFSIZE="$(expr 3 '*' "$TARSIZE" '/' 2)" +DISKSIZE="${3:-${DEFSIZE:-268435456}}" +# ...and in megabytes +DISKSIZEM="$(expr "$DISKSIZE" / 1048576)" + +# tested to work: ext[234], jfs +ROOTFSTYPE="${4:-ext4}" + +# single root partition hardwired so far, +# add another image for swap if needed +ROOTDEV="/dev/sda1" + +# last preparations... +for i in losetup parted kpartx mkfs."$ROOTFSTYPE"; do + if ! type -t "$i" >&/dev/null; then + fatal "$i required but not found" + fi +done + +LOOPDEV="$(losetup --find)" +ROOTFS="$WORKDIR/chroot" + +exit_handler() +{ + rc=$? + if [ -n "$ROOTFS" ]; then + umount "$ROOTFS"{/dev,/proc,/sys,} + if [ -n "$LOOPDEV" ]; then + kpartx -d "$LOOPDEV" + losetup --detach "$LOOPDEV" + fi + rm -r -- "$ROOTFS" + rmdir -- "$WORKDIR" + fi + exit $rc +} + +trap exit_handler EXIT + +# prepare disk image and a filesystem inside it +rm -f -- "$IMG" +dd if=/dev/zero of="$IMG" bs=1 count=1 seek="$DISKSIZE" conv=notrunc +losetup "$LOOPDEV" "$IMG" + +parted --script "$LOOPDEV" mklabel msdos +parted --script "$LOOPDEV" mkpart primary ext2 1 "$DISKSIZEM" + +kpartx -a "$LOOPDEV" +LOOPDEV1="/dev/mapper/$(basename "$LOOPDEV")p1" + +mkfs."$ROOTFSTYPE" "$LOOPDEV1" + +# mount and populate it +mkdir -pm755 "$ROOTFS" +mount "$LOOPDEV1" "$ROOTFS" +tar -C "$ROOTFS" --numeric-owner -xf "$TAR" +for i in /dev /proc /sys; do mount --bind "$i" "$ROOTFS$i"; done + +# NB: different storage modules might be needed for non-kvm +echo "$LOOPDEV1 / $ROOTFSTYPE defaults 1 1" >> "$ROOTFS/etc/fstab" +echo "MODULES_PRELOAD=sd_mod ata_piix $ROOTFSTYPE" >> "$ROOTFS/etc/initrd.mk" + +KERNEL="$(readlink $ROOTFS/boot/vmlinuz | sed 's,vmlinuz-,,')" +chroot "$ROOTFS" make-initrd -k "$KERNEL" + +sed -i "s,$LOOPDEV1,$ROOTDEV," "$ROOTFS/etc/fstab" + +# configure and install bootloader +REGEXP='^([0-9]+) heads, ([0-9]+) sectors/track, ([0-9]+) cylinders.*$' +set -- $(fdisk -l "$LOOPDEV" | grep -E "$REGEXP" | sed -r "s@$REGEXP@\1 \2 \3@") + +LILO_COMMON="lba32 +delay=1 +vga=0 +image=/boot/vmlinuz + initrd=/boot/initrd.img + append=\"root=$ROOTDEV rootdelay=3\" + label=linux" + +cat > "$ROOTFS"/etc/lilo-loop.conf << EOF +boot=$LOOPDEV +disk=$LOOPDEV + bios=0x80 + heads=$1 + sectors=$2 + cylinders=$3 + partition=$LOOPDEV1 + start=63 +$LILO_COMMON +EOF + +chroot "$ROOTFS" lilo -C /etc/lilo-loop.conf + +cat > "$ROOTFS"/etc/lilo.conf << EOF +boot=${ROOTDEV%[0-9]*} +$LILO_COMMON +EOF + +if [ -n "$SUDO_USER" ]; then + chown "$SUDO_USER" "$IMG" "$ROOTFS" "$WORKDIR" +fi diff --git a/doc/variables.txt b/doc/variables.txt index 44476dcb..3d2f35c5 100644 --- a/doc/variables.txt +++ b/doc/variables.txt @@ -76,6 +76,11 @@ + NB: в силу специфики обработки передаётся только явно + см. ../Makefile, ../report.mk, ../lib/report.mk +- ROOTPW + + устанавливает пароль root по умолчанию для образов виртуальных машин + + значение: пусто (по умолчанию root) либо строка + + см. ../features.in/build-vm/image-scripts.d/00root + - SAVE_PROFILE + сохраняет архив сгенерированного профиля в .disk/ + значение: пусто (по умолчанию) либо любая строка diff --git a/doc/vm.txt b/doc/vm.txt new file mode 100644 index 00000000..e0efd953 --- /dev/null +++ b/doc/vm.txt @@ -0,0 +1,20 @@ +сборка образов виртуальных машин +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +ВНИМАНИЕ: заключительная операция создания образа жёсткого диска +из архива с содержимым корневой файловой системы требует доступа +к sudo и разрешения на выполнение скрипта bin/tar2vm в корневом +каталоге метапрофиля при установке mkimage-profiles из пакета. + +Соответствующий фрагмент конфигурации sudo(8) может выглядеть как: + +mike ALL=NOPASSWD: /usr/share/mkimage-profiles/bin/tar2vm + +При работе с git следует иметь в виду, что предоставлять недоверенному +пользователю право выполнять с повышенными привилегиями доступный ему +по записи скрипт равнозначно предоставлению полных привилегий root. + +Также потребуется пакет multipath-tools (/sbin/kpartx). + +Пример сборки и запуска VM: + +$ make ROOTPW=reallysecret1 vm/bare.img && kvm -hda ~/out/bare.img diff --git a/features.in/build-distro/lib/90-build-distro.mk b/features.in/build-distro/lib/90-build-distro.mk index 8c1b0da2..dddf1f96 100644 --- a/features.in/build-distro/lib/90-build-distro.mk +++ b/features.in/build-distro/lib/90-build-distro.mk @@ -23,7 +23,6 @@ all: $(GLOBAL_DEBUG) prep copy-subdirs copy-tree run-scripts pack-image \ postprocess $(GLOBAL_CLEAN_WORKDIR) prep: $(GLOBAL_DEBUG) dot-disk $(WHATEVER) imagedir -#prep: $(GLOBAL_DEBUG) dot-disk metadata imagedir dot-disk: @mkdir -p files/.disk diff --git a/features.in/build-vm/README b/features.in/build-vm/README new file mode 100644 index 00000000..f3b72965 --- /dev/null +++ b/features.in/build-vm/README @@ -0,0 +1,3 @@ +Эта фича конфигурирует создание образа виртуальной машины (VM). +Дополняет финальную стадию сборки (lib/, image-scripts.d/). +Требует для работы sudo(8) -- см. тж. ../../doc/vm.txt . diff --git a/features.in/build-vm/config.mk b/features.in/build-vm/config.mk new file mode 100644 index 00000000..b7b3ff13 --- /dev/null +++ b/features.in/build-vm/config.mk @@ -0,0 +1,3 @@ +# hooked from ../../lib/sugar.mk +use/build-vm: + @$(call add_feature) diff --git a/features.in/build-vm/image-scripts.d/00root b/features.in/build-vm/image-scripts.d/00root new file mode 100755 index 00000000..c69a7d4d --- /dev/null +++ b/features.in/build-vm/image-scripts.d/00root @@ -0,0 +1,7 @@ +#!/bin/sh + +ROOTPW="${GLOBAL_ROOTPW:-root}" + +if type -t chpasswd >&/dev/null; then + echo "root:$ROOTPW" | chpasswd +fi diff --git a/features.in/build-vm/image-scripts.d/03services b/features.in/build-vm/image-scripts.d/03services new file mode 100755 index 00000000..494c76ef --- /dev/null +++ b/features.in/build-vm/image-scripts.d/03services @@ -0,0 +1,9 @@ +#!/bin/sh +# VMs might have no means to communicate with the outer +# world except for networking + +[ -x /sbin/chkconfig ] || exit 0 + +for i in network random syslogd; do chkconfig $i on; done +for i in fbsetfont netfs rawdevices; do chkconfig $i off; done +: diff --git a/features.in/build-vm/image-scripts.d/04inittab b/features.in/build-vm/image-scripts.d/04inittab new file mode 100755 index 00000000..9c99638d --- /dev/null +++ b/features.in/build-vm/image-scripts.d/04inittab @@ -0,0 +1,4 @@ +#!/bin/sh -e +# we don't need no extra gettys in VMs +[ -s /etc/inittab ] || exit 0 +sed -i 's,^[3-9]\+:[0-9]\+:respawn:/sbin/mingetty.*,#&,' /etc/inittab diff --git a/features.in/build-vm/image-scripts.d/06syslog b/features.in/build-vm/image-scripts.d/06syslog new file mode 100755 index 00000000..fe32fad7 --- /dev/null +++ b/features.in/build-vm/image-scripts.d/06syslog @@ -0,0 +1,16 @@ +#!/bin/sh -e +# tweak syslog configuration to: +# - avoid logging to a nonexistent tty; +# - avoid hard sync logging (one is better off doing +# remote syslog if you do care for reliable data anyways) +# credits: vvk@, thresh@ (2010) + +CONFIG=/etc/syslog.conf + +[ ! -f "$CONFIG" ] || \ + sed -i \ + -e 's,/dev/tty12,/var/log/syslog/console,' \ + -e 's,^.*/var/log/syslog/console$,#&,' \ + -e 's,-/var/log/,/var/log/,g' \ + -e 's,/var/log/,-/var/log/,g' \ + "$CONFIG" diff --git a/features.in/build-vm/image-scripts.d/07kernel b/features.in/build-vm/image-scripts.d/07kernel new file mode 100755 index 00000000..cc827351 --- /dev/null +++ b/features.in/build-vm/image-scripts.d/07kernel @@ -0,0 +1,13 @@ +#!/bin/sh +# predictable file locations make bootloader configuration simple; +# this script relates to .../features.in/stage2/stage1/scripts.d/80-make-initfs + +kver="$(rpm -qa 'kernel-image*' \ + --qf '%{installtime} %{version}-%{name}-%{release}\n' \ + | sort -n \ + | tail -n 1 \ + | cut -f 2 -d ' ' \ + | sed 's/kernel-image-//')" + +ln -s vmlinuz-$kver /boot/vmlinuz +ln -s initrd-$kver.img /boot/initrd.img diff --git a/features.in/build-vm/lib/90-build-vm.mk b/features.in/build-vm/lib/90-build-vm.mk new file mode 100644 index 00000000..5cee14d6 --- /dev/null +++ b/features.in/build-vm/lib/90-build-vm.mk @@ -0,0 +1,33 @@ +# step 4: build the virtual machine image + +IMAGE_PACKAGES = $(SYSTEM_PACKAGES) \ + $(COMMON_PACKAGES) \ + $(BASE_PACKAGES) \ + $(THE_PACKAGES) \ + $(call list,$(BASE_LISTS) $(THE_LISTS)) \ + $(call kpackages,$(THE_KMODULES) $(BASE_KMODULES),$(KFLAVOURS)) + +# intermediate chroot archive +VM_TARBALL := $(IMAGE_OUTDIR)/$(IMAGE_NAME).tar + +check-sudo: + @if ! type -t sudo >&/dev/null; then \ + echo "** error: sudo not available, see doc/vm.txt" >&2; \ + exit 1; \ + fi + +convert-image: check-sudo + @if ! sudo $(TOPDIR)/bin/tar2vm \ + "$(VM_TARBALL)" "$(IMAGE_OUTPATH)" $$VM_SIZE; then \ + echo "** error: sudo tar2vm failed, see also doc/vm.txt" >&2; \ + exit 1; \ + fi + +run-image-scripts: GLOBAL_ROOTPW := $(ROOTPW) + +pack-image: MKI_PACK_RESULTS := tar:$(VM_TARBALL) + +all: $(GLOBAL_DEBUG) build-image copy-tree run-image-scripts pack-image \ + convert-image postprocess $(GLOBAL_CLEAN_WORKDIR) + +prep: imagedir diff --git a/features.in/pack/config.mk b/features.in/pack/config.mk index 521b52f7..cce10adc 100644 --- a/features.in/pack/config.mk +++ b/features.in/pack/config.mk @@ -1,3 +1,4 @@ +# distributions DISTRO_EXTS := .iso use/pack: @@ -12,6 +13,7 @@ else @$(call set,IMAGE_PACKTYPE,isodata) endif +# virtual environments VE_ARCHIVES := tar cpio VE_COMPRESSORS := gz xz# there's no sense in bzip2 by now VE_ZIPS := $(call addsuffices, \ @@ -34,3 +36,8 @@ $(foreach c,$(VE_ARCHIVES), \ $(eval $(call PACK_containers,$(c))) \ $(foreach z,$(VE_COMPRESSORS), \ $(eval $(call PACK_compressors,$(c),$(z))))) + +# virtual machines +VM_EXTS := .img + +use/pack/img: use/pack; @: diff --git a/lib/help.mk b/lib/help.mk index 832c87d1..1a0526d9 100644 --- a/lib/help.mk +++ b/lib/help.mk @@ -6,7 +6,7 @@ define help_body endef else define help_body - @echo '** available $(1) targets:'; \ + echo '** available $(1) targets:'; \ columnize $(2) endef endif @@ -14,11 +14,15 @@ endif help = $(and $(2),$(help_body)) help/distro: - $(call help,distribution,$(sort $(DISTROS:distro/%=%))) + @$(call help,distribution,$(sort $(DISTROS:distro/%=%))) help/ve: - @echo '** available virtual environment targets:'; \ - columnize $(sort $(VES)) + @[ -n "$(SPACE)" ] && echo; \ + $(call help,virtual environment,$(sort $(VES))) -help: | help/distro help/space help/ve; @: -help/space:; @echo +help/vm: + @[ -n "$(SPACE)" ] && echo; \ + $(call help,virtual machine,$(sort $(VMS))) + +help: SPACE = 1 +help: help/distro help/ve help/vm; @: diff --git a/lib/vm.mk b/lib/vm.mk new file mode 100644 index 00000000..d1fedb76 --- /dev/null +++ b/lib/vm.mk @@ -0,0 +1,17 @@ +# step 2: build up virtual machine's configuration + +ifndef MKIMAGE_PROFILES +$(error this makefile is designed to be included in toplevel one) +endif + +ifeq (vm,$(IMAGE_CLASS)) + +#vm/.bare: profile/bare use/bootloader/grub +vm/.bare: profile/bare + @$(call add,BASE_PACKAGES,basesystem interactivesystem lilo passwd shadow-utils e2fsprogs mingetty) + @$(call set,KFLAVOURS,std-def) + +vm/bare: vm/.bare + @$(call add,BASE_PACKAGES,apt) + +endif diff --git a/main.mk b/main.mk index b75e7443..5cb8b71c 100644 --- a/main.mk +++ b/main.mk @@ -34,15 +34,19 @@ include lib/*.mk include conf.d/*.mk include features.in/*/config.mk +# starts to look copypastey DISTRO_TARGETS := $(shell sed -n 's,^\(distro/[^:.]\+\):.*$$,\1,p' \ lib/distro.mk $(wildcard conf.d/*.mk) | sort -u) VE_TARGETS := $(shell sed -n 's,^\(ve/[^:.]\+\):.*$$,\1,p' \ lib/ve.mk $(wildcard conf.d/*.mk) | sort -u) +VM_TARGETS := $(shell sed -n 's,^\(vm/[^:.]\+\):.*$$,\1,p' \ + lib/vm.mk $(wildcard conf.d/*.mk) | sort -u) DISTROS := $(call addsuffices,$(DISTRO_EXTS),$(DISTRO_TARGETS)) VES := $(call addsuffices,$(VE_EXTS),$(VE_TARGETS)) -IMAGES := $(DISTROS) $(VES) +VMS := $(call addsuffices,$(VM_EXTS),$(VM_TARGETS)) +IMAGES := $(DISTROS) $(VES) $(VMS) -.PHONY: $(IMAGES) $(DISTRO_TARGETS) $(VE_TARGETS) +.PHONY: $(IMAGES) $(DISTRO_TARGETS) $(VE_TARGETS) $(VM_TARGETS) .PHONY: debug everything help space ### duplicate but still needed