#!/bin/sh

# Initial bootstrap of a pristine Debian system.

# Copyright (C) 2007 Bob Proulx
#
# 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 3 of the License, or
# (at your option) any later version.
#
# This program 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
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

# Written by Bob Proulx <bob@proulx.com>

VERSION=0

test -x /usr/bin/dpkg || exit 0
test -x /usr/bin/apt-get || exit 0
test -r /etc/debian_version || exit 0

progname=$(basename $0)

print_help()
{
    # Create a help message suitable for help2man to format into a man
    # page directly from the online --help and --version message.
cat <<'EOF'
Usage: bootstrap [options]

Initial bootstrap of a pristine Debian system.

Options:
     --help     output help message
     --version  output version information

Examples:

The most common use is to run the script.

  $ bootstrap

But it is also possible to pull this from the web and run it on the
fly.

  $ apt-get install wget

  $ wget -q -O - http://www.proulx.com/~bob/debian/bootstrap | sh

Report bugs to Bob Proulx <bob@proulx.com>
EOF
}

print_version()
{
    cat <<EOF
$progname $VERSION
Written by Bob Proulx.

Copyright (C) 2007 Bob Proulx
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
EOF
}

SHORTOPTS="v"
LONGOPTS="help,verbose,version"

if $(getopt -T >/dev/null 2>&1) ; [ $? = 4 ] ; then # New longopts getopt.
    OPTS=$(getopt -o $SHORTOPTS --long $LONGOPTS -n "$progname" -- "$@")
else # Old classic getopt.
    # Special handling for --help and --version on old getopt.
    case $1 in --help) print_help ; exit 0 ;; esac
    case $1 in --version) print_version ; exit 0 ;; esac
    OPTS=$(getopt $SHORTOPTS "$@")
fi

if [ $? -ne 0 ]; then
    echo "'$progname --help' for more information" 1>&2
    exit 1
fi

eval set -- "$OPTS"

verbose=false
while [ $# -gt 0 ]; do
    : debug: $1
    case $1 in
        --help)
            print_help
            exit 0
            ;;
        --verbose|-v)
            verbose=true
            exit 0
            ;;
        --version)
            print_version
            exit 0
            ;;
        --)
            shift
            break
            ;;
        *)
            echo "Internal Error: option processing error: $1" 1>&2
            exit 1
            ;;
    esac
done

if ! (apt-get --version) >/dev/null 2>&1; then
  : No apt-get available so not an apt machine, exiting.
  exit 0
fi

if ! (dpkg --version) >/dev/null 2>&1; then
  : No dpkg available so not an dpkg machine, exiting.
  exit 0
fi

if [ ! -f /etc/apt/sources.list ]; then
  echo "Error: No /etc/apt/sources.list" 1>&2
  echo "Not running on a Debian GNU/Linux machine?" 1>&2
  exit 1
fi

if [ ! -w / ]; then
  echo "Error: Must be root to run this script" 1>&2
  exit 1
fi

case $(</etc/debian_version) in
  4.0) : okay, working with Etch ;;
  *)
    echo "Error: This script only handles Debian Etch machines." 1>&2
    exit 1
    ;;
esac

