Add a usage guide and an install script

GUIDE.md covers the parts a README does not: what happens when you click a
link, making the proxy stick, the handful of commands worth knowing, and
what to do when a page comes out wrong — which it will, since extraction is
heuristic.

install.sh builds the workspace, installs the four binaries, writes starter
configs, fetches the blocklists, sets up a systemd user service for the
proxy, and offers to register furst as the default browser. Every
system-changing step can be skipped, and the default-browser change is
prompted rather than assumed; a non-interactive run declines it.

Re-running never overwrites a config that already exists, so it is safe
after editing rules. --uninstall removes binaries, service and desktop
entry while keeping configs, and --purge takes those too.

The script passes --workspace to cargo, which is the mistake to avoid here:
the root package is only furst, so a bare build silently skips the other
three binaries.
This commit is contained in:
nak0x 2026-09-06 23:19:34 +02:00
parent 097a1bfc31
commit 2b017197d0
2 changed files with 497 additions and 0 deletions

225
GUIDE.md Normal file
View File

@ -0,0 +1,225 @@
# Using furst
A practical walkthrough. For what each piece *is*, see [README.md](README.md)
and the per-tool READMEs.
---
## Install
```sh
./install.sh
```
That builds the workspace, installs four binaries to `~/.local/bin`, writes
starter configs, fetches the blocklists, and offers to register furst as your
default browser. It is safe to re-run; it never overwrites a config you have
edited.
```
./install.sh --help # all options
./install.sh --prefix /usr/local/bin
./install.sh --no-default-browser --no-service
./install.sh --uninstall
```
Then install the handlers the rules refer to:
```sh
sudo pacman -S mpv yt-dlp zathura zathura-pdf-mupdf nsxiv surf
```
Nothing breaks if you skip some — a rule whose command is missing falls through
to the next one. `furst --list` shows which are absent.
---
## What happens when you click a link
Nothing to launch. The router is your default browser, and **the reader starts
itself on demand.** Only the proxy runs as a service.
```
click a link, anywhere
furst ─── youtube, vimeo, twitch ──► mpv, H.264 forced, no browser at all
│ *.pdf ──► zathura
*.png *.jpg *.gif ──► nsxiv
│ meet, figma, netflix ──► firefox
│ mailto: magnet: ──► xdg-email, transmission
└────── everything else ────────► furst-serve ──► surf
(reader, minimal HTML)
```
Every reader page has a bar: **refresh · original · browser**. Links inside an
article stay in the reader. When a page genuinely needs the real web, click
**browser** and it opens in Firefox.
Two URLs worth bookmarking:
| | |
|---|---|
| http://127.0.0.1:7714/ | reader home — your links, feeds, search box |
| http://furst.proxy/ | proxy status — rules, requests, share blocked |
---
## Making the proxy stick
The proxy is the one piece that must already be running. `install.sh` sets up a
systemd user service for it:
```sh
systemctl --user status furst-proxy
```
Programs find it through the environment, so add this to `~/.zshrc`:
```sh
eval "$(furst-proxy --env)"
```
`surf`, `curl`, `wget` and most CLI tools read those variables. **Firefox does
not** — set its proxy manually in *Settings → Network Settings → Manual*, to
`127.0.0.1` port `8228`, and tick *Also use this proxy for HTTPS*.
Check it is working: `curl -s -x http://127.0.0.1:8228 http://furst.proxy/`
should return the status page.
---
## Commands worth knowing
```sh
furst --explain <url> # why did that open there? ← the one you'll use most
furst --list # every rule, and what isn't installed
furst-read --text <url> # article as plain text; pipe it to less
furst-read --html <url> # article as minimal HTML
furst-serve --status # is the reader running?
furst-serve --clear # drop cached pages
furst-proxy --test <host> # would this host be blocked?
furst-proxy --update # refresh blocklists (weekly is automatic)
```
`furst --explain` is the habit that makes everything else legible:
```
$ 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
[reader] furst-serve --open https://youtu.be/abc123
[default] surf https://youtu.be/abc123
```
The arrow marks what will actually run. Anything above it that is missing from
`$PATH` is skipped, and marked as such.
---
## Fixing things
Extraction is heuristic, so some pages will come out wrong. In order of effort:
**An article looks truncated or empty.** Click **original** in the bar, or add
`&fresh=1` to the URL to bypass the cache.
**A site consistently extracts badly.** Look at what it kept:
```sh
curl -s https://site.example/article | furst-read --stdin --text https://site.example/article
```
The knobs are `POSITIVE` and `NEGATIVE` in `furst-read/src/extract.rs` — lists
of substrings matched against `class` and `id`. Adding the site's content
wrapper class to `POSITIVE` usually fixes it. Rebuild and reinstall with
`./install.sh`.
**A site should never go to the reader.** Add its host to the `heavy` rule in
`~/.config/furst/rules.toml`.
**Something legitimate is blocked.** Confirm it, then allow it:
```sh
furst-proxy --test cdn.example.com
# add to ~/.config/furst/proxy.toml:
# allow = ["cdn.example.com"]
systemctl --user restart furst-proxy
```
Allow rules win over everything, at any subdomain depth.
**Search says the engine refused.** DuckDuckGo rate-limits repeat visitors.
Point `[search] url` in `~/.config/furst/home.toml` at a SearXNG instance:
```toml
[search]
url = "https://searx.be/search?q={q}"
```
**A page renders blank in the reader.** It is probably client-rendered. There is
no JavaScript here by design — use **browser**.
---
## Tuning the rules
`~/.config/furst/rules.toml` is matched top to bottom, first match wins, and a
rule whose command is missing falls through to the next.
```toml
[[rule]]
name = "my-forum"
hosts = ["forum.example.com"]
run = ["furst-serve", "--open", "{url}"]
[[rule]]
name = "hn-in-terminal"
hosts = ["news.ycombinator.com"]
terminal = true # wraps in $TERMINAL -e
run = ["my-tui-client", "{url}"]
```
| host pattern | matches |
|---|---|
| `example.com` | the apex **and** every subdomain |
| `*.example.com` | the same thing |
| `=example.com` | that exact host only |
| `*` | anything |
Placeholders: `{url}` `{url_enc}` `{host}` `{path}` `{scheme}`. If no argument
mentions `{url}` or `{url_enc}`, the URL is appended last.
Order matters — put specific rules above the catch-all `reader` rule at the
bottom.
---
## Where things live
| | |
|---|---|
| `~/.config/furst/rules.toml` | routing rules |
| `~/.config/furst/home.toml` | reader home links, feeds, search engine |
| `~/.config/furst/proxy.toml` | proxy port, blocklists, block/allow |
| `~/.cache/furst/pages` | rendered pages — `furst-serve --clear` |
| `~/.cache/furst/lists` | blocklists, refreshed weekly |
| `~/.local/share/applications/furst.desktop` | the default-browser registration |
---
## Ports
| | |
|---|---|
| 7714 | reader (`FURST_PORT`) |
| 8228 | proxy (`port` in `proxy.toml`) |
Both bind `127.0.0.1` only. Neither is a network service; do not expose them.

