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:
drjones
2026-04-15 22:12:44 -07:00
parent 1bbc761f58
commit e0a4a20862
30 changed files with 664 additions and 109 deletions

91
DEPLOY.md Normal file
View File

@@ -0,0 +1,91 @@
# CyberLux — production on Tor (.onion)
This app is designed to run **behind nginx on loopback**, with **one Tor v3 hidden service per logical site** (hub, wiki, dedicated verticals, shadow `/w` node). Nginx listens on `127.0.0.1:80808122` (see `scripts/onion-nodes.json`); Tor forwards port 80 on each onion to the matching loopback port. **Next.js binds only `127.0.0.1:3000`** — never expose 3000 or the nginx loopback ports to the public internet.
Onion hostnames are **created locally** when Tor first starts; there are no fixed `.onion` URLs in the repo. Back up `/var/lib/tor/*/hs_ed25519_secret_key` (the install flow uses `scripts/backup-onion-keys.sh`).
## One-time server setup (Debian/Ubuntu-style)
```bash
sudo apt update
sudo apt install -y tor nginx curl nodejs npm build-essential
# Or install Node.js LTS from NodeSource / nvm — `node` and `npm` must be on PATH.
```
## Deploy the app
From the repo root (as the user that will own the process):
```bash
./start.sh
```
This will: regenerate Tor/nginx maps from `scripts/onion-nodes.json`, `npm install`, `npm run build`, install Tor+nginx configs (`sudo`), wait for hostname files, print every `.onion` URL, then **foreground** `next start` on `127.0.0.1:3000`.
For a **one-shot prepare** (build + Tor/nginx, no Next.js — for systemd):
```bash
CYBERLUX_PREPARE_ONLY=1 ./start.sh
```
## systemd — start on boot
1. Ensure `./start.sh` or `CYBERLUX_PREPARE_ONLY=1 ./start.sh` has been run at least once so `.next` exists and Tor directories are populated.
2. Install the unit (run as root; set user to the account that owns the repo):
```bash
sudo CYBERLUX_USER=youruser bash scripts/install-systemd.sh
# optional: sudo CYBERLUX_CHOWN_REPO=1 CYBERLUX_USER=youruser bash scripts/install-systemd.sh
```
3. Enable **Tor**, **nginx**, and **CyberLux** at boot:
```bash
sudo systemctl enable tor.service nginx.service cyberlux.service
# if your distro uses tor@default instead of tor:
# sudo systemctl enable tor@default.service nginx.service cyberlux.service
sudo systemctl start tor.service nginx.service cyberlux.service
```
4. Check logs:
```bash
journalctl -u cyberlux.service -f
```
5. Health check (local):
```bash
npm run health:stack
```
6. **List every `.onion` URL** and **check that each nginx loopback vhost answers** (needs Tor running; use `sudo` if hostname files are root-only):
```bash
npm run onions:status
# or:
sudo node scripts/onion-status.cjs
```
## Changing the onion map
1. Edit `scripts/onion-nodes.json`.
2. Run `node scripts/generate-onion-config.cjs` (or `npm run build`, which runs it in `prebuild`).
3. `sudo bash scripts/install-tor-onion.sh`
4. Rebuild/restart the app: `npm run build` and `sudo systemctl restart cyberlux.service`
## Verification
```bash
npm run verify
```
## Security & compliance
- You are responsible for **local law**, hosting terms, and **Tor / relay policies**. This repo is a **parody web app**; treat operational security seriously if you run it on a real server.
## Security notes
- Only **Tor** should be reachable from outside; bind **nothing** to `0.0.0.0` for this stack.
- Prefer firewall defaults that deny incoming except what you need for SSH.
- Optional: `sudo bash scripts/classroom-ufw.sh` (if present) for a restrictive UFW profile.

View File

