docs(hephaestus.x): §12 for parallel sessions + dots-are-OK note in §4
Parallel session assumed (a) Forgejo usernames can't contain dots, and (b) a wallet mnemonic has to be pasted into a browser to sign in. Both wrong — the docs now show that. - §4 gets an 'Allowed characters' subsection listing what Forgejo actually accepts (letters, digits, hyphens, underscores, dots). game.x works as-is as a username or an org name. - New §12 'Working with other sessions' — the wallet-vs-PAT split, a full five-step recipe for delegating a namespace to a parallel session (create org / bootstrap user / add to Owners / mint PAT / clone+push), a scriptable programmatic sign-in template so mnemonics never touch a browser, and a scope-per-session-shape table. - TOC + §13 (Help+source) renumbered.
This commit is contained in:
parent
cf2995c73c
commit
f34535721f
1 changed files with 112 additions and 1 deletions
|
|
@ -163,6 +163,7 @@
|
|||
<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="#sessions">Working with other sessions</a></li>
|
||||
<li><a href="#help">Help + source</a></li>
|
||||
</ol>
|
||||
</aside>
|
||||
|
|
@ -217,6 +218,18 @@
|
|||
<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>Allowed characters</h4>
|
||||
<p>Forgejo names — usernames, org names, repo names — accept letters (mixed case), digits, hyphens, underscores, <b>and dots</b>. So all of these are valid:</p>
|
||||
<ul>
|
||||
<li><code>alice</code></li>
|
||||
<li><code>game.x</code> <span class="pill">dots OK</span></li>
|
||||
<li><code>Game.X</code></li>
|
||||
<li><code>game-x</code></li>
|
||||
<li><code>Silent_Mode</code></li>
|
||||
<li><code>bch_qp3hvzy09lzcm46qm4cd</code></li>
|
||||
</ul>
|
||||
<p>Rejected: whitespace, slashes, and reserved routes like <code>assets</code>, <code>login</code>, <code>api</code>. If you're picking a name for an on-chain <code>.x</code> project, the natural mapping (<code>game.x</code> repo owner) works — no need to substitute a hyphen.</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" \
|
||||
|
|
@ -346,7 +359,105 @@ open https://your-domain.example/user/oauth2/hephaestus-wallet</pre>
|
|||
<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>
|
||||
<h2 id="sessions">12. Working with other sessions</h2>
|
||||
<p>Any tool, colleague, or parallel Claude session that needs to push to Hephaestus goes through the same short workflow. Once you internalize the wallet-vs-PAT split, everything else is one API call at a time.</p>
|
||||
|
||||
<h3>Wallet vs Personal Access Token — when to use each</h3>
|
||||
<p>The wallet is for <b>bootstrapping the account</b> once, and for <b>recovering</b> if a PAT ever leaks. Nothing else. Every git operation, every API call, every docker push, from every session, from every day after the first — runs on a PAT.</p>
|
||||
<table>
|
||||
<tr><th>Task</th><th>Auth</th></tr>
|
||||
<tr><td>Create the user account for the first time</td><td>Wallet sign-in</td></tr>
|
||||
<tr><td>Push, pull, clone, edit repo settings</td><td>PAT (<code>write:repository</code>)</td></tr>
|
||||
<tr><td>Docker push / pull to the OCI registry</td><td>PAT (<code>write:package</code>)</td></tr>
|
||||
<tr><td>Create repos under an org, manage members</td><td>PAT (<code>write:organization</code>)</td></tr>
|
||||
<tr><td>Revoke a compromised PAT</td><td>Any PAT with <code>write:user</code>, or admin</td></tr>
|
||||
<tr><td>Rotate ownership (transfer everything to a new wallet)</td><td>Wallet sign-in on the new address + admin transfer</td></tr>
|
||||
</table>
|
||||
<div class="note info"><b>Never share a wallet mnemonic across sessions.</b> A parallel session doesn't need it. Give them a PAT with the narrowest scope that works, and revoke it independently when they're done.</div>
|
||||
|
||||
<h3>Bootstrapping a new namespace for another session</h3>
|
||||
<p>Say a parallel session needs to push code to <code>game.x/checkers</code>. Full recipe, five steps, no browser required for the human at all:</p>
|
||||
<pre><span class="cm"># 1. Admin creates the org (one API call, no wallet involvement)</span>
|
||||
curl -X POST -H "Authorization: token $ADMIN_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"username":"game.x","visibility":"public"}' \
|
||||
https://code.silentmode.st/api/v1/orgs
|
||||
|
||||
<span class="cm"># 2. Whoever owns the game.x wallet signs in ONCE to bootstrap their user.
|
||||
# Either via browser (open /user/oauth2/hephaestus-wallet)
|
||||
# or programmatically (see next subsection — mnemonic never touches a browser).
|
||||
# Result: a user account named `bch_<first-20-of-cashaddr>`.</span>
|
||||
|
||||
<span class="cm"># 3. Admin adds that user to game.x/Owners</span>
|
||||
OWNERS_ID=$(curl -sk -H "Authorization: token $ADMIN_TOKEN" \
|
||||
https://code.silentmode.st/api/v1/orgs/game.x/teams | \
|
||||
python -c "import sys,json; \
|
||||
print(next(t['id'] for t in json.load(sys.stdin) if t['name']=='Owners'))")
|
||||
curl -X PUT -H "Authorization: token $ADMIN_TOKEN" \
|
||||
https://code.silentmode.st/api/v1/teams/$OWNERS_ID/members/bch_<addr>
|
||||
|
||||
<span class="cm"># 4. That user generates a PAT with write:repository scope
|
||||
# at Settings → Applications → Generate New Token.
|
||||
# Hand only the PAT to the parallel session.</span>
|
||||
|
||||
<span class="cm"># 5. Session clones + pushes using the PAT as HTTPS password</span>
|
||||
git clone https://code.silentmode.st/game.x/checkers.git
|
||||
cd checkers && ... && git push</pre>
|
||||
|
||||
<h3>Programmatic wallet sign-in (mnemonic stays local)</h3>
|
||||
<p>If the wallet owner wants to bootstrap without ever opening a browser — for a headless workstation, a CI job, or an AI session that reads its wallet from disk — the whole sign-in flow is scriptable. The mnemonic sits in a local <code>wallets.json</code>, never gets pasted anywhere, never enters a chat log.</p>
|
||||
<p>Sketch (Node + <a href="https://github.com/bitauth/libauth">@bitauth/libauth</a>):</p>
|
||||
<pre>import { deriveHdPath, deriveHdPrivateNodeFromSeed, deriveSeedFromBip39Mnemonic,
|
||||
encodeCashAddress, hash160, hash256, secp256k1, utf8ToBin, binToBase64,
|
||||
CashAddressType } from "@bitauth/libauth";
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
const mnemonic = JSON.parse(readFileSync("wallets.json", "utf8")).main.seed;
|
||||
const seed = deriveSeedFromBip39Mnemonic(mnemonic);
|
||||
const root = deriveHdPrivateNodeFromSeed(seed);
|
||||
const child = deriveHdPath(root, "m/44'/145'/0'/0/0");
|
||||
const pub = secp256k1.derivePublicKeyCompressed(child.privateKey);
|
||||
const cashaddr = encodeCashAddress({ prefix: "bchtest",
|
||||
type: CashAddressType.p2pkh, payload: hash160(pub) }).address;
|
||||
|
||||
<span class="cm">// 1. kick off OIDC → get state + redirect_uri</span>
|
||||
const kick = await fetch("https://code.silentmode.st/user/oauth2/hephaestus-wallet",
|
||||
{ redirect: "manual" });
|
||||
const url = new URL(kick.headers.get("location"));
|
||||
const state = url.searchParams.get("state");
|
||||
const redirect_uri = url.searchParams.get("redirect_uri");
|
||||
|
||||
<span class="cm">// 2. challenge → nonce + message</span>
|
||||
const { nonce, message } = await (await fetch(
|
||||
"https://code.silentmode.st/auth/challenge",
|
||||
{ method: "POST", headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ cashaddr, state, redirect_uri }) })).json();
|
||||
|
||||
<span class="cm">// 3. sign — Bitcoin Signed Message (see PROTOCOL.md for the exact bytes)</span>
|
||||
const signature = signBitcoinMessage(child.privateKey, message); <span class="cm">// see PROTOCOL.md</span>
|
||||
|
||||
<span class="cm">// 4. verify → callback URL</span>
|
||||
const { redirect } = await (await fetch(
|
||||
"https://code.silentmode.st/auth/verify",
|
||||
{ method: "POST", headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ nonce, signature, state }) })).json();
|
||||
|
||||
<span class="cm">// 5. follow callback with a cookie-jar client → session cookie in hand
|
||||
// → GET /user/settings/applications to mint a PAT for future use</span></pre>
|
||||
<p>Reference implementations in the tree: <a href="https://code.silentmode.st/silentmode/hephaestus/src/branch/master/auth-proxy/src/verify.ts"><code>auth-proxy/src/verify.ts</code></a> for byte-perfect signing, and <a href="https://code.silentmode.st/silentmode/hephaestus/src/branch/master/PROTOCOL.md"><code>PROTOCOL.md</code></a> for the full wire format. Adapt for <code>bitcoincash:</code> / <code>bchreg:</code> by swapping the <code>prefix</code>.</p>
|
||||
|
||||
<h3>Sensible PAT scopes per session shape</h3>
|
||||
<table>
|
||||
<tr><th>Session shape</th><th>Scopes</th><th>Rationale</th></tr>
|
||||
<tr><td>CI job pushing container images</td><td><code>write:package</code></td><td>Nothing else needed</td></tr>
|
||||
<tr><td>Docs / content writer</td><td><code>write:repository</code></td><td>Push commits, edit metadata</td></tr>
|
||||
<tr><td>Ops session managing an org</td><td><code>write:organization</code>, <code>write:repository</code></td><td>Create repos, adjust members</td></tr>
|
||||
<tr><td>Analytics / read-only reader</td><td><code>read:repository</code>, <code>read:package</code></td><td>Zero write surface</td></tr>
|
||||
<tr><td>Dedicated admin session</td><td>Admin account + <code>write:organization</code>, <code>write:user</code></td><td>Full backend</td></tr>
|
||||
</table>
|
||||
<p><b>Rotation habit:</b> issue one PAT per session, scope narrowly, revoke the moment the session ends or a machine changes hands. Every token has a name — use it (<code>ci-fly-x-deploy</code>, <code>session-2026-09-20</code>) so you know which one to revoke when things move.</p>
|
||||
|
||||
<h2 id="help">13. 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>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue