vitrine
=======
A tiny read-only webserver for putting local source trees on display.
Point it at some directories via a small config file, and it serves them:
markdown renders to HTML, directories become listings,
and `?fmt=raw` on any URL serves the file bytes directly.
No javascript required, no uploads, no auth, no dynamic anything beyond rendering.
Config
------
vitrine takes exactly one config file, via the required `-config` flag
(there is no default location and no search).
The format is YAML:
```yaml
exhibits:
moonshot-1:
path: "%"
meta/research/some-topic:
path: ../elsewhere/some-topic
some-repo:
path: ../src/some-repo
expose-git: true
```
- `exhibits` maps URL prefixes to directories.
A prefix may contain slashes; the intermediate URL levels then serve synthetic listings
that reveal only the mounted subtrees.
- `path` (required) is the directory to serve.
The placeholder `"%"` means "same as the exhibit name" --
the common case where the URL space mirrors the directory layout.
Relative paths resolve against the config file's directory,
which makes a config portable alongside the trees it describes.
Absolute paths also work.
Trailing slashes on names and paths are accepted and normalized away.
- `expose-git` (optional, default false): see below.
A bad config (parse error, unknown key, missing path, or zero exhibits)
is a startup error: vitrine refuses to run rather than serve the wrong thing.
Config changes apply live: vitrine watches the config file
(inotify for immediate pickup, plus a 5-second stat poll as backstop,
so a change lands within ~5s worst case, usually instantly).
At runtime the failure mode flips from fail-loud to fail-stable:
a reload that fails to parse, or names a directory that can't be opened,
logs a loud REJECTED line and the previous exhibits keep serving untouched.
Edit freely; a typo mid-edit can't take the server dark.
Theming
-------
Each host wears its own accent color, derived from its hostname,
so when several vitrines from several hosts are open in tabs,
each is recognizable at a glance.
The hostname's first dot-segment (e.g. `langoustine` from `langoustine.exe.xyz`)
hashes to a hue; that name is also shown next to the site name in the nav,
so the color never has to carry the meaning alone.
The derivation is deterministic: the same hostname always yields the same color,
on every build and every host.
The `-theme-seed` flag substitutes any string for the hostname segment --
useful for testing, or if you dislike the color your hostname landed on.
Callouts
--------
Markdown can carry callout boxes (notes, warnings, tips, and the like)
using colon fences:
```
:::warning Optional title text
body content, which is **full markdown**
:::
```
- A line of three-or-more colons followed by a non-empty remainder opens a callout.
The first word of the remainder is the callout *type*; anything after it is an optional *title*.
- A line of three-or-more colons alone closes it.
- The body is ordinary markdown: lists, emphasis, links, and code all work inside.
The rendered box is `
`
(matching the markdown-it-container / VuePress convention,
so docs authored for those tools render as intended).
The styled types are `info`, `note`, `tip`, `success`, `todo`, `warning`, `danger`, and `reject`,
each with its own accent color and icon; `info` and `note` ride the per-host accent.
`warn` is accepted as an alias for `warning`.
An unknown type still renders as a plain neutral box.
Callouts do not nest, by design,
and an unterminated callout simply runs to the end of the document rather than breaking it.
Diagrams
--------
A fenced code block tagged `dot` (or its alias `graphviz`)
renders server-side to inline SVG, so diagrams need no JavaScript in the page.
Write the DOT source in a normal fenced block:
````
```dot
digraph { rankdir=LR; a -> b -> c; b [shape=box]; }
```
````
- The source language is Graphviz's DOT.
Rendering runs entirely in-process via an embedded Graphviz WebAssembly build,
so there is no external `dot` binary to install and no browser involved.
- A diagram whose DOT fails to compile does not break the page:
it falls back to showing its own source in a code block, flagged with the parse error,
so the author sees exactly what did not render.
- Any other code language (or none) renders as an ordinary code block, unchanged.
expose-git
----------
By default, `.git` directories are invisible: hidden from listings and unreachable by direct URL.
Setting `expose-git: true` on an exhibit flips that:
`.git` lists and serves like any other directory,
which makes `git clone http://host/exhibit/.git` work over git's dumb HTTP transport.
Two caveats, read them before enabling:
- **vitrine writes into that exhibit's `.git`.**
Dumb-HTTP clone requires `.git/info/refs` and `.git/objects/info/packs`,
and ordinary commits never maintain them,
so vitrine runs `git update-server-info` on the exhibit at startup
and at every successful config reload.
This is a deliberate exception to vitrine's otherwise strictly read-only stance.
- **Dumb HTTP has dumb semantics.**
Clones reflect the refs as of the last `update-server-info` run,
so commits made after the last startup or config reload are invisible to clones
until the next one (touching the config file suffices to trigger a reload).
A stale clone succeeds silently at the old tip; there is no warning.
Concurrent `git gc` in the exhibit can also break an in-flight clone (loudly; retry works).
Deployment
----------
Run vitrine as your own user, under a systemd user unit. No root anywhere.
1. Build and install the binary somewhere on your user's path:
```
go build -o ~/bin/vitrine .
```
2. Write your config, e.g. `~/fusion/.vitrine.cfg`,
with paths relative to that directory (see Config above).
3. Install the unit file from this repo as a *user* unit:
```
mkdir -p ~/.config/systemd/user
cp vitrine.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now vitrine
```
Adjust `ExecStart` in the unit if your binary or config live elsewhere.
4. Keep it running after logout (user units otherwise stop with your session):
```
loginctl enable-linger $USER
```
Check on it with `systemctl --user status vitrine`
and `journalctl --user -u vitrine -f`.