# At the time that this script is executed a simple temporary file
# naming scheme is okay.  By not needing to keep track of temporary
# files the handling and cleaning up of them is greatly simplified.
# A generic cleanup of our style of temporary files.
trap 'rm -f /tmp/*.tmp.$$~' EXIT
rm -f /tmp/*.tmp.$$~

# Quiet down the installer.
export DEBIAN_FRONTEND=noninteractive
export DEBCONF_ADMIN_EMAIL=""

case $(</etc/debian_version) in
    3.0) debver="woody" ;;
    3.1) debver="sarge" ;;
    4.0) debver="etch" ;;
    *)
	echo "Unknown /etc/debian_version found."
	exit 1
	;;
esac

case $(sysctl -n vm.overcommit_memory) in
    2) : okay, vm.overcommit_memory is 2 ;;
    *)
	# Turn off Linux memory overcommit if there is enough swap.
	swap=$(free -m | awk '/^Swap:/{print$2}')
	if echo "$swap" | grep -q -E '^[[:digit:]]+$'; then
	    if (( $swap > 4000 )); then
		sysctl -w vm.overcommit_memory=2
		sleep 5 # Give system time to crash if it is going to.
		# If the system can take it then make it permanent.
		cat >>/etc/sysctl.conf <<EOF

# Disable Linux kernel memory overcommit.
vm.overcommit_memory = 2
EOF
	    fi
	fi
	;;
esac

cat > /tmp/recommends.tmp.$$~ <<EOF
// This file was placed here at system install time by 'bootstrap'.
// At this time this package is not in any package.
Aptitude::Recommends-Important "false";
EOF
if ! cmp -s /tmp/recommends.tmp.$$~ /etc/apt/apt.conf.d/00recommends; then
    cp /tmp/recommends.tmp.$$~ /etc/apt/apt.conf.d/00recommends
fi

cat > /tmp/sources.list.tmp.$$~ <<EOF
deb http://ftp.us.debian.org/debian/ $debver main contrib non-free
deb-src http://ftp.us.debian.org/debian/ $debver main contrib non-free
deb http://security.debian.org/ $debver/updates main contrib non-free
deb-src http://security.debian.org/ $debver/updates main contrib non-free
EOF

if [ ! -f /etc/apt/sources.list ]; then
    touch /etc/apt/sources.list
fi

if ! cmp -s /tmp/sources.list.tmp.$$~ /etc/apt/sources.list; then
    cp -f --backup=numbered /tmp/sources.list.tmp.$$~ /etc/apt/sources.list
fi

if ! apt-get update; then
  echo "Error: Failed apt-get update" 1>&2
  exit 1
fi

if ! apt-get -o DPkg::Options::=--force-confnew upgrade -q -y; then
    echo "Error: apt-get failed" 1>&2
    exit 1
fi

if [ ! -x /usr/local/bin/ll ]; then
    cat >/usr/local/bin/ll <<'EOF'
#!/bin/sh
exec ls -l "$@"
EOF
    chmod a+x /usr/local/bin/ll
fi

if [ ! -d /root/bin ]; then
    mkdir /root/bin
fi

mypurge()
{
    for pkgname in "$@"; do
	if [ -d /usr/share/doc/$pkgname ]; then
	    dpkg --purge $pkgname
	fi
    done
}

myinstall()
{
    for pkgname in "$@"; do
	if [ ! -d /usr/share/doc/$pkgname ]; then
	    apt-get install $pkgname
	fi
    done
}

myinstall debsums
# initscripts failed to reinstall when in conjuction with other packages.
# Therefore use a for-loop.
for pkg in $(debsums -l); do
    apt-get install --reinstall -y $pkg
done

myinstall dlocate

myinstall nvi
mypurge nano

myinstall postfix

eximlist='exim4 exim4-base exim4-config exim4-daemon-light'
for p in $eximlist; do
    if dpkg --status $p | grep -q '^Status: deinstall .* config-files'; then
	dpkg --purge $p
    fi
done

# Correct this problem with an empty field.
# mydestination = foo.example.com, localhost.example.com, , localhost
mydomain=$(postconf -h mydomain)
myhostname=$(postconf -h myhostname)
mydestination="$myhostname, localhost.$mydomain, localhost"
if ! postconf -h mydestination | grep -q "$mydestination"; then
    postconf -e mydestination="$mydestination"
fi

# FIXME: /root/.forward or /etc/aliases root forward

myinstall bind9
myinstall resolvconf
if ! grep -q '^include.*/var/run/bind/named.options' /etc/bind/named.conf; then
    sed --in-place '/^include/s|/etc/bind/named.conf.options|/var/run/bind/named.options|' /etc/bind/named.conf
fi

myinstall smartmontools
if ! grep -q '^start_smartd=yes' /etc/default/smartmontools; then
    sed --in-place '/^#start_smartd=/s/.*/start_smartd=yes/' /etc/default/smartmontools
    invoke-rc.d smartmontools start
fi

myinstall aptitude
# Download separately first since this is so large that it sometimes fails.
for i in $(seq 1 3); do # try download up to three times
    if aptitude -d -q --without-recommends -y install '~t^desktop$' '~t^gnome-desktop$'; then
	break
    fi
done
# The files are downloaded.  Fire for effect.
aptitude -q --without-recommends -y install '~t^desktop$' '~t^gnome-desktop$'
# Unfortunately aptitude does not always pass results back to the
# caller in the exit code.  Therefore there is no way to know if
# aptitude actually succeeded.  All we can do is push on regardless.

# crypt++el
# libdb2
# memshow
# nfs-kernel-server
# nis
# other-distro-compat
# rsemd
# ssh-setup-user
# libxft1
# xlibs-dev


