sueta / site / index.html
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>sueta โ€” serverless p2p e2e chat stack</title>
<style>
  :root {
    --bg: #0d1117; --panel: #161b22; --panel-2: #1c2129; --border: #262c36;
    --text: #e6edf3; --muted: #8b949e; --accent: #3fb68b; --accent-2: #2ea043;
  }
  * { margin: 0; padding: 0; box-sizing: border-box; }
  body {
    background: var(--bg); color: var(--text);
    font: 16px/1.6 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    padding: 40px 20px 80px;
  }
  main { max-width: 780px; margin: 0 auto; }
  h1 { font-size: 34px; letter-spacing: 1px; }
  h1 .lock { font-size: 24px; }
  .tag { color: var(--muted); margin: 6px 0 30px; font-size: 17px; }
  h2 { margin: 44px 0 12px; font-size: 22px; color: var(--accent); }
  h3 { margin: 24px 0 8px; font-size: 17px; }
  p, li { color: #c9d3dd; }
  ul { padding-left: 22px; margin: 10px 0; }
  li { margin: 5px 0; }
  a { color: var(--accent); }
  pre {
    background: var(--panel); border: 1px solid var(--border); border-radius: 10px;
    padding: 14px 16px; overflow-x: auto; margin: 12px 0;
    font: 13.5px/1.55 ui-monospace, SFMono-Regular, Menlo, monospace;
  }
  code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 14px;
    background: var(--panel-2); border-radius: 5px; padding: 1px 6px; }
  pre code { background: none; padding: 0; }
  .card {
    background: var(--panel); border: 1px solid var(--border); border-radius: 12px;
    padding: 18px 20px; margin: 14px 0;
  }
  .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; }
  .muted { color: var(--muted); font-size: 14px; }
  .btn {
    display: inline-block; background: var(--accent-2); color: #fff; text-decoration: none;
    font-weight: 600; border-radius: 10px; padding: 12px 22px; margin: 8px 12px 8px 0;
  }
  .btn.ghost { background: var(--panel-2); border: 1px solid var(--border); }
  footer { margin-top: 60px; color: var(--muted); font-size: 13px; }
</style>
</head>
<body>
<main>
  <h1><span class="lock">๐Ÿ”’</span> sueta</h1>
  <p class="tag">a complete, working stack for <strong>serverless p2p end-to-end-encrypted web apps</strong> โ€” this page is also the git repo.</p>

  <a class="btn" href="https://chat.ardegazu.ro">try the live chat</a>
  <a class="btn ghost" href="browse/index.html">browse the code</a>
  <a class="btn ghost" href="#clone">clone the repo</a>

  <h2 id="clone">Clone</h2>
  <p>This site serves the repository over git's dumb-HTTP protocol as plain static files (it's hosted on IPFS โ€” there is no git server here, or any server at all):</p>
  <pre><code>git clone https://git-chat.ardegazu.ro/sueta.git
