Furst/README.md
nak0x ec79c07b0f Add furst, a URL router for low-end hardware
Sits where the default browser used to and dispatches each URL to the
cheapest tool that can handle it: mpv for video, zathura for PDFs, a light
WebKit browser for reading, Firefox only when nothing else will do.

Rules live in ~/.config/furst/rules.toml and are matched in order. When a
rule's command is missing from $PATH the router falls through to the next
match, then to the default, so a config may name tools that do not exist
yet without breaking today.

- host patterns match apex plus subdomains, with = for exact and * for any
- paths glob case-insensitively, contains matches the raw URL
- {url} {url_enc} {host} {path} {scheme} placeholders, URL appended if unused
- terminal = true wraps a handler in $TERMINAL -e for TUI tools
- --explain shows the parse and every candidate in priority order
- --install registers a .desktop entry as the system default browser

Host parsing strips userinfo with rfind('@') so that
https://bank.example@evil.example/ routes on evil.example. Handlers are
exec'd rather than forked, leaving no process behind.
2026-09-06 19:18:13 +02:00

103 lines
3.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# furst
A URL router. It sits where your default browser used to, and sends each URL to
the cheapest tool that can actually handle it — `mpv` for video, a pager for
PDFs, a light WebKit browser for reading, and Firefox only when nothing else
will do.
Built for a Core2 Duo with 4GB of RAM, where the browser is the problem.
## Why
A news page is 25MB across 80+ requests with 13MB of JavaScript to parse and
JIT. The same article extracted is ~20KB. Choosing a lighter *engine* buys
23×; not loading the payload at all buys 10100×. `furst` is the dispatcher
that decides which of those you get, per URL.
## Install
```sh
cargo build --release
install -Dm755 target/release/furst ~/.local/bin/furst
furst --init # writes ~/.config/furst/rules.toml, probes for a light browser
furst --install # registers furst as the system default browser
```
`--install` writes `~/.local/share/applications/furst.desktop` and points
`xdg-settings` at it, so every link click in every application routes here.
## Use
```sh
furst <url> # match a rule and exec its command
furst --explain <url> # show what would run, and why; run nothing
furst --list # show the loaded rules
```
`--explain` is the one you want when a URL goes somewhere surprising:
```
$ furst --explain https://youtu.be/abc123
scheme https
host youtu.be
path /abc123
-> [video] mpv --ytdl-format=bestvideo[vcodec^=avc1][height<=?720]+... https://youtu.be/abc123
[default] surf https://youtu.be/abc123
```
## Rules
`~/.config/furst/rules.toml`. First matching rule wins. **If its command is
missing from `$PATH`, furst falls through to the next matching rule, and
finally to `default`** — which is what lets you name tools you have not written
yet and have the config stay working today.
```toml
default = ["surf", "{url}"]
[[rule]]
name = "video"
hosts = ["youtube.com", "youtu.be"]
run = ["mpv", "--ytdl-format=bestvideo[vcodec^=avc1][height<=?720]+bestaudio/best", "{url}"]
[[rule]]
name = "hn"
hosts = ["news.ycombinator.com"]
terminal = true # wrap in $TERMINAL -e
run = ["furst-hn", "{url}"]
```
A rule matches when every criterion it *states* is satisfied; a criterion is
satisfied by any one of its patterns. A rule that states nothing matches
everything.
| Key | Matches against |
|---|---|
| `schemes` | `https`, `mailto`, `magnet`, … |
| `hosts` | `example.com` = apex **and** every subdomain; `*.example.com` = same; `=example.com` = that host exactly; `*` = any |
| `paths` | path component only, glob with `*`, case-insensitive |
| `contains` | substring of the whole raw URL |
| Placeholder | |
|---|---|
| `{url}` `{url_enc}` | the URL, raw or percent-encoded |
| `{host}` `{path}` `{scheme}` | parsed components |
If no argument mentions `{url}` or `{url_enc}`, the URL is appended last.
## Notes for old hardware
- **Force H.264 for video.** A Core2 handles 720p `avc1` in software but stalls
on VP9/AV1, which is what YouTube serves by default. That format string is
doing more work than the resolution cap.
- Host matching strips userinfo with `rfind('@')`, so
`https://bank.example@evil.example/` routes on `evil.example`.
- `furst` `exec`s the handler rather than forking, so it leaves no process
behind.
## License
MIT