<title>Hephaestus — decentralized code forge for Silent Mode</title>
<metaname="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.">
<h1><spanclass="g">Hephaestus</span> — the forge</h1>
<pclass="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>
<divclass="cta">
<aclass="btn acid"href="https://code.silentmode.st/user/oauth2/hephaestus-wallet">Sign in with wallet</a>
<pclass="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>
<divclass="grid">
<divclass="card">
<h3><spanclass="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>
<divclass="card">
<h3><spanclass="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>
<divclass="card">
<h3><spanclass="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>
<sectionid="quickstart">
<h2>Sign in — 30 seconds</h2>
<olclass="steps">
<li><b>Open the forge.</b><span>Go to <ahref="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 & sign in".</b><span>The browser signs a challenge, the server verifies, Forgejo auto-provisions you as <code>bch_<first-20-of-your-cashaddr></code>. You land in the dashboard.</span></li>
</ol>
<pclass="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>
<h2>For AI sessions & programmatic testing <spanclass="pill">read before debugging</span></h2>
<pclass="lede">If you're an AI assistant, CI job, or anyone debugging the sign-in without a real browser, read this first. It heads off two misdiagnoses we've already hit.</p>
<h3>Misdiagnosis #1: "The login shell page is only 510 bytes — it's a broken stub"</h3>
<p>It's not a stub — it's a <b>deliberately small bootstrap page</b> that renders the wallet UI client-side via a JS module (standard SPA pattern). Size ≠ brokenness. The full UI (mnemonic generation, passphrase encryption, sign flow) is inside <code>wallet.js</code>, not the shell. Test by requesting <code>wallet.js</code> instead:</p>
<h3>Misdiagnosis #2: "static/wallet.js returns 404 at every path"</h3>
<p>It only 404s if you drop the <code>/auth/</code> prefix. Caddy path-multiplexes: <b><code>/auth/*</code> goes to the auth-proxy, everything else goes to Forgejo</b>. Forgejo has no <code>/static/wallet.js</code>, so that path is expected-404. The real paths are:</p>
<tr><td>/api/v1/user</td><td>Forgejo</td><td>Requires an API <b>token</b> (not a session cookie) — returning 401 in a browser is not a bug</td></tr>
</table>
<h3>Testing the sign-in flow end-to-end without a real browser</h3>
<p>The wallet signing is easiest to reproduce in Node/Deno or a headless browser — the crypto is BIP-137 "Bitcoin Signed Message" over BCH secp256k1. But <b>the server-side pieces alone can be smoke-tested with curl</b>:</p>
<p>For the JS side (which is what the browser does), inspect <code>auth-proxy/public/wallet.js</code> in the repo — it's ~250 lines and does exactly this: <code>generateBip39Mnemonic → deriveHdPath("m/44'/145'/0'/0/0") → sign(magicHash(message))</code>. Nothing exotic.</p>
<h3>Common browser-side checks</h3>
<p>If you're in a browser (or a browser-driving tool) and want to know if you're actually signed in:</p>
<pre>// The URL bar isn't reliable when Forgejo shows Security or Settings pages —
// they render for logged-in users but don't have "Dashboard" in the title.
// Best check: does the profile menu render, and is there no "Sign in" link?
<pclass="note warn"><b>Do not test with <code>fetch('/api/v1/user')</code> unless you have an API token.</b> That endpoint requires token auth even when you're signed into the web UI. Session cookies get you into Forgejo pages; they don't get you into the token-scoped API. A 401 there means "no token" — it does not mean "not signed in."</p>
<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>
<sectionid="protocol">
<h2>Wallet-auth protocol</h2>
<pclass="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:
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>
<sectionid="operators">
<h2>For operators <spanclass="pill">self-host your own</span></h2>
<pclass="lede">Hephaestus is a docker-compose stack. Everything you need is in the <ahref="https://code.silentmode.st/silentmode/silentmode">silentmode monorepo</a> under <code>Hephaestus/</code>. To stand up your own instance:</p>
<olclass="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>
# 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>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_<first-20></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>
<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><ahref="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) <spanclass="pill warn">alpha</span></td></tr>
<tr><th>BCNR name</th><td><code>hephaestus.x</code><spanclass="pill soon">to be minted on Sirius</span></td></tr>
</table>
<pclass="note"><b>Report an issue.</b> Open one at <ahref="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>
<divclass="wrap">
Silent Mode — a Deviant project · Hephaestus is one of the Silent Mode tools · <ahref="/">silentmode.st</a>