Install Debian on USB

From FlimzyWiki

Jump to: navigation, search

Contents

Prepare the USB media

Perform these steps from an installed Linux system, and *not* from the chroot prepared above.

Create a single ext2 partition on the USB drive:

foo:~/# fdisk /dev/sda

Command (m for help): d
Selected partition 1

Command (m for help): n
Command action
   e   extended
   p   primary partition (1-4)
p
Partition number (1-4): 1
First cylinder (1-1015, default 1): 
Last cylinder or +size or +sizeK (1-1015, default 1015):
Using default value 1015

Command (m for help): a
Partition number (1-4): 1

Command (m for help): p

Disk /dev/sda: 1031 MB, 1031798784 bytes
32 heads, 62 sectors/track, 1015 cylinders
Units = cylinders of 1984 * 512 = 1015808 bytes

   Device Boot      Start         End      Blocks   Id  System
/dev/sda1   *           1        1015     1006849   83  Linux

Command (m for help): w
The partition table has been altered!

Calling ioctl() to re-read partition table.

In short, create a Linux (type 83) partition that is bootable. Easy enough, right?

If you own a computer with an old BIOS take care that your partition does not exceed 512 MByte (1023 cylinder limit). Otherwise you will face the grub error 18 as you try to boot.

Create a filesystem on the USB media:

foo:~/# mkfs -t ext2 /dev/sda1

Build a Debian chroot

Prepare a work directory, and mount the USB media under the working directory as /boot:

foo:~# mkdir -p /mnt/chroot/boot
foo:~# mount /dev/sda1 /mnt/chroot/boot

From Debian Etch

Install Debian:

foo:~# debootstrap --arch i386 etch /mnt/chroot

From Debian Sarge

Install Debian:

foo:~# debootstrap sarge /mnt/chroot

Create /etc/apt/sources.list to point to etch:

deb http://ftp.us.debian.org/debian/ etch main

Then dist-upgrade:

foo:~# chroot /mnt/chroot
foo:/# apt-get update
foo:/# apt-get dist-upgrade
foo:/# exit

Then continue the process as normal.

Configure Locales

Chroot to the new directory, and install the locales we want (to avoid annoying errors while installing future packages), and remove unwanted locales to save space. At minimum, you probably want to generate and keep en_US ISO-8859-1, en_US.ISO-8859-15 ISO-8859-15 and en_US.UTF-8 UTF-8.

foo:~# chroot /mnt/chroot
foo:~# apt-get install locales
foo:~# dpkg-reconfigure locales
foo:~# apt-get install localepurge
foo:~# localepurge

Install kernel & modules

You may be asked if you want to abort the install due to the kernel version. Since you are in a chroot, this is not as dangerous as debconf thinks, so do NOT abort!

foo:~# apt-get install linux-image-2.6.18-5-686 unionfs-modules-2.6.18-5-686 squashfs-modules-2.6.18-5-686
foo:~# depmod 2.6.18-5-686

Install essential tools

Install other necessary packages, including my customized version of casper (available at http://aslan.flimzy.com/casper/) and do some basic configuration

foo:~# apt-get install unionfs-tools squashfs-tools grub casper
foo:~# user-setup
foo:~# cd /tmp
foo:~# wget http://aslan.flimzy.com/casper/casper_1.81+nymgy-2_i386.deb
foo:~# dpkg -i casper_1.81+nymgy-2_i386.deb

Install optional tools

These are some tools I like to use. You may have other favorite editors or tools you want.

foo:~# apt-get install joe screen ssh less

Remove unwanted packages

Remove some packages you don't need or want:

foo:~# dpkg --purge vim-tiny vim-common tasksel tasksel-data ed nano laptop-detect dmidecode
foo:~# dpkg --purge liblzo2-2 libconsole libsigc++-1.2-5c2 libldap-2.3-0 libgdbm3 man-db
foo:~# dpkg --purge info manpages dgroff-base libsasl2 cyrus-sasl2-doc

If you don't need DHCP:

foo:~# dpkg --purge dhcp3-client dhcp3-common

I'm a bit more anal about wasted space than you might be, so I like to remove these packages as well:

foo:~# dpkg --purge aptitude libsigc++-2.0-0c2a

Edit/Create config files

/etc/network/interfaces

# Used by ifup(8) and ifdown(8). See the interfaces(5) manpage or
# /usr/share/doc/ifupdown/examples for more information.
auto lo
iface lo inet loopback

auto eth0
iface eth0 inet dhcp

/etc/hostname

Create this file with an appropriate hostname in it.

/etc/hosts

127.0.0.1 localhost

Add a line for your specific hostname/IP address if you wish.

/etc/fstab

proc            /proc   proc    defaults        0       0
unionfs         /       unionfs rw              0       0
/dev/sda1       /boot   ext2    rw              0       0

NOTE: Even if your USB drive is not /dev/sda1 on the machine you're building this on, it likely will be on the target machine when booted, so you probably want this to read /dev/sda1 anyway.

If you are not using a USB device (perhaps a CF card in an IDE adaptor), however, this device name _could_ very well be different, in which case you'll want to edit accordingly.

Other

Create any other custom configurations necessary, such as updating /etc/apt/sources.list and /etc/resolv.conf.

Create Support Scripts

/etc/initramfs-tools/hooks/casper_custom

Available [1].

#!/bin/sh -e

PREREQS="casper"

# Output pre-requisites
prereqs()
{
       echo "$PREREQS"
}

case "$1" in
    prereqs)
       prereqs
       exit 0
       ;;
