diff --git a/hephaestus.x/docs/index.html b/hephaestus.x/docs/index.html new file mode 100644 index 0000000..885d486 --- /dev/null +++ b/hephaestus.x/docs/index.html @@ -0,0 +1,386 @@ + + + + + +Hephaestus docs โ€” how to sign in, push, pull, self-host + + + + + + + + + + + + +
+ + + +
+ +

Hephaestus docs

+

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.

+ +

1. What Hephaestus is

+

Hephaestus is a Forgejo 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.

+
+
runs on
Docker Compose ยท Caddy + Postgres + Forgejo 10 + auth-proxy
+
sign-in
BCH wallet signature โ†’ OIDC callback โ†’ Forgejo session
+
usernames
Auto: bch_<first-20-of-your-cashaddr>. Renameable to anything else.
+
storage
Live git + Postgres on SSD; LFS/releases/packages spill to Sia via s3.silentmode.st
+
source
silentmode/hephaestus โ€” MIT, open
+
+ +

2. Two hostnames, one instance

+

The same Forgejo is reachable at two addresses. Same account, same repos, same session cookie โ€” differs only in how you got there:

+ + + + +
HostHow to reach itCert
hephaestus.xA BCNR-aware browser (Theseus, Ariadne) resolves it directly, or through the public gateway at navigate.st/bns/hephaestus.x/Silent Mode Argonautica CA
code.silentmode.stRegular DNS + Let's Encrypt. Works in any browser, no resolver setupLet's Encrypt (public)
+
Which one to use? If you're setting up an OAuth callback, a docker registry, or a git remote โ€” use code.silentmode.st. It works everywhere. Use hephaestus.x when the whole path is BCNR-aware.
+ +

3. Sign in with a wallet

+

The sign-in button gives you three routes:

+ + +

What happens on the wire

+
    +
  1. Forgejo redirects you to the auth-proxy's /auth/authorize.
  2. +
  3. Your browser POSTs {cashaddr, state} to /auth/challenge and gets back a nonce + message.
  4. +
  5. The wallet signs the message. Standard "Bitcoin Signed Message" (varint-prefixed magic + double-SHA256 + secp256k1 recoverable-compact + base64).
  6. +
  7. Browser POSTs {nonce, signature, state} to /auth/verify. The auth-proxy recovers the pubkey, re-hashes to a cashaddr, matches, mints an Ed25519-signed id_token, and hands back a Forgejo callback URL.
  8. +
  9. Following the callback lands you in the dashboard with a session cookie.
  10. +
+ +

If you use the flow programmatically (no browser)

+

Every step is a JSON POST. The wallet-signing bit is the only crypto: sign the exact message string the server returned โ€” no trailing newline. See auth-proxy/src/verify.ts for the byte-perfect reference. A working sample lives in PROTOCOL.md.

+ +

4. Rename your username

+

The auto-generated bch_<addr-prefix> is a placeholder. Rename yourself to anything else โ€” BitcoinCash, alice, whatever's not taken.

+ +

From the UI

+

Settings โ†’ Profile โ†’ Username. Change the field, save. All your repo URLs move to /<new-name>/โ€ฆ and the old URLs 307-redirect for a while so bookmarks survive.

+ +

