88 lines
3.4 KiB
Markdown
88 lines
3.4 KiB
Markdown
|
|
# smolweb
|
||
|
|
|
||
|
|
Serve a folder of Markdown as [Gemini](https://geminiprotocol.net/) capsules,
|
||
|
|
[Spartan](https://portal.mozz.us/gemini/spartan.mozz.us/) capsules, and HTTP
|
||
|
|
pages content-negotiated between WML (WAP 1.x) and XHTML-MP (WAP 2.0 and
|
||
|
|
modern browsers) — all rendered on the fly, no build step.
|
||
|
|
|
||
|
|
```bash
|
||
|
|
smolweb --root ./content --host example.com
|
||
|
|
```
|
||
|
|
|
||
|
|
Every listener binds by default: Gemini on `:1965` (TLS), Spartan on `:300`,
|
||
|
|
HTTP on `:8080`. An empty address disables one:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
smolweb --root ./content --host example.com --spartan ""
|
||
|
|
```
|
||
|
|
|
||
|
|
## Rendering
|
||
|
|
|
||
|
|
Markdown is parsed once per file (cached by mtime, re-parsed the moment the
|
||
|
|
file changes) and rendered into every format from the same source:
|
||
|
|
|
||
|
|
- **gemtext** via [md2txt](https://code.randogoth.com/randogoth/md2txt)'s `gemini` renderer.
|
||
|
|
- **WML** and **XHTML-MP** via [wapdown](https://code.randogoth.com/randogoth/wapdown).
|
||
|
|
|
||
|
|
Both libraries read the same leading `---` frontmatter block independently
|
||
|
|
and ignore keys they don't recognise, so one file can freely mix wapdown's
|
||
|
|
deck-structure keys (`title`, `split_level`, `deck_per_card`, ...) with
|
||
|
|
md2txt's typography keys (`links_per_block`, ...).
|
||
|
|
|
||
|
|
wapdown's `{.card Title}` card-break directive has no meaning to md2txt's
|
||
|
|
parser, so it is stripped before gemtext rendering rather than leaking
|
||
|
|
through as literal text — a card break already renders as nothing in
|
||
|
|
XHTML-MP for the same reason (cards are a WAP 1.x screen-budget concern;
|
||
|
|
a Gemini client or browser just scrolls through one continuous document).
|
||
|
|
|
||
|
|
## URLs
|
||
|
|
|
||
|
|
Links should be root-relative and extensionless (`[about](/about)`, not
|
||
|
|
`about.md`) — the same link then resolves identically from a Gemini,
|
||
|
|
Spartan, or HTTP client:
|
||
|
|
|
||
|
|
| Request | Serves |
|
||
|
|
| --- | --- |
|
||
|
|
| `/` | `index.md` |
|
||
|
|
| `/foo` | `foo.md`, else `foo/index.md` |
|
||
|
|
| `/foo.md` | redirects to `/foo` |
|
||
|
|
| `/img.png` | served as-is, by MIME type |
|
||
|
|
|
||
|
|
A document with `deck_per_card: true` gets its own URL space for WML only:
|
||
|
|
`/trail` serves the whole document (the menu deck, if it has one), and
|
||
|
|
`/trail/weather` serves one card's own deck directly. Gemini, Spartan, and
|
||
|
|
an HTTP client negotiating gemtext/XHTML-MP all redirect `/trail/weather`
|
||
|
|
back to `/trail` instead — that URL space only ever makes sense for WML.
|
||
|
|
|
||
|
|
## HTTP content negotiation
|
||
|
|
|
||
|
|
| Accept | Served as |
|
||
|
|
| --- | --- |
|
||
|
|
| `text/vnd.wap.wml` (literal, non-wildcard) | WML |
|
||
|
|
| `application/vnd.wap.xhtml+xml` (literal, non-wildcard) | XHTML-MP |
|
||
|
|
| anything else, including a `*/*` wildcard or no header at all | the same XHTML-MP body, as `text/html` |
|
||
|
|
|
||
|
|
A `*/*` wildcard never selects WML — a browser's `Accept: text/html,
|
||
|
|
application/xhtml+xml;q=0.9,*/*;q=0.8` must never be handed WML through the
|
||
|
|
wildcard. `?format=wml`, `?format=xhtml`, or `?format=html` overrides
|
||
|
|
negotiation outright, for testing without real WAP hardware.
|
||
|
|
|
||
|
|
## TLS
|
||
|
|
|
||
|
|
If `--cert`/`--key` don't exist, a self-signed EC P-256 certificate is
|
||
|
|
generated on first run (via `openssl`, so devbox must provide it), valid for
|
||
|
|
ten years. Gemini clients use TOFU (trust-on-first-use) fingerprint pinning
|
||
|
|
rather than CA validation, so this is the norm rather than a warning.
|
||
|
|
|
||
|
|
## Development
|
||
|
|
|
||
|
|
```bash
|
||
|
|
devbox run sync # install everything, including wapdown/md2txt
|
||
|
|
devbox run test # pytest
|
||
|
|
devbox run run # serve ./content on localhost, Spartan on :3000 (unprivileged)
|
||
|
|
```
|
||
|
|
|
||
|
|
`pyproject.toml` currently points `wapdown`/`md2txt` at local paths (see the
|
||
|
|
comment there) rather than their git origins, since this checkout carries
|
||
|
|
unpushed changes to both.
|