#!/bin/sh
set -eu

BASE_URL="${CECIL_ROUTER_BASE_URL:-https://download.iipa.cc/router/openwrt}"
ROOT_PREFIX="${CECIL_ROUTER_ROOT:-}"
ROOT_PREFIX="${ROOT_PREFIX%/}"
TMP_DIR="${TMPDIR:-/tmp}/cecilrouter-install.$$"
MANIFEST="$TMP_DIR/manifest.json"
BACKUP_DIR="$TMP_DIR/backup"

CECIL_OWNED_PATHS="
/etc/config/cecil
/etc/init.d/cecil
/etc/uci-defaults/99-cecil-luci-zh-cn
/usr/libexec/cecil
/usr/share/luci/menu.d/luci-app-cecil.json
/usr/share/rpcd/acl.d/luci-app-cecil.json
/www/luci-static/resources/view/cecil
"

cleanup() {
	rm -rf "$TMP_DIR"
}

trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM

root_path() {
	printf '%s%s\n' "$ROOT_PREFIX" "$1"
}

record_missing() {
	printf 'missing=%s\n' "$1" >&2
	PREFLIGHT_MISSING=$((PREFLIGHT_MISSING + 1))
}

preflight_system() {
	PREFLIGHT_MISSING=0

	for required_directory in \
		/usr/share/luci/menu.d \
		/usr/share/rpcd/acl.d \
		/www/luci-static/resources/view
	do
		if [ ! -d "$(root_path "$required_directory")" ]; then
			record_missing "$required_directory"
		fi
	done

	for required_file in /www/cgi-bin/luci /etc/init.d/rpcd; do
		if [ ! -e "$(root_path "$required_file")" ]; then
			record_missing "$required_file"
		fi
	done

	for required_command in uci ubus lua sha256sum tar; do
		if ! command -v "$required_command" >/dev/null 2>&1; then
			record_missing "command:$required_command"
		fi
	done

	if command -v lua >/dev/null 2>&1; then
		if ! lua -e 'local ok=pcall(require, "openssl"); os.exit(ok and 0 or 1)' \
			>/dev/null 2>&1
		then
			record_missing "lua-module:openssl"
		fi
	fi

	if command -v opkg >/dev/null 2>&1 &&
		[ -d "$(root_path /usr/lib/opkg)" ]
	then
		PACKAGE_MANAGER="opkg"
	elif command -v apk >/dev/null 2>&1 &&
		[ -d "$(root_path /lib/apk/db)" ]
	then
		PACKAGE_MANAGER="apk"
	else
		record_missing "package-manager:opkg-or-apk"
	fi

	if [ "$PREFLIGHT_MISSING" -ne 0 ]; then
		printf 'error=luci_preflight_failed\n' >&2
		printf 'action=install_required_components_manually\n' >&2
		return 1
	fi

	printf 'preflight=ok\n'
	printf 'package_manager=%s\n' "$PACKAGE_MANAGER"
}

fetch() {
	local url="$1"
	local output="$2"

	if command -v uclient-fetch >/dev/null 2>&1; then
		if uclient-fetch -q -O "$output" "$url"; then
			return 0
		fi
		rm -f "$output"
	fi

	if command -v wget >/dev/null 2>&1; then
		if wget -q -O "$output" "$url"; then
			return 0
		fi
		rm -f "$output"
	fi

	printf 'error=download_failed\n' >&2
	printf 'url=%s\n' "$url" >&2
	return 1
}

json_value() {
	local key="$1"
	sed -n "s/.*\"$key\"[[:space:]]*:[[:space:]]*\"\\([^\"]*\\)\".*/\\1/p" \
		"$MANIFEST" | sed -n '1p'
}

backup_cecil_files() {
	local owned_path
	local source_path
	local backup_parent

	mkdir -p "$BACKUP_DIR"
	for owned_path in $CECIL_OWNED_PATHS; do
		source_path="$(root_path "$owned_path")"
		if [ -e "$source_path" ]; then
			backup_parent="$BACKUP_DIR$(dirname "$owned_path")"
			mkdir -p "$backup_parent"
			cp -a "$source_path" "$backup_parent/"
		fi
	done
	printf 'backup=ok\n'
}

