diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8da4835..8e5af98 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -109,6 +109,11 @@ jobs: find . -type f \( -name "*.tar.gz" -o -name "*.zip" \) -exec sha256sum {} \; > ../checksums.txt cat ../checksums.txt + - name: Get install script + run: | + cp scripts/install.sh install.sh + chmod +x install.sh + - name: Create Release uses: softprops/action-gh-release@v1 with: @@ -127,16 +132,18 @@ jobs: ### Installation - **Linux/macOS:** + **One-liner (Linux/macOS):** ```bash - # Download and extract - curl -sL https://github.com/openboatmobile/obm/releases/download/${{ steps.version.outputs.VERSION }}/obm-$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m | sed 's/x86_64/amd64/').tar.gz | tar xz - chmod +x obm - sudo mv obm /usr/local/bin/ + curl -fsSL https://raw.githubusercontent.com/openboatmobile/obm/main/scripts/install.sh | sh ``` - **Windows:** - Download the appropriate `.zip` file, extract, and add to PATH. + Or install a specific version: + ```bash + curl -fsSL https://raw.githubusercontent.com/openboatmobile/obm/main/scripts/install.sh | sh -s -- v1.2.3 + ``` + + **Manual download:** + Download the archive for your platform, extract, and place `obm` in your PATH. ### Usage ```bash @@ -149,6 +156,7 @@ jobs: artifacts/**/obm-*.tar.gz artifacts/**/obm-*.zip checksums.txt + install.sh draft: false prerelease: ${{ contains(steps.version.outputs.VERSION, '-') }} generate_release_notes: false \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..fd77885 --- /dev/null +++ b/README.md @@ -0,0 +1,167 @@ +# obm - OpenBoatMobile Infrastructure CLI + +A CLI tool for deploying AI agents on cloud infrastructure with Terraform. + +## Installation + +### Quick Install (Linux/macOS) + +```bash +curl -fsSL https://raw.githubusercontent.com/openboatmobile/obm/main/scripts/install.sh | sh +``` + +Install a specific version: + +```bash +curl -fsSL https://raw.githubusercontent.com/openboatmobile/obm/main/scripts/install.sh | sh -s -- v1.2.3 +``` + +### Manual Install + +Download the latest release for your platform from [GitHub Releases](https://github.com/openboatmobile/obm/releases/latest): + +| Platform | Architecture | Download | +|----------|-------------|----------| +| Linux | x86_64 (amd64) | `obm-linux-amd64.tar.gz` | +| Linux | ARM64 | `obm-linux-arm64.tar.gz` | +| macOS | Apple Silicon (arm64) | `obm-darwin-arm64.tar.gz` | +| Windows | x86_64 (amd64) | `obm-windows-amd64.zip` | +| Windows | ARM64 | `obm-windows-arm64.zip` | + +**Linux/macOS:** +```bash +# Download and extract +curl -sL https://github.com/openboatmobile/obm/releases/latest/download/obm-linux-amd64.tar.gz | tar xz +# Or for ARM64: +# curl -sL https://github.com/openboatmobile/obm/releases/latest/download/obm-linux-arm64.tar.gz | tar xz + +chmod +x obm +sudo mv obm /usr/local/bin/ +``` + +**Windows (PowerShell):** +```powershell +# Download and extract +Invoke-WebRequest -Uri https://github.com/openboatmobile/obm/releases/latest/download/obm-windows-amd64.zip -OutFile obm.zip +Expand-Archive obm.zip +# Add to PATH as needed +``` + +### From Source + +```bash +go build -o obm ./cmd/obm +``` + +## Usage + +### Interactive Mode (Default) + +Run the interactive wizard to configure your deployment: + +```bash +./obm deploy +``` + +The wizard will guide you through: +1. Agent framework selection (Hermes or OpenClaw) +2. Cloud provider (Hetzner or DigitalOcean) +3. Server configuration +4. Inference provider (Venice, OpenRouter, OpenAI, Anthropic, or Custom) +5. Optional: Tailscale VPN, Discord integration + +### Non-Interactive Mode (CI/CD) + +For automated deployments, use a YAML configuration file: + +```bash +./obm deploy --config deploy.yaml +``` + +See `deploy.yaml.example` for a complete configuration reference. + +## Commands + +| Command | Description | +|---------|-------------| +| `deploy` | Deploy an AI agent (interactive or --config for CI/CD) | +| `validate` | Check configuration and API credentials | +| `status` | Show current infrastructure state | +| `destroy` | Tear down provisioned infrastructure | +| `version` | Print version | +| `help` | Show help message | + +## Configuration File Format + +The YAML configuration file supports the following structure: + +```yaml +# Required: Agent framework +framework: hermes # or openclaw + +# Required: Cloud provider +provider: + name: hetzner # or digitalocean + token: "your-api-token" + ssh: + names: ["my-ssh-key"] # Hetzner + # fingerprints: ["aa:bb:cc:dd"] # DigitalOcean + +# Server configuration +server: + name: "my-agent" + location: "ash" # Hetzner: ash, fsn1, nbg1, hel1 + type: "cpx21" + +# Required: Inference provider +inference: + provider: venice # venice, openrouter, openai, anthropic, custom + api_key: "your-api-key" + primary_model: "zai-org-glm-5" + +# Optional: Tailscale VPN +tailscale: + enabled: true + auth_key: "tskey-auth-..." + tailnet: "mytailnet" + +# Optional: Discord integration +discord: + enabled: true + bot_token: "" + server_id: "" +``` + +## Example Workflows + +### Local Development + +```bash +# Interactive setup +./obm deploy + +# Validate your .env file +./obm validate --env-file .env +``` + +### CI/CD Pipeline + +```bash +# Create deploy.yaml from your secrets manager +# Then run non-interactive deployment +./obm deploy --config deploy.yaml +``` + +### GitOps Setup + +1. Store `deploy.yaml` in your repository (use template with placeholders) +2. Use a secrets manager for sensitive values +3. In CI: + ```bash + envsubst < deploy.yaml.template > deploy.yaml + ./obm deploy --config deploy.yaml + ``` + +## License + +MIT \ No newline at end of file diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100755 index 0000000..b46c0c6 --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,289 @@ +#!/bin/sh +# obm install script - curl | sh one-liner installer +# Usage: curl -fsSL https://raw.githubusercontent.com/openboatmobile/obm/main/scripts/install.sh | sh +# Or: curl -fsSL https://raw.githubusercontent.com/openboatmobile/obm/main/scripts/install.sh | sh -s -- v1.2.3 + +set -e + +# Configuration +GITHUB_REPO="openboatmobile/obm" +BINARY_NAME="obm" +INSTALL_DIR_DEFAULT="/usr/local/bin" + +# Colors for output (only if terminal) +if [ -t 1 ]; then + RED='\033[0;31m' + GREEN='\033[0;32m' + YELLOW='\033[0;33m' + BLUE='\033[0;34m' + NC='\033[0m' # No Color +else + RED='' + GREEN='' + YELLOW='' + BLUE='' + NC='' +fi + +info() { + printf "${BLUE}==>${NC} %s\n" "$1" +} + +success() { + printf "${GREEN}✓${NC} %s\n" "$1" +} + +warn() { + printf "${YELLOW}!${NC} %s\n" "$1" >&2 +} + +error() { + printf "${RED}✗${NC} %s\n" "$1" >&2 + exit 1 +} + +# Detect operating system +detect_os() { + case "$(uname -s)" in + Linux*) echo "linux";; + Darwin*) echo "darwin";; + CYGWIN*) echo "windows";; + MINGW*) echo "windows";; + MSYS*) echo "windows";; + *) error "Unsupported OS: $(uname -s)";; + esac +} + +# Detect architecture +detect_arch() { + arch="$(uname -m)" + case "$arch" in + x86_64|amd64) echo "amd64";; + aarch64|arm64) echo "arm64";; + armv7l|armv7) echo "arm64";; # Map armv7 to arm64 (may not work) + armv6) echo "arm64";; # Map armv6 to arm64 (may not work) + i386|i686) echo "amd64";; # Map 32-bit to amd64 (may not work) + *) error "Unsupported architecture: $arch";; + esac +} + +# Get latest release version from GitHub API +get_latest_version() { + api_url="https://api.github.com/repos/${GITHUB_REPO}/releases/latest" + + # Try curl first, fall back to wget + if command -v curl >/dev/null 2>&1; then + version=$(curl -fsSL "$api_url" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') + elif command -v wget >/dev/null 2>&1; then + version=$(wget -qO- "$api_url" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') + else + error "Neither curl nor wget found. Please install one of them." + fi + + if [ -z "$version" ]; then + error "Could not determine latest version" + fi + + echo "$version" +} + +# Download the binary +download_binary() { + version="$1" + os="$2" + arch="$3" + dest="$4" + + # Build download URL + platform="${os}-${arch}" + + if [ "$os" = "windows" ]; then + archive_name="obm-${platform}.zip" + binary_name="obm.exe" + else + archive_name="obm-${platform}.tar.gz" + binary_name="obm" + fi + + download_url="https://github.com/${GITHUB_REPO}/releases/download/${version}/${archive_name}" + + info "Downloading $archive_name..." + + # Create temp directory + tmp_dir=$(mktemp -d) + trap 'rm -rf "$tmp_dir"' EXIT + + archive_path="${tmp_dir}/${archive_name}" + + # Download + if command -v curl >/dev/null 2>&1; then + if ! curl -fsSL "$download_url" -o "$archive_path"; then + error "Failed to download from $download_url" + fi + elif command -v wget >/dev/null 2>&1; then + if ! wget -q "$download_url" -O "$archive_path"; then + error "Failed to download from $download_url" + fi + fi + + # Verify download + if [ ! -f "$archive_path" ] || [ ! -s "$archive_path" ]; then + error "Download failed or file is empty" + fi + + info "Extracting..." + + # Extract + if [ "$os" = "windows" ]; then + if command -v unzip >/dev/null 2>&1; then + if ! unzip -q "$archive_path" -d "$tmp_dir"; then + error "Failed to extract zip archive" + fi + else + error "unzip not found. Please install unzip." + fi + else + if ! tar -xzf "$archive_path" -C "$tmp_dir"; then + error "Failed to extract tar.gz archive" + fi + fi + + # Find the binary (it may be in a subdirectory or at root) + extracted_binary=$(find "$tmp_dir" -name "$binary_name" -type f | head -n1) + if [ -z "$extracted_binary" ]; then + error "Binary not found in archive" + fi + + # Move to destination + mv "$extracted_binary" "$dest" + chmod +x "$dest" +} + +# Check if we have write permission to install directory +check_install_dir() { + dir="$1" + if [ -d "$dir" ]; then + # Directory exists, check write permission + if [ ! -w "$dir" ]; then + return 1 + fi + else + # Directory doesn't exist, check parent + parent=$(dirname "$dir") + if [ ! -w "$parent" ]; then + return 1 + fi + fi + return 0 +} + +# Main installation logic +main() { + version="" + + # Parse arguments + while [ $# -gt 0 ]; do + case "$1" in + v*) + version="$1" + shift + ;; + *) + warn "Unknown argument: $1" + shift + ;; + esac + done + + # Detect platform + os=$(detect_os) + arch=$(detect_arch) + + info "Detected platform: ${os}-${arch}" + + # Get version if not specified + if [ -z "$version" ]; then + info "Fetching latest version..." + version=$(get_latest_version) + fi + + info "Installing ${BINARY_NAME} ${version}" + + # Determine install directory + install_dir="" + binary_path="" + + # Try default locations in order + for dir in "$INSTALL_DIR_DEFAULT" "/usr/bin" "$HOME/.local/bin" "$HOME/bin"; do + if check_install_dir "$dir"; then + install_dir="$dir" + break + fi + done + + # If no writable system directory, use home directory + if [ -z "$install_dir" ]; then + info "No writable system directory found" + + # Create ~/.local/bin if it doesn't exist + install_dir="$HOME/.local/bin" + mkdir -p "$install_dir" + success "Created $install_dir" + + # Check if ~/.local/bin is in PATH + case ":$PATH:" in + *":$install_dir:"*) + ;; + *) + warn "~/.local/bin is not in your PATH" + warn "Add 'export PATH=\"\$HOME/.local/bin:\$PATH\"' to your shell config" + ;; + esac + fi + + binary_path="${install_dir}/${BINARY_NAME}" + + # Check for existing installation + if [ -f "$binary_path" ]; then + warn "Removing existing installation at $binary_path" + rm -f "$binary_path" + fi + + # Download and install + download_binary "$version" "$os" "$arch" "$binary_path" + + # Verify installation + if [ ! -f "$binary_path" ]; then + error "Installation failed - binary not found at $binary_path" + fi + + success "Installed ${BINARY_NAME} to ${binary_path}" + + # Show version + installed_version=$("$binary_path" version 2>/dev/null || echo "unknown") + if [ "$installed_version" != "unknown" ]; then + success "Version: $installed_version" + fi + + # Final message + echo "" + success "Installation complete!" + info "Run '${BINARY_NAME} --help' to get started" + + # Reminder about PATH if needed + case ":$PATH:" in + *":$install_dir:"*) + ;; + *) + echo "" + warn "To use ${BINARY_NAME}, add ${install_dir} to your PATH:" + echo " export PATH=\"${install_dir}:\$PATH\"" + echo "" + warn "Or add to your shell config (~/.bashrc, ~/.zshrc):" + echo " echo 'export PATH=\"${install_dir}:\$PATH\"' >> ~/.bashrc" + ;; + esac +} + +# Run main +main "$@" \ No newline at end of file