esac

PRECLEANUP="02etc_casper_conf 10adduser 13swap 14locales 15autologin "
PRECLEANUP+="18hostname 19keyboard 20xconfig 22gnome_panel_data "
PRECLEANUP+="22screensaver 23etc_modules 23networking 24preseed "
PRECLEANUP+="25configure_init 30accessibility 31disable_update_notifier "
PRECLEANUP+="33disable_binary_drivers 34disable_kwallet "
PRECLEANUP+="35fix_language_selector 12fstab 32disable_hibernation"

script_dir="$DESTDIR/scripts/casper-bottom"

for script in $PRECLEANUP; do
        rm $script_dir/$script
done

Then change permissions:

foo:~# chmod +x /etc/initramfs-tools/hooks/casper_custom

/usr/local/bin/moo

Available here.

Remark: in this externally linked script, there's an error on line 84 (one "exit" to many, which prevents updating the live system). The version below on the wiki is correct though.

#!/bin/bash

# Configurable variables
MAXLOOPS=8	# Maximum number of loop devices to allow
MAXPCT=90	# Begin deleting files on /boot when this % is used
CLEANDOC="yes"	# Clean /usr/share/doc?
CLEANMAN="yes"	# Clean /usr/share/man?
CLEANINF="yes"	# Clean /usr/share/info?

# Do not edit anything below here!
let MAXLOOPS=$MAXLOOPS-1

EXCLUDES=/tmp/moo.excludes
INCLUDES=/tmp/moo.includes
EXLIST="^/cow/var/log/ ^/cow/var/lock/ ^/cow/tmp/ ^/cow/var/tmp/ "
EXLIST+="^/cow/sys/ ^/cow/dev/ ^/cow/cow/ ^/cow/proc/ ^/cow/var/run/ "
EXLIST+="^/cow/casper/ /.joe state$ ~$ /.bash_history$ /.lesshst$ "
EXLIST+="^/cow/etc/mtab$ ^/cow/etc/.wh.mtab~ ^/cow/var/cache/apt/archives/ "
EXLIST+="^/cow/var/lib/dhcp./ ^/cow/var/lib/urandom/ "
EXLIST+="^/cow/var/lib/dpkg/.*-old$ ~$"
EXBASE="/var/log/* /var/lock/* /tmp/* /var/tmp/* /sys/* /dev/* *~ "
EXBASE+="/cow/* /proc/* /var/run/* /etc/mtab /boot/* .lesshst .bash_history "
EXBASE+="/var/cache/apt/archives/*.deb /var/lib/dhcp?/* "
EXBASE+="/var/lib/urandom/* /var/lib/dpkg/*-old "
EXBASE+="/casper/* /var/cache/apt/archives/partial/*.deb"

