The catch-all rule now sends anything that did not match a more specific rule to furst-serve, while `default` stays a plain browser. Because rules fall through when their command is missing, that arrangement needs no new mechanism: if furst-serve is not installed the reader rule is skipped and the browser still gets the URL. Firefox drops to a short list of sites that genuinely need it. Every reader page carries a browser link, so the list can stay short. Adds a README for furst-serve and rewrites the top-level one around the whole pipeline: what it is for, what each piece does, measured output sizes, and the limits — client-rendered pages, rate-limited search endpoints, DRM.
105 lines
3.7 KiB
Markdown
105 lines
3.7 KiB
Markdown
# furst-serve
|
|
|
|
A local reader server. It fetches pages, strips them to what you came for, and
|
|
serves them from `127.0.0.1` as small documents with no scripts, no
|
|
stylesheets, no webfonts and no third-party requests.
|
|
|
|
The point is the link rewriting. Every link on a served page points back at
|
|
`/read?u=…`, so following one keeps you in the reader instead of dropping you
|
|
onto the live site. Combined with the disk cache, that is what makes browsing
|
|
on a Core2 feel immediate.
|
|
|
|
## Use
|
|
|
|
```sh
|
|
furst-serve # run in the foreground on 127.0.0.1:7714
|
|
furst-serve --open <url> # start it if needed, then open <url> in a browser
|
|
furst-serve --status
|
|
furst-serve --init # write a starter home.toml
|
|
furst-serve --clear # drop the page cache
|
|
```
|
|
|
|
`--open` is what [furst](../README.md)'s catch-all rule calls. It starts a
|
|
detached server when nothing is listening, waits for the port, then execs
|
|
`$FURST_BROWSER`, `$BROWSER`, or the first light browser on `$PATH`.
|
|
|
|
## Routes
|
|
|
|
| | |
|
|
|---|---|
|
|
| `/` | home: pinned links and feeds, and the search box |
|
|
| `/read?u=` | the page — article, site view, or link index |
|
|
| `/search?q=` | search results |
|
|
| `/feed?u=` | an RSS or Atom feed |
|
|
| `/go?u=` | hand the original URL to the heavy browser |
|
|
|
|
Add `&fresh=1` to `/read` or `/feed` to bypass the cache. Every page carries
|
|
**refresh**, **original** and **browser** links in the bar, so nothing is a
|
|
dead end.
|
|
|
|
## What it does with a page
|
|
|
|
1. **A feed** — rendered as a list of entries, whether it was requested at
|
|
`/feed` or just turned out to be XML.
|
|
2. **A site with an adapter** — Hacker News gets stories with score and
|
|
author, and comment threads with their indent preserved.
|
|
3. **An article** — extracted by [furst-read](../furst-read/README.md).
|
|
4. **Anything else** — if there is too little prose to be an article, the
|
|
page's links are listed instead. A front page has no article to find, and
|
|
saying so beats rendering an empty column.
|
|
|
|
Non-documents (a PDF, an image) redirect to the original rather than being fed
|
|
to an article extractor.
|
|
|
|
## Configuration
|
|
|
|
`~/.config/furst/home.toml`:
|
|
|
|
```toml
|
|
[[link]]
|
|
name = "Arch Wiki"
|
|
url = "https://wiki.archlinux.org/"
|
|
|
|
[[feed]]
|
|
name = "LWN"
|
|
url = "https://lwn.net/headlines/newrss"
|
|
|
|
[search]
|
|
url = "https://html.duckduckgo.com/html/?q={q}"
|
|
```
|
|
|
|
| Environment | |
|
|
|---|---|
|
|
| `FURST_PORT` | default 7714 |
|
|
| `FURST_BROWSER` / `BROWSER` | browser for `--open` |
|
|
| `FURST_HEAVY_BROWSER` | the `/go` target, default `firefox` |
|
|
| `XDG_CACHE_HOME` | pages land in `furst/pages` under it |
|
|
|
|
## On search
|
|
|
|
Search is engine-agnostic on purpose. **Every free HTML endpoint eventually
|
|
rate-limits a repeat visitor** — DuckDuckGo and searx.be both served this
|
|
machine a challenge page while it was being written. So:
|
|
|
|
- Known result shapes (DuckDuckGo, SearXNG) are tried first, then heading
|
|
links, then any link. An unknown engine that serves plain HTML still works.
|
|
- A challenge page is detected from the body, not the status, because they
|
|
arrive as 200 or 202 rather than an error. You get a page saying which
|
|
engine refused and where to change it, never a silently empty result list.
|
|
- Blocked searches are not cached.
|
|
|
|
If your engine rate-limits you, point `[search] url` at a SearXNG instance.
|
|
|
|
Engines that render results client-side cannot work here — there is no
|
|
JavaScript, by design.
|
|
|
|
## Design notes
|
|
|
|
The HTTP layer is blocking, hand-rolled, GET only, one response per
|
|
connection, with a fixed pool of four worker threads. No async runtime:
|
|
predictable memory matters more than concurrency on a 4GB machine. Only the
|
|
loopback interface is ever bound.
|
|
|
|
Cache entries carry a schema number, so changing the renderer drops them
|
|
rather than serving stale markup.
|