#!/bin/bash # # Copyright (C) 2003, Mark Lawrence # Copyright (C) 2003, Paul Sladen # Licence: GPL # # Install a virtual debian server (vserver) from a debian HTTP/FTP archive version="20030207.1" umask 022 me=${0##*/} ### Helper functions ### exec 3>&1 exec 4>&2 noninteractive () { exec &> /dev/null } interactive () { exec 1>&3 exec 2>&4 } info () { echo "I: $1" >&3 } warn () { echo "W: $1" >&4 } error () { echo "E: $2" >&4 exit $1 } ### Usage/Info functions ### usage () { cat <&2 Usage: $me [-c config] [OPTIONS] hostname domain 1.2.3.4 (see -h for help) EOF } full_usage () { cat < Copyright (C) 2003, Paul Sladen . This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. EOF } check_config () { if [ ! -x /usr/sbin/debootstrap ]; then info "Require the debootstrap package to bootstrap Debian" info " Debian Host: apt-get install debootstrap" info " RPM Host: rpm -i http://people.debian.org/~blade/install/debootstrap/debootstrap-0.1.17.3-2.i386.rpm" error 1 "Missing deboostrap" fi if [ ! -x /usr/bin/ar ]; then info "Require \"ar\" for debootstrap" info " Debian Host: apt-get install binutils" error 1 "Missing ar command" fi if ! cat /proc/self/status | grep '^s_context:[^0-9]0$'; then info "Must be run from the host server (security context 0)" info " on a "vserver/ctx-patch" enabled kernel" info " See: http://www.solucorp.qc.ca/miscprj/s_context.hc" error 1 "Not running from a context 0 host" fi if [ -x /usr/bin/id ] && [ `id -u` -ne 0 ]; then error 1 "Must be run as root!" fi ### Perform some sanity checks on the configuration ### hostname="$1" domain="$2" ip="$3" if ! (echo $hostname | grep -q '^[a-z][a-z0-9]\+'); then error 1 "$hostname is not a valid hostname" fi if ! (echo $domain | grep -q '^[a-z].*[a-z]$'); then error 1 "$domain is not a valid DNS domain" fi fqdn="$hostname.$domain" if ! (host $fqdn | grep -q 'has address'); then warn "$fqdn does not resolve into an IP address"; fi # Not perfect, but better than letters... if ! (echo $ip | grep -q '^[0-9]\{1,3\}\(\.[0-9]\{1,3\}\)\{3\}$' ); then error 1 "\"$ip\" is not a valid IP address" fi if ! (echo $arch | grep -q '[a-z].*'); then warn "$arch does not appear to be a valid architecture. eg i386" fi if ! (echo $dist | grep -q '[a-z].*'); then warn "$dist does not appear to be a valid Debian distribution. eg woody" fi if $fakeinit; then fakeinit=" fakeinit" else fakeinit="" fi if ! (echo $interface | grep -q '[a-z].*'); then warn "$interface does not appear to be a valid interface" warn " eg: eth0" fi if $localapt && [ ! -d /etc/apt ]; then error 1 "-l,--localapt is set but /etc/apt does not exist" fi mirror="${mirror%/}" if ! (echo $mirror | egrep -q '(f|ht)tp://[a-z].*[a-z]'); then error 1 "$mirror does not appear to be a valid mirror" fi vsroot="${vsroot%/}" if [ ! -d $vsroot ]; then error 1 "$vsroot does not exist. vserverutils installed?" fi if [ $sshkeys != "" -a ! -e $sshkeys ]; then error 1 "$sshkeys does not exist" fi target=$vsroot/$hostname if [ -d $target ]; then if $force; then warn "\"$target\" already exists but proceding anyway" vserver $hostname stop tmpf=$(tempfile) if [ -d $target/var/cache/apt/archives ] && \ cd $target/.. ; then tar cf $tmpf $hostname/var/cache/apt/archives rm -rf $target tar xf $tmpf rm -f $tmpf else rm -rf $target fi else error 1 "\"$target\" already exists" fi fi targetconf=/etc/vservers/$hostname.conf if [ -e $targetconf/etc ]; then if $force; then warn "\"$targetconf\" exists but proceding anyway" else error 1 "\"$targetconf\" already exists (-f overrides)" fi fi } ### vserver construction/cleanup functions ### clean_dev () { # We *really* don't want to do this to the root server if [ "$target" == "/" ]; then error 1 "Internal Error: \$target was defined as /!!!!" fi # Leave only the required devices for correct operation info "Cleaning up /dev" devices="full null ptmx random tty urandom zero stderr stdin stdout" tmpf=$(tempfile) if cd $target/dev && tar cfp $tmpf $devices; then rm -rf $target/dev/* tar xfp $tmpf rm -f $tmpf mkdir pts mkdir shm else warn "$target/dev does not exist. debootstrap failure?" fi } clean_inittab () { info "Cleaning up /etc/inittab" inittab=$target/etc/inittab tmpf=$(tempfile) if (sed -e 's/.*respawn:\/sbin\/getty.*/#&/' \ -e 's/.*respawn:\/sbin\/sulogin.*/#&/' $inittab > $tmpf; ); then mv $tmpf $inittab fi rm -f $tmpf } clean_rclinks () { info "Removing hardware related items from /etc/rc?.d/" # We have to remove the links themselves and not the packages because # of the deb dependancies hw_based="checkfs.sh checkroot.sh console-screen.sh devpts.sh \ hostname.sh hwclock.sh hwclockfirst.sh ifupdown iptables \ keymap.sh makedev modutils mountall.sh networking procps \ setserial umountfs urandom " for link in $hw_based do chroot $target /usr/sbin/update-rc.d -f $link remove done } create_mtab () { info "Creating dummy mtab (for df), fstab and motd" # Create a dummy mtab (for the mount/df commands) cat << EOF > $target/etc/mtab /dev/hdv1 / vfs rw 0 0 proc /proc proc rw 0 0 none /dev/pts devpts rw,gid=5,mode=620 0 0 EOF cat << EOF > $target/etc/fstab # /etc/fstab: static file system information. # # proc /proc proc defaults 0 0 EOF # Set the motd, and stop it from being edited when # /etc/init.d/bootmisc.sh runs echo "Debian GNU/Linux ($dist/$(uname -m)) $fqdn" > $target/etc/motd rcS=$target/etc/default/rcS tmpf=$(tempfile) if sed -e 's/^EDITMOTD=yes/EDITMOTD=no/' $rcS > $tmpf ; then mv $tmpf $rcS fi rm -f $tmpf } create_crontab () { # By default the Debian Install script runs zillions of cron jobs at # 0625 every morning. On a system with lots of vservers all trying to # scan the disk at the same time this causes $MAJOR disk-thrash. So # we randomize it a bit so that they run evenly between 1am and 7am, # avoiding the 5minutes either side of the hour when other stuff tends # to be scheduled. (BTW, this solution is Overkill!) # This looks over complicated--and it probably is...: # # a) We want the DAILY jobs to run between :05 and :55 minutes past # b) We want the WEEKLY job 3-5 minutes after the DAILY. # c) And the MONTHLY job 3-5 minutes after that. # d) Make sure all three jobs are started by 55minutes past (five-to) # ...if they were ever to all run on the same day! d1=$(($RANDOM % 3 + 3)); # between 3 and 5 d2=$(($RANDOM % 3 + 3)); # between 3 and 5 dt=$((50 - $d1 - $d2)); # between 0 and 44 DAILY=$(($RANDOM % $dt + 5)) # between 5 and 49 WEEKLY=$(($DAILY + $d1)) # between 8 and 52 MONTHLY=$(($WEEKLY + $d2)) # between 11 and 55 HOUR=$(($RANDOM % 6 + 1)) # between 1 and 7 (AM localtime) # Create replacement /etc/crontab with randomized times above cat << EOF > $target/etc/crontab # /etc/crontab: system-wide crontab # Unlike any other crontab you don't have to run the \`crontab' # command to install the new version when you edit this file. # This file also has a username field, that none of the other crontabs do. SHELL=/bin/sh PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin # m h dom mon dow user command $DAILY $HOUR * * * root test -e /usr/sbin/anacron || run-parts --report /etc/cron.daily $WEEKLY $HOUR * * 7 root test -e /usr/sbin/anacron || run-parts --report /etc/cron.weekly $MONTHLY $HOUR 1 * * root test -e /usr/sbin/anacron || run-parts --report /etc/cron.monthly EOF } configure_networking () { info "Configuring networking based on root server" # /etc/resolv.conf is created during the debootstrap process so # don't do anything for that here. # Set up the /etc/hosts file (needed for some parts of the base-config) cat << EOF > $target/etc/hosts # /etc/hosts (automatically generated by vserverutils) 127.0.0.1 localhost $ip $fqdn $hostname # The following lines are desirable for IPv6 capable hosts ::1 ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters ff02::3 ip6-allhosts EOF } configure_apt () { if $localapt; then info "Setting up apt based on root server" cp -r /etc/apt/* $target/etc/apt/ else info "Generating default /etc/apt/sources.list" cat << EOF > $target/etc/apt/sources.list deb $mirror/ stable main non-free contrib deb-src $mirror/ stable main non-free contrib deb http://non-us.debian.org/debian-non-US stable/non-US main contrib non-free deb-src http://non-us.debian.org/debian-non-US stable/non-US main contrib non-fr ee deb http://security.debian.org stable/updates main contrib non-free EOF fi } create_conf () { info "Creating config file: /etc/vservers/$hostname.conf" # Create default /etc/vservers entry cat << EOF > /etc/vservers/$hostname.conf S_HOSTNAME="$hostname" IPROOT="$ip" IPROOTDEV="$interface" ONBOOT="yes" S_NICE="" S_FLAGS="lock nproc$fakeinit" ULIMIT="-H -u 256 -n 1024" S_CAPS="CAP_NET_RAW" # This is for NIS, not DNS S_DOMAINNAME="" EOF } default_packages () { info "Setting default package selections" tmpf=(tempfile) for package in $purge do echo "$package purge" >> $tmpf done for package in $install do echo "$package install" >> $tmpf done cat $tmpf | chroot $target dpkg --set-selections rm -f $tmpf info "Updating apt databases" chroot $target dselect update # Download only - leave the configuration for later on info "Downloading default packages (this can take some time)" chroot $target apt-get --assume-yes --download-only dselect-upgrade } copy_sshkeys () { if [ -f $sshkeys ]; then info "Copying root SSH key to vserver" mkdir -p $target/root/.ssh chmod 700 $target/root/.ssh/ cat $sshkeys >> $target/root/.ssh/authorized_keys chmod 600 $target/root/.ssh/authorized_keys if [ -f $target/etc/ssh/ssh_host_rsa_key.pub ]; then info "Copying host SSH keys for vserver to roothost" echo "$hostname,$fqdn,$ip $(cat $target/etc/ssh/ssh_host_rsa_key.pub)" >> /etc/ssh/ssh_known_hosts2 fi fi } create_vserver () { info "Running debootstrap to install \"$dist\" (takes a while)" if ! debootstrap --arch $arch $dist $target $mirror; then error 1 "debootstrap failed" fi clean_dev clean_rclinks create_mtab clean_inittab create_crontab configure_networking configure_apt default_packages create_conf info "Interactive configuration..." interactive sleep 2 chroot $target tzsetup -y chroot $target dpkg-reconfigure passwd # Unfortunately the current packages dependencies don't let # you do with exim. ie impossible to run a server without mta rm -f $target/etc/exim/exim.conf chroot $target eximconfig # configure and install default packages downloaded earlier # This must be interactive in case debconf asks questions chmod 000 $target/.. vserver $hostname exec apt-get --assume-yes dselect-upgrade if ! $verbose; then clear noninteractive fi # ssh only just got installed, if at all copy_sshkeys info "Cleaning up vserver processes" export PREVLEVEL=2 vserver $hostname exec /etc/init.d/rc 0 vserver $hostname stop info "Virtual Server \"$hostname\" successfully installed." info " Make your changes to $targetconf and type" info " vserver $hostname start" info " to start it. debian/rules!" } ### Default values ### # Configuration priority is: # Internal defaults # /etc/vservers/debvserver (if it exists) # Values from --config (fail if it doesn't exist) # Command line options verbose=(false) conf=/etc/vservers/debvserver arch="i386" dist="woody" force=(false) fakeinit=(false) install="" purge="dhcp-client lilo makedev pcmcia-cs ppp pppconfig pppoe pppoeconf setserial syslinux nano fdutils iptables libpcap0 pciutils" interface="eth0" localapt=(false) mirror="http://http.us.debian.org/debian" vsroot="/vserver" sshkeys="" if [ -e $conf ]; then . $conf fi ### Command line options ### if [ $# -eq 0 ]; then # Script invoked with no command-line args? usage exit 1 fi temp=$(getopt -o hVvc:a:d:fi:klm:r:s: --long help,version,verbose,config,arch:,dist:,force,install,purge,interface:,fakeinit,localapt,mirror:,vsroot:,sshkeys: -n $me -- "$@") if [ $? != 0 ]; then echo " (See -h for help)" exit 1 fi # Note the quotes around `$temp': they are essential! eval set -- "$temp" while true; do case "$1" in -h|--help) full_usage exit 1 ;; -V|--version) full_version exit 1 ;; -v|--verbose) verbose=(true) shift ;; -c|--config) conf="$2" if [ ! -e $conf ]; then error 1 "$conf does not exist" fi . $conf shift 2 ;; -a|--arch) arch="$2" shift 2 ;; -d|--dist) dist="$2" shift 2 ;; -f|--force) force=(true) shift ;; --install) install="$2" shift 2 ;; --purge) purge="$2" shift 2 ;; -i|--interface) interface="$2" shift 2 ;; -k|--fakeinit) fakeinit=(true) shift ;; -l|--localapt) localapt=(true) shift ;; -m|--mirror) mirror="$2" shift 2 ;; -r|--vsroot) vsroot="$2" shift 2 ;; -s|--sshkeys) sshkeys="$2" shift 2 ;; --) shift break ;; *) echo "Internal error!" exit 1 ;; esac done if [ $# -ne 3 ]; then usage exit 1 fi info "Using $conf for defaults" if $verbose; then interactive else noninteractive fi check_config $* create_vserver exit 0