From the admin API (if you're the operator)

+
# The endpoint expects FORM-encoded, not JSON. If you send JSON it silently returns 422 "NewName required".
+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
+ +

5. Create + push a repo

+

Standard Forgejo. Nothing custom.

+ +

Create

+ + +

Clone + push

+
# HTTPS โ€” always works, no key setup
+git clone https://code.silentmode.st/<you>/<repo>.git
+
+# SSH โ€” needs your key uploaded under Settings โ†’ SSH keys. Port is 2222, not 22.
+git clone ssh://git@code.silentmode.st:2222/<you>/<repo>.git
+ +
Push over HTTPS 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.
+ +

6. Personal access tokens

+

Settings โ†’ Applications โ†’ Generate New Token. Pick the scopes you actually need โ€” the token that pushes commits does not need write:organization.

+ + + + + + +
ScopeWhat it does
read:repository, write:repositoryClone / push git over HTTPS, use the repo API
read:package, write:packageDocker login + push/pull on the container registry
write:organizationCreate repos under an org you belong to
read:user, write:userList and revoke your own tokens via API
+
Tokens are shown once. 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.
+ +

7. Container registry (OCI)

+

Forgejo speaks the standard OCI Distribution API at code.silentmode.st/v2/. Docker + skopeo + buildah + Kubernetes all work as-is.

+ +

Log in

+
docker login code.silentmode.st -u <your-username>
+# password: a Personal Access Token with write:package scope
+ +

Push

+
docker build -t code.silentmode.st/<owner>/<image>:<tag> .
+docker push code.silentmode.st/<owner>/<image>:<tag>
+

Owner is a username or an org you belong to. Owner-org pushes need you to be an Owners team member; add via the admin API or the org settings page.

+ +

Pull

+
docker pull code.silentmode.st/<owner>/<image>:<tag>
+

Anonymous pull works for public repos. The /v2/ 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 Flux can pull from Hephaestus without any credential setup.

+ +

List packages

+
curl https://code.silentmode.st/api/v1/packages/<owner>?type=container
+ +

8. API basics

+

Full Swagger at code.silentmode.st/api/swagger. Auth is a token in a header:

+
curl -H "Authorization: token $TOKEN" https://code.silentmode.st/api/v1/user
+# or a Bearer if you prefer:
+curl -H "Authorization: Bearer $TOKEN" https://code.silentmode.st/api/v1/user
+

A few endpoints you'll want early:

+ + + + + + + + +
EndpointWhat
GET /api/v1/userWhoami โ€” verify token works
POST /api/v1/user/reposCreate a repo under yourself
POST /api/v1/orgs/<org>/reposCreate a repo under an org
PATCH /api/v1/repos/<o>/<r>Change description / website / private flag
DELETE /api/v1/repos/<o>/<r>Delete a repo (destructive, no undo)
POST /api/v1/repos/<o>/<r>/branchesCopy a branch (rename via copy + delete)
+
Encoding gotcha. Descriptions with UTF-8 characters (em-dashes, curly quotes) need Content-Type: application/json; charset=utf-8 AND a properly UTF-8-encoded body. Shell interpolation on Windows Git Bash silently CP1252-encodes and the char lands as ๏ฟฝ. If that happens, PATCH again with a Python urllib caller or plain curl --data-binary @body.json.
+ +

9. Where files live

+
+
git repos
VPS SSD at /data/git/repositories/<owner>/<repo>.git inside the Forgejo container
+
Postgres
SSD at /var/lib/postgresql/data โ€” issues, PRs, users, sessions, tokens
+
LFS objects
Sia via S3 at s3.silentmode.st:8600, bucket hephaestus-lfs
+
Release attachments, packages
Same Sia bucket, different key prefixes
+
Container images
Same Sia bucket, prefix packages/container/
+
Nightly backup
restic encrypts the whole tree + Postgres dump to a separate Sia bucket, retained 30 days
+
DR mirror
Static landing (this page) also mirrored to Sia bucket bns/hephaestus.x/, updated on redeploy
+
+ +

10. Self-hosting

+

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.

+
# 1. clone
+git clone https://code.silentmode.st/silentmode/hephaestus.git
+cd hephaestus
+
+# 2. configure โ€” copy .env.example, fill in your domain, Sia S3 creds, OIDC secret
+cp .env.example .env
+$EDITOR .env
+
+# 3. run
+docker compose up -d
+
+# 4. first wallet signs in and takes the admin seat
+open https://your-domain.example/user/oauth2/hephaestus-wallet
+

Full deploy notes: README, and PROTOCOL.md for the wire format.

+ +

11. Known gotchas

+ + +

12. Help + source

+ + +
+
+ + + + + + + diff --git a/hephaestus.x/index.html b/hephaestus.x/index.html index 2bf2df5..bc3d457 100644 --- a/hephaestus.x/index.html +++ b/hephaestus.x/index.html @@ -167,8 +167,21 @@ 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)} - 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} - footer a{color:var(--mut)} footer a:hover{color:var(--ink)} + /* Deep multi-column footer (shared with docs) */ + footer.deep{border-top:1px solid var(--line);margin-top:4rem;padding:2.4rem 1.2rem 1.6rem;background:rgba(10,15,28,.4);color:var(--dim);font-size:13px} + 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){ footer.deep .fwrap{grid-template-columns:1fr 1fr;gap:24px} } + @media (max-width:520px){ footer.deep .fwrap{grid-template-columns:1fr} } @media (max-width:640px){ .topnav{padding:8px 1rem;gap:0} .topnav a{padding:6px 8px;font-size:13px} @@ -182,6 +195,7 @@