#!/usr/bin/env bash
set -Eeuo pipefail

thisDir="$(dirname "$(readlink -vf "$BASH_SOURCE")")"
source "$thisDir/.constants.sh" \
	--flags 'debian,debian-eol,debian-ports,non-debian' \
	--flags 'debootstrap:' \
	--flags 'debootstrap-script:' \
	--flags 'keyring:,arch:,include:,exclude:' \
	--flags 'merged-usr,no-merged-usr' \
	--flags 'check-gpg,no-check-gpg' \
	-- \
	'<target-dir> <suite> <timestamp>' \
	'rootfs stretch 2017-05-08T00:00:00Z
--debian-eol rootfs squeeze 2016-03-14T00:00:00Z' \
	\
	'--non-debian [--debootstrap-script=xyz] <target-dir> <suite> <mirror>' \
	'--non-debian rootfs xenial http://archive.ubuntu.com/ubuntu'

eval "$dgetopt"
nonDebian=
debianEol=
debianPorts=
debootstrap=
script=
keyring=
arch=
include=
exclude=
noMergedUsr=
noCheckGpg=
while true; do
	flag="$1"; shift
	dgetopt-case "$flag"
	case "$flag" in
		--debian)       nonDebian= ;;
		--debian-eol)   nonDebian= ; debianEol=1 ;;
		--debian-ports) nonDebian= ; debianPorts=1 ;;
		--non-debian)   nonDebian=1 ;;
		--debootstrap) debootstrap="$1"; shift ;;
		--debootstrap-script) script="$1"; shift ;;
		--keyring) keyring="$1"; shift ;;
		--arch) arch="$1"; shift ;;
		--include) include="${include:+$include,}$1"; shift ;;
		--exclude) exclude="${exclude:+$exclude,}$1"; shift ;;
		--merged-usr)    noMergedUsr=  ;;
		--no-merged-usr) noMergedUsr=1 ;;
		--check-gpg)    noCheckGpg=  ;;
		--no-check-gpg) noCheckGpg=1 ;;
		--) break ;;
		*) eusage "unknown flag '$flag'" ;;
	esac
done

targetDir="${1:-}"; shift || eusage 'missing target-dir'
[ -n "$targetDir" ] || eusage 'target-dir required' # must be non-empty
if [ -e "$targetDir" ] && [ -z "$(find "$targetDir" -maxdepth 0 -empty)" ]; then
	echo >&2 "error: '$targetDir' already exists (and isn't empty)!"
	exit 1
fi

suite="${1:-}"; shift || eusage 'missing suite'

timestamp=
mirror=
if [ -z "$nonDebian" ]; then
	timestamp="${1:-}"; shift || eusage 'missing timestamp'
else
	mirror="${1:-}"; shift || eusage 'missing mirror'

	timestamp="$(
		{
			wget -qO- "$mirror/dists/$suite/InRelease" 2>/dev/null \
				|| wget -qO- "$mirror/dists/$suite/Release" 2>/dev/null
		} | awk -F ': ' '$1 == "Date" { print $2; exit }'
	)" || :
	[ -n "$timestamp" ]

	# see "debuerreotype-recalculate-epoch" for a simple way to update this value appropriately after "sources.list" is updated and "apt-get update" has been run (which will be more accurate)
fi

epoch="$(date --date "$timestamp" '+%s')"
export SOURCE_DATE_EPOCH="$epoch"

if [ -z "$nonDebian" ]; then
	dpkgArch="${arch:-$(dpkg --print-architecture | awk -F- '{ print $NF }')}"
	mirrorArgs=()
	if [ -n "$debianPorts" ]; then
		mirrorArgs+=( --ports )
	fi
	if [ -n "$debianEol" ]; then
		mirrorArgs+=( --eol )
	fi
	mirrorArgs+=( "@$epoch" "$suite" "$dpkgArch" main )
	mirrors="$("$thisDir/.debian-mirror.sh" "${mirrorArgs[@]}")"
	eval "$mirrors"
	[ -n "$snapshotMirror" ]
	mirror="$snapshotMirror"
fi

debootstrapArgs=()

if [ -z "$noCheckGpg" ]; then
	debootstrapArgs+=( --force-check-gpg )
else
	debootstrapArgs+=( --no-check-gpg )
fi

: "${script:=$suite}"
script="$(
	if [ -s "$thisDir/.debootstrap-scripts/$script" ]; then
		readlink -vf "$thisDir/.debootstrap-scripts/$script"
	elif [ -s "/usr/share/debootstrap/scripts/$script" ]; then
		readlink -vf "/usr/share/debootstrap/scripts/$script"
	else
		readlink -vf "$script"
	fi
)"
if grep -q 'minbase' "$script"; then
	# --debian-eol sarge and older do not support minbase
	debootstrapArgs+=( --variant=minbase )
