#!/usr/bin/env zsh

## debuerreotype sequence
## docker run --cap-add SYS_ADMIN --cap-drop SETFCAP --tmpfs /tmp:dev,exec,suid,noatime ...

# bootstrapping a new architecture?
#   ./scripts/debuerreotype-init /tmp/docker-rootfs bullseye now
#   ./scripts/debuerreotype-minimizing-config /tmp/docker-rootfs
#   ./scripts/debuerreotype-debian-sources-list /tmp/docker-rootfs bullseye
#   ./scripts/debuerreotype-tar /tmp/docker-rootfs - | docker import - debian:bullseye-slim
# alternate:
#   debootstrap --variant=minbase bullseye /tmp/docker-rootfs
#   tar -cC /tmp/docker-rootfs . | docker import - debian:bullseye-slim
# (or your own favorite set of "debootstrap" commands to create a base image for building this one FROM)

## 

build_docker_dist() {
	fn build_docker_dist
	req=(workdir strapdir os arch size imageformat parted_type)
	ckreq || return 1

	notice "Building complete docker image(s)"

	act "obtain apt/dpkg versions."
	apt_version=apt-version
	dpkg_version=dpkg-version

	act "create and populate the rootfs."
	bootstrap_complete_base-wrapper    || { zerr; return 1; }
	blend_preinst                      || { zerr; return 1; }
	blend_postinst                     || { zerr; return 1; }

	notice "import, tag and push them image."

	docker-init-policy                 || { zerr; return 1; }
#	docker-hostname                    || { zerr; return 1; }
#	docker-resolvconf                  || { zerr; return 1; }

	act "apt related fixups move to sysconf-docker."
	docker-apt-speedup                 || { zerr; return 1; }
	docker-apt-autoremove-suggests     || { zerr; return 1; }
	docker-apt-clean	           || { zerr; return 1; }
	docker-apt-gzip-indexes	           || { zerr; return 1; }
	docker-apt-no-languages            || { zerr; return 1; }

	act "import, tag and push the image."
	docker-import                      || { zerr; return 1; }
	docker-push                        || { zerr; return 1; }

	act "slimify the rootfs."
	docker-purge_slimify_package       || { zerr; return 1; }
	docker-slimify                     || { zerr; return 1; }

	act "import, tag and push the slim image."
	docker-import --slim               || { zerr; return 1; }
	docker-push --slim                 || { zerr; return 1; }
	clean_strapdir                     || { zerr; return 1; }	
}


docker-push() {
	fn docker-push
	req=(R strapdir release registry_url registry_user registry_token registry_tag)
	ckreq || return 1

	notice "beginning the push procedure."
	act "registry login"
	docker login -u ${registry_user} -p ${registry_token} ${registry_url}
	
	#${registry_tag}  # date, $CI_COMMIT_SHA, $CI_COMMIT_REF_SLUG

	if [[ -n $1 ]]; then
		case $1 in
		    -s|--slim) 
			    release=${release}-slim 
			    act "setting release to ${release}."
			    shift
			    ;;
		    *) 
			    echo "Unknown parameter passed: $1" 
			    exit 1
			    ;;
		esac
	fi

	docker tag  ${registry_url}/${release}:${registry_tag} ${registry_url}:${release}
	docker push ${registry_url}/${release}
	docker logout ${registry_url}
}


