/docs/ covers sign-in flow, rename, repos, PATs, container registry, API, storage layout, self-hosting, and known gotchas — 12 sections with a sticky TOC on the left. Any session (human or LLM) hitting hephaestus.x now has a one-shot map of the forge. Landing footer replaced with a four-column layout (Product / Source+spec / Pantheon + brand) matching the docs page, with a mono bottom bar. Topnav gets a Docs link between How-it-works and Source.
386 lines
26 KiB
HTML
386 lines
26 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>Hephaestus docs — how to sign in, push, pull, self-host</title>
|
||
<meta name="description" content="Hephaestus documentation: wallet sign-in, creating repos, the OCI container registry, personal access tokens, and self-hosting the whole stack from silentmode/hephaestus.">
|
||
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||
<link rel="mask-icon" href="/favicon.svg" color="#d6ff3d">
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,300;9..144,400;9..144,500;9..144,600;9..144,700&family=DM+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&family=Ubuntu:wght@400;500;700&display=swap" rel="stylesheet">
|
||
<style>
|
||
:root{
|
||
--bg:#050810; --panel:#141a24; --panel2:#0a0f1c; --line:rgba(255,255,255,.06);
|
||
--sky-mid:#0a0f1c; --sky-rim:#131a2a;
|
||
--ink:#f1f4fa; --mut:#b8c2d4; --dim:#6a7488; --acid:#d6ff3d;
|
||
--chain-native:#0AC18E;
|
||
--forge:#ff8c00;
|
||
--serif:'Fraunces',Georgia,serif;
|
||
--sans:'DM Sans','Inter',system-ui,-apple-system,Segoe UI,Roboto,sans-serif;
|
||
--mono:'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
|
||
}
|
||
*{box-sizing:border-box}
|
||
html{scroll-behavior:smooth}
|
||
body{margin:0;
|
||
background:radial-gradient(ellipse 1200px 600px at 50% -5%,var(--sky-rim) 0%,transparent 60%),
|
||
radial-gradient(ellipse 900px 500px at 100% 100%,var(--sky-mid) 0%,transparent 55%),
|
||
radial-gradient(ellipse 900px 500px at 0% 100%,#0c1424 0%,transparent 55%),
|
||
var(--bg);
|
||
background-attachment:fixed;
|
||
color:var(--ink);font:16px/1.6 var(--sans);min-height:100vh}
|
||
h1,h2,h3,h4{font-family:var(--serif);font-feature-settings:"ss01","ss02"}
|
||
code,.mono{font-family:var(--mono)}
|
||
html[data-font-cmp="ubuntu"] body,
|
||
html[data-font-cmp="ubuntu"] h1, html[data-font-cmp="ubuntu"] h2, html[data-font-cmp="ubuntu"] h3, html[data-font-cmp="ubuntu"] h4,
|
||
html[data-font-cmp="ubuntu"] p, html[data-font-cmp="ubuntu"] a, html[data-font-cmp="ubuntu"] button,
|
||
html[data-font-cmp="ubuntu"] span, html[data-font-cmp="ubuntu"] div,
|
||
html[data-font-cmp="ubuntu"] label, html[data-font-cmp="ubuntu"] input, html[data-font-cmp="ubuntu"] li{font-family:'Ubuntu',system-ui,sans-serif !important}
|
||
html[data-font-cmp="ubuntu"] code, html[data-font-cmp="ubuntu"] .mono{font-family:var(--mono) !important}
|
||
.font-picker{display:inline-flex;align-items:center;gap:2px;padding:2px;margin:0 10px;
|
||
background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08);border-radius:999px;
|
||
font-family:var(--mono);font-size:10px;letter-spacing:.12em;text-transform:uppercase}
|
||
.font-picker button{background:transparent;border:none;color:var(--dim);
|
||
padding:4px 10px;border-radius:999px;cursor:pointer;font-family:inherit;font-size:inherit;
|
||
letter-spacing:inherit;text-transform:inherit;transition:color .12s}
|
||
.font-picker button:hover{color:var(--ink)}
|
||
.font-picker button.active{background:var(--acid);color:var(--bg);font-weight:600}
|
||
a{color:var(--acid)}
|
||
.topnav{position:sticky;top:0;z-index:20;display:flex;gap:2px;align-items:center;flex-wrap:wrap;
|
||
padding:10px 1.2rem;background:rgba(5,8,16,.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;display:inline-flex;align-items:center;gap:6px}
|
||
.topnav .brand .g{color:var(--forge)}
|
||
.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 .spacer{margin-left:auto}
|
||
|
||
/* Docs layout: TOC left, content right */
|
||
.doc{display:grid;grid-template-columns:220px 1fr;gap:32px;max-width:1080px;margin:0 auto;padding:2.4rem 1.2rem 4rem}
|
||
.toc{position:sticky;top:60px;align-self:start;font-size:13.5px;max-height:calc(100vh - 80px);overflow:auto;padding-right:6px}
|
||
.toc h4{font-family:var(--mono);font-size:10.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--dim);margin:0 0 8px;font-weight:500}
|
||
.toc ol{list-style:none;padding:0;margin:0 0 1.4rem;display:grid;gap:2px;counter-reset:t}
|
||
.toc ol li{counter-increment:t}
|
||
.toc ol li a{display:block;padding:5px 8px;border-radius:6px;color:var(--mut);text-decoration:none;line-height:1.35}
|
||
.toc ol li a::before{content:counter(t) ". ";color:var(--dim);font-family:var(--mono);font-size:11px}
|
||
.toc ol li a:hover{color:var(--ink);background:var(--panel)}
|
||
.content{min-width:0}
|
||
h1{font-size:clamp(2rem,4vw,2.5rem);margin:.4rem 0 .3rem;line-height:1.05}
|
||
h1 .g{color:var(--forge)}
|
||
h2{font-size:1.35rem;margin:2.4rem 0 .6rem;padding-top:1rem;border-top:1px solid var(--line)}
|
||
h2:first-of-type{border-top:none;padding-top:0;margin-top:1.6rem}
|
||
h3{font-size:1.05rem;margin:1.4rem 0 .4rem;color:var(--ink);font-family:var(--sans);font-weight:600}
|
||
h4{font-size:.95rem;margin:1.1rem 0 .3rem;color:var(--ink);font-family:var(--sans);font-weight:600}
|
||
p,li{color:var(--mut);line-height:1.65}
|
||
.content b{color:var(--ink)}
|
||
.content strong{color:var(--ink)}
|
||
ul,ol{padding-left:1.2rem}
|
||
ul li,ol li{margin:.3rem 0}
|
||
hr{border:0;border-top:1px solid var(--line);margin:2rem 0}
|
||
|
||
code{background:#0e131b;border:1px solid var(--line);border-radius:5px;padding:1px 6px;font-size:13px;color:var(--ink)}
|
||
pre{background:#0e131b;border:1px solid var(--line);border-radius:10px;padding:14px 16px;overflow-x:auto;
|
||
font-family:var(--mono);font-size:13px;color:var(--ink);margin:.9rem 0;line-height:1.55}
|
||
pre .cm{color:var(--dim)}
|
||
pre .k{color:var(--acid)}
|
||
pre .s{color:var(--chain-native)}
|
||
|
||
.note{background:var(--panel);border:1px solid var(--line);border-left:3px solid var(--acid);
|
||
border-radius:10px;padding:12px 16px;color:var(--mut);font-size:14px;margin:1rem 0}
|
||
.note.warn{border-left-color:#ffc75f}
|
||
.note.info{border-left-color:var(--forge)}
|
||
.note b{color:var(--ink)}
|
||
.note p{margin:.3rem 0}
|
||
|
||
dl.spec{display:grid;grid-template-columns:auto 1fr;gap:6px 22px;margin:.6rem 0;font-size:13.5px}
|
||
dl.spec dt{color:var(--dim);white-space:nowrap;font-family:var(--mono)}
|
||
dl.spec dd{margin:0;color:var(--mut)}
|
||
dl.spec dd b{color:var(--ink)}
|
||
|
||
table{width:100%;border-collapse:collapse;margin:1rem 0;font-size:13.5px}
|
||
th,td{text-align:left;padding:10px 12px;border-bottom:1px solid var(--line);vertical-align:top}
|
||
th{color:var(--dim);font-family:var(--mono);font-size:11px;letter-spacing:.1em;text-transform:uppercase;font-weight:500}
|
||
td{color:var(--mut)}
|
||
td b{color:var(--ink)}
|
||
td code{font-size:12px}
|
||
|
||
.kbd{display:inline-block;background:var(--panel2);border:1px solid var(--line);border-bottom-width:2px;
|
||
border-radius:4px;padding:0 6px;font-family:var(--mono);font-size:11.5px;color:var(--ink)}
|
||
.pill{display:inline-block;font-size:10.5px;padding:1px 8px;border-radius:999px;
|
||
background:rgba(255,140,0,.14);color:var(--forge);font-weight:500;text-transform:lowercase;letter-spacing:.02em;margin-left:6px;vertical-align:middle}
|
||
|
||
/* Multi-column footer (shared with landing — same look) */
|
||
footer.deep{border-top:1px solid var(--line);margin-top:4rem;padding:2.4rem 1.2rem 1.6rem;background:rgba(10,15,28,.4)}
|
||
footer.deep .fwrap{max-width:1080px;margin:0 auto;display:grid;grid-template-columns:1.4fr 1fr 1fr 1fr;gap:32px}
|
||
footer.deep h5{font-family:var(--mono);font-size:10.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--dim);margin:0 0 .8rem;font-weight:500}
|
||
footer.deep .brand{font-family:var(--serif);font-size:20px;color:var(--ink);text-decoration:none;display:inline-flex;align-items:center;gap:6px}
|
||
footer.deep .brand .g{color:var(--forge)}
|
||
footer.deep p{color:var(--dim);font-size:13px;line-height:1.55;margin:.6rem 0 0;max-width:280px}
|
||
footer.deep ul{list-style:none;padding:0;margin:0;display:grid;gap:6px;font-size:13.5px}
|
||
footer.deep a{color:var(--mut);text-decoration:none;transition:color .1s}
|
||
footer.deep a:hover{color:var(--ink)}
|
||
footer.deep .bar{max-width:1080px;margin:2rem auto 0;padding-top:1rem;border-top:1px solid var(--line);
|
||
display:flex;justify-content:space-between;gap:16px;font-size:12px;color:var(--dim);flex-wrap:wrap}
|
||
footer.deep .bar .mono{font-family:var(--mono);letter-spacing:.03em}
|
||
|
||
@media (max-width:820px){
|
||
.doc{grid-template-columns:1fr;gap:0}
|
||
.toc{position:relative;top:0;max-height:none;margin-bottom:1.2rem}
|
||
footer.deep .fwrap{grid-template-columns:1fr 1fr;gap:24px}
|
||
}
|
||
@media (max-width:520px){
|
||
footer.deep .fwrap{grid-template-columns:1fr}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<nav class="topnav">
|
||
<a class="brand" href="../">⚒️ <span class="g">Hephaestus</span>.X</a>
|
||
<a href="../#how">How it works</a>
|
||
<a href="./" class="here">Docs</a>
|
||
<a href="../#source">Source</a>
|
||
<a href="https://code.silentmode.st/explore/repos" rel="noopener">Explore repos</a>
|
||
<span class="spacer"></span>
|
||
<a href="https://code.silentmode.st/user/oauth2/hephaestus-wallet" style="color:var(--acid)">🔑 Sign in</a>
|
||
</nav>
|
||
|
||
<div class="doc">
|
||
|
||
<aside class="toc">
|
||
<h4>On this page</h4>
|
||
<ol>
|
||
<li><a href="#what">What Hephaestus is</a></li>
|
||
<li><a href="#hosts">Two hostnames</a></li>
|
||
<li><a href="#signin">Sign in with a wallet</a></li>
|
||
<li><a href="#rename">Rename your username</a></li>
|
||
<li><a href="#repos">Create + push a repo</a></li>
|
||
<li><a href="#tokens">Personal access tokens</a></li>
|
||
<li><a href="#registry">Container registry (OCI)</a></li>
|
||
<li><a href="#api">API basics</a></li>
|
||
<li><a href="#storage">Where files live</a></li>
|
||
<li><a href="#selfhost">Self-hosting</a></li>
|
||
<li><a href="#gotchas">Known gotchas</a></li>
|
||
<li><a href="#help">Help + source</a></li>
|
||
</ol>
|
||
</aside>
|
||
|
||
<main class="content">
|
||
|
||
<h1>Hephaestus <span class="g">docs</span></h1>
|
||
<p style="font-size:1.05rem">A code forge you sign in to with a Bitcoin Cash wallet. Repos on a fast local disk, cold blobs on Sia, container registry baked in. Open source, self-hostable. This page is the map — every section is a one-shot recipe.</p>
|
||
|
||
<h2 id="what">1. What Hephaestus is</h2>
|
||
<p>Hephaestus is a <a href="https://forgejo.org/">Forgejo</a> instance with a Silent Mode auth-proxy in front: instead of email + password, sign-in uses a Bitcoin Cash wallet signature (BIP-137 "Bitcoin Signed Message"). Everything else — git, PRs, issues, releases, container/npm registries, the API — is stock Forgejo. Nothing custom for you to learn if you have used Gitea or Forgejo before.</p>
|
||
<dl class="spec">
|
||
<dt>runs on</dt> <dd>Docker Compose · Caddy + Postgres + Forgejo 10 + auth-proxy</dd>
|
||
<dt>sign-in</dt> <dd>BCH wallet signature → OIDC callback → Forgejo session</dd>
|
||
<dt>usernames</dt> <dd>Auto: <code>bch_<first-20-of-your-cashaddr></code>. Renameable to anything else.</dd>
|
||
<dt>storage</dt> <dd>Live git + Postgres on SSD; LFS/releases/packages spill to Sia via s3.silentmode.st</dd>
|
||
<dt>source</dt> <dd><a href="https://code.silentmode.st/silentmode/hephaestus">silentmode/hephaestus</a> — MIT, open</dd>
|
||
</dl>
|
||
|
||
<h2 id="hosts">2. Two hostnames, one instance</h2>
|
||
<p>The same Forgejo is reachable at two addresses. Same account, same repos, same session cookie — differs only in how you got there:</p>
|
||
<table>
|
||
<tr><th>Host</th><th>How to reach it</th><th>Cert</th></tr>
|
||
<tr><td><b>hephaestus.x</b></td><td>A BCNR-aware browser (Theseus, Ariadne) resolves it directly, or through the public gateway at <a href="https://navigate.st/bns/hephaestus.x/"><code>navigate.st/bns/hephaestus.x/</code></a></td><td>Silent Mode Argonautica CA</td></tr>
|
||
<tr><td><b>code.silentmode.st</b></td><td>Regular DNS + Let's Encrypt. Works in any browser, no resolver setup</td><td>Let's Encrypt (public)</td></tr>
|
||
</table>
|
||
<div class="note info"><b>Which one to use?</b> If you're setting up an OAuth callback, a docker registry, or a git remote — use <code>code.silentmode.st</code>. It works everywhere. Use <code>hephaestus.x</code> when the whole path is BCNR-aware.</div>
|
||
|
||
<h2 id="signin">3. Sign in with a wallet</h2>
|
||
<p>The sign-in button gives you three routes:</p>
|
||
<ul>
|
||
<li><b>Sign up</b> — creates a fresh BIP-39 wallet in your browser, shows you the recovery phrase, and encrypts the wallet in <code>localStorage</code> with a passphrase you set. <span class="pill">easiest</span></li>
|
||
<li><b>Sign in</b> — you paste a 12- or 24-word recovery phrase you already have. Same wallet as anywhere else that speaks BIP-39.</li>
|
||
<li><b>WizardConnect</b> — sign with an external wallet (Cashonize / Paytaca) via an encrypted Nostr relay. Keys never leave the wallet. <span class="pill">most private</span></li>
|
||
</ul>
|
||
|
||
<h3>What happens on the wire</h3>
|
||
<ol>
|
||
<li>Forgejo redirects you to the auth-proxy's <code>/auth/authorize</code>.</li>
|
||
<li>Your browser POSTs <code>{cashaddr, state}</code> to <code>/auth/challenge</code> and gets back a nonce + message.</li>
|
||
<li>The wallet signs the message. Standard "Bitcoin Signed Message" (varint-prefixed magic + double-SHA256 + secp256k1 recoverable-compact + base64).</li>
|
||
<li>Browser POSTs <code>{nonce, signature, state}</code> to <code>/auth/verify</code>. The auth-proxy recovers the pubkey, re-hashes to a cashaddr, matches, mints an Ed25519-signed <code>id_token</code>, and hands back a Forgejo callback URL.</li>
|
||
<li>Following the callback lands you in the dashboard with a session cookie.</li>
|
||
</ol>
|
||
|
||
<h3>If you use the flow programmatically (no browser)</h3>
|
||
<p>Every step is a JSON POST. The wallet-signing bit is the only crypto: sign the exact <code>message</code> string the server returned — <b>no trailing newline</b>. See <a href="https://code.silentmode.st/silentmode/hephaestus/src/branch/master/auth-proxy/src/verify.ts">auth-proxy/src/verify.ts</a> for the byte-perfect reference. A working sample lives in <a href="https://code.silentmode.st/silentmode/hephaestus/src/branch/master/PROTOCOL.md">PROTOCOL.md</a>.</p>
|
||
|
||
<h2 id="rename">4. Rename your username</h2>
|
||
<p>The auto-generated <code>bch_<addr-prefix></code> is a placeholder. Rename yourself to anything else — <code>BitcoinCash</code>, <code>alice</code>, whatever's not taken.</p>
|
||
|
||
<h4>From the UI</h4>
|
||
<p><a href="https://code.silentmode.st/user/settings">Settings → Profile → Username</a>. Change the field, save. All your repo URLs move to <code>/<new-name>/…</code> and the old URLs 307-redirect for a while so bookmarks survive.</p>
|
||
|
||
<h4>From the admin API (if you're the operator)</h4>
|
||
<pre><span class="cm"># The endpoint expects FORM-encoded, not JSON. If you send JSON it silently returns 422 "NewName required".</span>
|
||
curl -X POST -H "Authorization: token $TOKEN" \
|
||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||
--data-urlencode "new_name=BitcoinCash" \
|
||
https://code.silentmode.st/api/v1/admin/users/<old-name>/rename</pre>
|
||
|
||
<h2 id="repos">5. Create + push a repo</h2>
|
||
<p>Standard Forgejo. Nothing custom.</p>
|
||
|
||
<h4>Create</h4>
|
||
<ul>
|
||
<li>UI: <b>+</b> menu top-right → New Repository</li>
|
||
<li>API: <code>POST /api/v1/user/repos</code> with a token</li>
|
||
</ul>
|
||
|
||
<h4>Clone + push</h4>
|
||
<pre><span class="cm"># HTTPS — always works, no key setup</span>
|
||
git clone https://code.silentmode.st/<you>/<repo>.git
|
||
|
||
<span class="cm"># SSH — needs your key uploaded under Settings → SSH keys. Port is 2222, not 22.</span>
|
||
git clone ssh://git@code.silentmode.st:2222/<you>/<repo>.git</pre>
|
||
|
||
<div class="note"><b>Push over HTTPS</b> uses a Personal Access Token as the password (see §6). Do not paste a wallet passphrase there — it's a Forgejo token, not your wallet.</div>
|
||
|
||
<h2 id="tokens">6. Personal access tokens</h2>
|
||
<p><a href="https://code.silentmode.st/user/settings/applications">Settings → Applications → Generate New Token</a>. Pick the scopes you actually need — the token that pushes commits does not need <code>write:organization</code>.</p>
|
||
<table>
|
||
<tr><th>Scope</th><th>What it does</th></tr>
|
||
<tr><td><code>read:repository</code>, <code>write:repository</code></td><td>Clone / push git over HTTPS, use the repo API</td></tr>
|
||
<tr><td><code>read:package</code>, <code>write:package</code></td><td>Docker login + push/pull on the container registry</td></tr>
|
||
<tr><td><code>write:organization</code></td><td>Create repos under an org you belong to</td></tr>
|
||
<tr><td><code>read:user</code>, <code>write:user</code></td><td>List and revoke your own tokens via API</td></tr>
|
||
</table>
|
||
<div class="note warn"><b>Tokens are shown once.</b> Copy the value on the "generated" screen — after you leave the page, only the last 8 characters are shown. Lost tokens are revoked and re-issued, not recovered.</div>
|
||
|
||
<h2 id="registry">7. Container registry (OCI)</h2>
|
||
<p>Forgejo speaks the standard OCI Distribution API at <code>code.silentmode.st/v2/</code>. Docker + <code>skopeo</code> + <code>buildah</code> + Kubernetes all work as-is.</p>
|
||
|
||
<h4>Log in</h4>
|
||
<pre>docker login code.silentmode.st -u <your-username>
|
||
<span class="cm"># password: a Personal Access Token with write:package scope</span></pre>
|
||
|
||
<h4>Push</h4>
|
||
<pre>docker build -t code.silentmode.st/<owner>/<image>:<tag> .
|
||
docker push code.silentmode.st/<owner>/<image>:<tag></pre>
|
||
<p>Owner is a username or an org you belong to. Owner-org pushes need you to be an <b>Owners</b> team member; add via the admin API or the org settings page.</p>
|
||
|
||
<h4>Pull</h4>
|
||
<pre>docker pull code.silentmode.st/<owner>/<image>:<tag></pre>
|
||
<p><b>Anonymous pull works for public repos.</b> The <code>/v2/</code> endpoint returns 401 to guide clients through the Bearer-challenge flow, but the token endpoint issues an anonymous token for public content. This means external systems like <a href="https://home.runonflux.io/">Flux</a> can pull from Hephaestus without any credential setup.</p>
|
||
|
||
<h4>List packages</h4>
|
||
<pre>curl https://code.silentmode.st/api/v1/packages/<owner>?type=container</pre>
|
||
|
||
<h2 id="api">8. API basics</h2>
|
||
<p>Full Swagger at <a href="https://code.silentmode.st/api/swagger">code.silentmode.st/api/swagger</a>. Auth is a token in a header:</p>
|
||
<pre>curl -H "Authorization: token $TOKEN" https://code.silentmode.st/api/v1/user
|
||
<span class="cm"># or a Bearer if you prefer:</span>
|
||
curl -H "Authorization: Bearer $TOKEN" https://code.silentmode.st/api/v1/user</pre>
|
||
<p>A few endpoints you'll want early:</p>
|
||
<table>
|
||
<tr><th>Endpoint</th><th>What</th></tr>
|
||
<tr><td><code>GET /api/v1/user</code></td><td>Whoami — verify token works</td></tr>
|
||
<tr><td><code>POST /api/v1/user/repos</code></td><td>Create a repo under yourself</td></tr>
|
||
<tr><td><code>POST /api/v1/orgs/<org>/repos</code></td><td>Create a repo under an org</td></tr>
|
||
<tr><td><code>PATCH /api/v1/repos/<o>/<r></code></td><td>Change description / website / private flag</td></tr>
|
||
<tr><td><code>DELETE /api/v1/repos/<o>/<r></code></td><td>Delete a repo (destructive, no undo)</td></tr>
|
||
<tr><td><code>POST /api/v1/repos/<o>/<r>/branches</code></td><td>Copy a branch (rename via copy + delete)</td></tr>
|
||
</table>
|
||
<div class="note info"><b>Encoding gotcha.</b> Descriptions with UTF-8 characters (em-dashes, curly quotes) need <code>Content-Type: application/json; charset=utf-8</code> AND a properly UTF-8-encoded body. Shell interpolation on Windows Git Bash silently CP1252-encodes and the char lands as <code><EFBFBD></code>. If that happens, PATCH again with a Python <code>urllib</code> caller or plain <code>curl --data-binary @body.json</code>.</div>
|
||
|
||
<h2 id="storage">9. Where files live</h2>
|
||
<dl class="spec">
|
||
<dt>git repos</dt> <dd>VPS SSD at <code>/data/git/repositories/<owner>/<repo>.git</code> inside the Forgejo container</dd>
|
||
<dt>Postgres</dt> <dd>SSD at <code>/var/lib/postgresql/data</code> — issues, PRs, users, sessions, tokens</dd>
|
||
<dt>LFS objects</dt> <dd>Sia via S3 at <code>s3.silentmode.st:8600</code>, bucket <code>hephaestus-lfs</code></dd>
|
||
<dt>Release attachments, packages</dt> <dd>Same Sia bucket, different key prefixes</dd>
|
||
<dt>Container images</dt><dd>Same Sia bucket, prefix <code>packages/container/</code></dd>
|
||
<dt>Nightly backup</dt> <dd><code>restic</code> encrypts the whole tree + Postgres dump to a separate Sia bucket, retained 30 days</dd>
|
||
<dt>DR mirror</dt> <dd>Static landing (this page) also mirrored to Sia bucket <code>bns/hephaestus.x/</code>, updated on redeploy</dd>
|
||
</dl>
|
||
|
||
<h2 id="selfhost">10. Self-hosting</h2>
|
||
<p>You can run your own Hephaestus on any Linux box with Docker. Nothing about it depends on Silent Mode's infrastructure — the operator's role is just to own the domain and hold the wallet secrets.</p>
|
||
<pre><span class="cm"># 1. clone</span>
|
||
git clone https://code.silentmode.st/silentmode/hephaestus.git
|
||
cd hephaestus
|
||
|
||
<span class="cm"># 2. configure — copy .env.example, fill in your domain, Sia S3 creds, OIDC secret</span>
|
||
cp .env.example .env
|
||
$EDITOR .env
|
||
|
||
<span class="cm"># 3. run</span>
|
||
docker compose up -d
|
||
|
||
<span class="cm"># 4. first wallet signs in and takes the admin seat</span>
|
||
open https://your-domain.example/user/oauth2/hephaestus-wallet</pre>
|
||
<p>Full deploy notes: <a href="https://code.silentmode.st/silentmode/hephaestus/src/branch/master/README.md">README</a>, and <a href="https://code.silentmode.st/silentmode/hephaestus/src/branch/master/PROTOCOL.md">PROTOCOL.md</a> for the wire format.</p>
|
||
|
||
<h2 id="gotchas">11. Known gotchas</h2>
|
||
<ul>
|
||
<li><b>Signature mismatch on <code>/auth/verify</code></b> almost always means your message string got a trailing newline (Python <code>print</code>, Bash heredoc). Write the message to a file with <code>newline=''</code> and hash the file bytes directly.</li>
|
||
<li><b>OIDC callback URL uses port 3000</b> if Forgejo's <code>ROOT_URL</code> is unset. Setting <code>[server].ROOT_URL = https://code.silentmode.st/</code> in <code>app.ini</code> fixes it; browsers can't reach internal port 3000.</li>
|
||
<li><b>Cross-origin session cookies.</b> Starting OAuth on hephaestus.x and finishing on code.silentmode.st loses the session cookie mid-flow. The landing dropdown always kicks off on code.silentmode.st to avoid this.</li>
|
||
<li><b>Renaming a user via admin API</b> takes <code>new_name</code> as a form field, not JSON. JSON returns 422 "NewName required".</li>
|
||
<li><b>Descriptions show as <code><EFBFBD></code></b> when shell → curl → API re-encodes UTF-8 as CP1252. Use a Python <code>urllib</code> PATCH or <code>curl --data-binary @file</code>.</li>
|
||
<li><b>SSH port is 2222, not 22</b>, so the compose stack doesn't fight the host's sshd. Your git remote URL must include <code>:2222</code>.</li>
|
||
<li><b>Docker Hub rate limits</b> can bite <code>docker compose build</code> if you're unauthenticated. Log in once with <code>docker login</code> against Docker Hub before running the stack build.</li>
|
||
</ul>
|
||
|
||
<h2 id="help">12. Help + source</h2>
|
||
<ul>
|
||
<li>Source: <a href="https://code.silentmode.st/silentmode/hephaestus">silentmode/hephaestus</a> (MIT)</li>
|
||
<li>Wire-format spec: <a href="https://code.silentmode.st/silentmode/hephaestus/src/branch/master/PROTOCOL.md">PROTOCOL.md</a></li>
|
||
<li>Auth-proxy verify reference: <a href="https://code.silentmode.st/silentmode/hephaestus/src/branch/master/auth-proxy/src/verify.ts">auth-proxy/src/verify.ts</a></li>
|
||
<li>Silent Mode: <a href="https://silentmode.st/">silentmode.st</a></li>
|
||
<li>Get a <code>.x</code> name for your fork: <a href="https://silentmode.st/bns/sirius.x/">sirius.x</a></li>
|
||
</ul>
|
||
|
||
</main>
|
||
</div>
|
||
|
||
<footer class="deep">
|
||
<div class="fwrap">
|
||
<div>
|
||
<a class="brand" href="../">⚒️ <span class="g">Hephaestus</span>.X</a>
|
||
<p>A code forge you sign in to with a Bitcoin Cash wallet. Open source, self-hostable, backed by Sia.</p>
|
||
</div>
|
||
<div>
|
||
<h5>Product</h5>
|
||
<ul>
|
||
<li><a href="../">Home</a></li>
|
||
<li><a href="./">Docs</a></li>
|
||
<li><a href="https://code.silentmode.st/explore/repos" rel="noopener">Explore repos</a></li>
|
||
<li><a href="https://code.silentmode.st/user/oauth2/hephaestus-wallet">Sign in</a></li>
|
||
</ul>
|
||
</div>
|
||
<div>
|
||
<h5>Source + spec</h5>
|
||
<ul>
|
||
<li><a href="https://code.silentmode.st/silentmode/hephaestus">silentmode/hephaestus</a></li>
|
||
<li><a href="https://code.silentmode.st/silentmode/hephaestus/src/branch/master/PROTOCOL.md">Wallet-auth PROTOCOL</a></li>
|
||
<li><a href="https://code.silentmode.st/silentmode/hephaestus/src/branch/master/auth-proxy">auth-proxy</a></li>
|
||
<li><a href="https://code.silentmode.st/api/swagger">API (Swagger)</a></li>
|
||
</ul>
|
||
</div>
|
||
<div>
|
||
<h5>Pantheon</h5>
|
||
<ul>
|
||
<li><a href="https://silentmode.st/bns/silentmode.x/" rel="noopener">Silentmode.X</a></li>
|
||
<li><a href="https://silentmode.st/bns/sirius.x/" rel="noopener">Sirius.X (names)</a></li>
|
||
<li><a href="https://silentmode.st/bns/theseus.x/" rel="noopener">Theseus.X (browser)</a></li>
|
||
<li><a href="https://silentmode.st/bns/fly.x/" rel="noopener">Fly.X</a></li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
<div class="bar">
|
||
<span>A pillar of <a href="https://silentmode.st/">Silent Mode</a> · alpha on chipnet · one wallet, all of it</span>
|
||
<span class="mono">MIT · self-hostable</span>
|
||
</div>
|
||
</footer>
|
||
|
||
<script defer src="../js/theme.js"></script>
|
||
|
||
</body>
|
||
</html>
|