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. Your browser POSTs {cashaddr, state} to /auth/challenge and gets back a nonce + message.
  3. The wallet signs the message. Standard "Bitcoin Signed Message" (varint-prefixed magic + double-SHA256 + secp256k1 recoverable-compact + base64).
  4. 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.
  5. 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

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

Worked example: publish an app's backend as an image

Any backend that runs in a container publishes the same way. Node example โ€” Dockerfile at the repo root:

# Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
EXPOSE 8080
CMD ["node", "server.js"]

Build + push in one pass:

docker login code.silentmode.st -u <you>
# password: PAT with write:package scope (see ยง6)
docker build -t code.silentmode.st/<owner>/<app>:latest .
docker push code.silentmode.st/<owner>/<app>:latest

The image is now pullable at code.silentmode.st/<owner>/<app>:latest. If the repo is public, Flux + friends can pull it anonymously (ยง7 above). Verify it in /-/packages or via the list-packages API.

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