docker-import() {
	fn docker-import
	req=(R strapdir release apt_version)
	ckreq || return 1

	notice "tar-ing the \$strapdir before importing for docker."
	act "adding additional excludes for apt versions older than 0.8"
	if dpkg --compare-versions "$apt_version" '>=' '0.8~'; then
	# if APT is new enough to auto-recreate "partial" directories, let it
	# (https://salsa.debian.org/apt-team/apt/commit/1cd1c398d18b78f4aa9d882a5de5385f4538e0be)
	excludes+=(
		'./var/cache/apt/**'
		'./var/lib/apt/lists/**'
		'./var/state/apt/lists/**'
	)
	# (see also the targeted exclusions in "tar-excludes" that these are overriding)
	fi

	act "calling docker-fixup before tar."
	docker-fixup

	act "setting arguments for tar."
	tarArgs=(
	--create
	--file "$strapdir"
	--auto-compress
	--directory "$strapdir"
	--exclude-from "${BLENDPATH}/tar-excludes"
	)

	if [ -z "$includeDev" ]; then
		act "excluding the contents of /dev from the archive."
	excludes+=( './dev/**' )
	fi

	for exclude in "${excludes[@]}"; do
		tarArgs+=( --exclude "$exclude" )
	done

	tarArgs+=(
		--numeric-owner
		--transform 's,^./,,'
		--sort name
		.
	)

	if [[ -n $1 ]]; then
		case $1 in
		    -s|--slim) 
			    release=${release}-slim 
			    act "setting release to ${release}." 
			    shift
			    ;;
		    *) 
			    echo "Unknown parameter passed: $1"
			    exit 1
			    ;;
		esac
	fi

	act "calling tar."
	tar "${tarArgs[@]}" - | docker import - devuan:${release}
}


	docker-fixup() {
	fn docker-fixup
	req=(R strapdir )
	ckreq || return 1
	
	notice "removing redundant log and cache files."
	pushd "$strapdir" || { zerr; return 1; }
	rm -f \
	"./var/log/dpkg.log" \
	"./var/log/bootstrap.log" \
	"./var/log/alternatives.log" \
	"./var/cache/ldconfig/aux-cache"

	act "removing redundant /run mounts."
	# https://github.com/debuerreotype/debuerreotype/pull/32
	rm -f "./run/mount/utab"
	
	# (also remove the directory, but only if it's empty)
	rmdir "./run/mount" 2>/dev/null || :
	
	# fix ownership/permissions on / (otherwise "debootstrap" leaves them as-is which causes reproducibility issues)
	chown 0:0 "./"
	chmod 0755 "./"
	popd
}


