Overview

Q: What does this doc do?
A: Saves you a bit of headache trying to get an initramfs built that will boot your encrypted root volume

Q: There's already plenty of doc, why bother?
A: This isn't a wholesale substitute for any of the existing documentation. However what I've found in the existing documentation is that you either find excessive information, that tries to solve every potential esoteric case a user might come across, and in the process making it a tad confusing and overwhelming for the 99% of users who just want a fairly simplistic setup. There are a dozen docs, some just on initramfs in general, some on luks/dm-crypt, some on lvm+dm-crypt+luks, all of which have a different way of doing things, all of which provide different examples, never really bothering to stop and give a simple example, or explain why something was done in a particular way in their example.

Q: Can I skip the other docs and just read this?
A: Maybe. Probably not. You should be somewhat familiar with the basics of cryptsetup (e.g. you need to know how to luksOpen, and if you haven't done so already, luksFormat - those aren't covered here), know how to mount things. Basically, after you've read all of the other doc and sat there banging your head trying to figure out how to modify their examples, failed, tried using some automated tool, failed, and are sick of it all, you probably have enough base knowledge to pick this up easily. Probably the best way to use this doc, is to go through the handbook, partition your disks, go through the bits of dm-crypt+luks doc, get your encrypted volumes (including root) formatted and set up, read through the Gentoo Initramfs wiki, see how much of it you can get your head around, then keep this page open in one tab, the Initramfs page open in another.

 

Existing Problems

Automated Tools: There are a ton of automated tools out there which will supposedly do all of the initramfs creation for you.

  • Embedded Initramfs: documented in the Initramfs wiki page. Sort of. They don't tell you how your grub.conf should look, and the troubleshooting part didn't cover off the error I kept getting every...single...time. "unable to execute init". It could have been that I needed to 'make clean' after remembering to chmod +x the 'init' script and not just 'make', so maybe it wasn't executable in the embedded initramfs. But, I could swear I've tried that before with the same result, and since the doc on this is pretty sparing, inclusive of what to put in grub.conf, I consider this feature totally non-functional. I've read the doc shipped with the kernel, read the Initramfs pages for many distributions, none of it got me a functional initramfs in spite of their instructions, so I consider this 'broken'.
  • Genkernel: This was epic fail in general when I tried it back in 2003 or thereabouts. Not just my take, this was the general consensus. Supposedly it had been improved as of late, so I tried it again, looking to take advantage of some of its supposed automagic support for making a luks-capable initramfs with no real interaction. As luck would have it, this was still epic fail, I picked apart the initramfs it made...it had absolutely nothing for opening crypto volumes. Brilliant.
  • Mkinitrd: The results were pretty much the same as with genkernel, and it's not really documented all that well.
  • Mkinitcpio: This is actually a really handy tool shipped with Arch, that handles things like lvm and crypto via a series of 'hooks' that are basically just little functions and whatnot that get built into the initramfs, get called by /init depending on the command-line passed to the kernel. Not a fault of of the tool, but I tried porting this over to Gentoo, and it just wasn't feasible. Thing is, the 'crypto' hook, for example, wasn't included with the mkinitcpio package, but rather was included with the cryptsetup package. So while I can download the mkinitcpio base sources from Arch's servers, in addition to having to maintain a bunch of patches to 'gentoo-ize' their base mkinitcpio stuff (consolekit was one, if memory serves, and they used udev in their initramfs too), I would have to pull down their cryptsetup pacage, extract that, snag the 'hooks' file, copy it over, merge it, or, maintain the file myself and host it on my server - just not a particularly tenable situation. Cool tool, too much work for me to port it to gentoo.

