README.md: rework against the third review
check / check (push) Canceled after 0s

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:16:00 +00:00
parent a3b18b6ce8
commit a6d68262cf
+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 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 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 the next round is due. A check that times out is recorded as a timeout, and so
recorded as a timeout. A check that fails with a network error is recorded as is one that answers after that limit. A check that fails with a network error is
unreachable. recorded as unreachable.
A round can start early, after an interval change or when the recovery probe 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 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 - 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. 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 When the page records a failed check, it writes one line to the browser console
`console.error`, and the same line to the debug log. The discarded first round, with `console.error`, and the same line to the debug log. Nothing is written for
the checks a round gives up and the recovery probe write none. The line gives the discarded first round, for a check a round gives up, or by the recovery
the target's name and URL, the time, what failed and how long the request took. probe. The line gives the target's name and URL, the time, what failed and how
The three kinds of failure are a timeout, an answer over the time limit, and a long the request took. The three kinds of failure are a timeout, an answer over
network error. For a network error the line quotes the error the browser gives the time limit, and a network error. For a network error the line quotes the
the page, such as `TypeError: Failed to fetch`, which does not say why. When a error the browser gives the page, such as `TypeError: Failed to fetch`, which
target answers again after failed checks, one `console.info` line gives its does not say why. When a target answers again after failed checks, one
latency and how many checks in a row had failed. `console.info` line gives its latency and how many checks in a row had failed.
### Browsers ### 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 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. 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 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 next report. So a server that stored them but answered late gets them twice.
twice.
A failed delivery is quiet. The page writes one debug log line when deliveries 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. 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 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 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. 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 seconds 100 minutes. Changing the interval clears the history. The cleared
samples not yet delivered are kept apart, the last 100 per host, until they are samples are kept apart, the last 100 per host, and those not yet delivered go in
delivered. the next report.
So at the 1-second interval, one refused or failed report loses the oldest 20 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 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 - refuses a request body over 1 MiB with nginx's own 413 error page; the backend
never sees such a request never sees such a request
- requests `/.well-known/healthcheck` through nginx every 30 seconds, as its - requests `/.well-known/healthcheck` through nginx every 30 seconds, as its
`HEALTHCHECK`. The check fails unless both nginx and the backend answer, and `HEALTHCHECK`. The check fails unless both nginx and the backend answer. It
while report files cannot be written. Three failures in a row make the also fails while report files cannot be written. Three failures in a row make
container `unhealthy`; one pass makes it `healthy` again. the container `unhealthy`; one pass makes it `healthy` again.
- on `docker stop`, writes the waiting reports to disk and exits 0. If nginx or - on `docker stop`, writes the waiting reports to disk and exits 0. If that
the backend exits on its own, the other is stopped and the container exits final write fails, the container exits non-zero. If nginx or the backend exits
non-zero, so the platform restarts it. on its own, the other is stopped and the container exits non-zero, so the
platform restarts it.
### Running under upaas ### 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 - `PORT` is nginx's public port, from 1 to 65535 and not `8081`, where the
backend listens inside the container. backend listens inside the container.
- `DATA_DIR` must be `/data` or a path below it, written in full, with no - `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 `.` or `..` part and no extra `/`. A path through a symbolic link is
leads out of `/data` is refused too. refused unless the link is relative and stays inside `/data`.
- `TRUSTED_PROXIES` names the proxies nginx trusts, as IP addresses or CIDRs - `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`. 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 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. only the addresses that nothing but the proxy connects from.
- Health: the image's `HEALTHCHECK`, described above. - Health: the image's `HEALTHCHECK`, described above.
A variable set to a value the container cannot use stops it at start, with the The container stops at start when a variable holds a value it cannot use. The
reason in its log. reason is in its log.
## Settings ## Settings
@@ -328,9 +328,8 @@ reason in its log.
`~/.config/netwatch-server/`, when one exists. A variable in the environment `~/.config/netwatch-server/`, when one exists. A variable in the environment
wins over a file. wins over a file.
A variable set to a value the server cannot use stops it from starting, with an The server does not start when a variable holds a value it cannot use. Its error
error naming the variable. An empty variable counts as unset, except names the variable. An empty variable counts as unset, except `TRUSTED_PROXIES`.
`TRUSTED_PROXIES`.
- `BIND_ADDRESS`, default empty: the IP address to listen on. Empty listens on - `BIND_ADDRESS`, default empty: the IP address to listen on. Empty listens on
every interface. The image sets it to `127.0.0.1`. 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 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 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 version. The request still gets a 500 answer. The server does not start if
the start. On shutdown the server waits up to 2 seconds for Sentry to finish Sentry rejects the DSN. On shutdown the server waits up to 2 seconds for Sentry
sending. Empty, the server sends nothing to Sentry. to finish sending. Empty, the server sends nothing to Sentry.
### Logging ### 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 - `CONFIG` holds the timing and display constants. The interval menu sets its
`updateInterval`; the check timeout, the history span and the time axis are `updateInterval`; the check timeout, the history span and the time axis are
computed from it. computed from it.
- `WAN_HOSTS` is the list of WAN targets, with the URL and method each is - `WAN_HOSTS` is the list of WAN targets, each with its URL. The S3 entries also
checked with. name HEAD as their method.
- `HostState` is one host's history, latest latency and status. - `HostState` is one host's history, latest latency and status.
- `AppState` holds the WAN hosts, the local hosts, the pause state and the round - `AppState` holds the WAN hosts, the local hosts, the pause state and the round
count, and computes the summary and the health. count, and computes the summary and the health.
- `measureLatency` runs one check. - `measureLatency` runs one check.
- `tick` runs one round. It checks every host in parallel and draws each row as - `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 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 - `SparklineRenderer` draws a graph on a canvas, scaled to the device's pixel
ratio. ratio.
- `buildUI` builds the page; `updateHostRow`, `updateSummary` and - `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 ### The backend
`netwatch-server` is a Go HTTP server wired with `fx`. Its packages are in `netwatch-server` is a Go HTTP server. The `fx` library starts and stops its
`internal/`: 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 - `cli` runs the command line. Given no arguments, it runs the server. It also
runs the `check-cidr` and `prepare-data-dir` subcommands, which runs the `check-cidr` and `prepare-data-dir` subcommands, which
@@ -588,7 +588,7 @@ it on port 8080.
This repository adheres to the This repository adheres to the
[Scripts to Rule Them All](https://github.com/github/scripts-to-rule-them-all) [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 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 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`, broken. Every script that runs yarn on the host does so through `script/yarn`,
except `script/bootstrap`. except `script/bootstrap`.