echo "Checking free space on /boot..."
while [ $(df /boot | grep boot | tr -s ' ' | cut -d' ' -f5 | sed -e s/%//) \> $MAXPCT ]; do
  in_use=0
  zap_file=$(ls -c /boot/casper/*.squashfs | grep -v /base.squashfs | tail
  -n1 | sed -e s%/boot/casper/%%)
  grep $zap_file /boot/casper/order.lst && in_use=1;
  if [ $in_use == 1 ]; then
    echo "Unable to zap /boot/casper/$zap_file!"
    echo "Please free up some space on /boot then try again"
    exit
  fi
  echo "Zapping $zap_file"
  rm /boot/casper/$zap_file
done

zerosize() {
  find $* | while read file; do
    echo -n "."
    rm $file
    touch $file
  done
}

if [ "$CLEANDOC" == "yes" ]; then
  echo -n "Cleaning /usr/share/doc"
  zerosize /usr/share/doc -type f -size +1b
  zerosize /usr/share/doc -type l
  echo ""
fi
if [ "$CLEANMAN" == "yes" ]; then
  echo -n "Cleaning /usr/share/man"
  zerosize /usr/share/man -type f -size +1b
  zerosize /usr/share/man -type l
  echo ""
fi
if [ "$CLEANINF" == "yes" ]; then
  echo -n "Cleaning /usr/share/info"
  zerosize /usr/share/info -type f -size +1b
  zerosize /usr/share/info -type l
  echo ""
fi

if [ -e /boot/casper/snapshot.ver ]; then
  let version=$(cat /boot/casper/snapshot.ver)+1
else
  echo "Creating initial base image."
  mkdir -p /boot/casper
  mkdir -p /cow/
  mkdir -p /casper/
  mksquashfs / /boot/casper/base.squashfs -e $EXBASE
  echo "base.squashfs" > /boot/casper/order.lst
  echo "0" > /boot/casper/snapshot.ver
  exit
fi
mount -o remount,rw /boot
echo $version > /boot/casper/snapshot.ver
let num_branches=$(/usr/sbin/unionctl / --list | wc -l)-1;
echo "$num_branches branches found..."
if [ $num_branches == $MAXLOOPS ]; then
  echo "$MAXLOOPS branches already exist; Creating new base image.";
  mksquashfs / /boot/casper/base-$version.squashfs -e $EXBASE
  echo base-$version.squashfs > /boot/casper/order.lst
  echo "Mounting new image..."
  mkdir /casper/base-$version.squashfs
  mount -o loop /boot/casper/base-$version.squashfs
  /casper/base-$version.squashfs
  unionctl / --add --after /cow --mode ro /casper/base-$version.squashfs
  echo "You MUST reboot before making another backup!"
  exit
fi
if [ $num_branches \> $MAXLOOPS ]; then
  echo "$MAXLOOPS branches already exist. Aborting."
  exit
fi

echo -n "" > $EXCLUDES
echo -n "" > $INCLUDES

echo "Reading /cow..."
cow_files=$(find /cow -depth);
echo $cow_files | sed -e 's% %\n%g' > /tmp/foo
for x in $EXLIST; do
	echo CHECKING: $x >> $EXCLUDES
	echo $cow_files | sed -e 's% %\n%g' | grep $x >> $EXCLUDES
	cow_files=$(echo $cow_files | sed -e 's% %\n%g' | grep -v $x)
done

for x in $cow_files; do
	let files=$files+1;
	file[$files]=$x;
done

branches=$(/usr/sbin/unionctl / --list | grep -v /cow | sed -e 's%\s%%g' |
cut -d'(' -f1);

# Create list of excludes
for x in $(seq 1 $files); do
  file=$(echo ${file[$x]} | sed -e 's%/cow%%')
  zap=0
  is_wh_file=0
  keep_wh=0
  if [ $(echo $file | grep /\.wh\.) ]; then
    deleted_file=$(echo $file | sed -e s%\.wh\.%%);
    is_wh_file=1
  fi
  for branch in $branches; do
    if [ $zap == 0 ];then
      if [ -f "$branch"/"$file" ]; then
        /usr/bin/cmp -s /cow"$file" "$branch"/"$file" && zap=1
      elif [ -d "$branch"/"$file" ]; then
        grep "$file"/ $INCLUDES > /dev/null || zap=1
      fi
    fi
    if [ $is_wh_file == 1 ] && [ $zap == 0 ]; then
      if [ -e "$branch"/"$deleted_file" ]; then
        keep_wh=1
      fi
    fi
  done

  if [ $is_wh_file == 1 ] && [ $keep_wh == 0 ]; then
    zap=1;
  fi
  if [ $zap == 0 ]; then
    echo $file >> $INCLUDES
    echo -n "+"
  else
    echo /cow$file >> $EXCLUDES
    echo -n "-"
  fi
done

echo
echo "Creating /boot/casper/$version.squashfs..."
mksquashfs /cow /boot/casper/$version.squashfs -ef $EXCLUDES
echo $version.squashfs >> /boot/casper/order.lst
mount -o remount,ro /boot
echo "Mounting new image..."
mkdir /casper/$version.squashfs
mount -o loop /boot/casper/$version.squashfs /casper/$version.squashfs
unionctl / --add --after /cow --mode ro /casper/$version.squashfs
rm $EXCLUDES
rm $INCLUDES

Then change permissions:

foo:~# chmod +x /usr/local/bin/moo

Note that by default, 'moo' will truncate all files in /usr/share/doc, /usr/share/man, and /usr/share/info to save space. (I don't mind reading my docs on another machine!) It truncates them rather than deleting them so that dpkg doesn't get confused when files go missing! 0-byte files will compress to practically no space in squashfs, so this shouldn't be much of a waste of space. I found that truncating these files saved roughly 2-5mb of space in the squashfs on a minimalistic Debian install.

If you do not want to delete these files, edit the 'moo' script and change the CLEANMAN, CLEANDOC, and/or CLEANINF variables to "no".

Putting it all together

Install any additional packages you know you'll need, and make any final configurations. Then package the chroot system onto your flash drive:

foo:~# mkdir -p /boot/casper
foo:~# update-initramfs -u all
foo:~# moo

The moo script will create a few essential directories (/cow, /casper, /boot/casper) and build the base squashfs and place it in /boot/casper.

Install GRUB

You must exit the chroot so that grub-install has proper access to the necessary device files:

foo:~# exit

Then run the following command. Replace '/dev/sda' with the appropriate name of your flash drive on the installation system (for me it's actually '/dev/sdc'):

foo:~# grub-install --recheck --root-directory=/mnt/chroot /dev/sda

Then go back into the chroot, and configure grub.

foo:~# chroot /mnt/chroot
foo:~# update-grub

Then manually edit the /boot/grub/menu.lst file. find the line that begins with # kopt= and change it to read:

# kopt=boot=casper ramdisk_size=16384 root=/dev/ram rw ip=frommedia quickreboot livemount=/boot showmounts

Also edit the line that begins with # groot and change it so that it corresponds to the device your flash drive will be on reboot. If you have no SATA drives, above grub-install command would have detected /dev/sda as (hd1,0) and that's what your grub root device should be.

Then update the final configuration, exit the chroot, and unmount the media:

foo:~# update-grub
foo:~# exit
foo:~# umount /mnt/chroot/boot

Plug the USB stick into a bootable system, and give it a try!

Making changes

At this point, you can update the live system using the 'moo' command as described below. If you want to re-write the main base image, you can do that by remounting your flash drive in /mnt/chroot/boot, and following these steps:

foo:~# mount /dev/sda1 /mnt/chroot/boot
foo:~# chroot /mnt/chroot
foo:~# rm /boot/casper/*

This removes the previously created image. Then make any changes, and run the 'moo' command again:

foo:~# moo

If you make any changes to the kernel, you will need to re-run the update-initramfs command:

foo:~# update-initramfs -u all

Cleaning up

When you are confident that you no longer need to make changes from your installation system, you can zap the chroot you've created--all of the necessary files are backed up to your flash drive, so they're no longer needed. Be sure your flash drive is no longer mounted on /mnt/chroot/boot before you do this!

foo:~# rm -rf /mnt/chroot

Updating the live system

Any time you make a change to your Debian-on-USB system that needs to be persistent, you will have to run the moo command. The moo command compares the /cow (ramdisk partition) with the underlying squashfs filesystem(s), and writes any changes to a new squashfs, stores it on the USB boot media, and inserts it into the unionfs.

Due to the limitation of the linux kernel for only mounting 8 loop devices, moo will create a new base image after 7 partial images are created. When this happens, moo will inform you that a reboot is desirable. I suggest following this advice, because additional backups cannot be created after this, without unpredicatable results.

Personal tools