list='
apt-utils
autoconf
autofs
automake1.9
biff
bind9
bind9-host
binutils
binutils-dev
bison
bonnie++
byacc
bzip2
c2man
cfengine2
cflow
cle
connect-proxy
cpp
cutils
cvs
dc
debconf-doc
debian-el
devscripts
devscripts-el
dh-make
diffstat
dnsutils
doc-debian
dosfstools
dpkg-dev
dpkg-dev-el
emacs
ethtool
expect
fakeroot
feh
finger
fingerd
flex
ftp
g++
g++-3.3
gawk
gcc
gcc-3.3
gdb
gettext
git-core
glibc-doc
gnupg
gnuserv
grep-dctrl
help2man
iamerican
indent
ispell
kernel-package
ksh
less
libc6-dev
libcurses-ruby
libgdbmg1
libncurses5-dev
libssl-dev
libterm-readline-gnu-perl
libtime-hires-perl
libtool
libxaw7-dev
lintian
liwc
lsb
lsof
lynx
mailcrypt
mailx
make
man-db
manpages
manpages-dev
metamail
mime-support
most
mpack
mtools
mutt
mysql-client
netcat
nmh
ntp
openssl
patch
perl-doc
perl-tk
pinfo
pkg-config
procmail
psmisc
psutils
rcs
rsync
ruby
ruby-elisp
screen
sharutils
ssh
stl-manual
strace
sudo
sysvbanner
talk
talkd
tcsh
time
tk8.3
tmpreaper
traceroute
tramp
unifdef
unzip
urlview
vim-gtk
tightvncserver
wamerican
wdiff
wget
xdu
xemacs21
xloadimage
xosview
xpdf
xsltproc
xterm
xtightvncviewer
ytalk
zip
zsh
'

# myinstall $list

# x-window-system-core

list='
doc-debian
fvwm
gimp
gnumeric
gnuplot
gpm
imagemagick
kde
iceweasel
mpeglib
mpg321
pan
procmail
slrn
spamassassin
libmail-spf-query-perl
libmailtools-perl
libnet-dns-perl
ssh-askpass
vacation
xterm
x2vnc
xautolock
xwit
'

# myinstall $list

# Consider installing the meta package nvidia-kernel-2.6-686 to pull
# in latest nvidia-kernel-$(uname -r) kernel specific driver.

aptitude install -sfy
aptitude install -sfD
# aptitude keep-all

# Now that iceweasel is installed configure alternatives.
update-alternatives --set x-www-browser /usr/bin/iceweasel

# Now that vim is installed it will own the alternatives.  Configure.
update-alternatives --set vi /usr/bin/nvi
update-alternatives --set view /usr/bin/nview
update-alternatives --set ex /usr/bin/nex
update-alternatives --set editor /usr/bin/nvi

test -d /etc/skel/.ssh || mkdir -m 0700 /etc/skel/.ssh
if [ ! -x /etc/skel/.xsession ]; then
    cat >/etc/skel/.xsession <<EOF
#!/bin/sh
exec x-session-manager
EOF
    chmod a+x /etc/skel/.xsession
fi
# There are three lines talking about umask that I wish to delete so
# that the user won't even consider the idea of setting umask there.
# I want them to use the default login umask.
#  "# the default umask is set in /etc/login.defs"
#  "#umask 022"
#  "#"
if grep -q umask /etc/skel/.bash_profile; then
    sed --in-place '/default umask/d;/umask 022/{N;s/.*\n//;/^$/d;};' /etc/skel/.bash_profile
fi
rm -f /etc/skel/.bash_logout

if ! grep -q 'umask' /etc/default/ssh; then
    cat >>/etc/default/ssh <<EOF

# Post-install addition: Set ssh umask.  See also libpam-umask.  --rwp
umask 02
EOF
fi

if grep -q '^umask 022' /etc/profile; then
    sed --in-place '/umask 022/s/022/02/' /etc/profile
fi

update-alternatives --auto x-session-manager
update-alternatives --auto x-terminal-emulator
update-alternatives --auto x-window-manager

myinstall shorewall
#list='blacklist init interfaces policy rules zones'
#for file in $list; do
#    if [ ! -f /etc/shorewall/$file ]; then
#	cp /usr/share/doc/shorewall/default-config/$file /etc/shorewall/
#    fi
#done

if [ ! -f /etc/shorewall/blacklist ]; then
    cat >/etc/shorewall/blacklist <<EOF
#ADDRESS/SUBNET		PROTOCOL	PORT
# Drop these without logging because there are so many.
0.0.0.0/0		tcp		1433  # MS SQL SQLslammer
0.0.0.0/0		tcp		1434  # MS SQL SQLslammer
0.0.0.0/0		tcp		1026  # unknown
0.0.0.0/0		udp		1026  # unknown
0.0.0.0/0		tcp		1027  # unknown
0.0.0.0/0		udp		1027  # unknown
0.0.0.0/0		tcp		1028  # unknown
0.0.0.0/0		udp		1028  # unknown
# http://www1.dshield.org/port_report.php?port=6129
0.0.0.0/0		tcp		6129	# Dameware Remote Admin
0.0.0.0/0		tcp		17300	# W32/Spybot.worm.gen
EOF
fi

