#!/bin/bash set -e # ───────────────────────────────────────────────────────────────────────────── # TEROS INSTALLER # ───────────────────────────────────────────────────────────────────────────── CYAN='\033[0;36m' BRIGHT_CYAN='\033[1;36m' WHITE='\033[1;37m' GREEN='\033[0;32m' YELLOW='\033[1;33m' RED='\033[0;31m' DIM='\033[2m' RESET='\033[0m' TEROS_DIR="$HOME/.teros" REPO_URL="https://github.com/teros-hq/teros.git" BACKEND_PORT=10001 FRONTEND_PORT=10002 # ───────────────────────────────────────────────────────────────────────────── # LOGO # ───────────────────────────────────────────────────────────────────────────── echo "" echo -e "${CYAN} __ ${WHITE} ${RESET}" echo -e "${CYAN} {██} ${WHITE} ${RESET}" echo -e "${CYAN} __ ¨¨ __ ${WHITE}████████╗███████╗██████╗ ██████╗ ███████╗${RESET}" echo -e "${CYAN} {██} {██} ${WHITE}╚══██╔══╝██╔════╝██╔══██╗██╔═══██╗██╔════╝${RESET}" echo -e "${CYAN} ¨¨ ¨¨ ${WHITE} ██║ █████╗ ██████╔╝██║ ██║███████╗${RESET}" echo -e "${CYAN} __ __ ${WHITE} ██║ ██╔══╝ ██╔══██╗██║ ██║╚════██║${RESET}" echo -e "${CYAN} {██} {██} ${WHITE} ██║ ███████╗██║ ██║╚██████╔╝███████║${RESET}" echo -e "${CYAN} ¨¨ __ ¨¨ ${WHITE} ╚═╝ ╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝${RESET}" echo -e "${CYAN} {██} ${WHITE} ${RESET}" echo -e "${CYAN} ¨¨ ${WHITE} ${RESET}" echo "" echo -e " ${DIM}───────────────────────────────────────────────────────────────────${RESET}" echo -e " ${DIM}The open-source AI agent operating system${RESET}" echo -e " ${DIM}───────────────────────────────────────────────────────────────────${RESET}" echo "" # ───────────────────────────────────────────────────────────────────────────── # HELPERS # ───────────────────────────────────────────────────────────────────────────── info() { echo -e " ${CYAN}→${RESET} $1"; } success() { echo -e " ${GREEN}✓${RESET} $1"; } warn() { echo -e " ${YELLOW}!${RESET} $1"; } error() { echo -e "\n ${RED}✗ $1${RESET}\n"; exit 1; } step() { echo ""; echo -e " ${WHITE}$1${RESET}"; echo -e " ${DIM}$(printf '%.0s─' {1..60})${RESET}"; echo ""; } generate_secret() { openssl rand -hex 32 2>/dev/null || cat /dev/urandom | tr -dc 'a-f0-9' | head -c 64 } # ───────────────────────────────────────────────────────────────────────────── # STEP 1 — CHECK REQUIREMENTS # ───────────────────────────────────────────────────────────────────────────── step "1/4 Checking requirements" if ! command -v docker &>/dev/null; then error "Docker is not installed. Please install it from https://docs.docker.com/get-docker/" fi success "Docker $(docker --version | awk '{print $3}' | tr -d ',')" if ! docker compose version &>/dev/null; then error "Docker Compose v2 is not installed. Please update Docker." fi success "Docker Compose $(docker compose version --short)" if ! command -v curl &>/dev/null; then error "curl is not installed. Please install it first." fi success "curl $(curl --version | head -1 | awk '{print $2}')" if ! command -v git &>/dev/null; then error "git is not installed. Please install it first." fi success "git $(git --version | awk '{print $3}')" if ! docker info &>/dev/null; then error "Docker daemon is not running. Please start Docker and try again." fi success "Docker daemon is running" # Check ports — skip if Teros is already running on them if curl -sf "http://localhost:${BACKEND_PORT}/health" &>/dev/null; then success "Teros is already running — will rebuild in place" else for PORT in $BACKEND_PORT $FRONTEND_PORT; do if lsof -i ":$PORT" &>/dev/null 2>&1 || ss -tnlp 2>/dev/null | grep -q ":$PORT "; then error "Port $PORT is already in use. Please free it and try again." fi done success "Ports $BACKEND_PORT and $FRONTEND_PORT are available" fi # ───────────────────────────────────────────────────────────────────────────── # STEP 2 — DOWNLOAD & PREPARE FILES # ───────────────────────────────────────────────────────────────────────────── step "2/4 Downloading Teros" if [ -d "$TEROS_DIR/.git" ]; then info "Updating existing installation..." cd "$TEROS_DIR" git pull --quiet success "Repository updated" elif [ -d "$TEROS_DIR" ] && [ "$(ls -A "$TEROS_DIR")" ]; then warn "Directory $TEROS_DIR already exists and is not empty." echo -ne " ${DIM}Remove it and continue? [y/N]${RESET}: " read -r confirm .secrets/system/auth.json << EOF { "sessionTokenSecret": "${SESSION_SECRET}" } EOF cat > .secrets/system/encryption.json << EOF { "masterKey": "${ENCRYPTION_KEY}" } EOF cat > .secrets/system/admin.json << EOF { "apiKey": "${ADMIN_API_KEY}" } EOF cat > .secrets/system/database.json << EOF { "uri": "mongodb://mongodb:27017", "database": "teros" } EOF success "Secrets written" # ───────────────────────────────────────────────────────────────────────────── # STEP 3 — START SERVICES # ───────────────────────────────────────────────────────────────────────────── step "3/4 Starting Teros" info "Building and starting containers (this may take a few minutes on first run)..." echo "" docker compose up -d --build --quiet-pull 2>&1 | grep -E "Container|=>|pulling|building|pushing|error|Error" | sed 's/^/ /' || true echo "" success "Containers started" info "Waiting for backend to be ready..." MAX_WAIT=120 WAITED=0 until curl -sf "http://localhost:${BACKEND_PORT}/health" &>/dev/null; do if [ $WAITED -ge $MAX_WAIT ]; then echo "" error "Backend did not start within ${MAX_WAIT}s. Run: cd ~/.teros && docker compose logs backend" fi sleep 2 WAITED=$((WAITED + 2)) echo -ne " ${DIM} ${WAITED}s...${RESET}\r" done echo "" success "Backend is ready" # ───────────────────────────────────────────────────────────────────────────── # DONE # ───────────────────────────────────────────────────────────────────────────── # Load APP_URL from .env APP_URL=$(set -a; source "$TEROS_DIR/.env"; echo "$APP_URL") [ -z "$APP_URL" ] && error "APP_URL is not set in .env" for SCRIPT in start.sh stop.sh update.sh logs.sh create-user.sh update-password.sh; do [ -f "$TEROS_DIR/docker/$SCRIPT" ] && chmod +x "$TEROS_DIR/docker/$SCRIPT" done step "4/4 Create your first user" echo -e " ${DIM}Run this command to create your first account:${RESET}" echo "" echo -e " ${WHITE} ~/.teros/docker/create-user.sh${RESET}" echo "" echo -e " ${DIM}───────────────────────────────────────────────────────────────────${RESET}" echo "" echo -e " ${GREEN}✓ Teros is up and running!${RESET}" echo "" echo -e " ${DIM}Open your browser and go to:${RESET}" echo "" echo -e " ${WHITE} ❯ ${BRIGHT_CYAN}${APP_URL}${RESET}" echo "" echo -e " ${DIM}───────────────────────────────────────────────────────────────────${RESET}" echo "" echo -e " ${DIM} ~/.teros/docker/start.sh — start Teros${RESET}" echo -e " ${DIM} ~/.teros/docker/stop.sh — stop Teros${RESET}" echo -e " ${DIM} ~/.teros/docker/update.sh — pull latest and rebuild${RESET}" echo -e " ${DIM} ~/.teros/docker/logs.sh — follow logs${RESET}" echo -e " ${DIM} ~/.teros/docker/create-user.sh — create a new user${RESET}" echo -e " ${DIM} ~/.teros/docker/update-password.sh — update a user password${RESET}" echo "" # Open browser if command -v open &>/dev/null; then open "$APP_URL" elif command -v xdg-open &>/dev/null; then xdg-open "$APP_URL" &>/dev/null & fi