Silent Mode Hephaestus — the docker-compose + Caddyfile + auth-proxy that runs code.silentmode.st and hephaestus.x. BCH wallet auth via OIDC, Sia cold storage, restic backups. Open source.
Architecture shift: hephaestus.x used to be Sia-bucket-only (via bns-gateway's
s3: record type). Now it's VPS-served with Silent Mode CA cert; the Sia bucket
stays populated as a disaster-recovery mirror that a single Argus CLI call
(update.js flipping ip: → s3:) can promote back to primary if the VPS dies.
Every code.silentmode.st URL now also works at hephaestus.x — the same Caddy
handles both hostnames; hephaestus.x additionally checks /srv/hephaestus.x/
for a matching static file first, so /forge/, /silentmode/theseus/, and the
other landing pages don't get eaten by Forgejo's dynamic routes.
- caddy/Caddyfile: add hephaestus.x server block with the Argonautica-issued
cert; static-first, Forgejo-fallback
- docker-compose.yml: mount ./caddy (whole dir, for certs/) and
/var/www/hephaestus.x → /srv/hephaestus.x
- scripts/sync-hephaestus-x-to-sia.sh: nightly rclone sync as DR backup
Off-tree changes (kept in memory record for future sessions):
- Silent Mode CA issued hephaestus.x.crt (fingerprint
6e251c2bb4987e66890aa28ebc8e16df5dfe03c1fc559634246ae69c2e00400b)
- BCNR record on hephaestus.x flipped to {"ip":"81.31.210.65","tls":"6e251c2b..."}
(TXID fdd301a2ae313636f71ec4dfba9a3ff723a9158b4a3937ac4ce0a83b3f8cda11)
- systemd timer hephaestus-x-sia-sync.timer on the p2pexchange VPS, 03:35 UTC daily
|
||
|---|---|---|
| auth-proxy | ||
| caddy | ||
| scripts | ||
| .env.example | ||
| docker-compose.yml | ||
| PROTOCOL.md | ||
| README.md | ||
Hephaestus
Decentralized code forge. Forgejo web UI on a VPS, Sia network for durable storage, Bitcoin Cash wallet signatures for identity — no email required.
Architecture
User browser
│
├── OIDC login via wallet signature ──▶ auth-proxy ──▶ (issues id_token)
│ │
└── Forgejo web UI ◀─── OIDC handshake ────────────────────┘
│
├── live git repos, Postgres ──▶ local SSD on VPS (hot path)
└── LFS, attachments, packages,
archive downloads, backups ──▶ sia.storage S3 (cold path)
- Identity: users generate or import a BCH wallet client-side. Their
cashaddris their username. Login = signing a challenge. - Hot storage: live
.gitbare repos + Postgres live on the VPS's local disk. Git operations stay fast. - Cold storage: every large or immutable blob (LFS, attachments, release artifacts, package registries) lives on sia.storage via its S3-compatible endpoint. Forgejo speaks S3 natively.
- Backups: nightly
resticsnapshot of repos +pg_dumpto a second bucket. Dual-write to independent storage for the belt-and-suspenders story since sia.storage caps liability at ~$100.
Why sia.storage instead of self-hosted renterd for MVP
- Their free tier gives 50 GB pooled + 4 Gbps throughput with no monthly egress meter
- Standard S3 API — zero code changes to Forgejo
- No Sia wallet management, no SC volatility, no contract-formation ops
- Swap to self-hosted
renterdlater without touching Forgejo config (both speak S3)
Quota math
The 50 GB sia.storage free tier is one pooled resource we allocate to Forgejo users via Forgejo's built-in per-user quotas.
| Our tier | Per-user quota | Users on Free (50 GB) | Users on Plus (500 GB) | Users on Pro (5 TB) |
|---|---|---|---|---|
| Free | 500 MB | ~100 | ~1,000 | ~10,000 |
| Pro | 10 GB | ~5 | ~50 | ~500 |
| Team | 50 GB | ~1 | ~10 | ~100 |
Quickstart
cp .env.example .env
# fill in SIA_STORAGE_ACCESS_KEY, SIA_STORAGE_SECRET_KEY, SIA_STORAGE_BUCKET
docker compose up -d
open http://localhost:3000
First launch:
- Forgejo initialises Postgres and its schema
- Auth-proxy generates its OIDC signing keypair
- On first browser visit, the login page offers "Generate new BCH wallet" or "Import wallet"
- After signing the challenge you're logged into Forgejo — your cashaddr is your username
Directory layout
Hephaestus/
├── docker-compose.yml # forgejo + postgres + auth-proxy
├── .env.example
├── forgejo/
│ └── app.ini.template # config with sia.storage S3 + OIDC + quotas
├── auth-proxy/
│ ├── Dockerfile
│ ├── package.json
│ └── src/ # Fastify OIDC provider
│ ├── index.ts
│ ├── oidc.ts
│ ├── verify.ts # BCH signed-message verification
│ └── sessions.ts
├── auth-proxy/public/
│ ├── login.html # Wallet UI
│ └── wallet.js # Client-side keygen + signing (libauth)
├── scripts/
│ ├── smoke-test-s3.sh # Verify sia.storage handles multipart LFS
│ └── backup-restic.sh # Nightly backup
└── PROTOCOL.md # Wallet-auth flow spec
Status
- MVP scaffolding: complete
- Wallet auth: functional (challenge + signature verify, no persistence beyond cashaddr)
- sia.storage backend: config template ready; must run
scripts/smoke-test-s3.shbefore trusting LFS - Restic backup: template ready, needs cron wiring
- WalletConnect: deferred to Phase 2
- BCNR-name registration for
code.silentmode.st: separate task in Ariadne stack
Known unknowns
- sia.storage S3 multipart maturity. Their roadmap targets full S3 compatibility Q3 2026 — partial today. Smoke test verifies this before we depend on it for LFS.
- Account-recovery UX for wallet loss. No email = no password reset. Users must back up their mnemonic. Docs will hammer this. Phase 2: social recovery via BCH multisig.
- Content moderation workflow. sia.storage ToS bans certain content classes; we need our own AUP + takedown flow before public launch.