#!/bin/bash
set -e

# install-gentoo.sh - installs gentoo in a chroot
# ed <irc.rizon.net>, MIT-licensed, 2019-02-18, v1.0
# https://ocv.me/dev/?install-gentoo.sh


# config
gr="/c/gentoo"
s3flavor="stage3-amd64-hardened+nomultilib"


##
## other config expected to rarely change

#url_stage3="http://distfiles.gentoo.org/releases/amd64/autobuilds"
url_stage3="https://mirror.leaseweb.com/gentoo/releases/amd64/autobuilds"
url_portage="https://mirror.leaseweb.com/gentoo/snapshots"
portage_fn="portage-latest.tar.xz"

# Gentoo Linux Release Engineering (Automated Weekly Release Key)
# https://www.gentoo.org/downloads/signatures/
stage3_expires=2019-08-22
stage3_kid="0xBB572E0E2D182910"
stage3_rsa="13EB BDBE DE7A 1277 5DFD B1BA BB57 2E0E 2D18 2910"

# Gentoo ebuild repository signing key (Automated Signing Key)
# https://www.gentoo.org/downloads/signatures/
portage_expires=2020-01-01
portage_kid="0xDB6B8C1F96D8BF6D"
portage_rsa="DCD0 5B71 EAB9 4199 527F 44AC DB6B 8C1F 96D8 BF6D"


##
## helper methods

msg() { printf '\033[1;30m>\033[0;34m>\033[1m>\033[0m %s\n' "$1" >&2; }
inf() { printf '\n\033[1;30m>\033[0;32m>\033[1m>\033[0;32m %s\033[0m\n' "$1" >&2; }
wrn() { printf '\n\033[1;30m>\033[0;33m>\033[1m>\033[0;33m %s\033[0m\n' "$1" >&2; }
err() { printf '\n\033[1;30m>\033[0;31m>\033[1m>\033[0;31m %s\033[0m\n' "$1" >&2; }

# ensure no trailing slash in the chroot path
gr="${gr%/}"


##
## get the gpg key

check_key_expired()
{
	name="$1"
	expires="$2"
	
	now=$(date -u +%Y-%m-%d)
	cmp=$(
		printf '%s\n' $expires $now |
		sort |
		head -n 1
	)
	
	[ "$expires" = "$cmp" ] || return 0
	
	err "$name pgp key expired on $expires"
	msg "update config section in this script with new pgp keys"
	echo
	exit 1
}

have_key()
{
	local key="$1"
	
	gpg -k |
	awk 'n==1 {print $1} {n=0} /^pub\s/ {n=1}' |
	grep -qE "^$key$"
}

# check key validity
msg "checking if any keys expired"
check_key_expired stage3 $stage3_expires
check_key_expired portage $portage_expires

# strip whitespace in the rsa fingerprint
stage3_rsa="${stage3_rsa// /}"
portage_rsa="${portage_rsa// /}"

msg "checking if we have the gpg keys"
while IFS=' ' read -r kid rsa
do
	have_key "$rsa" ||
	{
		msg "adding gpg key"
		gpg --keyserver hkps.pool.sks-keyservers.net --recv-keys $kid

		have_key "$rsa" ||
		{
			err "failed to retrieve key $kid; offline or mitm"
			exit 1
		}
	}
done < <(
	printf '%s %s\n' \
		"$stage3_kid" "$stage3_rsa" \
		"$portage_kid" "$portage_rsa"
)


##
## verify gpg signed message

gpg_validate()
{
	local rsa="$1"
	gpg_args=("$2")
	
	[ -z "$3" ] ||
		gpg_args+=("$3")
	
	msg "validating integrity of $2"
	gpg --verify "${gpg_args[@]}" 2>&1 | tee tmp.gpg
	
	[ ${PIPESTATUS[0]} -eq 0 ] &&
		msg "gpg validation succeeded" ||
	{
		err "gpg validation failed"
		return 1
	}
	
	awk '
		main==0&& /^gpg: +using RSA key/ {print $5;main=1}
		prim==0&& /^Primary key fingerprint: / {sub(/[^:]+: /,"");print;prim=1}
		subk==0&& /^     Subkey fingerprint: / {sub(/[^:]+: /,"");print;subk=1}
	' tmp.gpg |
	tr -d ' ' |
	grep -qE "^$rsa$" &&
		msg "validated with the expected gpg key" ||
	{
		err "validation succeeded with incorrect gpg key; expected $rsa"
		return 1
	}
	rm tmp.gpg
}

asc_part()
{
	cat "$1" |
	awk '
		/^--+BEGIN PGP SIGNED MESSAGE/ {sig=0;msg=1;next}
		/^--+BEGIN PGP SIGNATURE/ {sig=1;msg=0;next}
		/^--+END PGP/ {exit}
		'$2'==1
	'
}

