I’ve been meaning to post my notes on this for a while but I wanted to post some code which needs to be stripped of the proprietary work. At my job, we have a need to do multiple installs a day of ubuntu via the network and require a fairly complex lvm configuration. When done on feisty, first you need to deal with the three minutes it takes to create the lvm devices. This is a bug, check out this post about it.
Partman-auto and partman-lvm-auto configuration recipes are pretty complex to begin with. If you’re not simply formatting the whole disk, it can get really confusing, let alone figuring out the difference between partman and partman-lvm. There’s some sample code out there, but it’s hard to tell when era it’s from. Upon learning that partman-auto wasn’t going to support multiple disks, I started looking for alternatives. Thanks to cjwatson and fjp for pointing me in the right directions at times.
Finding the right hooks was the hard part. debian-installer (or d-i) is the alternative and network (pxe based) installer for ubuntu. It’s very modular and uses anna (the lightweight version of apt) specific udeb (anna’s version of debs) configurations to let d-i know in what order a specific module should be run. Basically what I did was force partman not to run, and have a shell script run instead.
Assuming that you’ve already got a pxe install going with a preseed file, use the following:
d-i preseed/early_command string wget http://server.example.com/ubuntu/config/lvm.sh -P /tmp ; chmod 755 /tmp/lvm.sh ; echo /tmp/lvm.sh installer >> /var/lib/dpkg/info/download-installer.postinst
This will download your script and set it to be executed after the base installer is downloaded. This is important because some of the install system is in the initrd, but a lot of it, partman included, is installed from a udeb after the initrd is loaded. So you can’t just hack partman from inside the initrd.
lvm.sh is a two phase script. the first is run when the base installer is setup, by the earlier call in download-installer.postinst. This phase then sets itself to be run instead of partman, allowing you to create and mount your partitions at the correct time.
Note that you should have your disk mounted as /target by the end of phase 2 as well as have your fstab configured. I’d also recommend running swapon against your swap, but keep in mind that the mkswap currently shipping will only prepare 2GB of swapspace, see bug 119900.
Theres some extra stuff in this script you won’t need. It’s also untested with my companies stuff removed. If having a bunch of lvms named ‘stuffN’ seems stupid, it’s because that’s my way of obfuscating the code so I won’t have someone from work complain. As always, YMMV.
# Manually create LVM configuration
# Partman does not currently support multi-disk lvm
# Designed to be run after download-installer but before partman-base
# This allows us to modify partman-base.postinst after it's been dropped in by anna
# Partman appears to be entirely an external program, removing the call to partman from partman-base.postinst prevents it from running.
case "$1" in
# we should have d-i downloaded by now.
# partman comes in a udeb from the network so we have to hook here
# and replace the partman-base.postinst file
sed -i 's/partman/\/tmp\/lvm.sh partman/' /var/lib/dpkg/info/partman-base.postinst
logger lvm.sh modified partman-base.postinst
# do filesystem stuff: detect our config, fdisk, lvms, mount /target
logger lvm.sh partition configuration starting
# FIXME: This is going to be really dirty to handle our configurations. More work will need to be done later.
# case1: sda: 1171842048 hda: 125056
# case2: sda: 976519168 hda: 58605120
# case3: sda: 732389376 hda:
SIZE_SDA=`sed -n 's/.* \([0-9]*\) sda$/\1/p' < /proc/partitions`
SIZE_HDA=`sed -n 's/.* \([0-9]*\) hda$/\1/p' < /proc/partitions`
echo sda: $SIZE_SDA hda: $SIZE_HDA
# pvcreate filters (ignored by filtering) if the there's a partition table
dd if=/dev/zero of=/dev/sda bs=512 count=1
# check for separate physical boot drive
if [ $SIZE_HDA ] ; then
# we have the boot disk create a primary partition
echo ",,83" | sfdisk /dev/hda
pvcreate -ff -y /dev/sda
# no separate boot drive
echo -e ",256,83\n,,8e" | sfdisk -uM /dev/sda
pvcreate -ff -y /dev/sda2
mke2fs -q $BOOT
vgcreate -s 256M system $LVM
if [ $SIZE_SDA -gt 700000000 ] ; then
lvcreate -L 20G -n stuff1 system
lvcreate -L 20G -n stuff2 system
lvcreate -L 8G -n swap system
lvcreate -L 20G -n stuff3 system
lvcreate -L 200G -n stuff4 system
lvcreate -L 200G -n stuff5 system
lvcreate -L 200G -n stuff6 system
for fs in stuff1 stuff2 stuff3 stuff4 stuff5 stuff6 ; do mkfs.reiserfs -q /dev/system/$fs 1>/dev/null; done
# FIXME: swap too big for vmware
lvcreate -L 8G -n swap system
lvcreate -l `pvdisplay | sed -n 's/Free PE \([0-9]*\)/\1/p'` -n config1 system
mkfs.reiserfs -q /dev/system/stuff1 1>/dev/null
# setup common swap
# Create directory structure
mount /dev/system/stuff11 /target -treiserfs
mount $BOOT /target/boot -text2
if [ $COMPLEXFS ] ; then
mkdir -p /target/stuff2
mkdir -p /target/stuff3
mkdir -p /target/stuff4
mount /dev/system/stuff2 /target/stuff2
# Create fstab
echo \# /etc/fstab: static file system information. > /target/etc/fstab
echo \# >> /target/etc/fstab
" >> /target/etc/fstab
echo $BOOT /boot ext2 defaults 1 2 >> /target/etc/fstab
echo /dev/system/stuff1 / reiserfs acl,user_xattr 1 1 >> /target/etc/fstab
if [ $COMPLEXFS ] ; then
echo /dev/system/stuff2 /stuff2 reiserfs acl,user_xattr 1 2 >> /target/etc/fstab
echo /dev/system/stuff3 /stuff3 reiserfs acl,user_xattr 1 2 >> /target/etc/fstab
echo /dev/system/stuff4 /stuff4 reiserfs acl,user_xattr 1 2 >> /target/etc/fstab
echo /dev/system/swap none swap sw 0 0 >> /target/etc/fstab
echo proc /proc proc defaults 0 0 >> /target/etc/fstab
# Secret udev rules hack for network cards
mkdir -p /target/etc/udev/rules.d
echo \# on board e100 > /target/etc/udev/rules.d/50-network.rules
echo KERNELS==\"0000:00:06.0\", NAME=\"eth2\" >> /target/etc/udev/rules.d/50-network.rules
echo \# on board tg3 \(2x1000\) >> /target/etc/udev/rules.d/50-network.rules
echo KERNELS==\"0000:02:09.0\", NAME=\"eth0\" >> /target/etc/udev/rules.d/50-network.rules
echo KERNELS==\"0000:02:09.1\", NAME=\"eth1\" >> /target/etc/udev/rules.d/50-network.rules
echo $0: This script is destructive and should only be run as part of the debian-installer process