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 +145 -0
- package/INSTALL-OPENSEARCH.md +501 -0
- package/config.d/opensearch.yaml +27 -0
- package/extras/grafana/frugaliot-readings.json +166 -0
- package/frugal-iot-createdb.sql +38 -0
- package/frugal-iot-server.js +48 -6
- package/lib/admin-guards.js +26 -0
- package/lib/config-for-user.js +10 -0
- package/lib/destinations.js +121 -0
- package/lib/forwarders.js +351 -0
- package/lib/grafana.js +420 -0
- package/lib/opensearch-access.js +252 -0
- package/lib/schema-columns.js +1 -0
- package/package.json +10 -4
- package/public/service-worker.js +1 -1
- package/scripts/backfill-opensearch.js +66 -0
- package/scripts/grafana-token.js +62 -0
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.
|