[245] | 1 | #!/usr/bin/env python2.5 |
---|
[231] | 2 | |
---|
| 3 | import sys |
---|
| 4 | import os |
---|
| 5 | import shutil |
---|
| 6 | import tempfile |
---|
[336] | 7 | import time |
---|
| 8 | from subprocess import call, check_call, Popen, PIPE |
---|
[231] | 9 | |
---|
| 10 | def losetup(source, offset=0): |
---|
| 11 | # XXX we avoid colliding with other instances of ourself, |
---|
| 12 | # but when it comes to other loop-device users we just |
---|
| 13 | # pick a range things don't seem to use and hope... |
---|
| 14 | lockfilename = '/tmp/losetup.lock' |
---|
| 15 | os.close(os.open(lockfilename, os.O_CREAT+os.O_EXCL)) #lock |
---|
| 16 | try: |
---|
| 17 | loopdevice = None |
---|
| 18 | for i in xrange(32,60): # totally arbitrary, just looks to be unused on black-mesa |
---|
| 19 | filename = '/dev/loop%d'%i |
---|
| 20 | if 0 == len(file(filename).read(1)): |
---|
| 21 | loopdevice = filename # it's empty |
---|
| 22 | break |
---|
| 23 | if loopdevice is not None: |
---|
| 24 | call(['losetup', '-o', str(offset), loopdevice, source]) |
---|
[336] | 25 | else: |
---|
| 26 | raise RuntimeError('out of loop devices for copying VM image: too many at once?') |
---|
[231] | 27 | finally: |
---|
| 28 | os.unlink(lockfilename) #unlock |
---|
| 29 | return loopdevice |
---|
| 30 | # XX this means we can duplicate 9 at once. since it takes around 30 seconds, |
---|
| 31 | # this seems like an adequate capacity. |
---|
| 32 | |
---|
| 33 | def duplicate_lv(source, dest): |
---|
[336] | 34 | '''OBSOLETE: duplicate boot record, filesystem from LV source to LV dest |
---|
[231] | 35 | |
---|
| 36 | source, dest should be device filenames. |
---|
[336] | 37 | |
---|
| 38 | Now we just dd. Doesn't support resizing, but it's easier, |
---|
| 39 | especially if we allow the partition table to vary between images. |
---|
[231] | 40 | ''' |
---|
| 41 | # XXX this is very specific to what the etch sipb-xen-make-iso installer does. |
---|
| 42 | # XXXX also, it's specific to four gigs. |
---|
| 43 | # step 1: copy the MBR |
---|
| 44 | call(['dd', 'if='+source, 'bs=512', 'count=63', 'of='+dest]) |
---|
| 45 | # step 2: fix up partition table |
---|
| 46 | # XX actually do this; this is our opportunity to resize the filesystem |
---|
| 47 | # step 3: make the filesystem, and copy its contents in |
---|
| 48 | sourcefs = losetup(source, 32256) |
---|
| 49 | destfs = losetup(dest, 32256) |
---|
| 50 | call(['mkfs.ext3', '-b', '4096', destfs, '987989']) |
---|
[336] | 51 | tmptree = tempfile.mkdtemp('', 'auto-install.dup.', '/tmp')#yes, args backward |
---|
[231] | 52 | os.mkdir(tmptree+"/source") |
---|
| 53 | call(['mount', '-t', 'ext3', '-o', 'ro', sourcefs, tmptree+"/source"]) |
---|
| 54 | os.mkdir(tmptree+"/dest") |
---|
| 55 | call(['mount', '-t', 'ext3', destfs, tmptree+"/dest"]) |
---|
| 56 | call(['cp', '-aT', tmptree+"/source", tmptree+"/dest"]) |
---|
| 57 | # step 4: twiddle what we want to twiddle |
---|
| 58 | # XX do this |
---|
| 59 | # step 5: make the swap area |
---|
| 60 | swapfs = losetup(dest, 4046870016) |
---|
| 61 | call(['mkswap', swapfs, str(240943)]) |
---|
| 62 | # call(['losetup', '-d', swapfs]) |
---|
| 63 | print 'losetup -d '+swapfs |
---|
| 64 | # step 6: clean up. |
---|
| 65 | # XX actually unmount etc (leaving it is for debugging) |
---|
| 66 | print 'umount '+tmptree+'/source' |
---|
| 67 | call(['umount', tmptree+"/dest"]) # needed to flush writes |
---|
| 68 | print 'losetup -d '+sourcefs |
---|
| 69 | print 'losetup -d '+destfs |
---|
| 70 | |
---|
[336] | 71 | def frob_copy(target, *args): |
---|
[338] | 72 | '''UNUSED: maybe we'll use this someday; it does isolate the frobber.''' |
---|
[336] | 73 | # 1. prepare arguments volume |
---|
[245] | 74 | args_volume = prefix+target+'_args' |
---|
| 75 | args_device = '/dev/xenvg/' + args_volume |
---|
| 76 | check_call(['/sbin/lvcreate', 'xenvg', '--name', args_volume, '--size', '4K']) |
---|
| 77 | file(args_device, 'w').write('\n'.join(args) + '\n') |
---|
| 78 | |
---|
[336] | 79 | # 2. invoke frobber vm |
---|
[245] | 80 | copier_device = '/dev/xenvg/d_wert_hda' |
---|
| 81 | check_call(['/usr/sbin/xm', 'create', 'sipb-database', |
---|
| 82 | 'machine_name='+target, |
---|
| 83 | 'disks=' + ' '.join(['phy:'+copier_device+',hda,w', |
---|
| 84 | 'phy:'+target_device+',hdc,w', |
---|
| 85 | 'phy:'+args_device+',hdd,w'])]) |
---|
| 86 | |
---|
[336] | 87 | # XXX should check_call(['/sbin/lvremove', '-f', 'xenvg/'+args_volume]) |
---|
[245] | 88 | |
---|
[338] | 89 | def frob_copy_simple(target, hostname, rootpw): |
---|
[336] | 90 | """target should be an LV device filename |
---|
| 91 | |
---|
| 92 | This is highly specific to the etch install produced by sipb-xen-make-iso. |
---|
| 93 | Generalizing and customizing to other distros is left to the future... |
---|
| 94 | """ |
---|
| 95 | # 1: mount filesystem |
---|
| 96 | fs = losetup(target, 32256) |
---|
| 97 | mntdir = tempfile.mkdtemp('', 'auto-install.frob.', '/tmp') |
---|
| 98 | call(['mount', '-t', 'ext3', fs, mntdir]) |
---|
| 99 | # 2: do frobbing |
---|
| 100 | # 2a: (printf "%s\n" "$ROOTPW"; sleep .3; printf "%s\n" "$ROOTPW") |
---|
| 101 | # | /usr/sbin/chroot "$TARGET" /usr/bin/passwd root |
---|
| 102 | p = Popen(['/usr/sbin/chroot', mntdir, '/usr/bin/passwd', 'root'], stdin=PIPE) |
---|
| 103 | p.stdin.write(rootpw+'\n') |
---|
| 104 | time.sleep(1) |
---|
| 105 | p.stdin.write(rootpw+'\n') |
---|
| 106 | p.stdin.close() |
---|
| 107 | p.wait() |
---|
| 108 | os.chdir(mntdir) |
---|
| 109 | # 2b: clear generated file that has eth0's old MAC address |
---|
| 110 | # rm $TARGET/etc/udev/rules.d/z25_persistent-net.rules |
---|
| 111 | os.unlink('etc/udev/rules.d/z25_persistent-net.rules') |
---|
| 112 | # 2c: hostname. |
---|
| 113 | # XX Use nullmailer in image, not exim4. (Fewer copies of hostname.) |
---|
| 114 | # 2c1: rm /var/lib/dhcp3/dhclient.eth0.leases |
---|
| 115 | os.unlink('var/lib/dhcp3/dhclient.eth0.leases') |
---|
| 116 | # 2c2: /etc/hosts, /etc/hostname; /etc/motd? /etc/mailname? |
---|
| 117 | call(['perl', '-i', '-pe', 's/ice3.servers.csail.mit.edu/'+hostname+'/g', |
---|
| 118 | 'etc/hosts', 'etc/hostname', 'etc/motd', 'etc/mailname']) |
---|
| 119 | # 3: clean up |
---|
| 120 | os.chdir('/') |
---|
| 121 | call(['umount', mntdir]) |
---|
[338] | 122 | os.rmdir(mntdir) |
---|
[336] | 123 | call(['losetup', '-d', fs]) |
---|
| 124 | |
---|
[338] | 125 | def duplicate_by_vm(source, target, rootpw, nodd=False): |
---|
[336] | 126 | # source, target should be machine names |
---|
| 127 | prefix = 'd_' |
---|
| 128 | # 1. copy (by dd) source to target |
---|
| 129 | source_device = '/dev/xenvg/' + prefix + source + '_hda' |
---|
| 130 | target_device = '/dev/xenvg/' + prefix + target + '_hda' |
---|
[338] | 131 | if not nodd: |
---|
| 132 | check_call(['/bin/dd', 'bs=1M', 'conv=nocreat', |
---|
| 133 | 'if='+source_device, 'of='+target_device]) |
---|
[336] | 134 | # 2. frob target |
---|
[338] | 135 | frob_copy_simple(target_device, target, rootpw) |
---|
[336] | 136 | |
---|
[338] | 137 | def main(*argv): |
---|
| 138 | subcommand = argv[1] |
---|
| 139 | args = argv[2:] |
---|
| 140 | os.environ['PATH'] = '/usr/sbin:/usr/bin:/sbin:/bin' |
---|
| 141 | if subcommand == 'lvcopy': |
---|
| 142 | kwargs = {} |
---|
| 143 | if args[0] == '--nodd': |
---|
| 144 | args = args[1:] |
---|
| 145 | kwargs['nodd'] = True |
---|
| 146 | duplicate_by_vm(*args, **kwargs) |
---|
| 147 | else: |
---|
| 148 | print >>sys.stderr, argv[0]+": unknown subcommand: "+subcommand |
---|
| 149 | return 2 |
---|
| 150 | return 0 |
---|
| 151 | |
---|
[231] | 152 | if __name__ == '__main__': |
---|
[338] | 153 | sys.exit(main(*sys.argv)) |
---|