#!/bin/sh
# iron-plugin-sdk: build a Wise Foundry Iron plugin outside Iron's tree, in the
# plugin SDK kit (docs/plugin-sdk.md). The kit's entrypoint; a project is the
# directory mounted at /work.
#
#   iron-plugin-sdk new ID            write a project from the template to ./ID
#   iron-plugin-sdk build [--unsigned]
#                                     check and build ./plugin.sh into ./dist
#   iron-plugin-sdk keygen --vendor-name NAME --plugin-ids "PATTERN ..."
#                                     a vendor key and the request to send
#   iron-plugin-sdk inspect FILE [--json]
#                                     what a host of this release makes of FILE
#   iron-plugin-sdk version           the Iron release this kit builds for
#
#	/sdk/kit.env         IRON_VERSION, IRON_VARIANT, IRON_ARTIFACT of the kit
#	/sdk/src             the parts of Iron a build uses (build/mksdk.sh)
#	/sdk/template        what `new` copies
#	./plugin.sh          the project's build, run with SRC, IRON_VERSION,
#	                     BASE_SBOM, OUT and IRON_PLUGIN_UI_SRC set
#	./signing/           vendor.key (never leaves this directory), vendor.pub,
#	                     vendor-request.txt, and cert/ once Wise Global
#	                     Solutions returns vendor.cert and vendor.cert.sig
#	./dist/              the built .ironplug
#	./.cache/            Go's module and build caches, kept between runs
#	IRON_VENDOR_KEY, IRON_VENDOR_CERT  override ./signing/vendor.key and
#	                     ./signing/cert
#
# Exit 2 for usage or a refusal, 1 for failure. `inspect` exits as
# `iron-plugin inspect` does: 3 when this release would not run the plugin.
set -eu

SDK=${SDK:-/sdk}
# shellcheck source=/dev/null
. "$SDK/kit.env"
SRC=${SRC:-$SDK/src}
export SRC IRON_VERSION IRON_VARIANT

die() {
	_code=$1
	shift
	echo "iron-plugin-sdk: $*" >&2
	exit "$_code"
}

usage() {
	sed -n '5,14p' "$0" | sed 's/^# \{0,1\}//' >&2
	exit 2
}

# kernel: the base image's kernel, which a plugin carrying modules must name.
kernel() { sed -n 1p "$SRC/out/work/$IRON_ARTIFACT/kernel-version"; }

# cmd_new ID: copy the template to ./ID with its placeholders filled in.
cmd_new() {
	[ $# = 1 ] || usage
	_id=$1
	echo "$_id" | grep -Eq '^[a-z][a-z0-9-]{1,31}$' || die 2 "a plugin id is a lower-case letter then 1 to 31 of a-z 0-9 -: $_id"
	[ ! -e "$_id" ] || die 2 "./$_id already exists"
	# A feature id is dotted (internal/plugins validFeature): the vendor
	# part, then the rest of the id.
	case $_id in
	*-*) _feature="${_id%%-*}.${_id#*-}" ;;
	*) _feature="$_id.main" ;;
	esac
	cp -R "$SDK/template" "$_id"
	find "$_id" -type f | while read -r _f; do
		sed -i -e "s/@ID@/$_id/g" -e "s/@FEATURE@/$_feature/g" \
			-e "s/@IRON_VERSION@/$IRON_VERSION/g" "$_f"
	done
	chmod 0755 "$_id/plugin.sh"
	echo "==> iron-plugin-sdk: ./$_id, feature $_feature, for Iron $IRON_VERSION"
	echo "    cd $_id and run: iron-plugin-sdk build"
}

# cmd_build [--unsigned]: typecheck and test what the project has, then run
# its plugin.sh, signed with the vendor key when there is a certificate.
cmd_build() {
	_unsigned=0
	while [ $# -gt 0 ]; do
		case $1 in
		--unsigned) _unsigned=1 ;;
		*) usage ;;
		esac
		shift
	done
	[ -f plugin.sh ] || die 2 "no plugin.sh here: run build in a project (iron-plugin-sdk new ID)"
	export GOMODCACHE="$PWD/.cache/go-mod" GOCACHE="$PWD/.cache/go-build"
	if [ -f go.mod ]; then
		echo "==> go vet and go test"
		go vet ./... || die 1 "go vet failed"
		go test ./... || die 1 "go test failed"
	fi
	if [ -f ui/tsconfig.json ]; then
		echo "==> typecheck the console extension"
		"$SRC/web/node_modules/.bin/tsc" -p ui || die 1 "the console extension does not typecheck"
	fi
	_key=${IRON_VENDOR_KEY:-$PWD/signing/vendor.key}
	_cert=${IRON_VENDOR_CERT:-$PWD/signing/cert}
	unset IRON_VENDOR_KEY IRON_VENDOR_CERT IRON_PLUGIN_UNSIGNED
	if [ "$_unsigned" = 1 ]; then
		export IRON_PLUGIN_UNSIGNED=1
	elif [ -f "$_key" ] && [ -f "$_cert/vendor.cert" ]; then
		export IRON_VENDOR_KEY="$_key" IRON_VENDOR_CERT="$_cert"
	else
		echo "note: no signing/vendor.key with signing/cert/vendor.cert; building unsigned, which a host installs only when told to (docs/plugin-sdk.md \"Signing\")" >&2
		export IRON_PLUGIN_UNSIGNED=1
	fi
	export BASE_SBOM="$SRC/out/work/$IRON_ARTIFACT/sbom.txt" OUT="$PWD/dist"
	export IRON_BASE_KERNEL
	IRON_BASE_KERNEL=$(kernel)
	[ ! -d ui ] || export IRON_PLUGIN_UI_SRC="$PWD/ui"
	mkdir -p dist
	bash ./plugin.sh || die 1 "plugin.sh failed"
}

