#!/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