#!/bin/sh -e
# Apt configurator.
# GPL 199 by Joey Hess

# Options passed from dselect
VARDIR=$1
GROUP=$2
METHOD=$3

# If this is not called from dselect, and the first parameter is "probe", it
# will do cd autoprobing on startup.
if [ "$VARDIR" = probe ]; then
	CDPROBE=1
fi

# Where is the mirrors master list?
MIRRORLIST=/usr/share/base-config/Mirrors.masterlist

. /usr/share/debconf/confmodule

# Get the configuration from /etc/apt/apt.conf
APTGET="/usr/bin/apt-get"
APTETC="/etc/apt/"
APTCDROM="/usr/bin/apt-cdrom"
RES=`apt-config shell \
	APTGET Dir::Bin::apt-get \
	APTETC Dir::Etc \
	APTCDROM Dir::Bin::apt-cdrom`
eval $RES

# Pass in a URI type; this function returns a list of countries
# that have mirrors in them that match the type.
country_list () {
	perl -ne '
		BEGIN {
			$type=shift;
			open (T, "/usr/share/zoneinfo/iso3166.tab");
			while (<T>) {
				next if /^#/;
				chomp;
				($code, $long)=split(/\t/, $_);
				$c{$code}=$long;
			}
			close T;
			$/="\n\n";
			$countries{"enter information manually"}=1;
		}
		if (/Archive-$type:/) {
			($c)=/Country: (.*?)\s*\n/;
			$countries{$c{$c}}=1 if $c;
		}
		END { print join(", ", sort(keys %countries)) if %countries }
	' $1 $MIRRORLIST
}

# Pass in a URI type and a country; this function returns a 
# list of mirrors of the correct type in the country.
# The list is ordered with push mirrors at the top.
# Other orderings can be added here.
mirror_list () {
	perl -ne '
		BEGIN {
			$type=shift;
			$country=shift;
			open (T, "/usr/share/zoneinfo/iso3166.tab");
			while (<T>) {
				next if /^#/;
				chomp;
				($code, $long)=split(/\t/, $_);
				if ($long eq $country) {
					$country=$code;
				        close T;
				}
			}
			close T;
			$/="\n\n";
		}
		if (/Archive-$type:/ && /Country: $country\s*\n/) {
			($s)=/Site: (.*?)\n/;
			($t)=/Type: (.*?)\n/;
			$rating=0;
			$rating=1 if $t=~/push/i;
			$rating=2 if $t=~/push-primary/i;
			$mirrors{$s}=$rating;
		}
		END { print join(", ",
			sort { $mirrors{$b} <=> $mirrors{$a} }
			(keys %mirrors)) }
	' $1 "$2" $MIRRORLIST
}

# Pass in the URI type and the hostname of a mirror; this returns the
# directory the debian mirror is located in on that host.
mirror_dir () {
	perl -ne '
		BEGIN { $/="\n\n"; $type=shift; $mirror=shift }
		if (/Site: $mirror/ && /Archive-$type: (.*)\n/) {
			print $1;
			exit;
		}
	' $1 $2 $MIRRORLIST
}

# After mounting a cd, call this function to scan
# it with apt-cdrom, which also adds it to the sources.list.
# The cd will then be unmounted.
scan_cd () {
	clear >/dev/tty </dev/tty
	echo "Scanning CD, this will take a minute."
	# Touch file because apt-cdrom bails if it doesn't exist.
	touch /etc/apt/sources.list
	if $APTCDROM add --no-mount </dev/tty >/dev/tty; then
		umount /cdrom 2>/dev/null || true
		clear >/dev/tty </dev/tty
		return 0
	else
		umount /cdrom 2>/dev/null || true
		clear >/dev/tty </dev/tty
		# Apt didn't like the cdrom for some reason.
		db_fset apt-setup/cd/bad isdefault true
		db_input critical apt-setup/cd/bad || true
		db_go || true
		return 1
	fi
}

