Client: - Commit: batch `p4 reopen` so huge changelists (63k+) no longer blow the Windows command-line limit; live "N / total" progress + spinner during commit. - Submit dialog: white arrow, "N of total" + % bar, running file log, Cancel button (aborts pre-commit; changelist stays pending). - Disk scan: streaming reconcile with a live "found N files" counter + Cancel. - Thumbnails: only decode browser-renderable image formats; glyph fallback (+ onError) instead of a broken-image icon for bmp/tga/dds/etc. Landing + waitlist: - waitlist-server: Express + SQLite email collector (dedup) with a secure admin panel (scrypt auth, rate-limited login, signed cookies) and CSV export. - landing form now POSTs signups to the waitlist API. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2.1 KiB
2.1 KiB
Exbyte Depot — Waitlist server + admin
Collects waitlist emails from the landing page (deduplicated) and gives you a password-protected admin panel to browse them and export to CSV.
Stack
- express — HTTP + static landing
- better-sqlite3 — storage;
UNIQUE COLLATE NOCASEon email → no duplicates, ever - express-rate-limit — brute-force protection on the login endpoint
- node:crypto — scrypt password hashing + HMAC-signed session cookies (no extra deps)
Setup
cd waitlist-server
npm install
cp .env.example .env
npm run gen-secret # → paste into SESSION_SECRET in .env
npm run set-password # type a password → paste the ADMIN_PASS_HASH line into .env
# set ADMIN_USER in .env (default: admin)
npm start
- Landing: http://localhost:8787/
- Admin: http://localhost:8787/admin
How it works
- The landing form POSTs
{ email }toPOST /api/waitlist.- Invalid emails are rejected; a repeat email is silently deduped (no dup row).
- A hidden honeypot field (
website) traps bots.
/adminrequires login. Session is a signed, HttpOnly, SameSite=Strict cookie (Secure in production), valid 8h. Wrong logins are rate-limited (8 / 15 min / IP).- Export CSV:
GET /admin/export.csvstreams all signups (UTF-8 + BOM for Excel).
Security notes
- No plaintext password anywhere — only a scrypt hash in
.env(git-ignored). - Refuses to start without
ADMIN_USER,ADMIN_PASS_HASH, and a strongSESSION_SECRET. - Security headers set (CSP, X-Frame-Options: DENY, nosniff, no-referrer).
- Behind a reverse proxy, set
TRUST_PROXY=1so rate-limiting and stored IPs are correct, and terminate TLS at the proxy (cookies becomeSecurewhenNODE_ENV=production).
Deploy (Linux VPS, sketch)
NODE_ENV=production, real.env,npm ci --omit=dev.- Run under systemd (or pm2); put nginx/Caddy in front for TLS +
TRUST_PROXY=1. - Point your domain at it; the landing is served from
../landing(or setLANDING_DIR).
Data
SQLite file at data/waitlist.db (git-ignored). Back it up to keep your list.