#!/usr/bin/env bash #////////// PROJECT FILE SEPARATION LINE ////////// CODE AFTER THIS LINE ARE FROM: /Borealis.sh # Linux parity for Borealis.ps1 (Engine focus). Aims to be OS-agnostic across Ubuntu/Debian, RHEL/Rocky/Fedora, Arch. # - Installs system deps when needed (python3, venv, pip, curl, unzip, tesseract) # - Bundles portable NodeJS into Dependencies/NodeJS to keep a known-good version (no root required) # - Mirrors Windows flow: create Engine venv, stage Data/Engine, stage web-interface, Vite dev or prod build, Flask launch # - Supports flags: --server/--agent (agent kept for compatibility), --vite/--flask, --quick, --engine-tests, # --EngineProduction, --EngineDev (auto-select server mode), plus interactive menu # NOTE: This script focuses on ENGINE parity. Agent paths remain but are not the goal here. set -o errexit set -o nounset set -o pipefail # ---- Colors / Icons ---- BOREALIS_BLUE="\033[38;5;39m" DARK_GRAY="\033[1;30m" GREEN="\033[0;32m" YELLOW="\033[1;33m" RED="\033[0;31m" RESET="\033[0m" CHECKMARK="[OK]"; HOURGLASS="[WAIT]"; CROSSMARK="[X]"; INFO="[i]" SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR" # ---- CLI flags (parity with Borealis.ps1) ---- SERVER_FLAG=0 AGENT_FLAG=0 VITE_FLAG=0 FLASK_FLAG=0 QUICK_FLAG=0 ENGINE_TESTS_FLAG=0 ENGINE_PROD_FLAG=0 ENGINE_DEV_FLAG=0 INSTALLER_CODE="" while (( "$#" )); do case "$1" in -Server|--server) SERVER_FLAG=1 ;; -Agent|--agent) AGENT_FLAG=1 ;; -Vite|--vite) VITE_FLAG=1 ;; -Flask|--flask) FLASK_FLAG=1 ;; -Quick|--quick) QUICK_FLAG=1 ;; -EngineTests|--engine-tests) ENGINE_TESTS_FLAG=1 ;; -EngineProduction|--engine-production) ENGINE_PROD_FLAG=1 ;; -EngineDev|--engine-dev) ENGINE_DEV_FLAG=1 ;; -InstallerCode|--installer-code) shift; INSTALLER_CODE="${1:-}" ;; *) ;; # ignore unknown for flexibility esac shift || true done # ---- Helpers ---- run_step() { local message="$1"; shift printf "%s %s... " "${HOURGLASS}" "$message" if "$@"; then printf "\r%s %s\n" "${CHECKMARK}" "$message" else printf "\r%s %s - Failed\n" "${CROSSMARK}" "$message" 1>&2 exit 1 fi } detect_distro() { DISTRO_ID="unknown"; DISTRO_LIKE="" if [[ -f /etc/os-release ]]; then # shellcheck disable=SC1091 . /etc/os-release DISTRO_ID=${ID:-unknown} DISTRO_LIKE=${ID_LIKE:-} fi } need_sudo() { [ "${EUID:-$(id -u)}" -ne 0 ]; } ensure_engine_log_dir() { mkdir -p "${SCRIPT_DIR}/Engine/Logs" echo "${SCRIPT_DIR}/Engine/Logs" } write_vite_log() { local msg="$1"; local svc="${2:-vite-dev}" local logdir; logdir=$(ensure_engine_log_dir) printf "%s-%s-%s\n" "$(date +%FT%T)" "$svc" "$msg" >> "${logdir}/vite.log" } # ---- Dependency Installation (Linux) ---- install_shared_dependencies() { detect_distro if command -v python3 >/dev/null 2>&1 && command -v pip3 >/dev/null 2>&1; then :; else case "$DISTRO_ID" in ubuntu|debian) sudo apt update -qq && sudo apt install -y python3 python3-venv python3-pip curl unzip ca-certificates ;; rhel|centos|fedora|rocky) if command -v dnf >/dev/null 2>&1; then sudo dnf install -y python3 python3-pip python3-virtualenv curl unzip ca-certificates ; else sudo yum install -y python3 python3-pip python3-virtualenv curl unzip ca-certificates ; fi ;; arch) sudo pacman -Sy --noconfirm python python-pip python-virtualenv curl unzip ca-certificates ;; *) : ;; esac fi } install_tesseract() { detect_distro case "$DISTRO_ID" in ubuntu|debian) sudo apt update -qq && sudo apt install -y tesseract-ocr ;; rhel|centos|fedora|rocky) if command -v dnf >/dev/null 2>&1; then sudo dnf install -y tesseract ; else sudo yum install -y tesseract ; fi ;; arch) sudo pacman -Sy --noconfirm tesseract ;; *) : ;; esac } NODE_VERSION="v23.11.0" NODE_DIR="${SCRIPT_DIR}/Dependencies/NodeJS" NODE_BIN="${NODE_DIR}/bin/node" NPM_BIN="${NODE_DIR}/bin/npm" NPX_BIN="${NODE_DIR}/bin/npx" install_node_portable() { if [[ -x "$NPM_BIN" ]]; then return 0; fi mkdir -p "$NODE_DIR" local tarball="node-${NODE_VERSION}-linux-x64.tar.xz" local url="https://nodejs.org/dist/${NODE_VERSION}/${tarball}" local dl_path="${SCRIPT_DIR}/Dependencies/${tarball}" write_vite_log "Downloading NodeJS ${NODE_VERSION} from ${url}" "bootstrap" curl -fsSL -o "$dl_path" "$url" rm -rf "${NODE_DIR:?}"/* tar -xJf "$dl_path" -C "$NODE_DIR" --strip-components=1 rm -f "$dl_path" } ensure_node_bins() { if [[ -x "$NPM_BIN" ]]; then export PATH="${NODE_DIR}/bin:${PATH}"; return 0; fi if command -v npm >/dev/null 2>&1; then NPM_BIN="$(command -v npm)"; NPX_BIN="$(command -v npx || echo npx)"; return 0 fi echo -e "${YELLOW}npm not found on PATH; installing portable NodeJS...${RESET}" install_node_portable export PATH="${NODE_DIR}/bin:${PATH}" } install_server_dependencies() { run_step "Dependency: Python (system)" install_shared_dependencies run_step "Dependency: Tesseract-OCR (system)" install_tesseract run_step "Dependency: NodeJS (portable)" install_node_portable } # Prefer a resilient resolver for the Engine venv interpreter (some venvs only have 'python') engine_python_bin() { if [[ -x "${SCRIPT_DIR}/Engine/bin/python3" ]]; then echo "${SCRIPT_DIR}/Engine/bin/python3" elif [[ -x "${SCRIPT_DIR}/Engine/bin/python" ]]; then echo "${SCRIPT_DIR}/Engine/bin/python" else echo "" fi } # ---- Engine TLS material (parity with Ensure-EngineTlsMaterial) ---- ensure_engine_tls_material() { local py="$1" # engine venv python local cert_root_arg="$2" # optional path with pre-provided certs local effective_root="" if [[ -x "$py" ]]; then local code='from Data.Engine.security import certificates; certificates.ensure_certificate(); print(certificates.engine_certificates_root())' set +e effective_root="$("$py" -c "$code" 2>/dev/null | tail -n 1 | tr -d '\r' || true)" set -e fi if [[ -z "$effective_root" && -n "${cert_root_arg}" ]]; then if [[ -f "${cert_root_arg}/borealis-server-cert.pem" && -f "${cert_root_arg}/borealis-server-key.pem" ]]; then effective_root="${cert_root_arg}" else write_vite_log "Provided certificate root '${cert_root_arg}' missing expected TLS material; using Engine runtime certificates instead." "tls" fi fi if [[ -z "$effective_root" ]]; then effective_root="${SCRIPT_DIR}/Engine/Certificates" mkdir -p "$effective_root" fi export BOREALIS_CERT_DIR="$effective_root" export BOREALIS_TLS_CERT="${effective_root}/borealis-server-cert.pem" export BOREALIS_TLS_KEY="${effective_root}/borealis-server-key.pem" export BOREALIS_TLS_BUNDLE="${effective_root}/borealis-server-bundle.pem" } # ---- Engine web interface staging (parity with Ensure-EngineWebInterface) ---- ensure_engine_web_interface() { local project_root="$1" local dest="${project_root}/Engine/web-interface" local stage="${project_root}/Data/Engine/web-interface" [[ -d "$stage" ]] || { echo -e "${RED}Engine web interface source missing at '$stage'.${RESET}" >&2; return 1; } rm -rf "$dest" 2>/dev/null || true mkdir -p "$dest" cp -a "${stage}/." "$dest/" [[ -f "${dest}/package.json" ]] || { echo -e "${RED}Failed to stage Engine web interface into '$dest'.${RESET}" >&2; return 1; } } # ---- Engine build+launch flow ---- create_engine_venv_and_stage_data() { local venv_dir="${SCRIPT_DIR}/Engine" local engine_src="${SCRIPT_DIR}/Data/Engine" local data_dest="${venv_dir}/Data/Engine" [[ -d "$venv_dir" ]] || python3 -m venv "$venv_dir" mkdir -p "${venv_dir}/Data" rm -rf "$data_dest" 2>/dev/null || true mkdir -p "$data_dest" # Copy everything except Assemblies (handled separately) shopt -s dotglob nullglob for item in "${engine_src}"/*; do base="$(basename "$item")" if [[ "$base" == "Assemblies" ]]; then continue; fi cp -a "$item" "$data_dest/" done shopt -u dotglob nullglob # Assemblies runtime folder [[ -d "${SCRIPT_DIR}/Engine/Assemblies" ]] || { if [[ -d "${engine_src}/Assemblies" ]]; then cp -a "${engine_src}/Assemblies" "${SCRIPT_DIR}/Engine/Assemblies" else mkdir -p "${SCRIPT_DIR}/Engine/Assemblies" fi } # Auth_Tokens and database mkdir -p "${SCRIPT_DIR}/Engine/Auth_Tokens" # database.db will be created by the app if not present; ensure dir exists } install_engine_python_deps() { local venv_py venv_py="$(engine_python_bin)" if [[ -z "$venv_py" ]]; then # Try to create the venv if it doesn't exist yet python3 -m venv "${SCRIPT_DIR}/Engine" || true venv_py="$(engine_python_bin)" fi local engine_src="${SCRIPT_DIR}/Data/Engine" local reqs=( "${engine_src}/engine-requirements.txt" "${engine_src}/requirements.txt" ) for r in "${reqs[@]}"; do if [[ -f "$r" && -n "$venv_py" ]]; then "$venv_py" -m pip install --disable-pip-version-check -q -r "$r" return 0 fi done return 0 } vite_web_frontend_install() { local engine_ui_dest="${SCRIPT_DIR}/Engine/web-interface" ensure_node_bins ( cd "$engine_ui_dest" && "$NPM_BIN" install --silent --no-fund --audit=false >/dev/null ) } vite_web_frontend_start() { local mode="$1" # developer|production local engine_ui_dest="${SCRIPT_DIR}/Engine/web-interface" ensure_node_bins ensure_engine_tls_material "$(engine_python_bin)" "" if [[ "$mode" == "developer" ]]; then local logdir; logdir=$(ensure_engine_log_dir) local stdout_log="${logdir}/vite-dev.stdout.log" local stderr_log="${logdir}/vite-dev.stderr.log" mv -f "$stdout_log" "${stdout_log}.$(date +%Y%m%d%H%M%S)" 2>/dev/null || true mv -f "$stderr_log" "${stderr_log}.$(date +%Y%m%d%H%M%S)" 2>/dev/null || true write_vite_log "Starting Vite dev server using TLS (cert=$BOREALIS_TLS_CERT bundle=$BOREALIS_TLS_BUNDLE)" "vite-dev" ( cd "$engine_ui_dest" PATH="${NODE_DIR}/bin:${PATH}" nohup "$NPM_BIN" run dev >"$stdout_log" 2>"$stderr_log" & ) else write_vite_log "Executing npm run build for production WebUI assets." "vite-build" ( cd "$engine_ui_dest" && "$NPM_BIN" run build ) fi } flask_engine_launch() { local mode="$1" # production|developer pushd "${SCRIPT_DIR}/Engine" >/dev/null local py py="$(engine_python_bin)" if [[ -z "$py" ]]; then python3 -m venv "${SCRIPT_DIR}/Engine" || true py="$(engine_python_bin)" fi local prev_mode="${BOREALIS_ENGINE_MODE:-}" local prev_port="${BOREALIS_ENGINE_PORT:-}" local prev_root="${BOREALIS_PROJECT_ROOT:-}" export BOREALIS_ENGINE_MODE="$mode" export BOREALIS_ENGINE_PORT="5000" export BOREALIS_PROJECT_ROOT="$SCRIPT_DIR" echo -e "\n${GREEN}Launching Borealis Engine...${RESET}" echo "====================================================================================" echo "${HOURGLASS} Engine Socket Server Started..." "$py" -m Data.Engine.bootstrapper || true # restore env if [[ -n "$prev_mode" ]]; then export BOREALIS_ENGINE_MODE="$prev_mode"; else unset BOREALIS_ENGINE_MODE; fi if [[ -n "$prev_port" ]]; then export BOREALIS_ENGINE_PORT="$prev_port"; else unset BOREALIS_ENGINE_PORT; fi if [[ -n "$prev_root" ]]; then export BOREALIS_PROJECT_ROOT="$prev_root"; else unset BOREALIS_PROJECT_ROOT; fi popd >/dev/null } # ---- Tests parity ---- if (( ENGINE_TESTS_FLAG )); then export BOREALIS_PROJECT_ROOT="${SCRIPT_DIR}" PYTHON_BIN="$(command -v python3 || command -v python || true)" if [[ -z "${PYTHON_BIN}" ]]; then echo -e "${RED}Python interpreter not found. Install Python 3 to run Engine tests.${RESET}" >&2 exit 1 fi "${PYTHON_BIN}" -m pytest 'Data/Engine/Unit_Tests' exit $? fi # ---- Banner ---- clear || true printf "%b" "${BOREALIS_BLUE}" cat << 'EOF' ::::::::: :::::::: ::::::::: :::::::::: ::: ::: ::::::::::: :::::::: :+: :+: :+: :+: :+: :+: :+: :+: :+: :+: :+: :+: :+: +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +:+ +#++:++#+ +#+ +:+ +#++:++#: +#++:++# +#++:++#++: +#+ +#+ +#++:++#++ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ #+# #+# #+# #+# #+# #+# #+# #+# #+# #+# #+# #+# #+# ######### ######## ### ### ########## ### ### ########## ########### ######## EOF printf "%b" "${RESET}" printf "%b\n" "${DARK_GRAY}Automation Platform${RESET}" # ---- Menus ---- server_menu() { echo -e "\nConfigure Borealis Engine Mode:" echo -e " 1) Build & Launch > Production Flask Server @ https://localhost:5000" echo -e " 2) [Skip Build] & Immediately Launch > Production Flask Server @ https://localhost:5000" echo -e " 3) Launch > [Hotload-Ready] Vite Dev Server @ https://localhost:5173" read -r -p "Enter choice [1/2/3]: " modeChoice case "$modeChoice" in 1) borealis_operation_mode="production" ;; 2) borealis_operation_mode="production" ;; 3) borealis_operation_mode="developer" ;; *) echo -e "${RED}Invalid mode choice${RESET}"; return 1 ;; esac echo -e "${GREEN}Ensuring Engine Dependencies Exist...${RESET}" install_server_dependencies export PATH="${NODE_DIR}/bin:${PATH}" if [[ "$modeChoice" == "2" ]]; then # Immediate launch of Flask without rebuild flask_engine_launch "$borealis_operation_mode" return 0 fi run_step "Create Borealis Engine Virtual Python Environment & Stage Data" create_engine_venv_and_stage_data run_step "Install Engine Python Dependencies" install_engine_python_deps run_step "Copy Engine WebUI Files" ensure_engine_web_interface "$SCRIPT_DIR" run_step "Vite Web Frontend: Install NPM Packages" vite_web_frontend_install run_step "Vite Web Frontend: Start (${borealis_operation_mode})" vite_web_frontend_start "$borealis_operation_mode" flask_engine_launch "$borealis_operation_mode" } main_menu() { echo -e "\nPlease choose which function you want to launch:" echo -e " 1) Borealis Engine" echo -e " 2) Borealis Agent (not the focus on Linux)" echo -e " 3) Exit" read -r -p "Enter a number: " choice case "$choice" in 1) server_menu ;; 2) echo -e "${YELLOW}Agent management is out-of-scope for this parity pass. Use Windows for full Agent features.${RESET}" ;; 3) exit 0 ;; *) echo -e "${RED}Invalid selection. Exiting...${RESET}"; exit 1 ;; esac } # ---- Flag-driven auto-select (parity logic) ---- if [[ $SERVER_FLAG -eq 1 && $AGENT_FLAG -eq 1 ]]; then echo -e "${RED}Cannot use --server and --agent together.${RESET}"; exit 1 fi # Auto-select main menu option for Server when EngineProduction/EngineDev provided if [[ $ENGINE_PROD_FLAG -eq 1 || $ENGINE_DEV_FLAG -eq 1 ]]; then SERVER_FLAG=1 fi if [[ $SERVER_FLAG -eq 1 ]]; then if [[ $VITE_FLAG -eq 1 && $FLASK_FLAG -eq 1 ]]; then echo -e "${RED}Cannot combine --vite and --flask.${RESET}"; exit 1 fi if [[ $ENGINE_PROD_FLAG -eq 1 && $ENGINE_DEV_FLAG -eq 1 ]]; then echo -e "${RED}Cannot combine --EngineProduction and --EngineDev.${RESET}"; exit 1 fi if [[ $ENGINE_PROD_FLAG -eq 1 || $ENGINE_DEV_FLAG -eq 1 ]]; then # Map to menu choice automatically if [[ $ENGINE_PROD_FLAG -eq 1 ]]; then if [[ $QUICK_FLAG -eq 1 ]]; then server_menu <<< $'2\n' # skip build, immediate launch else server_menu <<< $'1\n' # build & launch fi exit $? fi if [[ $ENGINE_DEV_FLAG -eq 1 ]]; then server_menu <<< $'3\n' # Vite dev exit $? fi fi # Resolve server mode from flags (legacy compatibility) if [[ $VITE_FLAG -eq 1 ]]; then server_menu <<< $'3\n' elif [[ $FLASK_FLAG -eq 1 && $QUICK_FLAG -eq 1 ]]; then server_menu <<< $'2\n' else server_menu <<< $'1\n' fi exit $? fi # Default to interactive menu main_menu