docker-slimify() {
	fn docker-slimify
	req=(R strapdir )
	ckreq || return 1

	notice "Removing files specified in the blends config-docker \$slimExcludes variable and adding the paths to the dpkg config."
	dest=$strapdir/etc/dpkg/dpkg.cfg.d
	dpkgCfgFile="${dest}/docker"
	mkdir -p $(dirname "$dpkgCfgFile")
	
	cat > ${dpkgCfgFile} <<-EOF
		# This is the "slim" variant of the Devuan base image.
		# Many files which are normally unnecessary in containers are excluded,
		# and this configuration file keeps them that way.
	EOF
	act "make a record of the directory structure under /usr/share/man."
	extraSpecialDirectories=( "$strapdir"/usr/share/man/man[0-9]/ )

	act "processing the includes in \$slimIncludes"
	findMatchIncludes=()
	for slimInclude in "${slimIncludes[@]}"; do
		[ "${#findMatchIncludes[@]}" -eq 0 ] || findMatchIncludes+=( '-o' )
		findMatchIncludes+=( -path "$slimInclude" )
	done
	findMatchIncludes=( '(' "${findMatchIncludes[@]}" ')' )

	act "processing the excludes in \$slimIncludes"
	for slimExclude in "${slimExcludes[@]}"; do
		{
			echo
			echo "# dpkg -S '$slimExclude'"
			if dpkgOutput="$(chroot-script /bin/bash -c " \
				dpkg -S "$slimExclude" 2>&1")"; then
				echo "$dpkgOutput" | sed 's/: .*//g; s/, /\n/g' | sort -u | xargs
			else
				echo "$dpkgOutput"
			fi | fold -w 76 -s | sed 's/^/#  /'
			echo "path-exclude $slimExclude"
		} >> "$dpkgCfgFile"

		notice "remove directories from slimExcludes in two passes."
		if [[ "$slimExclude" == *'/*' ]]; then
			if [ -d "$strapdir/$(dirname "$slimExclude")" ]; then
				# use two passes so that we don't fail trying to remove directories from $slimIncludes
				# this is our best effort at implementing https://sources.debian.net/src/dpkg/stretch/src/filters.c/#L96-L97 in shell

				act "pass one: delete everything that doesn't match '\$slimIncludes' and isn't a directory or a symlink"
				chroot-script /bin/bash -c " \
					find "$(dirname "$slimExclude")" \
						-depth -mindepth 1 \
						-not \( -type d -o -type l \) \
						-not "${findMatchIncludes[@]}" \
						-exec rm -f '{}' ';'
						"

				act "pass two: repeatedly delete any dangling symlinks and empty directories until there aren't any"
				# (might have a dangling symlink in a directory which then makes it empty, or a symlink to an empty directory)
				while [ "$(
					chroot-script /bin/bash -c " \
						find "$(dirname "$slimExclude")" \
							-depth -mindepth 1 \( -empty -o -xtype l \) \
							-exec rm -rf '{}' ';' -printf '.' \
						| wc -c "
				)" -gt 0 ]; do true; done
			fi
		else
			chroot-script bash -c "
				rm -f "$slimExclude"
			"
		fi
	done

	act "appending includes to the dpkg conf."
	{
		echo
		for slimInclude in "${slimIncludes[@]}"; do
			echo "path-include $slimInclude"
		done
	} >> "$dpkgCfgFile"
	
	chmod 0644 "$dpkgCfgFile"

	act "recreate directory structure under /usr/share/man"
	if [ "${#extraSpecialDirectories[@]}" -gt 0 ]; then
		mkdir -p "${extraSpecialDirectories[@]}"
	fi
}


docker-purge_slimify_package() {
	fn docker-purge_slimify_packages
	req=(strapdir slimify_packages)
	ckreq || return 1

	cat <<-EOF | sudo tee "$strapdir/purge_slimify_packages" >/dev/null
		#!/bin/sh
		apt-get --yes --force-yes purge ${purge_packages_option} ${slimify_packages} || exit 1
		apt-get --yes --force-yes --purge autoremove || exit 1
		apt-get clean
	EOF

	chroot-script -d purge_slimify_packages || { zerr; return 1; }
}


docker-init-policy() {
	fn docker-init-policy
	req=(R strapdir)
	ckreq || return 1

	dest=$strapdir/usr/sbin
	cat > ${dest}/policy-rc.d.docker <<EOF
#!/bin/sh
exit 101		# action forbidden by policy
EOF
	chmod 0755 "${dest}/policy-rc.d.docker"

	cat <<-EOF | sudo tee ${strapdir}/init-policy >/dev/null
		#!/bin/sh

		update-alternatives --quiet --install /usr/sbin/policy-rc.d \
		    policy-rc.d /usr/sbin/policy-rc.d.docker 100

		case "${release}" in
		    jessie|ascii)
		        dpkg-divert --quiet --local --add /sbin/initctl
		        ;;
		    *)
		        dpkg-divert --quiet --local --no-rename --add /sbin/initctl
		        ;;
		esac
		ln -fs /bin/true /sbin/initctl
	EOF
	chroot-script -d init-policy
}


docker-apt-speedup() {
	fn docker-apt-speedup
	req=(R strapdir)
	ckreq || return 1

	dest=$strapdir/etc/dpkg/dpkg.cfg.d
	sudo mkdir -p ${dest}

	cat <<-EOF | sudo tee ${dest}/docker-apt-speedup" >/dev/null
		force-unsafe-io
	EOF
	chmod 0644 "${dest}/docker-apt-speedup"
}


