#!/usr/bin/env sh
# Moothall one-command installer source.
# Release packaging replaces the public-key placeholder before publication.
set -eu
umask 077

MANIFEST_URL='https://get.moothall.dev/releases/stable/manifest.json'
SIGNATURE_URL='https://get.moothall.dev/releases/stable/manifest.json.sig'
PUBLIC_KEY_B64='LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQ0lqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FnOEFNSUlDQ2dLQ0FnRUFxOE1PdkV6VEVzTW1IVy9JZzJidApxM1FITTVaWVNyd0JZb1Y2MFpiOE9YSUg3MGRtZGVWblJLYVJNS096L2s3aWdtbnNjcXY5OTdKdXEwd2hSK0x3CjdiNWFMUEZOMHpLTXp4NkNkS1FOSUJFN3BmOXhCbWxHdC8xeFlxSUN1YUMwd1ArWW5pUXFtRy9RTEY2SFd0Tk8KSXpNVlhFejNQUFlqcmdaOUlxMHFYSFhxT3p6UFpSUEFwYkpRNU1sUTFzYnZxNzQybGZTcWlhVytXTjJVTm0xKwpyRjRoaGtZai9JTkt3OEd5ZnBSVFRHblZmOCswUGlsY0xOSnRBNFFTYWNaS0hqY3A2bk5XTmVrVlFBOEtibitzCnNEbUxUR1VsU1l1TUN5NnBaWXVXSW1CSWtRRER2U2pna2I5OXBYNjBKd1NkcXk4elJXYnEvTmpDWVBpZlNYWDEKbHZCdVVhQTk4MklJNVhGQ2M5a0FTMDZEREYxWEUzNHNNWlJ5VkwwVzAxZ1EzTGtVaDhmRGROcytySnBzcHlwNQpmR1c3di9rb242OUhnTjZEaTNZZVZDRzlWT0V2SXBSbm1ONzRJekVuT1JYQVl5ZEZEb0RSa21PUUt1ckhYSDBtCmtPdGVra2E0QXg2N1plVjBSSjNHeWxodEJjS1VDREErY3RESWdwdWxUQXJ3ZkwrV3B5Y0xUeEtkakwzeUVaQjEKQ0R2MldJNnkwSXJ0S1J0OWxDaEF5Z3JvalJ1Ty9kQ1lzYnZxc1hVNUlPWk5pSWVmTko1QTZYK2RjcnVZTVFSTAoza3duK2xVc1hRYUtTUDRFb0pyUDFvVzBYN2xCTU1penNSeTA2SHE5MkhtSzVsL014Q3lQdWp5TStIS09lbG1aCmZhVmRGeU5hZGtyWWNKYitaRGZZbmMwQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo='
MANIFEST_MAX_BYTES=262144
SIGNATURE_MAX_BYTES=65536
START_DIR=$(pwd -P)
DRY_RUN=0
ASSUME_YES=0
PUBLIC_DIR=''
SETUP_URL=''
HALL_DIR=''
REPLACE_SETUP_STUB_SHA256=''

usage() {
    cat <<'TEXT'
Usage: install.sh [options]

  --public-dir=/absolute/path
  --setup-url=https://host/path/moothall-setup.php
  --hall-dir=/absolute/path
  --replace-setup-stub-sha256=<64 hex>
  --dry-run
  --yes

Run from the desired empty web directory for automatic handoff detection.
The browser asks all product questions.
TEXT
}

for argument in "$@"; do
    case "$argument" in
        --public-dir=*) PUBLIC_DIR=${argument#*=} ;;
        --setup-url=*) SETUP_URL=${argument#*=} ;;
        --hall-dir=*) HALL_DIR=${argument#*=} ;;
        --replace-setup-stub-sha256=*) REPLACE_SETUP_STUB_SHA256=${argument#*=} ;;
        --dry-run) DRY_RUN=1 ;;
        --yes) ASSUME_YES=1 ;;
        --help|-h) usage; exit 0 ;;
        *) echo "install.sh: unknown argument: $argument" >&2; exit 2 ;;
    esac
