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