#!/usr/bin/env bash # 🚂 DEJA.js One-Command Installer # Usage: curl -fsSL https://install.dejajs.com | bash # # Developer mode (builds from local repo instead of downloading tarball): # ./install.sh --dev # ./install.sh --dev --repo /path/to/DEJA.js set -euo pipefail DEJA_DIR="${DEJA_DIR:-$HOME/.deja}" DEJA_BIN="${DEJA_DIR}/bin" SERVER_DIR="${DEJA_DIR}/server" ENV_FILE="${DEJA_DIR}/.env" CONFIG_FILE="${DEJA_DIR}/config.json" MIN_NODE_VERSION=20 INSTALL_BASE_URL="${DEJA_INSTALL_URL:-https://install.dejajs.com}" # --- Mode --- DEV_MODE=false DEV_REPO="" # --- Colors & Styles --- RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' BOLD='\033[1m' DIM='\033[2m' NC='\033[0m' info() { echo -e " 💬 ${BLUE}$*${NC}"; } ok() { echo -e " ✅ ${GREEN}$*${NC}"; } warn() { echo -e " ⚠️ ${YELLOW}$*${NC}"; } err() { echo -e " ❌ ${RED}$*${NC}" >&2; } print_banner() { echo -e " \033[38;2;0;232;252m██████╗ \033[38;2;0;195;245m███████╗\033[38;2;10;160;235m ██╗\033[38;2;25;130;220m █████╗ \033[38;2;255;0;170m██╗███████╗\033[0m" echo -e " \033[38;2;0;232;252m██╔══██╗\033[38;2;0;195;245m██╔════╝\033[38;2;10;160;235m ██║\033[38;2;25;130;220m██╔══██╗ \033[38;2;255;0;170m██║██╔════╝\033[0m" echo -e " \033[38;2;0;232;252m██║ ██║\033[38;2;0;195;245m█████╗\033[38;2;10;160;235m ██║\033[38;2;25;130;220m███████║ \033[38;2;255;0;170m██║███████╗\033[0m" echo -e " \033[38;2;0;232;252m██║ ██║\033[38;2;0;195;245m██╔══╝\033[38;2;10;160;235m ██ ██║\033[38;2;25;130;220m██╔══██║ \033[38;2;255;0;170m ██║╚════██║\033[0m" echo -e " \033[38;2;0;232;252m██████╔╝\033[38;2;0;195;245m███████╗\033[38;2;10;160;235m╚█████╔╝\033[38;2;25;130;220m██║ ██║\033[38;2;50;255;50m██╗\033[38;2;255;0;170m╚█████╔╝███████║\033[0m" echo -e " \033[38;2;0;232;252m╚═════╝ \033[38;2;0;195;245m╚══════╝\033[38;2;10;160;235m ╚════╝ \033[38;2;25;130;220m╚═╝ ╚═╝\033[38;2;50;255;50m╚═╝\033[38;2;255;0;170m ╚════╝ ╚══════╝\033[0m" } # ====================================================================== # 🔍 Step 1: Platform detection # ====================================================================== detect_platform() { echo "" echo -e " ${BOLD}🔍 Detecting platform...${NC}" OS="$(uname -s)" ARCH="$(uname -m)" case "${OS}" in Linux*) PLATFORM="linux" ;; Darwin*) PLATFORM="macos" ;; *) err "Unsupported OS: ${OS}. Windows users: install via WSL."; exit 1 ;; esac case "${ARCH}" in x86_64|amd64) ;; # ✅ supported aarch64|arm64) ;; # ✅ supported (Raspberry Pi, Apple Silicon) *) err "Unsupported architecture: ${ARCH}"; exit 1 ;; esac ok "Platform: ${PLATFORM} (${ARCH})" } # ====================================================================== # ⬢ Step 2: Check Node.js # ====================================================================== check_node() { echo "" echo -e " ${BOLD}⬢ Checking Node.js...${NC}" if ! command -v node &>/dev/null; then err "Node.js is not installed." echo "" info "📦 Install Node.js ${MIN_NODE_VERSION}+ from: https://nodejs.org/" if [ "${PLATFORM}" = "linux" ]; then info "Or use nvm: curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash" info "Then: nvm install ${MIN_NODE_VERSION}" elif [ "${PLATFORM}" = "macos" ]; then info "Or with Homebrew: brew install node@${MIN_NODE_VERSION}" fi exit 1 fi local node_version node_version=$(node --version | sed 's/v//' | cut -d. -f1) if [ "${node_version}" -lt "${MIN_NODE_VERSION}" ]; then err "Node.js ${MIN_NODE_VERSION}+ required. Found: $(node --version)" info "📦 Update Node.js: https://nodejs.org/" exit 1 fi ok "Node.js $(node --version)" } # ====================================================================== # 📝 Step 4: Environment setup # ====================================================================== setup_environment() { echo "" echo -e " ${BOLD}📝 Setting up environment...${NC}" mkdir -p "${DEJA_DIR}" if [ -f "${ENV_FILE}" ]; then ok "Environment file exists (${ENV_FILE})" return fi # Firebase client config — public client-side keys, safe to embed. # CI replaces these placeholders at release time. local fb_api_key="AIzaSyAJZyQcSPNRkNP9PSrEvFXFkTvLpViosqA" local fb_auth_domain="" local fb_project_id="dejajs" local fb_database_url="" local fb_storage_bucket="" local fb_messaging_id="" local fb_app_id="" if [[ "${fb_api_key}" == __* ]]; then # Placeholders not replaced — try local .env, then fetch from install API local repo_env="" local repo_dir="${DEV_REPO}" [ -z "${repo_dir}" ] && repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" for ef in "${repo_dir}/.env" "${repo_dir}/.env.local"; do [ -f "${ef}" ] && repo_env="${ef}" && break done if [ -z "${repo_env}" ] && [ -f "${DEJA_DIR}/.env" ]; then repo_env="${DEJA_DIR}/.env" fi if [ -n "${repo_env}" ]; then read_env_var() { grep "^${1}=" "${repo_env}" 2>/dev/null | head -1 | cut -d= -f2- | sed 's/^["'"'"']//;s/["'"'"']$//' || echo ""; } fb_api_key=$(read_env_var VITE_FIREBASE_API_KEY) fb_auth_domain=$(read_env_var VITE_FIREBASE_AUTH_DOMAIN) fb_project_id=$(read_env_var VITE_FIREBASE_PROJECT_ID) fb_database_url=$(read_env_var VITE_FIREBASE_DATABASE_URL) fb_storage_bucket=$(read_env_var VITE_FIREBASE_STORAGE_BUCKET) fb_messaging_id=$(read_env_var VITE_FIREBASE_MESSAGING_SENDER_ID) fb_app_id=$(read_env_var VITE_FIREBASE_APP_ID) ok "Firebase config loaded from ${repo_env}" else # Fetch public Firebase client config from install API info "Fetching Firebase config from ${INSTALL_BASE_URL}..." local config_json config_json=$(curl -fsSL "${INSTALL_BASE_URL}/api/config" 2>/dev/null) || { err "Failed to fetch Firebase config from ${INSTALL_BASE_URL}/api/config" exit 1 } json_val() { echo "${config_json}" | grep -o "\"${1}\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" | sed "s/.*\"${1}\"[[:space:]]*:[[:space:]]*\"\([^\"]*\)\".*/\1/"; } fb_api_key=$(json_val VITE_FIREBASE_API_KEY) fb_auth_domain=$(json_val VITE_FIREBASE_AUTH_DOMAIN) fb_project_id=$(json_val VITE_FIREBASE_PROJECT_ID) fb_database_url=$(json_val VITE_FIREBASE_DATABASE_URL) fb_storage_bucket=$(json_val VITE_FIREBASE_STORAGE_BUCKET) fb_messaging_id=$(json_val VITE_FIREBASE_MESSAGING_SENDER_ID) fb_app_id=$(json_val VITE_FIREBASE_APP_ID) if [ -z "${fb_api_key}" ]; then err "Firebase config response was empty. Check ${INSTALL_BASE_URL}/api/config" exit 1 fi ok "Firebase config fetched from install API" fi else ok "Firebase client config embedded" fi cat > "${ENV_FILE}" <&1 | tail -1) ok "Server ${version} installed 🎉" } install_server_dev() { local repo_dir="${DEV_REPO}" if [ -z "${repo_dir}" ]; then # Try to detect the repo from the script's own location local script_dir script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [ -f "${script_dir}/apps/server/package.json" ]; then repo_dir="${script_dir}" else err "Cannot find DEJA.js repo. Use --repo /path/to/DEJA.js" exit 1 fi fi if [ ! -f "${repo_dir}/apps/server/package.json" ]; then err "Not a valid DEJA.js repo: ${repo_dir}" exit 1 fi info "🛠️ Building server from local source: ${repo_dir}" # Check for pnpm if ! command -v pnpm &>/dev/null; then err "pnpm is required for dev builds. Install: npm install -g pnpm" exit 1 fi # Build the server bundle with tsup cd "${repo_dir}" pnpm --filter=deja-serverts run build:docker || { err "Server build failed. Check for errors above." exit 1 } # Copy built output to ~/.deja/server mkdir -p "${SERVER_DIR}" cp "${repo_dir}/apps/server/dist/index.js" "${SERVER_DIR}/index.js" # Write version from package.json local version version=$(node -e "console.log(require('${repo_dir}/apps/server/package.json').version)") echo "${version}-dev" > "${SERVER_DIR}/version.txt" # Generate a distribution package.json with only runtime externals. # Workspace @repo/* deps are bundled into index.js and must NOT appear here # (npm can't resolve "workspace:*" specifiers). node -e " const pkg = {name:'deja-server',version:'${version}-dev',private:true,type:'module',engines:{node:'>= 20'},dependencies:{'serialport':'^12.0.0','@serialport/parser-readline':'^12.0.0','firebase-admin':'^13.4.0','firebase':'^10.14.1','ws':'^8.13.0','mqtt':'^5.15.0','signale':'^1.4.0','dotenv':'^16.3.1','play-sound':'^1.1.6','wait-on':'^7.0.1','@sentry/node':'^8.0.0','vue':'^3.5.17','vuefire':'^3.2.1','@vueuse/core':'^11.0.3'}}; require('fs').writeFileSync('${SERVER_DIR}/package.json', JSON.stringify(pkg, null, 2)); " info "📦 Installing native dependencies..." (cd "${SERVER_DIR}" && npm install --production 2>&1 | tail -1) ok "Server ${version}-dev built and installed from source 🛠️" } # ====================================================================== # ⚡ Step 7: Install CLI # ====================================================================== install_cli() { echo "" echo -e " ${BOLD}⚡ Installing CLI...${NC}" mkdir -p "${DEJA_BIN}" if [ "${DEV_MODE}" = true ]; then # In dev mode, copy CLI from the repo local repo_dir="${DEV_REPO}" if [ -z "${repo_dir}" ]; then repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" fi cp "${repo_dir}/apps/cli/deja" "${DEJA_BIN}/deja" chmod +x "${DEJA_BIN}/deja" # Also copy TUI files if present if [ -d "${repo_dir}/apps/cli/tui" ]; then cp -r "${repo_dir}/apps/cli/tui" "${DEJA_BIN}/" [ -f "${repo_dir}/apps/cli/deja-ui-ink.mjs" ] && cp "${repo_dir}/apps/cli/deja-ui-ink.mjs" "${DEJA_BIN}/" # Install TUI dependencies at ~/.deja/ so node_modules resolves from # ~/.deja/bin/tui/**. The canonical list of TUI deps lives in # apps/cli/package.json; copy it and run a plain npm install. if [ -f "${repo_dir}/apps/cli/package.json" ]; then cp "${repo_dir}/apps/cli/package.json" "${DEJA_DIR}/package.json" info "📦 Installing TUI dependencies..." (cd "${DEJA_DIR}" && npm install 2>&1 | tail -1) fi fi ok "CLI installed from local repo 🛠️" else # Download the CLI from the install API curl -fsSL "${INSTALL_BASE_URL}/releases/latest/deja" \ -o "${DEJA_BIN}/deja" || { err "Failed to download DEJA CLI." exit 1 } chmod +x "${DEJA_BIN}/deja" ok "CLI downloaded" fi # Add to PATH if not already present if ! echo "${PATH}" | grep -q "${DEJA_BIN}"; then local shell_rc="" if [ -f "${HOME}/.zshrc" ]; then shell_rc="${HOME}/.zshrc" elif [ -f "${HOME}/.bashrc" ]; then shell_rc="${HOME}/.bashrc" fi if [ -n "${shell_rc}" ]; then if ! grep -q '.deja/bin' "${shell_rc}" 2>/dev/null; then echo '' >> "${shell_rc}" echo '# 🚂 DEJA.js CLI' >> "${shell_rc}" echo 'export PATH="${HOME}/.deja/bin:${PATH}"' >> "${shell_rc}" info "📝 Added ${DEJA_BIN} to PATH in ${shell_rc}" info "🔄 Run: ${CYAN}source ${shell_rc}${NC} (or open a new terminal)" fi else warn "Add ${DEJA_BIN} to your PATH manually." fi fi ok "CLI ready at ${DEJA_BIN}/deja ⚡" } # ====================================================================== # 🔑 Step 7: Login # ====================================================================== login_and_start() { export PATH="${DEJA_BIN}:${PATH}" echo "" echo -e " ${GREEN}${BOLD}════════════════════════════════════════${NC}" echo -e " ${GREEN}${BOLD} 🚂 DEJA.js installed! ${NC}" echo -e " ${GREEN}${BOLD}════════════════════════════════════════${NC}" echo "" if [ -t 0 ]; then # Interactive terminal — prompt for login "${DEJA_BIN}/deja" login || { warn "Login skipped. Run ${CYAN}deja login${NC} later to connect your account." echo "" return } echo "" info "🚀 Starting DEJA.js server..." "${DEJA_BIN}/deja" start -b else # Piped from curl — skip interactive login echo "" info "Run ${CYAN}deja login${NC} to connect your account, then ${CYAN}deja start${NC} to launch the server." fi echo "" echo -e " 🌐 ${DIM}Server${NC} ${CYAN}ws://localhost:8082${NC}" echo -e " 📁 ${DIM}Config${NC} ${CYAN}${DEJA_DIR}/${NC}" echo -e " 📋 ${DIM}Logs${NC} ${CYAN}deja logs -f${NC}" echo -e " 📊 ${DIM}Status${NC} ${CYAN}deja status${NC}" echo "" } # ====================================================================== # 🚂 Main # ====================================================================== main() { echo "" print_banner echo "" echo -e " ${BOLD}🚂 DCC-EX JavaScript API${NC} — Installer" if [ "${DEV_MODE}" = true ]; then echo -e " ${YELLOW}${BOLD}🛠️ Developer Mode${NC}" fi echo "" detect_platform check_node setup_environment detect_serial install_server install_cli login_and_start } # Parse arguments while [ $# -gt 0 ]; do case "$1" in --dev) DEV_MODE=true ;; --repo) shift; DEV_REPO="${1:-}" ;; --repo=*) DEV_REPO="${1#*=}" ;; *) ;; esac shift done main