frugal-iot-server 2.0.8 → 2.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/config.d/email.yaml +1 -1
- package/config.d/opensearch.yaml +19 -15
- package/config.d/schema/modules.yaml +13 -0
- package/config.d/schema/topics.yaml +50 -0
- package/{GRAFANA.md → docs/GRAFANA.md} +37 -6
- package/{INSTALL-OPENSEARCH.md → docs/INSTALL-OPENSEARCH.md} +183 -97
- package/docs/OPENWRT.md +208 -0
- package/extras/frugaliot.service +1 -1
- package/extras/mosquitto.conf +1 -1
- package/frugal-iot-createdb.sql +10 -1
- package/frugal-iot-server.js +28 -7
- package/lib/forwarders.js +49 -10
- package/lib/grafana.js +2 -2
- package/lib/language-requests.js +50 -0
- package/lib/opensearch-access.js +9 -2
- package/package.json +7 -6
- package/public/service-worker.js +10 -2
- package/scripts/addbridge-pi.zsh +1 -1
- package/scripts/addbridge-prod.zsh +2 -2
- package/scripts/addorganization.zsh +11 -2
- package/scripts/diagnostic.zsh +4 -4
- package/scripts/grafana-token.js +1 -1
- package/scripts/init.zsh +5 -1
- package/scripts/install-openwrt.sh +709 -0
- package/scripts/install-pi.sh +46 -12
- package/scripts/openwrt-preflight.sh +363 -0
- /package/{INSTALLATION.md → docs/INSTALLATION.md} +0 -0
|
@@ -1,25 +1,22 @@
|
|
|
1
1
|
# Installing OpenSearch, Grafana and Dashboards
|
|
2
2
|
|
|
3
3
|
This is for **browsing and charting your readings**. All of it is optional - the server and logger work
|
|
4
|
-
without any of it, and the readings are always safe in the CSV files under `data
|
|
4
|
+
without any of it, and the readings are always safe in the CSV files under `data/`,
|
|
5
|
+
and the client has its own graphing.
|
|
5
6
|
|
|
6
7
|
## What the pieces are
|
|
7
8
|
|
|
8
|
-
OpenSearch is **two programs
|
|
9
|
+
OpenSearch is **two programs**:
|
|
9
10
|
|
|
10
11
|
- **OpenSearch** is the database. It stores and searches the readings, and listens on port 9200.
|
|
11
|
-
- **OpenSearch Dashboards** is a separate web app for searching and charting what is in OpenSearch.
|
|
12
|
-
|
|
12
|
+
- **OpenSearch Dashboards** is a separate web app for searching and charting what is in OpenSearch.
|
|
13
|
+
It access readings from OpenSearch at port 9200, using one login in its own
|
|
13
14
|
configuration file. (It also keeps its own saved dashboards *inside* OpenSearch, in an index.)
|
|
14
15
|
|
|
15
|
-
**Grafana** is a third, unrelated program that makes charts. It can read the server directly
|
|
16
|
-
([GRAFANA.md](GRAFANA.md)), needing no OpenSearch at all.
|
|
17
|
-
|
|
18
16
|
## The ways to use it
|
|
19
17
|
|
|
20
18
|
| | Readings are kept in | You look at them with | Set up |
|
|
21
19
|
|---|---|---|---|
|
|
22
|
-
| **Grafana, reading the server** *(start here)* | the server's CSV files, all of them | Grafana on your computer | [GRAFANA.md](GRAFANA.md); needs no OpenSearch |
|
|
23
20
|
| **(a) OpenSearch on the server** | OpenSearch on the production server | OpenSearch Dashboards on **your own computer** | sections 2, 3, 4, 5 |
|
|
24
21
|
| **(b) OpenSearch on your own machine** | OpenSearch on a machine you run, which the server sends readings to | whatever you like - you run it all | section 6 |
|
|
25
22
|
|
|
@@ -28,9 +25,8 @@ Either of (a) and (b) can be used with Grafana too.
|
|
|
28
25
|
**None of this is for the Raspberry Pi bridge.**
|
|
29
26
|
|
|
30
27
|
> **Status:** the Grafana and Mac steps were followed on macOS with Homebrew (OpenSearch 3.9.0,
|
|
31
|
-
> Dashboards 3.9.0, Grafana 13.3.0). The Linux steps, the logins for Dashboards, and the Apache setup
|
|
32
|
-
>
|
|
33
|
-
> where they differ. [Open questions](#open-questions) lists what to check first.
|
|
28
|
+
> Dashboards 3.9.0, Grafana 13.3.0). The Linux steps, the logins for Dashboards, and the Apache setup
|
|
29
|
+
> have been run on the production server at frugaliot.naturalinnovation.org
|
|
34
30
|
|
|
35
31
|
We use **OpenSearch**, not Elasticsearch: it is Apache-licensed, installs with Homebrew and apt, and
|
|
36
32
|
Grafana can read it. OpenSearch Dashboards is its fork of Kibana, so Kibana guides are close but not
|
|
@@ -38,117 +34,122 @@ identical. (The reasoning is also in a comment above the `OpenSearch` class in f
|
|
|
38
34
|
`index.js`, in case the decision is revisited.)
|
|
39
35
|
|
|
40
36
|
How heavy is each? OpenSearch is the heavy one: a Java program with its own 1 GB heap by default.
|
|
41
|
-
|
|
37
|
+
Dashboards are small by comparison (about 60 MB and 20 MB resident on a Mac, which
|
|
42
38
|
under-reports, so read that loosely).
|
|
43
39
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
## 1. On your own computer: Grafana
|
|
40
|
+
## 1. (a) On the server: OpenSearch
|
|
47
41
|
|
|
48
|
-
|
|
42
|
+
**Debian or Ubuntu.**
|
|
49
43
|
|
|
44
|
+
**First, check there is room.** OpenSearch with its default 1 GB heap takes about 1.4 GB of RAM in
|
|
45
|
+
total (measured on a 2.4 GB server, where it became by far the biggest process). Run this *before*
|
|
46
|
+
installing:
|
|
50
47
|
```sh
|
|
51
|
-
|
|
52
|
-
brew services start grafana
|
|
48
|
+
free -h
|
|
53
49
|
```
|
|
50
|
+
The `available` column is what is left for OpenSearch; you want at least 1.5 GB. If you have less, either add
|
|
51
|
+
memory or give OpenSearch a smaller heap (the step before starting it, below). Run it again after
|
|
52
|
+
OpenSearch is started and `available` will, of course, be much lower; `top` (press `M` to sort by
|
|
53
|
+
memory, `q` to quit) shows what is using it.
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
Then add the Infinity plugin - in Grafana, Administration → Plugins → "Infinity" - or from the shell:
|
|
57
|
-
|
|
55
|
+
Install the prerequisites
|
|
58
56
|
```sh
|
|
59
|
-
|
|
60
|
-
--pluginsDir /opt/homebrew/var/lib/grafana/plugins plugins install yesoreyeram-infinity-datasource
|
|
61
|
-
brew services restart grafana
|
|
57
|
+
sudo apt-get update && sudo apt-get -y install lsb-release ca-certificates curl gnupg2
|
|
62
58
|
```
|
|
63
|
-
|
|
64
|
-
For other systems, Grafana's own download page has installers for [Windows, Linux and others](https://grafana.com/grafana/download).
|
|
65
|
-
|
|
66
|
-
Then continue at [GRAFANA.md](GRAFANA.md) to make a token and chart your readings.
|
|
67
|
-
|
|
68
|
-
To stop it: `brew services stop grafana` (Homebrew restarts it at login until you do).
|
|
69
|
-
|
|
70
|
-
---
|
|
71
|
-
|
|
72
|
-
---
|
|
73
|
-
|
|
74
|
-
## 2. (a) On the server: OpenSearch
|
|
75
|
-
|
|
76
|
-
Debian or Ubuntu.
|
|
77
|
-
|
|
78
|
-
`apt-get` does the installing. The `curl` line just downloads the signing key for OpenSearch's
|
|
79
|
-
repository, which apt needs in order to trust it:
|
|
80
|
-
|
|
59
|
+
Fetch the signing key for OpenSearch's repository
|
|
81
60
|
```sh
|
|
82
|
-
sudo apt-get update && sudo apt-get -y install lsb-release ca-certificates curl gnupg2
|
|
83
61
|
sudo mkdir -p /etc/apt/keyrings
|
|
84
62
|
curl -fsSL https://artifacts.opensearch.org/publickeys/opensearch-release.pgp \
|
|
85
63
|
| sudo gpg --dearmor -o /etc/apt/keyrings/opensearch.gpg
|
|
86
64
|
```
|
|
65
|
+
Add OpenSearch's apt repository
|
|
66
|
+
```sh
|
|
67
|
+
echo "deb [signed-by=/etc/apt/keyrings/opensearch.gpg] https://artifacts.opensearch.org/releases/bundle/opensearch/3.x/apt stable main" \
|
|
68
|
+
| sudo tee /etc/apt/sources.list.d/opensearch-3.x.list
|
|
69
|
+
```
|
|
87
70
|
|
|
88
|
-
|
|
89
|
-
[their Debian guide](https://docs.opensearch.org/latest/install-and-configure/install-opensearch/debian/),
|
|
90
|
-
which is printed there for your architecture - and install, choosing an admin password now:
|
|
91
|
-
|
|
71
|
+
Choose a strong password to replace `<strong password>` with below. Installing does not start it yet.
|
|
92
72
|
```sh
|
|
93
73
|
sudo apt-get update
|
|
94
|
-
sudo env OPENSEARCH_INITIAL_ADMIN_PASSWORD='<
|
|
74
|
+
sudo env OPENSEARCH_INITIAL_ADMIN_PASSWORD='<strong password>' apt-get install opensearch
|
|
95
75
|
sudo systemctl enable opensearch
|
|
96
|
-
sudo systemctl start opensearch
|
|
97
|
-
curl https://localhost:9200 -u 'admin:<that password>' --insecure
|
|
98
76
|
```
|
|
99
77
|
|
|
100
|
-
|
|
101
|
-
|
|
78
|
+
**Set the memory before starting.** It is set in `/etc/opensearch/jvm.options`, which ships with `-Xms1g`
|
|
79
|
+
and `-Xmx1g` (a 1 GB heap). If `free -h` above showed less than 1.5 GB available, change both lines to
|
|
80
|
+
`512m`, which is enough for a little data: all 598,000 readings of the `dev` organization came to about
|
|
81
|
+
35 MB. More heap helps with a lot of history, but the machine needs the whole amount free *beyond* what
|
|
82
|
+
the server, broker, database and logger already use. You can change it later, then
|
|
83
|
+
`sudo systemctl restart opensearch`.
|
|
84
|
+
|
|
85
|
+
The other settings, in `/etc/opensearch/opensearch.yml`, need no change.
|
|
86
|
+
|
|
87
|
+
Now start it. This can take a couple of minutes.
|
|
88
|
+
```sh
|
|
89
|
+
sudo systemctl start opensearch
|
|
90
|
+
```
|
|
91
|
+
Check it is answering (`--insecure` means "do not check the certificate", because the one it ships with
|
|
92
|
+
is self-signed):
|
|
93
|
+
```sh
|
|
94
|
+
curl https://localhost:9200 -u 'admin:<strong password>' --insecure
|
|
95
|
+
```
|
|
102
96
|
|
|
103
97
|
This install **has the security plugin switched on**: HTTPS, a login, and that self-signed certificate.
|
|
104
|
-
The password is only used to set up that demo security configuration.
|
|
105
|
-
`/etc/opensearch/opensearch.yml` and the memory setting in `/etc/opensearch/jvm.options`.
|
|
98
|
+
The password is only used to set up that demo security configuration.
|
|
106
99
|
|
|
107
|
-
- **Memory:** the file ships with `-Xms1g` and `-Xmx1g` (a 1 GB heap). Give both lines the same value;
|
|
108
|
-
more helps with a lot of history, and the machine needs that much free *beyond* what the server,
|
|
109
|
-
broker and logger use. The data is small: all 598,000 readings of the `dev` organization came to
|
|
110
|
-
about 35 MB.
|
|
111
100
|
- **If it will not start**, look at `journalctl -u opensearch`. A common cause on Linux is the kernel
|
|
112
101
|
setting `vm.max_map_count` being below 262144; `sudo sysctl -w vm.max_map_count=262144` fixes it until
|
|
113
102
|
reboot, and a line in `/etc/sysctl.d/` makes it permanent.
|
|
114
103
|
- **Leave OpenSearch listening on the machine itself** (the default). Only Apache, below, is open to the
|
|
115
104
|
internet - never port 9200 directly. Check under [Keeping it safe](#keeping-it-safe).
|
|
116
105
|
|
|
117
|
-
### Tell the
|
|
106
|
+
### Tell the Frugal IoT Server about it
|
|
118
107
|
|
|
119
|
-
Make `config.d/opensearch.yaml` in the server's directory (a commented example ships there),
|
|
120
|
-
the server:
|
|
108
|
+
Make `config.d/opensearch.yaml` in the server's directory (a commented example ships there),
|
|
121
109
|
|
|
122
110
|
```yaml
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
public: https://search.example.org # the address people outside reach it at - section 3
|
|
111
|
+
node: https://localhost:9200 # where THIS server reaches OpenSearch: on this machine
|
|
112
|
+
username: admin # what the forwarder writes readings as
|
|
113
|
+
password: <the password you chose>
|
|
114
|
+
insecure: true # OpenSearch's demo certificate cannot be checked; fine on this machine
|
|
115
|
+
admin: # the account that makes the per-organization logins (section 4)
|
|
116
|
+
username: admin
|
|
117
|
+
password: <the same>
|
|
118
|
+
public: https://search.example.org # the address people outside reach it at - section 3
|
|
132
119
|
```
|
|
120
|
+
and restart the Frugal IoT server:
|
|
121
|
+
|
|
122
|
+
```sh
|
|
123
|
+
sudo service frugaliot restart
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The lines go at the top level of the file - the file's name already says they are about `opensearch`.
|
|
133
127
|
|
|
134
|
-
This file is never sent to a browser.
|
|
128
|
+
This file is never sent to a browser. `username:` and `password:` at the top must be an account OpenSearch
|
|
129
|
+
knows - the `admin` one you chose when installing - or every send fails with "Response Error" and the
|
|
130
|
+
logger's journal says so. Using `admin` for everything is the simplest thing that works, not
|
|
135
131
|
the best: a role that can only write `frugaliot-*` for the forwarder would be better, and is not yet
|
|
136
132
|
written. Leave `admin:` and `public:` out until you have done sections 3 and 4.
|
|
137
133
|
|
|
138
134
|
### Send readings to it
|
|
139
135
|
|
|
140
|
-
In the dashboard, open the project's back, then the **Forwarders** card, and add **OpenSearch on this
|
|
136
|
+
In the dashboard, open the project's back, then the **Forwarders** card (not the **OpenSearch** card), and add **OpenSearch on this
|
|
141
137
|
server**. There is no address or password to type: they come from the file above. Optionally set *Only
|
|
142
138
|
these topics*, e.g. `dev/lotus/#`; leave it empty to send everything that is logged.
|
|
143
139
|
|
|
144
|
-
It applies at once, with no restart, and creates what it needs in OpenSearch (an index template that
|
|
140
|
+
Readings are sent in batches every few minutes, so the first ones take a few minutes to show up. It applies at once, with no restart, and creates what it needs in OpenSearch (an index template that
|
|
145
141
|
makes `value` a number) the first time it sends. An organization's administrator can add it for their own
|
|
146
142
|
organization, so only do this if you are happy for them to have their readings in OpenSearch.
|
|
147
143
|
|
|
148
144
|
### Loading the history you already have
|
|
149
145
|
|
|
150
|
-
A forwarder only sends readings from the moment it is switched on. To bring in
|
|
151
|
-
|
|
146
|
+
A forwarder only sends readings from the moment it is switched on. To bring in what is already recorded,
|
|
147
|
+
open the **Forwarders** card, and on the OpenSearch forwarder's row press **Send earlier readings...**.
|
|
148
|
+
Pick the day to start from (leave it empty for everything), optionally tick *Only count them* to see how
|
|
149
|
+
many there are first, and press **Start**. It runs in the background and the card shows how many it has
|
|
150
|
+
sent so far. It is safe to repeat, and safe while the server is running.
|
|
151
|
+
|
|
152
|
+
The same thing from a terminal, in the server's directory, if you prefer:
|
|
152
153
|
|
|
153
154
|
```sh
|
|
154
155
|
npx --no frugal-iot-backfill-opensearch dev dry-run # count what would be sent
|
|
@@ -158,12 +159,22 @@ npx --no frugal-iot-backfill-opensearch dev 2026-09-01 # only files from that d
|
|
|
158
159
|
|
|
159
160
|
### Looking after it
|
|
160
161
|
|
|
162
|
+
Run these on the server, in a terminal. `admin:<strong password>` is the login and password you chose when
|
|
163
|
+
installing OpenSearch; `--insecure` is the same "don't check the certificate" as before.
|
|
164
|
+
|
|
165
|
+
- **See what is there.** One line per index, with its size:
|
|
166
|
+
```sh
|
|
167
|
+
curl -s 'https://localhost:9200/_cat/indices/frugaliot-*?v' -u 'admin:<strong password>' --insecure
|
|
168
|
+
```
|
|
161
169
|
- **Old months.** Readings go into one index per organization per month, named like
|
|
162
|
-
`frugaliot-dev-2026.09
|
|
163
|
-
|
|
170
|
+
`frugaliot-dev-2026.09` (that name is in the first column above). To drop one:
|
|
171
|
+
```sh
|
|
172
|
+
curl -X DELETE 'https://localhost:9200/frugaliot-dev-2026.02' -u 'admin:<strong password>' --insecure
|
|
173
|
+
```
|
|
174
|
+
The CSV files are untouched, so nothing is lost for good.
|
|
164
175
|
- **A gap while OpenSearch was down.** The forwarder does not retry a batch it could not send; the CSV
|
|
165
|
-
files have everything, and
|
|
166
|
-
- **Starting afresh.** Delete
|
|
176
|
+
files have everything, and *Send earlier readings* (above) fills the gap.
|
|
177
|
+
- **Starting afresh.** Delete each index listed above, as for an old month, and send the earlier readings again.
|
|
167
178
|
|
|
168
179
|
---
|
|
169
180
|
|
|
@@ -171,15 +182,25 @@ npx --no frugal-iot-backfill-opensearch dev 2026-09-01 # only files from that d
|
|
|
171
182
|
|
|
172
183
|
## 3. (a) Apache: HTTPS in front of OpenSearch
|
|
173
184
|
|
|
174
|
-
Dashboards on someone's computer
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
of this server - add one more site to it, on its own name, e.g. `search.example.org`.
|
|
185
|
+
Dashboards on someone's computer reach OpenSearch across the internet, it needs HTTPS.
|
|
186
|
+
|
|
187
|
+
While opensearch ships with HTTP and some self-signed certificates, for most practical installs it will be behind an Apache proxy.
|
|
178
188
|
|
|
189
|
+
We'll add a domain name to it - e.g. `opensearch.naturalinnovation.org`
|
|
190
|
+
|
|
191
|
+
Add components needed for Apache
|
|
179
192
|
```sh
|
|
180
193
|
sudo a2enmod ssl proxy proxy_http headers
|
|
181
194
|
```
|
|
182
195
|
|
|
196
|
+
You need a DNS record pointing `search.example.org` (use your own name) at this server, and a
|
|
197
|
+
certificate for it, made the way you made the server's own, e.g. `sudo certbot certonly --apache -d search.example.org`.
|
|
198
|
+
|
|
199
|
+
Your certificate may be a wildcard one (e.g. `*.example.org`) you already have; then use its files in
|
|
200
|
+
the two `SSLCertificate...` lines below.
|
|
201
|
+
|
|
202
|
+
Make a new file for this site, `/etc/apache2/sites-available/opensearch.conf` (Debian and Ubuntu keep
|
|
203
|
+
every Apache site in that folder; it is separate from your existing site's file), with:
|
|
183
204
|
```apache
|
|
184
205
|
<VirtualHost *:443>
|
|
185
206
|
ServerName search.example.org
|
|
@@ -205,10 +226,17 @@ sudo a2enmod ssl proxy proxy_http headers
|
|
|
205
226
|
</LocationMatch>
|
|
206
227
|
</VirtualHost>
|
|
207
228
|
```
|
|
229
|
+
Switch the site on, then test and reload Apache:
|
|
230
|
+
```sh
|
|
231
|
+
sudo a2ensite opensearch
|
|
232
|
+
sudo apachectl configtest && sudo systemctl reload apache2
|
|
233
|
+
```
|
|
234
|
+
Check from your own computer:
|
|
235
|
+
`curl https://search.example.org` should answer 401 (it wants a login) - not a block of JSON, or HTML, and not a
|
|
236
|
+
timeout.
|
|
208
237
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
timeout. Put the address in `public:` in `config.d/opensearch.yaml`.
|
|
238
|
+
Put the address (with the `https://`) in `public:` in `config.d/opensearch.yaml`, with the `admin:` lines
|
|
239
|
+
from section 1, then restart the server so it reads the file: `sudo systemctl restart frugaliot`.
|
|
212
240
|
|
|
213
241
|
Port 9200 itself stays closed to the network (firewall) - only Apache's 443 is open.
|
|
214
242
|
|
|
@@ -256,17 +284,80 @@ on a Mac), start it (`brew services start opensearch-dashboards`) and open <http
|
|
|
256
284
|
|
|
257
285
|
```yaml
|
|
258
286
|
opensearch.hosts: [ "https://search.example.org" ]
|
|
259
|
-
opensearch.username: "
|
|
287
|
+
opensearch.username: "frugaliot_dev_mitra"
|
|
260
288
|
opensearch.password: "..."
|
|
261
289
|
opensearchDashboards.index: ".kibana_dev"
|
|
262
|
-
opensearch.requestHeadersAllowlist: [
|
|
290
|
+
opensearch.requestHeadersAllowlist: []
|
|
291
|
+
opensearch.customHeaders: { authorization: "Basic <the login, encoded - the card writes this line for you>" }
|
|
263
292
|
```
|
|
264
293
|
|
|
265
294
|
The `opensearchDashboards.index` line is what gives the organization its own place to keep saved
|
|
266
|
-
dashboards; without it Dashboards uses `.kibana`, which this login cannot touch.
|
|
267
|
-
|
|
295
|
+
dashboards; without it Dashboards uses `.kibana`, which this login cannot touch. The last two lines stop Dashboards asking your browser for a login: Dashboards uses the login above
|
|
296
|
+
only for its own housekeeping, so they add the same login to every request. Copy the whole block from
|
|
297
|
+
the card - the `Basic ...` line is the username and password encoded, which you cannot easily write by
|
|
298
|
+
hand. Nothing is typed when you open Dashboards. Keep Dashboards on your own computer: it listens on
|
|
299
|
+
`localhost` by default, and anyone who can reach its port acts as this login.
|
|
300
|
+
|
|
301
|
+
Open opensearch - by default it will be on `http://localhost:5601`
|
|
302
|
+
|
|
303
|
+
Then, in Dashboards:
|
|
304
|
+
**Dashboards Management → Index patterns → Create**, enter `frugaliot-<org>-*` (e.g. `frugaliot-dev-*`; your login can read only your own organization's), and choose `@timestamp` as the
|
|
268
305
|
time field.
|
|
269
306
|
|
|
307
|
+
If it says the pattern **"doesn't match any indices"**, OpenSearch has no readings for your organization yet:
|
|
308
|
+
the forwarder (section 2, "Send readings to it") is not added, or nothing has been sent. Readings only
|
|
309
|
+
arrive once the forwarder is on, so send the earlier ones as in "Loading the history you already have".
|
|
310
|
+
You can check from your own computer with the login that is in the Dashboards file - the generated one the card
|
|
311
|
+
showed (not the OpenSearch `admin` password). This reads it from the file on a Mac; use your own address
|
|
312
|
+
and organization:
|
|
313
|
+
```sh
|
|
314
|
+
curl -s -u "$(awk -F'"' '/^opensearch.username/{u=$2} /^opensearch.password/{p=$2} END{print u":"p}' /opt/homebrew/etc/opensearch-dashboards/opensearch_dashboards.yml)" 'https://search.example.org/frugaliot-dev-*/_count'
|
|
315
|
+
```
|
|
316
|
+
`"count":0` with `"total":0` shards means none are there; a number above 0 means they are.
|
|
317
|
+
|
|
318
|
+
If it says **"OpenSearch Dashboards server is not ready yet"**, look at its log
|
|
319
|
+
(`/opt/homebrew/var/log/opensearch-dashboards.log` on a Mac): it names what it could not do. A
|
|
320
|
+
`security_exception ... no permissions for [...]` line means the organization's role on the server is
|
|
321
|
+
missing that permission - press **Replace the password** on the OpenSearch card once (the server rewrites
|
|
322
|
+
the role), then restart Dashboards. A stalled "Another OpenSearch Dashboards instance appears to be
|
|
323
|
+
migrating" means a first start was interrupted; as the message says, delete the index it names
|
|
324
|
+
(`.kibana_<org>_1`, see "Looking after it" for the command) and restart it.
|
|
325
|
+
|
|
326
|
+
### Seeing your readings as a chart
|
|
327
|
+
|
|
328
|
+
Checked step by step against OpenSearch Dashboards 3.9 in a browser. Each reading has these fields:
|
|
329
|
+
`value` (the number), `@timestamp`, and `topic` split into `node` (the device), `module` and `leaf`
|
|
330
|
+
(e.g. `temperature`). First look at **Discover** (☰ menu, top left): with `frugaliot-<org>-*` chosen you
|
|
331
|
+
see a bar chart of how many readings arrived, and the readings below it.
|
|
332
|
+
|
|
333
|
+
Now a chart of one kind of reading, a line per device:
|
|
334
|
+
|
|
335
|
+
1. Go to **Visualize** (<http://localhost:5601/app/visualize>) and press **Create new visualization**.
|
|
336
|
+
Choose **Line**, then your `frugaliot-<org>-*` index pattern.
|
|
337
|
+
2. At the top right, click the calendar icon beside the time and choose **Last 24 hours**. The default
|
|
338
|
+
is the last 15 minutes, which is nearly always empty.
|
|
339
|
+
3. In the right-hand panel, under **Metrics**, open **Y-axis**. Set **Aggregation** to **Average**
|
|
340
|
+
(type "Average" and pick it). **Field** fills itself in with `value`.
|
|
341
|
+
4. Under **Buckets** press **Add**, choose **X-axis**, and set **Aggregation** to **Date Histogram**
|
|
342
|
+
(**Field** fills itself in with `@timestamp`). Press **Update** at the bottom right.
|
|
343
|
+
You now have one line, but it averages every kind of reading together (temperature, humidity,
|
|
344
|
+
wifi...), so the numbers mean nothing.
|
|
345
|
+
5. In the search bar above the chart type `leaf:temperature` and press Enter. Now only temperatures
|
|
346
|
+
are charted.
|
|
347
|
+
6. Under **Buckets** press **Add** again, choose **Split series**, set **Sub aggregation** to **Terms**
|
|
348
|
+
and **Field** to `node`. Press **Update**. You get one line per device, with its name in the legend.
|
|
349
|
+
7. Press **Save** (top right), give it a title such as `Temperature by device`, and **Save**.
|
|
350
|
+
|
|
351
|
+
Put it on a dashboard:
|
|
352
|
+
|
|
353
|
+
1. Go to **Dashboards** (<http://localhost:5601/app/dashboards>) and **Create new dashboard**.
|
|
354
|
+
2. Press **Add** (top right), click your chart in the list, and close the list with the **X**.
|
|
355
|
+
3. Press **Save**, give it a title, switch on **Store time with dashboard** (otherwise it opens on the
|
|
356
|
+
last 15 minutes and looks empty), and **Save**.
|
|
357
|
+
|
|
358
|
+
To chart something else, change `temperature` in step 5 to `humidity` or another `leaf`, or leave the
|
|
359
|
+
search empty and split by `topic` instead of `node`.
|
|
360
|
+
|
|
270
361
|
You can use the same login in **Grafana** instead, with its OpenSearch data source and the same address.
|
|
271
362
|
|
|
272
363
|
---
|
|
@@ -368,8 +459,7 @@ and only your own machine can reach it. Make `config.d/opensearch.yaml` in your
|
|
|
368
459
|
just:
|
|
369
460
|
|
|
370
461
|
```yaml
|
|
371
|
-
|
|
372
|
-
node: http://localhost:9200
|
|
462
|
+
node: http://localhost:9200
|
|
373
463
|
```
|
|
374
464
|
|
|
375
465
|
and restart the server. The Forwarders card then offers **OpenSearch on this server**. The Dashboards
|
|
@@ -397,11 +487,7 @@ To stop them: `brew services stop opensearch-dashboards opensearch`.
|
|
|
397
487
|
|
|
398
488
|
Please add what you find:
|
|
399
489
|
|
|
400
|
-
- **The Linux steps have not been run**: the exact `apt` repository line, and the Apache site above.
|
|
401
|
-
- **The role in section 4 is untried.** Its actions are the ones OpenSearch's documentation names for
|
|
402
|
-
Dashboards, and may be missing one - symptoms are Dashboards failing to start, or an index pattern that
|
|
403
|
-
cannot be created. If so, OpenSearch's log names the refused action; add it to `roleFor` in
|
|
404
|
-
`lib/opensearch-access.js`.
|
|
490
|
+
- **The Linux steps have not been run for the opensource-dashboards**: the exact `apt` repository line, and the Apache site above.
|
|
405
491
|
- **`opensearchDashboards.index`** is meant to give each organization its own saved-objects index. Check
|
|
406
492
|
that two organizations' Dashboards do not see each other's saved dashboards, and that neither can open
|
|
407
493
|
the other's `.kibana_<org>`.
|
package/docs/OPENWRT.md
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# Running a Frugal IoT server on an OpenWrt router
|
|
2
|
+
|
|
3
|
+
**Status: worked once, on one router, after fixes.** The Raspberry Pi installation
|
|
4
|
+
([INSTALLATION.md](INSTALLATION.md)) is proven; this is the same thing re-done for OpenWrt. The first
|
|
5
|
+
real run - an OpenWrt One, OpenWrt 25.12.5, see [Tested on](#tested-on) - needed five attempts and
|
|
6
|
+
found ten problems, which are fixed or described below. Expect the next router to find more. This
|
|
7
|
+
document says what a router needs, how to prepare it, gives two scripts, and says what to send back.
|
|
8
|
+
|
|
9
|
+
## What you are installing
|
|
10
|
+
|
|
11
|
+
A Frugal IoT server is three programs on one machine:
|
|
12
|
+
|
|
13
|
+
* **mosquitto**, an MQTT broker. Small sensor boards (ESP32s) publish readings to it, and the
|
|
14
|
+
dashboard in your browser listens to it.
|
|
15
|
+
* **the Frugal IoT server**, a Node.js program. It serves the dashboard on port 8080, keeps the
|
|
16
|
+
user logins and settings in an SQLite database, and writes every reading to disk.
|
|
17
|
+
* nothing else. No cloud, no internet needed once it is installed.
|
|
18
|
+
|
|
19
|
+
The router is simply where they run. It does not stop being a router.
|
|
20
|
+
|
|
21
|
+
## What the router needs
|
|
22
|
+
|
|
23
|
+
Run the preflight script (below) and it checks every line of this table for you.
|
|
24
|
+
|
|
25
|
+
| | Needed | Why |
|
|
26
|
+
| --- | --- | --- |
|
|
27
|
+
| **CPU** | **aarch64 or x86_64.** 32-bit ARM might work. MIPS will not. | OpenWrt's `node` package is only built for some CPUs, and the database module (`sqlite3`) is a compiled binary that has to exist for that CPU and for OpenWrt's C library (musl). Both are most likely on 64-bit. |
|
|
28
|
+
| **Memory** | 512 MB or more (256 MB at the very least) | The server uses about 250 MB once running. |
|
|
29
|
+
| **Storage** | **About 400 MB free** on ext4, f2fs or btrfs | The software is ~100 MB; npm needs room to work; readings accumulate. **Most routers' own flash is far too small**, so this means a USB stick or SSD, set up as extroot or just mounted, and `--dir` pointing at it. Not FAT, exFAT or NTFS: the install needs symlinks and Unix permissions. The image may also lack the USB storage driver - see [Preparing the router](#preparing-the-router). |
|
|
30
|
+
| **Node.js** | **20.17 or later**, and npm | From OpenWrt's feed if it has one - **OpenWrt 25.12 has none for aarch64**, in which case it is unpacked by hand from a musl build ([how](#if-the-feed-has-no-node)). The preflight and the installer accept either. |
|
|
31
|
+
| **mosquitto** | A build with **websockets** | The browser dashboard connects over websockets. If the router's mosquitto has none, the dashboard cannot work. |
|
|
32
|
+
| | Ideally the **dynamic security plugin** | It gives every node and every user its own broker password. Without it the server falls back to one shared password per organization: it works, but it is the older and weaker scheme. |
|
|
33
|
+
| **Network** | Internet access while installing | opkg and npm download from their own servers. |
|
|
34
|
+
| **Free ports** | 1883 (nodes), 9012 (dashboard websocket), 8080 (dashboard) | Nothing on OpenWrt uses these by default; LuCI is on 80/443. |
|
|
35
|
+
|
|
36
|
+
Better to have, not required: a fixed LAN address (a router has one already), and a drive that
|
|
37
|
+
tolerates constant small writes - the server logs readings all day, which is hard on a cheap USB stick.
|
|
38
|
+
|
|
39
|
+
## Preparing the router
|
|
40
|
+
|
|
41
|
+
Two things a router often lacks, both found on the first real run. Commands are shown for `apk`
|
|
42
|
+
(OpenWrt 25.12 and later); on older releases use `opkg install` instead of `apk add`.
|
|
43
|
+
|
|
44
|
+
### A drive the router can see
|
|
45
|
+
|
|
46
|
+
The preflight tells you when none is visible. A USB drive formatted ext4 was invisible on the test
|
|
47
|
+
router because the image carried no USB storage driver:
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
apk add kmod-usb-storage kmod-fs-ext4 block-mount
|
|
51
|
+
block info # is the drive listed now? what is it called?
|
|
52
|
+
block detect > /etc/config/fstab # NOTE: replaces your mount configuration - if you already have
|
|
53
|
+
block mount # mounts there, add the new one by hand (LuCI: System > Mount Points)
|
|
54
|
+
df -h /mnt/sda1
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Check that the drive is mounted again after a reboot (`uci show fstab`, or LuCI's Mount Points), or
|
|
58
|
+
the server will not find its files and will not start.
|
|
59
|
+
|
|
60
|
+
### If the feed has no node
|
|
61
|
+
|
|
62
|
+
The preflight's feed check says `no node package in this feed` when this applies. Node.js publishes
|
|
63
|
+
musl builds, which is what OpenWrt uses, at <https://unofficial-builds.nodejs.org/download/release/>.
|
|
64
|
+
Pick a version of 20.17 or later (24.9.0 worked) and the file for your CPU: `linux-arm64-musl` for
|
|
65
|
+
aarch64, `linux-x64-musl` for x86_64. **Take the `.tar.gz`, not the `.tar.xz`** - BusyBox `tar` cannot
|
|
66
|
+
unpack xz. Put it on the drive, not the router's flash:
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
cd /mnt/sda1
|
|
70
|
+
wget https://unofficial-builds.nodejs.org/download/release/v24.9.0/node-v24.9.0-linux-arm64-musl.tar.gz
|
|
71
|
+
tar xzf node-v24.9.0-linux-arm64-musl.tar.gz
|
|
72
|
+
for t in node npm npx; do ln -sf /mnt/sda1/node-v24.9.0-linux-arm64-musl/bin/$t /usr/bin/$t; done
|
|
73
|
+
apk add libstdcpp6 libatomic1 # the musl build needs both
|
|
74
|
+
node -v; npm -v
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
If `node -v` says a library is missing, that is the `apk add` line. The preflight and installer then
|
|
78
|
+
see node on the router and do not look for a package.
|
|
79
|
+
|
|
80
|
+
## Doing it
|
|
81
|
+
|
|
82
|
+
You need `ssh` access to the router as root. On the router, fetch the two scripts from the `main` branch:
|
|
83
|
+
|
|
84
|
+
```sh
|
|
85
|
+
B=https://raw.githubusercontent.com/mitra42/frugal-iot-server/main/scripts
|
|
86
|
+
wget -O openwrt-preflight.sh $B/openwrt-preflight.sh
|
|
87
|
+
wget -O install-openwrt.sh $B/install-openwrt.sh
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
If `wget` complains about https, the router has no TLS support for it yet
|
|
91
|
+
(`apk add ca-bundle libustream-mbedtls`, or `opkg update && opkg install ...` on older releases), or download the files on your computer
|
|
92
|
+
and copy them across with `scp`.
|
|
93
|
+
|
|
94
|
+
### 1. Preflight - on each candidate router
|
|
95
|
+
|
|
96
|
+
```sh
|
|
97
|
+
sh openwrt-preflight.sh --dir /mnt/sda1/frugal-iot --install 2>&1 | tee preflight.txt
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
* `--dir` is where you intend to put the server: a path on the drive. The default is `/opt/frugal-iot`.
|
|
101
|
+
* Without `--install` it only reads things, and refreshes the package list. **With `--install` it
|
|
102
|
+
installs node, mosquitto and the other packages**, after asking, so that it can try mosquitto and
|
|
103
|
+
sqlite3 for real rather than guess. That is the same set the installer would put there anyway, but it
|
|
104
|
+
does use the router's own flash: it prints how much is free first. If the flash is nearly full, run
|
|
105
|
+
it without `--install` first.
|
|
106
|
+
* It ends with `PASS`/`WARN`/`FAIL` counts and a verdict. **Please send the whole of `preflight.txt`
|
|
107
|
+
for every router you try**, including the ones that fail - which ones fail, and why, is the most
|
|
108
|
+
useful thing this stage can tell us.
|
|
109
|
+
|
|
110
|
+
Which router to choose is then mostly: one with no `FAIL`, a USB port with a decent drive in it, and
|
|
111
|
+
512 MB or more.
|
|
112
|
+
|
|
113
|
+
### 2. Install - on the one you pick
|
|
114
|
+
|
|
115
|
+
```sh
|
|
116
|
+
sh install-openwrt.sh --org myfarm --name "My Farm" --email you@example.com \
|
|
117
|
+
--phone +61123456789 --dir /mnt/sda1/frugal-iot
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
* `--org` is 1-10 lower-case letters or digits. It becomes the first part of every MQTT topic, and
|
|
121
|
+
the sensor boards must be flashed with the same one. Pick something you will keep.
|
|
122
|
+
* Passwords you do not give are generated and printed at the end. The whole run is also logged to
|
|
123
|
+
`/root/frugal-iot-install-<date>.log` (readable only by root; the generated passwords are in it).
|
|
124
|
+
* `--dry-run` checks the arguments and the router and says what it would do, changing nothing.
|
|
125
|
+
* It is safe to run again after a failure: steps already done are skipped. Give it the **same**
|
|
126
|
+
`--org` and `--dir` as before - a different `--org` creates a second organization. Passwords already
|
|
127
|
+
set are left alone, and the summary says "unchanged" rather than printing a made-up one.
|
|
128
|
+
* It runs the server as root, as most OpenWrt services do. `--user frugal` creates and uses a
|
|
129
|
+
separate account instead, which is tidier but is another thing that has not been tried.
|
|
130
|
+
|
|
131
|
+
When it finishes it prints the dashboard address and the settings the sensor boards need.
|
|
132
|
+
|
|
133
|
+
### 3. If something goes wrong
|
|
134
|
+
|
|
135
|
+
Send back:
|
|
136
|
+
|
|
137
|
+
1. `preflight.txt`
|
|
138
|
+
2. the install log (`/root/frugal-iot-install-*.log`) - or just the end of the terminal output
|
|
139
|
+
3. `logread -e frugaliot | tail -50` and `logread -e mosquitto | tail -50`
|
|
140
|
+
4. the output of `apk list -I | grep -E 'mosquitto|node|sqlite|zsh'` (`opkg list-installed | grep ...`
|
|
141
|
+
on older releases)
|
|
142
|
+
|
|
143
|
+
## What is different from a Raspberry Pi, and why it might fail
|
|
144
|
+
|
|
145
|
+
The installer is the Pi one, redone, so these are the places it can be wrong:
|
|
146
|
+
|
|
147
|
+
* **No systemd.** The service is a procd init script, `/etc/init.d/frugaliot`. Control it with
|
|
148
|
+
`service frugaliot restart`. Logs go to the system log: `logread -e frugaliot`.
|
|
149
|
+
* **`/var` is RAM.** On OpenWrt `/var` is `/tmp`, emptied at every reboot. The broker's password file
|
|
150
|
+
and account database would normally live in `/var/lib/mosquitto`, which would lose every account on
|
|
151
|
+
restart, so the installer keeps them in `/etc/mosquitto/state` and points the configuration there.
|
|
152
|
+
It also moves mosquitto's own persistence (the retained values) there if it was set to a
|
|
153
|
+
RAM path; the original configuration is kept as `/etc/mosquitto/mosquitto.conf.pre-frugal`.
|
|
154
|
+
* **No bash, no zsh, no sudo, no apt.** The installer is plain `sh` and installs zsh itself (the
|
|
155
|
+
server's own commands are zsh scripts). BusyBox lacks `install(1)` (it is in `coreutils-install`),
|
|
156
|
+
which stopped the first run, so the installer places files with `cp`, `chown`, `chmod` and `mv`
|
|
157
|
+
instead; and its `tar` cannot unpack `.xz`. `stat`, `sed`, `grep` and `su` worked.
|
|
158
|
+
* **The sqlite3 module.** The most likely single failure. If a binary does not exist for your CPU
|
|
159
|
+
and musl, `npm install` will try to compile it, and there is no compiler. The installer stops there
|
|
160
|
+
with the error. A fix exists (compile it on another machine, or use a build environment) but it
|
|
161
|
+
is a different project.
|
|
162
|
+
* **The mosquitto package.** Which parts OpenWrt builds in - websockets, the dynamic security plugin,
|
|
163
|
+
`mosquitto_ctrl`, `mosquitto_passwd` - is the main thing nobody here knows. The preflight tries each
|
|
164
|
+
one, and the installer lists every mosquitto package the feed offers into the log.
|
|
165
|
+
* **No WiFi fallback network.** On the Pi, a setup network is raised if its WiFi is lost. A router is
|
|
166
|
+
the access point, so this is skipped.
|
|
167
|
+
* **Time.** Routers without a battery clock start at an old date until NTP runs. The server stamps
|
|
168
|
+
readings when they arrive, so check the router has the right time.
|
|
169
|
+
|
|
170
|
+
## After it is running
|
|
171
|
+
|
|
172
|
+
* **Do not open the ports to the internet.** OpenWrt accepts LAN connections and refuses WAN ones by
|
|
173
|
+
default; leave 1883, 9012 and 8080 that way. HTTPS (needed for over-the-air firmware updates to the
|
|
174
|
+
boards) is not covered here.
|
|
175
|
+
* **Upgrading:**
|
|
176
|
+
```sh
|
|
177
|
+
cd /mnt/sda1/frugal-iot && npm update frugal-iot-server && service frugaliot restart
|
|
178
|
+
```
|
|
179
|
+
(and re-run `install-openwrt.sh` with the same arguments afterwards, which brings the broker
|
|
180
|
+
configuration up to date.)
|
|
181
|
+
* **If `http://<router>:8080/` answers 404** (the dashboard at `/dashboard` may still work), the
|
|
182
|
+
server cannot find its own web pages. An install whose `frugal-iot-init` failed part-way and was
|
|
183
|
+
re-run - as on the first OpenWrt run, before `init` was fixed - keeps a `config.d/server.yaml`
|
|
184
|
+
with `publicdir: ./public`. Set these in `<install dir>/config.d/server.yaml`, then
|
|
185
|
+
`service frugaliot restart`:
|
|
186
|
+
```yaml
|
|
187
|
+
htmldir: ./node_modules/frugal-iot-client
|
|
188
|
+
nodemodulesdir: ./node_modules
|
|
189
|
+
publicdir: ./node_modules/frugal-iot-server/public
|
|
190
|
+
privatedir: ./node_modules/frugal-iot-server/private
|
|
191
|
+
```
|
|
192
|
+
Without `publicdir` the home page, `/about.html` and the PWA's `service-worker.js` are missing too.
|
|
193
|
+
* **`npx --no frugal-iot-diagnostic`** was written for systemd and will report some things that do
|
|
194
|
+
not apply here. Treat it as a hint, not a verdict.
|
|
195
|
+
* **Removing it:**
|
|
196
|
+
```sh
|
|
197
|
+
service frugaliot stop; /etc/init.d/frugaliot disable; rm /etc/init.d/frugaliot
|
|
198
|
+
rm -rf /mnt/sda1/frugal-iot # your install directory: this deletes the readings too
|
|
199
|
+
rm /etc/mosquitto/conf.d/frugal-iot.conf /etc/mosquitto/aclfile; rm -rf /etc/mosquitto/state
|
|
200
|
+
cp /etc/mosquitto/mosquitto.conf.pre-frugal /etc/mosquitto/mosquitto.conf # if it exists
|
|
201
|
+
/etc/init.d/mosquitto restart
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Tested on
|
|
205
|
+
|
|
206
|
+
| Date | Router | OpenWrt | Node | Server | Result |
|
|
207
|
+
| --- | --- | --- | --- | --- | --- |
|
|
208
|
+
| 2026-11 | OpenWrt One (Banana Pi), MediaTek Filogic, aarch64_cortex-a53, 986 MB; USB drive, ext4, mounted at `/mnt/sda1` | 25.12.5 with LibreMesh, `apk`, kernel 6.12.94 | v24.9.0 and npm 11.6.0, unpacked by hand (no node in the aarch64 feed) | 2.0.8 | Installed and running: broker (websockets and dynamic security both present), organization, procd service, dashboard. Took five runs: no USB storage driver; no node package; no `install` command; `frugal-iot-init` failing on `extras/grafana`, which also left `server.yaml` unconfigured and made `/` answer 404. All now handled by the scripts or described above, except the `init` fix, which needs a server release to reach a router. |
|
package/extras/frugaliot.service
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Systemd unit for the Frugal IoT server.
|
|
2
2
|
#
|
|
3
|
-
# As shipped this matches the Raspberry Pi installation in INSTALLATION.md: user "pi", installed
|
|
3
|
+
# As shipped this matches the Raspberry Pi installation in docs/INSTALLATION.md: user "pi", installed
|
|
4
4
|
# with npm into /home/pi/frugal-iot. If that is what you have, install it unedited:
|
|
5
5
|
# sudo cp extras/frugaliot.service /etc/systemd/system/frugaliot.service
|
|
6
6
|
# sudo systemctl daemon-reload
|
package/extras/mosquitto.conf
CHANGED
|
@@ -6,7 +6,7 @@ protocol websockets
|
|
|
6
6
|
# Kept under /var/lib/mosquitto, which belongs to the mosquitto user, rather than /etc/mosquitto,
|
|
7
7
|
# which belongs to root. Both the broker and mosquitto_passwd warn unless the file belongs to
|
|
8
8
|
# whoever opened it, and mosquitto_passwd also writes a temporary backup beside it, so the
|
|
9
|
-
# directory has to be writable by that same user. See INSTALLATION.md step A5.
|
|
9
|
+
# directory has to be writable by that same user. See docs/INSTALLATION.md step A5.
|
|
10
10
|
password_file /var/lib/mosquitto/passwords
|
|
11
11
|
|
|
12
12
|
# Which topics each account may reach. Without this, every account that can log in can publish and
|