1 #!/usr/bin/env python2.5
8 from subprocess import call, check_call, Popen, PIPE
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.
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
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
32 if loopdevice is not None:
33 call(['losetup', '-o', str(offset), loopdevice, source])
35 raise RuntimeError('out of loop devices for copying VM image: too many at once?')
37 os.unlink(lockfilename) #unlock
39 # XX this means we can duplicate 9 at once. since it takes around 30 seconds,
40 # this seems like an adequate capacity.
42 def duplicate_lv(source, dest):
43 '''OBSOLETE: duplicate boot record, filesystem from LV source to LV dest
45 source, dest should be device filenames.
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.
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
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
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
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')
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'])])
95 # XXX should check_call(['/sbin/lvremove', '-f', 'xenvg/'+args_volume])
97 def frob_copy_simple(target, hostname, rootpw, *args):
98 """target should be an LV device filename
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...
103 # 1: mount filesystem
104 fs = losetup(target, 32256)
105 mntdir = tempfile.mkdtemp('', 'auto-install.frob.', '/tmp')
106 call(['mount', '-t', 'ext3', fs, mntdir])
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')
113 p.stdin.write(rootpw+'\n')
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')
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'])
129 call(['umount', mntdir])
130 call(['losetup', '-d', fs])
132 def duplicate_by_vm(source, target, *args):
133 # source, target should be machine names
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])
141 frob_copy_simple(target_device, *args)
143 if __name__ == '__main__':
144 duplicate_by_vm(*sys.argv[1:])