#!/usr/bin/env bash set -euo pipefail # ── Orbis Installer ────────────────────────────────────────────────────────── # Usage: bash <(curl -fsSL https://install.iamorbis.one/install.sh) # ----------------------------------------------------------------------------- ORBIS_VERSION="${ORBIS_VERSION:-latest}" INSTALL_DIR="${INSTALL_DIR:-$HOME/orbis}" PRIMARY_BASE_URL="https://install.iamorbis.one" MIRROR_BASE_URL="https://raw.githubusercontent.com/HAANGroup/ooainstall/master" # ── Colors ─────────────────────────────────────────────────────────────────── R='\033[0;31m'; G='\033[0;32m'; Y='\033[1;33m'; C='\033[0;36m' B='\033[0;34m'; M='\033[0;35m'; W='\033[1;37m'; DIM='\033[2m'; NC='\033[0m' BOLD='\033[1m' step() { echo -e "\n${BOLD}${C} ▸ $*${NC}"; } ok() { echo -e " ${G}✓${NC} $*"; } info() { echo -e " ${DIM}$*${NC}"; } warn() { echo -e " ${Y}⚠${NC} $*"; } ask() { echo -e " ${M}?${NC} $*"; } error() { echo -e "\n ${R}✖ $*${NC}" >&2; exit 1; } divider() { echo -e " ${DIM}────────────────────────────────────────────────${NC}"; } BOX_WIDTH=48 print_box_border() { local left="$1" local fill="$2" local right="$3" local line line=$(printf "%${BOX_WIDTH}s" "") line=${line// /$fill} printf " ${G}${BOLD}%s%s%s${NC}\n" "$left" "$line" "$right" } print_box_line() { local text="$1" local width="$BOX_WIDTH" local line local rest="$text" if [ -z "$rest" ]; then printf " ${G}${BOLD}║${NC} %-*s ${G}${BOLD}║${NC}\n" "$width" "" return fi while [ "${#rest}" -gt "$width" ]; do line=${rest:0:width} printf " ${G}${BOLD}║${NC} %-*s ${G}${BOLD}║${NC}\n" "$width" "$line" rest=${rest:width} done printf " ${G}${BOLD}║${NC} %-*s ${G}${BOLD}║${NC}\n" "$width" "$rest" } download_file() { local path="$1" local output="$2" local url for base in "$MIRROR_BASE_URL" "$PRIMARY_BASE_URL"; do url="${base}/${path}" if curl -fsSL "$url" -o "$output"; then ok "${output}" return 0 fi done error "Failed to download ${path} from all installer sources." } # ── Banner ─────────────────────────────────────────────────────────────────── echo "" echo -e "${C}${BOLD}" echo " ██████╗ ██████╗ ██████╗ ██╗███████╗" echo " ██╔═══██╗██╔══██╗██╔══██╗██║██╔════╝" echo " ██║ ██║██████╔╝██████╔╝██║███████╗" echo " ██║ ██║██╔══██╗██╔══██╗██║╚════██║" echo " ╚██████╔╝██║ ██║██████╔╝██║███████║" echo " ╚═════╝ ╚═╝ ╚═╝╚═════╝ ╚═╝╚══════╝" echo -e "${NC}" echo -e " ${BOLD}Self-Hosted Installer${NC} ${DIM}v${ORBIS_VERSION}${NC}" divider # ── Prerequisites ───────────────────────────────────────────────────────────── command -v docker >/dev/null 2>&1 || error "Docker not found. Install it from https://docs.docker.com/get-docker/" docker info >/dev/null 2>&1 || error "Docker is not running. Start Docker Desktop and re-run this script." docker compose version >/dev/null 2>&1 || error "Docker Compose plugin not found." DOCKER_VER=$(docker --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) COMPOSE_VER=$(docker compose version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) ok "Docker ${DOCKER_VER} • Compose ${COMPOSE_VER}" # ── Install directory ───────────────────────────────────────────────────────── mkdir -p "$INSTALL_DIR" cd "$INSTALL_DIR" ok "Install directory: ${INSTALL_DIR}" # ── Download config files ───────────────────────────────────────────────────── step "Downloading config files" download_file "docker-compose.yml" docker-compose.yml download_file "temporal-dynamicconfig.yaml" temporal-dynamicconfig.yaml download_file "nginx.selfhosted.conf" nginx.selfhosted.conf # ── Environment file ────────────────────────────────────────────────────────── if [ ! -f .env ]; then download_file ".env.example" .env if command -v openssl >/dev/null 2>&1; then step "Generating secrets" ENCRYPTION_KEY=$(openssl rand -hex 32) DB_PASSWORD=$(openssl rand -hex 16) JWT_SECRET=$(openssl rand -hex 32) ADMIN_PASSWORD=$(openssl rand -base64 12 | tr -d '/+=' | head -c 16) ADMIN_EMAIL="admin@orbis.local" sed "s/^ENCRYPTION_KEY=.*/ENCRYPTION_KEY=${ENCRYPTION_KEY}/" .env > .env.tmp && mv .env.tmp .env sed "s/^DB_PASSWORD=.*/DB_PASSWORD=${DB_PASSWORD}/" .env > .env.tmp && mv .env.tmp .env sed "s/^JWT_SECRET=.*/JWT_SECRET=${JWT_SECRET}/" .env > .env.tmp && mv .env.tmp .env # Write admin credentials (append, works regardless of template content) grep -v "^ADMIN_EMAIL=" .env > .env.tmp && mv .env.tmp .env grep -v "^ADMIN_PASSWORD=" .env > .env.tmp && mv .env.tmp .env { echo "ADMIN_EMAIL=${ADMIN_EMAIL}"; echo "ADMIN_PASSWORD=${ADMIN_PASSWORD}"; } >> .env chmod 600 .env ok "Secrets generated" else warn "openssl not found — set ENCRYPTION_KEY, DB_PASSWORD, JWT_SECRET manually in .env" fi divider info "Review .env before starting (APP_URL, SMTP, etc.):" info " nano ${INSTALL_DIR}/.env" echo "" ask "Open .env for editing now? Type Y or N within 15 seconds (default: Y)" if ! read -r -t 15 EDIT_ENV; then EDIT_ENV="Y" info "No response received after 15 seconds — opening .env" fi if [[ "${EDIT_ENV:-Y}" =~ ^[Yy]$ ]]; then "${EDITOR:-nano}" .env fi else ok ".env already exists — skipping" fi # ── Validate required secrets ───────────────────────────────────────────────── check_env_var() { local key="$1" local val val=$(grep -E "^${key}=" .env 2>/dev/null | cut -d= -f2- | tr -d '[:space:]') [ -n "$val" ] || error "${key} is not set in .env — run: nano ${INSTALL_DIR}/.env" } check_env_var ENCRYPTION_KEY check_env_var DB_PASSWORD check_env_var JWT_SECRET # ── Pull images ─────────────────────────────────────────────────────────────── step "Pulling images" info "This may take a few minutes on the first install..." echo "" docker compose pull || error "Failed to pull images. Check your internet connection." # ── Start ───────────────────────────────────────────────────────────────────── divider step "Starting Orbis" docker compose down -v --remove-orphans 2>/dev/null || true echo "" if ! docker compose up -d; then echo "" warn "Something went wrong. Container status:" docker compose ps echo "" warn "Diagnose with:" info " docker compose -f ${INSTALL_DIR}/docker-compose.yml logs postgres" info " docker compose -f ${INSTALL_DIR}/docker-compose.yml logs api" echo "" warn "Common causes:" info " • postgres unhealthy → missing secrets in .env" info " • port 80 in use → stop the conflicting process, then: cd ${INSTALL_DIR} && docker compose up -d" error "Startup failed. See above for details." fi # Read admin credentials from .env ADMIN_EMAIL_VAL=$(grep -E "^ADMIN_EMAIL=" .env 2>/dev/null | cut -d= -f2- | tr -d '[:space:]' || true) ADMIN_PASS_VAL=$(grep -E "^ADMIN_PASSWORD=" .env 2>/dev/null | cut -d= -f2- | tr -d '[:space:]' || true) # Wait for the API to become healthy (up to 3 minutes) step "Waiting for services to be ready" WAIT_TRIES=0 printf " " until docker compose ps api 2>/dev/null | grep -q "(healthy)" || [ $WAIT_TRIES -ge 36 ]; do printf "." sleep 5 WAIT_TRIES=$((WAIT_TRIES + 1)) done echo "" if docker compose ps api 2>/dev/null | grep -q "(healthy)"; then ok "All services are ready" else warn "Services are taking longer than expected to start. Check logs with: docker compose logs" fi if command -v open >/dev/null 2>&1; then open "http://localhost" 2>/dev/null || true elif command -v xdg-open >/dev/null 2>&1; then xdg-open "http://localhost" 2>/dev/null || true fi # ── Success banner ───────────────────────────────────────────────────────── echo "" print_box_border "╔" "═" "╗" print_box_line "Orbis is up and running!" print_box_border "╠" "═" "╣" print_box_line "URL: http://localhost" if [ -n "$ADMIN_EMAIL_VAL" ] && [ -n "$ADMIN_PASS_VAL" ]; then print_box_line "" print_box_line "Admin login (change after first sign-in):" print_box_line "Email: ${ADMIN_EMAIL_VAL}" print_box_line "Password: ${ADMIN_PASS_VAL}" fi print_box_border "╠" "═" "╣" print_box_line "Logs: cd ${INSTALL_DIR} && docker compose logs -f" print_box_line "Stop: cd ${INSTALL_DIR} && docker compose down" print_box_border "╚" "═" "╝" echo "" # ── Telemetry ping (anonymous) ──────────────────────────────────────────────── curl -fsSL -X POST "${PRIMARY_BASE_URL}/telemetry/install" \ -H "Content-Type: application/json" \ -d "{\"version\":\"${ORBIS_VERSION}\",\"os\":\"$(uname -s)\"}" \ --max-time 5 --silent > /dev/null 2>&1 || true