#!/usr/bin/env python2.5 import sys import os import shutil import tempfile import time from subprocess import call, check_call, Popen, PIPE # TODO: # . pick a loop device sanely # x adapt size of partition to fit volume # . fix hostname, root password # - (once debugging diminishes) umount, losetup -d, maybe rm -r # - avoid race conditions with other loop-device users, too (somehow) # - get numbers from actual source partition table rather than # as magic numbers based on what sipb-xen-make-iso does with etch. def losetup(source, offset=0): # XXX we avoid colliding with other instances of ourself, # but when it comes to other loop-device users we just # pick a range things don't seem to use and hope... lockfilename = '/tmp/losetup.lock' os.close(os.open(lockfilename, os.O_CREAT+os.O_EXCL)) #lock try: loopdevice = None for i in xrange(32,60): # totally arbitrary, just looks to be unused on black-mesa filename = '/dev/loop%d'%i if 0 == len(file(filename).read(1)): loopdevice = filename # it's empty break if loopdevice is not None: call(['losetup', '-o', str(offset), loopdevice, source]) else: raise RuntimeError('out of loop devices for copying VM image: too many at once?') finally: os.unlink(lockfilename) #unlock return loopdevice # XX this means we can duplicate 9 at once. since it takes around 30 seconds, # this seems like an adequate capacity. def duplicate_lv(source, dest): '''OBSOLETE: duplicate boot record, filesystem from LV source to LV dest source, dest should be device filenames. Now we just dd. Doesn't support resizing, but it's easier, especially if we allow the partition table to vary between images. ''' # XXX this is very specific to what the etch sipb-xen-make-iso installer does. # XXXX also, it's specific to four gigs. # step 1: copy the MBR call(['dd', 'if='+source, 'bs=512', 'count=63', 'of='+dest]) # step 2: fix up partition table # XX actually do this; this is our opportunity to resize the filesystem # step 3: make the filesystem, and copy its contents in sourcefs = losetup(source, 32256) destfs = losetup(dest, 32256) call(['mkfs.ext3', '-b', '4096', destfs, '987989']) tmptree = tempfile.mkdtemp('', 'auto-install.dup.', '/tmp')#yes, args backward os.mkdir(tmptree+"/source") call(['mount', '-t', 'ext3', '-o', 'ro', sourcefs, tmptree+"/source"]) os.mkdir(tmptree+"/dest") call(['mount', '-t', 'ext3', destfs, tmptree+"/dest"]) call(['cp', '-aT', tmptree+"/source", tmptree+"/dest"]) # step 4: twiddle what we want to twiddle # XX do this # step 5: make the swap area swapfs = losetup(dest, 4046870016) call(['mkswap', swapfs, str(240943)]) # call(['losetup', '-d', swapfs]) print 'losetup -d '+swapfs # step 6: clean up. # XX actually unmount etc (leaving it is for debugging) print 'umount '+tmptree+'/source' call(['umount', tmptree+"/dest"]) # needed to flush writes print 'losetup -d '+sourcefs print 'losetup -d '+destfs def frob_copy(target, *args): # 1. prepare arguments volume args_volume = prefix+target+'_args' args_device = '/dev/xenvg/' + args_volume check_call(['/sbin/lvcreate', 'xenvg', '--name', args_volume, '--size', '4K']) file(args_device, 'w').write('\n'.join(args) + '\n') # 2. invoke frobber vm copier_device = '/dev/xenvg/d_wert_hda' check_call(['/usr/sbin/xm', 'create', 'sipb-database', 'machine_name='+target, 'disks=' + ' '.join(['phy:'+copier_device+',hda,w', 'phy:'+target_device+',hdc,w', 'phy:'+args_device+',hdd,w'])]) # XXX should check_call(['/sbin/lvremove', '-f', 'xenvg/'+args_volume]) def frob_copy_simple(target, hostname, rootpw, *args): """target should be an LV device filename This is highly specific to the etch install produced by sipb-xen-make-iso. Generalizing and customizing to other distros is left to the future... """ # 1: mount filesystem fs = losetup(target, 32256) mntdir = tempfile.mkdtemp('', 'auto-install.frob.', '/tmp') call(['mount', '-t', 'ext3', fs, mntdir]) # 2: do frobbing # 2a: (printf "%s\n" "$ROOTPW"; sleep .3; printf "%s\n" "$ROOTPW") # | /usr/sbin/chroot "$TARGET" /usr/bin/passwd root p = Popen(['/usr/sbin/chroot', mntdir, '/usr/bin/passwd', 'root'], stdin=PIPE) p.stdin.write(rootpw+'\n') time.sleep(1) p.stdin.write(rootpw+'\n') p.stdin.close() p.wait() os.chdir(mntdir) # 2b: clear generated file that has eth0's old MAC address # rm $TARGET/etc/udev/rules.d/z25_persistent-net.rules os.unlink('etc/udev/rules.d/z25_persistent-net.rules') # 2c: hostname. # XX Use nullmailer in image, not exim4. (Fewer copies of hostname.) # 2c1: rm /var/lib/dhcp3/dhclient.eth0.leases os.unlink('var/lib/dhcp3/dhclient.eth0.leases') # 2c2: /etc/hosts, /etc/hostname; /etc/motd? /etc/mailname? call(['perl', '-i', '-pe', 's/ice3.servers.csail.mit.edu/'+hostname+'/g', 'etc/hosts', 'etc/hostname', 'etc/motd', 'etc/mailname']) # 3: clean up os.chdir('/') call(['umount', mntdir]) call(['losetup', '-d', fs]) def duplicate_by_vm(source, target, *args): # source, target should be machine names prefix = 'd_' # 1. copy (by dd) source to target source_device = '/dev/xenvg/' + prefix + source + '_hda' target_device = '/dev/xenvg/' + prefix + target + '_hda' check_call(['/bin/dd', 'bs=1M', 'conv=nocreat', 'if='+source_device, 'of='+target_device]) # 2. frob target frob_copy_simple(target_device, *args) if __name__ == '__main__': duplicate_by_vm(*sys.argv[1:])