OverviewFilesCommitsIssuesDocsReleasesPeers

pgh / scripts / bootstrap-install.sh

209 lines · bash ref:
#!/usr/bin/env bash
# pgh + pgh-sidecar installer (bare runtime) — CANONICAL stack.
#
# Bare runtime is bundled inside each tarball — NO system node, npm, or
# bare required on the target machine. Works on a fresh install.
#
# (The experimental stack installer lives in bootstrap-install-experimental.sh.)
#
# Usage:
#   curl -sSfL https://7rnx.net/git/bootstrap/install.sh | sh
#   curl -sSfL https://7rnx.net/git/bootstrap/install.sh | sh -s -- --prefix ~/opt
#
# Flags:
#   --prefix <dir>     install prefix (default: ~/.local)
#   --base <url>       override download base URL
#   --from-dir <path>  use local tarballs from <path> (skip download)
#   --no-path-warn     skip the PATH-not-set warning
#
# After install:
#   pgh --help
#   pgh app start pgh-sidecar                   # bring up the (adopted) sidecar
#   pgh init                                     # one-time
#   pgh app install pgh://<reg>/pear-git-v3
#   pgh app start pear-git-v3
#
# NOTE: This is the version-controlled SOURCE of the bootstrap installer
# (the served copy lives in the daemon's <storage>/bootstrap/ dir). Keep
# them in sync when changing this.

set -eu

DEFAULT_BASE="${PGH_DOWNLOAD_BASE:-https://7rnx.net/git/bootstrap}"
PREFIX="${HOME}/.local"
BASE=""
LOCAL_DIR=""
SKIP_PATH_WARN=0

while [ $# -gt 0 ]; do
  case "$1" in
    --prefix) PREFIX="$2"; shift 2 ;;
    --base) BASE="$2"; shift 2 ;;
    --from-dir) LOCAL_DIR="$2"; shift 2 ;;
    --no-path-warn) SKIP_PATH_WARN=1; shift ;;
    -h|--help) sed -n '2,28p' "$0"; exit 0 ;;
    *) echo "unknown flag: $1" >&2; exit 1 ;;
  esac
done

BASE="${BASE:-$DEFAULT_BASE}"
# Each package installs into the device-wide store at store/apps/<pkg>
# (store/apps/pgh, store/apps/pgh-sidecar) — the same location `pgh app
# install` materializes into, so apps + deps live under one store, not a
# parallel flat ~/.local/share/<pkg> tree.
SHARE_DIR="${PREFIX}/share/store/apps"
BIN_DIR="${PREFIX}/bin"

# Detect platform
os_raw="$(uname -s)"
case "$os_raw" in
  Linux)  OS="linux" ;;
  Darwin) OS="darwin" ;;
  *) echo "unsupported OS: $os_raw" >&2; exit 1 ;;
esac
arch_raw="$(uname -m)"
case "$arch_raw" in
  x86_64|amd64)  ARCH="x64" ;;
  aarch64|arm64) ARCH="arm64" ;;
  *) echo "unsupported arch: $arch_raw" >&2; exit 1 ;;
esac
LABEL="${OS}-${ARCH}"

PKGS="pgh pgh-sidecar"

echo "pgh installer (bare runtime)"
echo "  target: ${LABEL}"
echo "  prefix: ${PREFIX}"
echo

fetch () {
  local url="$1" out="$2"
  if [ -n "$LOCAL_DIR" ]; then
    cp "${LOCAL_DIR}/$(basename "$url")" "$out"
  elif command -v curl >/dev/null 2>&1; then
    curl -sSfL "$url" -o "$out"
  elif command -v wget >/dev/null 2>&1; then
    wget -qO "$out" "$url"
  else
    echo "neither curl nor wget available — install one and rerun" >&2
    exit 1
  fi
}

mkdir -p "$SHARE_DIR" "$BIN_DIR"

for PKG in $PKGS; do
  TARBALL="${PKG}-bootstrap-bare-${LABEL}.tar.gz"
  STAGE="${SHARE_DIR}/${PKG}"
  TMP="$(mktemp -t "${TARBALL}.XXXXXX")"

  echo "${PKG}:"
  echo "  fetching ${BASE}/${TARBALL}"
  fetch "${BASE}/${TARBALL}" "$TMP"

  echo "  extracting to ${STAGE}"
  rm -rf "${STAGE}.new" 2>/dev/null || true
  mkdir -p "${STAGE}.new"
  tar -xzf "$TMP" -C "${STAGE}.new"
  rm -f "$TMP"

  # Tarball extracts to <stage>.new/<pkg>-bootstrap-bare/; move it into place.
  INNER="${STAGE}.new/${PKG}-bootstrap-bare"
  if [ ! -d "$INNER" ]; then
    echo "  unexpected tarball layout in ${STAGE}.new" >&2
    ls -la "${STAGE}.new" >&2
    exit 1
  fi

  if [ -d "$STAGE" ]; then
    # Existing install: replace atomically by swap-rename.
    mv "$STAGE" "${STAGE}.old"
  fi
  mv "$INNER" "$STAGE"
  rm -rf "${STAGE}.new" "${STAGE}.old" 2>/dev/null || true

  # NOTE: the launcher is NOT created here. `pgh app adopt` (below) writes the
  # canonical `#!/bin/sh\nexec bare <entry> "$@"` launcher script for each
  # package. A raw `ln -sf bin/${PKG}` runs bin/pgh under its
  # `#!/usr/bin/env node` shebang → pgh under node → the bundled
  # corestore/hyperschema crash (c.optionalBuffer undefined). That ln -sf was
  # the root of the 2026-06-04/05 node-launcher failure — do not reintroduce.
  chmod +x "${STAGE}/bin/${PKG}" 2>/dev/null || true
  echo "  extracted ${PKG}"
  echo
