Bulk-inserts <link rel="icon" type="image/svg+xml" href="/favicon.svg"> before the </head> tag on every silentmode.st page that was missing it. All 36 site HTML pages now advertise the chain-links favicon; browsers show it in tabs and bookmarks across the whole umbrella site. Pages touched (29): apps/, choose/*, deviant/* (7 pages under the parent house), hephaestus/, hephaestus.x/* (9 chain-published mirror pages), nav/ (redirect stub), register.html (redirect stub), sirius/, sirius/register.html, store/ (redirect stub), tlds/. Already had favicon from earlier passes (7): index.html, brand/, tools/, prometheus/, helios/, hermes/, releases/. Line-ending preservation: choose/index.html and sirius/register.html were originally stored with CRLF endings; the plain Python read/write path normalized them to LF and produced a noisy diff. Both files re-serialized as CRLF before the favicon insert to keep the diff to +1 line each. Deploy: rsync-equivalent tar-pipe to /opt/silent-mode/site/, plus per-file scp for the two CRLF-corrected files. Verified live: 7 spot-checked URLs each report exactly one <link rel="icon"> in served HTML.
331 lines
22 KiB
HTML
331 lines
22 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>
|
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
|
</head>
|
|
<body>
|
|
|
|
<nav class="topnav">
|
|
<a class="brand" href="../">⛓️ Silent <span class="g">Mode</span></a>
|
|
<a href="../tools/">Theseus</a>
|
|
<a href="../prometheus/">Prometheus</a>
|
|
<a href="../sirius/">Sirius</a>
|
|
<a href="./" class="here">Hephaestus</a>
|
|
<a href="../helios/">Helios</a>
|
|
<a href="../hermes/">Hermes</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 & 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>
|
|
<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="cli">
|
|
<h2>For CLI & programmatic access <span class="pill">read before debugging</span></h2>
|
|
<p class="lede">If you're driving the sign-in from a CLI, CI job, or any scripted client 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>
|
|
<pre>curl -sI https://code.silentmode.st/auth/static/wallet.js
|
|
# expect: HTTP 200, ~10 KB, Content-Type: application/javascript</pre>
|
|
|
|
<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>
|
|
<table class="spec">
|
|
<tr><th>URL</th><th>Served by</th><th>Purpose</th></tr>
|
|
<tr><td>/auth/authorize</td><td>auth-proxy</td><td>Login shell HTML (SPA bootstrap)</td></tr>
|
|
<tr><td>/auth/static/wallet.js</td><td>auth-proxy</td><td>Wallet UI + signing logic (~10 KB)</td></tr>
|
|
<tr><td>/auth/static/login.css</td><td>auth-proxy</td><td>Styles for the login card</td></tr>
|
|
<tr><td>/auth/challenge</td><td>auth-proxy</td><td>POST cashaddr → get nonce + signed message</td></tr>
|
|
<tr><td>/auth/verify</td><td>auth-proxy</td><td>POST nonce + signature → get OAuth code</td></tr>
|
|
<tr><td>/auth/.well-known/openid-configuration</td><td>auth-proxy</td><td>OIDC discovery</td></tr>
|
|
<tr><td>/user/oauth2/hephaestus-wallet</td><td>Forgejo</td><td>Kicks off the OAuth handshake (307 → /auth/authorize)</td></tr>
|
|
<tr><td>/user/oauth2/hephaestus-wallet/callback</td><td>Forgejo</td><td>Receives the OAuth code, mints session</td></tr>
|
|
<tr><td>/api/v1/version</td><td>Forgejo</td><td>Public: returns Forgejo version</td></tr>
|
|
<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>
|
|
<pre>ISSUER=https://code.silentmode.st/auth
|
|
|
|
# 1. discovery document lists all endpoints
|
|
curl -s "$ISSUER/.well-known/openid-configuration" | jq .
|
|
|
|
# 2. request a challenge for any cashaddr (server just holds it 5 min)
|
|
curl -s -X POST -H 'content-type: application/json' \
|
|
-d '{"cashaddr":"bitcoincash:qq00000000000000000000000000000000000000q"}' \
|
|
"$ISSUER/challenge"
|
|
# → returns {"nonce": "...", "message": "code.silentmode.st wants you to sign in..."}
|
|
|
|
# 3. any BCH wallet that speaks "Sign Message" can produce a valid signature
|
|
# for that exact message. Then POST it:
|
|
curl -s -X POST -H 'content-type: application/json' \
|
|
-d '{"nonce":"THE_NONCE","signature":"BASE64_SIG"}' \
|
|
"$ISSUER/verify"
|
|
# → returns {"redirect": "https://code.silentmode.st/user/oauth2/hephaestus-wallet/callback?code=..."}</pre>
|
|
<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 your CLI drives a real browser (Playwright, Puppeteer, curl-with-cookies) and you 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?
|
|
|
|
const signedIn = !document.querySelector('a[href*="/user/login"]')
|
|
&& !!document.querySelector('[aria-label*="Profile"], .user-menu, .avatar');
|
|
|
|
// The username, if you're on the Dashboard:
|
|
document.title.match(/^(bch_\w+)/)?.[1];</pre>
|
|
|
|
<p class="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>
|
|
</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" > README.md
|
|
git add README.md && 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_<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>
|
|
</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> — minted on chipnet, points at <code>https://code.silentmode.st/</code>. Resolve via <a href="https://navigate.st/bns/hephaestus.x/">navigate.st gateway</a> or any BCNR-aware browser (Theseus, Ariadne).</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>
|