# cmd_keygen --vendor-name NAME --plugin-ids PATTERNS: an Ed25519 key in
# ./vendor, and the request to send for a certificate. The key is never
# overwritten: a vendor whose certificate names one key cannot use another.
cmd_keygen() {
	_name='' _ids=''
	while [ $# -gt 0 ]; do
		case $1 in
		--vendor-name) [ $# -ge 2 ] || usage; _name=$2; shift ;;
		--plugin-ids) [ $# -ge 2 ] || usage; _ids=$2; shift ;;
		*) usage ;;
		esac
		shift
	done
	[ -n "$_name" ] && [ -n "$_ids" ] || usage
	# The certificate's own rule for VENDOR_NAME (build/mkvendorcert.sh).
	printf '%s' "$_name" | grep -Eq '^[ -~]{1,64}$' || die 2 "--vendor-name is printable ASCII, at most 64 characters"
	case $_name in *'"'* | *\\* | *'$'* | *'`'*) die 2 "--vendor-name may not contain \" \\ \$ or \`" ;; esac
	[ ! -e signing/vendor.key ] || die 2 "signing/vendor.key exists; a certificate names one key, so it is never replaced"
	mkdir -p signing
	chmod 0700 signing
	(umask 077 && openssl genpkey -algorithm ed25519 -out signing/vendor.key) || die 1 "openssl could not make a key"
	openssl pkey -in signing/vendor.key -pubout -out signing/vendor.pub || die 1 "openssl could not write the public key"
	{
		echo "# A request for a Wise Foundry Iron plugin vendor certificate"
		echo "# (docs/plugins.md \"Third-party plugins\"). Send this file; never send vendor.key."
		echo "VENDOR_NAME=\"$_name\""
		echo "PLUGIN_IDS=\"$_ids\""
		echo "IRON_VERSION=$IRON_VERSION"
		echo "PUBKEY_SHA256=$(openssl pkey -pubin -in signing/vendor.pub -outform DER | sha256sum | cut -d' ' -f1)"
		echo "PUBKEY_PEM<<EOF"
		cat signing/vendor.pub
		echo "EOF"
	} >signing/vendor-request.txt
	echo "==> iron-plugin-sdk: signing/vendor.key (keep it; it signs your plugins and never leaves this machine)"
	echo "    send signing/vendor-request.txt to Wise Global Solutions; put the vendor.cert and"
	echo "    vendor.cert.sig you get back in signing/cert/, and build signs with them."
}

# cmd_inspect FILE [--json]: the host's own inspection, answering as a host of
# this kit's release: its Alpine, its kernel, its update key and revocations.
cmd_inspect() {
	[ $# -ge 1 ] || usage
	_var=$(mktemp -d)
	trap 'rm -rf "$_var"' EXIT
	_rc=0
	IRON_LIB="$SRC/tools/lib.sh" IRON_KEYS_DIR="$SRC/keys" IRON_VAR_DIR="$_var" \
		IRON_KERNEL=$(kernel) IRON_ALPINE_RELEASE=/etc/alpine-release \
		sh "$SRC/tools/iron-plugin" inspect "$@" || _rc=$?
	return "$_rc"
}

[ $# -ge 1 ] || usage
cmd=$1
shift
case $cmd in
new) cmd_new "$@" ;;
build) cmd_build "$@" ;;
keygen) cmd_keygen "$@" ;;
inspect) cmd_inspect "$@" ;;
version) echo "Iron $IRON_VERSION ($IRON_VARIANT), Alpine $(cut -d. -f1,2 /etc/alpine-release), kernel $(kernel)" ;;
help | -h | --help) usage ;;
*) usage ;;
esac
