1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
|
# dandokmang status console
Static status page for the dandokmang homelab server. A cron job runs
`render.sh`, which runs every check script in `checks/`, collects their
output, and writes a static `index.html` served by Caddy on the WireGuard
interface only.
## Layout
```
statuspage/
checks/ one script per metric or group of related metrics
lib/
common.sh shared helpers (svc_status, port_check, jail_status, json_line)
render.sh orchestrator + HTML renderer
README.md
```
## The check-script contract
This is the part to remember when you come back in six months to add a
metric: **every script in `checks/` is independent. `render.sh` never
changes to support a new check.**
A check script must:
- Be executable (`chmod +x`).
- Emit **one JSON object per line** (JSONL) to stdout, and nothing else.
- Emit no output to stdout on failure (send diagnostics to stderr if you
want them, but they're discarded by the renderer).
Each JSON object has exactly these fields:
| field | meaning |
|-----------|-------------------------------------------------------------------|
| `section` | groups rows under a heading: `hardware`, `host services`, `jails` (or a new section name - unrecognized sections are rendered after the known ones, in the order first seen) |
| `label` | display name for the row |
| `value` | display value; use `""` for a plain up/down check with no value |
| `status` | one of `ok`, `warn`, `down` - maps to a green/yellow/red square |
Example (`checks/wireguard.sh`):
```json
{"section": "host services", "label": "wireguard (wg0)", "value": "", "status": "ok"}
```
A script can emit multiple lines - `hw.sh` emits one line each for load
avg, uptime, free mem, zroot usage, and zpool health.
Use `json_line SECTION LABEL VALUE STATUS` from `lib/common.sh` instead of
hand-rolling the JSON - it escapes backslashes, quotes, and embedded
newlines (e.g. a multi-line `zpool status -x` when a pool is degraded) so
the JSONL stays one object per line no matter what a command prints.
### Adding a new check
1. Create `checks/<section>-<name>.sh` (or reuse an existing section
script if the metric belongs with it).
2. `. "$(dirname "$0")/../lib/common.sh"` and call `json_line` for each row.
3. `chmod +x` it.
That's it - no changes to `render.sh`, no template edits. It picks up any
executable `checks/*.sh` file automatically and renders whatever sections
it finds.
## Failure isolation
`render.sh` runs each check script and handles three failure modes without
aborting the whole render:
- **Non-zero exit** -> a `down` row labeled with the script's filename under
a synthetic `checks` section.
- **No output** -> a `warn` row, same treatment.
- **Output that doesn't look like our JSONL** -> a `warn` row, same
treatment.
So a broken check shows up as a visible red/yellow row on the page instead
of silently disappearing or crashing the render.
## Dependencies
Parsing is done with plain `awk`/`sed`/`grep` against the constrained JSONL
shape our own `json_line` helper produces - no `jq` dependency. If checks
ever need to emit richer JSON (nested objects, arrays), the awk parser in
`render.sh` (`jsonval()`) will need to grow or be swapped for `jq`.
## Deployment
Cron (as root):
```
* * * * * /usr/local/etc/statuspage/render.sh
```
Caddyfile (bind to the WireGuard interface IP only):
```
172.16.0.1:8080 {
root * /usr/local/www/status
file_server
}
```
Reload Caddy after editing: `service caddy reload`.
Access from a WireGuard peer: `http://172.16.0.1:8080`.
`render.sh` writes to `/usr/local/www/status/index.html` by default;
override with the `STATUSPAGE_OUT` environment variable (useful for local
testing without root).
|