cd sueta</code></pre>

  <h2>What you get</h2>
  <div class="grid">
    <div class="card"><h3>client/src/lib/ โ€” the p2p core</h3>
      <p class="muted">Reusable, app-agnostic TypeScript: WebRTC full-mesh with perfect negotiation, E2E crypto (HKDF + AES-GCM, structurally nonce-safe), blind signaling client, TURN credentials, persistent Ed25519 identities with emoji fingerprints.</p></div>
    <div class="card"><h3>server/ โ€” one Go binary</h3>
      <p class="muted">Blind WebSocket relay (routes only ciphertext), coturn REST credential minter, built-in Let's Encrypt. No database, no state, no logs of content.</p></div>
    <div class="card"><h3>client/src/app/ โ€” the chat</h3>
      <p class="muted">Installable PWA: rooms as capability links, threads, reactions, EXIF-stripped image sharing, p2p audio/video room calls (DTLS-SRTP), peer-to-peer history sync + on-device persistence, local notifications, numbered releases with an in-app update banner.</p></div>
    <div class="card"><h3>docs/ โ€” the contracts</h3>
      <p class="muted"><code>PROTOCOL.md</code> โ€” crypto &amp; wire spec. <code>SIGNAL-SERVER.md</code> โ€” ops runbook and how to attach new apps/domains to one relay.</p></div>
  </div>

  <h2>How it works (the short version)</h2>
  <ul>
    <li><strong>The room secret never leaves the browser.</strong> It lives in the URL fragment (<code>#โ€ฆ</code>). HKDF derives a one-way room id (rendezvous only) and per-sender AES-256-GCM keys.</li>
    <li><strong>The relay is blind.</strong> Peers meet through a ~500-line WebSocket relay that forwards opaque encrypted blobs. It cannot read offers, names, or messages โ€” verified by tests that grep every frame for plaintext.</li>
    <li><strong>Everything else is browser โ‡„ browser.</strong> WebRTC datachannels (DTLS + app-layer AES-GCM), full mesh up to ~12 peers, STUN/TURN for NAT traversal with ephemeral HMAC credentials.</li>
    <li><strong>History lives in the members.</strong> Devices persist their rooms locally (IndexedDB) and replay history to newcomers over the encrypted channels. No server stores a byte.</li>
    <li><strong>Identity without accounts.</strong> One Ed25519 seed per device (password-manager-sized), session binding signatures, emoji fingerprints, trust-on-first-use with key-change warnings.</li>
    <li><strong>Hosting without an origin server.</strong> The app is a static build pinned to IPFS, served through an HTTPS tunnel that is deliberately never connected โ€” its "offline page" <em>is</em> the site. This page you're reading works the same way.</li>
  </ul>

  <h2>Run it yourself</h2>
  <pre><code># dev: relay + app
cd server && go run . --dev          # :8080
cd client && npm install && npm run dev   # :5173, proxies /ws to :8080

# tests (real Chromium โ€” WebRTC needs real UDP)
cd server && go test ./...
cd client && node e2e/mesh.e2e.mjs</code></pre>
  <p>To deploy your own server side, one command on any Ubuntu/Debian box: <code>sudo deploy/install-server.sh signal.yourdomain.com</code> โ€” installs coturn + the relay from source, secrets, systemd, firewall. Host the client build anywhere static โ€” IPFS, a CDN, a folder behind nginx. Full steps in <code>docs/SIGNAL-SERVER.md</code>.</p>

  <h2>Build your own p2p app on this</h2>
  <p>The chat is just one consumer of the core. A new app is ~40 lines of bootstrap:</p>
  <ul>
    <li>pick an <strong>app salt</strong> (e.g. <code>"myapp.example.com/v1"</code>) โ€” it namespaces your rooms so apps can share one relay without collisions,</li>
    <li>copy <code>client/src/lib/</code> (zero app-specific code in it),</li>
    <li>point it at your relay โ€” or reuse an existing one; the relay never learns what app it's carrying,</li>
    <li>define your own payloads and broadcast them โ€” they're sealed per-sender automatically.</li>
  </ul>
  <p>The recipe with code is in <code>client/src/lib/README.md</code>. Shared whiteboards, turn-based games, collaborative editors, file drops โ€” anything that fits "small group, real-time, private" fits this stack.</p>

  <h2>Guarantees &amp; limits, honestly</h2>
  <ul>
    <li>The relay operator (or anyone on the wire) sees: connection metadata, opaque room ids, ciphertext sizes and timing. Not content, not names, not who-is-who inside a room.</li>
    <li>Anyone with the room link is a full member โ€” capability model. Guard links like keys.</li>
    <li>Identity verification is human: compare the four emoji out-of-band, like reading safety numbers aloud. No servers means no authority that can vouch for anyone.</li>
    <li>Full mesh scales to ~a dozen peers; beyond that you'd want an SFU, which is no longer serverless.</li>
    <li>History survives as long as any member's device kept it; a room everyone truly abandons is forgotten. That's the design, not a bug.</li>
  </ul>

  <footer>
    MIT licensed. Built with WebCrypto, WebRTC, Go, coturn, and IPFS โ€” no other services involved.<br>
    Live app: <a href="https://chat.ardegazu.ro">chat.ardegazu.ro</a> ยท this repo: <code>git clone https://git-chat.ardegazu.ro/sueta.git</code>
  </footer>
</main>
</body>
</html>