frugal-iot-server 2.0.7 → 2.0.9

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/GRAFANA.md ADDED
@@ -0,0 +1,145 @@
1
+ # Charting your readings in Grafana
2
+
3
+ Grafana can chart an organization's readings straight from this server, from the same CSV files the
4
+ logger writes. Nothing is copied into a second database, so Grafana sees the **complete** history of
5
+ every topic, not just what was exported somewhere.
6
+
7
+ It needs nothing extra on the server, and Grafana can run **on your own computer**: all it asks of the
8
+ server is ordinary HTTPS on the port the dashboard already uses, with a token you make for it. (To
9
+ run Grafana on the server instead, see [INSTALL-OPENSEARCH.md](INSTALL-OPENSEARCH.md).)
10
+
11
+ Grafana reads the server with its free **Infinity** plugin, which can call any URL that returns JSON.
12
+
13
+ > **Status:** the server, the data source, the starter dashboard and its topic list were followed on
14
+ > Grafana 13.3 with Infinity 4.1.5 (a Mac, Homebrew install), and the dashboard was loaded in a
15
+ > browser to confirm it draws real readings. Two things are written but not yet tried against a real
16
+ > Grafana: the downloadable data source file (it holds the same settings that worked when set through
17
+ > Grafana's API), and the Grafana card on the dashboard clicked through by hand. Correct this page
18
+ > where they differ.
19
+
20
+ ## Installing
21
+
22
+ #### On a Mac,
23
+
24
+ ```sh
25
+ brew install grafana
26
+ brew services start grafana
27
+ ```
28
+
29
+ Open <http://localhost:3000>. Grafana's default login is `admin` / `admin`, and it asks you to change it.
30
+ Then add the Infinity plugin - in Grafana, Administration → Plugins → "Infinity" - or from the shell:
31
+
32
+ ```sh
33
+ /opt/homebrew/opt/grafana/bin/grafana cli --homepath /opt/homebrew/opt/grafana/share/grafana \
34
+ --pluginsDir /opt/homebrew/var/lib/grafana/plugins plugins install yesoreyeram-infinity-datasource
35
+ brew services restart grafana
36
+ ```
37
+ To stop it: `brew services stop grafana` (Homebrew restarts it at login until you do).
38
+
39
+ #### On Windows, Linux and other systems
40
+
41
+ For other systems, Grafana's own download page has installers for [Windows, Linux and others](https://grafana.com/grafana/download).
42
+
43
+ ## Setting it up
44
+ Access Grafana from your browser at [localhost:3000](http://localhost:3000)
45
+
46
+ Anyone who can **read** the organization can make a token for themselves - you do not need to be an
47
+ administrator. A token is yours: Grafana sees what *you* can see, so a token made by someone who can
48
+ read only one project shows that project only.
49
+
50
+ 1. **Install the Infinity plugin in Grafana.** In Grafana: Administration → Plugins, search for
51
+ "Infinity", install it, and restart Grafana if it asks. (Installing Grafana itself is in
52
+ [INSTALL-OPENSEARCH.md](INSTALL-OPENSEARCH.md).)
53
+ 2. **Make a token.** In the Frugal IoT dashboard, open the project's back, then the **Grafana** card. Type a name
54
+ for this Grafana (so it can be revoked on its own later) and press *Make a token*. The token is shown
55
+ **once** - only a hash is kept, so it cannot be looked up again. If you lose it, make one with the
56
+ same name, which replaces it.
57
+ 3. **Give Grafana the data source.** Either:
58
+ a: press *Download the data source file* in the same place and
59
+ put it in Grafana's `provisioning/datasources` folder (On a Mac typically in /opt/homebrew/Cellar/grafana/13.3.0/share/grafana/conf/provisioning),
60
+ then restart Grafana:
61
+ - On Mac: `brew services restart grafana`;
62
+
63
+ or add it by hand in
64
+ Grafana under Connections → Data sources → Add → **Infinity**:
65
+ - Authentication: **Bearer Token**, with the token
66
+ - Allowed hosts: the address you reached this dashboard at, e.g. `https://frugaliot.naturalinnovation.org`
67
+ 4. **Import the dashboard.**
68
+ * On FrugalIoT: In the Grafana card press *Download the dashboard*.
69
+ * Then in Grafana choose
70
+ Dashboards → New → Import, upload the file, and pick the data source from step 3 when it asks.
71
+
72
+ The dashboard has a list of every topic at the top to choose from, and the time range at the top right.
73
+ The file already has this server and organization filled in.
74
+
75
+ A token lets one Grafana read **one organization**, read-only - and only the
76
+ projects of it that the person who made the token may read. For another
77
+ organization, make its own token, data source and import - the dashboard file is separate for each, so
78
+ importing a second does not replace the first. *Revoke* on the card cuts off a Grafana at once.
79
+
80
+ ## What the server provides
81
+
82
+ For building your own panels. All of these need the header `Authorization: Bearer fiot_...`, and the
83
+ token must be for the `<org>` in the URL.
84
+
85
+ | URL | Returns |
86
+ |---|---|
87
+ | `/grafana/<org>/health` | `{"status":"ok"}` |
88
+ | `/grafana/<org>/topics` | every topic with readings: `[{topic, project, node, module, leaf}]`. Narrow with `?project=` and `?node=` |
89
+ | `/grafana/<org>/data?topic=<topic>&from=<time>&to=<time>&maxPoints=<n>` | `[{"time": "2026-09-24T00:00:01.000Z", "value": 22.5}, ...]` |
90
+
91
+ - `topic` is the full topic, `<org>/<project>/<node>/<module>/<leaf>`, e.g.
92
+ `dev/lotus/esp32-944104/aht20/temperature`.
93
+ - `from` and `to` are epoch **milliseconds** (which is what Grafana's `${__from}` and `${__to}` give), or
94
+ an ISO date. Without them you get the last 24 hours.
95
+ - Past `maxPoints` (default 1000, at most 5000) readings are averaged into equal time buckets, so a year
96
+ of data does not travel as millions of rows.
97
+ - Readings that are not numbers (descriptions, colours) are left out - they cannot be charted.
98
+
99
+ A panel in Grafana, using the Infinity data source: Type **JSON**, Parser **Backend**, Format **Time
100
+ series**, URL
101
+
102
+ ```
103
+ https://your-server/grafana/dev/data?topic=dev/lotus/esp32-944104/aht20/temperature&from=${__from}&to=${__to}&maxPoints=1000
104
+ ```
105
+
106
+ and columns `time` → *Timestamp*, `value` → *Number*. Use a fixed `maxPoints`: Grafana has no macro for a
107
+ panel's width in pixels, and `${__interval_ms}` is a duration, not a count.
108
+
109
+ ## Keeping it safe
110
+
111
+ - **A token is a password.** Anyone holding it can read what its maker can read, and the data
112
+ source file the card offers has it inside, so do not leave that file lying about or send it by mail.
113
+ The server cannot show a token again, only replace it.
114
+ - It is **read-only and for one organization**. It cannot change anything, and the server refuses it for
115
+ any other organization.
116
+ - It **acts as the person who made it**, and is checked against their permissions every time it is
117
+ used. If their access to the organization or a project is removed, the token loses it at once - there
118
+ is nothing to revoke. Someone cannot use a token to see more than they could on the dashboard.
119
+ - You see and revoke your own tokens. An administrator of the whole organization sees everyone's and
120
+ can revoke any, for example when a Grafana is lost.
121
+ - **Revoke** on the card cuts a Grafana off at once. Make one token per Grafana, named for it, so one
122
+ can go without disturbing the rest, and revoke any that is no longer used - the card shows when each
123
+ was last used. Tokens do not expire on their own.
124
+ - Grafana should reach the server over **HTTPS**, since the token travels in a header.
125
+ - On a Grafana that other people use, give **each organization its own Grafana organization**, with its
126
+ own data source and token: everyone in a Grafana organization shares its data sources, so anyone who
127
+ can edit a dashboard there can read whatever the token allows.
128
+ - The server limits what a token can cost it: long histories are averaged as they are read, only a few
129
+ reads run at once - and only two for any one organization - (Grafana is told to retry), and the topic
130
+ list is cached for a minute. Each user can hold up to 10 tokens per organization.
131
+
132
+ ## From a shell instead
133
+
134
+ If you can get to the server's directory, `npx --no frugal-iot-grafana-token add <org> <username> <name>` makes a
135
+ token for that user, and `list` and `revoke` do what they say. It is the same as the card, except you name whose token it is.
136
+
137
+ ## Notes
138
+
139
+ - Readings are written to disk every few minutes, so the server first asks the logger to write out what
140
+ it holds; a chart that reaches "now" is not short.
141
+ - The CSV files can contain a sensor's error value (an SHT sensor reports -45 or 130 when it fails).
142
+ They are real readings as far as the logger knows, so they appear on charts; set the panel's axis
143
+ limits, or filter in Grafana, to leave them out.
144
+ - The first topic in the list is simply the first alphabetically, and may be a sensor that has stopped,
145
+ which shows "No data" for a recent time range. Pick another.
@@ -0,0 +1,501 @@
1
+ # Installing OpenSearch, Grafana and Dashboards
2
+
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/`,
5
+ and the client has its own graphing.
6
+
7
+ ## What the pieces are
8
+
9
+ OpenSearch is **two programs**:
10
+
11
+ - **OpenSearch** is the database. It stores and searches the readings, and listens on port 9200.
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
14
+ configuration file. (It also keeps its own saved dashboards *inside* OpenSearch, in an index.)
15
+
16
+ ## The ways to use it
17
+
18
+ | | Readings are kept in | You look at them with | Set up |
19
+ |---|---|---|---|
20
+ | **(a) OpenSearch on the server** | OpenSearch on the production server | OpenSearch Dashboards on **your own computer** | sections 2, 3, 4, 5 |
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 |
22
+
23
+ Either of (a) and (b) can be used with Grafana too.
24
+
25
+ **None of this is for the Raspberry Pi bridge.**
26
+
27
+ > **Status:** the Grafana and Mac steps were followed on macOS with Homebrew (OpenSearch 3.9.0,
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
30
+
31
+ We use **OpenSearch**, not Elasticsearch: it is Apache-licensed, installs with Homebrew and apt, and
32
+ Grafana can read it. OpenSearch Dashboards is its fork of Kibana, so Kibana guides are close but not
33
+ identical. (The reasoning is also in a comment above the `OpenSearch` class in frugal-iot-logger's
34
+ `index.js`, in case the decision is revisited.)
35
+
36
+ How heavy is each? OpenSearch is the heavy one: a Java program with its own 1 GB heap by default.
37
+ Dashboards are small by comparison (about 60 MB and 20 MB resident on a Mac, which
38
+ under-reports, so read that loosely).
39
+
40
+ ## 1. (a) On the server: OpenSearch
41
+
42
+ **Debian or Ubuntu.**
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:
47
+ ```sh
48
+ free -h
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
+
55
+ Install the prerequisites
56
+ ```sh
57
+ sudo apt-get update && sudo apt-get -y install lsb-release ca-certificates curl gnupg2
58
+ ```
59
+ Fetch the signing key for OpenSearch's repository
60
+ ```sh
61
+ sudo mkdir -p /etc/apt/keyrings
62
+ curl -fsSL https://artifacts.opensearch.org/publickeys/opensearch-release.pgp \
63
+ | sudo gpg --dearmor -o /etc/apt/keyrings/opensearch.gpg
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
+ ```
70
+
71
+ Choose a strong password to replace `<strong password>` with below. Installing does not start it yet.
72
+ ```sh
73
+ sudo apt-get update
74
+ sudo env OPENSEARCH_INITIAL_ADMIN_PASSWORD='<strong password>' apt-get install opensearch
75
+ sudo systemctl enable opensearch
76
+ ```
77
+
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
+ ```
96
+
97
+ This install **has the security plugin switched on**: HTTPS, a login, and that self-signed certificate.
98
+ The password is only used to set up that demo security configuration.
99
+
100
+ - **If it will not start**, look at `journalctl -u opensearch`. A common cause on Linux is the kernel
101
+ setting `vm.max_map_count` being below 262144; `sudo sysctl -w vm.max_map_count=262144` fixes it until
102
+ reboot, and a line in `/etc/sysctl.d/` makes it permanent.
103
+ - **Leave OpenSearch listening on the machine itself** (the default). Only Apache, below, is open to the
104
+ internet - never port 9200 directly. Check under [Keeping it safe](#keeping-it-safe).
105
+
106
+ ### Tell the Frugal IoT Server about it
107
+
108
+ Make `config.d/opensearch.yaml` in the server's directory (a commented example ships there),
109
+
110
+ ```yaml
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
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`.
127
+
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
131
+ the best: a role that can only write `frugaliot-*` for the forwarder would be better, and is not yet
132
+ written. Leave `admin:` and `public:` out until you have done sections 3 and 4.
133
+
134
+ ### Send readings to it
135
+
136
+ In the dashboard, open the project's back, then the **Forwarders** card (not the **OpenSearch** card), and add **OpenSearch on this
137
+ server**. There is no address or password to type: they come from the file above. Optionally set *Only
138
+ these topics*, e.g. `dev/lotus/#`; leave it empty to send everything that is logged.
139
+
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
141
+ makes `value` a number) the first time it sends. An organization's administrator can add it for their own
142
+ organization, so only do this if you are happy for them to have their readings in OpenSearch.
143
+
144
+ ### Loading the history you already have
145
+
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:
153
+
154
+ ```sh
155
+ npx --no frugal-iot-backfill-opensearch dev dry-run # count what would be sent
156
+ npx --no frugal-iot-backfill-opensearch dev # send it
157
+ npx --no frugal-iot-backfill-opensearch dev 2026-09-01 # only files from that day on
158
+ ```
159
+
160
+ ### Looking after it
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
+ ```
169
+ - **Old months.** Readings go into one index per organization per month, named like
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.
175
+ - **A gap while OpenSearch was down.** The forwarder does not retry a batch it could not send; the CSV
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.
178
+
179
+ ---
180
+
181
+ ---
182
+
183
+ ## 3. (a) Apache: HTTPS in front of OpenSearch
184
+
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.
188
+
189
+ We'll add a domain name to it - e.g. `opensearch.naturalinnovation.org`
190
+
191
+ Add components needed for Apache
192
+ ```sh
193
+ sudo a2enmod ssl proxy proxy_http headers
194
+ ```
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:
204
+ ```apache
205
+ <VirtualHost *:443>
206
+ ServerName search.example.org
207
+ # your certificate, as for the server's own site (e.g. from certbot)
208
+ SSLEngine on
209
+ SSLCertificateFile /etc/letsencrypt/live/search.example.org/fullchain.pem
210
+ SSLCertificateKeyFile /etc/letsencrypt/live/search.example.org/privkey.pem
211
+
212
+ # Apache to OpenSearch, on this machine. OpenSearch's own certificate is the self-signed demo one,
213
+ # so do not try to check it - the connection never leaves the machine.
214
+ SSLProxyEngine on
215
+ SSLProxyVerify none
216
+ SSLProxyCheckPeerCN off
217
+ SSLProxyCheckPeerName off
218
+ ProxyPreserveHost off
219
+ ProxyPass / https://127.0.0.1:9200/
220
+ ProxyPassReverse / https://127.0.0.1:9200/
221
+
222
+ # Nobody outside manages logins or roles. The server does that itself, on the machine, not through
223
+ # here - and this keeps the admin account's management calls off the internet.
224
+ <LocationMatch "^/_plugins/_security/api">
225
+ Require all denied
226
+ </LocationMatch>
227
+ </VirtualHost>
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.
237
+
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`.
240
+
241
+ Port 9200 itself stays closed to the network (firewall) - only Apache's 443 is open.
242
+
243
+ ---
244
+
245
+ ## 4. (a) A login for each person
246
+
247
+ Dashboards needs a login to OpenSearch, and it must not be able to read another organization's readings.
248
+ Each person who wants Dashboards opens, in the dashboard, the project's back, then the **OpenSearch** card,
249
+ and presses **Make a login**. It is theirs, named `frugaliot_<org>_<username>` (for example
250
+ `frugaliot_dev_mitra`), so the same person has a separate one in each organization. The server then,
251
+ through OpenSearch's security API:
252
+
253
+ - makes (or refreshes) a **role** `frugaliot_<org>`, shared by that organization's people, that may **read**
254
+ the organization's readings (`frugaliot-<org>-*`) and nothing else, plus do anything to the
255
+ organization's own saved-dashboards index (`.kibana_<org>*`) - so the people of an organization share
256
+ their saved dashboards, and two organizations do not see each other's;
257
+ - makes the person's **user** with a new password, shown once, and connects it to the role.
258
+
259
+ *Replace the password* makes a new one; *Remove my login* deletes it, and nobody else's. An administrator
260
+ of the whole organization also sees everyone's logins on the card and can remove one - do that when
261
+ someone leaves, because **OpenSearch does not notice a change to who may read** on the dashboard: a login
262
+ stays until it is removed.
263
+
264
+ It needs **READ for the whole organization**: the role covers all of it, so someone who can read only
265
+ some projects is told to use Grafana, whose tokens follow project permissions. It works only for an
266
+ organization whose id is **lower-case letters and digits**, a username of letters, digits and `. _ -`, and
267
+ only with the `admin:` and `public:` lines of `config.d/opensearch.yaml` filled in. The card says what is
268
+ missing.
269
+
270
+ ---
271
+
272
+ ## 5. (a) OpenSearch Dashboards, on your own computer
273
+
274
+ Whoever wants it installs OpenSearch Dashboards **on their own computer** - not on the server. On a Mac:
275
+
276
+ ```sh
277
+ mkdir -p /opt/homebrew/var/run # Homebrew forgets this folder, and Dashboards will not start without it
278
+ brew install opensearch-dashboards
279
+ ```
280
+
281
+ (Other systems: [OpenSearch's download page](https://opensearch.org/downloads.html).) Then put the lines the
282
+ card showed into the Dashboards configuration file (`/opt/homebrew/etc/opensearch-dashboards/opensearch_dashboards.yml`
283
+ on a Mac), start it (`brew services start opensearch-dashboards`) and open <http://localhost:5601>:
284
+
285
+ ```yaml
286
+ opensearch.hosts: [ "https://search.example.org" ]
287
+ opensearch.username: "frugaliot_dev_mitra"
288
+ opensearch.password: "..."
289
+ opensearchDashboards.index: ".kibana_dev"
290
+ opensearch.requestHeadersAllowlist: []
291
+ opensearch.customHeaders: { authorization: "Basic <the login, encoded - the card writes this line for you>" }
292
+ ```
293
+
294
+ The `opensearchDashboards.index` line is what gives the organization its own place to keep saved
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
305
+ time field.
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
+
361
+ You can use the same login in **Grafana** instead, with its OpenSearch data source and the same address.
362
+
363
+ ---
364
+
365
+ ## 6. (b) OpenSearch on your own machine
366
+
367
+ If you already run OpenSearch somewhere (or want to), the server can send readings to it. Nothing needs
368
+ installing on the Frugal IoT server. In the dashboard, the **Forwarders** card, add **OpenSearch on
369
+ another server**, with:
370
+
371
+ - its URL - **https**, reachable from the Frugal IoT server over the internet;
372
+ - a username and password, or an API key, for an account that can write indices (`frugaliot-*`) and
373
+ create the index template;
374
+ - optionally *Only these topics*.
375
+
376
+ What it must be: on a **public address**, with a **certificate that can be checked** (a real one, not a
377
+ self-signed demo certificate). The server refuses an address on its own machine or on a private network
378
+ (`localhost`, `192.168.x.x`, `10.x.x.x`) and cannot be told to skip the certificate check. So a computer
379
+ at home behind a router will not work unless you make it reachable (a forwarded port, a tunnel, or put
380
+ OpenSearch on a small cloud machine). The forwarder only sends **new** readings; to load history, the
381
+ operator runs the backfill from section 2 (it reads the same forwarder).
382
+
383
+ ---
384
+
385
+ ## Keeping it safe
386
+
387
+ Which login can see what:
388
+
389
+ | | Can read | How it is kept in |
390
+ |---|---|---|
391
+ | Grafana token | what its maker may READ, in one organization | checked by this server on every request |
392
+ | Dashboards login (one per person per organization) | the whole of one organization's readings (`frugaliot-<org>-*`) | OpenSearch's own role; stays until removed |
393
+ | the `admin` login in `config.d/opensearch.yaml` | everything, and can change security | stays on the machine, never sent anywhere |
394
+
395
+ 1. **OpenSearch listens on the machine itself only.** Check, on the server:
396
+ ```sh
397
+ sudo ss -ltnp | grep -E ':(9200|9300)\b'
398
+ ```
399
+ Both should show `127.0.0.1` or `[::1]`, not `0.0.0.0` or `*`. If not, look for a `network.host` line
400
+ in `/etc/opensearch/opensearch.yml` and remove it. Block 9200 and 9300 in your firewall as well.
401
+ 2. **Only Apache's HTTPS port faces the internet**, and it refuses `/_plugins/_security/api`. Do not
402
+ expose OpenSearch's own port, and do not run Dashboards on the server.
403
+ 3. **The demo security setup is for trying things out.** It ships certificates that are the same on every
404
+ OpenSearch install, and accounts that are not meant for production. The `admin` account is now
405
+ reachable through Apache for anyone who guesses its password, so choose a long one, and consider
406
+ changing OpenSearch's built-in accounts you do not use (Security → Internal users in Dashboards).
407
+ Rate-limiting repeated failures (`fail2ban`) in front of that site is worth doing.
408
+ 4. **Passwords in the database.** The Forwarders card's password for *another server* is stored in the
409
+ clear in the server's database (`frugal-iot.db`), like every other credential in this system, and
410
+ never sent back. The `config.d/opensearch.yaml` file holds `admin`'s password: protect it like
411
+ `secrets.yaml`.
412
+ 5. **Anything put in front of Grafana for other people needs HTTPS**, and one Grafana organization per
413
+ Frugal IoT organization, each with its own data source and token - everyone in a Grafana organization
414
+ shares its data sources.
415
+
416
+ ---
417
+
418
+ ## 7. Optional: Grafana on the server too
419
+
420
+ Optional - for charts that need no setup on anyone's computer. Everyone who uses it then needs a login
421
+ to this Grafana, which is one more thing to look after. Because it is on the same machine as the
422
+ server, its data source can reach the server at `http://localhost:8080` and nothing has to be exposed.
423
+
424
+ ```sh
425
+ sudo apt-get install -y apt-transport-https wget gnupg
426
+ sudo mkdir -p /etc/apt/keyrings
427
+ sudo wget -O /etc/apt/keyrings/grafana.asc https://apt.grafana.com/gpg-full.key
428
+ sudo chmod 644 /etc/apt/keyrings/grafana.asc
429
+ echo "deb [signed-by=/etc/apt/keyrings/grafana.asc] https://apt.grafana.com stable main" \
430
+ | sudo tee -a /etc/apt/sources.list.d/grafana.list
431
+ sudo apt-get update
432
+ sudo apt-get install grafana
433
+ sudo systemctl daemon-reload
434
+ sudo systemctl enable grafana-server
435
+ sudo systemctl start grafana-server
436
+ sudo grafana cli plugins install yesoreyeram-infinity-datasource
437
+ sudo systemctl restart grafana-server
438
+ ```
439
+
440
+ It listens on port 3000, with Grafana's default login `admin` / `admin`, which it asks you to change.
441
+ Do not expose port 3000 to the internet without HTTPS in front of it. Then continue at
442
+ [GRAFANA.md](GRAFANA.md).
443
+
444
+ ---
445
+
446
+ ## For developers: trying the OpenSearch forwarder on a Mac
447
+
448
+ Not something users do. To work on the logger's OpenSearch forwarder you want OpenSearch on your own
449
+ machine:
450
+
451
+ ```sh
452
+ brew install opensearch opensearch-dashboards
453
+ brew services start opensearch
454
+ curl http://localhost:9200 # a block of JSON with "cluster_name"
455
+ ```
456
+
457
+ Homebrew's OpenSearch has **no security plugin**: it listens on `http://localhost:9200` with no login,
458
+ and only your own machine can reach it. Make `config.d/opensearch.yaml` in your server's directory contain
459
+ just:
460
+
461
+ ```yaml
462
+ node: http://localhost:9200
463
+ ```
464
+
465
+ and restart the server. The Forwarders card then offers **OpenSearch on this server**. The Dashboards
466
+ access card will say it cannot make logins, since that needs the security plugin; on your own Mac you
467
+ do not need one, because Dashboards reaches `http://localhost:9200` with no login.
468
+
469
+ Dashboards will not start until a folder exists that Homebrew forgot to create:
470
+
471
+ ```sh
472
+ mkdir -p /opt/homebrew/var/run
473
+ brew services start opensearch-dashboards
474
+ ```
475
+
476
+ (Without it the service shows `error 1` in `brew services list`, and the log
477
+ `/opt/homebrew/var/log/opensearch-dashboards.log` says it cannot open `opensearchDashboards.pid`.)
478
+ It is then at <http://localhost:5601>; create the `frugaliot-*` index pattern as in [section 5](#5-opensearch-dashboards-on-your-own-computer).
479
+
480
+ To stop them: `brew services stop opensearch-dashboards opensearch`.
481
+
482
+ ---
483
+
484
+ ---
485
+
486
+ ## Open questions
487
+
488
+ Please add what you find:
489
+
490
+ - **The Linux steps have not been run for the opensource-dashboards**: the exact `apt` repository line, and the Apache site above.
491
+ - **`opensearchDashboards.index`** is meant to give each organization its own saved-objects index. Check
492
+ that two organizations' Dashboards do not see each other's saved dashboards, and that neither can open
493
+ the other's `.kibana_<org>`.
494
+ - **A write-only role for the forwarder**, in place of `admin`. Not written, because it cannot be tested
495
+ on a Mac. It needs to be able to install the index template too, or `value` loses its decimals (the
496
+ forwarder logs when it cannot).
497
+ - **Limiting a login to one project** (OpenSearch can filter by the `project` field of each reading) is not
498
+ done: a Dashboards login sees the whole organization, so it needs READ for all of it.
499
+ - **Logins outlive a change of permissions.** Removing someone's READ on the dashboard does not remove their
500
+ OpenSearch login; an administrator removes it on the card. Doing it automatically (the server already
501
+ notes when a user's permissions change) is not built.