fi

[ -n "$noMergedUsr" ] && debootstrapArgs+=( --no-merged-usr ) || debootstrapArgs+=( --merged-usr )
[ -z "$keyring" ] || debootstrapArgs+=( --keyring="$keyring" )
[ -z "$arch" ] || debootstrapArgs+=( --arch="$arch" )
[ -z "$include" ] || debootstrapArgs+=( --include="$include" )
[ -z "$exclude" ] || debootstrapArgs+=( --exclude="$exclude" )

debootstrapArgs+=(
	"$suite" "$targetDir" "$mirror" "$script"
)

unshare-debootstrap() {
	# avoid bugs in packages that might leave things mounted ("/run/shm" is a common one that sometimes ends up dangling)
	unshare --mount debootstrap "$@"
}

: "${debootstrap:=unshare-debootstrap}"
if ! "$debootstrap" "${debootstrapArgs[@]}"; then
	if [ -f "$targetDir/debootstrap/debootstrap.log" ]; then
		echo >&2
		echo >&2 "error: '$debootstrap' failed!"
		echo >&2
		echo >&2 '  Full command:'
		echo >&2
		echo >&2 "   $(printf ' %q' "$debootstrap" "${debootstrapArgs[@]}")"
		echo >&2
		echo >&2 '  Logs:'
		echo >&2
		cat >&2 "$targetDir/debootstrap/debootstrap.log"
		echo >&2
	fi
	exit 1
fi
echo "$epoch" > "$targetDir/debuerreotype-epoch"

if [ -z "$nonDebian" ]; then
	"$thisDir/debuerreotype-debian-sources-list" --snapshot \
		$([ -z "$debianEol" ] || echo '--eol') \
		$([ -z "$debianPorts" ] || echo '--ports') \
		"$targetDir" "$suite"
	"$thisDir/debuerreotype-apt-get" "$targetDir" update -qq
fi

# since we're minbase, we know everything included is either essential, or a dependency of essential, so let's get clean "apt-mark showmanual" output
"$thisDir/debuerreotype-chroot" "$targetDir" bash -c '
	# --debian-eol squeeze and below do not have python in minbase, thus "apt-mark" fails to run
	# bash: /usr/bin/apt-mark: /usr/bin/python: bad interpreter: No such file or directory
	# (also, squeeze APT does not treat essential packages as special, and will offer to purge them if they get marked as auto-installed)
	if apt-mark --help &> /dev/null; then
		apt-mark auto ".*" > /dev/null
		if [ -n "$1" ]; then
			# if the user asked for anything to be included extra (like "xyz-archive-keyring"), mark those packages as manually installed
			IFS=","; includePackages=( $1 ); unset IFS
			apt-mark manual "${includePackages[@]}"
		fi
	fi
' -- "$include"

echo 'debuerreotype' > "$targetDir/etc/hostname"
{
	echo '# https://1.1.1.1 (privacy-focused, highly-available DNS service)'
	echo 'nameserver 1.1.1.1'
	echo 'nameserver 1.0.0.1'
} > "$targetDir/etc/resolv.conf"
chmod 0644 \
	"$targetDir/etc/hostname" \
	"$targetDir/etc/resolv.conf"

# fix ownership/permissions on / (otherwise "debootstrap" leaves them as-is which causes reproducibility issues)
chown 0:0 "$targetDir"
chmod 0755 "$targetDir"

# https://bugs.debian.org/857803
# adjust field 3 in /etc/shadow and /etc/shadow- to $(( epoch / 60 / 60 / 24 )), if it's larger
sp_lstchg="$(( epoch / 60 / 60 / 24 ))"
for shadowFile in etc/shadow etc/shadow-; do
	# --debian-eol etch and older do not include /etc/shadow-
	[ -e "$targetDir/$shadowFile" ] || continue

	newShadowFile="$shadowFile.debuerreotype"
	awk -F ':' \
		-v OFS=':' \
		-v sp_lstchg="$sp_lstchg" \
		'{
			if ($3 > sp_lstchg) {
				$3 = sp_lstchg
			}
			print
		}' "$targetDir/$shadowFile" > "$targetDir/$newShadowFile"
	if [ "$(< "$targetDir/$shadowFile")" != "$(< "$targetDir/$newShadowFile")" ]; then
		# use "cat" instead of "mv" so permissions don't change
		cat "$targetDir/$newShadowFile" > "$targetDir/$shadowFile"
	fi
	rm -f "$targetDir/$newShadowFile"
done
