#!/bin/bash # # lxc: linux Container library # Authors: # Serge Hallyn # Daniel Lezcano # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA set -e usage() { echo "usage: $(basename $0) -o ORIG_NAME -n NEW_NAME [-s] [-h] [-L FS_SIZE]" >&2 echo " [-v VG_NAME] [-p LV_PREFIX] [-t FS_TYPE]" >&2 } help() { usage echo >&2 echo "Clone an existing container on the system." >&2 echo >&2 echo "Options:" >&2 echo " -o ORIG_NAME specify the name of the original container" >&2 echo " -n NEW_NAME specify the name of the new container" >&2 echo " -s make the new rootfs a snapshot of the original" >&2 echo " -L FS_SIZE specify the new filesystem size (default: 2G)" >&2 echo " -v VG_NAME specify the new LVM volume group name (default: lxc)" >&2 echo " -p LV_PREFIX add a prefix to new LVM logical volume names" >&2 echo " -t FS_TYPE specify the new filesystem type (default: ext3;" >&2 echo " only works for non-snapshot LVM)" >&2 } shortoptions='ho:n:sL:v:p:t:' longoptions='help,orig:,name:,snapshot,fssize:,vgname:,lvprefix:,fstype:' lxc_path=@LXCPATH@ bindir=@BINDIR@ snapshot=no lxc_defsize=2G lxc_size=_unset lxc_vg=lxc lxc_lv_prefix="" fstype=ext3 getopt=$(getopt -o $shortoptions --longoptions $longoptions -- "$@") if [ $? != 0 ]; then usage exit 1; fi eval set -- "$getopt" while true; do case "$1" in -h|--help) help exit 1 ;; -s|--snapshot) shift snapshot=yes snapshot_opt="-s" ;; -o|--orig) shift lxc_orig=$1 shift ;; -L|--fssize) shift lxc_size=$1 shift ;; -v|--vgname) shift lxc_vg=$1 shift ;; -n|--name) shift lxc_new=$1 shift ;; -p|--lvprefix) shift lxc_lv_prefix=$1 shift ;; --) shift break ;; *) usage exit 1 ;; esac done if [ -z "$lxc_path" ]; then echo "$(basename $0): no configuration path defined" >&2 exit 1 fi if [ ! -r $lxc_path ]; then echo "$(basename $0): configuration path '$lxc_path' not found" >&2 exit 1 fi if [ -z "$lxc_orig" ]; then echo "$(basename $0): no original container name specified" >&2 usage exit 1 fi if [ -z "$lxc_new" ]; then echo "$(basename $0): no new container name specified" >&2 usage exit 1 fi if [ "$(id -u)" != "0" ]; then echo "$(basename $0): must be run as root" >&2 exit 1 fi if [ ! -d "$lxc_path/$lxc_orig" ]; then echo "$(basename $0): '$lxc_orig' does not exist" >&2 exit 1 fi if [ -d "$lxc_path/$lxc_new" ]; then echo "$(basename $0): '$lxc_new' already exists" >&2 exit 1 fi mounted=0 frozen=0 oldroot=`grep lxc.rootfs $lxc_path/$lxc_orig/config | awk -F= '{ print $2 '}` cleanup() { if [ -b $oldroot ]; then if [ $mounted -eq 1 ]; then umount $rootfs || true fi lvremove -f $rootdev || true fi ${bindir}/lxc-destroy -n $lxc_new || true if [ $frozen -eq 1 ]; then lxc-unfreeze -n $lxc_orig fi echo "$(basename $0): aborted" >&2 exit 1 } trap cleanup HUP INT TERM mkdir -p $lxc_path/$lxc_new hostname=$lxc_new echo "Tweaking configuration" cp $lxc_path/$lxc_orig/config $lxc_path/$lxc_new/config sed -i '/lxc.utsname/d' $lxc_path/$lxc_new/config echo "lxc.utsname = $hostname" >> $lxc_path/$lxc_new/config grep "lxc.mount =" $lxc_path/$lxc_new/config >/dev/null 2>&1 && { sed -i '/lxc.mount =/d' $lxc_path/$lxc_new/config; echo "lxc.mount = $lxc_path/$lxc_new/fstab" >> $lxc_path/$lxc_new/config; } if [ -e $lxc_path/$lxc_orig/fstab ];then cp $lxc_path/$lxc_orig/fstab $lxc_path/$lxc_new/fstab sed -i "s@$lxc_path/$lxc_orig@$lxc_path/$lxc_new@" $lxc_path/$lxc_new/fstab fi echo "Copying rootfs..." oldroot=`grep lxc.rootfs $lxc_path/$lxc_orig/config | awk -F'[= \t]+' '{ print $2 }'` rootfs=`echo $oldroot |sed "s/$lxc_orig/$lxc_new/"` container_running=True lxc-info -s -n $lxc_orig|grep RUNNING >/dev/null 2>&1 || container_running=False sed -i '/lxc.rootfs/d' $lxc_path/$lxc_new/config if [ -b $oldroot ]; then type vgscan || { echo "$(basename $0): lvm is not installed" >&2; false; } lvdisplay $oldroot > /dev/null 2>&1 || { echo "$(basename $0): non-lvm blockdev cloning is not supported" >&2; false; } lvm=TRUE # ok, create a snapshot of the lvm device if [ $container_running = "True" ]; then lxc-freeze -n $lxc_orig frozen=1 fi if [ $lxc_size = "_unset" ]; then lxc_size=`lvdisplay $oldroot | grep Size | awk '{ print $3 $4 }'` fi newlv="${lxc_lv_prefix}${lxc_new}_snapshot" lvcreate -s -L $lxc_size -n $newlv $oldroot type xfs_admin > /dev/null 2>&1 && { # change filesystem UUID if it is an xfs filesystem xfs_admin -u /dev/$lxc_vg/$newlv && xfs_admin -U generate /dev/$lxc_vg/$newlv } if [ $container_running = "True" ]; then lxc-unfreeze -n $lxc_orig frozen=0 fi if [ $snapshot = "no" ]; then #mount snapshot mkdir -p ${rootfs}_snapshot mount /dev/$lxc_vg/${lxc_lv_prefix}${lxc_new}_snapshot ${rootfs}_snapshot || { echo "$(basename $0): failed to mount new rootfs_snapshot" >&2; false; } #create a new lv lvcreate -L $lxc_size $lxc_vg -n ${lxc_lv_prefix}$lxc_new echo "lxc.rootfs = /dev/$lxc_vg/${lxc_lv_prefix}$lxc_new" >> $lxc_path/$lxc_new/config # and mount it so we can tweak it mkdir -p $rootfs mkfs -t $fstype /dev/$lxc_vg/${lxc_lv_prefix}$lxc_new mount /dev/$lxc_vg/${lxc_lv_prefix}$lxc_new $rootfs || { echo "$(basename $0): failed to mount new rootfs" >&2; false; } mounted=1 rsync -ax ${rootfs}_snapshot/ ${rootfs}/ || { echo "$(basename $0): copying data to new lv failed" >&2; false; } umount ${rootfs}_snapshot rmdir ${rootfs}_snapshot lvremove -f $lxc_vg/${lxc_lv_prefix}${lxc_new}_snapshot else lvrename $lxc_vg/${lxc_lv_prefix}${lxc_new}_snapshot $lxc_vg/${lxc_lv_prefix}$lxc_new echo "lxc.rootfs = /dev/$lxc_vg/${lxc_lv_prefix}$lxc_new" >> $lxc_path/$lxc_new/config # and mount it so we can tweak it mkdir -p $rootfs mount /dev/$lxc_vg/${lxc_lv_prefix}$lxc_new $rootfs || { echo "$(basename $0): failed to mount new rootfs" >&2; false; } mounted=1 fi elif which btrfs >/dev/null 2>&1 && btrfs subvolume list $oldroot >/dev/null 2>&1; then # if oldroot is a btrfs subvolume, assume they want a snapshot btrfs subvolume snapshot "$oldroot" "$rootfs" 2>&1 || { echo "$(basename $0): btrfs snapshot failed" >&2; false; } echo "lxc.rootfs = $rootfs" >> "$lxc_path/$lxc_new/config" else if [ $snapshot = "yes" ]; then echo "$(basename $0): cannot snapshot a directory" >&2 cleanup fi if [ $container_running = "True" ]; then lxc-freeze -n $lxc_orig frozen=1 fi mkdir -p $rootfs/ rsync -ax $oldroot/ $rootfs/ echo "lxc.rootfs = $rootfs" >> $lxc_path/$lxc_new/config if [ $container_running = "True" ]; then lxc-unfreeze -n $lxc_orig frozen=0 fi fi echo "Updating rootfs..." # so you can 'ssh $hostname.' or 'ssh $hostname.local' if [ -f $rootfs/etc/dhcp/dhclient.conf ] && ! grep -q "^send host-name.*hostname" $rootfs/etc/dhcp/dhclient.conf; then sed -i "s/send host-name.*$/send host-name \"$hostname\";/" $rootfs/etc/dhcp/dhclient.conf fi c=$lxc_path/$lxc_new/config # change hwaddrs mv ${c} ${c}.old ( while read line; do if [ "${line:0:18}" = "lxc.network.hwaddr" ]; then echo "lxc.network.hwaddr= 00:16:3e:$(openssl rand -hex 3| sed 's/\(..\)/\1:/g; s/.$//')" else echo "$line" fi done ) < ${c}.old > ${c} rm -f ${c}.old # set the hostname cat < $rootfs/etc/hostname $hostname EOF # set minimal hosts cat < $rootfs/etc/hosts 127.0.0.1 localhost $hostname EOF # if this was a block device, then umount it now if [ $mounted -eq 1 ]; then umount $rootfs fi echo "'$lxc_new' created"