rollback_cecil_files() {
	local owned_path
	local target_root

	for owned_path in $CECIL_OWNED_PATHS; do
		rm -rf "$(root_path "$owned_path")"
	done

	target_root="$(root_path /)"
	if [ -d "$BACKUP_DIR" ]; then
		cp -a "$BACKUP_DIR/." "$target_root"
	fi
	printf 'rollback=restored\n' >&2
}

verify_installed_files() {
	local required_file
	local required_path

	REQUIRED_FILES="
/usr/libexec/cecil/cecil-router
/usr/share/luci/menu.d/luci-app-cecil.json
/usr/share/rpcd/acl.d/luci-app-cecil.json
/www/luci-static/resources/view/cecil/overview.js
"

	for required_file in $REQUIRED_FILES; do
		required_path="$(root_path "$required_file")"
		if [ ! -f "$required_path" ]; then
			printf 'error=installed_file_missing\n' >&2
			printf 'missing=%s\n' "$required_file" >&2
			return 1
		fi
	done

	if [ ! -x "$(root_path /usr/libexec/cecil/cecil-router)" ]; then
		printf 'error=router_helper_not_executable\n' >&2
		return 1
	fi
}

printf 'CecilRouter OpenWrt installer\n'
printf 'base_url=%s\n' "$BASE_URL"

if ! preflight_system; then
	exit 7
fi

mkdir -p "$TMP_DIR"
fetch "$BASE_URL/manifest.json" "$MANIFEST"

VERSION="$(json_value version)"

if [ "$PACKAGE_MANAGER" = "opkg" ]; then
	PACKAGE="$(json_value package_ipk)"
	SHA256="$(json_value sha256_ipk)"
else
	PACKAGE="$(json_value package_apk)"
	SHA256="$(json_value sha256_apk)"
	# Accept the legacy manifest during CDN propagation.
	[ -n "$PACKAGE" ] || PACKAGE="$(json_value package)"
	[ -n "$SHA256" ] || SHA256="$(json_value sha256)"
fi

if [ -z "$PACKAGE" ] || [ -z "$SHA256" ]; then
	printf 'error=package_unavailable_for_manager\n' >&2
	printf 'package_manager=%s\n' "$PACKAGE_MANAGER" >&2
	exit 2
fi

PKG_PATH="$TMP_DIR/$PACKAGE"
fetch "$BASE_URL/$PACKAGE" "$PKG_PATH"

ACTUAL_SHA256="$(sha256sum "$PKG_PATH" | awk '{ print $1 }')"
if [ "$ACTUAL_SHA256" != "$SHA256" ]; then
	printf 'error=sha256_mismatch\n' >&2
	printf 'expected=%s\n' "$SHA256" >&2
	printf 'actual=%s\n' "$ACTUAL_SHA256" >&2
	exit 3
fi

backup_cecil_files

if [ "$PACKAGE_MANAGER" = "apk" ]; then
	if ! apk add --allow-untrusted --network=no --repositories-file /dev/null "$PKG_PATH"; then
		printf 'error=package_install_failed\n' >&2
		printf 'package_manager=%s\n' "$PACKAGE_MANAGER" >&2
		rollback_cecil_files
		exit 5
	fi
else
	if ! opkg install "$PKG_PATH"; then
		printf 'error=package_install_failed\n' >&2
		printf 'package_manager=%s\n' "$PACKAGE_MANAGER" >&2
		rollback_cecil_files
		exit 5
	fi
fi

if ! verify_installed_files; then
	rollback_cecil_files
	exit 6
fi

"$(root_path /usr/libexec/cecil/cecil-router)" prepare

rm -f /tmp/luci-indexcache /tmp/luci-indexcache.* 2>/dev/null || true
rm -rf /tmp/luci-modulecache /tmp/luci-modulecache.* 2>/dev/null || true

printf 'install=ok\n'
printf 'version=%s\n' "$VERSION"
printf 'package_manager=%s\n' "$PACKAGE_MANAGER"
printf 'package=%s\n' "$PACKAGE"
printf 'system_changes=cecil_owned_files_only\n'
printf 'luci_action=relogin\n'