asc_sanitize()
{
	printf -- '-----BEGIN PGP SIGNED MESSAGE-----\n'
	asc_part "$1" msg
	printf -- '-----BEGIN PGP SIGNATURE-----\n'
	asc_part "$1" sig
	printf -- '-----END PGP SIGNATURE-----\n'
}

asc_validate()
{
	local rsa="$1"
	local fn="$2"
	
	msg "validating structure of $fn"
	printf '\033[33m'
	diff -NarU3 \
		<(asc_sanitize "$fn" | grep -E .) \
		<(grep -E . < "$fn") ||
	{
		err "gpg message has been tampered with"
		return 1
	}
	printf '\033[0m'

	gpg_validate "$rsa" "$fn"
}


##
## stage3

get_stage3_filename()
{
	msg "resolving latest stage3 image"
	printf '\033[1;30m' >&2
	# expected file contents:
	# 20190131T214503Z/hardened/stage3-amd64-hardened+nomultilib-20190131T214503Z.tar.xz 190697312
	wget -qO- "$url_stage3/latest-$s3flavor.txt" |
	tee /dev/stderr |
	awk '/^ *[^# ]/ {sub(/.*\//,"");sub(/ .*/,"");print;exit}'
}

download_stage3()
{
	s3fn="$(get_stage3_filename)"
	#s3fn='stage3-amd64-hardened+nomultilib-20190203T214502Z.tar.xz'

	msg "stage3 filename: [$s3fn]"
	wget -nc "$url_stage3/current-$s3flavor/$s3fn"{,.DIGESTS.asc}

	asc_validate "$stage3_rsa" "$s3fn.DIGESTS.asc"

	msg "verifying checksum of the stage3 image"
	hash="$(sha512sum "$s3fn")"

	msg "$hash"
	grep -qF "$hash" "$s3fn.DIGESTS.asc" ||
	{
		err "stage3 checksum incorrect"
		exit 1
	}

	inf "stage3 download verified"
}


##
## grab portage snapshot

download_portage()
{
	rm -f "$portage_fn.gpgsig"
	wget -nc "$url_portage/$portage_fn.gpgsig"

	[ -e "$portage_fn" ] &&
	{
		gpg_validate "$portage_rsa" "$portage_fn"{.gpgsig,} ||
		{
			exit 1
			msg "probably outdated snapshot, deleting and redownloading"
			rm -f "$portage_fn"
		}
	}

	[ -e "$portage_fn" ] ||
		wget -nc "$url_portage/$portage_fn"

	gpg_validate "$portage_rsa" "$portage_fn"{.gpgsig,}

	inf "portage download verified"
}


##
## do the install

install_gentoo()
{
	[ -e "$gr/etc" ] &&
	{
		err "chroot already exists: $gr"
		msg "rm -rf the folder to continue"
		echo
		exit 1
	}

	mkdir -p "$gr"
	cd "$gr"

	download_stage3
	download_portage
	
	msg "unpacking stage3"
	tar xvJpf "$s3fn"

	msg "unpacking portage"
	tar xvJf "$portage_fn" -C usr
}


##
## make an activation script

write_chroot_script()
{
	msg "creating chroot scripts"

	# deactivate
	{
		cat <<'EOFF'
deactivate()
{
	mount |
	grep -E "^[^ ]+ on chrootdir(/| )" |
	awk '{print $3}' |
	xargs umount 
}

printf '\033[33munmounting chroot stuff\033[0m\n'
deactivate 2>/dev/null ||
deactivate 2>/dev/null ||
deactivate # :^)
EOFF
	} |
	sed -r "s@chrootdir@$gr@g" > "$gr"/deactivate

	# activate
	{
		cat <<'EOFF'
set -e
cd "chrootdir"

mount |
grep -qF " on chrootdir" ||
{
	mount --rbind /dev  dev
	mount --make-rslave dev
	mount -t proc /proc proc
	mount --rbind /sys  sys
	mount --make-rslave sys
	mount --rbind /tmp  tmp
}

cp /etc/resolv.conf etc
cat <<'EOF'

# entering the chroot now;
# copy-paste the following if you make it in:
source /etc/profile
env-update
#emerge --sync
emerge-webrsync

EOF
chroot "chrootdir" /bin/bash &&
	rv=0 || rv=$?

[ $rv -eq 0 ] ||
{
	printf "\033[1;31m\n[!] left chroot with exit code $rv\033[0m\n\n" >&2
	exit $rv
}

find /proc/ -mindepth 2 -maxdepth 2 -name exe -printf '%l\n' 2>/dev/null |
grep -qE '^chrootdir' &&
{
	printf '\033[33mother shells using the chroot; will not deactivate\033[0m\n' >&2
} ||
{
	chrootdir/deactivate
}
EOFF
	} |
	sed -r "s@chrootdir@$gr@g" > "$gr"/activate

	chmod 755 "$gr"/activate "$gr"/deactivate

	inf "done; run this: $gr/activate"
	echo
}


##
## nothing really happens until this point;
## let's go

install_gentoo
write_chroot_script
