sirius-press/README.md
Silent Mode 17c60b9e11 fix(sirius-press): the exporter could never reach its own site in Docker
Deployed to a real VPS for the first time. The stack came up, the patched
wizard served, the plugins activated and the 40-check auth suite passed over
the public internet — and then every single export failed:

  cURL error 28: Failed to connect to <public ip> port 8081 after 10001 ms

Not a bug in the export. A container cannot reach the host's own published
port on most Docker hosts; there is simply no route back in. The exporter
renders each page by asking the site for it over HTTP, so the one thing it
depends on is the one thing a container cannot do. This would have hit every
user of the documented install path on their first publish, and the error
says nothing about the cause.

SIRIUS_PRESS_LOOPBACK_URL gives the exporter a second address — in the
bundled stack, the nginx service on the compose network — while the request
still carries the site's real Host header. That matters twice: WordPress
renders exactly the page a visitor gets rather than redirecting to a
canonical URL the container cannot follow, and the internal hostname never
appears in the exported HTML. Verified on the VPS: the page builds, the
content is right, no absolute self-links, no `//web/` leaking out.

Also documents the question underneath it — what WP_HOME should be when the
site's public address is a BCNR name. Not the name: whatever the server is
actually reachable at. The exported links are document-relative, so they work
under the name regardless.
2026-09-22 22:43:30 +02:00

204 lines
8.6 KiB
Markdown

