#!/bin/sh # AgentHub connector installer. # # curl -fsSL | sh # curl -fsSL | sh -s -- --code ABCD-1234 # # It downloads a prebuilt binary, verifies its sha256 against the published # SHA256SUMS, installs it to /usr/local/bin, and — on Linux with systemd or on # macOS with launchd — installs the background service. It needs no Go toolchain # and no source checkout. # # THIS IS A TEMPLATE. The two __AGENTHUB_*__ placeholders below are substituted # by connector/build-release.sh when it publishes the release tree, so no host # name lives in this repository. The copy served by the release host has them # filled in. To run this repo copy directly, set AGENTHUB_BASE_URL (and # AGENTHUB_HUB_URL, or pass --hub). # # Options: # --code pairing code from the AgentHub app (10-minute life, one use) # --hub AgentHub server (default: the host this release was built for) # --version install this version instead of the latest # --prefix install directory (default /usr/local/bin) # --hermes-url Hermes API base URL (default http://127.0.0.1:8642) # --hermes-key Hermes API key (default $API_SERVER_KEY) # --name display name for this agent # --no-service install the binary only # # Trust model: HTTPS to the release host, plus the sha256 it publishes. Both come # from the same origin, so this proves the download is intact — it does not prove # who produced it. Read this script before you pipe it into a shell. set -eu BASE_URL="${AGENTHUB_BASE_URL:-https://get.agent-hub.rnd.baby}" HUB_URL="${AGENTHUB_HUB_URL:-https://agent-hub.rnd.baby}" PREFIX="${PREFIX:-/usr/local/bin}" BIN_NAME="agenthub" VERSION="" CODE="" HERMES_URL="" HERMES_KEY="" NAME="" INSTALL_SERVICE=1 log() { printf '\033[36m›\033[0m %s\n' "$*"; } err() { printf '\033[31m✗\033[0m %s\n' "$*" >&2; } die() { err "$*"; exit 1; } while [ $# -gt 0 ]; do case "$1" in --code) CODE="${2:-}"; shift 2 ;; --hub) HUB_URL="${2:-}"; shift 2 ;; --version) VERSION="${2:-}"; shift 2 ;; --prefix) PREFIX="${2:-}"; shift 2 ;; --hermes-url) HERMES_URL="${2:-}"; shift 2 ;; --hermes-key) HERMES_KEY="${2:-}"; shift 2 ;; --name) NAME="${2:-}"; shift 2 ;; --no-service) INSTALL_SERVICE=0; shift ;; # Not `sed -n … "$0"`: piped into a shell, $0 is "sh" and this file is not # on disk to read back. -h|--help) cat <<'USAGE' AgentHub connector installer. curl -fsSL | sh curl -fsSL | sh -s -- --code ABCD-1234 Downloads a prebuilt binary, verifies its sha256 against the published SHA256SUMS, installs it to /usr/local/bin, and installs the background service (systemd on Linux, launchd on macOS). Needs no Go toolchain and no checkout. --code pairing code from the AgentHub app (10-minute life, one use) --hub AgentHub server (default: the host this release was built for) --version install this version instead of the latest --prefix install directory (default /usr/local/bin) --hermes-url Hermes API base URL (default http://127.0.0.1:8642) --hermes-key Hermes API key (default $API_SERVER_KEY) --name display name for this agent --no-service install the binary only Environment: AGENTHUB_BASE_URL overrides the release host, AGENTHUB_HUB_URL the AgentHub server. USAGE exit 0 ;; *) die "unknown option: $1" ;; esac done command -v curl >/dev/null 2>&1 || die "curl is required" # The published copy has these substituted. Reaching here with a placeholder means # someone is running the repo template directly. case "$BASE_URL" in *__AGENTHUB_*__*) die "no release host configured — set AGENTHUB_BASE_URL, or use the installer the release host serves" ;; esac case "$HUB_URL" in *__AGENTHUB_*__*) HUB_URL="" ;; # not fatal: --hub covers it, and pairing is optional esac # ---- platform ---------------------------------------------------------------- case "$(uname -s)" in Linux) OS="linux" ;; Darwin) OS="darwin" ;; *) die "unsupported operating system: $(uname -s). AgentHub supports Linux and macOS." ;; esac case "$(uname -m)" in x86_64|amd64) ARCH="amd64" ;; arm64|aarch64) ARCH="arm64" ;; *) die "unsupported architecture: $(uname -m)" ;; esac PLATFORM="$OS-$ARCH" # ---- elevation --------------------------------------------------------------- SUDO="" if [ "$(id -u)" -ne 0 ]; then if command -v sudo >/dev/null 2>&1; then SUDO="sudo" else die "not root and sudo is missing; re-run as root or pass --prefix \$HOME/.local/bin" fi fi # Writing into a directory we already own needs no elevation. # (Plain `[ … ] && SUDO=""` would abort the script under `set -e` when the test # fails, which is exactly the case that needs sudo.) if [ -w "$PREFIX" ]; then SUDO=""; fi # ---- checksum tool ----------------------------------------------------------- if command -v sha256sum >/dev/null 2>&1; then sha256_of() { sha256sum "$1" | cut -d' ' -f1; } elif command -v shasum >/dev/null 2>&1; then sha256_of() { shasum -a 256 "$1" | cut -d' ' -f1; } else die "no sha256 tool found (need sha256sum or shasum)" fi # ---- download and verify ----------------------------------------------------- if [ -z "$VERSION" ]; then VERSION="$(curl -fsSL "$BASE_URL/bin/latest.txt" | tr -d ' \t\r\n')" \ || die "cannot read the current version from $BASE_URL/bin/latest.txt" fi [ -n "$VERSION" ] || die "the release host published an empty version" # Version directories are tagged (v0.3.1). Accept either form on --version. case "$VERSION" in v*) ;; *) VERSION="v$VERSION" ;; esac ASSET="$BIN_NAME-$PLATFORM" ASSET_URL="$BASE_URL/bin/$VERSION/$ASSET" TMP="$(mktemp -d)" trap 'rm -rf "$TMP"' EXIT INT TERM log "Downloading $BIN_NAME $VERSION for $PLATFORM" curl -fsSL "$ASSET_URL" -o "$TMP/$ASSET" || die "download failed: $ASSET_URL" curl -fsSL "$BASE_URL/bin/$VERSION/SHA256SUMS" -o "$TMP/SHA256SUMS" \ || die "cannot fetch SHA256SUMS for $VERSION" WANT="$(awk -v a="$ASSET" '$2 == a || $2 == "*"a { print $1 }' "$TMP/SHA256SUMS" | head -n1)" [ -n "$WANT" ] || die "SHA256SUMS has no entry for $ASSET" GOT="$(sha256_of "$TMP/$ASSET")" [ "$WANT" = "$GOT" ] || die "checksum mismatch for $ASSET (want $WANT, got $GOT) — refusing to install" log "Checksum verified" # ---- install ----------------------------------------------------------------- $SUDO mkdir -p "$PREFIX" $SUDO install -m 0755 "$TMP/$ASSET" "$PREFIX/$BIN_NAME" log "Installed $PREFIX/$BIN_NAME" BIN="$PREFIX/$BIN_NAME" case ":$PATH:" in *":$PREFIX:"*) ;; *) log "note: $PREFIX is not on your PATH — call the binary as $BIN" ;; esac # ---- pair and run ------------------------------------------------------------ if [ -n "$CODE" ]; then set -- pair "$CODE" # An empty hub is legal only when the binary carries a stamped default. if [ -n "$HUB_URL" ]; then set -- "$@" --hub "$HUB_URL"; fi if [ -n "$HERMES_URL" ]; then set -- "$@" --hermes-url "$HERMES_URL"; fi if [ -n "$HERMES_KEY" ]; then set -- "$@" --hermes-key "$HERMES_KEY"; fi if [ -n "$NAME" ]; then set -- "$@" --name "$NAME"; fi "$BIN" "$@" if [ "$INSTALL_SERVICE" -eq 1 ]; then # `service install` writes a launchd plist on macOS and a systemd unit on # Linux, for the current user. It is the connector's own installer, so this # script does not duplicate unit templates. "$BIN" service install || log "service install failed — run '$BIN start' by hand" fi log "Done. Approve the agent in the AgentHub app." else if [ -n "$HUB_URL" ]; then PAIR_HINT="pair --hub $HUB_URL"; else PAIR_HINT="pair "; fi cat < from the AgentHub app. It lives ten minutes and works once. EOF fi