Custom Partitioning using Preseed

Simplistic Beginnings

Whether using a preseed or a manual install the default partitioning setup is to use what Debian calls "atomic". When using atomic the installer creates two partitions

  1. /boot at 250MB
  2. LVM physical volume (PV) filled to rest of disk

The one PV contains a single Volume Group (VG) which will contain two Logical Volumes (LV).

  1. $(hostname)-vg/swap_1 at the same size as your RAM
  2. $(hostname)-vg/root mounted on / filled to rest of VG

The logic is a little deeper; while swap defaults to the same size as the RAM you have it will expand up to twice the RAM. Similarly / will be at least 500MB and will expand out to fill the rest of the partition.

This is quick, easy, you don't have to worry about partitions filling up, and all together pretty scary. I'll save you the rant by linking to the ServerFault answer but I've written about some of the dangers of single partitions in the past. Suffice to say separating your system out into multiple partitions is handy and allows us to have an overall more resilient and secure system.

Baby Steps into Complexity

Debian has a preseed notion that's called "multi". Just like "atomic" it will automatically create two partitions, but the real changes happen within the VG itself. Instead of the two LVs above we'll now have 6 arranged thusly.

  1. $(hostname)-vg/swap_1 at 96MB up to twice size of installed RAM
  2. $(hostname)-vg/root mounted on / minimum of 300MB up to 350MB
  3. $(hostname)-vg/usr mounted on /usr/ minimum of 500MB up to 9000MB
  4. $(hostname)-vg/var mounted on /var/ minimum of 300MB up to 3000MB
  5. $(hostname)-vg/tmp mounted on /tmp/ minimum of 20MB up to 400MB
  6. $(hostname)-vg/home mounted on /home/ minimum of 300MB up to remaining size

How the partitions resize gets a little weird. As we can see each partition has a minimum size and a maximum size, additionally each partition has what's called a priority where priority is a number between 1 and big. During preseeding the minimum, maximum, and priority are all fed into a formula, given below, to determine the actual allocated size. All in all it's an interesting system but the formula could be more straightforward and the defaults chosen above seen much too small for my tastes. This means I need to dig in and craft my own.

Building Your Own

If you'll recall from some previous posts I've been getting Cobbler running to handle all my provisioning which is both good and annoying here. Good in that I can move the partitioning to a snippet, making it easy to customize for a specific host. Annoying in that the syntax is a little janky and requires some extra considerations for the Cheetah interpreter. To see what I mean let's look at a single partition example.

d-i partman-auto/expert_recipe string         \
    4000 800 8000  $default_filesystem        \
        $lvmok{ }                             \
        method{ format }                      \
        format{ }                             \
        use_filesystem{ }                     \
        $default_filesystem{ }                \
        mountpoint{ / }                       \
    .

The first line tells preseed that what follows is a custom partition config. Then for each partition we have to create a block that looks like the one above. Since that block contains $lvmok it will be a Logical Volume (LV) in the default VG, since we didn't specify one. The LV will be formatted using whatever the standard filesystem is, most likely ext3, and the /etc/fstab file will be configured to mount it at /. Notice the string of numbers at the beginning? Those are the minimum size (4000MB), the priority (800), and the maximum size (8000MB).

The Cheetah problem comes into play when we look at the variables. Since I'm using Cobbler the preseed will be passed through an interpreting engine so I have to treat it a little differently, much like a Puppet template file only with less explicit code. When we request the preseed file from Cobbler it will pass it through the Cheetah interpreter which will process all the linked files and pass them back up to Cobbler for display. This means the variables, $default_filesystem and $lvmok, will be evaluated as Cheetah code and almost definitely fail. The fix is easy, albeit annoying. We have to escape the dollar signs so Cheetah leaves them alone and lets debian-installer handle them at install time.

Partioning Guidance