Documentation: As is the case with the 'tools' that are supposed to make this automatic, there are loads of pieces of documentation that, if you piece them together, have a huge chunk of useful information, but none of them are really straightforward enough or organized enough to give you a functional end result. They may be written for an audience beyond your (and certainly my) level of comfort and knowledge, they may have poorly documented examples or none whatsoever, or a combination of all of these. Either way, no single document gave me the information I need in a clear, concise, easy-to-parse format or manner rather. This is not to rant and rave and complain about the documentation, really, never blame the tool for your inability to understand the tool, but at the same time if you can't figure out how to use the tool, it's not particularly useful to you now is it.

  • Initramfs wiki: Probably the most useful piece of doc, but the scope is so broad, they don't really sufficiently cover off all the areas you need for functional root crypto. In some places you can't tell if something is relevant to crypto or not, needed, etc, and in the places they name as relevant to crypto, they're lacking huge chunks of information you'll have to fish out of other docs, that have their own info on making an initramfs, and said other docs provide hugely long confusing examples that aren't really explained.
  • The dozen DM-Crypt and/or LUKS docs: Too much information, none of it really explained in detail, samples and examples that try to cover off every single possible contingency, resulting in so much extraneous data you can't really put pieces together and figure out the very simple pieces you need just for a standard encrypted root, unlocked with a passphrase and mounted with a simple small initramfs. Have a gander at the /init samples on some of these, you will invariably get lost - these are examples for coders, people who can read things that are laid out like a proper piece of source code, not for a random Joe like you and me
Purpose and Scope

In case it hasn't been made abundantly clear already, this is NOT a wholesale replacement for at least a vague familiarity with encrypted volumes in general, nor will reading this along be a sufficient replacement for the other existing wikis should you wish to do anything more complex than have a standard root volume, encrypted with a passphrase provided at boot time. This doesn't cover off using key files, or dmcrypt setup - there's a wiki and plenty of doc for once you've at least gotten your system to unlock and mount root, and well, start itself up.

You should understand what 'mount' means to use this, you should have an idea of device nodes (for example, you need to know if your root partition is sda1, sda2, sda3, whatever). You should have already done a luksFormat of root, made a file system, mounted it, and all of that. You should know how to configure a custom kernel that works with your hardware, what file systems you need, and so forth. This document doesn't explain how to configure your kernel to support dm-crypt, how to enable initrd support, and what have you. This is already covered in detail within the Gentoo Initramfs Wiki Page.

If you already know how to set up encrypted volumes, know how to mount things, configure a kernel, and have a general understanding of the existing docs for this sort of thing, you should be fine. If you've tried getting this working multiple times, and failed, you should be just fine as you'll have picked up enough in your failed attempts to know how to manage here.

Getting Started

Before you get started actually hammering away at your keyboard typing commands, it's probably useful to point out in general how an initramfs is laid out, and give a crude explanation of how things interact. I may be technically wrong on some of these explanations, but aside from being wrong bruising my ego, it shouldn't have any negative effect on the outcome of you following this guide - things should still work for you.

Relevant Pieces

Generally speaking, an initramfs is going to contain a small, minimalist rather, directory structure with a few key binaries and device nodes, which are utilized within a script named 'init' which will be located at the root of this directory structure. Basically, when you boot up your kernel, if you tell your kernel to use an initramfs (specified with the initrd= parameter in grub.conf), it will unpack this miniature directory structure and all of its binaries into memory, then execute '/init', which will make use of these binaries and device nodes to do things like, in our case, run cryptsetup to unlock your root volume, do the initial read-only mount of your root volume so that its contents can be read and processed, and go through the on-disk startup routine.

Think of it along the lines of someone making a very very minimal linux distro that contains the bare minimum number of directories for all of the small few programs it needs to run, contains just the one or two programs, as well as any files they depend on such as device nodes (e.g. /dev/tty, /dev/console, /dev/urandom)

Continuing with this example, say all that you want this minimalist Linux distribution to do, every time, is cat the contents of a file named /hello.txt to the console whenever the system starts up. So what do you do? You make the character device /dev/console, copy over cat, copy over any files cat needs, and write a script that will run every time this minimalist Linux distro loads, which will 'cat /hello.txt'. Now, to make it portable, you decide to tar up this entire minimalist Linux distro into an archive. Really this is no different from how an initramfs functions.

In the case of an initramfs, the kernel will unpack the initramfs archive, and by default, it will automatically look for an executable script inside the archive at the root named 'init'. You could of course name it 'ralph', but then you'd have to put 'init=/ralph' within grub.conf to override the default name of 'init'.

