#!/usr/bin/env bash # ═══════════════════════════════════════════════════════════ # Haven: Cross-Platform Launcher (Linux / macOS) # Usage: chmod +x start.sh && ./start.sh # ═══════════════════════════════════════════════════════════ set -e GREEN='\033[0;32m' YELLOW='\033[1;33m' RED='\033[0;31m' NC='\033[0m' BOLD='\033[1m' DIR="$(cd "$(dirname "$0")" && pwd)" cd "$DIR" # ── Data directory (~/.haven) ────────────────────────────── HAVEN_DATA="${HAVEN_DATA_DIR:-$HOME/.haven}" mkdir -p "$HAVEN_DATA" echo "" echo -e "${GREEN}${BOLD} ========================================${NC}" echo -e "${GREEN}${BOLD} HAVEN: Private Chat Server${NC}" echo -e "${GREEN}${BOLD} ========================================${NC}" echo "" # ── Check Node.js ────────────────────────────────────────── if ! command -v node &> /dev/null; then echo -e "${RED} [ERROR] Node.js is not installed.${NC}" echo " Haven needs Node.js 22 or newer. Install it from https://nodejs.org or:" echo " Ubuntu/Debian: curl -fsSL https://deb.nodesource.com/setup_26.x | sudo -E bash -" echo " sudo apt install nodejs" echo " macOS (brew): brew install node" echo " Fedora: sudo dnf install nodejs" echo " Arch: sudo pacman -S nodejs npm" echo " (Plain 'apt install nodejs' gives Node 18 on Ubuntu 24.04, which is too old.)" exit 1 fi NODE_VER=$(node -v | sed 's/v//' | cut -d. -f1) echo " [✓] Node.js $(node -v) detected" if [ "$NODE_VER" -lt 22 ]; then echo -e "${RED} [ERROR] Haven needs Node.js 22 or newer. You have $(node -v).${NC}" echo " Install a current version from https://nodejs.org or NodeSource:" echo " Ubuntu/Debian: curl -fsSL https://deb.nodesource.com/setup_26.x | sudo -E bash -" echo " sudo apt install nodejs" exit 1 fi if [ "$NODE_VER" -ge 27 ]; then echo -e "${YELLOW} [!] Node.js v${NODE_VER} detected. Haven is tested on Node 22-26.${NC}" echo " Continuing. Native modules are verified functionally below." elif [ "$NODE_VER" -ge 24 ]; then echo " [*] Node.js v${NODE_VER}: verifying native modules load (below)." fi # ── Install dependencies ─────────────────────────────────── # Also after an update that changed them: npm records what it installed in # node_modules/.package-lock.json, so a newer package-lock.json means new or # changed dependencies to fetch. if [ ! -d "node_modules" ]; then echo " [*] First run: installing dependencies..." npm install echo "" elif [ package-lock.json -nt node_modules/.package-lock.json ]; then echo " [*] Dependencies changed, updating..." npm install echo "" fi # ── Functional gate: the version number matters less than whether the # native module actually loads on THIS node. Same check the Windows # launcher uses. A load failure gets one rebuild attempt first. if ! node -e "require('better-sqlite3')" &> /dev/null; then echo -e "${YELLOW} [!] better-sqlite3 failed to load on Node $(node -v), rebuilding...${NC}" npm rebuild better-sqlite3 || true if ! node -e "require('better-sqlite3')" &> /dev/null; then echo -e "${RED} [ERROR] better-sqlite3 cannot load on Node $(node -v).${NC}" echo " Fix options:" echo " - Install Node 26 LTS (https://nodejs.org/), or" echo " - Install C++ build tools and re-run:" echo " Ubuntu/Debian: sudo apt install build-essential python3" echo " Fedora: sudo dnf group install 'Development Tools'" echo " then: npm rebuild better-sqlite3" exit 1 fi echo " [✓] Native modules rebuilt successfully" fi echo " [✓] Native modules OK" # ── Create .env in data directory if missing ─────────────── if [ ! -f "$HAVEN_DATA/.env" ]; then if [ -f ".env.example" ]; then cp .env.example "$HAVEN_DATA/.env" echo -e "${YELLOW} [!] Created .env in $HAVEN_DATA. Edit it before going live!${NC}" else echo -e "${YELLOW} [!] No .env file found. Server will use defaults.${NC}" fi fi # ── Generate SSL certs in data directory if missing (skip if FORCE_HTTP=true) ── if [ "${FORCE_HTTP:-false}" = "true" ]; then echo " [*] FORCE_HTTP=true, skipping SSL certificate generation" elif [ ! -f "$HAVEN_DATA/certs/cert.pem" ]; then echo " [*] Generating self-signed SSL certificate..." # Made with Node (scripts/gen-cert.js), the same as Start Haven.bat, so # OpenSSL is not needed and its progress output stays off the screen. HAVEN_DATA_DIR="$HAVEN_DATA" node scripts/gen-cert.js || true if [ -f "$HAVEN_DATA/certs/cert.pem" ]; then echo " [✓] SSL cert generated" else echo -e "${RED} [!] SSL certificate generation failed. See the output above.${NC}" echo " Haven will run in HTTP mode." fi echo "" fi # ── Read PORT from .env (default 3000) ───────────────────── HAVEN_PORT="${PORT:-3000}" if [ -f "$HAVEN_DATA/.env" ]; then ENV_PORT=$(grep -E '^PORT=' "$HAVEN_DATA/.env" 2>/dev/null | head -1 | cut -d= -f2) if [ -n "$ENV_PORT" ]; then HAVEN_PORT="$ENV_PORT" fi fi echo " [*] Data directory: $HAVEN_DATA" echo " [*] Starting Haven server..." echo "" # ── Start server ─────────────────────────────────────────── node server.js & SERVER_PID=$! # ── Wait for readiness ───────────────────────────────────── # The probe runs on node itself: it is guaranteed present (it runs the # server), unlike curl/wget which minimal server images often lack. The # old curl-only probe silently failed on such systems every second for # 15s and then KILLED a perfectly healthy server (the frontend was # already serving) with a misleading "failed to start". # # Two rules now: # 1. A dead server process is the only real startup failure, detected # immediately, with a pointer at the real log. # 2. A probe failure alone NEVER kills a live server. If readiness # can't be confirmed after the window, we say so and leave it up. probe_url() { node -e 'const u=process.argv[1];const m=u.indexOf("https")===0?require("https"):require("http");const q=m.get(u,{rejectUnauthorized:false,timeout:1500},r=>{r.resume();process.exit(0)});q.on("error",()=>process.exit(1));q.on("timeout",()=>{q.destroy();process.exit(1)});' "$1" 2>/dev/null } PROBE_OK=0 for i in $(seq 1 30); do sleep 1 if ! kill -0 "$SERVER_PID" 2>/dev/null; then echo "" echo -e "${RED} [ERROR] The server process exited during startup.${NC}" echo " The real reason is printed above, or in: $HAVEN_DATA/crash.log" echo " Common causes: port ${HAVEN_PORT} already in use (another Haven still" echo " running?), a broken install, or a bad .env value." exit 1 fi if probe_url "https://localhost:${HAVEN_PORT}/api/health" || \ probe_url "http://localhost:${HAVEN_PORT}/api/health"; then PROBE_OK=1 break fi done if [ "$PROBE_OK" -ne 1 ]; then echo -e "${YELLOW} [!] Could not confirm readiness on port ${HAVEN_PORT} after 30s,${NC}" echo -e "${YELLOW} but the server process is running, so leaving it up.${NC}" echo " If it never becomes reachable, check $HAVEN_DATA/crash.log," echo " the PORT in $HAVEN_DATA/.env, and any firewall on ${HAVEN_PORT}." fi PORT=${HAVEN_PORT} echo -e "${GREEN}${BOLD} ========================================${NC}" echo -e "${GREEN}${BOLD} Haven is LIVE on port ${PORT}${NC}" echo -e "${GREEN}${BOLD} ========================================${NC}" echo "" echo " Local: https://localhost:${PORT}" echo " LAN: https://YOUR_LOCAL_IP:${PORT}" echo " Remote: https://YOUR_PUBLIC_IP:${PORT}" echo "" echo " First time? Browser will show a certificate warning." echo " Click 'Advanced' → 'Proceed' (self-signed cert)." echo "" # ── Open browser (platform-specific) ────────────────────── if command -v xdg-open &> /dev/null; then xdg-open "https://localhost:${PORT}" 2>/dev/null & elif command -v open &> /dev/null; then open "https://localhost:${PORT}" 2>/dev/null & fi echo " Press Ctrl+C to stop the server." echo "" # Keep alive: clean shutdown on Ctrl+C trap "echo ''; echo ' Shutting down Haven...'; kill $SERVER_PID 2>/dev/null; exit 0" INT TERM wait $SERVER_PID