#!/bin/bash set -e # SafetyWing CLI Installer # Usage: curl -fsSL https://cli-inst.safetywing.dev | bash # # This script is safe to publish publicly — it contains no secrets. # It installs the sw CLI and walks the user through prerequisite setup. # # Prerequisites (installed interactively if missing): # 1. gcloud CLI — for GCP authentication # 2. gh CLI — for GitHub access (private repo) # # After installing sw, it runs `sw setup` to bootstrap the config from Vault. # # Environment variable overrides (for testing): # SW_VERSION - Skip version detection, use this version # SW_BASE_URL - Override binary download base URL # SW_NON_INTERACTIVE - Set to "1" to skip interactive prompts REPO="${SW_REPO:-safetywing/cli}" INSTALL_DIR="${SW_INSTALL_DIR:-$HOME/.sw/bin}" BINARY_NAME="sw" GITHUB_ORG="safetywing" # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BOLD='\033[1m' NC='\033[0m' info() { echo -e "${GREEN}✓${NC} $1"; } warn() { echo -e "${YELLOW}!${NC} $1"; } error() { echo -e "${RED}✗${NC} $1"; exit 1; } step() { echo -e "\n${BOLD}$1${NC}"; } # --- Platform detection --- detect_platform() { OS=$(uname -s | tr '[:upper:]' '[:lower:]') ARCH=$(uname -m) case "$ARCH" in x86_64) ARCH="amd64" ;; aarch64|arm64) ARCH="arm64" ;; *) error "Unsupported architecture: $ARCH" ;; esac case "$OS" in darwin|linux) ;; *) error "Unsupported operating system: $OS" ;; esac PLATFORM="${OS}-${ARCH}" } # --- Prerequisite: gcloud --- ensure_gcloud() { step "Checking Google Cloud SDK..." if command -v gcloud &> /dev/null; then info "gcloud is installed" else if [ "${SW_NON_INTERACTIVE:-0}" = "1" ]; then error "gcloud is not installed (required)" fi warn "gcloud is not installed" echo "" echo " The SafetyWing CLI requires Google Cloud SDK." echo "" case "$OS" in darwin) echo " Install with Homebrew:" echo " brew install --cask google-cloud-sdk" ;; linux) echo " Install from:" echo " https://cloud.google.com/sdk/docs/install" ;; esac echo "" echo " After installing, re-run this script." exit 1 fi # Check gcloud auth (not ADC — just basic login) if gcloud auth list --filter="status:ACTIVE" --format="value(account)" 2>/dev/null | grep -q "@"; then GCLOUD_ACCOUNT=$(gcloud auth list --filter="status:ACTIVE" --format="value(account)" 2>/dev/null | head -1) info "gcloud authenticated as $GCLOUD_ACCOUNT" else if [ "${SW_NON_INTERACTIVE:-0}" = "1" ]; then error "gcloud is not authenticated" fi warn "gcloud is not authenticated" echo "" echo " Launching Google Cloud login..." echo "" gcloud auth login echo "" fi # Check ADC if gcloud auth application-default print-access-token &> /dev/null 2>&1; then info "Application Default Credentials configured" else if [ "${SW_NON_INTERACTIVE:-0}" = "1" ]; then warn "ADC not configured (skipping in non-interactive mode)" return fi warn "Application Default Credentials not configured" echo "" echo " Setting up ADC (this opens a browser)..." echo "" gcloud auth application-default login echo "" info "ADC configured" fi } # --- Prerequisite: gh CLI --- ensure_gh() { step "Checking GitHub CLI..." if command -v gh &> /dev/null; then info "gh is installed" else if [ "${SW_NON_INTERACTIVE:-0}" = "1" ]; then error "gh CLI is not installed (required)" fi warn "gh is not installed" echo "" echo " The SafetyWing CLI requires GitHub CLI for access to the private repo." echo "" case "$OS" in darwin) echo " Install with Homebrew:" echo " brew install gh" ;; linux) echo " Install from:" echo " https://cli.github.com" ;; esac echo "" echo " After installing, re-run this script." exit 1 fi # Check gh auth if gh auth status &> /dev/null 2>&1; then GH_USER=$(gh api user --jq '.login' 2>/dev/null || echo "unknown") info "gh authenticated as $GH_USER" else if [ "${SW_NON_INTERACTIVE:-0}" = "1" ]; then error "gh CLI is not authenticated" fi warn "gh is not authenticated" echo "" echo " Launching GitHub login..." echo "" gh auth login echo "" fi # Check org membership step "Verifying GitHub organization access..." if gh api "orgs/${GITHUB_ORG}/members" --jq '.[].login' &> /dev/null 2>&1; then info "Access to ${GITHUB_ORG} organization confirmed" else # Try checking own membership specifically GH_USER=$(gh api user --jq '.login' 2>/dev/null || echo "") if [ -n "$GH_USER" ] && gh api "orgs/${GITHUB_ORG}/members/${GH_USER}" &> /dev/null 2>&1; then info "Access to ${GITHUB_ORG} organization confirmed" else error "You don't have access to the ${GITHUB_ORG} GitHub organization.\n Ask your team lead to add you to https://github.com/${GITHUB_ORG}" fi fi } # --- Version detection --- get_latest_version() { if [ -n "${SW_VERSION:-}" ]; then LATEST_VERSION="$SW_VERSION" return fi # Use gh CLI for private repo access LATEST_VERSION=$(gh api "repos/${REPO}/releases/latest" --jq '.tag_name' 2>/dev/null || echo "") if [ -z "$LATEST_VERSION" ]; then error "Could not determine latest version. Is the ${REPO} repo accessible?" fi } # --- Download and install --- install_binary() { step "Installing sw ${LATEST_VERSION}..." detect_platform if [ -n "${SW_BASE_URL:-}" ]; then DOWNLOAD_URL="${SW_BASE_URL}/${BINARY_NAME}-${PLATFORM}" else DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${LATEST_VERSION}/${BINARY_NAME}-${PLATFORM}" fi mkdir -p "$INSTALL_DIR" # Use gh for authenticated download from private repo if [ -z "${SW_BASE_URL:-}" ]; then gh release download "$LATEST_VERSION" --repo "$REPO" --pattern "${BINARY_NAME}-${PLATFORM}" --dir "$INSTALL_DIR" --clobber 2>/dev/null mv "$INSTALL_DIR/${BINARY_NAME}-${PLATFORM}" "$INSTALL_DIR/$BINARY_NAME" elif command -v curl &> /dev/null; then curl -fsSL "$DOWNLOAD_URL" -o "$INSTALL_DIR/$BINARY_NAME" elif command -v wget &> /dev/null; then wget -q "$DOWNLOAD_URL" -O "$INSTALL_DIR/$BINARY_NAME" else error "Neither curl nor wget found." fi chmod +x "$INSTALL_DIR/$BINARY_NAME" info "Installed sw ${LATEST_VERSION} to $INSTALL_DIR/$BINARY_NAME" } # --- PATH setup --- update_path() { SHELL_NAME=$(basename "$SHELL") case "$SHELL_NAME" in bash) PROFILE="$HOME/.bashrc" ;; zsh) PROFILE="$HOME/.zshrc" ;; fish) PROFILE="$HOME/.config/fish/config.fish" ;; *) PROFILE="" ;; esac PATH_EXPORT='export PATH="$HOME/.sw/bin:$PATH"' if echo "$PATH" | grep -q "$INSTALL_DIR"; then return fi if [ -n "$PROFILE" ] && [ -f "$PROFILE" ]; then if ! grep -q '.sw/bin' "$PROFILE"; then echo "" >> "$PROFILE" echo "# SafetyWing CLI" >> "$PROFILE" echo "$PATH_EXPORT" >> "$PROFILE" info "Added to PATH in $PROFILE" fi fi # Add to current session export PATH="$INSTALL_DIR:$PATH" } # --- Shell completions --- setup_completion() { SHELL_NAME=$(basename "$SHELL") case "$SHELL_NAME" in bash) BASH_COMPLETION_DIR="" BREW_PREFIX="$(brew --prefix 2>/dev/null || true)" if [ -n "$BREW_PREFIX" ] && [ -d "$BREW_PREFIX/etc/bash_completion.d" ]; then BASH_COMPLETION_DIR="$BREW_PREFIX/etc/bash_completion.d" elif [ -d "/etc/bash_completion.d" ] && [ -w "/etc/bash_completion.d" ]; then BASH_COMPLETION_DIR="/etc/bash_completion.d" else mkdir -p "$HOME/.local/share/bash-completion/completions" BASH_COMPLETION_DIR="$HOME/.local/share/bash-completion/completions" fi "$INSTALL_DIR/$BINARY_NAME" completion bash > "$BASH_COMPLETION_DIR/sw" 2>/dev/null && \ info "Shell completions installed" ;; zsh) ZSH_COMPLETIONS="$HOME/.zsh/completions" mkdir -p "$ZSH_COMPLETIONS" "$INSTALL_DIR/$BINARY_NAME" completion zsh > "$ZSH_COMPLETIONS/_sw" if ! grep -q '.zsh/completions' "$HOME/.zshrc" 2>/dev/null; then echo 'fpath=(~/.zsh/completions $fpath)' >> "$HOME/.zshrc" echo 'autoload -Uz compinit && compinit' >> "$HOME/.zshrc" fi info "Shell completions installed" ;; fish) FISH_COMPLETIONS="$HOME/.config/fish/completions" mkdir -p "$FISH_COMPLETIONS" "$INSTALL_DIR/$BINARY_NAME" completion fish > "$FISH_COMPLETIONS/sw.fish" info "Shell completions installed" ;; esac } # --- Bootstrap --- run_bootstrap() { step "Bootstrapping configuration..." echo "" "$INSTALL_DIR/$BINARY_NAME" setup } # --- Main --- main() { echo "" echo -e "${BOLD} SafetyWing CLI Installer${NC}" echo "" ensure_gcloud ensure_gh get_latest_version install_binary update_path setup_completion run_bootstrap echo "" echo -e "${GREEN}${BOLD}All done!${NC}" echo "" echo " Get started:" echo " sw config get-contexts # See available environments" echo " sw config use-context staging" echo " sw setup kubectl # Set up cluster access" echo " sw --help # See all commands" echo "" if ! echo "$PATH" | grep -q "$INSTALL_DIR"; then warn "Start a new shell or run: source ${PROFILE:-~/.bashrc}" fi } main