site/hephaestus/index.html
Local Dev be41efedee feat(site): add Hephaestus to silentmode.st + silentmode.bch
- Nav link between Sirius (live) and Helios (designed) — keeps live things adjacent
- Product card with p-live pill (Hephaestus is running at code.silentmode.st today)
- Meta description updated to include Hephaestus in the catalog
- New site/hephaestus/index.html — full docs (user quickstart, git push, architecture,
  wallet-auth protocol, operator self-host guide with the gotchas we hit)

Both silentmode.st and silentmode.bch serve from the same bucket path so this one
edit updates both. Files also copied into /opt/silent-mode/site/ on the silentmode VPS.
2026-08-31 03:21:39 +02:00

267 lines
17 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Hephaestus — decentralized code forge for Silent Mode</title>
<meta name="description" content="Hephaestus is Silent Mode's code host. Sign in with a Bitcoin Cash wallet — no email, no password reset. Repos hosted on your VPS, cold blobs (LFS, attachments, packages) stored on the Sia network. Live at code.silentmode.st.">
<style>
:root{
--bg:#0b0e14; --panel:#141a24; --panel2:#18202c; --line:rgba(255,255,255,.09);
--ink:#e7eaf1; --mut:#8b98a9; --dim:#5e6678; --acid:#d6ff3d;
--chain:#d6ff3d; --sia:#b39ddb; --srv:#4fd1a5; --ok:#4fd1a5; --taken:#f6768a;
--warn:#ffc75f;
}
*{box-sizing:border-box}
html{scroll-behavior:smooth}
body{margin:0;background:radial-gradient(1100px 560px at 50% -12%,#16202e,var(--bg));
color:var(--ink);font:16px/1.65 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;min-height:100vh}
a{color:var(--acid)}
.wrap{max-width:980px;margin:0 auto;padding:0 1.2rem}
.topnav{position:sticky;top:0;z-index:20;display:flex;gap:2px;align-items:center;flex-wrap:wrap;
padding:10px 1.2rem;background:rgba(11,14,20,.82);backdrop-filter:blur(10px);
border-bottom:1px solid var(--line)}
.topnav .brand{font-weight:600;color:var(--ink);text-decoration:none;margin-right:14px;font-size:15px;letter-spacing:.2px}
.topnav .brand .g{color:var(--acid)}
.topnav a{color:var(--mut);text-decoration:none;padding:6px 12px;border-radius:8px;font-size:14px}
.topnav a:hover{color:var(--ink);background:var(--panel)}
.topnav a.here{color:var(--acid);background:rgba(214,255,61,.08)}
.topnav a.portal{margin-left:auto;background:rgba(214,255,61,.10);color:var(--acid);font-weight:500}
.topnav a.portal:hover{background:rgba(214,255,61,.18)}
header.hero{text-align:center;padding:5rem 1.2rem 1rem}
.mark{font-size:56px;line-height:1}
h1{font-size:clamp(2rem,5.5vw,2.9rem);margin:.4rem 0 .3rem}
h1 .g{color:var(--acid)}
.tag{color:var(--mut);font-size:1.05rem;max-width:660px;margin:0 auto}
.cta{margin-top:1.8rem;display:flex;gap:10px;justify-content:center;flex-wrap:wrap}
.btn{display:inline-block;text-decoration:none;padding:11px 20px;border-radius:10px;background:#4b7bec;color:#fff;font-size:14px;border:none;cursor:pointer;font-family:inherit;transition:filter .1s}
.btn.ghost{background:transparent;border:1px solid var(--line);color:var(--ink)}
.btn.acid{background:var(--acid);color:#0b0e14;font-weight:600}
.btn:hover{filter:brightness(1.12)}
section{padding:2.6rem 0;border-top:1px solid var(--line);margin-top:2.5rem}
section:first-of-type{border-top:none;margin-top:1.5rem}
h2{font-size:1.5rem;margin:0 0 .4rem}
h3{font-size:1.1rem;margin:1.2rem 0 .35rem}
.lede{color:var(--mut);margin:0 0 1.6rem;max-width:720px}
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:14px}
.card{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:18px 20px}
.card h3{margin:0 0 .5rem;display:flex;align-items:center;gap:8px}
.card p{margin:0;color:var(--mut);font-size:14px}
.card .cta{margin-top:14px;justify-content:flex-start}
.dot{width:9px;height:9px;border-radius:50%;display:inline-block}
.d-chain{background:var(--chain)} .d-sia{background:var(--sia)} .d-srv{background:var(--srv)}
.note{background:var(--panel);border:1px solid var(--line);border-left:3px solid var(--acid);
border-radius:10px;padding:14px 18px;color:var(--mut);font-size:14px;margin-top:1.4rem}
.note.warn{border-left-color:var(--warn)}
.note b{color:var(--ink)}
code,.mono{background:#0e131b;border:1px solid var(--line);border-radius:6px;padding:1px 6px;font-family:ui-monospace,monospace;font-size:13px}
pre{background:#0e131b;border:1px solid var(--line);border-radius:10px;padding:12px 14px;overflow-x:auto;
font-family:ui-monospace,monospace;font-size:13px;color:var(--ink);margin:1rem 0;line-height:1.55}
ol.steps{counter-reset:s;list-style:none;padding:0;margin:1.2rem 0 0;display:grid;gap:10px}
ol.steps li{counter-increment:s;display:grid;grid-template-columns:30px 1fr;gap:14px;align-items:start;
background:var(--panel2);border:1px solid var(--line);border-radius:12px;padding:13px 16px}
ol.steps li::before{content:counter(s);grid-column:1;grid-row:1;
display:grid;place-items:center;width:24px;height:24px;border-radius:50%;
background:rgba(214,255,61,.14);color:var(--acid);font-size:12.5px;font-weight:700}
ol.steps li > *{grid-column:2;min-width:0}
ol.steps b{color:var(--ink)} ol.steps span{color:var(--mut);font-size:14px}
.pill{display:inline-block;font-size:11px;padding:2px 8px;border-radius:999px;background:rgba(255,255,255,.07);color:var(--dim);margin-left:8px;vertical-align:middle;font-weight:500}
.pill.soon{background:rgba(214,255,61,.13);color:var(--acid)}
.pill.warn{background:rgba(255,199,95,.14);color:var(--warn)}
table.spec{width:100%;border-collapse:collapse;margin:1.2rem 0;font-size:14px}
table.spec th,table.spec td{padding:8px 12px;border-bottom:1px solid var(--line);text-align:left;vertical-align:top}
table.spec th{color:var(--mut);font-weight:500;font-size:12.5px;text-transform:uppercase;letter-spacing:.3px}
table.spec td:first-child{color:var(--acid);white-space:nowrap;font-family:ui-monospace,monospace;font-size:13px}
footer{border-top:1px solid var(--line);padding:2.4rem 0 3.5rem;color:var(--dim);font-size:13px;text-align:center;margin-top:2.5rem}
</style>
</head>
<body>
<nav class="topnav">
<a class="brand" href="/">⛓️ Silent<span class="g">Mode</span></a>
<a href="/tools/">Tools</a>
<a href="/sirius/">Sirius</a>
<a href="/hermes/">Hermes</a>
<a href="/hephaestus/" class="here">Hephaestus</a>
<a href="https://code.silentmode.st/" class="portal">Open forge →</a>
</nav>
<div class="wrap">
<header class="hero">
<div class="mark">⚒️</div>
<h1><span class="g">Hephaestus</span> — the forge</h1>
<p class="tag">A code host you sign into with a Bitcoin Cash wallet. Repositories on your own VPS, cold blobs on the Sia network. No email. No password reset. No middleman.</p>
<div class="cta">
<a class="btn acid" href="https://code.silentmode.st/user/oauth2/hephaestus-wallet">Sign in with wallet</a>
<a class="btn ghost" href="https://code.silentmode.st/explore/repos">Browse repos</a>
</div>
</header>
<section id="what">
<h2>What Hephaestus is</h2>
<p class="lede">A friction-free GitHub alternative for people who want to hold their own identity. Under the hood it's Forgejo (a soft-fork of Gitea) with a wallet-authentication layer bolted on and cold storage moved from disk to Sia.</p>
<div class="grid">
<div class="card">
<h3><span class="dot d-chain"></span> Wallet is your identity</h3>
<p>Generate or import a BCH wallet in your browser. Sign a one-time challenge to prove control. That's your account — nothing to remember except your recovery phrase.</p>
</div>
<div class="card">
<h3><span class="dot d-sia"></span> Cold storage on Sia</h3>
<p>Every LFS object, release archive, package registry blob and issue attachment lives on the Sia network via <code>s3.silentmode.st:8600</code>. Live git repos + Postgres stay on the VPS SSD for speed.</p>
</div>
<div class="card">
<h3><span class="dot d-srv"></span> Standard git — push over HTTPS or SSH</h3>
<p>Nothing weird. <code>git clone</code>, <code>git push</code>, PRs, issues, releases, container registry, the works. Bring your existing muscle memory.</p>
</div>
</div>
</section>
<section id="quickstart">
<h2>Sign in — 30 seconds</h2>
<ol class="steps">
<li><b>Open the forge.</b> <span>Go to <a href="https://code.silentmode.st/">code.silentmode.st</a> and click <b>Sign in</b>, then <b>Sign in with hephaestus-wallet</b>.</span></li>
<li><b>Generate or import a wallet.</b> <span>Pick "Generate new" for a fresh BIP-39 12-word recovery phrase, or "Import existing" if you already have one. Write it down — <b>lose it and the account is gone</b>.</span></li>
<li><b>Set a browser passphrase</b> (min 8 chars). <span>Used to encrypt the wallet in your browser's localStorage. Never leaves your device.</span></li>
<li><b>Click "Create wallet &amp; sign in".</b> <span>The browser signs a challenge, the server verifies, Forgejo auto-provisions you as <code>bch_&lt;first-20-of-your-cashaddr&gt;</code>. You land in the dashboard.</span></li>
</ol>
<p class="note"><b>Why no email?</b> Nothing to leak, nothing to phish, no password-reset flow to hijack. The trade: if you lose your recovery phrase there is no support form to send you back in. Back the phrase up. Twice. On paper.</p>
</section>
<section id="git">
<h2>Push some code</h2>
<p class="lede">Two paths, either works. Pick whichever fits your setup.</p>
<h3>Over HTTPS (with an access token)</h3>
<p>Log in, open <b>Settings → Applications → Generate New Token</b>, give it <code>write:repository</code> scope. Then:</p>
<pre>git clone https://<b>your_bch_username</b>:<b>YOUR_TOKEN</b>@code.silentmode.st/<b>your_bch_username</b>/<b>your-repo</b>.git
cd your-repo
echo "Hello, forge" &gt; README.md
git add README.md &amp;&amp; git commit -m "hello"
git push</pre>
<h3>Over SSH (with your existing key)</h3>
<p>Add your public key to <b>Settings → SSH / GPG Keys → Add Key</b>. Then use port 2222:</p>
<pre>git clone ssh://git@code.silentmode.st:2222/<b>your_bch_username</b>/<b>your-repo</b>.git</pre>
<p>Your key is only trusted for git-over-SSH; there's no shell access.</p>
</section>
<section id="architecture">
<h2>How it fits together</h2>
<pre> you (browser)
https://code.silentmode.st/
┌──────────┴──────────┐
│ Caddy │ ← Let's Encrypt cert, HTTP/3
│ (path-based mux) │
└──┬───────────────┬──┘
│ │
│/auth/* │/
▼ ▼
auth-proxy Forgejo ── Postgres (live git repos + DB
(OIDC provider) ───┬─── on local VPS SSD)
Node/Fastify │
libauth for │ storage backend
BCH signature ▼
verification s3.silentmode.st:8600 ← s3d gateway
Sia network hosts
(LFS, attachments,
packages, archives)</pre>
<table class="spec">
<tr><th>Component</th><th>Role</th></tr>
<tr><td>Forgejo 10</td><td>Web UI, PRs, issues, releases, container/npm registry, git-over-HTTPS + SSH</td></tr>
<tr><td>Postgres 16</td><td>User records, issues, PRs, permissions, session state</td></tr>
<tr><td>Caddy 2</td><td>Reverse proxy + automatic Let's Encrypt TLS + HTTP/3</td></tr>
<tr><td>auth-proxy</td><td>OIDC provider (Node/Fastify + <a href="https://libauth.org">libauth</a>). Issues signed challenges, verifies BCH signatures, mints OAuth codes.</td></tr>
<tr><td>s3d → Sia</td><td>Cold storage. Files are encrypted client-side, sharded via Reed-Solomon, scattered across independent hosts.</td></tr>
<tr><td>restic</td><td>Nightly encrypted snapshots (Postgres + /data/git) to a separate Sia bucket. Systemd timer at 03:15 UTC.</td></tr>
</table>
</section>
<section id="protocol">
<h2>Wallet-auth protocol</h2>
<p class="lede">Any BCH wallet that speaks Bitcoin Signed Message can produce a signature Hephaestus will accept. There is no Hephaestus-specific signing format.</p>
<pre>Message the wallet signs:
─────────────────────────────
code.silentmode.st wants you to sign in with your Bitcoin Cash account:
bitcoincash:qzkc695pm4r3p7kcq36sh0t3fdu5e6ua7u2ar36vea
By signing, you prove you control this address. This request will not
trigger a blockchain transaction or cost any fees.
Domain: code.silentmode.st
Nonce: e65d80b51d3f1baa2efb8d952d827104
Issued At: 2026-08-29T17:50:12.655Z</pre>
<p>Signature is standard <b>BIP-137-style</b> recoverable ECDSA over the <code>SHA256(SHA256(varint(magic) || magic || varint(msg) || msg))</code> digest, base64-encoded. The server recovers the pubkey, derives the cashaddr, and matches it against the address you claimed. Nonces expire after 5 minutes and are single-use.</p>
<p>OIDC id_token claims: <code>sub = full cashaddr</code>, <code>preferred_username = "bch_" + first-20-of-address</code>, plus a custom <code>cashaddr</code> claim carrying the full address for downstream tools. Signed with EdDSA (Ed25519).</p>
</section>
<section id="operators">
<h2>For operators <span class="pill">self-host your own</span></h2>
<p class="lede">Hephaestus is a docker-compose stack. Everything you need is in the <a href="https://code.silentmode.st/silentmode/silentmode">silentmode monorepo</a> under <code>Hephaestus/</code>. To stand up your own instance:</p>
<ol class="steps">
<li><b>Get a Linux VPS with Docker</b> <span>(2 GB RAM, ~10 GB free disk minimum). A domain name pointing at it. Ports 80/443/2222 open.</span></li>
<li><b>Clone the repo, copy the env template</b>
<pre>git clone ssh://git@code.silentmode.st:2222/silentmode/silentmode.git
cd silentmode/Hephaestus
cp .env.example .env
# fill in real values — see .env.example for what each field means</pre></li>
<li><b>Bring it up</b>
<pre>docker compose up -d</pre>
<span>First run pulls ~500 MB of images and builds the auth-proxy. Give it a few minutes.</span></li>
<li><b>Complete Forgejo install</b> <span>via POST to <code>/</code> with your admin creds (see <code>PROTOCOL.md</code> in the repo for the exact field list — Forgejo's <code>--config</code> path prevents the CLI installer working, so it has to be the HTTP path).</span></li>
<li><b>Register the wallet OIDC provider</b>
<pre>docker compose exec forgejo forgejo \
--config /data/gitea/conf/app.ini admin auth add-oauth \
--provider openidConnect \
--name hephaestus-wallet \
--key "$AUTH_PROXY_CLIENT_ID" --secret "$AUTH_PROXY_CLIENT_SECRET" \
--auto-discover-url "$AUTH_PROXY_ISSUER/.well-known/openid-configuration" \
--scopes "openid profile"</pre></li>
</ol>
<h3>Gotchas we hit — save yourself the same debugging</h3>
<ul style="color:var(--mut);font-size:14.5px;line-height:1.75">
<li>Forgejo's docker entrypoint re-writes <code>app.ini</code> from env vars on every start — sed edits inside the container get clobbered. Everything must come from <code>FORGEJO__section__KEY</code> env vars in <code>docker-compose.yml</code>.</li>
<li><code>DISABLE_REGISTRATION=true</code> blocks OIDC auto-provisioning too, not just the local form. Use <code>DISABLE_REGISTRATION=false</code> + <code>ALLOW_ONLY_EXTERNAL_REGISTRATION=true</code> + <code>SHOW_REGISTRATION_BUTTON=false</code>.</li>
<li>Forgejo needs <code>[oauth2_client] ENABLE_AUTO_REGISTRATION=true</code> AND <code>USERNAME=preferred_username</code> AND an <code>email</code> claim on the id_token (auth-proxy synthesizes one).</li>
<li>Forgejo usernames are <code>[a-zA-Z0-9-_.]+</code>, max 40 chars. Raw cashaddrs (54 chars, colon) don't fit — auth-proxy sends <code>bch_&lt;first-20&gt;</code> as the username while keeping the full cashaddr in <code>sub</code>.</li>
<li>When serving auth-proxy under a <code>/auth/*</code> path prefix behind a reverse proxy, all client-side asset paths and fetch URLs must be <b>path-relative</b> (no leading slash), otherwise the browser resolves them to origin root and hits the wrong service.</li>
</ul>
</section>
<section id="status">
<h2>Status</h2>
<table class="spec">
<tr><th>Live at</th><td><a href="https://code.silentmode.st/">code.silentmode.st</a></td></tr>
<tr><th>Signup</th><td>Open (wallet-only; local registration disabled)</td></tr>
<tr><th>Storage</th><td>Sia network for cold blobs, VPS SSD for hot path</td></tr>
<tr><th>Backups</th><td>Nightly restic → separate Sia bucket, encrypted client-side</td></tr>
<tr><th>Source</th><td><a href="https://code.silentmode.st/silentmode/silentmode">silentmode/silentmode</a> (private — request access from an admin), or the local checkout under <code>Hephaestus/</code></td></tr>
<tr><th>License</th><td>Forgejo is GPL-3.0. Auth-proxy + Caddyfile + docker-compose scaffolding is part of the Silent Mode monorepo.</td></tr>
<tr><th>Chain</th><td>Chipnet (Bitcoin Cash test network) <span class="pill warn">alpha</span></td></tr>
<tr><th>BCNR name</th><td><code>hephaestus.x</code> <span class="pill soon">to be minted on Sirius</span></td></tr>
</table>
<p class="note"><b>Report an issue.</b> Open one at <a href="https://code.silentmode.st/silentmode/silentmode/issues">silentmode/silentmode/issues</a> once you have access, or ping in the usual Silent Mode channels.</p>
</section>
</div>
<footer>
<div class="wrap">
Silent Mode — a Deviant project · Hephaestus is one of the Silent Mode tools · <a href="/">silentmode.st</a>
</div>
</footer>
</body>
</html>