1 | #!/usr/bin/env python2.5 |
---|
2 | |
---|
3 | import sys |
---|
4 | import os |
---|
5 | import shutil |
---|
6 | import tempfile |
---|
7 | import time |
---|
8 | from subprocess import call, check_call, Popen, PIPE |
---|
9 | |
---|
10 | # TODO: |
---|
11 | # . pick a loop device sanely |
---|
12 | # x adapt size of partition to fit volume |
---|
13 | # . fix hostname, root password |
---|
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]) |
---|
34 | else: |
---|
35 | raise RuntimeError('out of loop devices for copying VM image: too many at once?') |
---|
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): |
---|
43 | '''OBSOLETE: duplicate boot record, filesystem from LV source to LV dest |
---|
44 | |
---|
45 | source, dest should be device filenames. |
---|
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. |
---|
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']) |
---|
60 | tmptree = tempfile.mkdtemp('', 'auto-install.dup.', '/tmp')#yes, args backward |
---|
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 | |
---|
80 | def frob_copy(target, *args): |
---|
81 | # 1. prepare arguments volume |
---|
82 | args_volume = prefix+target+'_args' |
---|
83 | args_device = '/dev/xenvg/' + args_volume |
---|
84 | check_call(['/sbin/lvcreate', 'xenvg', '--name', args_volume, '--size', '4K']) |
---|
85 | file(args_device, 'w').write('\n'.join(args) + '\n') |
---|
86 | |
---|
87 | # 2. invoke frobber vm |
---|
88 | copier_device = '/dev/xenvg/d_wert_hda' |
---|
89 | check_call(['/usr/sbin/xm', 'create', 'sipb-database', |
---|
90 | 'machine_name='+target, |
---|
91 | 'disks=' + ' '.join(['phy:'+copier_device+',hda,w', |
---|
92 | 'phy:'+target_device+',hdc,w', |
---|
93 | 'phy:'+args_device+',hdd,w'])]) |
---|
94 | |
---|
95 | # XXX should check_call(['/sbin/lvremove', '-f', 'xenvg/'+args_volume]) |
---|
96 | |
---|
97 | def frob_copy_simple(target, hostname, rootpw, *args): |
---|
98 | """target should be an LV device filename |
---|
99 | |
---|
100 | This is highly specific to the etch install produced by sipb-xen-make-iso. |
---|
101 | Generalizing and customizing to other distros is left to the future... |
---|
102 | """ |
---|
103 | # 1: mount filesystem |
---|
104 | fs = losetup(target, 32256) |
---|
105 | mntdir = tempfile.mkdtemp('', 'auto-install.frob.', '/tmp') |
---|
106 | call(['mount', '-t', 'ext3', fs, mntdir]) |
---|
107 | # 2: do frobbing |
---|
108 | # 2a: (printf "%s\n" "$ROOTPW"; sleep .3; printf "%s\n" "$ROOTPW") |
---|
109 | # | /usr/sbin/chroot "$TARGET" /usr/bin/passwd root |
---|
110 | p = Popen(['/usr/sbin/chroot', mntdir, '/usr/bin/passwd', 'root'], stdin=PIPE) |
---|
111 | p.stdin.write(rootpw+'\n') |
---|
112 | time.sleep(1) |
---|
113 | p.stdin.write(rootpw+'\n') |
---|
114 | p.stdin.close() |
---|
115 | p.wait() |
---|
116 | os.chdir(mntdir) |
---|
117 | # 2b: clear generated file that has eth0's old MAC address |
---|
118 | # rm $TARGET/etc/udev/rules.d/z25_persistent-net.rules |
---|
119 | os.unlink('etc/udev/rules.d/z25_persistent-net.rules') |
---|
120 | # 2c: hostname. |
---|
121 | # XX Use nullmailer in image, not exim4. (Fewer copies of hostname.) |
---|
122 | # 2c1: rm /var/lib/dhcp3/dhclient.eth0.leases |
---|
123 | os.unlink('var/lib/dhcp3/dhclient.eth0.leases') |
---|
124 | # 2c2: /etc/hosts, /etc/hostname; /etc/motd? /etc/mailname? |
---|
125 | call(['perl', '-i', '-pe', 's/ice3.servers.csail.mit.edu/'+hostname+'/g', |
---|
126 | 'etc/hosts', 'etc/hostname', 'etc/motd', 'etc/mailname']) |
---|
127 | # 3: clean up |
---|
128 | os.chdir('/') |
---|
129 | call(['umount', mntdir]) |
---|
130 | call(['losetup', '-d', fs]) |
---|
131 | |
---|
132 | def duplicate_by_vm(source, target, *args): |
---|
133 | # source, target should be machine names |
---|
134 | prefix = 'd_' |
---|
135 | # 1. copy (by dd) source to target |
---|
136 | source_device = '/dev/xenvg/' + prefix + source + '_hda' |
---|
137 | target_device = '/dev/xenvg/' + prefix + target + '_hda' |
---|
138 | check_call(['/bin/dd', 'bs=1M', 'conv=nocreat', |
---|
139 | 'if='+source_device, 'of='+target_device]) |
---|
140 | # 2. frob target |
---|
141 | frob_copy_simple(target_device, *args) |
---|
142 | |
---|
143 | if __name__ == '__main__': |
---|
144 | duplicate_by_vm(*sys.argv[1:]) |
---|