# 🕸 itsybitsy [![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE) [![AI-DECLARATION: copilot](https://img.shields.io/badge/%E4%B7%BC%20AI--DECLARATION-copilot-fee2e2?labelColor=fee2e2)](https://ai-declaration.md) Serve folders of Markdown to different domains over HTTP, [Spartan](https://portal.mozz.us/gemini/spartan.mozz.us/) and [Nex](https://nightfall.city/nex/info/specification.txt), rendered on the fly with no build step. A Rust re-implementation of [smolweb](https://code.randogoth.com/randogoth/smolweb), which serves one folder from one process with its output formats hardcoded and its configuration in per-file frontmatter. itsybitsy changes all three: one process serves many domains, each output format is a separate crate behind a cargo feature, and configuration lives in a TOML file per content directory. ## Status Early. Configuration and path resolution are implemented and tested; nothing is served yet. | Area | State | | --- | --- | | Server configuration, virtual hosts, `--check` | implemented | | Path resolution, traversal containment, live reload | implemented | | Markdown parsing | planned | | gemtext and HTML output | planned | | HTTP, Spartan and Nex listeners | planned | | 80-column text and Nex output | planned | | WML and XHTML-MP decks | planned | | Gemini and Gopher | planned | ## Configuration Two files. A server file names the virtual hosts and the listeners: ```toml version = 1 [site.smol] root = "/srv/smol/content" hosts = ["smol.place", "www.smol.place"] [listener.web] protocol = "http" bind = "0.0.0.0:8080" formats = ["xhtmlmp", "html"] default_site = "smol" ``` A `.itsybitsy.toml` in any content directory says how the Markdown in **that directory** renders. There is no inheritance: a subdirectory without its own file uses the built-in defaults rather than its parent's. Repetition in a deep tree is the price of never having to look elsewhere to know how a page renders. ```toml [defaults] cache_control = 3600 [page."index.md"] title = "Notes" cache_control = 300 ``` `[defaults]` applies to every Markdown file in the directory; a `[page."name.md"]` table overrides it for one file. `title` is per-page by nature, so setting it in `[defaults]` is an error; left unset, it is derived from the page's first level-1 heading. Each renderer's own keys arrive with that renderer. Editing the file re-renders that directory's pages on the next request, and a file that fails to parse makes them error rather than silently falling back to defaults. Because the name begins with a dot, the same rule that keeps `.secret.md` out of the URL space keeps this file out of it too. `--check` validates the configuration and reports the routing it resolved, without binding a port: ```console $ itsybitsy --config /etc/itsybitsy.toml --check sites: smol: /srv/smol/content hosts: smol.place -> smol www.smol.place -> smol listeners: web: http on 0.0.0.0:8080 [xhtmlmp, html] by host, default smol formats rendered per page: html, xhtmlmp ``` Everything the schema cannot express is checked here rather than on first request: a format no enabled feature provides, two sites claiming one hostname, a content root that does not exist, a server file sitting inside a content root where it would be served. ## URLs Links should be root-relative and extensionless (`[about](/about)`, not `about.md`), so the same link resolves identically from every protocol. | Request | Serves | | --- | --- | | `/` | `index.md` | | `/foo` | `foo.md`, else `foo/index.md` | | `/foo.md` | redirects to `/foo` | | `/img.png` | the file itself, by media type | Nothing outside the content root is reachable. A request target is percent-decoded before it is normalised, so an encoded `..` becomes a real one and gets clamped at the root rather than quietly matching nothing; any path component beginning with a dot is refused outright; and whatever survives is canonicalised and required to still be inside the root, which is what defeats a symlink pointing out of it. There are no generated directory listings — only `index.md`. ## Development ```bash devbox run check # fmt --check, clippy -D warnings, test devbox run test devbox run build ```