Now in order for all of the commands in 'init' to be successful, for the ones that require certain device nodes, those device nodes need to be present. For example, cryptsetup may need /dev/urandom, so that has to be there. If you don't do a static cryptsetup binary, you need to copy all of the libraries it's linked against into this file system at whichever place cryptsetup expects them. To that same end, one common point of confusion, is that when writing 'init' and telling it what to do, the paths specified inside 'init' are relative to your initramfs directory structure, and not the structure of what you have installed on your hard disk! So even if your hard disk has a /dev/sda1, for example, if there's no device node for /dev/sda1 inside your initramfs' directory structure, you can't write any commands into 'init' that depend on /dev/sda1, as the initramfs doesn't even know about your hard disk at this point (well, it does, but pretend it doesn't for our purposes here).

Directory Structure

Following this is an example directory structure for an initramfs. Again, think of it as a minimalist Linux distribution, unpacked to ram, used to do a few small tasks, then discarded. Full steps for making the directory structure, copying over files, and so forth, can be found later on this document. So let's look at this example directory structure - the paths should look familiar if you read the Gentoo Initramfs wiki page:

Devtmpfs Users:
$ pwd
/usr/src/initramfs

meat@vunnable /usr/src/initramfs $ find . |xargs file 
.:                          directory
./sbin:                     directory
./sbin/cryptsetup:          ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, for GNU/Linux 2.6.9, stripped
./bin:                      directory
./bin/busybox:              ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, for GNU/Linux 2.6.9, stripped
./mnt:                      directory
./mnt/root:                 directory
./dev:                      directory
./root:                     directory
./etc:                      directory
./lib:                      directory
./init:                     a /bin/busybox sh script text executable
./proc:                     directory
./sys:                      directory
			
Without Devtmpfs:
meat@vunnable /usr/src/initramfs $ find . |xargs file 
.:                          directory
./sbin:                     directory
./sbin/cryptsetup:          ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, for GNU/Linux 2.6.9, stripped
./bin:                      directory
./bin/busybox:              ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, for GNU/Linux 2.6.9, stripped
./mnt:                      directory
./mnt/root:                 directory
./dev:                      directory
./dev/sda1:                 block special
./dev/sda2:                 block special
./dev/null:                 character special
./dev/tty:                  character special
./dev/urandom:              character special
./dev/random:               character special
./dev/console:              character special
./root:                     directory
./etc:                      directory
./lib:                      directory
./init:                     a /bin/busybox sh script text executable
./proc:                     directory
./sys:                      directory
			
Files Needed

If you look at the directory listing output above, you will note it is made up of five different things

  • directory: these are made just as you'd make any other directory, with mkdir
  • ELF executable, statically linked: this will simply be busybox, and cryptsetup. Both are available in portage, both can be built statically by simply setting USE="static" for these packages (via /etc/portage/package.use, setting this in make.conf will mean ANY package that supports that USE flag is statically linked, which you don't want; you just need these two statically linked, so that you dont have to resolve a bunch of dependencies for these files and copy tonnes of garbage they're linked against into the initramfs directory structure. As a side note, the cryptsetup pacage also includes the dmcrypt init script, as well /etc/conf.d/dmcrypt, where you specify settings to be used for any other encrypted volumes you might have besides root
  • block special: whichever install cd, or usb, or whatever image you're using, should provide block device files under /dev. You can easily enough copy these into your initramfs directory structure, as you'll see below when I start giving you commands to regurgitate. You will need these for the initial temporary mount of your root vol. Only needed if you don't build devtmpfs support into your kernel
  • character special: character devices such as /dev/console and /dev/urandom. These too, should be provided by either the stage tarball, or whichever install medium your using. As with the block device files, you can simply copy these over into your initramfs directory structure. You will need, for example, /dev/urandom, in order for cryptsetup to work. Only needed if you don't build devtmpfs support into your kernel
  • a /bin/busybox sh script text executable: This is the 'init' script. You create this from scratch, and this is the script that does the leg work in running cryptsetup, which will ask you for a password to unlock the device, then doing the initial mounting of your encrypted root as soon as it's unlocked. This is really the meat of this document, and where I think other documentation either provides too much code with too little commenting, or too little code with heaps of comments. I'll provide what is, in my estimation, a simplistic, small but fully functional code sample below, which is heavily commented
Putting It All Together

So by this point, you're probably tired of reading fluff. Or did you even read the fluff? Forget the fluff, just give me the commands to type dammit! Since you're twisting my arm, I suppose I can accommodate. If you just skimmed through the rest of this doc, or just skipped over every other section, this is the section you need to pay attention to, especially when it comes to discussions on making the 'init' file/script.

Making Directories

Before we can begin copying over files, we need to set up the directory structure. You can use the same directory structure detailed in the Initramfs wiki, there's nothing new in here that isn't covered there, but in case you don't have that open in another tab, I include it below. You don't have to do this on the machine you're configuring - I did this on my laptop, bundled it up, copied it to my thumb drive, moved it to the new system where I'm setting this up - it doesn't matter where you're doing this, so long as you can bundle it up correctly (instructions to follow in a few!) and move it to /boot. And remember, as mentioned before, once the initramfs is loaded, all paths are relative to the root of the initramfs, so it doesn't matter what you call the directory where you start your work on making all of these directories; you're just going to cd to it later, and end up packaging everything up relative to the root of that anyway:

mkdir /usr/src/initramfs
cd /usr/src/initramfs
mkdir /usr/src/initramfs/bin
mkdir /usr/src/initramfs/lib
mkdir /usr/src/initramfs/dev
mkdir /usr/src/initramfs/etc
mkdir -p /usr/src/initramfs/mnt/root
mkdir /usr/src/initramfs/proc
mkdir /usr/src/initramfs/root
mkdir /usr/src/initramfs/sbin
mkdir /usr/src/initramfs/sys
Copying Character Devices

NOTE: If you compiled your kernel with devtmpfs support, you can skip this section. These character devices will be created by init when you mount the devtmpfs at /dev

Now we need to copy over the character devices things like cryptsetup and busybox are going to need. Note the -a switch applied to the 'cp' command. If you don't know why this is relevant, see 'man cp'. If you don't think this is relevant, do it anyway. If you forget this switch and your machine catches fire, don't come crying to me:

cp -a /dev/null /usr/src/initramfs/dev/
cp -a /dev/console /usr/src/initramfs/dev/
cp -a /dev/tty /usr/src/initramfs/dev/
cp -a /dev/random /usr/src/initramfs/dev/
cp -a /dev/urandom /usr/src/initramfs/dev/
Copying Block Devices

NOTE: If you compiled your kernel with devtmpfs support, you can skip this section. These block devices will be created by init when you mount the devtmpfs at /dev

Before we can unlock the encrypted root, our initramfs is going to need somewhere to put it, so we need to copy over the block device that corresponds to our root partition. My root partition is /dev/sda2 (which gets unlocked via cryptsetup as /dev/mapper/root), so I'm copying over /dev/sda2 to the directory where I have all of the other initramfs files. I actually copied over sda1 first, because when I was partitioning this thing, I wasn't sure if I wanted root to be sda1, or sda2, so I copied both, figuring my initramfs should work either way (assuming I write my 'init' correctly). Again, note the switch supplied to 'cp', as well note you don't have to do both, only the one that corresponds to your root partition - I show both, because I typed both:

cp -a /dev/sda1 /usr/src/initramfs/dev/
cp -a /dev/sda2 /usr/src/initramfs/dev/
Copying Binaries

At this point you should have all of the directories and screwy special devices under /dev that your binaries are going to need, so you can begin copying those over. Busybox will provide 'mount', and a basic shell, cryptsetup will provide, well, cryptsetup, used for unlocking the volume and asking you for a password:

cp -a /bin/busybox /usr/src/initramfs/bin/busybox
cp -a /sbin/cryptsetup /usr/src/initramfs/sbin/cryptsetup
Done and done. Easy enough eh?
Making Simple 'init'

So now we have all of the tools, files, directories, binaries, that we need in order to get the root volume unlocked and mounted. All that's left is fashioning a script that will run these tools and binaries in the correct order to get things unlocked and mounted. This should be easy enough if you have a gander at the example below. By default, the kernel looks for a file named 'init' to execute - init is the glue that pieces all of these binaries and commands and everything else together. Its creation is probably the biggest stumbling block I personally hit, and the main reason for my writing this document - it takes far less to have a functional init than the other documentation tells you, and this contains no fancy shell syntax, just a handful of 'mount' commands, plus one reference to cryptsetup. The script is heavily commented, so I won't editorialize too much before and after:

#!/bin/busybox sh

# dropping to a rescue shell lets you attempt to mount, cryptsetup, etc, by
# hand at least. Below, I call the rescue_shell function if mounting root fails.
# It must be defined prior to its call, so we do it up top. 
rescue_shell() {
        echo "Something went wrong. Dropping you to a shell."
                busybox --install -s
        exec /bin/sh
}

# temporarily mount proc and sys
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev #only do this if you've built devtmpfs support into your kernel

#do your stuff here, e.g. lvm, cryptsetup, whatever

# [crypt]

# adjust 'sda2' below to match your root device. 
# the below makes the devmapper file /dev/mapper/root. If you used
# "crypt-root" as your name, change the last string 'root' to 'crypt-root'
# this command should be fairly familiar, as you will have run it when you 
# initially set up the crypto on your disk. 

cryptsetup -T 5 luksOpen /dev/sda2 root 

# mount root fs read only on /mnt/root, one of the directories in the initramfs, which you should have
# created if you followed this guide, or fail back to busybox shell so you can at least
# attempt to do some of this stuff manually. 

mount -o ro /dev/mapper/root /mnt/root || rescue_shell

#clean up. The init process will remount proc sys and dev later
umount /proc
umount /sys
umount /dev

# boot and do switch_root nonsense. Similar to a chroot I suppose, actually this
# whole process is similar to mounting things and chrooting for a gentoo base install,
# only in this case it's a script inside your initramfs doing it - not exactly, but close enough

exec switch_root /mnt/root /sbin/init
Once you've adjusted this to your liking, and saved the file, DO NOT FORGET TO GO BACK IN AND 'chmod +x init' or, if you used the same path as me, 'chmod +x /usr/src/initramfs/init', else init can't be executed and your boot will fail with a panic!
Adding Extra Functionality

The above init and initramfs structure give you a very basic set of functionality, but hopefully do so in a way that, should your needs for an initramfs be different (e.g. creating one for lvm or raid) you can do so with minimal modification. There are two main pieces of this that you may want to modify (still excluding suspend and hibernate for the time being):

  • Adding LVM or RAID support: In order to do this, you would need to copy the lvm binaries, for example, into the initramfs structure before bundling. If you need vgscan, the binary required for doing vgscan needs to be in the initramfs structure, and you need to include a line in your 'init' that does the requisite vgscan. Need to do mdadm? Same story.
  • Parsing command line arguments: You will note in my above 'init' example, everything is hard-coded, such as which partition is root. You will also notice below, in my grub setup, I don't pass any command line arguments to the kernel, because again, everything is already hard-coded into init. What if you want to specify command-line settings though? Any arguments you put into grub.conf on the 'kernel' line will be contained within /proc/cmdline, and should you need to do so, once /proc is mounted you can parse out /proc/cmdline, store its different components/arguments as variables, and modify the above init to mount $variable instead of hard-coding e.g. your root volume. This adds an extra layer of complexity, and my script-fu isn't particularly great, so I'll avoid including it in this doc (a very detailed example is already provided on the initramfs wiki page)
Bundling Into Initramfs Archive

The heavy lifting is done. We've done all of the bits where you actually have to use your brain. Now, all you have to do is bundle up all of these files and directories into a cpio archive that the kernel can understand, gzip it, move it over to boot, pass it on the kernel command line via grub.conf. This is easy enough to do, and the steps are covered in other documents. You can read more about bundling it on the Gentoo wiki. I've copied these commands from there, and you may have to emerge a handful of packages in order to get the binaries needed to make a cpio archive - again, this is already covered on the initramfs wiki.

cd /usr/src/initramfs/
find . -print0 | cpio --null -ov --format=newc | gzip -9 > /boot/initramfs.cpio.gz
That's it. You're done. Assuming you've followed my instructions, all you need to do is configure grub.conf like so (adjusting file names where appropriate)
title Gentoo (2.6.36-zen2)
root (hd0,0)
kernel /kernel-zen
initrd /initramfs.cpio.gz
...and Robert's your father's brother. You should be asked for a password early on in the boot process, and unless you screwed something up horribly, your system should start to boot up happily. Any other volumes to be decrypted will be handled via the init system (not to be confused with the 'init' script we wrote) parsing /etc/conf.d/dmcrypt and acting accordingly. The only tricky one is root, because the file system isn't readable upon initial boot if it's encrypted, but we've gone and kicked that one in the nuts by this point haven't we.

Questions? Comments? I mean, I hope this is helpful and all, but there's a reason I haven't left any contact details here. Enjoy!

--cach0rr0