]>
git.siccegge.de Git - forks/vmdebootstrap.git/blob - vmdebootstrap
2 # Copyright 2011 Lars Wirzenius
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 class VmDebootstrap(cliapp
.Application
):
29 def add_settings(self
):
30 default_arch
= 'amd64'
32 self
.settings
.boolean(['verbose'], 'report what is going on')
33 self
.settings
.string(['image'], 'put created disk image in FILE',
35 self
.settings
.bytesize(['size'],
36 'create a disk image of size SIZE (%default)',
39 self
.settings
.string(['mirror'],
40 'use MIRROR as package source (%default)',
42 default
='http://cdn.debian.net/debian/')
43 self
.settings
.string(['arch'], 'architecture to use (%default)',
46 self
.settings
.string(['distribution'],
47 'release to use (%default)',
50 self
.settings
.string_list(['package'], 'install PACKAGE onto system')
51 self
.settings
.boolean(['enable-dhcp'], 'enable DHCP on eth0')
52 self
.settings
.string(['root-password'], 'set root password',
54 self
.settings
.string(['customize'],
55 'run SCRIPT after setting up system',
57 self
.settings
.string(['hostname'],
58 'set name to HOSTNAME (%default)',
61 self
.settings
.string_list(['user'],
62 'create USER with PASSWORD',
63 metavar
='USER/PASSWORD')
65 def process_args(self
, args
):
66 if not self
.settings
['image']:
67 raise cliapp
.AppException('You must give image filename.')
68 if not self
.settings
['size']:
69 raise cliapp
.AppException('You must give image size.')
72 self
.mount_points
= []
75 self
.create_empty_image()
76 self
.partition_image()
78 rootdev
= self
.setup_kpartx()
80 rootdir
= self
.mount(rootdev
)
81 self
.debootstrap(rootdir
)
82 self
.set_hostname(rootdir
)
83 self
.create_fstab(rootdir
)
84 self
.set_root_password(rootdir
)
85 self
.create_users(rootdir
)
86 self
.remove_udev_persistent_rules(rootdir
)
87 self
.setup_networking(rootdir
)
88 self
.install_extlinux(rootdev
, rootdir
)
89 self
.customize(rootdir
)
90 except BaseException
, e
:
91 self
.message('EEEK! Something bad happened...')
97 def message(self
, msg
):
98 if self
.settings
['verbose']:
101 def runcmd(self
, argv
, stdin
='', ignore_fail
=False, **kwargs
):
102 logging
.debug('runcmd: %s %s' % (argv
, kwargs
))
103 p
= subprocess
.Popen(argv
, stdin
=subprocess
.PIPE
,
104 stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
,
106 out
, err
= p
.communicate(stdin
)
107 if p
.returncode
!= 0:
108 msg
= 'command failed: %s\n%s\n%s' % (argv
, out
, err
)
111 raise cliapp
.AppException(msg
)
115 dirname
= tempfile
.mkdtemp()
116 self
.remove_dirs
.append(dirname
)
117 logging
.debug('mkdir %s' % dirname
)
120 def mount(self
, device
):
121 self
.message('Mounting %s' % device
)
122 mount_point
= self
.mkdtemp()
123 self
.runcmd(['mount', device
, mount_point
])
124 self
.mount_points
.append(mount_point
)
125 logging
.debug('mounted %s on %s' % (device
, mount_point
))
128 def create_empty_image(self
):
129 self
.message('Creating disk image')
130 self
.runcmd(['qemu-img', 'create', '-f', 'raw',
131 self
.settings
['image'],
132 str(self
.settings
['size'])])
134 def partition_image(self
):
135 self
.message('Creating partitions')
136 self
.runcmd(['parted', '-s', self
.settings
['image'],
138 self
.runcmd(['parted', '-s', self
.settings
['image'],
139 'mkpart', 'primary', '0%', '100%'])
140 self
.runcmd(['parted', '-s', self
.settings
['image'],
141 'set', '1', 'boot', 'on'])
143 def install_mbr(self
):
144 self
.message('Installing MBR')
145 self
.runcmd(['install-mbr', self
.settings
['image']])
147 def setup_kpartx(self
):
148 out
= self
.runcmd(['kpartx', '-av', self
.settings
['image']])
149 devices
= [line
.split()[2]
150 for line
in out
.splitlines()
151 if line
.startswith('add map ')]
152 if len(devices
) != 1:
153 raise cliapp
.AppException('Surprising number of partitions')
154 return '/dev/mapper/%s' % devices
[0]
156 def mkfs(self
, device
):
157 self
.message('Creating filesystem')
158 self
.runcmd(['mkfs', '-t', 'ext2', device
])
160 def debootstrap(self
, rootdir
):
161 self
.message('Debootstrapping')
163 if self
.settings
['arch'] == 'i386':
166 kernel_arch
= self
.settings
['arch']
167 kernel_image
= 'linux-image-2.6-%s' % kernel_arch
169 include
= [kernel_image
] + self
.settings
['package']
171 self
.runcmd(['debootstrap',
172 '--arch=%s' % self
.settings
['arch'],
173 '--include=%s' % ','.join(include
),
174 self
.settings
['distribution'],
176 self
.settings
['mirror']])
178 def set_hostname(self
, rootdir
):
179 hostname
= self
.settings
['hostname']
180 with
open(os
.path
.join(rootdir
, 'etc', 'hostname'), 'w') as f
:
181 f
.write('%s\n' % hostname
)
183 etc_hosts
= os
.path
.join(rootdir
, 'etc', 'hosts')
184 with
open(etc_hosts
, 'r') as f
:
186 with
open(etc_hosts
, 'w') as f
:
187 for line
in data
.splitlines():
188 if line
.startswith('127.0.0.1'):
189 line
+= ' %s' % hostname
190 f
.write('%s\n' % line
)
192 def create_fstab(self
, rootdir
):
193 fstab
= os
.path
.join(rootdir
, 'etc', 'fstab')
194 with
open(fstab
, 'w') as f
:
195 f
.write('proc /proc proc defaults 0 0\n')
196 f
.write('/dev/sda1 / ext4 errors=remount-ro 0 1\n')
198 def set_root_password(self
, rootdir
):
199 if self
.settings
['root-password']:
200 self
.message('Setting root password')
201 self
.set_password(rootdir
, 'root', self
.settings
['root-password'])
203 self
.message('Locking root password')
204 self
.runcmd(['chroot', rootdir
, 'passwd', '-l', 'root'])
206 def create_users(self
, rootdir
):
207 def create_user(user
):
208 self
.runcmd(['chroot', rootdir
, 'adduser', '--gecos', user
,
209 '--disabled-password', user
])
211 for userpass
in self
.settings
['user']:
213 user
, password
= userpass
.split('/', 1)
215 self
.set_password(rootdir
, user
, password
)
217 create_user(userpass
)
219 def set_password(self
, rootdir
, user
, password
):
220 encrypted
= crypt
.crypt(password
, '..')
221 self
.runcmd(['chroot', rootdir
, 'usermod', '-p', encrypted
, user
])
223 def remove_udev_persistent_rules(self
, rootdir
):
224 self
.message('Removing udev persistent cd and net rules')
225 for x
in ['70-persistent-cd.rules', '70-persistent-net.rules']:
226 pathname
= os
.path
.join(rootdir
, 'etc', 'udev', 'rules.d', x
)
227 if os
.path
.exists(pathname
):
228 logging
.debug('rm %s' % pathname
)
231 logging
.debug('not removing non-existent %s' % pathname
)
233 def setup_networking(self
, rootdir
):
234 self
.message('Setting up networking')
236 f
= open(os
.path
.join(rootdir
, 'etc', 'network', 'interfaces'), 'w')
238 f
.write('iface lo inet loopback\n')
240 if self
.settings
['enable-dhcp']:
242 f
.write('allow-hotplug eth0\n')
243 f
.write('iface eth0 inet dhcp\n')
247 def install_extlinux(self
, rootdev
, rootdir
):
248 self
.message('Installing extlinux')
251 dirname
= os
.path
.join(rootdir
, 'boot')
252 basenames
= os
.listdir(dirname
)
253 logging
.debug('find: %s' % basenames
)
254 for basename
in basenames
:
255 if re
.search(pattern
, basename
):
256 return os
.path
.join('boot', basename
)
257 raise cliapp
.AppException('Cannot find match: %s' % pattern
)
259 kernel_image
= find('vmlinuz-.*')
260 initrd_image
= find('initrd.img-.*')
262 out
= self
.runcmd(['blkid', '-c', '/dev/null', '-o', 'value',
263 '-s', 'UUID', rootdev
])
264 uuid
= out
.splitlines()[0].strip()
266 conf
= os
.path
.join(rootdir
, 'extlinux.conf')
267 logging
.debug('configure extlinux %s' % conf
)
275 append initrd=%(initrd)s root=UUID=%(uuid)s ro quiet
277 'kernel': kernel_image
,
278 'initrd': initrd_image
,
283 self
.runcmd(['extlinux', '--install', rootdir
])
284 self
.runcmd(['sync'])
285 import time
; time
.sleep(2)
288 # Clean up after any errors.
290 self
.message('Cleaning up')
292 for mount_point
in self
.mount_points
:
293 self
.runcmd(['umount', mount_point
], ignore_fail
=True)
295 self
.runcmd(['kpartx', '-d', self
.settings
['image']], ignore_fail
=True)
297 for dirname
in self
.remove_dirs
:
298 shutil
.rmtree(dirname
)
300 def customize(self
, rootdir
):
301 script
= self
.settings
['customize']
303 self
.message('Running customize script %s' % script
)
304 self
.runcmd([script
, rootdir
])
307 if __name__
== '__main__':
308 VmDebootstrap().run()