#!/usr/bin/env bash # ───────────────────────────────────────────────────────── # machine — One-command installer # Usage: curl -sSL install.getmachine.io | bash # ───────────────────────────────────────────────────────── set -euo pipefail VERSION="0.1.0" IMAGE="getmachinenow/machine:latest" INSTALL_DIR="$HOME/.machine" DATA_DIR="$INSTALL_DIR/data" LOGS_DIR="$INSTALL_DIR/logs" ENV_FILE="$INSTALL_DIR/.env" # ── Colors ─────────────────────────────────────────────── GREEN='\033[0;32m' RED='\033[0;31m' YELLOW='\033[1;33m' CYAN='\033[0;36m' BOLD='\033[1m' NC='\033[0m' banner() { echo "" echo -e "${GREEN}${BOLD}" echo " ┌─────────────────────────────────┐" echo " │ m a c h i n e │" echo " │ automated trading platform │" echo " │ v${VERSION} │" echo " └─────────────────────────────────┘" echo -e "${NC}" } log() { echo -e " ${GREEN}✓${NC} $1"; } warn() { echo -e " ${YELLOW}!${NC} $1"; } err() { echo -e " ${RED}✗${NC} $1" >&2; } ask() { echo -en " ${CYAN}?${NC} $1"; } # ── Preflight checks ──────────────────────────────────── check_requirements() { echo -e "\n${BOLD}Checking requirements...${NC}\n" # OS check if [[ "$(uname)" != "Linux" ]]; then err "machine requires Linux (Ubuntu 22.04 recommended)" exit 1 fi log "Linux detected" # Docker if ! command -v docker &>/dev/null; then warn "Docker not found — installing..." curl -fsSL https://get.docker.com | sh sudo usermod -aG docker "$USER" log "Docker installed" else log "Docker found: $(docker --version | head -1)" fi # Docker Compose if ! docker compose version &>/dev/null 2>&1; then if ! command -v docker-compose &>/dev/null; then warn "Docker Compose not found — installing plugin..." sudo apt-get update -qq && sudo apt-get install -y -qq docker-compose-plugin fi fi log "Docker Compose available" # curl + jq for cmd in curl jq; do if ! command -v "$cmd" &>/dev/null; then sudo apt-get install -y -qq "$cmd" fi done log "Dependencies satisfied" } # ── Collect configuration ──────────────────────────────── collect_config() { echo -e "\n${BOLD}Configuration${NC}\n" # License key ask "License key: " read -r LICENSE_KEY if [[ ! "$LICENSE_KEY" =~ ^MCH- ]]; then err "Invalid license key format. Expected: MCH-XXXX-XXXX-XXXX-XXXX" exit 1 fi # Validate license echo -e " Validating license..." RESPONSE=$(curl -sf -m 15 \ -H "Content-Type: application/json" \ -d "{\"key\": \"${LICENSE_KEY}\"}" \ "https://license.getmachine.io/api/validate" 2>/dev/null) || { err "Could not reach license server. Check your internet connection." exit 1 } VALID=$(echo "$RESPONSE" | jq -r '.valid') if [[ "$VALID" != "true" ]]; then err "Invalid license key. Subscribe at https://getmachine.io" exit 1 fi log "License validated" # Coinbase echo "" ask "Coinbase Advanced Trade API key: " read -r COINBASE_API_KEY ask "Coinbase Advanced Trade API secret: " read -rs COINBASE_API_SECRET echo "" log "Coinbase credentials captured" # Kraken (optional) echo "" ask "Kraken API key (press Enter to skip): " read -r KRAKEN_API_KEY KRAKEN_API_SECRET="" if [[ -n "$KRAKEN_API_KEY" ]]; then ask "Kraken API secret: " read -rs KRAKEN_API_SECRET echo "" log "Kraken credentials captured" else warn "Skipping Kraken — cross-exchange strategy will be disabled" fi # Capital echo "" ask "Total capital to deploy (USD) [10000]: " read -r TOTAL_CAPITAL TOTAL_CAPITAL="${TOTAL_CAPITAL:-10000}" log "Capital: \$${TOTAL_CAPITAL}" } # ── Write environment file ─────────────────────────────── write_env() { mkdir -p "$INSTALL_DIR" "$DATA_DIR" "$LOGS_DIR" # Generate a deterministic CONFIG_PASSWORD from the license key CONFIG_PW_HASH=$(echo -n "$LICENSE_KEY" | md5sum | cut -c1-12) cat > "$ENV_FILE" < "$INSTALL_DIR/docker-compose.yml" < "$INSTALL_DIR/update.sh" <<'SCRIPT' #!/usr/bin/env bash set -euo pipefail cd "$HOME/.machine" echo "[machine] Pulling latest image..." docker pull getmachinenow/machine:latest echo "[machine] Restarting container..." docker compose down docker compose up -d echo "[machine] Updated successfully." docker compose logs --tail=20 SCRIPT chmod +x "$INSTALL_DIR/update.sh" log "Update script written to ${INSTALL_DIR}/update.sh" } # ── Pull and start ─────────────────────────────────────── start_machine() { echo -e "\n${BOLD}Starting machine...${NC}\n" cd "$INSTALL_DIR" echo " Pulling image (this may take a few minutes)..." docker pull "$IMAGE" log "Image pulled" docker compose up -d log "Container started" # Wait for health check echo -e " Waiting for services to initialize..." for i in $(seq 1 30); do if curl -sf http://localhost:8080/api/health &>/dev/null; then break fi sleep 2 done if curl -sf http://localhost:8080/api/health &>/dev/null; then log "All services running" else warn "Services still starting — check: docker logs machine" fi } # ── Summary ────────────────────────────────────────────── show_summary() { LOCAL_IP=$(hostname -I | awk '{print $1}') echo "" echo -e "${GREEN}${BOLD} ── machine is running ──${NC}" echo "" echo -e " Dashboard: ${CYAN}http://${LOCAL_IP}:8080${NC}" echo -e " Logs: docker logs machine -f" echo -e " Status: docker exec machine supervisorctl status" echo -e " Update: bash ~/.machine/update.sh" echo -e " Stop: cd ~/.machine && docker compose down" echo "" echo -e " ${YELLOW}Config:${NC} ${ENV_FILE}" echo -e " ${YELLOW}Data:${NC} ${DATA_DIR}" echo -e " ${YELLOW}Logs:${NC} ${LOGS_DIR}" echo "" echo -e " ${YELLOW}━━━━ Terms ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo "" echo -e " • All sales are final — no refunds" echo -e " • Security updates are always free" echo -e " • Feature updates require an active subscription" echo -e " • Cancel anytime — billing stops at period end" echo -e " • Not investment advice. Trading involves risk of loss." echo "" echo -e " Terms: ${CYAN}https://getmachine.io/terms${NC}" echo -e " Privacy: ${CYAN}https://getmachine.io/privacy${NC}" echo -e " ${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo "" } # ── Phone home (notify license server of successful install) ─ phone_home() { LOCAL_IP=$(hostname -I | awk '{print $1}' 2>/dev/null || echo "unknown") # Non-blocking — don't fail install if this doesn't work curl -sf -m 10 \ -H "Content-Type: application/json" \ -d "{\"licenseKey\": \"${LICENSE_KEY}\", \"ip\": \"${LOCAL_IP}\", \"version\": \"${VERSION}\"}" \ "https://license.getmachine.io/api/setup/status" &>/dev/null || true } # ── Auto mode (pre-configured by setup wizard) ───────── # When called with --auto, the .env file has already been written # by the setup wizard command. Skip all interactive prompts. auto_install() { banner check_requirements if [[ ! -f "$ENV_FILE" ]]; then err ".env file not found at $ENV_FILE" err "Run the setup wizard at https://setup.getmachine.io first" exit 1 fi log "Configuration found (written by setup wizard)" # Source the env to get LICENSE_KEY for phone_home # shellcheck disable=SC1090 source "$ENV_FILE" 2>/dev/null || true write_compose write_update_script start_machine phone_home show_summary } # ── Main ───────────────────────────────────────────────── main() { # Check for --auto flag (used by setup wizard) if [[ "${1:-}" == "--auto" ]]; then auto_install return fi banner check_requirements collect_config write_env write_compose write_update_script start_machine phone_home show_summary } main "$@"