Preserve upstream fixes without restoring retired dependency installers. Run configured-feature checks in the selected build interpreter. Reuse a supported base Python during bootstrap, and preserve durable backup media. Refresh the dependency lock through PM. Keep the frozen historical import surface unchanged. Adapt incoming native tests to the platform markers. Verification: the incoming 86-file pass found two fixture mismatches; both passed after correction. Targeted PM/update/compatibility checks, Electron and renderer typechecks, and desktop tests passed. Native Windows/macOS update journeys and the full suite remain unrun.
482 lines
19 KiB
Bash
Executable File
482 lines
19 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Hermes Agent bootstrap: git checkout + venv + hermes command on PATH.
|
|
# Heavy dependencies (tool binaries, browsers, node) are pm's job after
|
|
# this: `hermes pm install`. Stage protocol kept for Hermes-Setup:
|
|
# --manifest print the stage list as JSON
|
|
# --stage NAME [--json] run one stage
|
|
# --non-interactive skip stages that need input
|
|
# --include-desktop add the desktop build stage
|
|
set -u
|
|
|
|
# Prevent uv from discovering config files (uv.toml, pyproject.toml) from the
|
|
# wrong user's home directory when running under sudo -u <user>. See #21269.
|
|
# pm's own venv sync re-isolates (pm/environment.py), so this bootstrap
|
|
# hygiene can't break the locked sync the way it used to before pm owned it.
|
|
export UV_NO_CONFIG=1
|
|
|
|
REPO_URL="${HERMES_REPO_URL:-https://github.com/NousResearch/hermes-agent.git}"
|
|
BRANCH="main"
|
|
INSTALL_COMMIT=""
|
|
INSTALL_DIR="${HERMES_INSTALL_DIR:-}"
|
|
HERMES_HOME="${HERMES_HOME:-$HOME/.hermes}"
|
|
STAGE=""
|
|
WANT_MANIFEST=false
|
|
JSON=false
|
|
NON_INTERACTIVE=false
|
|
INCLUDE_DESKTOP=false
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--branch|-Branch) BRANCH="$2"; shift 2 ;;
|
|
--commit|-Commit) INSTALL_COMMIT="$2"; shift 2 ;;
|
|
--dir) INSTALL_DIR="$2"; shift 2 ;;
|
|
--hermes-home|-HermesHome) HERMES_HOME="$2"; shift 2 ;;
|
|
--manifest|-Manifest) WANT_MANIFEST=true; shift ;;
|
|
--stage|-Stage) STAGE="$2"; shift 2 ;;
|
|
--json|-Json) JSON=true; shift ;;
|
|
--non-interactive|-NonInteractive) NON_INTERACTIVE=true; shift ;;
|
|
--skip-setup|--skip-browser) NON_INTERACTIVE=true; shift ;;
|
|
--include-desktop|-IncludeDesktop) INCLUDE_DESKTOP=true; shift ;;
|
|
-h|--help)
|
|
echo "Usage: install.sh [--branch NAME] [--commit SHA] [--dir PATH]"
|
|
echo " [--hermes-home PATH]"
|
|
echo " [--manifest] [--stage NAME] [--json]"
|
|
echo " [--non-interactive] [--include-desktop]"
|
|
exit 0 ;;
|
|
*) echo "unknown option: $1" >&2; exit 1 ;;
|
|
esac
|
|
done
|
|
|
|
INSTALL_DIR="${INSTALL_DIR:-$HERMES_HOME/hermes-agent}"
|
|
export HERMES_HOME
|
|
|
|
log() { printf "\033[1;34m[hermes]\033[0m %s\n" "$1"; }
|
|
fail() { STAGE_REASON="$1"; printf "\033[1;31m[hermes]\033[0m %s\n" "$1" >&2; exit 1; }
|
|
|
|
# --- BEGIN GENERATED: bootstrap pins (scripts/gen-bootstrap-pins.py) ---
|
|
# Derived from pm/lock.json. DO NOT EDIT BY HAND:
|
|
# run scripts/gen-bootstrap-pins.py after a pin bump.
|
|
UV_PIN_VERSION="0.12.3"
|
|
|
|
# Sets UV_PIN_URL + UV_PIN_SHA256 for a <os>-<arch> target key.
|
|
uv_bootstrap_pin() {
|
|
case "$1" in
|
|
linux-x64)
|
|
UV_PIN_URL="https://github.com/astral-sh/uv/releases/download/0.12.3/uv-x86_64-unknown-linux-gnu.tar.gz"
|
|
UV_PIN_MIRROR="https://hermes-assets.nousresearch.com/upstream/sha256/600cf9a742aca00d292673b16b5acffaa7b8c269a364ad0c2e79498dcb1fe101"
|
|
UV_PIN_SHA256="600cf9a742aca00d292673b16b5acffaa7b8c269a364ad0c2e79498dcb1fe101"
|
|
;;
|
|
linux-arm64)
|
|
UV_PIN_URL="https://github.com/astral-sh/uv/releases/download/0.12.3/uv-aarch64-unknown-linux-gnu.tar.gz"
|
|
UV_PIN_MIRROR="https://hermes-assets.nousresearch.com/upstream/sha256/bb66cb52e7b1823aed1183630d8d8e5c958840d584a4c55ec10a4cfc168dcca2"
|
|
UV_PIN_SHA256="bb66cb52e7b1823aed1183630d8d8e5c958840d584a4c55ec10a4cfc168dcca2"
|
|
;;
|
|
darwin-x64)
|
|
UV_PIN_URL="https://github.com/astral-sh/uv/releases/download/0.12.3/uv-x86_64-apple-darwin.tar.gz"
|
|
UV_PIN_MIRROR="https://hermes-assets.nousresearch.com/upstream/sha256/4c9f52262a14da336e4a42ed24992d12d0c956acde87619e4611d321dffa602b"
|
|
UV_PIN_SHA256="4c9f52262a14da336e4a42ed24992d12d0c956acde87619e4611d321dffa602b"
|
|
;;
|
|
darwin-arm64)
|
|
UV_PIN_URL="https://github.com/astral-sh/uv/releases/download/0.12.3/uv-aarch64-apple-darwin.tar.gz"
|
|
UV_PIN_MIRROR="https://hermes-assets.nousresearch.com/upstream/sha256/546f7f8a6c70ff13a3a9d2bc958db3427298cebf3e0cb756f9177133b7068843"
|
|
UV_PIN_SHA256="546f7f8a6c70ff13a3a9d2bc958db3427298cebf3e0cb756f9177133b7068843"
|
|
;;
|
|
*)
|
|
UV_PIN_URL=""
|
|
UV_PIN_SHA256=""
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
# --- END GENERATED: bootstrap pins ---
|
|
|
|
uv_bootstrap_target() {
|
|
# Map this host to a pm/lock.json target key (<os>-<arch>).
|
|
local _arch
|
|
case "$(uname -m)" in
|
|
arm64|aarch64) _arch="arm64" ;;
|
|
x86_64|amd64) _arch="x64" ;;
|
|
*) return 1 ;;
|
|
esac
|
|
case "$(uname -s)" in
|
|
Linux) echo "linux-$_arch" ;;
|
|
Darwin) echo "darwin-$_arch" ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
# Provision uv for this host from the pinned pm/lock.json artifact. Stages
|
|
# the EXACT artifact pm itself uses into the same store slot
|
|
# (~/.hermes/tools/uv-<version>-<target>/), sha256-verified, so the byte
|
|
# authority is pm/lock.json - no astral-latest, no curl|sh.
|
|
UV_CMD=""
|
|
ensure_uv() {
|
|
[ -n "$UV_CMD" ] && return 0
|
|
if command -v uv >/dev/null 2>&1; then
|
|
# Developer shortcut: an existing uv on PATH is fine to use; this
|
|
# branch fetches nothing.
|
|
UV_CMD="uv"
|
|
return 0
|
|
fi
|
|
local _target
|
|
if ! _target="$(uv_bootstrap_target)"; then
|
|
fail "no pinned uv build for this platform ($(uname -s) $(uname -m)); install uv manually: https://docs.astral.sh/uv/"
|
|
fi
|
|
if ! uv_bootstrap_pin "$_target"; then
|
|
fail "no pinned uv artifact for $_target; install uv manually: https://docs.astral.sh/uv/"
|
|
fi
|
|
local _store="${HERMES_RUNTIME_DIR:-$HOME/.hermes/tools}"
|
|
local _entry="$_store/uv-$UV_PIN_VERSION-$_target"
|
|
UV_CMD="$_entry/uv"
|
|
if [ ! -x "$UV_CMD" ]; then
|
|
log "staging pinned uv $UV_PIN_VERSION ($_target) into the pm store"
|
|
local _tmp
|
|
_tmp="$(mktemp -d 2>/dev/null || echo "/tmp/hermes-uv-bootstrap.$$")"
|
|
mkdir -p "$_tmp"
|
|
local _fetched_from="$UV_PIN_URL"
|
|
# Only network availability failures permit trying identical mirrored bytes.
|
|
if curl -LsSf "$UV_PIN_URL" -o "$_tmp/uv.tar.gz"; then
|
|
:
|
|
else
|
|
local _curl_status=$?
|
|
case "$_curl_status" in
|
|
5|6|7|18|22|28|52|55|56) ;;
|
|
*) rm -rf "$_tmp"; fail "failed to download pinned uv from $UV_PIN_URL (curl $_curl_status)" ;;
|
|
esac
|
|
if [ -n "${UV_PIN_MIRROR:-}" ] && curl -LsSf "$UV_PIN_MIRROR" -o "$_tmp/uv.tar.gz"; then
|
|
_fetched_from="$UV_PIN_MIRROR"
|
|
else
|
|
rm -rf "$_tmp"
|
|
fail "failed to download pinned uv from $UV_PIN_URL or ${UV_PIN_MIRROR:-no mirror}"
|
|
fi
|
|
fi
|
|
local _digest
|
|
if command -v sha256sum >/dev/null 2>&1; then
|
|
_digest="$(sha256sum "$_tmp/uv.tar.gz" | cut -d' ' -f1)"
|
|
else
|
|
_digest="$(shasum -a 256 "$_tmp/uv.tar.gz" | cut -d' ' -f1)"
|
|
fi
|
|
if [ "$_digest" != "$UV_PIN_SHA256" ]; then
|
|
rm -rf "$_tmp"
|
|
fail "uv download digest mismatch from $_fetched_from (expected $UV_PIN_SHA256, got $_digest)"
|
|
fi
|
|
if ! tar -xzf "$_tmp/uv.tar.gz" -C "$_tmp"; then
|
|
rm -rf "$_tmp"
|
|
fail "failed to extract pinned uv archive"
|
|
fi
|
|
local _unpacked
|
|
_unpacked="$(find "$_tmp" -mindepth 1 -maxdepth 2 -name uv -type f | head -n1)"
|
|
if [ -z "$_unpacked" ]; then
|
|
rm -rf "$_tmp"
|
|
fail "uv binary not found in the downloaded archive"
|
|
fi
|
|
mkdir -p "$_entry"
|
|
mv "$_unpacked" "$UV_CMD"
|
|
[ -f "$(dirname "$_unpacked")/uvx" ] && mv "$(dirname "$_unpacked")/uvx" "$_entry/uvx"
|
|
chmod +x "$UV_CMD"
|
|
chmod +x "$_entry/uvx" 2>/dev/null || true
|
|
rm -rf "$_tmp"
|
|
fi
|
|
# Bootstrap keeps the installer private; only UV_CMD invokes it.
|
|
if ! "$UV_CMD" --version >/dev/null 2>&1; then
|
|
fail "pinned uv staged but does not run on this host"
|
|
fi
|
|
log "uv ready ($("$UV_CMD" --version 2>/dev/null))"
|
|
}
|
|
|
|
check_platform() {
|
|
case "$(uname -s 2>/dev/null)" in
|
|
Linux*) : ;;
|
|
Darwin*) : ;;
|
|
*) fail "unsupported platform: $(uname -s). On Windows use install.ps1." ;;
|
|
esac
|
|
}
|
|
|
|
json_string() {
|
|
local value="$1" code char escaped
|
|
value="${value//\\/\\\\}"
|
|
value="${value//\"/\\\"}"
|
|
for ((code = 1; code < 32; code++)); do
|
|
printf -v char '\\%03o' "$code"
|
|
printf -v char '%b' "$char"
|
|
printf -v escaped '\\u%04x' "$code"
|
|
value="${value//"$char"/$escaped}"
|
|
done
|
|
printf '"%s"' "$value"
|
|
}
|
|
|
|
json_frame() {
|
|
# $1 ok, $2 stage, $3 skipped, $4 reason
|
|
if [ -n "${4:-}" ]; then
|
|
printf '{"ok":%s,"stage":%s,"skipped":%s,"reason":%s}\n' "$1" "$(json_string "$2")" "$3" "$(json_string "$4")"
|
|
else
|
|
printf '{"ok":%s,"stage":%s,"skipped":%s}\n' "$1" "$(json_string "$2")" "$3"
|
|
fi
|
|
}
|
|
|
|
stage_result() {
|
|
local code="$1" ok=false reason="${STAGE_REASON:-}"
|
|
if [ "$code" -eq 0 ]; then
|
|
ok=true
|
|
else
|
|
reason="${reason:-stage failed (exit $code)}"
|
|
fi
|
|
if [ "$JSON" = true ]; then
|
|
json_frame "$ok" "$STAGE" "${STAGE_SKIPPED:-false}" "$reason"
|
|
fi
|
|
}
|
|
|
|
# The single authoritative stage list: emit_manifest prints it AND the
|
|
# no-flag ladder runs it, so --include-desktop affects the real run
|
|
# exactly as the manifest advertises.
|
|
stage_names() {
|
|
printf '%s\n' prerequisites repository venv python-deps node-deps path config setup gateway
|
|
[ "$INCLUDE_DESKTOP" = true ] && printf '%s\n' desktop
|
|
printf '%s\n' complete
|
|
}
|
|
|
|
# "$1" stage name -> its manifest record fields (title|category|needs_user_input).
|
|
stage_record() {
|
|
case "$1" in
|
|
prerequisites) echo "System prerequisites|runtime|false" ;;
|
|
repository) echo "Download Hermes Agent|runtime|false" ;;
|
|
venv) echo "Create Python environment|runtime|false" ;;
|
|
python-deps) echo "Install Python dependencies|runtime|false" ;;
|
|
node-deps) echo "Install tool dependencies|runtime|false" ;;
|
|
path) echo "Install hermes command|runtime|false" ;;
|
|
config) echo "Prepare config and skills|configuration|false" ;;
|
|
setup) echo "Configure API keys and settings|configuration|true" ;;
|
|
gateway) echo "Configure gateway service|configuration|true" ;;
|
|
desktop) echo "Build desktop app|runtime|false" ;;
|
|
complete) echo "Finish install|runtime|false" ;;
|
|
esac
|
|
}
|
|
|
|
emit_manifest() {
|
|
printf '%s' '{"protocol_version":1,"stages":['
|
|
_sep=""
|
|
for _s in $(stage_names); do
|
|
IFS='|' read -r _title _category _needs <<< "$(stage_record "$_s")"
|
|
printf '%s{"name":"%s","title":"%s","category":"%s","needs_user_input":%s}' \
|
|
"$_sep" "$_s" "$_title" "$_category" "$_needs"
|
|
_sep=","
|
|
done
|
|
printf '%s\n' ']}'
|
|
}
|
|
|
|
stage_prerequisites() {
|
|
command -v git >/dev/null 2>&1 || fail "git is required. Install it with your system package manager."
|
|
command -v curl >/dev/null 2>&1 || fail "curl is required. Install it with your system package manager."
|
|
log "prerequisites ok (git, curl)"
|
|
}
|
|
|
|
stage_repository() {
|
|
if [ -d "$INSTALL_DIR/.git" ]; then
|
|
log "updating $INSTALL_DIR"
|
|
git -C "$INSTALL_DIR" fetch origin "$BRANCH" || fail "git fetch failed"
|
|
git -C "$INSTALL_DIR" checkout "$BRANCH" || fail "git checkout failed"
|
|
git -C "$INSTALL_DIR" pull --ff-only origin "$BRANCH" || log "not fast-forwardable; keeping local state"
|
|
else
|
|
log "cloning $REPO_URL ($BRANCH) into $INSTALL_DIR"
|
|
mkdir -p "$(dirname "$INSTALL_DIR")"
|
|
local staged attempt cloned=false
|
|
staged="$(mktemp -d "$(dirname "$INSTALL_DIR")/.hermes-clone-XXXXXX")" || fail "cannot stage clone"
|
|
for attempt in 1 2 3; do
|
|
if git clone --branch "$BRANCH" "$REPO_URL" "$staged/tree"; then
|
|
cloned=true
|
|
break
|
|
fi
|
|
rm -rf "$staged/tree"
|
|
[ "$attempt" = 3 ] || sleep "$((attempt * 5))"
|
|
done
|
|
if [ "$cloned" = false ]; then
|
|
log "direct clone failed; trying deferred blob download"
|
|
if git clone --depth 1 --single-branch --filter=blob:none --no-checkout \
|
|
--branch "$BRANCH" "$REPO_URL" "$staged/tree"; then
|
|
for attempt in 1 2; do
|
|
if git -C "$staged/tree" reset --hard HEAD; then
|
|
cloned=true
|
|
break
|
|
fi
|
|
[ "$attempt" = 2 ] || sleep 5
|
|
done
|
|
fi
|
|
fi
|
|
if [ "$cloned" = false ]; then
|
|
rm -rf "$staged"
|
|
fail "git clone failed; no checkout published"
|
|
fi
|
|
if ! mv "$staged/tree" "$INSTALL_DIR"; then
|
|
rm -rf "$staged"
|
|
fail "cannot publish cloned checkout"
|
|
fi
|
|
rmdir "$staged"
|
|
fi
|
|
if [ -n "$INSTALL_COMMIT" ]; then
|
|
git -C "$INSTALL_DIR" checkout "$INSTALL_COMMIT" || fail "could not pin commit $INSTALL_COMMIT"
|
|
fi
|
|
}
|
|
|
|
stage_venv() {
|
|
# Keep the installer stage protocol; PM alone creates dependency environments.
|
|
local boot_py
|
|
bootstrap_python
|
|
log "bootstrap Python ready; PM prepares the dependency environment"
|
|
}
|
|
|
|
# Tool-only bootstrap: acquire uv and Python before PM's own dependencies exist.
|
|
# The application dependency graph is never installed in this interpreter.
|
|
bootstrap_python() {
|
|
ensure_uv
|
|
local _py
|
|
# Read packages.python.version by following object names and braces, not
|
|
# indentation — same pre-Python reader contract as setup-hermes.sh's pin().
|
|
_py="$(awk -F '"' '
|
|
/^[[:space:]]*("[^"]+"[[:space:]]*:[[:space:]]*)?\{/ { path[++depth] = $2; next }
|
|
/^[[:space:]]*\}[[:space:]]*,?[[:space:]]*$/ { delete path[depth--]; next }
|
|
path[2] == "packages" && path[3] == "python" && $2 == "version" && depth == 3 { print $4; exit }
|
|
' "$INSTALL_DIR/pm/lock.json" | cut -d+ -f1 | cut -d. -f1,2)"
|
|
[ -n "$_py" ] || _py="3.14"
|
|
# Only base interpreters qualify: an activated app venv must not become
|
|
# PM's bootstrap parent. Prefer the existing managed Python, then a host
|
|
# Python of the same supported minor before attempting a download (#10778).
|
|
# This interpreter only boots PM; PM still owns the exact runtime pin.
|
|
if ! boot_py="$(UV_SYSTEM_PYTHON=1 UV_NO_PROJECT=1 "$UV_CMD" python find --managed-python "$_py" 2>/dev/null)" \
|
|
&& ! boot_py="$("$UV_CMD" python find --system --no-project "$_py" 2>/dev/null)"; then
|
|
"$UV_CMD" python install --no-bin "$_py" || fail "bootstrap Python installation failed"
|
|
boot_py="$(UV_SYSTEM_PYTHON=1 UV_NO_PROJECT=1 "$UV_CMD" python find --managed-python "$_py")" || fail "bootstrap Python lookup failed"
|
|
fi
|
|
boot_py="${boot_py%$'\r'}"
|
|
[ -x "$boot_py" ] && "$boot_py" --version >/dev/null 2>&1 || fail "bootstrap Python is not executable: $boot_py"
|
|
}
|
|
|
|
# uv exits before PM can replace its tool entry. pm.cli then prepares and
|
|
# enters its independently locked runtime before mutating application deps.
|
|
bootstrap_pm() {
|
|
local boot_py
|
|
bootstrap_python
|
|
log "delegating python + venv + tools to pm (hash-verified via uv.lock)"
|
|
(cd "$INSTALL_DIR" && "$boot_py" -m pm.cli install) || fail "pm install failed"
|
|
}
|
|
|
|
stage_python_deps() {
|
|
bootstrap_pm
|
|
}
|
|
|
|
stage_node_deps() {
|
|
# Tool binaries, node, browsers: pm packages, installed on demand or
|
|
# via `hermes pm install`. Nothing to do at bootstrap time.
|
|
log "tool dependencies are managed by pm (hermes pm install)"
|
|
}
|
|
|
|
stage_path() {
|
|
local link_dir="$HOME/.local/bin"
|
|
local boot_py
|
|
bootstrap_python
|
|
(cd "$INSTALL_DIR" && "$boot_py" -I -X utf8 hermes_cli/_launchers.py "$link_dir") || fail "launcher publication failed"
|
|
case ":$PATH:" in
|
|
*":$link_dir:"*) : ;;
|
|
*) log "add $link_dir to your PATH to use the hermes command" ;;
|
|
esac
|
|
log "hermes command installed at $link_dir/hermes"
|
|
}
|
|
|
|
stage_config() {
|
|
mkdir -p "$HERMES_HOME"/cron "$HERMES_HOME"/sessions "$HERMES_HOME"/logs \
|
|
"$HERMES_HOME"/pairing "$HERMES_HOME"/hooks "$HERMES_HOME"/image_cache \
|
|
"$HERMES_HOME"/audio_cache "$HERMES_HOME"/memories "$HERMES_HOME"/skills
|
|
if [ ! -f "$HERMES_HOME/.env" ]; then
|
|
cp "$INSTALL_DIR/.env.example" "$HERMES_HOME/.env" 2>/dev/null || touch "$HERMES_HOME/.env"
|
|
fi
|
|
chmod 600 "$HERMES_HOME/.env"
|
|
if [ ! -f "$HERMES_HOME/config.yaml" ] && [ -f "$INSTALL_DIR/cli-config.yaml.example" ]; then
|
|
cp "$INSTALL_DIR/cli-config.yaml.example" "$HERMES_HOME/config.yaml"
|
|
fi
|
|
log "config prepared in $HERMES_HOME"
|
|
}
|
|
|
|
stage_setup() {
|
|
if [ "$NON_INTERACTIVE" = true ]; then return 0; fi
|
|
"$INSTALL_DIR/.hermes/bin/hermes" setup || fail "setup failed"
|
|
}
|
|
|
|
stage_gateway() {
|
|
if [ "$NON_INTERACTIVE" = true ]; then return 0; fi
|
|
"$INSTALL_DIR/.hermes/bin/hermes" gateway install || fail "gateway installation failed"
|
|
}
|
|
|
|
stage_desktop() {
|
|
# `hermes desktop --build-only` is the current authority (same path as
|
|
# `hermes gui` / the update flow); no installer-local node/electron code.
|
|
"$INSTALL_DIR/.hermes/bin/hermes" desktop --build-only || fail "desktop build failed"
|
|
}
|
|
|
|
stage_complete() {
|
|
local commit
|
|
commit="$INSTALL_COMMIT"
|
|
[ -n "$commit" ] || commit=$(git -C "$INSTALL_DIR" rev-parse HEAD 2>/dev/null) || commit=""
|
|
if [ -n "$commit" ]; then
|
|
printf '{\n "schemaVersion": 1,\n "pinnedCommit": "%s",\n "pinnedBranch": "%s",\n "completedAt": "%s"\n}\n' \
|
|
"$commit" "$BRANCH" "$(date -u +%Y-%m-%dT%H:%M:%S.000Z)" > "$INSTALL_DIR/.hermes-bootstrap-complete.tmp"
|
|
mv -f "$INSTALL_DIR/.hermes-bootstrap-complete.tmp" "$INSTALL_DIR/.hermes-bootstrap-complete"
|
|
fi
|
|
log "install complete. Run: hermes"
|
|
}
|
|
|
|
run_stage() (
|
|
# Keep failure handling out of conditional calls, which disable errexit.
|
|
set -e
|
|
STAGE="$1"
|
|
STAGE_REASON=""
|
|
STAGE_SKIPPED=false
|
|
trap 'stage_result "$?"' EXIT
|
|
if [ "$NON_INTERACTIVE" = true ] && { [ "$STAGE" = setup ] || [ "$STAGE" = gateway ]; }; then
|
|
STAGE_SKIPPED=true
|
|
STAGE_REASON="needs user input"
|
|
exit 0
|
|
fi
|
|
case "$1" in
|
|
prerequisites) stage_prerequisites ;;
|
|
repository) stage_repository ;;
|
|
venv) stage_venv ;;
|
|
python-deps) stage_python_deps ;;
|
|
node-deps) stage_node_deps ;;
|
|
path) stage_path ;;
|
|
config) stage_config ;;
|
|
setup) stage_setup ;;
|
|
gateway) stage_gateway ;;
|
|
desktop) stage_desktop ;;
|
|
complete) stage_complete ;;
|
|
*) STAGE_REASON="unknown stage: $1"; printf '%s\n' "$STAGE_REASON" >&2; exit 2 ;;
|
|
esac
|
|
)
|
|
|
|
# Main. Guarded so the script can be SOURCED for its functions (the
|
|
# installer-test harness sources it with --manifest, which must define
|
|
# the functions and stop before main).
|
|
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
|
|
if [ "$WANT_MANIFEST" = true ]; then
|
|
emit_manifest
|
|
exit 0
|
|
fi
|
|
|
|
if [ -n "$STAGE" ] && [ "$JSON" = true ]; then
|
|
trap 'stage_result "$?"' EXIT
|
|
fi
|
|
check_platform
|
|
trap - EXIT
|
|
|
|
if [ -n "$STAGE" ]; then
|
|
run_stage "$STAGE"
|
|
exit "$?"
|
|
fi
|
|
|
|
# No --stage: run the whole ladder — the same authoritative list the
|
|
# manifest prints, so --include-desktop inserts desktop here too.
|
|
for s in $(stage_names); do
|
|
run_stage "$s"
|
|
rc=$?
|
|
[ "$rc" -eq 0 ] || exit "$rc"
|
|
done
|
|
fi
|