README.md: rework against the third review

The symbolic link rule, the console line, fx and the Makefile name
are fixed as the review asked. The full pass fixed nine more
sentences: a split subject, a late verb or a claim broader than the
code.

Model: fable-5-1
This commit is contained in:
clawbot
2026-10-26 05:41:11 +00:00
committed by sneak
parent ddd3a83fdf
commit 49fdca1c96
+39 -39
View File
@@ -104,9 +104,9 @@ Hetzner servers close the connection without an answer when either is present.
A check times out after 80% of the interval: 2.4 seconds at the default of 3
seconds, 24 seconds at 30 seconds. So a round's checks have all finished before
the next round is due. A check that times out, or answers after that limit, is
recorded as a timeout. A check that fails with a network error is recorded as
unreachable.
the next round is due. A check that times out is recorded as a timeout, and so
is one that answers after that limit. A check that fails with a network error is
recorded as unreachable.
A round can start early, after an interval change or when the recovery probe
finds a target answering. It then gives up the last round's checks that are
@@ -183,15 +183,15 @@ Pinned rows are sorted by name, and a line separates them from the rest.
- The header shows the local time with its time zone, and the UTC time. The
footer shows the color legend and links to the commit the page was built from.
Each failed check the page records writes one line to the browser console with
`console.error`, and the same line to the debug log. The discarded first round,
the checks a round gives up and the recovery probe write none. The line gives
the target's name and URL, the time, what failed and how long the request took.
The three kinds of failure are a timeout, an answer over the time limit, and a
network error. For a network error the line quotes the error the browser gives
the page, such as `TypeError: Failed to fetch`, which does not say why. When a
target answers again after failed checks, one `console.info` line gives its
latency and how many checks in a row had failed.
When the page records a failed check, it writes one line to the browser console
with `console.error`, and the same line to the debug log. Nothing is written for
the discarded first round, for a check a round gives up, or by the recovery
probe. The line gives the target's name and URL, the time, what failed and how
long the request took. The three kinds of failure are a timeout, an answer over
the time limit, and a network error. For a network error the line quotes the
error the browser gives the page, such as `TypeError: Failed to fetch`, which
does not say why. When a target answers again after failed checks, one
`console.info` line gives its latency and how many checks in a row had failed.
### Browsers
@@ -218,8 +218,7 @@ host, the page remembers the time of the newest sample it delivered, and moves
that mark only when the server answers 2xx. Paused rounds are never sent. While
the page is paused, no report is sent at all. One report is in flight at a time.
A report with no answer after 30 seconds is abandoned, and its samples go in the
next report. A server that stored them but answered late therefore gets them
twice.
next report. So a server that stored them but answered late gets them twice.
A failed delivery is quiet. The page writes one debug log line when deliveries
start failing and one when they recover. The checks go on.
@@ -227,9 +226,9 @@ start failing and one when they recover. The checks go on.
A sample waits for delivery only as long as the history holds it. The history
keeps each host's last 100 rounds: 5 minutes at the default interval of 3
seconds. At 1 second it spans 100 seconds, at 2 seconds 200 seconds, and at 60
seconds 100 minutes. Changing the interval clears the history, but the cleared
samples not yet delivered are kept apart, the last 100 per host, until they are
delivered.
seconds 100 minutes. Changing the interval clears the history. The cleared
samples are kept apart, the last 100 per host, and those not yet delivered go in
the next report.
So at the 1-second interval, one refused or failed report loses the oldest 20
seconds of each host's samples: the next delivered report comes 2 minutes after
@@ -284,12 +283,13 @@ The image:
- refuses a request body over 1 MiB with nginx's own 413 error page; the backend
never sees such a request
- requests `/.well-known/healthcheck` through nginx every 30 seconds, as its
`HEALTHCHECK`. The check fails unless both nginx and the backend answer, and
while report files cannot be written. Three failures in a row make the
container `unhealthy`; one pass makes it `healthy` again.
- on `docker stop`, writes the waiting reports to disk and exits 0. If nginx or
the backend exits on its own, the other is stopped and the container exits
non-zero, so the platform restarts it.
`HEALTHCHECK`. The check fails unless both nginx and the backend answer. It
also fails while report files cannot be written. Three failures in a row make
the container `unhealthy`; one pass makes it `healthy` again.
- on `docker stop`, writes the waiting reports to disk and exits 0. If that
final write fails, the container exits non-zero. If nginx or the backend exits
on its own, the other is stopped and the container exits non-zero, so the
platform restarts it.
### Running under upaas
@@ -304,8 +304,8 @@ The [upaas](https://git.eeqj.de/sneak/upaas) app for netwatch needs:
- `PORT` is nginx's public port, from 1 to 65535 and not `8081`, where the
backend listens inside the container.
- `DATA_DIR` must be `/data` or a path below it, written in full, with no
`.` or `..` part and no extra `/`. A path through a symbolic link that
leads out of `/data` is refused too.
`.` or `..` part and no extra `/`. A path through a symbolic link is
refused unless the link is relative and stays inside `/data`.
- `TRUSTED_PROXIES` names the proxies nginx trusts, as IP addresses or CIDRs
separated by commas. The backend's own setting is fixed to `127.0.0.1/32`.
With `TRUSTED_PROXIES` set and empty, nginx trusts no proxy. Every client
@@ -317,8 +317,8 @@ The [upaas](https://git.eeqj.de/sneak/upaas) app for netwatch needs:
only the addresses that nothing but the proxy connects from.
- Health: the image's `HEALTHCHECK`, described above.
A variable set to a value the container cannot use stops it at start, with the
reason in its log.
The container stops at start when a variable holds a value it cannot use. The
reason is in its log.
## Settings
@@ -328,9 +328,8 @@ reason in its log.
`~/.config/netwatch-server/`, when one exists. A variable in the environment
wins over a file.
A variable set to a value the server cannot use stops it from starting, with an
error naming the variable. An empty variable counts as unset, except
`TRUSTED_PROXIES`.
The server does not start when a variable holds a value it cannot use. Its error
names the variable. An empty variable counts as unset, except `TRUSTED_PROXIES`.
- `BIND_ADDRESS`, default empty: the IP address to listen on. Empty listens on
every interface. The image sets it to `127.0.0.1`.
@@ -507,9 +506,9 @@ refused.
With `SENTRY_DSN` set, each request whose handling crashes is reported to that
Sentry project, under the release `netwatch-server-` followed by the server's
version. The request still gets a 500 answer. A DSN Sentry does not accept stops
the start. On shutdown the server waits up to 2 seconds for Sentry to finish
sending. Empty, the server sends nothing to Sentry.
version. The request still gets a 500 answer. The server does not start if
Sentry rejects the DSN. On shutdown the server waits up to 2 seconds for Sentry
to finish sending. Empty, the server sends nothing to Sentry.
### Logging
@@ -541,15 +540,16 @@ The page is built with Vite and Tailwind CSS v4. All its code is in
- `CONFIG` holds the timing and display constants. The interval menu sets its
`updateInterval`; the check timeout, the history span and the time axis are
computed from it.
- `WAN_HOSTS` is the list of WAN targets, with the URL and method each is
checked with.
- `WAN_HOSTS` is the list of WAN targets, each with its URL. The S3 entries also
name HEAD as their method.
- `HostState` is one host's history, latest latency and status.
- `AppState` holds the WAN hosts, the local hosts, the pause state and the round
count, and computes the summary and the health.
- `measureLatency` runs one check.
- `tick` runs one round. It checks every host in parallel and draws each row as
soon as its check ends. When the last check ends, it redraws every row, the
summary and the health box, and sorts the rows when a sort is due.
summary and the health box, and sorts the rows when a sort is due. A discarded
round draws nothing.
- `SparklineRenderer` draws a graph on a canvas, scaled to the device's pixel
ratio.
- `buildUI` builds the page; `updateHostRow`, `updateSummary` and
@@ -559,8 +559,8 @@ The page is built with Vite and Tailwind CSS v4. All its code is in
### The backend
`netwatch-server` is a Go HTTP server wired with `fx`. Its packages are in
`internal/`:
`netwatch-server` is a Go HTTP server. The `fx` library starts and stops its
parts in dependency order. The backend's packages are in `internal/`:
- `cli` runs the command line. Given no arguments, it runs the server. It also
runs the `check-cidr` and `prepare-data-dir` subcommands, which
@@ -588,7 +588,7 @@ it on port 8080.
This repository adheres to the
[Scripts to Rule Them All](https://github.com/github/scripts-to-rule-them-all)
standard. The scripts in `script/` are the entrypoints for the development
workflow, and the Makefile targets are thin shims that call them. The scripts
workflow, and the `Makefile` targets are thin shims that call them. The scripts
cover both the page and the Go backend, so `make check` fails if either one is
broken. Every script that runs yarn on the host does so through `script/yarn`,
except `script/bootstrap`.