Files
cet6-90-day-checkin/scripts/install-pnpm.sh
T

217 lines
8.8 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [[ "${SITES_ENV_READY:-}" != "1" ]]; then
exec "${script_dir}/sites-env.sh" -- "$0" "$@"
fi
require_shared=0
prepare_only=0
for argument in "$@"; do
case "${argument}" in
--require-shared) require_shared=1 ;;
--prepare-store) prepare_only=1 ;;
*) echo "Unknown pnpm installation option: ${argument}" >&2; exit 64 ;;
esac
done
for tool in flock timeout node; do
command -v "${tool}" >/dev/null || { echo "pnpm setup requires Linux flock, GNU timeout, and Node.js." >&2; exit 69; }
done
cache_seed=decision_unavailable
store_scope=unknown
store_state=unavailable
report_store() {
node "${script_dir}/pnpm-install.mjs" --report-store "${cache_seed}" "${store_scope}" "${store_state}" "${prepare_only}"
}
# Publish preparation failures too; the optional report never decides success.
trap 'report_store || true' EXIT
if [[ -n "${SITES_PNPM_BIN:-}" && -f "${SITES_PNPM_BIN}" && -r "${SITES_PNPM_BIN}" ]]; then
pnpm_command=(node "${SITES_PNPM_BIN}")
elif [[ "${require_shared}" == 1 ]]; then
echo "[sites] the image-pinned pnpm is unavailable" >&2
exit 69
elif command -v corepack >/dev/null; then
# Corepack honors the established project's pin even when PATH has a different
# pnpm version. A bare pnpm is used only if Corepack is absent, then verified.
pnpm_command=(corepack pnpm)
elif command -v pnpm >/dev/null; then
pnpm_command=(pnpm)
else
echo "This pnpm project requires pnpm 11.25.0 or Corepack." >&2
exit 69
fi
runtime_root="${SITES_RUNTIME_ROOT:-${SITES_PROJECT_ROOT}/.sites-runtime}"
private_store="${runtime_root}/pnpm-store"
if [[ "${HOME}" != "${runtime_root}/home" || -L "${private_store}" ]]; then
echo "Dependency setup requires a project-owned writable home and pnpm store." >&2
exit 78
fi
# A single Node process owns the regular-file descriptors for both leases.
# Closing its input (including this shell exiting) releases the project lock.
coproc SITES_INSTALL_LOCKS {
node "${script_dir}/pnpm-install.mjs" --hold-install-locks \
"${runtime_root}/install.lock" "${SITES_PNPM_SHARED_STORE:-}.seed.lock" \
"${SITES_PNPM_STORE_LOCK_TIMEOUT:-30}"
}
install_lock_pid="${SITES_INSTALL_LOCKS_PID}"
install_lock_output="${SITES_INSTALL_LOCKS[0]}"
install_lock_input="${SITES_INSTALL_LOCKS[1]}"
lock_status=unavailable
read -r lock_status <&"${install_lock_output}" || true
if [[ "${lock_status}" != locked ]]; then
exec {install_lock_input}>&-
exec {install_lock_output}<&-
wait "${install_lock_pid}" || true
if [[ "${lock_status}" == busy ]]; then
echo "Another dependency install is already running for ${SITES_PROJECT_ROOT}." >&2
exit 75
fi
echo "Dependency setup requires a regular, writable project install lock." >&2
exit 78
fi
export XDG_CACHE_HOME="${runtime_root}/xdg-cache" XDG_DATA_HOME="${runtime_root}/xdg-data"
mkdir -p "${XDG_CACHE_HOME}" "${XDG_DATA_HOME}" || exit 70
pnpm_version="$(timeout --signal=TERM --kill-after="${SITES_INSTALL_KILL_AFTER:-15s}" \
"${SITES_PNPM_BOOTSTRAP_TIMEOUT:-30s}" "${pnpm_command[@]}" --version)"
if [[ "${pnpm_version}" != "11.25.0" ]]; then
echo "This project requires pnpm 11.25.0 and its v11 store format." >&2
exit 69
fi
can_write_directory() {
local probe
# access()/test -w can succeed even when a kernel sandbox denies a write.
probe="$(mktemp "${1}/.sites-store-probe.XXXXXX" 2>/dev/null)" || return 1
rm -f -- "${probe}" 2>/dev/null
}
release_shared_lock() {
if [[ "${shared_lock_held:-}" == 1 ]]; then
printf '%s\n' release-shared >&"${install_lock_input}"
local status
read -r status <&"${install_lock_output}" && [[ "${status}" == released ]] || return 70
shared_lock_held=0
fi
}
acquire_shared_lock() {
# The Node holder opens this workspace-controlled entry with no-follow and
# nonblocking flags, then verifies a regular file before bounded flock.
printf '%s\n' shared >&"${install_lock_input}"
local status
if read -r status <&"${install_lock_output}" && [[ "${status}" == locked ]]; then
shared_lock_held=1
return 0
fi
return 1
}
writable_store="${private_store}"
shared_store="${SITES_PNPM_SHARED_STORE:-}"
if [[ "${shared_store}" == "/workspace/.sites-runtime/pnpm-store" &&
! -L "${shared_store}" && ! -L "${shared_store%/*}" &&
-d "${shared_store%/*/*}" ]]; then
# Normal Work already writes this owner's workspace. Narrower profiles must
# pass the actual create probe, without widening their permissions.
if mkdir -p "${shared_store%/*}" 2>/dev/null &&
can_write_directory "${shared_store%/*}" &&
acquire_shared_lock; then
if [[ ! -L "${shared_store}" && ! -L "${shared_store%/*}" &&
( ! -e "${shared_store}" || ( -d "${shared_store}" && ! -L "${shared_store}" && -w "${shared_store}" ) ) ]] &&
{ [[ ! -d "${shared_store}" ]] || can_write_directory "${shared_store}"; }; then
writable_store="${shared_store}"
store_scope=workspace
else
release_shared_lock
fi
else
release_shared_lock
fi
fi
if [[ "${store_scope}" != workspace ]]; then
if [[ "${require_shared}" == 1 ]]; then
echo "[sites] the workspace pnpm store is unavailable" >&2
exit 69
fi
store_scope=project
fi
seed="${SITES_PNPM_CACHE_SEED:-}"
cache_seed=seed_unavailable
if [[ -d "${writable_store}" ]]; then
# Never merge a newer image seed into an existing mutable store.
store_state=reused
cache_seed=not_applicable
if [[ -f "${writable_store}/.sites-pnpm-seed-applied.json" ]]; then cache_seed=seed_used; fi
else
seed_compatible=0
if [[ -n "${seed}" && -d "${seed}/v11" ]] && node --input-type=module - "${seed}/.sites-pnpm-seed.json" <<'NODE'
import { readFileSync, statSync } from "node:fs";
try {
const filename = process.argv[2];
if (statSync(filename).size > 4096) process.exit(1);
const seed = JSON.parse(readFileSync(filename, "utf8"));
process.exit(seed.version === 1 && seed.pnpm_version === "11.25.0" &&
seed.store_version === "v11" && /^[a-f0-9]{64}$/.test(seed.lockfile_sha256) ? 0 : 1);
} catch { process.exit(1); }
NODE
then seed_compatible=1; fi
if [[ "${seed_compatible}" == 1 ]]; then
if [[ "${prepare_only}" == 1 ]]; then
echo "[sites] copying the image seed into the writable ${store_scope} pnpm store" >&2
else
echo "[sites] copying the image seed into the writable ${store_scope} pnpm store"
fi
seed_stage="$(mktemp -d "${writable_store}.seed.XXXXXX")" || exit 70
trap 'if [[ -n "${seed_stage:-}" ]]; then rm -rf -- "${seed_stage}"; fi; report_store || true' EXIT
timeout --signal=TERM --kill-after="${SITES_INSTALL_KILL_AFTER:-15s}" \
"${SITES_PNPM_STORE_PREPARE_TIMEOUT:-60s}" bash -c \
'cp -a --no-preserve=ownership "$1/." "$2/" && chmod -R u+rwX "$2"' _ "${seed}" "${seed_stage}" || exit 70
cp "${seed}/.sites-pnpm-seed.json" "${seed_stage}/.sites-pnpm-seed-applied.json" || exit 70
# An ordinary pnpm command can initialize the store without our seed lock.
# Publish without replacing even an empty directory created by that command.
mv -T --update=none "${seed_stage}" "${writable_store}" || exit 70
if [[ -d "${seed_stage}" ]]; then
if [[ -L "${writable_store}" || ! -d "${writable_store}" ]]; then exit 78; fi
rm -rf -- "${seed_stage}" || exit 70
store_state=reused
cache_seed=not_applicable
if [[ -f "${writable_store}/.sites-pnpm-seed-applied.json" ]]; then cache_seed=seed_used; fi
else
store_state=seeded
cache_seed=seed_used
fi
seed_stage=""
else
mkdir -p "${writable_store}" || exit 70
store_state=created
fi
fi
if ! can_write_directory "${writable_store}"; then store_state=unavailable; exit 70; fi
release_shared_lock
report_store
trap - EXIT
if [[ "${prepare_only}" == 1 ]]; then exit 0; fi
# Configuration travels with source without embedding an absolute machine path.
# A later restricted session selects a private store before its frozen repair.
configured_store=.sites-runtime/pnpm-store
if [[ "${store_scope}" == workspace ]]; then
configured_store='${SITES_PNPM_SHARED_STORE:-.sites-runtime/pnpm-store}'
elif [[ "${runtime_root}" != "${SITES_PROJECT_ROOT}/.sites-runtime" ]]; then
configured_store='${SITES_RUNTIME_ROOT:-.sites-runtime}/pnpm-store'
fi
"${pnpm_command[@]}" config set package-import-method clone-or-copy --location project
"${pnpm_command[@]}" config set store-dir "${configured_store}" --location project
"${pnpm_command[@]}" config set cache-dir "${configured_store}/policy-cache" --location project
# CI=true lets native pnpm install rebuild modules whose store moved, without a
# prompt, --force, changing the lockfile, or translating the project into npm.
CI=true timeout --signal=TERM --kill-after="${SITES_INSTALL_KILL_AFTER:-15s}" \
"${SITES_INSTALL_TIMEOUT:-8m}" node "${script_dir}/pnpm-install.mjs" \
"${cache_seed}" "${store_scope}" "${store_state}" "${writable_store}" "${pnpm_command[@]}"