Harden onion boot flow and deepen site surfaces

Add persistent onion key backup and restore, improve startup resilience, and flesh out the major site verticals with richer navigation, search coverage, and operator documentation.

Made-with: Cursor
This commit is contained in:
drjones
2026-04-07 21:35:52 -07:00
parent 52432dccfa
commit 78a071ba02
162 changed files with 21692 additions and 39 deletions

248
start.sh Executable file
View File

@@ -0,0 +1,248 @@
#!/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
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
echo "[*] Starting Next.js on 127.0.0.1:3000 (Ctrl+C to stop)"
exec npm run start:onion