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

View File

@@ -0,0 +1,69 @@
#!/usr/bin/env bash
set -euo pipefail
[[ "${EUID}" -eq 0 ]] || {
echo "Run as root (sudo)."
exit 1
}
REPO="$(cd "$(dirname "$0")/.." && pwd)"
TOR_DIRS_FILE="${REPO}/scripts/generated/tor-dirs.txt"
BACKUP_ROOT="${CYBERLUX_ONION_BACKUP_ROOT:-/var/backups/cyberlux-onion-keys}"
CURRENT_DIR="${BACKUP_ROOT}/current"
SNAPSHOT_ROOT="${BACKUP_ROOT}/snapshots"
KEEP_COUNT="${CYBERLUX_ONION_BACKUP_KEEP:-10}"
if [[ ! -f "${TOR_DIRS_FILE}" ]]; then
echo "Missing ${TOR_DIRS_FILE}. Run the generator first."
exit 1
fi
mkdir -p "${BACKUP_ROOT}" "${SNAPSHOT_ROOT}"
chmod 0700 "${BACKUP_ROOT}" "${SNAPSHOT_ROOT}"
tmp_current="$(mktemp -d "${BACKUP_ROOT}/current.tmp.XXXXXX")"
tmp_snapshot="$(mktemp -d "${BACKUP_ROOT}/snapshot.tmp.XXXXXX")"
cleanup() {
rm -rf "${tmp_current}" "${tmp_snapshot}"
}
trap cleanup EXIT
saved=0
while IFS= read -r dir || [[ -n "${dir}" ]]; do
[[ -z "${dir}" ]] && continue
src="/var/lib/tor/${dir}"
[[ -d "${src}" ]] || continue
if [[ ! -s "${src}/hostname" ]] && [[ ! -f "${src}/hs_ed25519_secret_key" ]] && [[ ! -f "${src}/private_key" ]]; then
continue
fi
cp -a "${src}" "${tmp_current}/${dir}"
cp -a "${src}" "${tmp_snapshot}/${dir}"
((saved++)) || true
done < "${TOR_DIRS_FILE}"
if (( saved == 0 )); then
echo "[*] No onion key directories are ready to back up yet."
exit 0
fi
snapshot_dir="${SNAPSHOT_ROOT}/$(date +%Y%m%d-%H%M%S)"
mv "${tmp_snapshot}" "${snapshot_dir}"
rm -rf "${CURRENT_DIR}"
mv "${tmp_current}" "${CURRENT_DIR}"
trap - EXIT
chmod -R go-rwx "${BACKUP_ROOT}" || true
chown -R root:root "${BACKUP_ROOT}" || true
mapfile -t snapshots < <(ls -1dt "${SNAPSHOT_ROOT}"/* 2>/dev/null || true)
if (( ${#snapshots[@]} > KEEP_COUNT )); then
for old in "${snapshots[@]:KEEP_COUNT}"; do
rm -rf "${old}"
done
fi
echo "[*] Backed up ${saved} onion service directories."
echo " Current restore set: ${CURRENT_DIR}"

21
scripts/classroom-ufw.sh Executable file
View File

@@ -0,0 +1,21 @@
#!/bin/bash
# Optional firewall for a demo host: clearnet cannot reach Next/nginx loopback fronts.
# Run once with sudo after SSH access is confirmed. Re-read if your SSH port is not 22.
#
# sudo bash scripts/classroom-ufw.sh
#
set -euo pipefail
[[ "${EUID}" -eq 0 ]] || { echo "Run as root (sudo)."; exit 1; }
SSH_PORT="${SSH_PORT:-22}"
echo "This will enable UFW: default deny incoming, allow outgoing, allow TCP ${SSH_PORT} (SSH)."
echo "Tor hidden services use outbound circuits only; no inbound clearnet ports are opened for CyberLux."
read -r -p "Continue? [y/N] " ok || true
[[ "${ok:-}" =~ ^[Yy]$ ]] || { echo "Aborted."; exit 0; }
ufw default deny incoming
ufw default allow outgoing
ufw allow "${SSH_PORT}/tcp" comment "ssh"
ufw --force enable
ufw status verbose

19
scripts/cyberlux.service Normal file
View File

@@ -0,0 +1,19 @@
[Unit]
Description=CyberLux Next.js (binds 127.0.0.1:3000 for nginx/Tor)
After=network-online.target tor.service nginx.service
Wants=network-online.target tor.service nginx.service
[Service]
Type=simple
# Use the account that owns the repo and ran ./start.sh (not root).
# This unit only starts Next.js; Tor/nginx are expected to be configured already.
User=REPLACE_ME
Group=REPLACE_ME
WorkingDirectory=/path/to/cyberlux
Environment=NODE_ENV=production
ExecStart=/usr/bin/npm run start:onion
Restart=on-failure
RestartSec=8
[Install]
WantedBy=multi-user.target

26
scripts/fix-next-perms.sh Executable file
View File

@@ -0,0 +1,26 @@
#!/usr/bin/env bash
# Fix root-owned .next (e.g. after sudo npm run build). From repo root:
# sudo bash scripts/fix-next-perms.sh
#
# Optional: pass a different path as first arg (default: REPO/.next)
set -euo pipefail
[[ "${EUID}" -eq 0 ]] || { echo "Run with sudo."; exit 1; }
REPO="$(cd "$(dirname "$0")/.." && pwd)"
TARGET="${1:-$REPO/.next}"
# Under sudo, prefer the user who invoked sudo
if [[ -n "${SUDO_USER:-}" ]]; then
u="$SUDO_USER"
g="$(id -gn "$SUDO_USER" 2>/dev/null || echo "$SUDO_USER")"
elif [[ -n "${SUDO_UID:-}" ]]; then
u="$(id -nu "$SUDO_UID")"
g="$(id -gn "$SUDO_UID")"
else
echo "Could not determine target user. Run: sudo chown -R youruser:youruser $TARGET"
exit 1
fi
chown -R "$u:$g" "$TARGET"
echo "Ownership reset to $u:$g for $TARGET"

301
scripts/generate-onion-config.cjs Executable file
View File

@@ -0,0 +1,301 @@
#!/usr/bin/env node
/**
* Reads scripts/onion-nodes.json and writes:
* lib/onionRoutes.generated.ts
* tor/cyberlux-nodes.conf
* nginx/cyberlux-onion-servers.inc
* scripts/generated/tor-dirs.txt
* scripts/generated/onion-labels.tsv
* scripts/generated/onion-port-range.txt
*/
"use strict";
const fs = require("fs");
const path = require("path");
const REPO = path.join(__dirname, "..");
const jsonPath = path.join(__dirname, "onion-nodes.json");
const data = JSON.parse(fs.readFileSync(jsonPath, "utf8"));
const nodes = data.nodes;
function dedicatedNginx(port, entry) {
const h = JSON.stringify(entry);
return `
# ${entry} @ ${port}
server {
listen 127.0.0.1:${port};
server_name _;
include /etc/nginx/cyberlux-server-common.inc;
proxy_hide_header X-Powered-By;
client_max_body_size 25m;
location /checkout {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto http;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location /api/ {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto http;
}
location /forum {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto http;
}
location /exchange {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto http;
}
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto http;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header X-Cyberlux-Node ${h};
proxy_read_timeout 86400;
}
}
`;
}
function wikiNginx(port) {
return `
# wiki @ ${port}
server {
listen 127.0.0.1:${port};
server_name _;
include /etc/nginx/cyberlux-server-common.inc;
proxy_hide_header X-Powered-By;
client_max_body_size 25m;
location /checkout {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto http;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location /api/ {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto http;
}
location /forum {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto http;
}
location /exchange {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto http;
}
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto http;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header X-Cyberlux-Node "wiki";
proxy_read_timeout 86400;
}
}
`;
}
function wNginx(port) {
return `
# shadow nodes (/w/[slug]) @ ${port}
server {
listen 127.0.0.1:${port};
server_name _;
include /etc/nginx/cyberlux-server-common.inc;
proxy_hide_header X-Powered-By;
client_max_body_size 25m;
location /checkout {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto http;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location /api/ {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto http;
}
location /forum {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto http;
}
location /exchange {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto http;
}
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto http;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header X-Cyberlux-Node "w";
proxy_read_timeout 86400;
}
}
`;
}
function torBlock(dir, port) {
return `
HiddenServiceDir /var/lib/tor/${dir}
HiddenServicePort 80 127.0.0.1:${port}
HiddenServiceEnableIntroDoSDefense 1
HiddenServiceEnableIntroDoSRatePerSec 25
HiddenServiceEnableIntroDoSBurstPerSec 200`;
}
const ports = nodes.map((n) => n.port);
const minP = Math.min(...ports);
const maxP = Math.max(...ports);
const dedicated = {};
for (const n of nodes) {
if (n.kind === "dedicated") dedicated[n.entry] = n.path;
}
const uniqueEntries = ["hub", "wiki", "w", ...Object.keys(dedicated).sort()];
const drLines = Object.keys(dedicated)
.sort()
.map((k) => ` ${JSON.stringify(k)}: ${JSON.stringify(dedicated[k])},`)
.join("\n");
const ts = `// AUTO-GENERATED by scripts/generate-onion-config.cjs — do not edit
export const CYBERLUX_ENTRY_VALUES = [
${uniqueEntries.map((e) => ` ${JSON.stringify(e)},`).join("\n")}
] as const;
export type CyberluxEntry = (typeof CYBERLUX_ENTRY_VALUES)[number];
export const DEDICATED_ROOT = {
${drLines}
} as const;
`;
// fix typo "generon" -> "generate"
let nginxInc = "# CyberLux — generated onion vhosts (included from nginx.example.conf)\n";
let torConf =
"# CyberLux — generated v3 onion services (include from torrc)\n#\n# Tor ≥ 0.4.7: DoS lines follow each HiddenServiceDir/Port pair.\n";
for (const n of nodes) {
torConf += torBlock(n.torDir, n.port);
if (n.kind === "hub") continue;
if (n.kind === "dedicated") nginxInc += dedicatedNginx(n.port, n.entry);
else if (n.kind === "wiki") nginxInc += wikiNginx(n.port);
else if (n.kind === "w") nginxInc += wNginx(n.port);
}
const genDir = path.join(REPO, "scripts", "generated");
fs.mkdirSync(genDir, { recursive: true });
const torDirs = nodes.map((n) => n.torDir);
fs.writeFileSync(path.join(genDir, "tor-dirs.txt"), torDirs.join("\n") + "\n");
const labels = [];
for (const n of nodes) {
let desc = "";
if (n.kind === "hub") desc = "Hub — full storefront";
else if (n.kind === "wiki") desc = "Wiki — / → /hidden-wiki";
else if (n.kind === "w") desc = "Shadow nodes — / → /syndicate, /x → /w/x";
else desc = `${n.entry} — / → ${n.path}`;
labels.push(`${n.torDir}\t${desc}`);
}
fs.writeFileSync(path.join(genDir, "onion-labels.tsv"), labels.join("\n") + "\n");
fs.writeFileSync(path.join(genDir, "onion-port-range.txt"), `${minP} ${maxP}\n`);
fs.writeFileSync(path.join(REPO, "lib", "onionRoutes.generated.ts"), ts);
fs.writeFileSync(path.join(REPO, "tor", "cyberlux-nodes.conf"), torConf.trim() + "\n");
fs.writeFileSync(path.join(REPO, "nginx", "cyberlux-onion-servers.inc"), nginxInc.trim() + "\n");
console.log(
`[generate-onion-config] ${nodes.length} onions, nginx loopback ${minP}${maxP}, TS entries: ${uniqueEntries.length}`,
);

View File

@@ -0,0 +1,43 @@
cyberlux Hub — full storefront
cyberlux_forum forum — / → /forum
cyberlux_exchange exchange — / → /exchange
cyberlux_wiki Wiki — / → /hidden-wiki
cyberlux_market market — / → /market
cyberlux_barter barter — / → /barter
cyberlux_chatter chatter — / → /chatter
cyberlux_search search — / → /search
cyberlux_syndicate syndicate — / → /syndicate
cyberlux_arb_academy arb-academy — / → /arb-academy
cyberlux_reviews reviews — / → /reviews
cyberlux_trust trust — / → /trust
cyberlux_vault vault — / → /vault
cyberlux_messages messages — / → /messages
cyberlux_drop_box drop-box — / → /drop-box
cyberlux_easter_eggs easter-eggs — / → /easter-eggs
cyberlux_links links — / → /links
cyberlux_red_room red-room — / → /red-room
cyberlux_drops drops — / → /drops
cyberlux_inner_circle inner-circle — / → /inner-circle
cyberlux_comparison comparison — / → /comparison
cyberlux_testimonials testimonials — / → /testimonials
cyberlux_wallets wallets — / → /wallets
cyberlux_support support — / → /support
cyberlux_darknet_atlas darknet-atlas — / → /darknet-atlas
cyberlux_security_analysis security-analysis — / → /security-analysis
cyberlux_mixer mixer — / → /mixer
cyberlux_secret_layer secret-layer — / → /secret-layer
cyberlux_trees trees — / → /trees
cyberlux_presswire presswire — / → /presswire
cyberlux_awards awards — / → /awards
cyberlux_raffle raffle — / → /raffle
cyberlux_game game — / → /game
cyberlux_webring webring — / → /webring
cyberlux_conspiracies conspiracies — / → /conspiracies
cyberlux_sanctuary sanctuary — / → /sanctuary
cyberlux_dashboard dashboard — / → /dashboard
cyberlux_checkout checkout — / → /checkout
cyberlux_vendors vendors — / → /vendors
cyberlux_sign_in sign-in — / → /sign-in
cyberlux_sign_up sign-up — / → /sign-up
cyberlux_account account — / → /account
cyberlux_w Shadow nodes — / → /syndicate, /x → /w/x
1 cyberlux Hub — full storefront
2 cyberlux_forum forum — / → /forum
3 cyberlux_exchange exchange — / → /exchange
4 cyberlux_wiki Wiki — / → /hidden-wiki
5 cyberlux_market market — / → /market
6 cyberlux_barter barter — / → /barter
7 cyberlux_chatter chatter — / → /chatter
8 cyberlux_search search — / → /search
9 cyberlux_syndicate syndicate — / → /syndicate
10 cyberlux_arb_academy arb-academy — / → /arb-academy
11 cyberlux_reviews reviews — / → /reviews
12 cyberlux_trust trust — / → /trust
13 cyberlux_vault vault — / → /vault
14 cyberlux_messages messages — / → /messages
15 cyberlux_drop_box drop-box — / → /drop-box
16 cyberlux_easter_eggs easter-eggs — / → /easter-eggs
17 cyberlux_links links — / → /links
18 cyberlux_red_room red-room — / → /red-room
19 cyberlux_drops drops — / → /drops
20 cyberlux_inner_circle inner-circle — / → /inner-circle
21 cyberlux_comparison comparison — / → /comparison
22 cyberlux_testimonials testimonials — / → /testimonials
23 cyberlux_wallets wallets — / → /wallets
24 cyberlux_support support — / → /support
25 cyberlux_darknet_atlas darknet-atlas — / → /darknet-atlas
26 cyberlux_security_analysis security-analysis — / → /security-analysis
27 cyberlux_mixer mixer — / → /mixer
28 cyberlux_secret_layer secret-layer — / → /secret-layer
29 cyberlux_trees trees — / → /trees
30 cyberlux_presswire presswire — / → /presswire
31 cyberlux_awards awards — / → /awards
32 cyberlux_raffle raffle — / → /raffle
33 cyberlux_game game — / → /game
34 cyberlux_webring webring — / → /webring
35 cyberlux_conspiracies conspiracies — / → /conspiracies
36 cyberlux_sanctuary sanctuary — / → /sanctuary
37 cyberlux_dashboard dashboard — / → /dashboard
38 cyberlux_checkout checkout — / → /checkout
39 cyberlux_vendors vendors — / → /vendors
40 cyberlux_sign_in sign-in — / → /sign-in
41 cyberlux_sign_up sign-up — / → /sign-up
42 cyberlux_account account — / → /account
43 cyberlux_w Shadow nodes — / → /syndicate, /x → /w/x

View File

@@ -0,0 +1 @@
8080 8122

View File

@@ -0,0 +1,43 @@
cyberlux
cyberlux_forum
cyberlux_exchange
cyberlux_wiki
cyberlux_market
cyberlux_barter
cyberlux_chatter
cyberlux_search
cyberlux_syndicate
cyberlux_arb_academy
cyberlux_reviews
cyberlux_trust
cyberlux_vault
cyberlux_messages
cyberlux_drop_box
cyberlux_easter_eggs
cyberlux_links
cyberlux_red_room
cyberlux_drops
cyberlux_inner_circle
cyberlux_comparison
cyberlux_testimonials
cyberlux_wallets
cyberlux_support
cyberlux_darknet_atlas
cyberlux_security_analysis
cyberlux_mixer
cyberlux_secret_layer
cyberlux_trees
cyberlux_presswire
cyberlux_awards
cyberlux_raffle
cyberlux_game
cyberlux_webring
cyberlux_conspiracies
cyberlux_sanctuary
cyberlux_dashboard
cyberlux_checkout
cyberlux_vendors
cyberlux_sign_in
cyberlux_sign_up
cyberlux_account
cyberlux_w

124
scripts/install-tor-onion.sh Executable file
View File

@@ -0,0 +1,124 @@
#!/bin/bash
# Run with sudo: sudo bash scripts/install-tor-onion.sh
# Regenerates Tor + nginx from scripts/onion-nodes.json, installs configs only when
# they changed, restores backed-up onion keys if any service dirs are missing, and
# starts/restarts nginx + Tor only when required.
set -euo pipefail
[[ "${EUID}" -eq 0 ]] || { echo "Run as root (sudo)."; exit 1; }
REPO="$(cd "$(dirname "$0")/.." && pwd)"
TORRC="/etc/tor/torrc"
TOR_INCLUDE="/etc/tor/cyberlux-nodes.conf"
if ! command -v node >/dev/null 2>&1; then
echo "node is required to run scripts/generate-onion-config.cjs"
exit 1
fi
install_if_changed() {
local src="$1" dest="$2" mode="$3"
if [[ -f "${dest}" ]] && cmp -s "${src}" "${dest}"; then
return 1
fi
install -m "${mode}" "${src}" "${dest}"
return 0
}
tor_is_active() {
systemctl is-active --quiet tor@default.service 2>/dev/null || systemctl is-active --quiet tor 2>/dev/null
}
nginx_is_active() {
systemctl is-active --quiet nginx 2>/dev/null
}
restart_tor() {
systemctl restart tor@default.service 2>/dev/null || systemctl restart tor
}
start_tor() {
systemctl start tor@default.service 2>/dev/null || systemctl start tor
}
node "${REPO}/scripts/generate-onion-config.cjs"
mkdir -p /etc/tor /etc/nginx/sites-available /etc/nginx/sites-enabled
touch "${TORRC}"
tor_changed=0
nginx_changed=0
if install_if_changed "${REPO}/tor/cyberlux-nodes.conf" "${TOR_INCLUDE}" 0644; then
tor_changed=1
fi
if grep -qF '%include /etc/tor/cyberlux-onion.conf' "${TORRC}"; then
sed -i '\|%include /etc/tor/cyberlux-onion.conf|d' "${TORRC}"
tor_changed=1
fi
if ! grep -qF "${TOR_INCLUDE}" "${TORRC}"; then
printf '\n%%include %s\n' "${TOR_INCLUDE}" >> "${TORRC}"
tor_changed=1
fi
if install_if_changed "${REPO}/nginx/cyberlux-server-common.inc" /etc/nginx/cyberlux-server-common.inc 0644; then
nginx_changed=1
fi
if install_if_changed "${REPO}/nginx/cyberlux-onion-servers.inc" /etc/nginx/cyberlux-onion-servers.inc 0644; then
nginx_changed=1
fi
if install_if_changed "${REPO}/nginx.example.conf" /etc/nginx/sites-available/cyberlux-onion 0644; then
nginx_changed=1
fi
ln -sf /etc/nginx/sites-available/cyberlux-onion /etc/nginx/sites-enabled/cyberlux-onion
if [[ -x "${REPO}/scripts/restore-onion-keys.sh" ]]; then
bash "${REPO}/scripts/restore-onion-keys.sh"
fi
nginx -t
if command -v tor >/dev/null 2>&1; then
tor --verify-config -f "${TORRC}" >/dev/null
fi
if (( nginx_changed )); then
systemctl reload nginx 2>/dev/null || systemctl restart nginx
elif ! nginx_is_active; then
systemctl start nginx
fi
if (( tor_changed )); then
restart_tor
elif ! tor_is_active; then
start_tor
fi
if [[ "${CYBERLUX_INSTALL_QUIET:-}" == "1" ]]; then
exit 0
fi
echo ""
echo "CyberLux Tor nodes (see hostname files):"
while IFS= read -r dir || [[ -n "${dir}" ]]; do
[[ -z "${dir}" ]] && continue
f="/var/lib/tor/${dir}/hostname"
if [[ -f "$f" ]]; then
echo " ${dir}: http://$(tr -d '\n' < "$f")"
else
echo " ${dir}: (generating…) sudo cat $f"
fi
done < "${REPO}/scripts/generated/tor-dirs.txt"
echo ""
echo "Config status:"
echo " • Tor: $([[ ${tor_changed} -eq 1 ]] && echo changed || echo unchanged)"
echo " • nginx: $([[ ${nginx_changed} -eq 1 ]] && echo changed || echo unchanged)"
echo ""
echo "Host hardening:"
echo " • Next: npm run start:onion (127.0.0.1:3000)"
echo " • Do not expose 3000 or nginx loopback ports to the public internet."
echo " • Onion key backup set: /var/backups/cyberlux-onion-keys/current"
echo " • Optional: sudo bash ${REPO}/scripts/classroom-ufw.sh"
echo ""
echo "Start app: cd ${REPO} && ./start.sh or npm run build && npm run start:onion"

47
scripts/onion-nodes.json Normal file
View File

@@ -0,0 +1,47 @@
{
"nodes": [
{ "torDir": "cyberlux", "port": 8080, "kind": "hub" },
{ "torDir": "cyberlux_forum", "port": 8081, "kind": "dedicated", "entry": "forum", "path": "/forum" },
{ "torDir": "cyberlux_exchange", "port": 8082, "kind": "dedicated", "entry": "exchange", "path": "/exchange" },
{ "torDir": "cyberlux_wiki", "port": 8083, "kind": "wiki" },
{ "torDir": "cyberlux_market", "port": 8084, "kind": "dedicated", "entry": "market", "path": "/market" },
{ "torDir": "cyberlux_barter", "port": 8085, "kind": "dedicated", "entry": "barter", "path": "/barter" },
{ "torDir": "cyberlux_chatter", "port": 8086, "kind": "dedicated", "entry": "chatter", "path": "/chatter" },
{ "torDir": "cyberlux_search", "port": 8087, "kind": "dedicated", "entry": "search", "path": "/search" },
{ "torDir": "cyberlux_syndicate", "port": 8088, "kind": "dedicated", "entry": "syndicate", "path": "/syndicate" },
{ "torDir": "cyberlux_arb_academy", "port": 8089, "kind": "dedicated", "entry": "arb-academy", "path": "/arb-academy" },
{ "torDir": "cyberlux_reviews", "port": 8090, "kind": "dedicated", "entry": "reviews", "path": "/reviews" },
{ "torDir": "cyberlux_trust", "port": 8091, "kind": "dedicated", "entry": "trust", "path": "/trust" },
{ "torDir": "cyberlux_vault", "port": 8092, "kind": "dedicated", "entry": "vault", "path": "/vault" },
{ "torDir": "cyberlux_messages", "port": 8093, "kind": "dedicated", "entry": "messages", "path": "/messages" },
{ "torDir": "cyberlux_drop_box", "port": 8094, "kind": "dedicated", "entry": "drop-box", "path": "/drop-box" },
{ "torDir": "cyberlux_easter_eggs", "port": 8095, "kind": "dedicated", "entry": "easter-eggs", "path": "/easter-eggs" },
{ "torDir": "cyberlux_links", "port": 8096, "kind": "dedicated", "entry": "links", "path": "/links" },
{ "torDir": "cyberlux_red_room", "port": 8097, "kind": "dedicated", "entry": "red-room", "path": "/red-room" },
{ "torDir": "cyberlux_drops", "port": 8098, "kind": "dedicated", "entry": "drops", "path": "/drops" },
{ "torDir": "cyberlux_inner_circle", "port": 8099, "kind": "dedicated", "entry": "inner-circle", "path": "/inner-circle" },
{ "torDir": "cyberlux_comparison", "port": 8100, "kind": "dedicated", "entry": "comparison", "path": "/comparison" },
{ "torDir": "cyberlux_testimonials", "port": 8101, "kind": "dedicated", "entry": "testimonials", "path": "/testimonials" },
{ "torDir": "cyberlux_wallets", "port": 8102, "kind": "dedicated", "entry": "wallets", "path": "/wallets" },
{ "torDir": "cyberlux_support", "port": 8103, "kind": "dedicated", "entry": "support", "path": "/support" },
{ "torDir": "cyberlux_darknet_atlas", "port": 8104, "kind": "dedicated", "entry": "darknet-atlas", "path": "/darknet-atlas" },
{ "torDir": "cyberlux_security_analysis", "port": 8105, "kind": "dedicated", "entry": "security-analysis", "path": "/security-analysis" },
{ "torDir": "cyberlux_mixer", "port": 8106, "kind": "dedicated", "entry": "mixer", "path": "/mixer" },
{ "torDir": "cyberlux_secret_layer", "port": 8107, "kind": "dedicated", "entry": "secret-layer", "path": "/secret-layer" },
{ "torDir": "cyberlux_trees", "port": 8108, "kind": "dedicated", "entry": "trees", "path": "/trees" },
{ "torDir": "cyberlux_presswire", "port": 8109, "kind": "dedicated", "entry": "presswire", "path": "/presswire" },
{ "torDir": "cyberlux_awards", "port": 8110, "kind": "dedicated", "entry": "awards", "path": "/awards" },
{ "torDir": "cyberlux_raffle", "port": 8111, "kind": "dedicated", "entry": "raffle", "path": "/raffle" },
{ "torDir": "cyberlux_game", "port": 8112, "kind": "dedicated", "entry": "game", "path": "/game" },
{ "torDir": "cyberlux_webring", "port": 8113, "kind": "dedicated", "entry": "webring", "path": "/webring" },
{ "torDir": "cyberlux_conspiracies", "port": 8114, "kind": "dedicated", "entry": "conspiracies", "path": "/conspiracies" },
{ "torDir": "cyberlux_sanctuary", "port": 8115, "kind": "dedicated", "entry": "sanctuary", "path": "/sanctuary" },
{ "torDir": "cyberlux_dashboard", "port": 8116, "kind": "dedicated", "entry": "dashboard", "path": "/dashboard" },
{ "torDir": "cyberlux_checkout", "port": 8117, "kind": "dedicated", "entry": "checkout", "path": "/checkout" },
{ "torDir": "cyberlux_vendors", "port": 8118, "kind": "dedicated", "entry": "vendors", "path": "/vendors" },
{ "torDir": "cyberlux_sign_in", "port": 8119, "kind": "dedicated", "entry": "sign-in", "path": "/sign-in" },
{ "torDir": "cyberlux_sign_up", "port": 8120, "kind": "dedicated", "entry": "sign-up", "path": "/sign-up" },
{ "torDir": "cyberlux_account", "port": 8121, "kind": "dedicated", "entry": "account", "path": "/account" },
{ "torDir": "cyberlux_w", "port": 8122, "kind": "w" }
]
}

View File

@@ -0,0 +1,68 @@
#!/usr/bin/env node
/**
* Runs before `next build`. Ensures `.next/` is writable and removes stale diagnostics.
*
* `build-diagnostics.json` only holds build metadata (stage, options) — failures are almost
* always EACCES from root-owned `.next/` after `sudo npm run build`.
*
* Fix: sudo bash scripts/fix-next-perms.sh
* or: sudo chown -R "$(whoami)" .next
* or: sudo rm -rf .next
*/
"use strict";
const fs = require("fs");
const path = require("path");
const repoRoot = path.join(__dirname, "..");
const nextDir = path.join(repoRoot, ".next");
const diagnostics = path.join(nextDir, "diagnostics");
const diagFile = path.join(diagnostics, "build-diagnostics.json");
function die(msg) {
console.error(msg);
process.exit(1);
}
if (fs.existsSync(nextDir)) {
try {
fs.accessSync(nextDir, fs.constants.W_OK);
} catch {
die(`
[!] ${nextDir}
is not writable (often root-owned). Next.js cannot update .next/diagnostics/build-diagnostics.json.
Fix (one of):
sudo chown -R "$(whoami)" "${nextDir}"
sudo bash scripts/fix-next-perms.sh
sudo rm -rf "${nextDir}"
`);
}
}
if (fs.existsSync(diagFile)) {
try {
fs.accessSync(diagFile, fs.constants.W_OK);
} catch {
die(`
[!] ${diagFile}
is not writable. Same fix as above — give your user ownership of .next/
sudo chown -R "$(whoami)" "${nextDir}"
`);
}
}
try {
fs.rmSync(diagnostics, { recursive: true, force: true });
} catch (e) {
if (e && (e.code === "EACCES" || e.code === "EPERM")) {
die(`
[!] Cannot remove ${diagnostics}
${e.message}
sudo chown -R "$(whoami)" "${nextDir}"
`);
}
if (e && e.code !== "ENOENT") throw e;
}

View File

@@ -0,0 +1,50 @@
#!/usr/bin/env bash
set -euo pipefail
[[ "${EUID}" -eq 0 ]] || {
echo "Run as root (sudo)."
exit 1
}
REPO="$(cd "$(dirname "$0")/.." && pwd)"
TOR_DIRS_FILE="${REPO}/scripts/generated/tor-dirs.txt"
BACKUP_ROOT="${CYBERLUX_ONION_BACKUP_ROOT:-/var/backups/cyberlux-onion-keys}"
CURRENT_DIR="${BACKUP_ROOT}/current"
if [[ ! -f "${TOR_DIRS_FILE}" ]]; then
echo "Missing ${TOR_DIRS_FILE}. Run the generator first."
exit 1
fi
if [[ ! -d "${CURRENT_DIR}" ]]; then
echo "[*] No onion key backup found at ${CURRENT_DIR}; nothing to restore."
exit 0
fi
mkdir -p /var/lib/tor
restored=0
timestamp="$(date +%s)"
while IFS= read -r dir || [[ -n "${dir}" ]]; do
[[ -z "${dir}" ]] && continue
src="${CURRENT_DIR}/${dir}"
dst="/var/lib/tor/${dir}"
[[ -d "${src}" ]] || continue
if [[ -d "${dst}" ]] && [[ -s "${dst}/hostname" ]]; then
continue
fi
if [[ -e "${dst}" ]]; then
mv "${dst}" "${dst}.stale.${timestamp}"
fi
cp -a "${src}" "${dst}"
((restored++)) || true
done < "${TOR_DIRS_FILE}"
if (( restored == 0 )); then
echo "[*] Onion key restore check complete; nothing was missing."
else
echo "[*] Restored ${restored} onion service directories from backup."
fi

31
scripts/verify.cjs Executable file
View File

@@ -0,0 +1,31 @@
#!/usr/bin/env node
/**
* Local verification: generator, TypeScript, shell scripts, production build.
* Usage: npm run verify or node scripts/verify.cjs
*/
"use strict";
const { execSync } = require("node:child_process");
const path = require("node:path");
process.chdir(path.join(__dirname, ".."));
function run(label, cmd) {
console.log(`\n━━ ${label} ━━`);
execSync(cmd, { stdio: "inherit", shell: "/bin/bash" });
}
try {
run("onion config generator", "node scripts/generate-onion-config.cjs");
run("TypeScript (tsc --noEmit)", "npx tsc --noEmit");
run(
"bash syntax (start.sh, install, backup, restore)",
"bash -n start.sh && bash -n scripts/install-tor-onion.sh && bash -n scripts/backup-onion-keys.sh && bash -n scripts/restore-onion-keys.sh",
);
run("Next.js production build", "npm run build");
console.log("\n✓ verify: all checks passed.\n");
} catch {
console.error("\n✗ verify failed. If build hit EACCES on .next/, run:");
console.error(" sudo bash scripts/fix-next-perms.sh\n");
process.exit(1);
}