#!/bin/sh # agoches network installer (macOS/Linux). Usage: # curl -fsSL https://agoches.app/install.sh | sh set -eu BASE_URL="${AGOCHES_INSTALL_BASE:-https://agoches.app}" BIN_DIR="${AGOCHES_BIN_DIR:-/usr/local/bin}" # Map OS + arch to a Rust target triple. Args are injectable for testing; # default to the live uname values. detect_target() { os="${1:-$(uname -s)}" arch="${2:-$(uname -m)}" case "$os" in Linux) os_part="unknown-linux-gnu" ;; Darwin) os_part="apple-darwin" ;; *) echo "agoches: unsupported OS '$os'" >&2; return 1 ;; esac case "$arch" in x86_64|amd64) arch_part="x86_64" ;; aarch64|arm64) arch_part="aarch64" ;; *) echo "agoches: unsupported architecture '$arch'" >&2; return 1 ;; esac printf '%s-%s\n' "$arch_part" "$os_part" } # Print the lowercase hex sha256 of a file using whatever tool is present. sha256_of() { if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | awk '{print $1}' elif command -v shasum >/dev/null 2>&1; then shasum -a 256 "$1" | awk '{print $1}' else echo "agoches: no sha256 tool (need sha256sum or shasum)" >&2 return 1 fi } # Look up the expected hash for an artifact filename in a SHA256SUMS manifest. # Manifest lines are " " (sha256sum format); tolerate the # binary-mode "*filename" marker some tools emit, matching install.ps1. expected_sum() { awk -v f="$2" '{ sub(/^\*/, "", $2) } $2 == f { print $1; exit }' "$1" } # Verify a file's sha256 against an expected hex string. Non-zero on mismatch. verify_checksum() { actual="$(sha256_of "$1")" || return 1 if [ "$actual" != "$2" ]; then echo "agoches: checksum mismatch for $1" >&2 echo " expected $2" >&2 echo " actual $actual" >&2 return 1 fi } # Download $1 (URL path under BASE_URL) to $2 (local file). Fail on any HTTP error. fetch() { curl -fsSL "$BASE_URL/$1" -o "$2" } # Read the current published version (plain text) from the host. resolve_version() { curl -fsSL "$BASE_URL/version" | tr -d '[:space:]' } # Copy the downloaded binary into BIN_DIR, escalating with sudo only if needed. install_binary() { # $1 = path to verified binary if [ -w "$BIN_DIR" ] || mkdir -p "$BIN_DIR" 2>/dev/null && [ -w "$BIN_DIR" ]; then install -m 0755 "$1" "$BIN_DIR/agoches" else echo "agoches: $BIN_DIR is not writable; using sudo to install" >&2 sudo install -m 0755 "$1" "$BIN_DIR/agoches" fi } # Confirm the installed binary runs. self_check() { "$BIN_DIR/agoches" --version } # Register a USER-scoped service that runs `agoches run`. Binary is system-wide # (BIN_DIR) but the service runs as the invoking user, which is required because # the daemon needs the user's Claude Code login (~/.claude) and ~/.config/agoches. # Templates live next to this script when run locally; when piped via curl they # are written inline below. register_service() { os="$(uname -s)" case "$os" in Linux) register_service_systemd ;; Darwin) register_service_launchd ;; *) echo "agoches: no service integration for '$os'; start manually with 'agoches run'" >&2 ;; esac } register_service_systemd() { command -v systemctl >/dev/null 2>&1 || { echo "agoches: systemctl not found; start manually with 'agoches run'" >&2; return 0; } unit_dir="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user" mkdir -p "$unit_dir" cat > "$unit_dir/agoches.service" </dev/null; then echo "Service: systemd user unit 'agoches' enabled and started." echo " logs: journalctl --user -u agoches -f" else echo "agoches: could not start the user service (no user systemd session?)." >&2 echo " Start manually: agoches run" >&2 fi } register_service_launchd() { plist="$HOME/Library/LaunchAgents/dev.agoches.daemon.plist" log="$HOME/Library/Logs/agoches.log" mkdir -p "$HOME/Library/LaunchAgents" "$HOME/Library/Logs" cat > "$plist" < Labeldev.agoches.daemon ProgramArguments $BIN_DIR/agochesrun RunAtLoad KeepAlive StandardOutPath$log StandardErrorPath$log EOF launchctl bootout "gui/$(id -u)/dev.agoches.daemon" 2>/dev/null || true if launchctl bootstrap "gui/$(id -u)" "$plist" 2>/dev/null; then echo "Service: launchd agent 'dev.agoches.daemon' loaded." echo " logs: tail -f $log" else echo "agoches: could not load the launchd agent; start manually with 'agoches run'." >&2 fi } main() { target="$(detect_target)" version="$(resolve_version)" [ -n "$version" ] || { echo "agoches: could not resolve version from $BASE_URL/version" >&2; exit 1; } echo "Installing agoches $version ($target) ..." tmp="$(mktemp -d)" trap 'rm -rf "$tmp"' EXIT artifact="agoches-$target" fetch "dl/$version/$artifact" "$tmp/$artifact" fetch "dl/$version/SHA256SUMS" "$tmp/SHA256SUMS" want="$(expected_sum "$tmp/SHA256SUMS" "$artifact")" [ -n "$want" ] || { echo "agoches: $artifact not found in SHA256SUMS" >&2; exit 1; } verify_checksum "$tmp/$artifact" "$want" chmod +x "$tmp/$artifact" install_binary "$tmp/$artifact" if [ "${AGOCHES_NO_SERVICE:-}" != "1" ]; then register_service fi self_check echo "" echo "Installed: $BIN_DIR/agoches" command -v claude >/dev/null 2>&1 || \ echo "Note: the 'claude' CLI was not found on PATH — install Claude Code so agoches can run agents." echo "Next: run 'agoches pair' to connect a device." } if [ "${AGOCHES_LIB_ONLY:-}" != "1" ]; then main "$@"; fi