done

if [ -n "$REPLACE_SETUP_STUB_SHA256" ]; then
    case "$REPLACE_SETUP_STUB_SHA256" in
        *[!0-9a-f]*)
            echo 'install.sh: replacement setup-stub hash must be 64 lowercase hex characters.' >&2
            exit 2
            ;;
    esac
    if [ "${#REPLACE_SETUP_STUB_SHA256}" -ne 64 ]; then
        echo 'install.sh: replacement setup-stub hash must be 64 lowercase hex characters.' >&2
        exit 2
    fi
    if [ -z "$PUBLIC_DIR" ] || [ -z "$SETUP_URL" ]; then
        echo 'install.sh: setup-stub replacement requires explicit --public-dir and --setup-url.' >&2
        exit 2
    fi
fi

if [ -z "${HOME:-}" ] || [ ! -d "$HOME" ]; then
    echo 'install.sh: HOME is not available.' >&2
    exit 2
fi
case "$HOME" in
    /*) ;;
    *) echo 'install.sh: HOME must be an absolute path.' >&2; exit 2 ;;
esac

find_php() {
    for candidate in \
        /opt/cpanel/ea-php84/root/usr/bin/php \
        /opt/cpanel/ea-php83/root/usr/bin/php \
        /opt/cpanel/ea-php82/root/usr/bin/php \
        /opt/cpanel/ea-php81/root/usr/bin/php \
        /usr/local/bin/php8.4 \
        /usr/local/bin/php8.3 \
        /usr/local/bin/php8.2 \
        /usr/local/bin/php8.1 \
        /usr/local/bin/php \
        /usr/bin/php8.4 \
        /usr/bin/php8.3 \
        /usr/bin/php8.2 \
        /usr/bin/php8.1 \
        /usr/bin/php \
        /bin/php8.4 \
        /bin/php8.3 \
        /bin/php8.2 \
        /bin/php8.1 \
        /bin/php
    do
        if [ -x "$candidate" ] && "$candidate" -r 'exit(PHP_VERSION_ID >= 80100 && extension_loaded("mbstring") && extension_loaded("json") ? 0 : 1);' >/dev/null 2>&1; then
            "$candidate" -r '$p=realpath($argv[1]);if(!is_string($p)||!is_file($p)||!is_executable($p)){exit(1);}echo $p,PHP_EOL;' -- "$candidate"
            return 0
        fi
    done
    for name in php8.4 php8.3 php8.2 php8.1 php
    do
        if command -v "$name" >/dev/null 2>&1; then
            candidate=$(command -v "$name")
            if "$candidate" -r 'exit(PHP_VERSION_ID >= 80100 && extension_loaded("mbstring") && extension_loaded("json") ? 0 : 1);' >/dev/null 2>&1; then
                "$candidate" -r '$p=realpath($argv[1]);if(!is_string($p)||!is_file($p)||!is_executable($p)){exit(1);}echo $p,PHP_EOL;' -- "$candidate"
                return 0
            fi
        fi
    done
    return 1
}

PHP_BIN=$(find_php || true)
if [ -z "$PHP_BIN" ]; then
    echo 'install.sh: no PHP 8.1 or newer with the mbstring and json extensions was found.' >&2
    echo 'Ask your host to enable account-level PHP 8.1 or newer, then run this command again.' >&2
    exit 2
fi
if ! command -v curl >/dev/null 2>&1; then
    echo 'install.sh: curl is required for the one-command path.' >&2
    echo 'Use the single-file cPanel browser installer instead.' >&2
    exit 2
fi
if ! command -v tar >/dev/null 2>&1; then
    echo 'install.sh: tar is required for the one-command path.' >&2
    echo 'Use the single-file cPanel browser installer instead.' >&2
    exit 2
fi
case "$PUBLIC_KEY_B64" in
    *'@@'*) echo 'install.sh: this installer was not bound to a release signing key.' >&2; exit 2 ;;
esac

normalize_install_path() {
    path_name=$1
    raw_path=$2
    must_exist=$3
    "$PHP_BIN" -r '
$name = $argv[1];
$raw = $argv[2];
$mustExist = $argv[3] === "1";
if ($raw === "" || $raw[0] !== "/" || strlen($raw) > 4096
    || preg_match("/[\x00-\x1f\x7f]/", $raw) === 1) {
    fwrite(STDERR, "install.sh: " . $name . " must be a safe absolute path.\n");
    exit(2);
}
$parts = [];
foreach (explode("/", $raw) as $part) {
    if ($part === "" || $part === ".") {
        continue;
    }
    if ($part === "..") {
        if ($parts === []) {
            fwrite(STDERR, "install.sh: " . $name . " escapes the filesystem root.\n");
            exit(2);
        }
        array_pop($parts);
        continue;
    }
    $parts[] = $part;
}
$normalized = "/" . implode("/", $parts);
$current = "";
foreach ($parts as $part) {
    $current .= "/" . $part;
    clearstatcache(true, $current);
    if (is_link($current)) {
        fwrite(STDERR, "install.sh: " . $name . " has a symlinked ancestor: " . $current . "\n");
        exit(2);
    }
    $stat = @lstat($current);
    if (is_array($stat) && (($stat["mode"] & 0170000) !== 0040000)) {
        fwrite(STDERR, "install.sh: " . $name . " has a non-directory ancestor: " . $current . "\n");
        exit(2);
    }
}
if ($mustExist && !is_dir($normalized)) {
    fwrite(STDERR, "install.sh: " . $name . " is not an existing directory.\n");
    exit(2);
}
fwrite(STDOUT, $normalized);
' "$path_name" "$raw_path" "$must_exist"
}

validate_path_separation() {
    "$PHP_BIN" -r '
$start = $argv[1];
$public = $argv[2];
$release = $argv[3];
$hall = $argv[4];
$overlaps = static function (string $left, string $right): bool {
    if ($left === $right) {
        return true;
    }
    $leftPrefix = $left === "/" ? "/" : $left . "/";
    $rightPrefix = $right === "/" ? "/" : $right . "/";
    return str_starts_with($left, $rightPrefix) || str_starts_with($right, $leftPrefix);
};
$publicRoots = $public !== "" ? [$public] : [$start];
foreach (["release root" => $release, "Hall root" => $hall] as $name => $target) {
    foreach ($publicRoots as $publicRoot) {
        if ($overlaps($target, $publicRoot)) {
            fwrite(
                STDERR,
                "install.sh: " . $name . " must not overlap the public/start directory: "
                . $publicRoot . "\n"
            );
            exit(2);
        }
    }
}
if ($overlaps($release, $hall)) {
    fwrite(STDERR, "install.sh: release root and Hall root must not overlap.\n");
    exit(2);
}
' "$START_DIR" "$PUBLIC_DIR" "$1" "$HALL_DIR"
}

START_DIR=$(normalize_install_path 'start directory' "$START_DIR" 1)
if [ -n "$PUBLIC_DIR" ]; then
    PUBLIC_DIR=$(normalize_install_path 'public directory' "$PUBLIC_DIR" 1)
fi
RELEASE_PARENT=$(normalize_install_path \
    'release parent' "$HOME/apps/moothall/releases" 0)
if [ -z "$HALL_DIR" ]; then
    HALL_DIR="$HOME/moothall-data/hub"
fi
HALL_DIR=$(normalize_install_path 'Hall root' "$HALL_DIR" 0)
validate_path_separation "$RELEASE_PARENT"

TMP_BASE=${TMPDIR:-/tmp}
WORK=$(mktemp -d "$TMP_BASE/moothall-install.XXXXXXXX")
case "$WORK" in
    "$TMP_BASE"/moothall-install.*) ;;
    *) echo 'install.sh: unsafe temporary directory result.' >&2; exit 2 ;;
esac
MARKER="$WORK/.owned-by-moothall-installer"
: > "$MARKER"
cleanup() {
    if [ -f "$MARKER" ]; then
        rm -rf -- "$WORK"
    fi
}
trap cleanup EXIT HUP INT TERM

MANIFEST="$WORK/manifest.json"
SIGNATURE="$WORK/manifest.json.sig"
PUBLIC_KEY="$WORK/release-public-key.pem"
ARCHIVE="$WORK/release.tar.gz"
STAGE="$WORK/release-stage"

download_bounded() {
    download_name=$1
    download_url=$2
    download_target=$3
    download_limit=$4
    download_timeout=$5
    download_fifo="$WORK/download.fifo"
    download_partial="$download_target.partial"
    curl_status=0
    limiter_status=0

    rm -f -- "$download_fifo" "$download_partial"
    mkfifo -m 600 "$download_fifo"
    curl -fsSL --proto '=https' --proto-redir '=https' --tlsv1.2 \
        --connect-timeout 10 --max-time "$download_timeout" \
        --max-filesize "$download_limit" -- "$download_url" > "$download_fifo" &
    download_pid=$!
    "$PHP_BIN" -r '
$limitText = $argv[1];
$name = $argv[2];
if (preg_match("/^[1-9][0-9]*$/D", $limitText) !== 1) {
    fwrite(STDERR, "install.sh: invalid download size limit.\n");
    exit(2);
}
$limit = (int)$limitText;
$received = 0;
while (!feof(STDIN)) {
    $chunk = fread(STDIN, 65536);
    if (!is_string($chunk)) {
        fwrite(STDERR, "install.sh: failed while reading " . $name . ".\n");
        exit(2);
    }
    if ($chunk === "") {
        continue;
    }
    $received += strlen($chunk);
    if ($received > $limit) {
        fwrite(STDERR, "install.sh: " . $name . " exceeds its streaming size limit.\n");
        exit(2);
    }
    if (fwrite(STDOUT, $chunk) !== strlen($chunk)) {
        fwrite(STDERR, "install.sh: failed while writing " . $name . ".\n");
        exit(2);
    }
}
' "$download_limit" "$download_name" < "$download_fifo" > "$download_partial" \
        || limiter_status=$?
    wait "$download_pid" || curl_status=$?
    rm -f -- "$download_fifo"
    if [ "$limiter_status" -ne 0 ] || [ "$curl_status" -ne 0 ]; then
        rm -f -- "$download_partial"
        echo "install.sh: bounded download failed for $download_name." >&2
        return 2
    fi
    mv "$download_partial" "$download_target"
}

printf '%s' "$PUBLIC_KEY_B64" | "$PHP_BIN" -r '
$raw = stream_get_contents(STDIN);
$decoded = base64_decode(trim($raw), true);
if (!is_string($decoded) || $decoded === "") {
    fwrite(STDERR, "install.sh: embedded public key is invalid.\n");
    exit(2);
}
fwrite(STDOUT, $decoded);
' > "$PUBLIC_KEY"

download_bounded 'release manifest' "$MANIFEST_URL" "$MANIFEST" \
    "$MANIFEST_MAX_BYTES" 60
download_bounded 'release manifest signature' "$SIGNATURE_URL" "$SIGNATURE" \
    "$SIGNATURE_MAX_BYTES" 60

MANIFEST_FIELDS=$("$PHP_BIN" -r '
$manifestPath = $argv[1];
$signaturePath = $argv[2];
$keyPath = $argv[3];
$manifest = file_get_contents($manifestPath);
$signatureText = file_get_contents($signaturePath);
$key = file_get_contents($keyPath);
if (!is_string($manifest) || $manifest === "" || strlen($manifest) > 262144
    || !is_string($signatureText) || $signatureText === ""
    || strlen($signatureText) > 65536 || !is_string($key)) {
    fwrite(STDERR, "install.sh: release metadata is unreadable.\n");
    exit(2);
}
$signature = base64_decode(preg_replace("/\s+/", "", $signatureText), true);
if (!is_string($signature) || $signature === "") {
    $signature = $signatureText;
}
if (openssl_verify($manifest, $signature, $key, OPENSSL_ALGO_SHA256) !== 1) {
    fwrite(STDERR, "install.sh: release manifest signature verification failed.\n");
    exit(2);
}
$data = json_decode($manifest, true);
$artifact = is_array($data) ? ($data["artifacts"]["source_tar_gz"] ?? null) : null;
if (!is_array($artifact)
    || ($data["schema_version"] ?? null) !== 1
    || !is_string($data["version"] ?? null)
    || preg_match("/^v?[0-9]+\.[0-9]+\.[0-9]+(?:-[a-z0-9.-]+)?$/D", $data["version"]) !== 1
    || !is_string($artifact["url"] ?? null)
    || !str_starts_with($artifact["url"], "https://")
    || !is_string($artifact["sha256"] ?? null)
    || preg_match("/^[a-f0-9]{64}$/D", $artifact["sha256"]) !== 1
    || !is_int($artifact["size"] ?? null)
    || $artifact["size"] < 1
    || $artifact["size"] > 536870912
    || ($artifact["format"] ?? null) !== "tar.gz") {
    fwrite(STDERR, "install.sh: signed release manifest has an invalid source artifact.\n");
    exit(2);
}
echo $data["version"], "\t", hash("sha256", $manifest), "\t",
    $artifact["url"], "\t", $artifact["sha256"], "\t", $artifact["size"];
' "$MANIFEST" "$SIGNATURE" "$PUBLIC_KEY")

OLD_IFS=$IFS
IFS='	'
set -- $MANIFEST_FIELDS
IFS=$OLD_IFS
VERSION=${1:-}
MANIFEST_SHA=${2:-}
ARCHIVE_URL=${3:-}
ARCHIVE_SHA=${4:-}
ARCHIVE_SIZE=${5:-}
if [ -z "$VERSION" ] || [ -z "$ARCHIVE_URL" ]; then
    echo 'install.sh: verified manifest fields are incomplete.' >&2
    exit 2
fi

RELEASE_ROOT=$(normalize_install_path \
    'release root' "$RELEASE_PARENT/$VERSION" 0)
validate_path_separation "$RELEASE_ROOT"

echo
echo 'Moothall install plan'
echo
echo "  PHP:          $PHP_BIN"
echo "  Release:      $VERSION"
echo "  Code target:  $RELEASE_ROOT"
echo "  Private hall: $HALL_DIR"
echo "  Start dir:    $START_DIR"
if [ -n "$PUBLIC_DIR" ]; then
    echo "  Public dir:   $PUBLIC_DIR"
else
    echo '  Public dir:   browser handoff tool will detect from the start directory'
fi
if [ -n "$SETUP_URL" ]; then
    echo "  Setup URL:    $SETUP_URL"
else
    echo '  Setup URL:    browser handoff tool will detect or return one exact correction'
fi
if [ -n "$REPLACE_SETUP_STUB_SHA256" ]; then
    echo "  Setup entry:  replace only uploaded file matching $REPLACE_SETUP_STUB_SHA256"
fi
echo
echo 'No product configuration is collected in the terminal.'

if [ "$DRY_RUN" -eq 1 ]; then
    echo
    echo 'Dry run complete. No product or instance path was written.'
    exit 0
fi
if [ "$ASSUME_YES" -ne 1 ]; then
    if ! exec 3<>/dev/tty 2>/dev/null; then
        echo 'install.sh: explicit confirmation requires a controlling terminal.' >&2
        echo 'Review with --dry-run, then rerun the complete command with --yes.' >&2
        exit 2
    fi
    printf '\nDownload the signed release and write this plan? [y/N]: ' >&3
    IFS= read -r answer <&3 || answer=''
    exec 3>&-
    case "$answer" in
        y|Y|yes|YES|Yes) ;;
        *) echo 'Declined. Nothing was installed.'; exit 0 ;;
    esac
fi

RELEASE_PARENT=$(normalize_install_path 'release parent' "$RELEASE_PARENT" 0)
RELEASE_ROOT=$(normalize_install_path 'release root' "$RELEASE_ROOT" 0)
HALL_DIR=$(normalize_install_path 'Hall root' "$HALL_DIR" 0)
validate_path_separation "$RELEASE_ROOT"
download_bounded 'release archive' "$ARCHIVE_URL" "$ARCHIVE" \
    "$ARCHIVE_SIZE" 300
"$PHP_BIN" -r '
$path = $argv[1];
$expectedHash = $argv[2];
$expectedSize = (int)$argv[3];
if (!is_file($path) || filesize($path) !== $expectedSize
    || !hash_equals($expectedHash, hash_file("sha256", $path))) {
    fwrite(STDERR, "install.sh: release archive does not match the signed manifest.\n");
    exit(2);
}
' "$ARCHIVE" "$ARCHIVE_SHA" "$ARCHIVE_SIZE"

if tar -tzf "$ARCHIVE" | "$PHP_BIN" -r '
$root = null;
$count = 0;
while (($line = fgets(STDIN)) !== false) {
    $name = rtrim($line, "\r\n");
    $count++;
    if ($count > 20000 || $name === "" || strlen($name) > 4096
        || $name[0] === "/" || str_contains($name, "\\")
        || str_contains($name, "\0")
        || preg_match("#(^|/)\.\.?(/|$)#", $name) === 1
        || preg_match("/[\x00-\x1f\x7f]/", $name) === 1) {
        fwrite(STDERR, "install.sh: release archive has an unsafe path.\n");
        exit(2);
    }
    $part = explode("/", rtrim($name, "/"))[0] ?? "";
    if ($part === "" || preg_match("/^moothall(?:-[A-Za-z0-9._-]+)?$/D", $part) !== 1) {
        fwrite(STDERR, "install.sh: release archive top-level directory is invalid.\n");
        exit(2);
    }
    if ($root === null) {
        $root = $part;
    } elseif ($root !== $part) {
        fwrite(STDERR, "install.sh: release archive has multiple top-level directories.\n");
        exit(2);
    }
}
exit($count > 0 ? 0 : 2);
'; then
    :
else
    exit 2
fi
if tar -tvzf "$ARCHIVE" | "$PHP_BIN" -r '
while (($line = fgets(STDIN)) !== false) {
    $kind = $line[0] ?? "";
    if ($kind === "l" || $kind === "h") {
        fwrite(STDERR, "install.sh: release archive contains a link entry.\n");
        exit(2);
    }
}
'; then
    :
else
    exit 2
fi

RELEASE_PARENT=$(normalize_install_path 'release parent' "$RELEASE_PARENT" 0)
RELEASE_ROOT=$(normalize_install_path 'release root' "$RELEASE_ROOT" 0)
HALL_DIR=$(normalize_install_path 'Hall root' "$HALL_DIR" 0)
validate_path_separation "$RELEASE_ROOT"
if [ -e "$RELEASE_ROOT" ]; then
    echo "install.sh: release target already exists: $RELEASE_ROOT" >&2
    exit 2
fi
mkdir -p -m 700 "$RELEASE_PARENT"
mkdir -m 700 "$STAGE"
tar -xzf "$ARCHIVE" --strip-components=1 --no-same-owner --no-same-permissions -C "$STAGE"
if [ ! -f "$STAGE/tools/PRODUCT-MANIFEST.txt" ] || [ ! -f "$STAGE/tools/moothall-install.php" ]; then
    echo 'install.sh: extracted release is incomplete.' >&2
    exit 2
fi
"$PHP_BIN" -r '
$root = realpath($argv[1]);
if (!is_string($root) || is_link($argv[1])) {
    fwrite(STDERR, "install.sh: extracted release root is unsafe.\n");
    exit(2);
}
$lines = file($root . "/tools/PRODUCT-MANIFEST.txt", FILE_IGNORE_NEW_LINES);
if (!is_array($lines) || $lines === []) {
    fwrite(STDERR, "install.sh: product manifest is empty.\n");
    exit(2);
}
$expected = [];
foreach ($lines as $path) {
    if (!is_string($path) || $path === "" || trim($path) !== $path
        || $path[0] === "/" || str_contains($path, "\\")
        || preg_match("#(^|/)\.\.?(/|$)#", $path) === 1
        || preg_match("/[\x00-\x1f\x7f]/", $path) === 1
        || isset($expected[$path])
        || !is_file($root . "/" . $path)
        || is_link($root . "/" . $path)) {
        fwrite(STDERR, "install.sh: extracted product tree does not match its manifest.\n");
        exit(2);
    }
    $expected[$path] = true;
}
$files = 0;
$bytes = 0;
$iterator = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator($root, FilesystemIterator::SKIP_DOTS)
);
foreach ($iterator as $item) {
    if ($item->isLink()) {
        fwrite(STDERR, "install.sh: extracted release contains an unsupported entry.\n");
        exit(2);
    }
    if ($item->isDir()) {
        continue;
    }
    if (!$item->isFile()) {
        fwrite(STDERR, "install.sh: extracted release contains an unsupported entry.\n");
        exit(2);
    }
    $relative = substr($item->getPathname(), strlen($root) + 1);
    if (!isset($expected[$relative])) {
        fwrite(STDERR, "install.sh: extracted release contains an unmanifested file.\n");
        exit(2);
    }
    $files++;
    $bytes += $item->getSize();
    if ($files > 20000 || $bytes > 536870912) {
        fwrite(STDERR, "install.sh: extracted release exceeds its bounded tree limits.\n");
        exit(2);
    }
}
if ($files !== count($expected)) {
    fwrite(STDERR, "install.sh: extracted product file count is incomplete.\n");
    exit(2);
}
' "$STAGE"
mv "$STAGE" "$RELEASE_ROOT"
chmod 700 "$RELEASE_ROOT"

set -- \
    "--release-root=$RELEASE_ROOT" \
    "--release-version=$VERSION" \
    "--manifest-sha256=$MANIFEST_SHA" \
    "--hall-dir=$HALL_DIR"
if [ -n "$PUBLIC_DIR" ]; then
    set -- "$@" "--public-dir=$PUBLIC_DIR"
fi
if [ -n "$SETUP_URL" ]; then
    set -- "$@" "--setup-url=$SETUP_URL"
fi
if [ -n "$REPLACE_SETUP_STUB_SHA256" ]; then
    set -- "$@" "--replace-setup-stub-sha256=$REPLACE_SETUP_STUB_SHA256"
fi
# The complete signed plan was already accepted above, either through /dev/tty
# or the explicit outer --yes. Do not ask the handoff tool to read the script
# pipe as a second confirmation.
set -- "$@" --yes
cd "$START_DIR"
if "$PHP_BIN" "$RELEASE_ROOT/tools/moothall-install.php" "$@"; then
    cleanup
    trap - 0 HUP INT TERM
    exit 0
else
    handoff_status=$?
fi

failed_index=0
FAILED_ROOT="$RELEASE_PARENT/.failed-handoff-$VERSION-$$"
while [ -e "$FAILED_ROOT" ] || [ -L "$FAILED_ROOT" ]; do
    failed_index=$((failed_index + 1))
    if [ "$failed_index" -gt 100 ]; then
        echo "install.sh: browser handoff failed and no inert recovery name was available for $RELEASE_ROOT." >&2
        exit "$handoff_status"
    fi
    FAILED_ROOT="$RELEASE_PARENT/.failed-handoff-$VERSION-$$-$failed_index"
done
if mv "$RELEASE_ROOT" "$FAILED_ROOT"; then
    chmod 700 "$FAILED_ROOT" 2>/dev/null || true
    echo "install.sh: browser handoff failed; the verified release was moved aside inertly: $FAILED_ROOT" >&2
    echo 'install.sh: correct the reported handoff issue and rerun the same command.' >&2
else
    echo "install.sh: browser handoff failed and the verified release could not be moved aside: $RELEASE_ROOT" >&2
fi
cleanup
trap - 0 HUP INT TERM
exit "$handoff_status"