# Sirius Press
WordPress, with the mailbox taken out of the middle of it.
An account on a Sirius Press site is a Bitcoin Cash address. Signing in means
signing a challenge with the key that controls it. There is no registration
email, no confirmation link, no password reset — and no list of your readers'
email addresses sitting in a database waiting to leak.
Everything the site publishes is also mirrored, as plain static HTML, to the
BCNR name it belongs to. When the server is down, being upgraded, or seized,
the name keeps serving the last thing it published.
It is still WordPress. Yoast, WooCommerce, Contact Form 7 and the rest of the
ecosystem install and run; roles, capabilities, nonces and the REST API are
untouched. The fork's diff against upstream core is **one file, 75 lines**
everything else is plugins.
---
## What this is for
You own a BCNR name. You want a real site behind it — one with an editor, a
media library, plugins, a theme you did not have to write — without the two
things WordPress assumes and this project rejects:
1. **That identity comes from email.** It does not. A mailbox is rented from
somebody who can close it, read it, or be compelled to hand it over. A key
is not.
2. **That the site lives at one server.** Sirius Press publishes a static copy
of every page to your name's storage on Sia, signed by the key that owns the
name. Readers reach that copy through any BCNR resolver, with no DNS and no
certificate authority in the path.
Email itself still works. `wp_mail()` is not disabled — configure SMTP and your
contact forms send, your WooCommerce receipts arrive. The objection was never
to email as a feature. It was to email as an identity.
---
## Getting it running
On a fresh Ubuntu 22.04 or 24.04 server:
```bash
curl -fsSL https://silentmode.st/sirius-press/install.sh | sudo bash
```
That installs Docker if needed, brings up MariaDB + PHP-FPM + nginx, and prints
the URL to finish setup. It takes about five minutes on a small VPS, most of it
building the PHP image.
For shared hosting, a `.zip` you can upload is on
[the releases page](https://code.silentmode.st/silentmode/sirius-press/releases).
Full instructions, including how to point your name at it:
**[docs/install.md](docs/install.md)**.
---
## How it works
### Signing in
The login page issues a short, single-use challenge. You sign it, the server
recovers the public key from the signature, derives the address, and looks up
the account. Nothing is typed but the signature.
Three ways to produce it, in order of preference:
- **A wallet the browser already has.** Theseus exposes one; the key never
touches the page.
- **A recovery phrase typed into the login form.** Used once, in the page, then
wiped. It is never transmitted — only the signature is.
- **Any Bitcoin Cash wallet at all.** The challenge is a BIP-137 message, the
format Electron Cash has had a "Sign message" box for since forever. Copy the
text, sign it on a machine that never touches the web, paste the result back.
This path works with JavaScript switched off.
The signature becomes an ordinary WordPress session cookie. Everything
downstream — `current_user_can()`, nonces, REST permissions, every plugin that
checks a capability — behaves exactly as it does on stock WordPress.
**There is no password reset, because there is nothing to reset.** If you lose
your phrase, the account is gone. An administrator can point an account at a
new address from the user editor, which makes recovery a decision a human
makes about a person they recognise — not something an attacker triggers by
compromising a mailbox. [docs/accounts.md](docs/accounts.md) says this at more
length, and the fork's "lost password" page says it to your users.
### Publishing
Publish a post and the affected pages — the post, the home page, the archives
it belongs to — are queued. Draining the queue renders each page over a
loopback request, rewrites its links to be document-relative, and PUTs it to
`/api/site/<name>/<path>` on the BNS gateway, signed by the key that owns the
name.
Signing happens one of two ways, and the choice is yours:
| | Where the key lives | Unattended publishing |
|---|---|---|
| **Manual** (default) | Nowhere. You type the phrase on the Publishing screen, it signs in your browser and uploads straight to the gateway. | No — the queue waits for you. |
| **Automatic** | Encrypted in the database, under `SIRIUS_PRESS_KEY` from `wp-config.php`. | Yes — cron drains the queue. |
Automatic is genuinely convenient and genuinely risky, and the settings screen
says so in those words. The gateway only accepts writes signed by the name's
current on-chain owner, so the key that can publish for your name is the key
that owns your name and its funds. There is no middle option where the server
holds something weaker; that is a property of the protocol, not a gap in this
fork. [docs/publishing.md](docs/publishing.md) has the details.
---
## Layout
```
plugins/
sirius-press-core/ wallet crypto, gateway client, settings, the inbox
sirius-press-auth/ wallet sign-in, registration, the recovery page
sirius-press-sia-export/ the static export queue and uploader
sirius-press-compat/ shims for plugins that insist on an admin email
mu-plugins/ the bits that must load before plugins do
wordpress/ WordPress itself, vendored as a subtree, patched
patches/ a generated record of the core diff — one file
docker/ compose stack: MariaDB, PHP-FPM, nginx
tools/ build, upstream update, release
tests/ 138 unit checks plus a live end-to-end suite
docs/
```
### Where WordPress itself is
In `wordpress/`, as a git subtree, already patched. That is what gets built
and what gets shipped — there is no download step and no checksum to trust at
build time.
Upstream releases arrive through `git subtree merge` against a branch of
pristine imports, which three-way merges them against the fork's own commit
on top. `tools/update-wordpress.sh <version>` does the whole thing;
`patches/` keeps a generated, readable record of the 75 lines that differ
from stock WordPress, so nobody has to read a 3,800-file log to find out what
this fork changes in core.
The trade is repository size — WordPress is about 149 MB. Worth it for a fork
that can absorb a security release in a minute.
[docs/upstream-merges.md](docs/upstream-merges.md) has the procedure.
---
## Requirements
- PHP 7.4+ — 8.2 or newer recommended
- **GMP or BCMath.** Not optional: the wallet cryptography runs in PHP. GMP is
about twenty times faster and is what the Docker image uses.
- 64-bit PHP — address checksums need 40-bit arithmetic
- OpenSSL, for encrypting a stored publishing key
- MySQL 5.7+ / MariaDB 10.4+
---
## Testing
```bash
tests/run.sh # 138 checks, no framework, about a second
node tests/live.mjs # 40 more, against a running instance
```
The suite that matters most is `tests/interop.mjs`, which pins the browser
wallet against the PHP one. Both implement secp256k1, RFC 6979, BIP-32 and
BIP-137 independently, and every vector came from libauth — the library the
Sirius portal wallet and the BNS gateway both use. A signature made in a
browser verifies on the server and at the gateway, or the suite fails.
### What has actually been run
On WordPress 7.1.1, against a live instance:
- The patched setup wizard asks for a wallet and installs with or without one.
- Sign-in, registration, replay refusal, wrong-key refusal, altered-text
refusal and purpose separation — 40 checks, all through real HTTP with real
session cookies.
- Yoast SEO 28.6, Contact Form 7 6.1.7 and WooCommerce 11.1.1 install,
activate, and leave every Sirius Press screen rendering cleanly.
- Publishing: 29 files signed by the server and accepted by a gateway running
the real verification logic, read back intact, with a wrong key refused 403.
- Signing in **in an actual browser**, which found two bugs the HTTP suites
structurally could not — see docs/testing.md.
- Taking WordPress **7.1.2** through `tools/update-wordpress.sh`: imported,
three-way merged into the subtree, clean, tests still green.
- **`install.sh` on a real Ubuntu 24.04 VPS**, start to finish: Docker stack
built, MariaDB healthy, the patched wizard served, plugins activated, and
the 40-check auth suite run against it over the public internet.
Not yet run: an upload to the real gateway under a registered name — the
signing is verified, the byte has not been written.
[docs/testing.md](docs/testing.md) says how to do it and what it proves.
---
## Licence
GPL-2.0-or-later, inherited from WordPress.