#!/bin/sh # gox bootstrap — served at https://gox.vzs.one, HTTPS only: # curl --proto '=https' --tlsv1.2 -fsSL https://gox.vzs.one | sh # # Makes this machine ready for VZS agent environments, and repairs it when run again: # detect mac / wsl / linux and the package manager -> install git, gh, uv if missing -> GitHub login (gh) # -> install or upgrade gox (uv tool) -> set up skills for each provider CLI found -> checklist. # # Options: --yes no questions (assume yes; sudo may still ask for a password) # --dry-run show what would happen, change nothing # --ref REF gox source ref (default: main) # --no-auth skip the GitHub login step (CI / testing) # --no-setup skip the per-provider skill setup # No secrets live here: it is install logic only. Prompts read from /dev/tty, so piping from curl works. set -eu REPO="vzs-one/agents-env-setup" REF="main"; YES=0; DRY=0; AUTH=1; SETUP=1 while [ $# -gt 0 ]; do case "$1" in --yes|-y) YES=1 ;; --dry-run) DRY=1 ;; --no-auth) AUTH=0 ;; --no-setup) SETUP=0 ;; --ref) shift; REF="${1:?--ref needs a value}" ;; -h|--help) sed -n '2,14p' "$0" 2>/dev/null || true; exit 0 ;; *) printf 'gox install: unknown option %s\n' "$1" >&2; exit 2 ;; esac shift done # ---- output ------------------------------------------------------------------------------------------------------- if [ -t 1 ] && [ -z "${NO_COLOR:-}" ] && command -v tput >/dev/null 2>&1 && [ "$(tput colors 2>/dev/null || echo 0)" -ge 8 ]; then B=$(tput bold); G=$(tput setaf 2); Y=$(tput setaf 3); R=$(tput setaf 1); C=$(tput setaf 6); N=$(tput sgr0) else B=; G=; Y=; R=; C=; N=; fi step() { printf '\n%s==>%s %s%s%s\n' "$C$B" "$N" "$B" "$*" "$N"; } ok() { printf ' %s✓%s %s\n' "$G" "$N" "$*"; } warn() { printf ' %s!%s %s\n' "$Y" "$N" "$*"; } fail() { printf ' %s✗%s %s\n' "$R" "$N" "$*" >&2; } die() { fail "$*"; exit 1; } have() { command -v "$1" >/dev/null 2>&1; } TTY=/dev/tty; [ -r "$TTY" ] && [ -w "$TTY" ] || TTY= ask() { # ask "question" -> 0 yes / 1 no [ "$YES" = 1 ] && return 0 [ -n "$TTY" ] || { warn "no terminal to ask: $1 (re-run with --yes)"; return 1; } printf ' %s?%s %s [Y/n] ' "$Y" "$N" "$1" > "$TTY"; read -r a < "$TTY" || a=n case "$a" in ""|y|Y|yes) return 0 ;; *) return 1 ;; esac } run() { # run a command, or show it in --dry-run if [ "$DRY" = 1 ]; then printf ' %s·%s would run: %s\n' "$C" "$N" "$*"; return 0; fi "$@" } SUDO=; if [ "$(id -u)" != 0 ]; then SUDO=sudo; fi # ---- 1. environment ----------------------------------------------------------------------------------------------- step "Environment" case "$(uname -s)" in Darwin) ENV=mac ;; Linux) if grep -qi microsoft /proc/version 2>/dev/null; then ENV=wsl; else ENV=linux; fi ;; *) die "unsupported system $(uname -s): gox runs on macOS, Linux and WSL" ;; esac PM= if have brew; then PM=brew; elif have apt-get; then PM=apt; elif have dnf; then PM=dnf; fi ok "system: $ENV ($(uname -m))" [ -n "$PM" ] && ok "packages: $PM" || warn "no supported package manager (brew, apt, dnf): missing tools must be installed by hand" [ "$DRY" = 1 ] && warn "dry run: nothing will change" pkg_install() { # pkg_install : install one package with the detected manager case "$PM" in brew) run brew install "$1" ;; apt) run $SUDO apt-get update -qq && run $SUDO apt-get install -y -qq "$1" ;; dnf) run $SUDO dnf install -y -q "$1" ;; *) die "install $1 by hand, then run this again" ;; esac } gh_repo_setup() { # official GitHub CLI package repositories (apt / dnf) case "$PM" in apt) run $SUDO install -d -m 0755 /etc/apt/keyrings if [ "$DRY" = 1 ]; then printf ' %s·%s would add the GitHub CLI apt repository\n' "$C" "$N"; return 0; fi curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | $SUDO tee /etc/apt/keyrings/githubcli-archive-keyring.gpg >/dev/null $SUDO chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \ | $SUDO tee /etc/apt/sources.list.d/github-cli.list >/dev/null ;; dnf) run $SUDO dnf install -y -q 'dnf-command(config-manager)' && run $SUDO dnf config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo ;; esac } # ---- 2. tools ----------------------------------------------------------------------------------------------------- step "Tools" need() { # need if have "$1"; then ok "$3: $($1 --version 2>/dev/null | head -1)"; return 0; fi warn "$3 missing" ask "install $3 ($2)?" || die "$3 is required" [ "$1" = gh ] && [ "$PM" != brew ] && gh_repo_setup pkg_install "$2" [ "$DRY" = 1 ] || have "$1" || die "$3 still missing after install" [ "$DRY" = 1 ] || ok "$3 installed" } have curl || need curl curl "curl" need git git "git" need gh gh "GitHub CLI" if have uv; then ok "uv: $(uv --version)"; else warn "uv missing" ask "install uv (Astral's official installer, no Python needed)?" || die "uv is required" if [ "$PM" = brew ]; then run brew install uv elif [ "$DRY" = 1 ]; then printf ' %s·%s would run the uv installer\n' "$C" "$N" else curl -LsSf https://astral.sh/uv/install.sh | sh; fi PATH="$HOME/.local/bin:$PATH"; export PATH [ "$DRY" = 1 ] || have uv || die "uv still missing after install" fi # ---- 3. GitHub access --------------------------------------------------------------------------------------------- step "GitHub access" if [ "$AUTH" = 0 ]; then warn "skipped (--no-auth)" elif [ "$DRY" = 0 ] && gh auth status >/dev/null 2>&1; then ok "gh logged in as $(gh api user --jq .login 2>/dev/null || echo '?')" else if [ "$DRY" = 1 ]; then printf ' %s·%s would check gh auth and run gh auth login if needed\n' "$C" "$N" else [ -n "$TTY" ] || die "GitHub login needs a terminal: run 'gh auth login', then this again" warn "not logged in to GitHub: starting gh auth login (browser)" gh auth login --hostname github.com --git-protocol https --web < "$TTY" > "$TTY" 2>&1 || die "GitHub login failed" ok "gh logged in" fi fi if [ "$AUTH" = 1 ]; then if git config --global --get-all credential.https://github.com.helper 2>/dev/null | grep -q 'gh auth git-credential'; then ok "git already uses gh for github.com" # never rewrite an existing global git config elif ask "let git use gh for github.com (adds a credential helper to ~/.gitconfig)?"; then run gh auth setup-git --hostname github.com && ok "git uses gh for github.com" else warn "git has no gh credential helper: private repos will ask for a password"; fi fi # ---- 4. gox ------------------------------------------------------------------------------------------------------- step "gox" SRC="git+https://github.com/$REPO@$REF" verb=installed; force=; if have gox; then verb=upgraded; force=--force; fi err=$(mktemp) if run uv tool install --quiet $force "$SRC" 2>"$err"; then rm -f "$err"; [ "$DRY" = 1 ] || ok "gox $verb ($REF)" elif grep -q 'pyproject.toml' "$err"; then rm -f "$err" die "gox is not on '$REF' yet - try: curl --proto '=https' --tlsv1.2 -fsSL https://gox.vzs.one | sh -s -- --ref feat/worker-nodes" elif grep -qi 'could not read Username\|Authentication failed\|Repository not found' "$err"; then rm -f "$err" die "no access to github.com/$REPO (private): log in with gh (run again without --no-auth) or ask for access" else sed 's/^/ /' "$err" >&2; rm -f "$err"; die "could not install gox from $REPO ($REF)"; fi case ":$PATH:" in *":$HOME/.local/bin:"*) ;; *) PATH="$HOME/.local/bin:$PATH"; export PATH; run uv tool update-shell >/dev/null 2>&1 || true warn "added ~/.local/bin to your shell PATH (open a new terminal)";; esac [ "$DRY" = 1 ] || GOX_REF="$REF" gox self where >/dev/null || die "gox installed but not runnable" [ "$DRY" = 1 ] || GOX_REF="$REF" gox self update >/dev/null && ok "gox source ready: $(gox self where 2>/dev/null || echo '(dry run)')" # ---- 5. skills per provider --------------------------------------------------------------------------------------- step "Agent skills" found=0 for p in claude codex cursor; do bin=$p; [ "$p" = cursor ] && bin=cursor-agent if have "$bin"; then found=1 if [ "$SETUP" = 0 ]; then warn "$p found; setup skipped (--no-setup)"; continue; fi if [ "$DRY" = 1 ]; then printf ' %s·%s would run: setup --provider %s --profile %s\n' "$C" "$N" "$p" "$ENV"; continue; fi if "$(gox self where)/setup/setup" --provider "$p" --profile "$ENV" >/dev/null 2>&1; then ok "$p: skills installed" else warn "$p: setup reported a problem (run: \"\$(gox self where)/setup/setup\" --provider $p)"; fi fi done [ "$found" = 1 ] || warn "no provider CLI found (claude, codex, cursor-agent): install one, then run this again" # ---- 6. checklist ------------------------------------------------------------------------------------------------- step "Ready" for t in git gh uv gox; do if have "$t"; then ok "$t"; else fail "$t"; fi; done printf '\n Next: %sgox help%s · operators: %sgox node up%s · repair any time: run this again\n\n' "$B" "$N" "$B" "$N"