#!/usr/bin/env bash # forg installer # Usage: # curl -fsSL https://forg.pro/install | sh # curl -fsSL https://forg.pro/install | sh -s -- lic_xxxxxxxxxxxxxxxxxxxx # curl -fsSL https://forg.pro/install | LICENSE_KEY=lic_xxxxxxxxxxxxxxxxxxxx sh # curl -fsSL https://forg.pro/install | sh -s -- --version v2.1.0 --dir ~/bin set -eu DL_BASE="https://api.forg.pro/install/dl" CHECK_URL="https://api.forg.pro/install/license/check" VERSION="${VERSION:-latest}" INSTALL_DIR="" BIN_NAME="forg" LICENSE_KEY="${LICENSE_KEY:-}" SKIP_LICENSE="${SKIP_LICENSE:-0}" # Positional first arg = license key (if it looks like one) if [ $# -gt 0 ]; then case "$1" in lic_*|forg_lic_*) LICENSE_KEY="$1"; shift ;; esac fi while [ $# -gt 0 ]; do case "$1" in --version) VERSION="$2"; shift 2 ;; --version=*) VERSION="${1#*=}"; shift ;; --dir) INSTALL_DIR="$2"; shift 2 ;; --dir=*) INSTALL_DIR="${1#*=}"; shift ;; --license|--key) LICENSE_KEY="$2"; shift 2 ;; --license=*|--key=*) LICENSE_KEY="${1#*=}"; shift ;; --skip-license) SKIP_LICENSE=1; shift ;; --help|-h) cat <<'EOF' forg installer first positional arg (or --license / $LICENSE_KEY) --version install specific release (default: latest) --dir install dir (default: ~/.local/bin) --skip-license bypass license precheck (for offline / air-gapped installs) --help this message The installer requires a license key. Get one from https://forg.pro/pricing or https://forg.pro/dashboard/license if you already have a subscription. EOF exit 0 ;; *) echo "unknown arg: $1" >&2; exit 2 ;; esac done # ── color ────────────────────────────────────────────────── if [ -t 1 ]; then C_GRN="\033[32m"; C_YLW="\033[33m"; C_RED="\033[31m"; C_DIM="\033[2m"; C_RST="\033[0m" else C_GRN=""; C_YLW=""; C_RED=""; C_DIM=""; C_RST="" fi say() { printf "%s\n" "$*"; } ok() { printf "${C_GRN}✓${C_RST} %s\n" "$*"; } warn() { printf "${C_YLW}!${C_RST} %s\n" "$*"; } err() { printf "${C_RED}✗${C_RST} %s\n" "$*" >&2; } # ── prerequisites ────────────────────────────────────────── need() { command -v "$1" >/dev/null 2>&1 || { err "missing: $1"; exit 1; }; } need curl need uname if command -v sha256sum >/dev/null 2>&1; then SHA_CMD="sha256sum"; else SHA_CMD="shasum -a 256"; fi # ── license: arg → env → interactive prompt ──────────────── prompt_license() { if [ ! -t 0 ] && [ ! -r /dev/tty ]; then err "no license key provided and no TTY for interactive prompt" err " pass it as the first argument: ... | sh -s -- lic_xxxxxxxxxxxxxxxxxxxx" err " or set the env var: ... | LICENSE_KEY=lic_xxx sh" exit 1 fi printf "${C_DIM}License key (lic_…):${C_RST} " >/dev/tty IFS= read -r LICENSE_KEY /dev/null || echo '{"valid":false,"reason":"network"}')" case "$RESP" in *'"valid":true'*) ok "license valid";; *'"reason":"format"'*) err "license key format invalid"; exit 1 ;; *'"reason":"network"'*) err "could not reach license server (network)"; exit 1 ;; *) err "license not active. Check https://forg.pro/dashboard/license" err " response: $RESP" exit 1 ;; esac fi # ── detect os / arch ─────────────────────────────────────── UNAME_S="$(uname -s)" case "$UNAME_S" in Darwin) GOOS="darwin" ;; Linux) GOOS="linux" ;; *) err "unsupported OS: $UNAME_S. Windows: download forg-windows-amd64.exe via https://api.forg.pro/install/dl/forg-windows-amd64.exe"; exit 1 ;; esac UNAME_M="$(uname -m)" case "$UNAME_M" in x86_64|amd64) GOARCH="amd64" ;; arm64|aarch64) GOARCH="arm64" ;; *) err "unsupported arch: $UNAME_M"; exit 1 ;; esac if [ "$GOOS" = "linux" ] && [ "$GOARCH" = "arm64" ]; then err "linux/arm64 build not yet published. Track https://forg.pro/changelog" exit 1 fi ASSET="forg-${GOOS}-${GOARCH}" ok "platform: ${GOOS}/${GOARCH}" URL="${DL_BASE}/${ASSET}?v=${VERSION}" SUMS_URL="${DL_BASE}/SHA256SUMS?v=${VERSION}" ok "version: ${VERSION}" # ── install dir ──────────────────────────────────────────── if [ -z "$INSTALL_DIR" ]; then INSTALL_DIR="$HOME/.local/bin" fi mkdir -p "$INSTALL_DIR" ok "install dir: ${INSTALL_DIR}" # ── download to temp ─────────────────────────────────────── TMP="$(mktemp -d -t forg-install-XXXXXX)" trap 'rm -rf "$TMP"' EXIT say " downloading ${ASSET}…" if ! curl -fsSL "$URL" -o "$TMP/${ASSET}"; then err "download failed: $URL" exit 1 fi say " downloading SHA256SUMS…" if ! curl -fsSL "$SUMS_URL" -o "$TMP/SHA256SUMS"; then err "failed to download SHA256SUMS — cannot verify binary integrity" exit 1 fi # Extract expected hash for this asset EXPECTED="$(grep " ${ASSET}$" "$TMP/SHA256SUMS" | awk '{print $1}')" if [ -z "$EXPECTED" ]; then err "no checksum entry found for ${ASSET} in SHA256SUMS" exit 1 fi # Validate hash format: exactly 64 hexadecimal characters if ! echo "$EXPECTED" | grep -qE '^[a-f0-9]{64}$'; then err "invalid hash format for ${ASSET} (expected 64 hex chars, got '$EXPECTED')" exit 1 fi # Compute and verify actual hash ACTUAL="$($SHA_CMD "$TMP/${ASSET}" | awk '{print $1}')" if [ "$ACTUAL" != "$EXPECTED" ]; then err "sha256 mismatch! expected $EXPECTED, got $ACTUAL" exit 1 fi ok "sha256 verified" # ── install ──────────────────────────────────────────────── TARGET="${INSTALL_DIR}/${BIN_NAME}" chmod +x "$TMP/${ASSET}" mv "$TMP/${ASSET}" "$TARGET" ok "installed: ${TARGET}" # ── PATH wiring (idempotent) ─────────────────────────────── add_path_to_rc() { RC="$1" LINE="$2" if [ -f "$RC" ] && grep -Fq "$LINE" "$RC" 2>/dev/null; then return 0 fi printf "\n# Added by forg installer\n%s\n" "$LINE" >> "$RC" ok "PATH added to $RC" } case ":$PATH:" in *":${INSTALL_DIR}:"*) ;; *) PATH_LINE="export PATH=\"${INSTALL_DIR}:\$PATH\"" case "$SHELL" in */zsh) add_path_to_rc "$HOME/.zshrc" "$PATH_LINE" ;; */bash) add_path_to_rc "$HOME/.bashrc" "$PATH_LINE" [ -f "$HOME/.bash_profile" ] && add_path_to_rc "$HOME/.bash_profile" "$PATH_LINE" ;; *) warn "unknown shell $SHELL — add manually: $PATH_LINE" ;; esac warn "open a new terminal OR run: source ~/.zshrc (or ~/.bashrc)" ;; esac # ── verify ───────────────────────────────────────────────── if VERSION_OUT="$("$TARGET" --version 2>/dev/null || "$TARGET" version 2>/dev/null)"; then ok "$VERSION_OUT" fi # ── auto-activate (binds license to this machine) ────────── if [ -n "$LICENSE_KEY" ]; then say " activating…" if ! "$TARGET" activate "$LICENSE_KEY"; then err "activation failed — run manually: forg activate $LICENSE_KEY" exit 1 fi ok "activated" # ── auto-start background service ────────────────────────── say " starting service…" if "$TARGET" on >/dev/null 2>&1; then ok "forg on — service running" else warn "could not auto-start service; run manually: forg on" fi fi cat <