Tor ops: launch UI, onion list/health scripts, systemd installer, README glow-up
- 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
This commit is contained in:
@@ -1,19 +1,5 @@
|
||||
[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
|
||||
# Legacy template — the real unit is generated by:
|
||||
# sudo CYBERLUX_USER=<you> bash scripts/install-systemd.sh
|
||||
# which writes /etc/systemd/system/cyberlux.service
|
||||
#
|
||||
# See DEPLOY.md in the repo root.
|
||||
|
||||
94
scripts/health-check-stack.sh
Normal file
94
scripts/health-check-stack.sh
Normal file
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env bash
|
||||
# Verify Tor, nginx loopback vhosts, and Next.js are responding (local checks).
|
||||
# Usage: bash scripts/health-check-stack.sh
|
||||
# Exit 0 if stack looks healthy; non-zero if something is wrong.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
REPO="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
RANGE_FILE="${REPO}/scripts/generated/onion-port-range.txt"
|
||||
FAIL=0
|
||||
|
||||
if ! command -v curl >/dev/null 2>&1; then
|
||||
echo "[!] curl is required for HTTP checks. Install: apt install curl"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
http_code() {
|
||||
local url="$1"
|
||||
local out
|
||||
# stderr off — connection refused is expected when Next is down
|
||||
out=$(curl -g -sS -o /dev/null -w "%{http_code}" --connect-timeout 3 --max-time 8 "$url" 2>/dev/null) || out="000"
|
||||
[[ -z "${out}" ]] && out="000"
|
||||
echo "${out:0:3}"
|
||||
}
|
||||
|
||||
echo "[*] CyberLux stack health"
|
||||
echo ""
|
||||
|
||||
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: OK (active)"
|
||||
else
|
||||
echo " tor: FAIL (not active)"
|
||||
FAIL=1
|
||||
fi
|
||||
|
||||
if systemctl is-active --quiet nginx 2>/dev/null; then
|
||||
echo " nginx: OK (active)"
|
||||
else
|
||||
echo " nginx: FAIL (not active)"
|
||||
FAIL=1
|
||||
fi
|
||||
else
|
||||
echo " tor/nginx: SKIP (no systemctl — not Linux systemd?)"
|
||||
fi
|
||||
|
||||
code="$(http_code "http://127.0.0.1:3000/")"
|
||||
if [[ "${code}" =~ ^(200|304|302|301)$ ]]; then
|
||||
echo " Next.js: OK (127.0.0.1:3000 → HTTP ${code})"
|
||||
else
|
||||
echo " Next.js: FAIL (127.0.0.1:3000 → HTTP ${code})"
|
||||
FAIL=1
|
||||
fi
|
||||
|
||||
if [[ -f "${RANGE_FILE}" ]]; then
|
||||
read -r pmin pmax < "${RANGE_FILE}" || true
|
||||
hub_code="$(http_code "http://127.0.0.1:${pmin:-8080}/")"
|
||||
if [[ "${hub_code}" =~ ^(200|304|302|301)$ ]]; then
|
||||
echo " hub vhost: OK (127.0.0.1:${pmin:-8080} → HTTP ${hub_code})"
|
||||
else
|
||||
echo " hub vhost: FAIL (127.0.0.1:${pmin:-8080} → HTTP ${hub_code})"
|
||||
FAIL=1
|
||||
fi
|
||||
else
|
||||
echo " hub vhost: SKIP (no ${RANGE_FILE} — run npm run build or ./start.sh once)"
|
||||
fi
|
||||
|
||||
if [[ "${FAIL}" -ne 0 ]]; then
|
||||
echo ""
|
||||
echo " ▶ Next must be RUNNING while you run this check (second terminal)."
|
||||
echo " Terminal A: cd ${REPO} && npm run start:onion ← leave it open"
|
||||
echo " Terminal B: npm run health:stack"
|
||||
echo ""
|
||||
echo " Hint: HTTP 000 on :3000 = nothing listening (Next stopped or never started)."
|
||||
echo " nginx 502 on :8080 = nginx is up but upstream :3000 is dead."
|
||||
echo " Start Next (pick one):"
|
||||
echo " cd ${REPO} && npm run start:onion"
|
||||
echo " ./start.sh"
|
||||
echo " sudo systemctl start cyberlux.service # if you installed systemd"
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
if systemctl list-unit-files cyberlux.service &>/dev/null; then
|
||||
st="$(systemctl is-active cyberlux.service 2>/dev/null || echo unknown)"
|
||||
echo " cyberlux.service status: ${st}"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
if [[ "${FAIL}" -eq 0 ]]; then
|
||||
echo "✓ health-check-stack: OK"
|
||||
exit 0
|
||||
fi
|
||||
echo "✗ health-check-stack: one or more checks failed"
|
||||
exit 1
|
||||
125
scripts/install-systemd.sh
Normal file
125
scripts/install-systemd.sh
Normal file
@@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env bash
|
||||
# Install CyberLux as a systemd service (Next.js on 127.0.0.1:3000).
|
||||
# Tor + nginx must already be configured (run: sudo bash scripts/install-tor-onion.sh
|
||||
# after npm run build / ./start.sh has generated configs).
|
||||
#
|
||||
# Usage (from repo root):
|
||||
# sudo CYBERLUX_USER=drjones bash scripts/install-systemd.sh
|
||||
#
|
||||
# Optional env:
|
||||
# CYBERLUX_USER — Unix user to run the app (default: $SUDO_USER or first arg)
|
||||
# CYBERLUX_REPO — Absolute path to repo (default: parent of this script)
|
||||
# CYBERLUX_GROUP — Group (default: primary group of CYBERLUX_USER)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
[[ "${EUID}" -eq 0 ]] || { echo "Run as root: sudo bash $0"; exit 1; }
|
||||
|
||||
REPO_DEFAULT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
CYBERLUX_REPO="${CYBERLUX_REPO:-$REPO_DEFAULT}"
|
||||
CYBERLUX_USER="${CYBERLUX_USER:-${SUDO_USER:-}}"
|
||||
if [[ -z "${CYBERLUX_USER}" ]] && [[ -n "${1:-}" ]]; then
|
||||
CYBERLUX_USER="$1"
|
||||
fi
|
||||
if [[ -z "${CYBERLUX_USER}" ]]; then
|
||||
echo "Set CYBERLUX_USER or run: sudo bash $0 <username>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! id -u "${CYBERLUX_USER}" >/dev/null 2>&1; then
|
||||
echo "[!] Unix user '${CYBERLUX_USER}' does not exist. Create it first, e.g.:"
|
||||
echo " sudo useradd -r -m -s /bin/bash ${CYBERLUX_USER}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CYBERLUX_GROUP="${CYBERLUX_GROUP:-$(id -gn "${CYBERLUX_USER}")}"
|
||||
|
||||
if [[ ! -d "${CYBERLUX_REPO}" ]]; then
|
||||
echo "[!] CYBERLUX_REPO is not a directory: ${CYBERLUX_REPO}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
NODE_BIN="$(command -v node || true)"
|
||||
NPM_BIN="$(command -v npm || true)"
|
||||
if [[ -z "${NODE_BIN}" ]] || [[ -z "${NPM_BIN}" ]]; then
|
||||
echo "[!] node and npm must be on PATH for root when installing (or edit the unit)."
|
||||
echo " e.g. export PATH=/usr/bin:\$PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ENV_FILE="/etc/default/cyberlux"
|
||||
UNIT_DST="/etc/systemd/system/cyberlux.service"
|
||||
|
||||
# Minimal PATH so systemd finds node/npm when not using login shells
|
||||
cat > "${ENV_FILE}" <<EOF
|
||||
# CyberLux — sourced by cyberlux.service
|
||||
NODE_ENV=production
|
||||
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
EOF
|
||||
chmod 0644 "${ENV_FILE}"
|
||||
|
||||
cat > "${UNIT_DST}" <<EOF
|
||||
[Unit]
|
||||
Description=CyberLux Next.js (Tor onion backend, 127.0.0.1:3000)
|
||||
Documentation=man:systemd.service(5)
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
# Reverse proxy + hidden services must be up first
|
||||
After=tor.service tor@default.service nginx.service
|
||||
Wants=tor.service nginx.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=${CYBERLUX_USER}
|
||||
Group=${CYBERLUX_GROUP}
|
||||
WorkingDirectory=${CYBERLUX_REPO}
|
||||
EnvironmentFile=-${ENV_FILE}
|
||||
|
||||
# Regenerate onion route maps if onion-nodes.json changed (no network I/O)
|
||||
ExecStartPre=${NODE_BIN} ${CYBERLUX_REPO}/scripts/generate-onion-config.cjs
|
||||
|
||||
ExecStart=${NPM_BIN} run start:onion
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
# Avoid thrashing if Tor/nginx are still starting
|
||||
StartLimitIntervalSec=120
|
||||
StartLimitBurst=5
|
||||
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
LimitNOFILE=65535
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
chown root:root "${UNIT_DST}"
|
||||
chmod 0644 "${UNIT_DST}"
|
||||
|
||||
if [[ "${CYBERLUX_CHOWN_REPO:-}" == "1" ]]; then
|
||||
chown -R "${CYBERLUX_USER}:${CYBERLUX_GROUP}" "${CYBERLUX_REPO}"
|
||||
fi
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable cyberlux.service
|
||||
|
||||
# Best-effort: enable Tor + nginx at boot (unit names vary by distro)
|
||||
systemctl enable tor.service 2>/dev/null || systemctl enable tor@default.service 2>/dev/null || true
|
||||
systemctl enable nginx.service 2>/dev/null || true
|
||||
|
||||
echo ""
|
||||
echo "Installed: ${UNIT_DST}"
|
||||
echo "Env: ${ENV_FILE}"
|
||||
echo "User: ${CYBERLUX_USER}"
|
||||
echo ""
|
||||
echo "Enable boot order (recommended):"
|
||||
echo " systemctl enable tor.service nginx.service cyberlux.service"
|
||||
echo " # or: systemctl enable tor@default.service (distribution-dependent)"
|
||||
echo ""
|
||||
echo "Start now:"
|
||||
echo " systemctl start cyberlux.service"
|
||||
echo " systemctl status cyberlux.service"
|
||||
echo ""
|
||||
echo "Logs:"
|
||||
echo " journalctl -u cyberlux.service -f"
|
||||
echo ""
|
||||
47
scripts/list-onion-urls.sh
Executable file
47
scripts/list-onion-urls.sh
Executable file
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env bash
|
||||
# Print every CyberLux hidden-service URL (reads /var/lib/tor/*/hostname).
|
||||
# Tor creates these files — not Next.js. Use sudo if not readable as your user.
|
||||
#
|
||||
# bash scripts/list-onion-urls.sh
|
||||
# sudo bash scripts/list-onion-urls.sh
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
REPO="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
TOR_DIRS="${REPO}/scripts/generated/tor-dirs.txt"
|
||||
|
||||
if [[ ! -f "${TOR_DIRS}" ]]; then
|
||||
echo "Missing ${TOR_DIRS}. Run: cd ${REPO} && node scripts/generate-onion-config.cjs"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
read_host() {
|
||||
local f="$1"
|
||||
if [[ -r "${f}" ]]; then
|
||||
tr -d '\n' < "${f}"
|
||||
elif command -v sudo >/dev/null 2>&1 && sudo test -r "${f}" 2>/dev/null; then
|
||||
sudo tr -d '\n' < "${f}"
|
||||
else
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
echo ""
|
||||
echo "CyberLux .onion URLs (http:// only — open in Tor Browser)"
|
||||
echo "────────────────────────────────────────────────────────────"
|
||||
|
||||
while IFS= read -r d || [[ -n "${d}" ]]; do
|
||||
[[ -z "${d}" ]] && continue
|
||||
f="/var/lib/tor/${d}/hostname"
|
||||
host="$(read_host "${f}")"
|
||||
if [[ -n "${host}" ]]; then
|
||||
printf '%-28s http://%s\n' "${d}" "${host}"
|
||||
else
|
||||
printf '%-28s (no hostname yet — %s)\n' "${d}" "${f}"
|
||||
fi
|
||||
done < "${TOR_DIRS}"
|
||||
|
||||
echo "────────────────────────────────────────────────────────────"
|
||||
echo ""
|
||||
echo "If lines show (no hostname yet): wait for Tor, or sudo ls -la /var/lib/tor/"
|
||||
echo ""
|
||||
109
scripts/onion-status.cjs
Normal file
109
scripts/onion-status.cjs
Normal file
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Lists every Tor hidden-service hostname + nginx loopback URL, and checks
|
||||
* that each loopback port returns HTTP (proves nginx → Next are up for that vhost).
|
||||
*
|
||||
* node scripts/onion-status.cjs
|
||||
* npm run onions:status
|
||||
*
|
||||
* Reading /var/lib/tor/*/hostname usually requires sudo:
|
||||
* sudo node scripts/onion-status.cjs
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const fs = require("fs");
|
||||
const http = require("http");
|
||||
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"));
|
||||
|
||||
function readOnionHost(torDir) {
|
||||
const f = path.join("/var/lib/tor", torDir, "hostname");
|
||||
try {
|
||||
return fs.readFileSync(f, "utf8").trim();
|
||||
} catch (e) {
|
||||
if (e.code === "EACCES" || e.code === "EPERM") {
|
||||
return "(hostname file exists but not readable — try: sudo node scripts/onion-status.cjs)";
|
||||
}
|
||||
if (e.code === "ENOENT") {
|
||||
return "(no hostname yet — is Tor running?)";
|
||||
}
|
||||
return `(${e.message})`;
|
||||
}
|
||||
}
|
||||
|
||||
function checkLocalPort(port) {
|
||||
return new Promise((resolve) => {
|
||||
const req = http.request(
|
||||
{
|
||||
hostname: "127.0.0.1",
|
||||
port,
|
||||
path: "/",
|
||||
method: "GET",
|
||||
timeout: 6000,
|
||||
headers: { Connection: "close", Host: "127.0.0.1" },
|
||||
},
|
||||
(res) => {
|
||||
res.resume();
|
||||
resolve(res.statusCode || 0);
|
||||
},
|
||||
);
|
||||
req.on("error", () => resolve(0));
|
||||
req.on("timeout", () => {
|
||||
req.destroy();
|
||||
resolve(0);
|
||||
});
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const rows = [];
|
||||
for (const n of data.nodes) {
|
||||
const onion = readOnionHost(n.torDir);
|
||||
const httpCode = await checkLocalPort(n.port);
|
||||
const ok = httpCode >= 200 && httpCode < 500;
|
||||
rows.push({
|
||||
torDir: n.torDir,
|
||||
port: n.port,
|
||||
kind: n.kind,
|
||||
onion,
|
||||
httpCode,
|
||||
ok,
|
||||
});
|
||||
}
|
||||
|
||||
console.log("");
|
||||
console.log("CyberLux — onion URLs (from /var/lib/tor/*/hostname) + loopback health");
|
||||
console.log("─".repeat(100));
|
||||
let bad = 0;
|
||||
for (const r of rows) {
|
||||
const url =
|
||||
r.onion.startsWith("(") || r.onion.includes("not readable")
|
||||
? r.onion
|
||||
: `http://${r.onion}`;
|
||||
const local = `http://127.0.0.1:${r.port}/`;
|
||||
const status =
|
||||
r.httpCode === 0 ? "DOWN/timeout" : `HTTP ${r.httpCode}`;
|
||||
if (!r.ok) bad++;
|
||||
console.log(`${r.torDir.padEnd(28)} ${String(r.kind).padEnd(12)} ${String(r.port).padEnd(5)} ${status.padEnd(14)} ${url}`);
|
||||
console.log(`${"".padEnd(28)} ${"".padEnd(12)} ${"".padEnd(5)} loopback: ${local}`);
|
||||
console.log("");
|
||||
}
|
||||
console.log("─".repeat(100));
|
||||
if (bad > 0) {
|
||||
console.log(`✗ ${bad} loopback vhost(s) did not return OK HTTP — ensure: systemctl status nginx tor; systemctl status cyberlux`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log("✓ All loopback nginx vhosts responded (Tor .onion forwarding uses these ports).");
|
||||
console.log(" To test via the Tor network use Tor Browser (not curl from clearnet).");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(2);
|
||||
});
|
||||
@@ -19,8 +19,8 @@ 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",
|
||||
"bash syntax (start.sh, install, backup, restore, systemd, health)",
|
||||
"bash -n start.sh && bash -n scripts/install-tor-onion.sh && bash -n scripts/install-systemd.sh && bash -n scripts/health-check-stack.sh && bash -n scripts/list-onion-urls.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");
|
||||
|
||||
Reference in New Issue
Block a user