# This function should be called only after one cd has been successfully
# scanned. It prompts the user if there are more cd's to scan and
# scans them. Pass in the device that is known to be the cd drive.
handle_rest_cds () {
	LOOP=1
	while [ "$LOOP" ]; do
		# Make sure the cd is unmounted, we may be prompting
		# them to change cd's.
		umount /cdrom 2>/dev/null || true

		db_set apt-setup/cd/another false
		db_fset apt-setup/cd/another isdefault true
		db_input medium apt-setup/cd/another || true
		db_go || true
		db_get apt-setup/cd/another
		if [ "$RET" = true ]; then
			while ! mount $1 /cdrom -o ro -t iso9660
			do
				db_fset apt-setup/cd/dev isdefault true
				db_input critical apt-setup/cd/dev || true
				db_go || continue 2
				db_get apt-setup/cd/dev
				CDDEV="$RET"
			done
			scan_cd || true
		else
			LOOP=''
		fi
	done
}

# This function will ask the user if they want to add another
# apt source. If so, it returns true.
add_another () {
	# Now ask them if they want to add another entry,
	# and if so, restart.
	db_set apt-setup/another false
	db_fset apt-setup/another isdefault true
	db_input medium apt-setup/another || true
	db_go || return 1
	db_get apt-setup/another
	if [ "$RET" = true ]; then
		return 0
	fi
	return 1
}

# Establish the preliminaries.
db_version 2.0
db_capb 'backup'
db_title 'Apt Configuration'

MAINLOOP=1

# First, try to do cdrom autodetection. This is so in the most common case,
# you don't have to configure anything at all. Note that /dev/cdrom is made
# by the install process if you used the cd to install.

# If /dev/cdrom exists, use it.
if [ -e /dev/cdrom ]; then
	CDDEV=/dev/cdrom
	db_set apt-setup/cd/dev "$CDDEV"
fi
# TODO: other probing here.

if [ "$CDPROBE" -a "$CDDEV" ]; then
	umount /cdrom 2>/dev/null || true
	# Try mounting the detected cd rom.
	if mount $CDDEV /cdrom -o ro -t iso9660 && scan_cd; then
		handle_rest_cds $CDDEV
		MAINLOOP=''
	else
		# Unable to mount it, or the cd was bad. Make sure that cdrom
		# is the default, and just go on to ask them where they want
		# apt to install from.
		db_set apt-setup/uri_type "cdrom"
		db_subst apt-setup/uri_type note "You probably used a CD to install the Debian base system, but it is not currently in the drive. You should probably just insert it and select \"cdrom\"."
	fi
fi