docker-apt-autoremove-suggests() {
	fn docker-apt-autoremove-suggests
	req=(R strapdir)
	ckreq || return 1

	dest=$strapdir/etc/apt/apt.conf.d
	sudo mkdir -p ${dest}

	cat <<-EOF | sudo tee ${dest}/docker-apt-autoremove-suggests" >/dev/null
		Apt::AutoRemove::SuggestsImportant "false";
	EOF
	chmod 0644 "${dest}/docker-apt-autoremove-suggests"
}


docker-apt-clean() {
	fn docker-apt-clean
	req=(R strapdir)
	ckreq || return 1

	dest=$strapdir/etc/apt/apt.conf.d
	sudo mkdir -p ${dest}

	aptGetClean='"rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true";'

	cat <<-EOF | sudo tee ${dest}/docker-apt-clean" >/dev/null
		APT::Update::Post-Invoke { $aptGetClean };

		Dir::Cache::pkgcache "";
		Dir::Cache::srcpkgcache "";
	EOF
	chmod 0644 "${dest}/docker-apt-clean"
}


docker-apt-gzip-indexes() {
	fn docker-apt-gzip-indexes
	req=(R strapdir)
	ckreq || return 1

	dest=$strapdir/etc/apt/apt.conf.d
	sudo mkdir -p ${dest}

	cat <<-EOF | sudo tee ${dest}/docker-apt-gzip-indexes" >/dev/null
		Acquire::GzipIndexes "true";
	EOF
	chmod 0644 "${dest}/docker-apt-gzip-indexes"
}


docker-apt-no-languages() {
	fn docker-apt-no-languages
	req=(R strapdir)
	ckreq || return 1

	dest=$strapdir/etc/apt/apt.conf.d
	sudo mkdir -p ${dest}

	cat <<-EOF | sudo tee ${dest}/docker-apt-no-languages" >/dev/null
		Acquire::Languages "none";
	EOF
	chmod 0644 "${dest}/docker-apt-no-languages"
}


remove-apt-comments() {
	fn remove-apt-comments
	req=(R strapdir apt_version)
	ckreq || return 1

	dest=$strapdir/etc/apt/apt.conf.d
	if dpkg --compare-versions "$apt_version" '>=' '0.7.22~'; then
		exit
	fi

	sed -ri -e 's!^#!//!' "${dest}"
}


# set in zlibs/sysconf:conf_print_hostname() using $os
# override in $BLENDPATH/sysconf:conf_print_hosts
# a container’s hostname defaults to be the container’s ID in 
# Docker unless overriden at build. So this is less important here.
docker-hostname() {
	fn docker-hostname
	req=(R strapdir hostname)
	ckreq || return 1

	pushd
	act "setting the hostname."
	echo "${hostname}" > "./etc/hostname"
	chmod 0644 \
		"$strapdir/etc/hostname"
	popd
}	

# set in zlibs/sysconf:conf_print_resolvconf() 
# override in $BLENDPATH/sysconf
docker-resolvconf() {
	fn docker-resolvconf
	req=(R strapdir)
	ckreq || return 1

	pushd $strapdir
	act "replacing /etc/resolv.conf."
	cat <<-EOF | sudo tee ./etc/resolv.conf" >/dev/null
		# https://1.1.1.1 (privacy-focused, highly-available DNS service)
		nameserver 1.1.1.1
		nameserver 1.0.0.1
	EOF
	chmod 0644 \
		"./etc/resolv.conf"
	popd
}


# If inside a virtual machine or container environment the
# /etc/machine-id is usually taken from the container UUID
#docker-id() {
#	fn docker-id
#	req=(R strapdir epoch)
#	ckreq || return 1
#
#	pushd $strapdir
	
	#act "Initialize the machine ID in /etc/machine-id"
	#echo "$epoch" \
#		| md5sum \
#		| cut -f1 -d' ' \
#		> "./etc/machine-id" 
#	chmod 0644 \
#		"./etc/machine-id" 
#	popd
#}