if [ ! -f /etc/shorewall/init ]; then
    cat >/etc/shorewall/init <<EOF
dmesg -n5
EOF
fi

if [ ! -f /etc/shorewall/interfaces ]; then
    ETH=$(ip link show | sed -n '/eth.*,UP/{s/^[[:digit:]]\+: //;s/:.*//p;q;}')
    cat >/etc/shorewall/interfaces <<EOF
#ZONE	INTERFACE	BROADCAST	OPTIONS
net	$ETH		detect		blacklist
EOF
fi

if [ ! -f /etc/shorewall/policy ]; then
    cat >/etc/shorewall/policy <<EOF
#SOURCE		DEST		POLICY		LOGLEVEL	LIMIT:BURST
fw		net		ACCEPT
all		all		DROP		info
EOF
fi

if [ ! -f /etc/shorewall/rules ]; then
    cat >/etc/shorewall/rules <<EOF
#ACTION SOURCE	DEST	PROTO	DEST	SOURCE	ORIGINAL RATE	USER/
#			PORT(S) PORT(S)		DEST	 LIMIT	GROUP
#SECTION ESTABLISHED
#SECTION RELATED
SECTION NEW

ACCEPT	all	all	icmp	echo-request,time-exceeded
ACCEPT	all	all	tcp	ssh

ACCEPT	all	all	tcp	ntp
ACCEPT	all	all	udp	ntp
EOF
fi

if [ ! -f /etc/shorewall/zones ]; then
    cat >/etc/shorewall/zones <<EOF
fw	firewall
net	ipv4
EOF
fi

# sed --in-place 's/^startup=0/startup=1/' /etc/default/shorewall

if grep -q '^SHOWWARNING=true' /etc/tmpreaper.conf; then
    sed --in-place 's/SHOWWARNING=true/SHOWWARNING=false/' /etc/tmpreaper.conf
fi

myinstall aide
if [ ! -f /etc/aide/aide.conf.d/80_local ]; then
    cat >/etc/aide/aide.conf.d/80_local <<EOF
# The rules that come with the aide package seem wacky to me.  Basically
# rewrite the entire set here.
!/proc
!/sys
!/mnt
!/home
!/root
!/tmp
!/usr/local
!/usr/share/doc
!/var/cache
!/var/lib
!/var/lock
!/var/mail
!/var/run
!/var/spool
!/var/tmp
!/var/log
!/var/backups
!/usr/X11R6
!/usr/include
!/usr/share/info
!/usr/share/man
!/usr/share/locale
!/usr/src
!/etc/aide

!/etc/network/run
!/lib/init

BinLib = p+i+n+u+g+s+b+m+c+md5+sha1
ConfFiles = p+i+n+u+g+s+b+m+c+md5+sha1

/usr/bin BinLib
/bin BinLib
/lib BinLib
/usr/lib BinLib
/usr/sbin BinLib
/sbin BinLib
/usr/games BinLib
/usr/local/bin BinLib
/usr/local/lib BinLib
/usr/local/sbin BinLib
/usr/X11R6/bin BinLib
/usr/X11R6/lib BinLib
/usr/share BinLib

/boot BinLib
/lib BinLib

/etc ConfFiles

# In 70_aide_dev a 'find' command is used to generate a full listing
# of /dev dynamically.  Because some of these are always changed by
# normal operation of the system anyway these need to be excluded.
# Since I don't want to modify the find command these can only be
# enumerated after it.  I would prefer to avoid having them in the
# list at all but this is the best that I can do.
!/dev
!/dev/.udev
!/dev/vcsa
!/dev/vcs
!/dev/ram8
!/dev/ram7
!/dev/ram6
!/dev/ram5
!/dev/ram4
!/dev/ram3
!/dev/ram2
!/dev/ram15
!/dev/ram14
!/dev/ram13
!/dev/ram12
!/dev/ram11
!/dev/ram10
!/dev/ram1
!/dev/ram0
!/dev/.initramfs
!/dev/.initramfs/progress_state
!/dev/.initramfs-tools
!/dev/null
!/dev/console
EOF
fi

if [ ! -x /etc/cron.daily/aide-post ]; then
    cat > /etc/cron.daily/aide-post <<EOF
#!/bin/sh
if [ -f /var/lib/aide/aide.db ]; then
  savelog -q /var/lib/aide/aide.db
fi
if [ -f /var/lib/aide/aide.db.new ]; then
  mv -f /var/lib/aide/aide.db.new /var/lib/aide/aide.db
fi
exit 0
EOF
    chmod a+x /etc/cron.daily/aide-post
fi
# FIXME: Does aideinit need to be run here?

exit 0
