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:
69
scripts/backup-onion-keys.sh
Normal file
69
scripts/backup-onion-keys.sh
Normal 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
21
scripts/classroom-ufw.sh
Executable 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
19
scripts/cyberlux.service
Normal 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
26
scripts/fix-next-perms.sh
Executable 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
301
scripts/generate-onion-config.cjs
Executable 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}`,
|
||||
);
|
||||
43
scripts/generated/onion-labels.tsv
Normal file
43
scripts/generated/onion-labels.tsv
Normal 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
scripts/generated/onion-port-range.txt
Normal file
1
scripts/generated/onion-port-range.txt
Normal file
@@ -0,0 +1 @@
|
||||
8080 8122
|
||||
43
scripts/generated/tor-dirs.txt
Normal file
43
scripts/generated/tor-dirs.txt
Normal 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
124
scripts/install-tor-onion.sh
Executable 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
47
scripts/onion-nodes.json
Normal 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" }
|
||||
]
|
||||
}
|
||||
68
scripts/prebuild-clean-next.cjs
Normal file
68
scripts/prebuild-clean-next.cjs
Normal 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;
|
||||
}
|
||||
50
scripts/restore-onion-keys.sh
Normal file
50
scripts/restore-onion-keys.sh
Normal 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
31
scripts/verify.cjs
Executable 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);
|
||||
}
|
||||
Reference in New Issue
Block a user