272
install.sh Executable file
View File

@ -0,0 +1,272 @@
#!/usr/bin/env bash
#
# Build and install the furst toolset. Safe to re-run: it never overwrites a
# config you have edited, and every system-changing step can be skipped.
#
# ./install.sh --help
set -euo pipefail
PREFIX="${HOME}/.local/bin"
CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/furst"
CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/furst"
UNIT_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user"
DESKTOP="${XDG_DATA_HOME:-$HOME/.local/share}/applications/furst.desktop"
BINARIES=(furst furst-read furst-serve furst-proxy)
HANDLERS=(mpv yt-dlp zathura nsxiv surf firefox)
MODE=install
DO_BUILD=1
DO_LISTS=1
DO_SERVICE=1
DO_BROWSER=ask
PURGE=0
if [ -t 1 ]; then
B=$'\033[1m'; DIM=$'\033[2m'; GREEN=$'\033[32m'; YELLOW=$'\033[33m'; RED=$'\033[31m'; R=$'\033[0m'
else
B=; DIM=; GREEN=; YELLOW=; RED=; R=
fi
step() { printf '\n%s==>%s %s%s%s\n' "$GREEN" "$R" "$B" "$1" "$R"; }
info() { printf ' %s\n' "$1"; }
skip() { printf ' %s%s%s\n' "$DIM" "$1" "$R"; }
warn() { printf ' %s!%s %s\n' "$YELLOW" "$R" "$1"; }
die() { printf '\n%serror:%s %s\n' "$RED" "$R" "$1" >&2; exit 1; }
usage() {
cat <<'EOF'
install.sh — build and install the furst toolset
USAGE
./install.sh [OPTIONS]
OPTIONS
--prefix <dir> where to install binaries (default ~/.local/bin)
--no-build install from an existing target/release build
--no-lists skip downloading the proxy blocklists
--no-service skip the systemd user service for the proxy
--no-default-browser do not register furst as the default browser
-y, --yes answer every prompt with yes
--uninstall remove binaries, service and desktop entry
--purge with --uninstall, also remove configs and caches
-h, --help this
The install never overwrites an existing config file, so re-running it after
editing your rules is safe.
EOF
}
while [ $# -gt 0 ]; do
case "$1" in
--prefix) PREFIX="${2:?--prefix needs a directory}"; shift 2 ;;
--no-build) DO_BUILD=0; shift ;;
--no-lists) DO_LISTS=0; shift ;;
--no-service) DO_SERVICE=0; shift ;;
--no-default-browser) DO_BROWSER=no; shift ;;
-y|--yes) DO_BROWSER=yes; shift ;;
--uninstall) MODE=uninstall; shift ;;
--purge) PURGE=1; shift ;;
-h|--help) usage; exit 0 ;;
*) die "unknown option $1 (--help for usage)" ;;
esac
done
# ---------------------------------------------------------------- uninstall ---
if [ "$MODE" = uninstall ]; then
step "Removing the service"
if command -v systemctl >/dev/null 2>&1; then
systemctl --user disable --now furst-proxy.service 2>/dev/null || true
rm -f "$UNIT_DIR/furst-proxy.service" "$UNIT_DIR/furst-serve.service"
systemctl --user daemon-reload 2>/dev/null || true
info "stopped and removed"
else
skip "no systemctl"
fi
step "Removing binaries"
for b in "${BINARIES[@]}"; do
if [ -e "$PREFIX/$b" ]; then rm -f "$PREFIX/$b"; info "removed $PREFIX/$b"; fi
done
step "Removing the desktop entry"
rm -f "$DESKTOP"
info "removed $DESKTOP"
warn "your default browser is now unset — pick one in your desktop settings,"
warn "or run: xdg-settings set default-web-browser firefox.desktop"
if [ "$PURGE" = 1 ]; then
step "Purging configs and caches"
rm -rf "$CONFIG_DIR" "$CACHE_DIR" "${XDG_CACHE_HOME:-$HOME/.cache}/furst-read"
info "removed $CONFIG_DIR and caches"
else
step "Kept"
info "$CONFIG_DIR and caches — remove with --uninstall --purge"
fi
printf '\n%sUninstalled.%s\n\n' "$B" "$R"
exit 0
fi
# ------------------------------------------------------------------- build ---
cd "$(dirname "$0")"
if [ "$DO_BUILD" = 1 ]; then
step "Building"
command -v cargo >/dev/null 2>&1 || die "cargo not found — install rust first"
info "$(cargo --version)"
# --workspace matters: the root package is only furst, so a bare build would
# silently skip the other three.
cargo build --release --workspace
info "built 4 binaries"
else
step "Skipping build"
fi
for b in "${BINARIES[@]}"; do
[ -x "target/release/$b" ] || die "target/release/$b missing — run without --no-build"
done
# ----------------------------------------------------------------- install ---
step "Installing to $PREFIX"
mkdir -p "$PREFIX"
for b in "${BINARIES[@]}"; do
install -m755 "target/release/$b" "$PREFIX/$b"
info "$b $(du -h "$PREFIX/$b" | cut -f1)"
done
case ":$PATH:" in
*":$PREFIX:"*) ;;
*) warn "$PREFIX is not on your \$PATH — add this to ~/.zshrc:"
warn " export PATH=\"$PREFIX:\$PATH\"" ;;
esac
export PATH="$PREFIX:$PATH"
# ----------------------------------------------------------------- configs ---
step "Writing starter configs"
mkdir -p "$CONFIG_DIR"
init_config() {
local file="$1" cmd="$2"
if [ -e "$CONFIG_DIR/$file" ]; then
skip "$file already exists, left alone"
else
"$PREFIX/$cmd" --init >/dev/null && info "$file"
fi
}
init_config rules.toml furst
init_config home.toml furst-serve
init_config proxy.toml furst-proxy
# ---------------------------------------------------------------- handlers ---
step "Checking handlers"
missing=()
for h in "${HANDLERS[@]}"; do
command -v "$h" >/dev/null 2>&1 || missing+=("$h")
done
if [ ${#missing[@]} -eq 0 ]; then
info "all present"
else
warn "not installed: ${missing[*]}"
warn "rules naming them fall through to the next handler; install with:"
warn " sudo pacman -S mpv yt-dlp zathura zathura-pdf-mupdf nsxiv surf"
fi
# --------------------------------------------------------------- blocklists ---
if [ "$DO_LISTS" = 1 ]; then
step "Fetching blocklists"
if "$PREFIX/furst-proxy" --update 2>&1 | sed 's/^/ /'; then :; else
warn "could not fetch — the proxy will run with only its built-in rules"
fi
else
step "Skipping blocklists"
skip "run 'furst-proxy --update' later"
fi
# ------------------------------------------------------------------ service ---
if [ "$DO_SERVICE" = 1 ] && command -v systemctl >/dev/null 2>&1; then
step "Installing the proxy service"
mkdir -p "$UNIT_DIR"
cat > "$UNIT_DIR/furst-proxy.service" <<EOF
[Unit]
Description=furst filtering proxy
After=network-online.target
[Service]
ExecStart=$PREFIX/furst-proxy
Restart=on-failure
RestartSec=5
[Install]
WantedBy=default.target
EOF
# The reader starts itself on demand, so this one is offered, not enabled.
cat > "$UNIT_DIR/furst-serve.service" <<EOF
[Unit]
Description=furst reader server (optional; it also starts on demand)
[Service]
ExecStart=$PREFIX/furst-serve
Restart=on-failure
RestartSec=5
[Install]
WantedBy=default.target
EOF
systemctl --user daemon-reload 2>/dev/null || true
if systemctl --user enable --now furst-proxy.service 2>/dev/null; then
info "furst-proxy.service enabled and started"
else
warn "could not start the service — run it yourself with: furst-proxy &"
fi
skip "furst-serve.service written but not enabled (the reader autostarts)"
else
step "Skipping the service"
skip "start the proxy yourself: furst-proxy &"
fi
# ---------------------------------------------------------- default browser ---
step "Default browser"
if [ "$DO_BROWSER" = ask ]; then
if [ -t 0 ]; then
printf ' Register furst as your default browser? [y/N] '
read -r reply
case "$reply" in [yY]*) DO_BROWSER=yes ;; *) DO_BROWSER=no ;; esac
else
DO_BROWSER=no
fi
fi
if [ "$DO_BROWSER" = yes ]; then
"$PREFIX/furst" --install 2>&1 | sed 's/^/ /'
else
skip "not changed — run 'furst --install' when you want it"
fi
# -------------------------------------------------------------------- done ---
cat <<EOF
${B}Installed.${R}
Add to ~/.zshrc so everything uses the proxy:
eval "\$(furst-proxy --env)"
Firefox needs it set manually: Settings > Network Settings > Manual,
127.0.0.1 port 8228, and tick "Also use this proxy for HTTPS".
Reader home http://127.0.0.1:7714/
Proxy status http://furst.proxy/
Read ${B}GUIDE.md${R} next. The command to remember is:
furst --explain <url>
EOF