#!/usr/bin/env bash # CyberLux — single entry: regenerate onion config, deps, build, Tor/nginx, # self-heal onion key directories from backup, wait for hostname readiness, # print all .onion URLs, back them up, then start Next. # # ./start.sh # git pull && ./start.sh # # Env (optional): # CYBERLUX_SKIP_GIT_PULL=1 # CYBERLUX_SKIP_TOR=1 — local only → http://127.0.0.1:3000 # CYBERLUX_DEPLOY_SEED=n # CYBERLUX_RANDOM_SEED=1 # CYBERLUX_SKIP_ONION_BACKUP=1 # CYBERLUX_PREPARE_ONLY=1 — build + Tor/nginx install, then exit (no Next.js; use with systemd) set -euo pipefail REPO="$(cd "$(dirname "$0")" && pwd)" cd "${REPO}" if ! command -v node >/dev/null 2>&1; then echo "[!] node not found (required for scripts/generate-onion-config.cjs)." exit 1 fi echo "[*] Regenerating Tor/nginx/app maps from scripts/onion-nodes.json" node "${REPO}/scripts/generate-onion-config.cjs" TOR_DIRS_FILE="${REPO}/scripts/generated/tor-dirs.txt" LABELS_FILE="${REPO}/scripts/generated/onion-labels.tsv" RANGE_FILE="${REPO}/scripts/generated/onion-port-range.txt" RESTORE_SCRIPT="${REPO}/scripts/restore-onion-keys.sh" BACKUP_SCRIPT="${REPO}/scripts/backup-onion-keys.sh" if [[ ! -f "${TOR_DIRS_FILE}" ]]; then echo "[!] Missing ${TOR_DIRS_FILE} — generator failed." exit 1 fi declare -A CYBERLUX_ROLE=() if [[ -f "${LABELS_FILE}" ]]; then while IFS=$'\t' read -r dir desc || [[ -n "${dir}" ]]; do [[ -z "${dir}" ]] && continue CYBERLUX_ROLE["${dir}"]="${desc}" done < "${LABELS_FILE}" fi read -r ONION_PORT_MIN ONION_PORT_MAX < "${RANGE_FILE}" || true ONION_PORT_MIN="${ONION_PORT_MIN:-8080}" ONION_PORT_MAX="${ONION_PORT_MAX:-8122}" mapfile -t CYBERLUX_TOR_DIRS < "${TOR_DIRS_FILE}" count_ready_hostnames() { local ready=0 local dir for dir in "${CYBERLUX_TOR_DIRS[@]}"; do [[ -z "${dir}" ]] && continue if sudo test -s "/var/lib/tor/${dir}/hostname" 2>/dev/null; then ((ready++)) || true fi done echo "${ready}" } wait_for_all_hostnames() { local max_wait="${1:-90}" local waited=0 local total=0 local ready=0 total="${#CYBERLUX_TOR_DIRS[@]}" while (( waited < max_wait )); do ready="$(count_ready_hostnames)" if (( ready >= total )); then return 0 fi sleep 1 ((waited++)) || true done ready="$(count_ready_hostnames)" echo "[!] Timeout waiting for all onion hostnames (${ready}/${total} ready)." >&2 return 1 } print_onion_banner() { echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo " CYBERLUX — copy these .onion URLs (Tor Browser, http:// only)" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" local any=0 local ready=0 for dir in "${CYBERLUX_TOR_DIRS[@]}"; do [[ -z "${dir}" ]] && continue local f="/var/lib/tor/${dir}/hostname" local onion="" if onion=$(sudo cat "${f}" 2>/dev/null | tr -d '\n'); then any=1 ((ready++)) || true printf " %-26s %s\n" "${dir}" "http://${onion}" printf " %-26s %s\n" "" "${CYBERLUX_ROLE[$dir]:-}" echo "" else printf " %-26s %s\n" "${dir}" "(hostname not readable — sudo cat ${f})" echo "" fi done if [[ "${any}" -eq 0 ]]; then echo " [!] Could not read hostnames. Check: sudo systemctl status tor" echo "" fi echo " Hostnames ready: ${ready}/${#CYBERLUX_TOR_DIRS[@]}" echo "" echo " Loopback (do not bind publicly):" echo " Next.js http://127.0.0.1:3000" echo " nginx 127.0.0.1:${ONION_PORT_MIN}–${ONION_PORT_MAX} → Next (one port per .onion)" echo "" echo " Service status:" if command -v systemctl >/dev/null 2>&1; then if systemctl is-active --quiet tor@default.service 2>/dev/null || systemctl is-active --quiet tor 2>/dev/null; then echo " tor: active" else echo " tor: inactive or unknown" fi if systemctl is-active --quiet nginx 2>/dev/null; then echo " nginx: active" else echo " nginx: inactive or unknown" fi else echo " (systemctl not found)" fi echo "" echo " Onion key backup:" echo " /var/backups/cyberlux-onion-keys/current" echo "" echo " Firewall (optional): sudo bash ${REPO}/scripts/classroom-ufw.sh" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" } # --- Deploy seed (optional) --- if [[ -n "${CYBERLUX_DEPLOY_SEED:-}" ]]; then export NEXT_PUBLIC_DEPLOY_SEED="${CYBERLUX_DEPLOY_SEED}" echo "[*] NEXT_PUBLIC_DEPLOY_SEED=${NEXT_PUBLIC_DEPLOY_SEED} (from env)" elif [[ "${CYBERLUX_RANDOM_SEED:-}" == "1" ]]; then export NEXT_PUBLIC_DEPLOY_SEED=$((RANDOM % 10000)) echo "[*] NEXT_PUBLIC_DEPLOY_SEED=${NEXT_PUBLIC_DEPLOY_SEED} (random)" fi if [[ -d .git ]] && [[ "${CYBERLUX_SKIP_GIT_PULL:-}" != "1" ]]; then echo "[*] git pull --ff-only" if ! git pull --ff-only; then echo "[!] git pull failed (offline, dirty tree, or no upstream). Continuing — use CYBERLUX_SKIP_GIT_PULL=1 to skip this step." fi fi if ! command -v npm >/dev/null 2>&1; then echo "[!] npm not found. Install Node.js LTS." exit 1 fi echo "[*] npm install" npm install # Root-owned .next (e.g. after `sudo npm run build`) breaks `next build` — fix with sudo once. if [[ -d "${REPO}/.next" ]] && [[ ! -w "${REPO}/.next" ]]; then echo "[*] .next/ is not writable (often root-owned). Repairing ownership (sudo may ask for password)…" if command -v sudo >/dev/null 2>&1; then sudo chown -R "$(id -un):$(id -gn)" "${REPO}/.next" || { echo "[!] Could not chown .next. Run manually:" echo " sudo bash ${REPO}/scripts/fix-next-perms.sh" exit 1 } else echo "[!] No sudo — run as root once: chown -R \$(whoami) ${REPO}/.next" exit 1 fi fi echo "[*] npm run build" if ! npm run build; then echo "" echo "[!] Build failed. Common fix after a root-owned build:" echo " sudo bash ${REPO}/scripts/fix-next-perms.sh" echo " ./start.sh" exit 1 fi if command -v ss >/dev/null 2>&1; then if ss -ltn 2>/dev/null | grep -qE '127\.0\.0\.1:3000\b'; then echo "[!] Something is already listening on 127.0.0.1:3000 — stop it first." exit 1 fi elif command -v lsof >/dev/null 2>&1; then if lsof -iTCP:3000 -sTCP:LISTEN >/dev/null 2>&1; then echo "[!] Port 3000 is in use." exit 1 fi fi if [[ "${CYBERLUX_SKIP_TOR:-}" == "1" ]]; then echo "[*] CYBERLUX_SKIP_TOR=1 — skipping Tor/nginx. Open http://127.0.0.1:3000" echo "" else if ! command -v sudo >/dev/null 2>&1; then echo "[!] sudo required for Tor/nginx install." exit 1 fi bin_exists() { command -v "$1" >/dev/null 2>&1 || [[ -x "/usr/bin/$1" ]] || [[ -x "/usr/sbin/$1" ]] || [[ -x "/sbin/$1" ]] } for bin in tor nginx; do if ! bin_exists "$bin"; then echo "[!] Missing '${bin}'. On Debian/Ubuntu: sudo apt install tor nginx" exit 1 fi done if [[ ! -f "${REPO}/scripts/install-tor-onion.sh" ]]; then echo "[!] Missing install script." exit 1 fi if [[ -f "${RESTORE_SCRIPT}" ]]; then echo "[*] Restoring backed-up onion keys (missing dirs only)…" sudo bash "${RESTORE_SCRIPT}" fi echo "[*] Installing Tor + nginx (sudo)…" sudo env CYBERLUX_INSTALL_QUIET=1 bash "${REPO}/scripts/install-tor-onion.sh" echo "[*] Waiting for Tor hostname files…" wait_for_all_hostnames 90 || true if [[ "${CYBERLUX_SKIP_ONION_BACKUP:-}" != "1" ]] && [[ -f "${BACKUP_SCRIPT}" ]]; then echo "[*] Refreshing onion key backup set…" sudo bash "${BACKUP_SCRIPT}" || echo "[!] Onion key backup refresh failed." fi print_onion_banner fi if [[ "${CYBERLUX_PREPARE_ONLY:-}" == "1" ]]; then echo "[*] CYBERLUX_PREPARE_ONLY=1 — skipping Next.js start (use: systemctl start cyberlux.service)" exit 0 fi echo "[*] Starting Next.js on 127.0.0.1:3000 (Ctrl+C to stop)" exec npm run start:onion