while [ "$MAINLOOP" ] || add_another; do
	# Ask what source apt should install from.
	db_fset apt-setup/uri_type isdefault true
	db_input critical apt-setup/uri_type || exit 0
	db_go || exit 1
	db_get apt-setup/uri_type
	URI="$RET"
	# Clear any note that is on the uri_type, now that they've seen it.
	db_subst apt-setup/uri_type note ""

	# If they chose to use CD, there is little point in asking
	# these questions, since the CD's they insert will answer them for us.
	# Same goes if they are entering manually.
	if [ "$URI" != "cdrom" -a "$URI" != "edit sources list by hand" ]; then
		db_beginblock
		db_fset apt-setup/distribution isdefault true
		db_input low apt-setup/distribution || true
		db_fset apt-setup/non-free isdefault true
		db_fset apt-setup/non-free isdefault true
		db_input high apt-setup/non-us || true
		db_input high apt-setup/non-free || true
		db_endblock
		db_go || continue

		# If they choose to use non-free, ask about contrib, with a
		# default of yes. Doesn't seem to make much sense to even ask,
		# otherwise.
		db_get apt-setup/non-free
		if [ "$RET" = true ]; then
			db_fset apt-setup/contrib isdefault true
			db_set apt-setup/contrib true
			db_input medium apt-setup/contrib || true
			db_go || continue
		fi
	fi

	case "$URI" in
	ftp|http)
		# Ask them which country they're in.
		db_subst apt-setup/country countries "`country_list $URI`"
		db_fset apt-setup/country isdefault true
		db_input critical apt-setup/country || true
		db_go || continue
		
		db_get apt-setup/country
		if [ "$RET" != "enter information manually" ]; then
			# Now prompt with the mirrors in the selected country.
			db_subst apt-setup/mirror mirrors "`mirror_list $URI \"$RET\"`"
			db_fset apt-setup/mirror isdefault true
			db_input critical apt-setup/mirror || true
			db_go || continue
			
			# Now shove the data about the mirror into some other
			# questions.
			db_get apt-setup/mirror
			MIRROR="$RET"
			db_set apt-setup/hostname $MIRROR
			db_set apt-setup/directory "`mirror_dir $URI $MIRROR`"
		else
			# They elected to enter info manually.
			# Prompt for hostname and directory the mirror is in.
			db_beginblock
			db_fset apt-setup/hostname isdefault true
			db_input critical apt-setup/hostname || true
			db_fset apt-setup/directory isdefault true
			db_input critical apt-setup/directory || true
			db_endblock
			db_go || continue
		fi

		# Ask about a proxy if no proxy is yet defined.
		if [ "$URI" = "http" -a -z "$http_proxy" ]; then
			if [ ! -e "$APTETC/apt.conf" ] || \
			   ! grep -iq 'Acquire::http::Proxy' $APTETC/apt.conf; then
				db_fset apt-setup/http_proxy isdefault true
				db_input high apt-setup/http_proxy || true
				db_go || continue
			fi
		fi
	;;
	cdrom)
		# We've already probed earlier to try to figure out the
		# cd device.
		LOOP=1
		while [ "$LOOP" ]; do
			# Make sure the cd is unmounted, we may be prompting
			# them to change cd's.
			umount /cdrom 2>/dev/null || true
		
			# Prompt for the cd device if it wasn't autodetected.
			# TODO: We could give them a list of likely devices..
			#       This is unfriendly right now.
			if [ -z "$CDDEV" ]; then
				db_input critical apt-setup/cd/dev || true
				db_go || continue 2
				db_get apt-setup/cd/dev
				CDDEV="$RET"
				# Make /dev/cdrom link now, with device
				# they entered. This is for later use by
				# apt.
				if [ "$CDDEV" -a "$CDDEV" != '/dev/cdrom' ]; then
					ln -sf $CDDEV /dev/cdrom
				fi
			fi
			
			# Now try to mount the cdrom.
			if mount $CDDEV /cdrom -o ro -t iso9660; then
				LOOP=""
				if scan_cd; then
					handle_rest_cds $CDDEV
					MAINLOOP=""
				fi
			else
				# Loop and prompt again for cd device.
				db_fset apt-setup/cd/dev isdefault true
				CDDEV=""
			fi
		done
		continue
	;;
	
	filesystem)
		# they need to have a mirror already mounted somewhere.
		URI=file
		LOOP=1
		while [ "$LOOP" ]; do
			db_fset apt-setup/directory isdefault true
			db_input critical apt-setup/directory || true
			db_go || continue 2
			db_get apt-setup/directory
			
			# A very simple mirror sanity check.
			if [ ! -d "$RET" ]; then
				db_fset apt-setup/baddir isdefault true
				db_input critical apt-setup/baddir || true
				db_go || true
			elif [ ! -d "$RET/dists" ]; then
				db_fset apt-setup/not-mirror isdefault true
				db_input critical apt-setup/not-mirror || true
				db_go || true
			else
				LOOP=''
			fi
		done
	;;
	*)
		touch ${APTETC}sources.list
		cp -f ${APTETC}sources.list ${APTETC}sources.list.bak
		# Just run an editor on the sources.list file, then
		# check the result. If it fails, show the problem and loop.
		if [ -z "$EDITOR" ]; then
			EDITOR=/usr/bin/editor
		fi
		EDITOK=""
		while [ ! "$EDITOK" ]; do
			touch ${APTETC}sources.list
			$EDITOR ${APTETC}sources.list >/dev/tty </dev/tty
			tempfile=`tempfile`
			clear >/dev/tty </dev/tty
			echo "Testing apt sources ..."
			if $APTGET -o APT::Get::List-Cleanup=false -o Dir::Etc::sourcelist=${APTETC}sources.list update 2>$tempfile; then
				clear >/dev/tty </dev/tty
				EDITOK=1
			else
				clear >/dev/tty </dev/tty
				db_fset apt-setup/badedit isdefault true
				db_subst apt-setup/badedit apt_error `tr '\n' ' ' < $tempfile`
				db_input critical apt-setup/badedit || true
				db_go || true
				db_get apt-setup/badedit
				if [ "$RET" != "edit" ]; then
					mv -f ${APTETC}sources.list.bak ${APTETC}sources.list
					continue 2
				fi
			fi
		done
		MAINLOOP=""
		rm -f /etc/apt/sources.list.bak
		continue
	esac

	# The temporary file to use as sources.list for testing
	# new items.
	tmpsources=`tempfile`

	db_get apt-setup/hostname
	HOST="$RET"
	db_get apt-setup/directory
	DIR="$RET"
	db_get apt-setup/distribution
	DIST="$RET"
	
	db_get apt-setup/non-free
	if [ "$RET" = true ]; then
		NONFREE="non-free"
	else
		NONFREE=""
	fi
	db_get apt-setup/contrib
	if [ "$RET" = true ]; then
		CONTRIB="contrib"
	else
		CONTRIB=""
	fi
	if [ "$URI" = ftp -o "$URI" = http ]; then
		SEP=//
	else
		SEP=""
	fi
	if [ "$URI" = file ]; then
		HOST=""
	fi

	echo "deb $URI:$SEP$HOST$DIR $DIST main $NONFREE $CONTRIB" >> $tmpsources
	SRCENTRY="deb-src $URI:$SEP$HOST$DIR $DIST main $NONFREE $CONTRIB"

	# If there is a http proxy, make apt use it temporarily.
	db_get apt-setup/http_proxy
	if [ "$RET" ]; then
		PROXY_OPTS="-o Acquire::http::Proxy=\"$RET\""
	else
		PROXY_OPTS=""
	fi

	# As a final sanity check, run apt-get update, and catch the
	# return code and errors.
	tempfile=`tempfile`
	clear >/dev/tty </dev/tty
	echo "Testing apt sources ..."
	if ! $APTGET $PROXY_OPTS -o APT::Get::List-Cleanup=false -o Dir::Etc::sourcelist=$tmpsources update 2>$tempfile; then
		clear >/dev/tty </dev/tty
		# Show the user the error message and loop.
		db_subst apt-setup/badsource apt_error `tr '\n' ' ' < $tempfile`
		db_fset apt-setup/badsource isdefault true
		db_input critical apt-setup/badsource || true
		db_go || true
		rm -f $tempfile $tmpsources
		continue
	fi
	# Success, so add the entry to the real sources.list
	touch ${APTETC}sources.list
	echo "" >> ${APTETC}sources.list
	cat $tmpsources >> ${APTETC}sources.list
	MAINLOOP=""
	
	# Success, so add proxy information if not already present.
	db_get apt-setup/http_proxy
	if [ "$RET" ]; then
		touch $APTETC/apt.conf
		if ! grep -iq 'Acquire::http::Proxy' $APTETC/apt.conf; then
			echo "Acquire::http::Proxy \"$RET\";" >> $APTETC/apt.conf
		fi
	fi
	
	# We have a deb-src line to add too. Test it, see if it works. If not,
	# don't fail, just don't add it.
	echo "$SRCENTRY" > $tmpsources
	if $APTGET -o APT::Get::List-Cleanup=false -o Dir::Etc::sourcelist=$tmpsources update 2>/dev/null; then
		echo "$SRCENTRY" >> ${APTETC}sources.list
	fi
	clear >/dev/tty </dev/tty

	# Handle non-us entries.
	db_get apt-setup/non-us
	if [ "$RET" = true ]; then
		LINE="http://non-us.debian.org/debian-non-US $DIST/non-US main"
		if [ "$CONTRIB" ]; then
			LINE="$LINE contrib"
		fi
		if [ "$NONFREE" ]; then
			LINE="$LINE non-free"
		fi
		# Test the generated line with apt.
		echo "deb $LINE" > $tmpsources
		clear >/dev/tty </dev/tty
		echo "Testing apt sources ..."
		if ! $APTGET -o APT::Get::List-Cleanup=false -o Dir::Etc::sourcelist=$tmpsources update 2>$tempfile; then
			# Display failure message.
			clear >/dev/tty </dev/tty
			echo $LINE
			db_subst apt-setup/non-us-failed apt_error `tr '\n' ' ' < $tempfile`
			db_fset apt-setup/non-us-failed isdefault true
			db_input critical apt-setup/non-us-failed || true
			db_go || true
			rm -f $tempfile $tmpsources
			continue
		fi
		# Success, add to real sources.list.
		echo "deb $LINE" >> ${APTETC}sources.list

		# Try to add a deb-src line too.
		echo "deb-src $LINE" > $tmpsources
		if $APTGET -o APT::Get::List-Cleanup=false -o Dir::Etc::sourcelist=$tmpsources update 2>/dev/null; then
			echo "deb-src $LINE" >> ${APTETC}sources.list
		fi
		clear >/dev/tty </dev/tty
	fi

	rm -f $tempfile $tmpsources
