#!/bin/bash VERSION='0.6' # # Copyright (C) 2002-2008 Ola Lundqvist # Copyright (C) 2008 Vincent Bernat # Copyright (C) 2007 Tijs van Dam # Copyright (C) 2007 Jochen Voss # Copyright (C) 2006 Miroslav Kure # Copyright (C) 2005 Thomas Gelf # Copyright (C) 2005 Daniel Hermann # Copyright (C) 2002 Paul Sladen # Copyright (C) 2002 Mark Lawrence # # This 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, or (at your option) any later # version. # # This is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License # for more details. # # You should have received a copy of the GNU General Public License with # the source package as the file COPYING. If not, write to the Free # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, # MA 02110-1301, USA. # # Install a virtual debian server (vserver) from a debian HTTP/FTP archive # # ---------------------------------------------------------------- # Configurable items: shopt -s extglob # No context id set by default CONTEXT= # Root directory of your virtual servers (probably shouldn't change this) # Support for 0.30.203 and above of util-vserver VROOTDIR="/etc/vservers/.defaults/vdirbase" # Packages to install in addition to the base defaults # MUST INCLUDE ALL DEPENDENCIES (seperated by "," commas) INSTALL_PACKAGES="" if [ -n "$LANG" ] && [ "$LANG" != "C" ] ; then INSTALL_PACKAGES="$INSTALL_PACKAGES locales" fi # Packages installed from within the vserver after it has # been setup (seperated by "," commas) EXTRA_PACKAGES="" # Packages to remove from the base defaults (seperated by "," commas) #REMOVE_PACKAGES="dhcp-client,lilo,makedev,pcmcia-cs,ppp,pppconfig,pppoe,pppoeconf,setserial,syslinux,nano,fdutils,iptables,libpcap0,pciutils" REMOVE_PACKAGES="sparc-utils,dhcp-client,lilo,makedev,pcmcia-cs,ppp,pppconfig,pppoe,pppoeconf,setserial,syslinux,fdutils,libpcap0,iptables,pciutils" # sysvinit services relating to hardware access to remove REMOVE_LINKS="klogd hwclock.sh setserial urandom networking umountfs halt reboot mountvirtfs mountall.sh mountnfs.sh ifupdown" # Post installation script, run inside vserver guest context POST_INSTALL_SCRIPT="" # Post installation script, run inside vserver host context and before # POST_INSTALL_SCRIPT POST_INSTALL_HOST_SCRIPT="" # Architecture: overide on non-Debian host such as Redhat # otherwise dpkg will detect whether we are i386/powerpc/sparc/etc ARCH="" # Which debian distribution # (warning: this has only been tested with woody, sarge, etch and lenny) DIST="lenny" # Local or nearest location of a debian mirror (must include the `/debian') MIRROR="http://ftp.uk.debian.org/debian" # debian-non-US mirror (must include the '/debian-non-US', only used for woody) MIRROR_NON_US="http://non-us.debian.org/debian-non-US" # mirror for security updates MIRROR_SECURITY="http://security.debian.org" # Default network interface for vservers: INTERFACE="eth0" # Package caching PKGCACHE=0 # The name of a debconf database to be read by debconf's file driver. # This can be used to store the answers for debconf questions during # install and thus allows for unattended installation. See the # debconf.conf(5) manual page for the file format. DEBCONF_FILE_DB="" if [ -r /etc/vservers/newvserver-vars ] ; then . /etc/vservers/newvserver-vars fi # ---------------------------------------------------------------- # Nothing from here on should need changing. # ---------------------------------------------------------------- # NOTE: debootstrap handles multiple MIRRORs, so there is no reason why # we shouldn't too--that way we could just let it build /etc/apt/sources.list usage () { cat << EOF 1>&2 usage: ${0##*/} --hostname x --domain y.z --ip 1.2.3.4/24 [OPTIONS] (see --help for more information) EOF } full_usage () { cat << EOF Usage: ${0##*/} --hostname x --domain y.z --ip 1.2.3.4/24 [OPTIONS] Creates a new Debian vserver by calling "vserver ... build" Options: -h, --help this help -V, --version copyright and version information --arch set target architecture (eg. --arch "i386") (autodetected on Debian host if dpkg available) --dist defaults to "lenny", passed to debootstrap. --context n Set the context id to be used. --fakeinit use "/sbin/init" to boot vserver --conffile extra configuration file to load. --interface interface for IP addresses (if not "eth0") --mirror Debian HTTP/FTP mirror (including the /debian) --sshkeys copy pub-keys to "/root/.ssh/authorized_keys" --pkgcache Package caching. -v, --verbose show extra output during setup --vsroot location of "/vserver/" directory Required: --hostname hostname for new vserver (eg. "alpha") --domain dns domain for new vserver (eg. "example.com") --ip IPv4 address for new vserver (syntax: --ip [/]) Either --ip or --domain/--hostname may be omitted if the corresponding information can be found via a name server query. For this to work the dnsutils package must be installed. You can also set variables in /etc/vservers/newvserver-vars. EOF } full_version () { cat << EOF ${0##*/} version $VERSION Copyright (C) 2002-2008 Ola Lundqvist Copyright (C) 2008 Vincent Bernat Copyright (C) 2007 Tijs van Dam Copyright (C) 2007 Jochen Voss Copyright (C) 2006 Miroslav Kure Copyright (C) 2005 Thomas Gelf Copyright (C) 2005 Daniel Hermann Copyright (C) 2002 Paul Sladen Copyright (C) 2002 Mark Lawrence 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 } parse_args () { while [ $# -gt 0 ] ; do #echo "parse_args() doing :$#: :$1: :$*:" case "$1" in --help|-h) full_usage shift exit 0 ;; --version|-V) full_version shift exit 0 ;; --arch) case "$2" in [a-z]*) ARCH="$2" ;; *) echo "${0##*/} error: $1 overrides dpkg autodetect on non-Debian host-servers" 1>&2 echo 'e.g. "i386"' 1>&2 exit 1 ;; esac shift 2 ;; --context) CONTEXT=$2 shift 2 ;; --conffile) if [ -r "$2" ] ; then . "$2" else echo "Error, can not load config file $2." exit 1 fi shift 2 ;; --post-install-script) if [ -r "$2" ] ; then POST_INSTALL_SCRIPT="$2" echo "Script $2 found." else echo "Error, can not locate the script $2." exit 1 fi shift 2 ;; --post-install-host-script) if [ -r "$2" ] ; then POST_INSTALL_HOST_SCRIPT="$2" echo "Script $2 found." else echo "Error, can not locate the script $2." exit 1 fi shift 2 ;; --dist) case "$2" in [a-z]*) DIST="$2" if [ "sarge" != "$2" ] && [ "woody" != "$2" ] && [ "etch" != "$2" ] && [ "lenny" != "$2" ] ; then echo "${0##*/} warning: I only know how to do \"woody\", \"sarge\", \"etch\" and \"lenny\", be careful!" 1>&2 fi ;; *) echo "${0##*/} error: $1 requires a Debian distribution" 1>&2 echo 'e.g. "woody", "sarge", "etch" or "lenny"' 1>&2 exit 1 ;; esac shift 2 ;; --domain) case "$2" in [a-z]*[a-z]) VDOMAIN="$2" ;; *) echo "${0##*/} error: $1 requires a dns domain-name" 1>&2 echo 'e.g. "example.com"' 1>&2 exit 1 ;; esac shift 2 ;; --fakeinit) # Note space at beginning--this gets tagged straight on FAKEINIT=" fakeinit" shift ;; --pkgcache) PKGCACHE=1 shift ;; --hostname) case "$2" in [a-z0-9]*([a-z0-9_-])[a-z0-9]) VHOST="$2" ;; *) echo "${0##*/} error: $1 must be a hostname for the vserver" 1>&2 echo 'e.g. "alpha"' 1>&2 exit 1 ;; esac shift 2 ;; --interface) case "$2" in [a-z]*) INTERFACE="$2" ;; *) echo "${0##*/} error: $1 must be followed by a network interface" 1>&2 echo 'e.g. "eth1"' 1>&2 exit 1 ;; esac shift 2 ;; --ip) # This does for an octet: ([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]) ;-) case "$2" in [0-9]*.[0-9]*.[0-9]*.[0-9]*) IP="$2" ;; *) echo "${0##*/} error: $1 requires a single IPv4 e.g. \"192.168.100.1\"" 1>&2 exit 1 ;; esac shift 2 ;; --mirror) case "$2" in [hf]*:/*) MIRROR="$2" ;; *) echo "${0##*/} error: $1 requires a debian mirror" 1>&2 echo 'e.g. "http://ftp.uk.debian.org/debian"' 1>&2 exit 1 ;; esac shift 2 ;; --verbose|-v) export verbose="true" shift ;; --sshkeys) if [ -f "$2" ]; then SSH_KEYS="$2" else echo "${0##*/} error: $1 must be followed by a valid public-key-file!" 1>&2 echo 'e.g. "/root/.ssh/id_dsa.pub"' 1>&2 exit 1 fi shift 2 ;; --vsroot) case "$2" in /*) if [ -d "$2" ]; then VROOTDIR="$2" else echo "${0##*/} error: $1 needs a valid absolute directory" 1>&2 echo 'e.g. "/vservers"' 1>&2 exit 1 fi ;; *) echo "${0##*/} error: $1 needs a valid absolute directory" 1>&2 echo 'e.g. "/vservers"' 1>&2 exit 1 ;; esac shift 2 ;; -*) usage exit 1 ;; ?*) usage exit 1 ;; esac done } parse_args $@ if [ -x "$(which dig)" ] ; then if [ -n "$IP" -a \( -z "$VHOST" -o -z "$VDOMAIN" \) ]; then tmp=$(dig +short -x $(echo $IP | sed 's;/[0-9]*$;;')) if [ -n "$tmp" ]; then if [ -z "$VHOST" ]; then VHOST=$(echo $tmp | sed 's/\..*//') fi if [ -z "$VDOMAIN" ]; then VDOMAIN=$(echo $tmp | sed 's/[^.]*\.//') fi fi fi if [ -n "$VHOST" -a -n "$VDOMAIN" -a -n "$INTERFACE" -a -z "$IP" ]; then prefix=$(ip -o addr show dev "$INTERFACE" primary scope global | \ sed -n 's;.*inet [0-9.]*/\([0-9]*\).*;\1;p') IP="$(dig +short "$VHOST.$VDOMAIN")/$prefix" fi if ! [ -n "$VHOST" -a -n "$VDOMAIN" -a -n "$IP" ]; then echo "${0##*/} error: --hostname, --domain and --ip are required" 1>&2 usage exit 1 fi fi IP_ADDR=${IP%%/*} # Strip final slashes off a couple of things MIRROR="${MIRROR%/}" MIRROR_NON_US="${MIRROR_NON_US%/}" MIRROR_SECURITY="${MIRROR_SECURITY%/}" VROOTDIR="${VROOTDIR%/}" ############################################################################## # Arch specific issues # Dist specific issues ############################################################################## if ! cat /proc/self/status | grep '^\(VxID:[^0-9]0\|s_context:[^0-9]0\)$'; then echo "${0##*/} error:" echo " Must be run from the host server (security context 0)" 1>&2 echo ' on a "vserver/ctx-patch" enabled kernel' 1>&2 echo ' See: http://www.solucorp.qc.ca/miscprj/s_context.hc' 1>&2 exit 1 fi if [ -x /usr/bin/id ] && [ `id -u` -ne 0 ]; then echo "${0##*/} error: Must be run as root!" 1>&2 exit 1 fi aptcleanup() { apt-get autoclean -o Dir::Cache::Archives="$1" OLD="" for P in $(find "$1" -mindepth 1 -maxdepth 1 -name *.deb -printf "%f\n" | sed -e "s/_.*//;") ; do if [ "$P" == "$OLD" ] ; then rm -f "$1"/$P_* > /dev/null 2>&1 fi OLD=$P done } # This is used to keep a cache of the downloaded .deb packges for next install if [ -d "$VROOTDIR/ARCHIVES/$DIST" ] && [ $PKGCACHE -eq 1 ] ; then mkdir -p "$VROOTDIR/$VHOST/var/cache/apt/archives" if [ ! -d "$VROOTDIR/ARCHIVES/$DIST/partial" ] ; then # Needed to make apt-get autoclean work fine. mkdir "$VROOTDIR/ARCHIVES/$DIST/partial" fi echo "Clean package cache." aptcleanup "$VROOTDIR/ARCHIVES/$DIST" cp -a "$VROOTDIR/ARCHIVES/$DIST/"*.deb "$VROOTDIR/$VHOST/var/cache/apt/archives" > /dev/null 2>&1 fi # We only want to pass the Architecture if we need to (autodectected otherwise) if [ -n "$ARCH" ]; then ARCH_ARGUMENT="--arch $ARCH" fi # Function to optionally set a context id. CONTEXT_ARGUMENT= if [ -n "$CONTEXT" ] ; then CONTEXT_ARGUMENT="--context $CONTEXT" fi TMP_INCLUDE="--include='$INSTALL_PACKAGES'" TMP_EXCLUDE="--exclude='$REMOVE_PACKAGES'" ## use "vserver ... build" to build the new vserver if ! /usr/sbin/vserver "$VHOST" build -m debootstrap \ --rootdir "$VROOTDIR" --hostname "$VHOST" --interface "$INTERFACE:$IP" \ $CONTEXT_ARGUMENT \ -- -d "$DIST" -m "$MIRROR" \ -- $ARCH_ARGUMENT \ $TMP_INCLUDE $TMP_EXCLUDE then echo "${0##*/}: error: vserver-build failure. Cannot continue." exit 1 fi # Create a locale.gen if needed. if [ -n "$LANG" ] && [ "$LANG" != "C" ] ; then if [ ! -e "$VROOTDIR/$VHOST/etc/locale.gen" ] ; then echo $LANG $(locale charmap) > "$VROOTDIR/$VHOST/etc/locale.gen" elif ! grep $LANG "$VROOTDIR/$VHOST/etc/locale.gen" > /dev/null ; then echo $LANG $(locale charmap) >> "$VROOTDIR/$VHOST/etc/locale.gen" fi fi # Make it so that apt and friends work if [ "woody" == "$DIST" ] ; then cat << EOF > "$VROOTDIR/$VHOST/etc/apt/sources.list" deb $MIRROR/ $DIST main deb-src $MIRROR/ $DIST main deb $MIRROR_NON_US $DIST/non-US main deb-src $MIRROR_NON_US $DIST/non-US main deb $MIRROR_SECURITY $DIST/updates main EOF else cat << EOF > "$VROOTDIR/$VHOST/etc/apt/sources.list" deb $MIRROR/ $DIST main deb-src $MIRROR/ $DIST main deb $MIRROR_SECURITY $DIST/updates main EOF fi # Fix up the available device nodes (mostly done by vserver-build above) if cd "$VROOTDIR/$VHOST/dev"; then ln -s /proc/self/fd fd ln -s fd/2 stderr ln -s fd/0 stdin ln -s fd/1 stdout fi # Give the new host a hostname echo "$VHOST" > "$VROOTDIR/$VHOST/etc/hostname" # Set up the /etc/hosts file (needed for some parts of the base-config) cat << EOF > "$VROOTDIR/$VHOST/etc/hosts" # /etc/hosts 127.0.0.1 localhost $IP_ADDR $VHOST.$VDOMAIN $VHOST # 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 # grab DNS servers from the host-server for `resolv.conf' HOST_IP=$(ip -o addr show dev $INTERFACE primary scope global | \ sed -n 's;.*inet \([^/]*\)/.*;\1;p' | head -1) (echo search $VDOMAIN; grep '^nameserver' /etc/resolv.conf | sed "s/127.0.0.1/$HOST_IP/") \ > "$VROOTDIR/$VHOST/etc/resolv.conf" # If there is a proxy server statement in-use in the Host server, copy it across if [ -f /etc/apt/apt.conf ]; then cp /etc/apt/apt.conf $VROOTDIR/$VHOST/etc/apt/apt.conf fi # support the apt.conf.d directories if [ -d /etc/apt/apt.conf.d ] ; then cp -a /etc/apt/apt.conf.d/* $VROOTDIR/$VHOST/etc/apt/apt.conf.d/ > /dev/null 2>&1 fi # Create a dummy fstab cat << EOF > "$VROOTDIR/$VHOST/etc/fstab" # /etc/fstab: static file system information. # # proc /proc proc defaults 0 0 EOF # Create a reduced inittab that doesn't start getty on the consoles mv "$VROOTDIR/$VHOST/etc/inittab" "$VROOTDIR/$VHOST/etc/inittab.dist" && \ grep -v respawn\:/sbin/getty "$VROOTDIR/$VHOST/etc/inittab.dist" > \ "$VROOTDIR/$VHOST/etc/inittab" && rm -f "$VROOTDIR/$VHOST/etc/inittab.dist" # 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 > "$VROOTDIR/$VHOST/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 if [ -n "$EXTRA_PACKAGES" ]; then EXTRA_PACKAGES_INSTALL="apt-get --assume-yes install ${EXTRA_PACKAGES//,/ }" fi if [ -f /etc/timezone ]; then tz=$(cat /etc/timezone) zonefile="$VROOTDIR/$VHOST/usr/share/zoneinfo/$tz" if [ -f "$zonefile" ]; then echo "$tz" >$VROOTDIR/$VHOST/etc/timezone cp "$zonefile" "$VROOTDIR/$VHOST/etc/localtime" fi fi if [ -n "$DEBCONF_FILE_DB" ]; then cp "$DEBCONF_FILE_DB" "$VROOTDIR/$VHOST/config.dat" SET_DEBCONF_OVERRIDE='export DEBCONF_DB_OVERRIDE="File{/config.dat}"' fi # ------------------------------------------------------------ # From here on we do things live in the server # Generate the script that runs the rest of the setup from within the # virtual server. cat << EOF > "$VROOTDIR/$VHOST/vserver-config.sh" #!/bin/sh $SET_DEBCONF_OVERRIDE [ -x /usr/sbin/locale-gen ] && /usr/sbin/locale-gen dselect update if [ ! -f /etc/timezone ]; then # tzsetup was part of base-config which is gone since etch # tzconfig is part of libc, so it should be ubiquitious if [ -x /usr/sbin/tzsetup ]; then /usr/sbin/tzsetup -y elif [ -x /usr/sbin/tzconfig ]; then /usr/sbin/tzconfig fi fi dpkg-reconfigure -u passwd tasksel if [ "$DIST" == "woody" ]; then rm -f /etc/exim/exim.conf eximconfig fi # because the --exclude flag doesn\'t seem to work on debootstrap dpkg -P `echo $REMOVE_PACKAGES | sed -e 's/,/ /g'` for link in $REMOVE_LINKS do update-rc.d -f \$link remove update-rc.d -f \$link stop 90 3 . done $EXTRA_PACKAGES_INSTALL EOF # Run the post-installation script from outside the server if [ -n "$POST_INSTALL_HOST_SCRIPT" ]; then "$POST_INSTALL_HOST_SCRIPT" fi ## start vserver before we can exec anything inside it vserver $VHOST start # Run the above commands from within the server chmod 755 $VROOTDIR/$VHOST/vserver-config.sh vserver $VHOST exec /vserver-config.sh rm -f $VROOTDIR/$VHOST/vserver-config.sh rm -f "$VROOTDIR/$VHOST/config.dat" rm -f "$VROOTDIR/$VHOST/config.dat-old" # Run the post-installation script from within the server if [ -n "$POST_INSTALL_SCRIPT" ]; then cp "$POST_INSTALL_SCRIPT" $VROOTDIR/$VHOST/post_install_script chmod 755 $VROOTDIR/$VHOST/post_install_script vserver $VHOST exec /post_install_script rm -f $VROOTDIR/$VHOST/post_install_script fi # If you need to install your SSH management keys into the vserver if [ -f "$SSH_KEYS" ]; then mkdir -p "$VROOTDIR/$VHOST/root/.ssh" chmod 700 "$VROOTDIR/$VHOST/root/.ssh/" cat "$SSH_KEYS" >> "$VROOTDIR/$VHOST/root/.ssh/authorized_keys" chmod 600 "$VROOTDIR/$VHOST/root/.ssh/authorized_keys" fi ## stop the vserver vserver $VHOST stop # Populate the archive for future virtual servers if [ $PKGCACHE -eq 1 ] ; then if [ ! -d "$VROOTDIR/ARCHIVES/$DIST/partial" ] ; then mkdir -p "$VROOTDIR/ARCHIVES/$DIST/partial" fi cp "$VROOTDIR/$VHOST/"var/cache/apt/archives/*.deb "$VROOTDIR/ARCHIVES/$DIST" echo "Cleanup package cache." aptcleanup "$VROOTDIR/ARCHIVES/$DIST" fi echo echo "You should now adjust the configuration in /etc/vservers/$VHOST/" echo "to suit your needs," echo "or else just go ahead and type \`vserver $VHOST start' to start" echo "your new virtual server. debian/rules!" echo