Now that we've figured out exactly how to construct our scheme we need to decide what our partitions are going to be. My typical guidance has been to create, at minimum

  • /boot
  • /
  • /home
  • /tmp
  • /var
  • /var/log/audit

This gives you some logical separation of primary components while keeping things rather simple. I've always been rather happy with the hardening guides produced by the Center for Internet Security. Their OS guides will typically have partitioning guidance, as well as some specialized mount options. What they don't do is give you an idea as to what size to use for each partition. In the RedHat world I usually recommended

# Mount Point       Min Size (MB)    Max Size (MB)
/                   4000             8000
/home               1000             4000
/tmp                1000             2000
/var                2000             4000
swap                1000             2000
/var/log/audit       250

I'm still getting a feel for how Ubuntu does things so I don't yet know if those sizes are really the best idea. I suspect they will be but time will tell.

Creating the Snippet

Combining all that information we can concoct our partitioning snippet. The trick, of course, is getting the priority values right. I'm not convinced they're correct yet, since my / partition was less than max but my swap was max. I find that a bit less than ideal so put some effort into figuring out if different priorities work better. If you find the right ones, please let me know. I have to admit that the thought of tweaking those sounds painful.

# Disk Partitioning
# Use LVM, and wipe out anything that already exists
d-i partman/choose_partition select finish
d-i partman/confirm boolean true
d-i partman/confirm_nooverwrite boolean true
d-i partman-auto/method string lvm
d-i partman-lvm/device_remove_lvm boolean true
d-i partman-lvm/confirm boolean true
d-i partman-lvm/confirm_nooverwrite boolean true
d-i partman-auto-lvm/new_vg_name string system
d-i partman-md/device_remove_md boolean true
d-i partman-partitioning/confirm_write_new_label boolean true
d-i partman/default_filesystem string ext4

d-i partman-auto/expert_recipe string     \
  multi-cnx ::                            \
    128 512 256 ext2                      \
    \$defaultignore{ }                    \
    method{ format }                      \
    format{ }                             \
    use_filesystem{ }                     \
    filesystem{ ext2 }                    \
    mountpoint{ /boot }                   \
    .                                     \
    4000 800 8000  \$default_filesystem   \
    \$lvmok{ }                            \
    method{ format }                      \
    format{ }                             \
    use_filesystem{ }                     \
    \$default_filesystem{ }               \
    mountpoint{ / }                       \
    .                                     \
    2000 1500 4000 \$default_filesystem   \
    \$lvmok{ }                            \
    method{ format }                      \
    format{ }                             \
    use_filesystem{ }                     \
    \$default_filesystem{ }               \
    mountpoint{ /var }                    \
    .                                     \
    96 512 200% linux-swap                \
    \$lvmok{ }                            \
    \$reusemethod{ }                      \
    method{ swap }                        \
    format{ }                             \
    .                                     \
    1000 300 2000 \$default_filesystem    \
    \$lvmok{ }                            \
    method{ format }                      \
    format{ }                             \
    use_filesystem{ }                     \
    \$default_filesystem{ }               \
    mountpoint{ /tmp }                    \
    .                                     \
    1000 3000 8000 \$default_filesystem   \
    \$lvmok{ }                            \
    method{ format }                      \
    format{ }                             \
    use_filesystem{ }                     \
    \$default_filesystem{ }               \
    mountpoint{ /home }                   \
    .                                     

Priority Based Partition Size Formula

    for(i=1;i<=N;i++) {
      factor[i] = priority[i] - min[i];
    }
    ready = FALSE;
    while (! ready) {
      minsum = min[1] + min[2] + ... + min[N];
      factsum = factor[1] + factor[2] + ... + factor[N];
      ready = TRUE;
      for(i=1;i<=N;i++) {
        x = min[i] + (free_space - minsum) * factor[i] / factsum;
        if (x > max[i])
          x = max[i];
        if (x != min[i]) {
          ready = FALSE;
          min[i] = x;
        }
      }
    }