feat: add curl|sh one-liner install script
- Add scripts/install.sh for easy installation via curl - Auto-detects OS (linux, darwin, windows) and arch (amd64, arm64) - Supports version pinning: sh -s -- v1.2.3 - Installs to /usr/local/bin or ~/.local/bin as fallback - Updates release workflow to include install.sh in release assets - Adds README.md with installation documentation
This commit is contained in:
parent
d080e107d0
commit
21f2bd3a9d
3 changed files with 471 additions and 7 deletions
22
.github/workflows/release.yml
vendored
22
.github/workflows/release.yml
vendored
|
|
@ -109,6 +109,11 @@ jobs:
|
||||||
find . -type f \( -name "*.tar.gz" -o -name "*.zip" \) -exec sha256sum {} \; > ../checksums.txt
|
find . -type f \( -name "*.tar.gz" -o -name "*.zip" \) -exec sha256sum {} \; > ../checksums.txt
|
||||||
cat ../checksums.txt
|
cat ../checksums.txt
|
||||||
|
|
||||||
|
- name: Get install script
|
||||||
|
run: |
|
||||||
|
cp scripts/install.sh install.sh
|
||||||
|
chmod +x install.sh
|
||||||
|
|
||||||
- name: Create Release
|
- name: Create Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
|
|
@ -127,16 +132,18 @@ jobs:
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
**Linux/macOS:**
|
**One-liner (Linux/macOS):**
|
||||||
```bash
|
```bash
|
||||||
# Download and extract
|
curl -fsSL https://raw.githubusercontent.com/openboatmobile/obm/main/scripts/install.sh | sh
|
||||||
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/
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Windows:**
|
Or install a specific version:
|
||||||
Download the appropriate `.zip` file, extract, and add to PATH.
|
```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
|
### Usage
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -149,6 +156,7 @@ jobs:
|
||||||
artifacts/**/obm-*.tar.gz
|
artifacts/**/obm-*.tar.gz
|
||||||
artifacts/**/obm-*.zip
|
artifacts/**/obm-*.zip
|
||||||
checksums.txt
|
checksums.txt
|
||||||
|
install.sh
|
||||||
draft: false
|
draft: false
|
||||||
prerelease: ${{ contains(steps.version.outputs.VERSION, '-') }}
|
prerelease: ${{ contains(steps.version.outputs.VERSION, '-') }}
|
||||||
generate_release_notes: false
|
generate_release_notes: false
|
||||||
167
README.md
Normal file
167
README.md
Normal file
|
|
@ -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
|
||||||
289
scripts/install.sh
Executable file
289
scripts/install.sh
Executable file
|
|
@ -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 "$@"
|
||||||
Loading…
Add table
Add a link
Reference in a new issue