done

# PATH check
if [ "$SKIP_PATH_WARN" -ne 1 ]; then
  case ":$PATH:" in
    *":${BIN_DIR}:"*) : ;;
    *)
      echo "WARNING: ${BIN_DIR} is not on your PATH."
      echo "Add this line to your shell rc:"
      echo
      echo "    export PATH=\"${BIN_DIR}:\$PATH\""
      echo
      ;;
  esac
fi

# Register BOTH pgh and pgh-sidecar as MANAGED apps via the canonical
# `pgh app adopt`. Adopt writes the proper `exec bare <entry>` launcher script
# AND the .pgh-install.json, so each shows up in `pgh app status` and is driven
# by `pgh app {start,stop,restart,boot}`. This replaces the old `ln -sf`
# launcher (which ran pgh under node) — adopt is the ONLY canonical way to
# create a launcher.
#
# Chicken-egg: pgh has no launcher yet, so drive the FIRST pgh invocation
# directly through the bundled bare runtime from the just-extracted tarball.
# (resolveRuntimePath('bare') then records THIS bare as the launcher's runtime,
# so the result is fully self-contained — no system bare required.)
PGH_BARE="${SHARE_DIR}/pgh/runtime/bare"
# The bootstrap-bare tarball renames the JS entry to bin/pgh.bare.js and makes
# bin/pgh a `#!/bin/sh` wrapper. Drive the first invocation at the JS entry
# under the bundled bare — NOT `bare bin/pgh` (that would feed bare the shell
# wrapper and fail to parse).
PGH_ENTRY="${SHARE_DIR}/pgh/bin/pgh.bare.js"
[ -x "$PGH_BARE" ] || { echo "ERROR: bundled bare runtime missing at ${PGH_BARE}" >&2; exit 1; }
[ -f "$PGH_ENTRY" ] || { echo "ERROR: pgh bare entry missing at ${PGH_ENTRY}" >&2; exit 1; }

for PKG in $PKGS; do
  echo "Registering ${PKG} as a managed app..."
  # Capture adopt output (NOT /dev/null) and VERIFY the manifest was written.
  # A swallowed adopt failure is how an app silently ends up unmanaged
  # (no .pgh-install.json) → invisible to `pgh app status`/`boot`.
  adopt_out="$("$PGH_BARE" "$PGH_ENTRY" app adopt "${SHARE_DIR}/${PKG}" --name "${PKG}" 2>&1)"
  adopt_rc=$?
  if [ "${adopt_rc}" -eq 0 ] && [ -f "${SHARE_DIR}/${PKG}/.pgh-install.json" ]; then
    echo "  adopted (launcher ${BIN_DIR}/${PKG} + manifest written)"
  else
    echo "" >&2
    echo "ERROR: failed to register ${PKG} as a managed app (rc=${adopt_rc})." >&2
    echo "  Without .pgh-install.json it is invisible to 'pgh app status'/'boot'." >&2
    [ -n "${adopt_out}" ] && { echo "  adopt output:" >&2; echo "${adopt_out}" | sed 's/^/    /' >&2; }
    echo "  Remediate: ${PGH_BARE} ${PGH_ENTRY} app adopt ${SHARE_DIR}/${PKG} --name ${PKG}" >&2
    exit 1
  fi
done

# pgh-sidecar is the always-on daemon → autostart so it survives reboots and
# `pgh app boot` brings it up. (pgh is a CLI; adopting it is enough — its
# launcher + status entry are what matter, it is never "started".)
if "$PGH_BARE" "$PGH_ENTRY" app autostart pgh-sidecar on >/dev/null 2>&1; then
  echo "pgh-sidecar autostart enabled"
else
  echo "WARNING: could not enable pgh-sidecar autostart — run: pgh app autostart pgh-sidecar on" >&2
fi

cat <<EOF

Installed.

Next steps:
  pgh app start pgh-sidecar                   # bring up the (adopted) sidecar
  pgh init                                     # one-time local registry setup
  pgh app install pgh://<registry-key>/pear-git-v3
  pgh app start pear-git-v3

EOF