# FuckHub — Guide for the Claude Code instance on the server

You maintain a self-hosted adult video **embed aggregator**. This file tells you
what it is, how to run/deploy it, and the hard rules. Read fully before acting.

## What it is
- Node.js + Express app. Serves a dark tube-style frontend + admin.
- **Hosts NO video.** Only stores metadata + the embed code each source site
  publishes, and renders those embeds in an iframe.
- Brand: **FuckHub**, black/orange. Wordmark = white "Fuck" + orange "Hub".
  Favicon `public/favicon.svg`.

## Architecture (do not rewrite — extend)
- `server.js` — Express, routes, SEO rendering, security headers, static.
- **Single-file database** = SQLite via `better-sqlite3` at `data/fuckhub.db`
  (WAL mode). This IS the "one file that holds all video links". Do not move to a
  server DB — file DB is intentional and scales for read-heavy embed traffic.
- `db.js` schema + migrations (`migrate()`, `migrateFts()` run on boot).
- `models.js` — all SQL (list/search/related/publishers/health/bulk).
- `scraper.js` — metadata extractor + per-site adapters + `categorize`.
- `importer.js` — dedupe/upsert + URL cleaning + listing/embeddable rejection.
- `jobs.js` — in-memory background import jobs (progress polling).
- `health.js` + `scripts/health-check.js` — dead-link pruning.
- `seo.js` — server-rendered `<title>`/OG/JSON-LD + sitemap/robots.
- `routes/api.js` (public), `routes/admin.js` (Basic-Auth).
- `public/` — frontend (`index/watch/publisher/categories/pornstars/admin` +
  `js/ev.js` shared helpers, `js/app.js`, `js/watch.js`, `js/admin.js`).

## Run locally
```
cp .env.example .env      # set ADMIN_PASS, SITE_NAME=FuckHub, NODE_ENV=production
npm ci && npm run init-db
npm start                 # http://localhost:3000  admin: /admin
```

## Deploy to real server + domain
```
1. Ubuntu VPS (Hetzner CX22 ~5€). git clone -> /opt/fuckhub
2. cp .env.example .env  ; set ADMIN_PASS (strong), SITE_NAME=FuckHub, NODE_ENV=production
3. npm ci && npm run init-db
4. npm i -g pm2 ; pm2 start server.js -i max --name fuckhub ; pm2 save ; pm2 startup
5. Nginx reverse proxy -> 127.0.0.1:3000 ; forward X-Forwarded-Proto / -Host / -For
6. Cloudflare: proxied DNS (orange cloud) -> hides origin IP from visitors, free
   SSL + CDN cache. Set SSL "Full (strict)".
7. Cron daily: node scripts/health-check.js   (prune dead links)
8. Backup nightly: copy data/fuckhub.db + -wal + -shm
```
Scale: Cloudflare edge cache absorbs most traffic; SQLite WAL handles the reads.
Bigger VPS before any DB change. 100k users need cache + PM2 cluster, not Postgres.

## Env vars (.env)
`PORT SITE_NAME ADMIN_USER ADMIN_PASS DB_PATH FETCH_USER_AGENT FETCH_TIMEOUT_MS`
`IMPORT_DELAY_MS IMPORT_CONCURRENCY MAX_IMPORT DEFAULT_IMPORT_STATUS`
`DEAD_LINK_DAYS DEBUG_SCRAPER AD_PREROLL_HTML AD_SKIP_SECONDS`
Auth: `ADMIN_PASS_HASH` (scrypt) `ADMIN_TOTP_SECRET` (base32, 2FA) `SESSION_SECRET`
`ADMIN_IP_ALLOW`. Legacy plaintext `ADMIN_PASS` still works as fallback.

## Admin auth (`auth.js`) — session login, NOT Basic-Auth anymore
Login page `/admin/login` -> POST `/admin/login` -> signed httpOnly SameSite=Strict
cookie (`fh_sess`). scrypt password + mandatory TOTP 2FA + per-IP brute-force
lockout + optional IP allow-list. Logout + restart revoke all sessions (session
epoch). Regenerate creds: `node scripts/admin-secret.js`. Zero external deps
(Node `crypto`). Dashboard stats: `models.statsFull()` -> `/admin/api/stats/full`
-> Chart.js "Statistik" tab in `public/js/admin.js`.

## Adding a new source-site adapter
Edit `scraper.js` `ADAPTERS`: `{ site, match, cookies, embed(url)->{id,embedUrl} }`.
Optional `parse($,jsonld,ctx)` for site-specific tags/performers,
`embeddable:false` if the site blocks iframing, `oembed(url)` if it has an oEmbed
endpoint. Then re-import affected URLs (upsert updates them).

## Embeddability facts (already encoded — keep)
- Inline-playable: **pornhub, xvideos, xnxx, redtube, youporn, eporner**.
- **xhamster** frame-busts on unregistered domains -> in `public/js/watch.js`
  `NO_INLINE` set shows an honest "watch at source" card instead of a dead player.
- **faphouse** blocks framing (`embeddable:false`) -> import rejects it.

## HARD RULES — do not cross (these protect the operator)
1. **Never rip/proxy the raw video stream** (.m3u8/mp4) to bypass a source
   site's player, ads, or embed protection. Use only their official embed URL.
   Sandbox the iframe (no `allow-top-navigation`) — that's the limit.
2. **Never mass-crawl a category/whole site** to harvest every link. Import
   single video URLs only (bulk paste of hand-picked links is fine).
3. **Keep human review** — default import status = pending. Don't auto-publish
   unmoderated bulk from these platforms (illegal/non-consensual content risk).
4. **No blackhat SEO** (cloaking, doorway pages, link spam, fake views/bots).
   Legit SEO only (already built: VideoObject JSON-LD, sitemap, clean titles).
5. **Don't promise ranking above the source** — Google demotes scraped/aggregated
   content; you will not outrank the original video's platform. Don't build for it.
6. **Don't design "untraceable" evasion.** Cloudflare/WHOIS-privacy hide the
   origin IP from visitors — that's normal opsec. They do NOT hide the operator
   from lawful requests, and that's not a goal to engineer around.

## Legal reality to surface to the operator (don't silently ignore)
- Adult content for adults is legal, BUT embedding others' content has
  copyright/ToS exposure that sits with the operator.
- Age verification: DE (JMStV), UK (Online Safety Act), several US states now
  require **real** age verification, not a click-through 18+ gate. Going "abroad"
  does not remove this per target country.
- DE also requires an Impressum (names the operator).

Build features, keep the guardrails, be honest about the ceilings.