done

# Finally, see about adding a security.debian.org entry. This is a PITA. I
# have to deal with these situations:
# * installing from CD; no network connectivity: add it, commented out
# * already have it in sources.list, uncommented: don't ask about it
# * adding it when the user has picked non-free and/or contrib at least
#   some of the time: include those
# * adding it when the user has not: don't include those

# First, if it exists and is not commented out, don't ask about it.
# (Well, this isn't perfect, because they may have a bad entry that doesn't
# cover what they just added.)
if ! grep -q '^[^#]*security.debian.org' ${APTETC}sources.list; then
	# Figure out what line to add (we're definitely going to add something,
	# even if it's just a comment).
	#
	# To determine if non-free and contrib should be included, grep
	# the file to see if they are listed in it.
	DISTS="main"
	for dist in contrib non-free; do
		if grep -q '^[^#]*non-free' ${APTETC}sources.list; then
			DISTS="$DISTS $dist"
		fi
	done
	LINE="http://security.debian.org/ stable/updates $DISTS"

	COMMENT="# "

	# Now ask if they want the entry to be added.
	db_fset apt-setup/security-updates isdefault true
	db_input medium apt-setup/security-updates || true
	db_go || true
	db_get apt-setup/security-updates
	if [ "$RET" = true ]; then
		# Test it.
		tmpsources=`tempfile`
		echo "deb $LINE" > $tmpsources
		clear >/dev/tty </dev/tty
		echo "Testing apt sources ..."
		tempfile=`tempfile`
		if ! $APTGET -o APT::Get::List-Cleanup=false -o Dir::Etc::sourcelist=$tmpsources update 2>$tempfile; then
			# Display failure message
			clear >/dev/tty </dev/tty
			db_subst apt-setup/security-updates-failed apt_error `tr '\n' ' ' < $tempfile`
			db_fset apt-setup/security-updates-failed isdefault true
			db_input critical apt-setup/security-updates-failed || true
			db_go || true
		else
			# Success. Don't comment them out.
			COMMENT=""
			# TODO: test and add deb-src too.
		fi
	
		rm -f $tempfile $tmpsources
	fi

	# Now add the entry, commented out or not. (Don't add the commented
	# version if a commented version already exists.)
	if [ -z "$COMMENT" ] || ! grep -q '#.*security.debian.org' ${APTETC}sources.list; then
		echo "" >>${APTETC}sources.list
		echo "${COMMENT}deb $LINE" >>${APTETC}sources.list
	fi	
fi

