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:
+| Host | How to reach it | Cert |
|---|---|---|
| hephaestus.x | A 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.st | Regular DNS + Let's Encrypt. Works in any browser, no resolver setup | Let's Encrypt (public) |
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:
+-
+
- Sign up โ creates a fresh BIP-39 wallet in your browser, shows you the recovery phrase, and encrypts the wallet in
localStoragewith a passphrase you set. easiest
+ - Sign in โ you paste a 12- or 24-word recovery phrase you already have. Same wallet as anywhere else that speaks BIP-39. +
- WizardConnect โ sign with an external wallet (Cashonize / Paytaca) via an encrypted Nostr relay. Keys never leave the wallet. most private +
What happens on the wire
+-
+
- Forgejo redirects you to the auth-proxy's
/auth/authorize.
+ - Your browser POSTs
{cashaddr, state}to/auth/challengeand gets back a nonce + message.
+ - The wallet signs the message. Standard "Bitcoin Signed Message" (varint-prefixed magic + double-SHA256 + secp256k1 recoverable-compact + base64). +
- Browser POSTs
{nonce, signature, state}to/auth/verify. The auth-proxy recovers the pubkey, re-hashes to a cashaddr, matches, mints an Ed25519-signedid_token, and hands back a Forgejo callback URL.
+ - Following the callback lands you in the dashboard with a session cookie. +
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
+-
+
- UI: + menu top-right โ New Repository +
- API:
POST /api/v1/user/reposwith a token
+
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+ +
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.
| Scope | What it does |
|---|---|
read:repository, write:repository | Clone / push git over HTTPS, use the repo API |
read:package, write:package | Docker login + push/pull on the container registry |
write:organization | Create repos under an org you belong to |
read:user, write:user | List and revoke your own tokens via API |
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:
+| Endpoint | What |
|---|---|
GET /api/v1/user | Whoami โ verify token works |
POST /api/v1/user/repos | Create a repo under yourself |
POST /api/v1/orgs/<org>/repos | Create 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>/branches | Copy a branch (rename via copy + delete) |
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>.gitinside 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, buckethephaestus-lfs
+ - Release attachments, packages
- Same Sia bucket, different key prefixes +
- Container images
- Same Sia bucket, prefix
packages/container/
+ - Nightly backup
resticencrypts 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
+-
+
- Signature mismatch on
/auth/verifyalmost always means your message string got a trailing newline (Pythonprint, Bash heredoc). Write the message to a file withnewline=''and hash the file bytes directly.
+ - OIDC callback URL uses port 3000 if Forgejo's
ROOT_URLis unset. Setting[server].ROOT_URL = https://code.silentmode.st/inapp.inifixes it; browsers can't reach internal port 3000.
+ - Cross-origin session cookies. 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. +
- Renaming a user via admin API takes
new_nameas a form field, not JSON. JSON returns 422 "NewName required".
+ - Descriptions show as
๏ฟฝwhen shell โ curl โ API re-encodes UTF-8 as CP1252. Use a PythonurllibPATCH orcurl --data-binary @file.
+ - SSH port is 2222, not 22, so the compose stack doesn't fight the host's sshd. Your git remote URL must include
:2222.
+ - Docker Hub rate limits can bite
docker compose buildif you're unauthenticated. Log in once withdocker loginagainst Docker Hub before running the stack build.
+
12. Help + source
+-
+
- Source: silentmode/hephaestus (MIT) +
- Wire-format spec: PROTOCOL.md +
- Auth-proxy verify reference: auth-proxy/src/verify.ts +
- Silent Mode: silentmode.st +
- Get a
.xname for your fork: sirius.x
+