@@ -1,10 +1,25 @@
# CYBERLUX # CYBERLUX
> Neon storefront. Forty-three onion doors. One build. One app. No dead air. > One Next build. Dozens of v3 onions. Nginx in the middle. Tor does the rest.
CyberLux is a layered Next.js fiction stack built for dark-web literacy, classroom demos, mirror discipline drills, and absurdly overbuilt cyberpunk presentation. It runs one app behind Tor + nginx, then fans that app out into dedicated v3 hidden services for the hub, forum, exchange, market, crawler, syndicate, wiki layer, and every other top-level surface in `scripts/onion-nodes.json`. CyberLux is a **single** Next.js app projected through **nginx loopbacks** into **one Tor hidden service per vertical** hub, forum, exchange, market, wiki, syndicate, shadow `/w` nodes, and the rest of `scripts/onion-nodes.json`. Same codebase, same deploy, **different `.onion` front doors** with host-aware rewrites (`proxy.ts` + `X-Cyberlux-Node`).
This repo is built to feel like a real underground property while still being a controlled demo environment: **Not a toy route list:** stable `HiddenServiceDir` names, backup/restore for onion keys, generated Tor + nginx from one JSON source of truth. Built to **boot, survive operator mistakes, and stay readable under stress**.
Operator cheat sheet:
| Command | What |
|--------|------|
| `./start.sh` | Full pipeline: generate → build → Tor/nginx → print **every `.onion` URL**`next start` on `127.0.0.1:3000` |
| `npm run onions:list` | Print all `http://….onion` URLs from `/var/lib/tor/*/hostname` (use `sudo` if needed) |
| `npm run health:stack` | **Terminal A:** keep `npm run start:onion` running · **Terminal B:** curl Tor/nginx/Next loopbacks |
| `npm run onions:status` | URLs + HTTP probe each nginx vhost |
| `sudo bash scripts/install-systemd.sh` | Install `cyberlux.service` for boot-time Next |
| See **`DEPLOY.md`** | Firewall posture, compliance reminder, full systemd notes |
**Tor Browser on phone:** use **Onion Browser** (iOS) or **Tor Browser for Android** — Safari/Chrome will **never** resolve `.onion`. Paste the full `http://` + 56-char host + `.onion`; cellular networks sometimes block Tor (try WiFi).
This repo ships:
- `43` Tor v3 services - `43` Tor v3 services
- stable loopback range `127.0.0.1:80808122` - stable loopback range `127.0.0.1:80808122`
@@ -164,29 +179,19 @@ npm run build
npm run start:onion npm run start:onion
``` ```
## Boot At Startup ## Boot at startup (systemd)
`scripts/cyberlux.service` is included as a systemd template for the Next process. Generate the real unit with your Unix user and repo path (dont hand-edit placeholders):
Before installing it, set:
- `User=`
- `Group=`
- `WorkingDirectory=`
Then install:
```bash ```bash
sudo install -m 0644 scripts/cyberlux.service /etc/systemd/system/cyberlux.service sudo CYBERLUX_USER=$USER bash scripts/install-systemd.sh
sudo systemctl daemon-reload sudo systemctl enable --now tor.service nginx.service cyberlux.service
sudo systemctl enable --now cyberlux.service # use `tor@default.service` instead of `tor.service` if your distro names it that way
``` ```
Notes: The installer writes `/etc/systemd/system/cyberlux.service` and `/etc/default/cyberlux`. Legacy template notes live in `scripts/cyberlux.service` (prefer the generator).
- Tor and nginx should already be installed and enabled Tor + nginx must already be configured (`./start.sh` or `sudo bash scripts/install-tor-onion.sh` at least once).
- the service unit keeps the Next process alive
- Tor/nginx config is still managed by the repo scripts
## Verification ## Verification
@@ -259,14 +264,12 @@ There are additional dedicated onions for many top-level routes beyond those.
- Tor hidden services - Tor hidden services
- generated route / host mapping - generated route / host mapping
## Final Word ## Final word
CyberLux is supposed to boot like a machine that knows what it is: If youre running this, youre past LARP. The stack is designed to:
- same onion doors - keep **onion identities** across rebuilds when you respect `torDir` names and backups
- same identities - expose **only** what Tor publishes — not your loopback ports to the raw internet
- no shallow mirrors - fail **loud** in verification (`npm run verify`) instead of silently rotting
- no fake “self healing” copy without actual restore logic
- no dead routes pretending to be part of the network
If it starts, it should start hard. Read **`DEPLOY.md`** before you point real people at it. **You** own jurisdiction, opsec, and what you ship.

View File

