- Replace fake-site footer with dark-web launch CTA; fix HubChatter inside AppProviders (client crash) - Add /launch, DEPLOY.md, onions:list|status|health, list-onion-urls, install-systemd, onion-status - start.sh CYBERLUX_PREPARE_ONLY; copy pass on satellite pages; Navbar launch link - README: operator cheat sheet, phone Tor note, systemd via install-systemd.sh Made-with: Cursor
255 lines
8.2 KiB
Bash
Executable File
255 lines
8.2 KiB
Bash
Executable File
#!/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
|