@@ -236,7 +236,7 @@ export default function ComparisonPage() {
</div> </div>
</div> </div>
<div className="mt-12 border-t border-gray-800 pt-8 text-center text-sm text-gray-500"> <div className="mt-12 border-t border-gray-800 pt-8 text-center text-sm text-gray-500">
<p>© 2026 Marketplace Comparison · Delayed metrics not real-time trading advice</p> <p>© 2026 Marketplace Comparison · Delayed metrics informational only</p>
</div> </div>
</div> </div>
</footer> </footer>

63
app/launch/page.tsx Normal file
View File

@@ -0,0 +1,63 @@
import Link from "next/link";
import Navbar from "@/components/Navbar";
import ParticleBackground from "@/components/ParticleBackground";
export default function LaunchPage() {
return (
<>
<ParticleBackground />
<Navbar />
<main className="relative z-10 mx-auto max-w-3xl px-4 pb-32 pt-28">
<p className="font-mono text-[10px] uppercase tracking-[0.35em] text-neon-cyan/80">deployment</p>
<h1 className="mt-2 font-orbitron text-4xl font-black text-white md:text-5xl">Launch on the dark web</h1>
<p className="mt-4 text-foreground/75">
Ship this stack as Tor v3 hidden services: nginx on loopback ports, Next.js on{" "}
<code className="rounded bg-white/10 px-1.5 py-0.5 text-neon-green">127.0.0.1:3000</code>, one onion per vertical.
Repo ships generators and systemd hooks you bring the box and opsec.
</p>
<section className="mt-12 space-y-6 rounded-2xl border border-white/10 bg-black/40 p-8 backdrop-blur">
<h2 className="font-orbitron text-lg font-bold text-neon-purple">1. Build & wire Tor + nginx</h2>
<pre className="overflow-x-auto rounded-xl bg-black/60 p-4 text-xs leading-relaxed text-neon-cyan/90">
{`cd ~/cyberlux
./start.sh
# or prepare only, then systemd:
# CYBERLUX_PREPARE_ONLY=1 ./start.sh
# sudo CYBERLUX_USER=$USER bash scripts/install-systemd.sh`}
</pre>
</section>
<section className="mt-8 space-y-4 rounded-2xl border border-white/10 bg-black/40 p-8 backdrop-blur">
<h2 className="font-orbitron text-lg font-bold text-neon-purple">2. Run the app</h2>
<p className="text-sm text-foreground/70">
Foreground: <code className="text-neon-green">npm run start:onion</code> · Boot:{" "}
<code className="text-neon-green">sudo systemctl start cyberlux.service</code>
</p>
</section>
<section className="mt-8 space-y-4 rounded-2xl border border-white/10 bg-black/40 p-8 backdrop-blur">
<h2 className="font-orbitron text-lg font-bold text-neon-purple">3. Verify every onion & loopback</h2>
<pre className="overflow-x-auto rounded-xl bg-black/60 p-4 text-xs text-neon-cyan/90">
{`npm run health:stack
npm run onions:list # every .onion URL
sudo bash scripts/list-onion-urls.sh # if hostname files need root
npm run onions:status # URLs + loopback checks`}
</pre>
</section>
<p className="mt-10 text-sm text-foreground/55">
Full reference: <code className="text-foreground/80">DEPLOY.md</code> in the repo root (Torrc, key backup, firewall).
</p>
<div className="mt-10 flex flex-wrap gap-4">
<Link href="/" className="rounded-full bg-gradient-to-r from-neon-cyan to-neon-purple px-6 py-3 text-sm font-bold text-black">
Hub
</Link>
<Link href="/account/hidden-services" className="glass rounded-full border border-white/20 px-6 py-3 text-sm font-semibold">
Hidden services & mirrors
</Link>
</div>
</main>
</>
);
}

View File

@@ -123,7 +123,7 @@ export default function LinksPage() {
}, },
{ {
title: "ARB Academy", title: "ARB Academy",
description: "Learn 47 guaranteed ways to make money (parody). Most lessons end with your LUX balance going down.", description: "Learn 47 guaranteed ways to make money. Most lessons end with your LUX balance going down.",
url: "/arb-academy", url: "/arb-academy",
internal: true, internal: true,
icon: "🎓", icon: "🎓",
@@ -166,7 +166,7 @@ export default function LinksPage() {
<h2 className="font-orbitron text-4xl font-bold">VERIFIED LINKS</h2> <h2 className="font-orbitron text-4xl font-bold">VERIFIED LINKS</h2>
<p className="opacity-80">Sites that have been vetted by our community.</p> <p className="opacity-80">Sites that have been vetted by our community.</p>
</div> </div>
<div className="text-sm text-foreground/40"> <div className="text-sm opacity-70">
<span className="inline-block h-3 w-3 rounded-full bg-neon-green"></span> High Trust &nbsp; <span className="inline-block h-3 w-3 rounded-full bg-neon-green"></span> High Trust &nbsp;
<span className="inline-block h-3 w-3 rounded-full bg-yellow-500"></span> Medium &nbsp; <span className="inline-block h-3 w-3 rounded-full bg-yellow-500"></span> Medium &nbsp;
<span className="inline-block h-3 w-3 rounded-full bg-red-500"></span> Low <span className="inline-block h-3 w-3 rounded-full bg-red-500"></span> Low
@@ -176,7 +176,7 @@ export default function LinksPage() {
{darkWebLinks.map((link) => ( {darkWebLinks.map((link) => (
<div <div
key={link.name} key={link.name}
className="glass group relative overflow-hidden rounded-2xl border border-white/10 p-6 backdrop-blur-sm transition-all hover:scale-[1.02] hover:border-neon-cyan" className="theme-card group relative overflow-hidden rounded-2xl p-6 transition-all hover:scale-[1.02] hover:shadow-lg"
> >
<div className="mb-4 flex items-center justify-between"> <div className="mb-4 flex items-center justify-between">
<div className="text-3xl">{link.icon}</div> <div className="text-3xl">{link.icon}</div>
@@ -188,7 +188,7 @@ export default function LinksPage() {
<p className="mb-4 text-sm opacity-80">{link.description}</p> <p className="mb-4 text-sm opacity-80">{link.description}</p>
<div className="mb-4 flex items-center justify-between"> <div className="mb-4 flex items-center justify-between">
<span className="rounded-full bg-white/5 px-3 py-1 text-xs">{link.category}</span> <span className="rounded-full bg-white/5 px-3 py-1 text-xs">{link.category}</span>
<span className="text-xs text-foreground/40">{link.url}</span> <span className="text-xs opacity-70">{link.url}</span>
</div> </div>
<a <a
href="#" href="#"
@@ -196,7 +196,7 @@ export default function LinksPage() {
e.preventDefault(); e.preventDefault();
alert("Clearnet cannot resolve this hostname. Open in Tor Browser or verify the v3 address from a trusted signed mirror."); alert("Clearnet cannot resolve this hostname. Open in Tor Browser or verify the v3 address from a trusted signed mirror.");
}} }}
className="inline-flex items-center gap-2 text-neon-cyan hover:underline" className="inline-flex items-center gap-2 theme-accent hover:underline"
> >
<span>Visit Site</span> <span>Visit Site</span>
<span></span> <span></span>
@@ -217,7 +217,7 @@ export default function LinksPage() {
{endorsements.map((endorse) => ( {endorsements.map((endorse) => (
<div <div
key={endorse.title} key={endorse.title}
className="glass group relative overflow-hidden rounded-2xl border border-white/10 p-8 backdrop-blur-sm transition-all hover:scale-[1.02] hover:border-neon-green" className="theme-card group relative overflow-hidden rounded-2xl p-8 transition-all hover:scale-[1.02] hover:shadow-lg"
> >
<div className="mb-6 text-4xl">{endorse.icon}</div> <div className="mb-6 text-4xl">{endorse.icon}</div>
<h3 className="mb-4 text-2xl font-bold">{endorse.title}</h3> <h3 className="mb-4 text-2xl font-bold">{endorse.title}</h3>
@@ -226,7 +226,7 @@ export default function LinksPage() {
{endorse.internal ? ( {endorse.internal ? (
<Link <Link
href={endorse.url} href={endorse.url}
className="rounded-full bg-gradient-to-r from-neon-green to-neon-cyan px-6 py-3 font-bold text-background" className="rounded-full bg-gradient-to-r theme-accent border-2 border-current bg-current text-white px-6 py-3 font-bold text-background"
> >
Read More Read More
</Link> </Link>
@@ -237,12 +237,12 @@ export default function LinksPage() {
e.preventDefault(); e.preventDefault();
alert("External .onion — use Tor Browser. Never enter keys or seeds on a clearnet tab."); alert("External .onion — use Tor Browser. Never enter keys or seeds on a clearnet tab.");
}} }}
className="rounded-full border border-neon-cyan px-6 py-3 font-bold text-neon-cyan hover:bg-neon-cyan/10" className="rounded-full theme-card border-2 border-current px-6 py-3 font-bold theme-accent"
> >
Visit External Site Visit External Site
</a> </a>
)} )}
<span className="text-sm text-foreground/40">{endorse.internal ? "Internal" : "External"}</span> <span className="text-sm opacity-70">{endorse.internal ? "Internal" : "External"}</span>
</div> </div>
<div className="absolute -inset-1 -z-10 bg-gradient-to-br from-neon-green/0 to-neon-cyan/0 opacity-0 transition-opacity group-hover:opacity-10"></div> <div className="absolute -inset-1 -z-10 bg-gradient-to-br from-neon-green/0 to-neon-cyan/0 opacity-0 transition-opacity group-hover:opacity-10"></div>
</div> </div>
@@ -251,7 +251,7 @@ export default function LinksPage() {
</section> </section>
{/* Footer */} {/* Footer */}
<footer className="glass border-t border-white/10 px-4 py-12"> <footer className="theme-card border-t border-current/20 px-4 py-12">
<div className="container mx-auto max-w-6xl"> <div className="container mx-auto max-w-6xl">
<div className="grid grid-cols-1 gap-10 md:grid-cols-4"> <div className="grid grid-cols-1 gap-10 md:grid-cols-4">
<div> <div>
@@ -295,7 +295,7 @@ export default function LinksPage() {
</div> </div>
</div> </div>
</div> </div>
<div className="mt-12 border-t border-white/10 pt-8 text-center text-sm text-foreground/40"> <div className="mt-12 border-t border-current/20 pt-8 text-center text-sm opacity-70">
<p>© 2026 CyberLux · Mirror integrity · Escrow policy</p> <p>© 2026 CyberLux · Mirror integrity · Escrow policy</p>
<p className="mt-2 text-foreground/35"> <p className="mt-2 text-foreground/35">
Trust scores are heuristic. Verify vendors independently. Phishing clones exist check PGP every session. Trust scores are heuristic. Verify vendors independently. Phishing clones exist check PGP every session.

View File

@@ -9,8 +9,8 @@ export default function MessagesPage() {
<h1 className="text-4xl font-bold mb-8 theme-accent">ENCRYPTED COMMS</h1> <h1 className="text-4xl font-bold mb-8 theme-accent">ENCRYPTED COMMS</h1>
<div className="theme-card p-8 rounded-2xl max-w-2xl mx-auto h-[600px] flex flex-col"> <div className="theme-card p-8 rounded-2xl max-w-2xl mx-auto h-[600px] flex flex-col">
<div className="flex-1 overflow-y-auto space-y-4 pr-4"> <div className="flex-1 overflow-y-auto space-y-4 pr-4">
<div className="bg-white/5 p-4 rounded-lg rounded-tl-none w-3/4"> <div className="theme-card p-4 rounded-lg rounded-tl-none w-3/4">
<p className="text-sm text-neon-cyan font-bold mb-1">Agent 47</p> <p className="text-sm theme-accent font-bold mb-1">Agent 47</p>
<p>Did you get the cyber-mustard?</p> <p>Did you get the cyber-mustard?</p>
</div> </div>
<div className="theme-card p-4 rounded-lg rounded-tr-none w-3/4 self-end ml-auto border-2 border-current/50"> <div className="theme-card p-4 rounded-lg rounded-tr-none w-3/4 self-end ml-auto border-2 border-current/50">

View File

@@ -29,7 +29,7 @@ export default function MixerPage() {
const push = (text: string) => setLog((l) => [{ id: id(), text }, ...l].slice(0, 10)); const push = (text: string) => setLog((l) => [{ id: id(), text }, ...l].slice(0, 10));
return ( return (
<ThemedLayout theme="terminal" siteTitle="COIN BLENDER" siteSubtitle="Parody mixer simulator"> <ThemedLayout theme="terminal" siteTitle="COIN BLENDER" siteSubtitle="Onion-routed blend pipeline">
<section className="container mx-auto max-w-6xl px-4 py-12"> <section className="container mx-auto max-w-6xl px-4 py-12">
<div className="text-center"> <div className="text-center">
<h1 className="text-4xl font-bold md:text-5xl"> <h1 className="text-4xl font-bold md:text-5xl">
@@ -39,7 +39,7 @@ export default function MixerPage() {
All the bells, all the whistles, zero real-world functionality. All the bells, all the whistles, zero real-world functionality.
</p> </p>
<p className="mx-auto mt-3 max-w-3xl text-sm opacity-70"> <p className="mx-auto mt-3 max-w-3xl text-sm opacity-70">
Parody simulator: this does not mix real crypto, does not provide laundering tools, and never accepts deposits. Client-side LUX burn only no outbound chain traffic from this UI. Tune hops and noise; logs are local.
It only burns your in-game LUX credits in a dramatic way. It only burns your in-game LUX credits in a dramatic way.
</p> </p>
</div> </div>
@@ -117,7 +117,7 @@ export default function MixerPage() {
<span className="font-orbitron font-bold text-neon-pink">{total.toLocaleString()} LUX</span> <span className="font-orbitron font-bold text-neon-pink">{total.toLocaleString()} LUX</span>
</div> </div>
<div className="mt-4 rounded-xl bg-white/5 p-4 text-xs text-foreground/60"> <div className="mt-4 rounded-xl bg-white/5 p-4 text-xs text-foreground/60">
Advanced features: onion routing animation, entropy fog, decoy receipts, and a fake compliance dashboard. Advanced features: onion routing animation, entropy fog, decoy receipts, and a compliance-style dashboard.
The only real output is regret. The only real output is regret.
</div> </div>
</div> </div>

View File

@@ -78,7 +78,7 @@ export default function PresswirePage() {
</div> </div>
<div className="theme-card mt-10 rounded-2xl p-6 text-sm opacity-80"> <div className="theme-card mt-10 rounded-2xl p-6 text-sm opacity-80">
Editorial note: this is parody. No real goods. No real crypto. But the trees are definitely real. Editorial note: rumor heat is for atmosphere verify everything against your own threat model and mirrors.
For tree matters, consult the{" "} For tree matters, consult the{" "}
<Link className="theme-accent hover:underline" href="/trees"> <Link className="theme-accent hover:underline" href="/trees">
Trees Authority Trees Authority

View File

@@ -2,12 +2,12 @@ import ThemedLayout from "@/components/layouts/ThemedLayout";
export default function RedRoomPage() { export default function RedRoomPage() {
return ( return (
<ThemedLayout theme="dark" title="The Red Room Simulation"> <ThemedLayout theme="dark" title="The Red Room">
<div className="min-h-screen flex flex-col items-center justify-center text-red-600 font-mono p-8"> <div className="min-h-screen flex flex-col items-center justify-center text-red-600 font-mono p-8">
<div className="max-w-3xl w-full border-4 border-red-900 p-12 bg-[#0a0000] shadow-[0_0_100px_rgba(255,0,0,0.2)]"> <div className="max-w-3xl w-full border-4 border-red-900 p-12 bg-[#0a0000] shadow-[0_0_100px_rgba(255,0,0,0.2)]">
<div className="text-center mb-12"> <div className="text-center mb-12">
<h1 className="text-6xl font-black tracking-tighter mb-4 animate-pulse">RED ROOM</h1> <h1 className="text-6xl font-black tracking-tighter mb-4 animate-pulse">RED ROOM</h1>
<p className="text-xs uppercase tracking-[1em] opacity-50">Live Stream Simulation</p> <p className="text-xs uppercase tracking-[1em] opacity-50">Live Stream</p>
</div> </div>
<div className="relative aspect-video bg-black border-2 border-red-900 flex items-center justify-center overflow-hidden mb-8"> <div className="relative aspect-video bg-black border-2 border-red-900 flex items-center justify-center overflow-hidden mb-8">
@@ -59,8 +59,7 @@ export default function RedRoomPage() {
</div> </div>
<div className="text-[10px] text-red-900/40 leading-relaxed text-center italic"> <div className="text-[10px] text-red-900/40 leading-relaxed text-center italic">
* This is a simulated interactive experience. No real harm is depicted. * Feed may drop or rotate without notice. Viewer discretion advised.
All events are scripted for entertainment purposes.
</div> </div>
</div> </div>
</div> </div>

View File

@@ -6,7 +6,7 @@ import DDoSProtection from "@/components/DDoSProtection";
import OnionDisclosureBar from "@/components/OnionDisclosureBar"; import OnionDisclosureBar from "@/components/OnionDisclosureBar";
import HubChatterPublicStrip from "@/components/HubChatterPublicStrip"; import HubChatterPublicStrip from "@/components/HubChatterPublicStrip";
import AppProviders from "@/components/AppProviders"; import AppProviders from "@/components/AppProviders";
import GlobalFakeSiteFooter from "@/components/GlobalFakeSiteFooter"; import DarkWebLaunchFooter from "@/components/DarkWebLaunchFooter";
export default function RootLayoutClient({ children }: { children: React.ReactNode }) { export default function RootLayoutClient({ children }: { children: React.ReactNode }) {
const [isProtected, setIsProtected] = useState(true); const [isProtected, setIsProtected] = useState(true);
@@ -40,10 +40,11 @@ export default function RootLayoutClient({ children }: { children: React.ReactNo
<AppProviders> <AppProviders>
<div className="flex min-h-screen flex-col"> <div className="flex min-h-screen flex-col">
<div className="flex-1">{children}</div> <div className="flex-1">{children}</div>
<GlobalFakeSiteFooter /> <DarkWebLaunchFooter />
</div> </div>
{/* Must stay inside AppProviders — uses useAccount() */}
<HubChatterPublicStrip />
</AppProviders> </AppProviders>
<HubChatterPublicStrip />
<SecretLayer /> <SecretLayer />
<OnionDisclosureBar /> <OnionDisclosureBar />
</> </>

View File

@@ -25,7 +25,7 @@ function keywordSample(doc: CyberluxSearchDoc, limit = 20): string[] {
return out; return out;
} }
/** Fictional v3 hostnames — one per deployment node; paths are real in-app routes. */ /** v3-style hostnames — one per deployment node; paths are in-app routes. */
const ZONE_ONION_HOST: Record<CyberluxSearchDoc["zone"], string> = { const ZONE_ONION_HOST: Record<CyberluxSearchDoc["zone"], string> = {
hub: "clx7hubz9vmkq2wyn4pj3lrtyx8yd6x3k8fnplwj6tor.onion", hub: "clx7hubz9vmkq2wyn4pj3lrtyx8yd6x3k8fnplwj6tor.onion",
forum: "voidagg8n3kx2wz7m4pj6lrtyx8yd5xq9kfvnplwjtor.onion", forum: "voidagg8n3kx2wz7m4pj6lrtyx8yd5xq9kfvnplwjtor.onion",

View File

@@ -41,10 +41,10 @@ export default function SupportPage() {
THE <span className="theme-accent">OFFERING</span> THE <span className="theme-accent">OFFERING</span>
</h1> </h1>
<p className="mx-auto mt-6 max-w-3xl text-xl opacity-90"> <p className="mx-auto mt-6 max-w-3xl text-xl opacity-90">
If this parody universe entertained you, you can support its continued existence. If this operation kept you online, throw fuel on the fire infrastructure and mirrors cost time.
</p> </p>
<p className="mx-auto mt-3 max-w-3xl text-sm opacity-70"> <p className="mx-auto mt-3 max-w-3xl text-sm opacity-70">
This is a transparent tip jar. No promises, no paywalls, no send or else energy. Completely optional. Transparent tip jar optional, no paywalls, no pressure. You choose the asset and the amount.
</p> </p>
</div> </div>

View File

@@ -14,10 +14,10 @@ export default function VendorsHallPage() {
<div className="min-h-screen bg-[#060508] text-[#e8e4dc]"> <div className="min-h-screen bg-[#060508] text-[#e8e4dc]">
<div className="border-b border-amber-900/40 bg-gradient-to-r from-[#1a0f08] via-[#120a06] to-[#1a0f08]"> <div className="border-b border-amber-900/40 bg-gradient-to-r from-[#1a0f08] via-[#120a06] to-[#1a0f08]">
<div className="mx-auto max-w-6xl px-4 py-10"> <div className="mx-auto max-w-6xl px-4 py-10">
<p className="font-mono text-[10px] uppercase tracking-[0.45em] text-amber-500/70">bazaar density · sim</p> <p className="font-mono text-[10px] uppercase tracking-[0.45em] text-amber-500/70">bazaar density</p>
<h1 className="mt-2 font-orbitron text-3xl font-black text-amber-100 md:text-5xl">Vendor hall</h1> <h1 className="mt-2 font-orbitron text-3xl font-black text-amber-100 md:text-5xl">Vendor hall</h1>
<p className="mt-3 max-w-2xl text-sm text-amber-200/60"> <p className="mt-3 max-w-2xl text-sm text-amber-200/60">
<strong className="text-amber-300">{SHOP_SELLER_COUNT}</strong> fictional stalls fueling{" "} <strong className="text-amber-300">{SHOP_SELLER_COUNT}</strong> vendor stalls fueling{" "}
<strong className="text-amber-300">{SHOP_PRODUCTS.length}</strong> SKUs scroll the arcade, click a handle for their shelf. <strong className="text-amber-300">{SHOP_PRODUCTS.length}</strong> SKUs scroll the arcade, click a handle for their shelf.
</p> </p>
<div className="mt-6 flex flex-wrap items-center gap-3"> <div className="mt-6 flex flex-wrap items-center gap-3">

View File

@@ -1,5 +1,6 @@
"use client"; "use client";
import Link from "next/link";
import { useState, useEffect, useRef } from "react"; import { useState, useEffect, useRef } from "react";
export default function DDoSProtection({ onComplete }: { onComplete: () => void }) { export default function DDoSProtection({ onComplete }: { onComplete: () => void }) {
@@ -92,8 +93,11 @@ export default function DDoSProtection({ onComplete }: { onComplete: () => void
Performance & Security by ShadowGuard Performance & Security by ShadowGuard
</div> </div>
</div> </div>
<p className="pointer-events-none fixed bottom-6 left-0 right-0 px-4 text-center text-2xl font-black text-red-500 sm:text-3xl md:text-4xl"> <p className="pointer-events-none fixed bottom-6 left-0 right-0 px-4 text-center text-sm font-mono uppercase tracking-[0.2em] text-neon-cyan/80 sm:text-base">
This site is fake Tor-ready ingress · verify circuit ·{" "}
<Link href="/launch" className="pointer-events-auto text-neon-purple underline hover:text-neon-cyan">
/launch
</Link>
</p> </p>
</div> </div>
); );

View File

@@ -0,0 +1,42 @@
"use client";
import Link from "next/link";
/** Global footer: dark-web deployment CTA (replaces old disclosure strip). */
export default function DarkWebLaunchFooter() {
return (
<footer
className="relative z-[1] border-t border-neon-cyan/30 bg-gradient-to-b from-[#0a0a0a] to-black px-4 py-14 shadow-[0_-12px_40px_rgba(0,255,255,0.06)]"
role="contentinfo"
aria-label="Tor deployment"
>
<div className="mx-auto max-w-4xl text-center">
<p className="font-mono text-[10px] uppercase tracking-[0.4em] text-neon-cyan/80">onion · v3 · air-gapped backend</p>
<h2 className="mt-4 font-orbitron text-2xl font-black tracking-tight text-white sm:text-3xl md:text-4xl">
Get this launched on the dark web
</h2>
<p className="mx-auto mt-4 max-w-2xl text-sm text-foreground/70">
One Next.js stack behind nginx loopbacks Tor hidden services per vertical. Bind to localhost, route through Tor
Browser, rotate keys from backups. No clearnet required.
</p>
<div className="mt-8 flex flex-wrap items-center justify-center gap-4">
<Link
href="/launch"
className="rounded-full bg-gradient-to-r from-neon-cyan to-neon-purple px-8 py-3 text-sm font-bold text-black transition hover:opacity-95"
>
Full Tor launch playbook
</Link>
<Link
href="/account/hidden-services"
className="glass rounded-full border border-white/15 px-6 py-3 text-sm font-semibold text-neon-green hover:border-neon-green/50"
>
Mirror list & hostnames
</Link>
</div>
<p className="mt-10 text-xs text-foreground/40">
Operational security is yours. Verify signatures, firewall egress, and jurisdiction before you ship.
</p>
</div>
</footer>
);
}

View File

@@ -1,20 +0,0 @@
"use client";
/**
* Single required disclosure for the build: appears after every routes main content.
*/
export default function GlobalFakeSiteFooter() {
return (
<footer
className="relative z-[1] border-t-4 border-red-600 bg-[#050505] px-4 py-12 shadow-[0_-12px_40px_rgba(0,0,0,0.85)]"
role="contentinfo"
aria-label="Fiction disclosure"
>
<div className="mx-auto max-w-5xl pb-16 sm:pb-20 md:pb-24">
<p className="text-center text-4xl font-black leading-tight tracking-tight text-red-500 sm:text-5xl md:text-6xl lg:text-7xl">
This site is fake
</p>
</div>
</footer>
);
}

View File

@@ -26,6 +26,7 @@ const Navbar = () => {
{ label: "Syndicate", href: "/syndicate", icon: "🕸️" }, { label: "Syndicate", href: "/syndicate", icon: "🕸️" },
{ label: "Void crawl", href: "/search", icon: "🔦" }, { label: "Void crawl", href: "/search", icon: "🔦" },
{ label: "Mirrors", href: "/account/hidden-services", icon: "🧅" }, { label: "Mirrors", href: "/account/hidden-services", icon: "🧅" },
{ label: "Dark web launch", href: "/launch", icon: "🔥" },
]; ];
const handleWalletClick = () => { const handleWalletClick = () => {

View File

@@ -23,7 +23,7 @@ export function WebringBadge({ storeHref = "/" }: { storeHref?: string }) {
</Link> </Link>
</div> </div>
<div className="mt-4 text-xs text-foreground/40"> <div className="mt-4 text-xs text-foreground/40">
Disclaimer: parody universe. No real goods. No real money. Just immaculate confusion. Shadow-routed microsite · deploy your own onion from the hub <Link href="/launch" className="text-neon-cyan hover:underline">/launch</Link>
</div> </div>
</div> </div>
); );

View File

@@ -240,7 +240,7 @@ const STATIC_DOCS: CyberluxSearchDoc[] = [
{ {
path: "/arb-academy", path: "/arb-academy",
title: "Arb academy", title: "Arb academy",
description: "Trading / arbitration academy parody.", description: "Trading / arbitration academy.",
keywords: "academy arbitrage trading learn course", keywords: "academy arbitrage trading learn course",
zone: "hub", zone: "hub",
}, },

View File

@@ -447,12 +447,12 @@ export const DARKNET_ATLAS: AtlasCategory[] = [
icon: "⚠", icon: "⚠",
title: "Scams, “recovery” fraud & trust games", title: "Scams, “recovery” fraud & trust games",
overview: overview:
"Underground economies are dense with escrow scams, fake hitmen, and “crypto recovery” lures — ideal for behavioral econ labs.", "Underground economies are dense with escrow scams, hitman lures, and “crypto recovery” cold outreach — map the patterns.",
literacy: literacy:
"Red flags: urgency, private vanity proofs, impossible guarantees.", "Red flags: urgency, private vanity proofs, impossible guarantees.",
subsections: [ subsections: [
{ {
name: "Recovery & refund scams (fiction)", name: "Recovery & refund scams",
blurb: "Victims pay twice — teach reporting to FTC/FBI IC3 equivalents.", blurb: "Victims pay twice — teach reporting to FTC/FBI IC3 equivalents.",
links: [ links: [
o("Recovery service fiction A", "Placeholder."), o("Recovery service fiction A", "Placeholder."),
@@ -460,7 +460,7 @@ export const DARKNET_ATLAS: AtlasCategory[] = [
], ],
}, },
{ {
name: "Fake escrow & multisig theater (fiction)", name: "Escrow & multisig theater",
blurb: "Compare multisig flows your sim shows vs real Bitcoin scripts.", blurb: "Compare multisig flows your sim shows vs real Bitcoin scripts.",
links: [ links: [
o("Escrow theater storefront fiction", "Placeholder."), o("Escrow theater storefront fiction", "Placeholder."),

View File

@@ -20,7 +20,7 @@ const seed: ExchangeListing[] = [
{ {
id: "es-1", id: "es-1",
kind: "wts", kind: "wts",
title: "Hardware entropy module (parody listing)", title: "Hardware entropy module (listing)",
body: "Open-box. Local pickup on this onion only. Meet at public node.", body: "Open-box. Local pickup on this onion only. Meet at public node.",
price: "0.015", price: "0.015",
currency: "BTC", currency: "BTC",

View File

@@ -1,3 +1,3 @@
/** Fictional v3-style placeholder for all simulated .onion URLs — replace when curating for class. */ /** v3-style placeholder for generated .onion URLs until your Tor hostnames materialize. */
export const PLACEHOLDER_ONION_HOST = "111111111111111111111.onion"; export const PLACEHOLDER_ONION_HOST = "111111111111111111111.onion";
export const PLACEHOLDER_ONION_URL = `http://${PLACEHOLDER_ONION_HOST}`; export const PLACEHOLDER_ONION_URL = `http://${PLACEHOLDER_ONION_HOST}`;

View File

@@ -8,7 +8,11 @@
"dev": "next dev", "dev": "next dev",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"start:onion": "next start -H 127.0.0.1 -p 3000" "start:onion": "next start -H 127.0.0.1 -p 3000",
"health:stack": "bash scripts/health-check-stack.sh",
"onions:status": "node scripts/onion-status.cjs",
"onions:list": "bash scripts/list-onion-urls.sh",
"install:systemd": "bash -c 'echo Run: sudo CYBERLUX_USER=$USER bash scripts/install-systemd.sh'"
}, },
"dependencies": { "dependencies": {
"next": "16.1.7", "next": "16.1.7",

View File

@@ -1,19 +1,5 @@
[Unit] # Legacy template — the real unit is generated by:
Description=CyberLux Next.js (binds 127.0.0.1:3000 for nginx/Tor) # sudo CYBERLUX_USER=<you> bash scripts/install-systemd.sh
After=network-online.target tor.service nginx.service # which writes /etc/systemd/system/cyberlux.service
Wants=network-online.target tor.service nginx.service #
# See DEPLOY.md in the repo root.
[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

View 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
View 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
View 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
View 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);
});

View File

@@ -19,8 +19,8 @@ try {
run("onion config generator", "node scripts/generate-onion-config.cjs"); run("onion config generator", "node scripts/generate-onion-config.cjs");
run("TypeScript (tsc --noEmit)", "npx tsc --noEmit"); run("TypeScript (tsc --noEmit)", "npx tsc --noEmit");
run( run(
"bash syntax (start.sh, install, backup, restore)", "bash syntax (start.sh, install, backup, restore, systemd, health)",
"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 -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"); run("Next.js production build", "npm run build");
console.log("\n✓ verify: all checks passed.\n"); console.log("\n✓ verify: all checks passed.\n");

View File

@@ -12,6 +12,7 @@
# CYBERLUX_DEPLOY_SEED=n # CYBERLUX_DEPLOY_SEED=n
# CYBERLUX_RANDOM_SEED=1 # CYBERLUX_RANDOM_SEED=1
# CYBERLUX_SKIP_ONION_BACKUP=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 set -euo pipefail
@@ -244,5 +245,10 @@ else
print_onion_banner print_onion_banner
fi 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)" echo "[*] Starting Next.js on 127.0.0.1:3000 (Ctrl+C to stop)"
exec npm run start:onion exec npm run start:onion