frugal-iot-server 0.3.4 → 0.3.5
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/INSTALLATION.md +108 -34
- package/config.d/server.yaml +4 -4
- package/extras/mosquitto.conf +5 -1
- package/frugal-iot-server.js +19 -0
- package/package.json +4 -3
- package/scripts/addorganization.zsh +55 -11
- package/scripts/diagnostic.zsh +289 -0
- package/scripts/init.zsh +33 -6
package/INSTALLATION.md
CHANGED
|
@@ -13,9 +13,13 @@ Two hardware paths are covered:
|
|
|
13
13
|
If you already have a working Linux server (not a Pi), you do not need this document —
|
|
14
14
|
see [README.md](https://github.com/mitra42/frugal-iot-server/blob/main/README.md) instead.
|
|
15
15
|
|
|
16
|
-
> **Status:**
|
|
17
|
-
>
|
|
18
|
-
>
|
|
16
|
+
> **Status:** the Raspberry Pi 4 path has been followed end to end on hardware (see
|
|
17
|
+
> [Tested on](#tested-on)), and corrected from what that run found. Part B, and HTTPS/OTA in step
|
|
18
|
+
> A10, are not written yet. Remaining uncertainties are listed under
|
|
19
|
+
> [Open questions](#open-questions) — please add your findings there as you go.
|
|
20
|
+
|
|
21
|
+
**Already have a Frugal IoT server running and just want a newer version?** Skip everything below
|
|
22
|
+
and go to [Upgrading](#upgrading).
|
|
19
23
|
|
|
20
24
|
---
|
|
21
25
|
|
|
@@ -25,8 +29,11 @@ see [README.md](https://github.com/mitra42/frugal-iot-server/blob/main/README.md
|
|
|
25
29
|
|
|
26
30
|
**Hardware**
|
|
27
31
|
|
|
28
|
-
* Raspberry Pi 4 Model B.
|
|
29
|
-
|
|
32
|
+
* Raspberry Pi 4 Model B. Any RAM size — a running server uses around 250 MB, so even the 1 GB
|
|
33
|
+
model has plenty of room. (Measured on a 4 GB Pi 4: 231 MB in use with the server running.)
|
|
34
|
+
* A microSD card. 8 GB is enough for the system and software — a working install occupies about
|
|
35
|
+
5 GB — but sensor data accumulates on this card for as long as the server runs, so 16 GB or larger
|
|
36
|
+
is the safer choice. Class 10 / A1 or better.
|
|
30
37
|
* The official Raspberry Pi USB-C power supply (5 V / 3 A). Phone chargers frequently cause
|
|
31
38
|
random reboots and corrupted SD cards — this is the single most common cause of "it doesn't work".
|
|
32
39
|
* A way to write the SD card from your laptop: a built-in SD slot or a USB card reader.
|
|
@@ -48,10 +55,10 @@ see [README.md](https://github.com/mitra42/frugal-iot-server/blob/main/README.md
|
|
|
48
55
|
|
|
49
56
|
**Have these to hand in case the headless setup does not come up**
|
|
50
57
|
|
|
51
|
-
*
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
* An Ethernet cable from the Pi to your router — the simplest way in if the Pi does not appear on
|
|
59
|
+
the Wi-Fi, and you can then sort the Wi-Fi out over SSH (step A2).
|
|
60
|
+
* A micro-HDMI to HDMI cable plus a monitor, and a USB keyboard — for when the Pi is nowhere near the
|
|
61
|
+
router, or you want to see boot messages.
|
|
55
62
|
|
|
56
63
|
### A1. Write the operating system to the SD card
|
|
57
64
|
|
|
@@ -72,9 +79,11 @@ a desktop would only consume memory and SD card space.
|
|
|
72
79
|
* Type the SSID exactly as the network broadcasts it, including capitals. Imager does not store
|
|
73
80
|
your Wi-Fi password; it converts it into a 64-character key using the SSID, so a mistyped SSID
|
|
74
81
|
produces a key that fails even though the password was right.
|
|
75
|
-
*
|
|
76
|
-
|
|
77
|
-
|
|
82
|
+
* If the Pi does not join the network, a mistyped SSID or password is much the likeliest cause -
|
|
83
|
+
neither is echoed back to you here, and Imager combines the two into a key, so a slip in either
|
|
84
|
+
looks the same later. Step A2 fixes it in a couple of minutes with `nmtui`.
|
|
85
|
+
* A WPA3 network cannot use the key Imager derives at all, so on one of those expect to finish the
|
|
86
|
+
Wi-Fi setup with `nmtui` regardless.
|
|
78
87
|
10. Enable SSH, either choose *Use password authentication* or paste your public key if you already use SSH keys.
|
|
79
88
|
11. Leave Raspberry Pi Connect off for now - feel free to experiment with this, as we haven't yet.
|
|
80
89
|
12. Confirm that you want to save settings and write to the card, and click through operating system prompts wanting to stop you !
|
|
@@ -170,12 +179,12 @@ address does not change. Sensor nodes and phones then have something stable to t
|
|
|
170
179
|
```
|
|
171
180
|
sudo apt install -y nodejs npm sqlite3 zsh
|
|
172
181
|
node -v
|
|
173
|
-
npm -v
|
|
174
182
|
```
|
|
175
183
|
|
|
176
|
-
* `nodejs` — the server needs **Node 18 or later**. Raspberry Pi OS
|
|
177
|
-
which is fine. If `node -v`
|
|
178
|
-
version from
|
|
184
|
+
* `nodejs` — the server needs **Node 18 or later**. Current Raspberry Pi OS (Debian 13, trixie)
|
|
185
|
+
provides 20.19.2, which is fine. Older images shipped Node 18, also fine. If `node -v` reports
|
|
186
|
+
anything below 18, install a current version from
|
|
187
|
+
[NodeSource](https://github.com/nodesource/distributions) instead.
|
|
179
188
|
* `npm` — installs the server; it is a separate package from `nodejs` on Debian.
|
|
180
189
|
* `sqlite3` — the database the server keeps its accounts in.
|
|
181
190
|
* `zsh` — the setup commands in step A6 are zsh scripts.
|
|
@@ -257,15 +266,22 @@ sudo cp extras/mosquitto.conf /etc/mosquitto/conf.d/frugal-iot.conf
|
|
|
257
266
|
`/etc/mosquitto/mosquitto.conf`, which keeps its own settings for logging and persistence.)
|
|
258
267
|
|
|
259
268
|
That configuration names a password file, and Mosquitto will not start if the file is missing, so
|
|
260
|
-
create an empty one. The accounts inside it get created for you in the next step
|
|
261
|
-
`addorganization.zsh` — which runs as you rather than as root, hence the ownership:
|
|
269
|
+
create an empty one. The accounts inside it get created for you in the next step:
|
|
262
270
|
|
|
263
271
|
```
|
|
264
|
-
sudo
|
|
265
|
-
sudo chown ${USER}:mosquitto /etc/mosquitto/mosquitto_passwords
|
|
266
|
-
sudo chmod 640 /etc/mosquitto/mosquitto_passwords
|
|
272
|
+
sudo install -o mosquitto -g mosquitto -m 600 /dev/null /var/lib/mosquitto/passwords
|
|
267
273
|
```
|
|
268
274
|
|
|
275
|
+
That makes an empty file belonging to the `mosquitto` user, readable by nobody else — one command
|
|
276
|
+
instead of a `touch`, a `chown` and a `chmod`.
|
|
277
|
+
|
|
278
|
+
> The ownership matters, and is easy to get wrong. Mosquitto warns unless the password file belongs
|
|
279
|
+
> to whoever opened it, and the broker runs as the `mosquitto` user — so the file belongs to
|
|
280
|
+
> `mosquitto`, and it lives under `/var/lib/mosquitto` (which that user owns) rather than
|
|
281
|
+
> `/etc/mosquitto` (which root owns). `mosquitto_passwd` also writes a temporary backup file
|
|
282
|
+
> alongside it, so it needs to write to that directory too, not just to the file. Step A6 runs it as
|
|
283
|
+
> the right user for you.
|
|
284
|
+
|
|
269
285
|
Start the broker and have it start at every boot:
|
|
270
286
|
|
|
271
287
|
```
|
|
@@ -525,6 +541,16 @@ network and everything is correct — which is what you will see during this ins
|
|
|
525
541
|
cannot: Raspberry Pi OS saves the time periodically and restores that value at boot, so after a
|
|
526
542
|
power cut the Pi comes up believing it is whenever it last saved, and the gap never gets made up.
|
|
527
543
|
|
|
544
|
+
To see whether the Pi's clock is actually right at any moment:
|
|
545
|
+
|
|
546
|
+
```
|
|
547
|
+
timedatectl
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
`System clock synchronized: yes` means it has reached a time server and the clock is trustworthy.
|
|
551
|
+
`no`, with `NTP service: active`, means it is trying but has not succeeded — normal on a Pi with no
|
|
552
|
+
route to the internet, and the point at which the timestamps below become a concern.
|
|
553
|
+
|
|
528
554
|
What that affects:
|
|
529
555
|
|
|
530
556
|
* **Logged data is stamped with the wrong time**, so graphs and history drift after each power cut.
|
|
@@ -539,6 +565,54 @@ but close enough for sensor data.
|
|
|
539
565
|
|
|
540
566
|
---
|
|
541
567
|
|
|
568
|
+
## Upgrading
|
|
569
|
+
|
|
570
|
+
To move an existing server to a newer release, from the directory you installed into:
|
|
571
|
+
|
|
572
|
+
```
|
|
573
|
+
cd ~/frugal-iot
|
|
574
|
+
npm update frugal-iot-server
|
|
575
|
+
npx frugal-iot-init
|
|
576
|
+
sudo systemctl restart frugaliot
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
Taking those in turn:
|
|
580
|
+
|
|
581
|
+
* `npm update` fetches the new version of the server, and of the client and logger it depends on.
|
|
582
|
+
* `frugal-iot-init` adds any configuration file or directory the new version expects and you do not
|
|
583
|
+
have yet. It never changes files you already have, so your settings, database and data are safe.
|
|
584
|
+
* `systemctl restart frugaliot` is what actually puts the new version into service — without it the
|
|
585
|
+
old one keeps running from memory, and nothing appears to have changed. (Skip this if you are
|
|
586
|
+
running the server by hand rather than as a service; stop it with `Ctrl-C` and start it again.)
|
|
587
|
+
|
|
588
|
+
Check it came back up, and is running the version you expect:
|
|
589
|
+
|
|
590
|
+
```
|
|
591
|
+
systemctl status frugaliot
|
|
592
|
+
journalctl -u frugaliot -n 30
|
|
593
|
+
npm ls frugal-iot-server
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
`frugal-iot-init` leaves your files alone, so watch its output for a section headed **"These files
|
|
597
|
+
were left as you have them, but this release ships a different version"**. It gives you the `diff`
|
|
598
|
+
command for each. That matters most for the files you copy somewhere else — a changed
|
|
599
|
+
`extras/mosquitto.conf` or `extras/frugaliot.service` does nothing until you install it again:
|
|
600
|
+
|
|
601
|
+
```
|
|
602
|
+
sudo cp extras/mosquitto.conf /etc/mosquitto/conf.d/frugal-iot.conf && sudo systemctl restart mosquitto
|
|
603
|
+
sudo cp extras/frugaliot.service /etc/systemd/system/ && sudo systemctl daemon-reload && sudo systemctl restart frugaliot
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
> It does not compare `config.yaml`, `config.d/mqtt.yaml` or `config.d/server.yaml`, because those
|
|
607
|
+
> hold your own settings and would differ every time. If a release note mentions a new setting in
|
|
608
|
+
> one of them, compare it yourself:
|
|
609
|
+
> `diff config.d/server.yaml node_modules/frugal-iot-server/config.d/server.yaml`
|
|
610
|
+
|
|
611
|
+
Your organizations, accounts, database and logged data are untouched by an upgrade — they live in
|
|
612
|
+
this directory, not in `node_modules`.
|
|
613
|
+
|
|
614
|
+
---
|
|
615
|
+
|
|
542
616
|
## Part B — Raspberry Pi Zero W
|
|
543
617
|
|
|
544
618
|
**Not yet tested — do not follow this section expecting it to work.** It records what is known
|
|
@@ -547,8 +621,9 @@ and what has to be checked.
|
|
|
547
621
|
First, work out which board you have, because they are very different:
|
|
548
622
|
|
|
549
623
|
* **Raspberry Pi Zero 2 W** — 64-bit ARM (Cortex-A53), 512 MB RAM. Should follow **Part A**
|
|
550
|
-
almost unchanged: choose *Raspberry Pi Zero 2 W* in Imager, and everything else applies.
|
|
551
|
-
|
|
624
|
+
almost unchanged: choose *Raspberry Pi Zero 2 W* in Imager, and everything else applies. Running
|
|
625
|
+
the server needs about 250 MB, so 512 MB should be enough; the pinch point is more likely to be
|
|
626
|
+
`npm install`, which is far hungrier than the running server.
|
|
552
627
|
* **Raspberry Pi Zero W** (the original) — 32-bit ARMv6 (BCM2835), 512 MB RAM. This is the
|
|
553
628
|
awkward one: it needs the 32-bit Raspberry Pi OS, and **the official Node.js builds no longer
|
|
554
629
|
support ARMv6**. Unofficial ARMv6 builds exist at
|
|
@@ -565,8 +640,9 @@ Differences to expect on either Zero:
|
|
|
565
640
|
* Everything will be slow. `npm install` may take a long time, especially if `sqlite3` has to be
|
|
566
641
|
compiled from source — which is likely on ARMv6, since no ready-made binaries are published.
|
|
567
642
|
|
|
568
|
-
**
|
|
569
|
-
|
|
643
|
+
**Both boards are to be tested**, one of each having been ordered for the purpose. This section gets
|
|
644
|
+
written once we know how they behave — in particular whether a usable Node exists for the original
|
|
645
|
+
Zero W's ARMv6.
|
|
570
646
|
|
|
571
647
|
---
|
|
572
648
|
|
|
@@ -584,20 +660,18 @@ they get settled.
|
|
|
584
660
|
|
|
585
661
|
**Needs information I do not have**
|
|
586
662
|
|
|
587
|
-
2. **
|
|
663
|
+
2. **Pi Zero W and Zero 2 W** (Part B) — both boards are on order; Part B stays unwritten until they
|
|
664
|
+
have been tried. The open technical question is Node on the original Zero W's ARMv6.
|
|
588
665
|
3. **Bridging to the shared server** — the local broker could optionally bridge to
|
|
589
666
|
naturalinnovation.org so data also reaches the shared server. Not covered here; a later task.
|
|
590
667
|
4. **Organization naming** — this guide sets up exactly one organization named `dev`, because that
|
|
591
668
|
is the node firmware's default. Is that the right default for a farm installation, or should the
|
|
592
669
|
guide encourage a meaningful organization id (which then has to be set in the node firmware too)?
|
|
593
|
-
5. **Upgrading an existing installation** — `npm update frugal-iot-server` followed by
|
|
594
|
-
`npx frugal-iot-init` should be all it takes, since init adds missing configuration without
|
|
595
|
-
touching what is there. Not yet tried on a server that has been running for a while.
|
|
596
670
|
|
|
597
|
-
|
|
671
|
+
## Tested on
|
|
598
672
|
|
|
599
|
-
|
|
673
|
+
What the guide has actually been proven against — add a row for each run:
|
|
600
674
|
|
|
601
|
-
| Date | Board | OS image | Node
|
|
602
|
-
| --- | --- | --- | --- | --- |
|
|
603
|
-
|
|
|
675
|
+
| Date | Board | OS image | Node | Server | Result |
|
|
676
|
+
| --- | --- | --- | --- | --- | --- |
|
|
677
|
+
| 2026-07-31 | Pi 4 Model B, 4 GB | Raspberry Pi OS Lite 64-bit, Debian 13.6 (trixie) | 20.19.2 and npm 9.2.0, both from `apt` | frugal-iot-server 0.3.3 from npm | Steps A1–A8 completed, plus an upgrade over the top of it. Wi-Fi from Imager did not connect and was fixed with `nmtui` (A2). `sqlite3` installed as a prebuilt binary, nothing compiled. Mosquitto started with an empty password file, and `frugal-iot-addorganization` wrote the broker account without sudo. Dashboard reached over `frugaliot.local` from a Mac (Chrome, after allowing local network access) and from an iPhone, MQTT status *connected*. A9 (sensor nodes) and A10 (HTTPS/OTA) not exercised. |
|
package/config.d/server.yaml
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
port: 8080
|
|
2
2
|
#Production mode
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
nodemodulesdir: ./node_modules
|
|
4
|
+
htmldir: ./node_modules/frugal-iot-client
|
|
5
5
|
# Development mode (uncomment these two, and comment out the two above, when working on the client)
|
|
6
|
-
htmldir: ../frugal-iot-client
|
|
7
|
-
nodemodulesdir: ../frugal-iot-client/node_modules
|
|
6
|
+
#htmldir: ../frugal-iot-client
|
|
7
|
+
#nodemodulesdir: ../frugal-iot-client/node_modules
|
|
8
8
|
# The path to the private directory is relative to the server's working directory.
|
|
9
9
|
privatedir: ./private
|
|
10
10
|
publicdir: ./public
|
package/extras/mosquitto.conf
CHANGED
|
@@ -3,4 +3,8 @@ protocol mqtt
|
|
|
3
3
|
listener 9012
|
|
4
4
|
protocol websockets
|
|
5
5
|
#http_dir /home/mitra/github_mitra42/frugal-iot/html
|
|
6
|
-
|
|
6
|
+
# Kept under /var/lib/mosquitto, which belongs to the mosquitto user, rather than /etc/mosquitto,
|
|
7
|
+
# which belongs to root. Both the broker and mosquitto_passwd warn unless the file belongs to
|
|
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.
|
|
10
|
+
password_file /var/lib/mosquitto/passwords
|
package/frugal-iot-server.js
CHANGED
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
*Δ /admin (get) dashboard for administrators - includes OTA and will include permission management
|
|
31
31
|
P /ota_update (post) protected place to upload new binaries (OTAUPDATE)
|
|
32
32
|
XP /ota_list/:org list all ota files for an organization
|
|
33
|
+
XP /ota_get/:org/*remainingpath download a binary so the client can flash it over USB
|
|
33
34
|
O /private Serve up private files under authentication - currently unused
|
|
34
35
|
* /register (post) register a new user
|
|
35
36
|
*/
|
|
@@ -920,6 +921,24 @@ mqttLogger.readYamlConfig('.', (err, configobj) => {
|
|
|
920
921
|
});
|
|
921
922
|
}
|
|
922
923
|
);
|
|
924
|
+
// Download an OTA binary so the client can flash it over USB (see FLASH_PLAN.md in
|
|
925
|
+
// frugal-iot-client). Deliberately not /ota_update, which is unauthenticated for devices
|
|
926
|
+
// and answers 304 rather than sending bytes.
|
|
927
|
+
app.get('/ota_get/:org/*remainingpath',
|
|
928
|
+
loggedInOrFail,
|
|
929
|
+
can_OTAUPDATE,
|
|
930
|
+
(req,res) => {
|
|
931
|
+
let remainingpath = req.params.remainingpath.join('/');
|
|
932
|
+
let filepath = `${config.server.otadir}/${req.params.org}/${sanitize(remainingpath)}/firmware.bin`;
|
|
933
|
+
console.log("Sending OTA file", filepath);
|
|
934
|
+
res.sendFile(filepath, {}, (err) => {
|
|
935
|
+
if (err) {
|
|
936
|
+
console.error("Error sending ota file:", filepath, err);
|
|
937
|
+
if (!res.headersSent) res.status(404).send(err.message);
|
|
938
|
+
}
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
);
|
|
923
942
|
app.get('/people_list/:org',
|
|
924
943
|
loggedInOrFail,
|
|
925
944
|
can_ADMIN, // Gets org from URL
|
package/package.json
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"express": "^5.2.1",
|
|
13
13
|
"express-session": "^1.19.0",
|
|
14
14
|
"frugal-iot-client": "^1.3.11",
|
|
15
|
-
"frugal-iot-logger": "^1.1.
|
|
15
|
+
"frugal-iot-logger": "^1.1.18",
|
|
16
16
|
"hash-wasm": "^4.12.0",
|
|
17
17
|
"morgan": "^1.11.0",
|
|
18
18
|
"multer": "^2.1.1",
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"frugal-iot-server": "frugal-iot-server.js",
|
|
25
25
|
"frugal-iot-init": "scripts/init.zsh",
|
|
26
26
|
"frugal-iot-addorganization": "scripts/addorganization.zsh",
|
|
27
|
-
"frugal-iot-setpassword": "scripts/setpassword.zsh"
|
|
27
|
+
"frugal-iot-setpassword": "scripts/setpassword.zsh",
|
|
28
|
+
"frugal-iot-diagnostic": "scripts/diagnostic.zsh"
|
|
28
29
|
},
|
|
29
30
|
"description": "Frugal IoT client and server",
|
|
30
31
|
"//files": "An allowlist, not an ignore list - anything not named here is never published, so a stray file (a database backup, a credential) cannot leak by default. Check with: npm pack --dry-run",
|
|
@@ -66,5 +67,5 @@
|
|
|
66
67
|
"test:coverage": "vitest run --coverage"
|
|
67
68
|
},
|
|
68
69
|
"type": "module",
|
|
69
|
-
"version": "0.3.
|
|
70
|
+
"version": "0.3.5"
|
|
70
71
|
}
|
|
@@ -75,13 +75,50 @@ if ! command -v mosquitto_passwd >/dev/null; then
|
|
|
75
75
|
MOSQUITTO_PASSWD_MISSING=1
|
|
76
76
|
fi
|
|
77
77
|
|
|
78
|
-
#
|
|
79
|
-
#
|
|
80
|
-
|
|
78
|
+
# Ask the broker's own configuration where its password file is, rather than guessing - that way
|
|
79
|
+
# this works whatever layout a machine uses, including installations older than this script.
|
|
80
|
+
# Mosquitto reads /etc/mosquitto/mosquitto.conf and then the .conf files in conf.d, last one wins.
|
|
81
|
+
MOSQUITTO_PASSWD_FILE=$(grep -hE '^[[:space:]]*password_file[[:space:]]' \
|
|
82
|
+
/etc/mosquitto/mosquitto.conf /etc/mosquitto/conf.d/*.conf(N) 2>/dev/null | tail -1 | awk '{print $2}')
|
|
81
83
|
USED_LOCAL_MOSQUITTO_FILE=0
|
|
82
|
-
if [[
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
if [[ -n "$MOSQUITTO_PASSWD_FILE" ]]; then
|
|
85
|
+
MOSQUITTO_PASSWD_FROM_CONFIG=1
|
|
86
|
+
else
|
|
87
|
+
MOSQUITTO_PASSWD_FROM_CONFIG=0
|
|
88
|
+
# No mosquitto configuration here - fall back to the usual locations, and then to the copy under
|
|
89
|
+
# extras/ so that a machine without mosquitto installed still gets something useful.
|
|
90
|
+
for f in /var/lib/mosquitto/passwords /etc/mosquitto/mosquitto_passwords; do
|
|
91
|
+
if [[ -e "$f" ]]; then
|
|
92
|
+
MOSQUITTO_PASSWD_FILE="$f"
|
|
93
|
+
break
|
|
94
|
+
fi
|
|
95
|
+
done
|
|
96
|
+
if [[ -z "$MOSQUITTO_PASSWD_FILE" ]]; then
|
|
97
|
+
if [[ -e "extras/mosquitto_passwords" ]]; then
|
|
98
|
+
MOSQUITTO_PASSWD_FILE="extras/mosquitto_passwords"
|
|
99
|
+
USED_LOCAL_MOSQUITTO_FILE=1
|
|
100
|
+
else
|
|
101
|
+
MOSQUITTO_PASSWD_FILE="/var/lib/mosquitto/passwords"
|
|
102
|
+
fi
|
|
103
|
+
fi
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
# Run mosquitto_passwd as whoever owns that file. Mosquitto warns unless the file belongs to the user
|
|
107
|
+
# opening it, and mosquitto_passwd writes a temporary backup beside it, so it needs the directory too
|
|
108
|
+
# - neither of which we get by running as ourselves against a file owned by mosquitto or by root.
|
|
109
|
+
# On Raspberry Pi OS the first user has passwordless sudo, so this is invisible.
|
|
110
|
+
MOSQUITTO_PASSWD_CMD=(mosquitto_passwd)
|
|
111
|
+
if [[ ! -w "${MOSQUITTO_PASSWD_FILE:h}" || ( -e "$MOSQUITTO_PASSWD_FILE" && ! -w "$MOSQUITTO_PASSWD_FILE" ) ]]; then
|
|
112
|
+
if command -v sudo >/dev/null; then
|
|
113
|
+
# stat's spelling differs between Linux (-c) and BSD/macOS (-f)
|
|
114
|
+
MOSQUITTO_PASSWD_OWNER=$(stat -c '%U' "$MOSQUITTO_PASSWD_FILE" 2>/dev/null \
|
|
115
|
+
|| stat -f '%Su' "$MOSQUITTO_PASSWD_FILE" 2>/dev/null || true)
|
|
116
|
+
if [[ -n "$MOSQUITTO_PASSWD_OWNER" && "$MOSQUITTO_PASSWD_OWNER" != "$(id -un)" && "$MOSQUITTO_PASSWD_OWNER" != "root" ]]; then
|
|
117
|
+
MOSQUITTO_PASSWD_CMD=(sudo -u "$MOSQUITTO_PASSWD_OWNER" mosquitto_passwd)
|
|
118
|
+
else
|
|
119
|
+
MOSQUITTO_PASSWD_CMD=(sudo mosquitto_passwd)
|
|
120
|
+
fi
|
|
121
|
+
fi
|
|
85
122
|
fi
|
|
86
123
|
|
|
87
124
|
# ---- Escaping helpers ----
|
|
@@ -168,17 +205,24 @@ MOSQUITTO_PASSWD_FAILED=0
|
|
|
168
205
|
MOSQUITTO_PASSWD_ERROR=""
|
|
169
206
|
if [[ "$MOSQUITTO_PASSWD_MISSING" -eq 1 ]]; then
|
|
170
207
|
echo "Warning: mosquitto_passwd command not found - skipped setting MQTT broker password" >&2
|
|
208
|
+
elif [[ "$MOSQUITTO_PASSWD_FROM_CONFIG" -eq 1 && ! -e "$MOSQUITTO_PASSWD_FILE" ]]; then
|
|
209
|
+
# Nothing to add an account to - and mosquitto will not be running either, for the same reason.
|
|
210
|
+
echo "Warning: mosquitto is configured to use ${MOSQUITTO_PASSWD_FILE}, but that file does not exist," >&2
|
|
211
|
+
echo "so the MQTT broker password was NOT set. Create it, then add this organization to it:" >&2
|
|
212
|
+
echo " sudo install -o mosquitto -g mosquitto -m 600 /dev/null \"${MOSQUITTO_PASSWD_FILE}\"" >&2
|
|
213
|
+
echo " sudo -u mosquitto mosquitto_passwd -b \"${MOSQUITTO_PASSWD_FILE}\" \"${ORG_ID}\" \"${PASSWORD}\"" >&2
|
|
214
|
+
MOSQUITTO_PASSWD_FAILED=1
|
|
171
215
|
else
|
|
172
216
|
# Capture stderr without letting `set -e` abort the whole script on failure - the org's config file
|
|
173
217
|
# and DB rows are already written by this point, so a failure here should be reported, not fatal.
|
|
174
|
-
MOSQUITTO_PASSWD_ERROR=$(
|
|
218
|
+
MOSQUITTO_PASSWD_ERROR=$("${MOSQUITTO_PASSWD_CMD[@]}" -b "$MOSQUITTO_PASSWD_FILE" "${ORG_ID}" "${PASSWORD}" 2>&1 >/dev/null) || MOSQUITTO_PASSWD_FAILED=1
|
|
175
219
|
if [[ "$MOSQUITTO_PASSWD_FAILED" -eq 1 ]]; then
|
|
176
220
|
echo "Warning: mosquitto_passwd failed to set the MQTT broker password:" >&2
|
|
177
221
|
echo " ${MOSQUITTO_PASSWD_ERROR}" >&2
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
222
|
+
echo "Everything else was done, so set just the broker password by hand:" >&2
|
|
223
|
+
echo " sudo mosquitto_passwd -b \"${MOSQUITTO_PASSWD_FILE}\" \"${ORG_ID}\" \"${PASSWORD}\"" >&2
|
|
224
|
+
echo "If it complains about creating a backup file, the directory holding that file is not" >&2
|
|
225
|
+
echo "writable by you - which is why the command above uses sudo." >&2
|
|
182
226
|
else
|
|
183
227
|
echo "Set mosquitto password for ${ORG_ID} in ${MOSQUITTO_PASSWD_FILE}"
|
|
184
228
|
fi
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
#!/usr/bin/env zsh
|
|
2
|
+
#
|
|
3
|
+
# Report the state of a Frugal IoT installation, for working out why something in INSTALLATION.md
|
|
4
|
+
# did not do what it said it would. It only looks - it changes nothing.
|
|
5
|
+
#
|
|
6
|
+
# Run it from the directory the server is installed in (the one holding frugal-iot.db):
|
|
7
|
+
# zsh diagnostic.zsh
|
|
8
|
+
# or, once installed from npm:
|
|
9
|
+
# npx frugal-iot-diagnostic
|
|
10
|
+
#
|
|
11
|
+
# Paste the whole output into a bug report. Passwords are deliberately not printed: the password
|
|
12
|
+
# file is reported by account name only, and organization configs by name only.
|
|
13
|
+
|
|
14
|
+
# No "set -e": a failing check should report and carry on, not stop the script.
|
|
15
|
+
setopt no_unset 2>/dev/null
|
|
16
|
+
|
|
17
|
+
section() { print -r -- ""; print -r -- "===== $* ====="; }
|
|
18
|
+
item() { print -r -- " $*"; }
|
|
19
|
+
have() { command -v "$1" >/dev/null 2>&1; }
|
|
20
|
+
# Ownership and mode of a file, spelled for either Linux or BSD/macOS stat
|
|
21
|
+
fileinfo() {
|
|
22
|
+
local f=$1
|
|
23
|
+
if [[ ! -e "$f" ]]; then print -r -- "missing"; return; fi
|
|
24
|
+
stat -c '%U:%G %a %s bytes' "$f" 2>/dev/null || stat -f '%Su:%Sg %Lp %z bytes' "$f" 2>/dev/null || print -r -- "(cannot stat)"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
PROBLEMS=()
|
|
28
|
+
problem() { PROBLEMS+=("$1"); }
|
|
29
|
+
|
|
30
|
+
section "When and where"
|
|
31
|
+
item "date: $(date 2>/dev/null)"
|
|
32
|
+
item "host: $(hostname 2>/dev/null)"
|
|
33
|
+
item "user: $(id -un 2>/dev/null) (groups: $(id -Gn 2>/dev/null))"
|
|
34
|
+
item "directory: $PWD"
|
|
35
|
+
|
|
36
|
+
section "The machine"
|
|
37
|
+
have uname && item "kernel: $(uname -srm)"
|
|
38
|
+
[[ -r /etc/os-release ]] && item "os: $(grep PRETTY_NAME /etc/os-release | cut -d'"' -f2)"
|
|
39
|
+
[[ -r /proc/device-tree/model ]] && item "board: $(tr -d '\0' < /proc/device-tree/model)"
|
|
40
|
+
have free && item "memory: $(free -h | awk '/^Mem:/ {print $3 " used of " $2 ", " $7 " available"}')"
|
|
41
|
+
have df && item "disk (/): $(df -h / | awk 'NR==2 {print $3 " used of " $2 ", " $4 " free"}')"
|
|
42
|
+
if have timedatectl; then
|
|
43
|
+
item "clock: $(timedatectl show -p NTPSynchronized --value 2>/dev/null | sed 's/^yes$/synchronized with a time server/; s/^no$/NOT synchronized - timestamps may be wrong/')"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
section "Software versions"
|
|
47
|
+
for c in node npm sqlite3 zsh mosquitto mosquitto_passwd mosquitto_sub; do
|
|
48
|
+
if have $c; then
|
|
49
|
+
case $c in
|
|
50
|
+
node|npm) item "$c: $($c -v 2>&1 | head -1)" ;;
|
|
51
|
+
sqlite3) item "$c: $(sqlite3 --version 2>&1 | awk '{print $1}')" ;;
|
|
52
|
+
zsh) item "$c: $ZSH_VERSION" ;;
|
|
53
|
+
mosquitto) item "$c: $(mosquitto -h 2>&1 | head -1)" ;;
|
|
54
|
+
*) item "$c: present" ;;
|
|
55
|
+
esac
|
|
56
|
+
else
|
|
57
|
+
item "$c: NOT INSTALLED"
|
|
58
|
+
[[ $c == (node|npm|sqlite3) ]] && problem "$c is not installed - see INSTALLATION.md step A3"
|
|
59
|
+
fi
|
|
60
|
+
done
|
|
61
|
+
|
|
62
|
+
section "This installation"
|
|
63
|
+
for f in config.yaml config.d frugal-iot.db data ota node_modules; do
|
|
64
|
+
if [[ -e $f ]]; then item "$f: present"; else item "$f: MISSING"; fi
|
|
65
|
+
done
|
|
66
|
+
[[ ! -e frugal-iot.db ]] && problem "No frugal-iot.db here - either the wrong directory, or 'npx frugal-iot-init' has not been run"
|
|
67
|
+
if [[ -d node_modules/frugal-iot-server ]]; then
|
|
68
|
+
item "frugal-iot-server: $(node -e 'console.log(require("./node_modules/frugal-iot-server/package.json").version)' 2>/dev/null)"
|
|
69
|
+
item "frugal-iot-client: $(node -e 'console.log(require("./node_modules/frugal-iot-client/package.json").version)' 2>/dev/null)"
|
|
70
|
+
item "frugal-iot-logger: $(node -e 'console.log(require("./node_modules/frugal-iot-logger/package.json").version)' 2>/dev/null)"
|
|
71
|
+
item "commands: $(ls node_modules/.bin 2>/dev/null | grep frugal | tr '\n' ' ')"
|
|
72
|
+
else
|
|
73
|
+
item "frugal-iot-server is not installed in this directory"
|
|
74
|
+
fi
|
|
75
|
+
if [[ -f config.d/mqtt.yaml ]]; then item "broker configured as: $(grep -h '^broker:' config.d/mqtt.yaml 2>/dev/null)"; fi
|
|
76
|
+
if [[ -d config.d/organizations ]]; then
|
|
77
|
+
# Names only - these files contain the broker password.
|
|
78
|
+
# (N) makes a non-matching glob expand to nothing rather than erroring - and the list has to be
|
|
79
|
+
# tested before use, because "ls" with no arguments would list the current directory instead.
|
|
80
|
+
ORGS=(config.d/organizations/*.yaml(N:t:r))
|
|
81
|
+
if (( ${#ORGS} )); then
|
|
82
|
+
item "organizations: ${ORGS}"
|
|
83
|
+
else
|
|
84
|
+
item "organizations: none defined yet"
|
|
85
|
+
fi
|
|
86
|
+
fi
|
|
87
|
+
if [[ -f frugal-iot.db ]] && have sqlite3; then
|
|
88
|
+
item "accounts: $(sqlite3 frugal-iot.db 'SELECT group_concat(username, ", ") FROM users;' 2>/dev/null)"
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
section "Mosquitto configuration"
|
|
92
|
+
if [[ -f /etc/mosquitto/mosquitto.conf ]]; then
|
|
93
|
+
item "/etc/mosquitto/mosquitto.conf: $(fileinfo /etc/mosquitto/mosquitto.conf)"
|
|
94
|
+
else
|
|
95
|
+
item "/etc/mosquitto/mosquitto.conf: missing - is mosquitto installed?"
|
|
96
|
+
fi
|
|
97
|
+
item "files in /etc/mosquitto/conf.d:"
|
|
98
|
+
for f in /etc/mosquitto/conf.d/*(N); do
|
|
99
|
+
item " ${f}: $(fileinfo $f)"
|
|
100
|
+
grep -nE '^[[:space:]]*(listener|protocol|password_file|allow_anonymous)' $f 2>/dev/null | sed 's/^/ /'
|
|
101
|
+
done
|
|
102
|
+
[[ -z "$(print -r -- /etc/mosquitto/conf.d/*(N))" ]] && problem "Nothing in /etc/mosquitto/conf.d - the Frugal IoT config was never copied there (step A5)"
|
|
103
|
+
|
|
104
|
+
# Which password file does the running configuration actually name?
|
|
105
|
+
# mosquitto's include_dir only loads files ending in .conf, so look at exactly those, last one wins
|
|
106
|
+
CONFIGURED_PWFILE=$(grep -hE '^[[:space:]]*password_file[[:space:]]' /etc/mosquitto/mosquitto.conf /etc/mosquitto/conf.d/*.conf(N) 2>/dev/null | tail -1 | awk '{print $2}')
|
|
107
|
+
section "Mosquitto password file"
|
|
108
|
+
if [[ -n "$CONFIGURED_PWFILE" ]]; then
|
|
109
|
+
item "configuration names: $CONFIGURED_PWFILE"
|
|
110
|
+
item " that file: $(fileinfo $CONFIGURED_PWFILE)"
|
|
111
|
+
if [[ ! -e "$CONFIGURED_PWFILE" ]]; then
|
|
112
|
+
problem "Mosquitto is configured to use $CONFIGURED_PWFILE but that file does not exist - it will refuse to start"
|
|
113
|
+
else
|
|
114
|
+
# Account names only, never the hashes
|
|
115
|
+
item " accounts in it: $(cut -d: -f1 "$CONFIGURED_PWFILE" 2>/dev/null | tr '\n' ' ')"
|
|
116
|
+
[[ ! -s "$CONFIGURED_PWFILE" ]] && item " (the file is empty - no accounts yet)"
|
|
117
|
+
fi
|
|
118
|
+
else
|
|
119
|
+
item "no password_file line found in any mosquitto configuration"
|
|
120
|
+
problem "No password_file configured - the broker would allow anonymous access or reject everything"
|
|
121
|
+
fi
|
|
122
|
+
# The copy in this installation is what step A5 tells you to put into /etc - if the two disagree,
|
|
123
|
+
# either the copy never happened or the installed package is older than the instructions.
|
|
124
|
+
if [[ -f extras/mosquitto.conf ]]; then
|
|
125
|
+
item "this installation's extras/mosquitto.conf names: $(grep -hE '^[[:space:]]*password_file' extras/mosquitto.conf 2>/dev/null | awk '{print $2}')"
|
|
126
|
+
if [[ -n "$CONFIGURED_PWFILE" ]] && ! diff -q extras/mosquitto.conf /etc/mosquitto/conf.d/frugal-iot.conf >/dev/null 2>&1; then
|
|
127
|
+
problem "extras/mosquitto.conf here differs from /etc/mosquitto/conf.d/frugal-iot.conf - if you followed step A5 with an older installed package, copy it again after upgrading"
|
|
128
|
+
fi
|
|
129
|
+
fi
|
|
130
|
+
item "candidate locations, whether or not configured:"
|
|
131
|
+
for f in /var/lib/mosquitto/passwords /etc/mosquitto/mosquitto_passwords; do
|
|
132
|
+
item " ${f}: $(fileinfo $f)"
|
|
133
|
+
done
|
|
134
|
+
item "directories they live in:"
|
|
135
|
+
for d in /var/lib/mosquitto /etc/mosquitto; do
|
|
136
|
+
item " ${d}: $(fileinfo $d)"
|
|
137
|
+
done
|
|
138
|
+
|
|
139
|
+
section "Mosquitto service"
|
|
140
|
+
if have systemctl; then
|
|
141
|
+
item "enabled: $(systemctl is-enabled mosquitto 2>&1)"
|
|
142
|
+
item "active: $(systemctl is-active mosquitto 2>&1)"
|
|
143
|
+
[[ "$(systemctl is-active mosquitto 2>/dev/null)" != "active" ]] && problem "Mosquitto is not running - see its own log, quoted above under 'Mosquitto service'"
|
|
144
|
+
print -r -- " --- last 12 journal lines (systemd's view: usually only an exit code) ---"
|
|
145
|
+
journalctl -u mosquitto -n 12 --no-pager 2>&1 | sed 's/^/ /'
|
|
146
|
+
else
|
|
147
|
+
item "systemctl not available on this machine"
|
|
148
|
+
fi
|
|
149
|
+
# Debian's mosquitto logs to a file rather than the journal, so this is where the real reason is.
|
|
150
|
+
MOSQUITTO_LOG=$(grep -hE '^[[:space:]]*log_dest[[:space:]]+file' /etc/mosquitto/mosquitto.conf /etc/mosquitto/conf.d/*(N) 2>/dev/null | tail -1 | awk '{print $3}')
|
|
151
|
+
[[ -z "$MOSQUITTO_LOG" ]] && MOSQUITTO_LOG=/var/log/mosquitto/mosquitto.log
|
|
152
|
+
item "its own log file: ${MOSQUITTO_LOG} ($(fileinfo $MOSQUITTO_LOG))"
|
|
153
|
+
if [[ -r "$MOSQUITTO_LOG" ]]; then
|
|
154
|
+
print -r -- " --- last 12 lines of that log (this is where startup errors appear) ---"
|
|
155
|
+
tail -12 "$MOSQUITTO_LOG" 2>&1 | sed 's/^/ /'
|
|
156
|
+
elif [[ -e "$MOSQUITTO_LOG" ]]; then
|
|
157
|
+
# The log belongs to the mosquitto user, so reading it needs privilege. Try without prompting -
|
|
158
|
+
# on Raspberry Pi OS the first user has passwordless sudo, so this usually just works.
|
|
159
|
+
if have sudo && MOSQUITTO_LOG_TAIL=$(sudo -n tail -12 "$MOSQUITTO_LOG" 2>/dev/null); then
|
|
160
|
+
print -r -- " --- last 12 lines of that log, read with sudo (startup errors appear here) ---"
|
|
161
|
+
print -r -- "$MOSQUITTO_LOG_TAIL" | sed 's/^/ /'
|
|
162
|
+
else
|
|
163
|
+
item " it belongs to $(fileinfo $MOSQUITTO_LOG | awk '{print $1}') so it cannot be read as $(id -un)."
|
|
164
|
+
item " Re-run this whole script with sudo to include it: sudo zsh $0"
|
|
165
|
+
problem "Could not read ${MOSQUITTO_LOG}, which is where Mosquitto explains itself - re-run with sudo"
|
|
166
|
+
fi
|
|
167
|
+
fi
|
|
168
|
+
|
|
169
|
+
section "Listening ports"
|
|
170
|
+
PORTS=""
|
|
171
|
+
if have ss; then
|
|
172
|
+
PORTS=$(ss -tln 2>/dev/null | grep -E ':(1883|9012|8080)\b')
|
|
173
|
+
elif have netstat; then
|
|
174
|
+
PORTS=$(netstat -an 2>/dev/null | grep -E '[.:](1883|9012|8080) ')
|
|
175
|
+
else
|
|
176
|
+
item "neither ss nor netstat available"
|
|
177
|
+
fi
|
|
178
|
+
if [[ -n "$PORTS" ]]; then
|
|
179
|
+
print -r -- "$PORTS" | sed 's/^/ /'
|
|
180
|
+
else
|
|
181
|
+
item "none of 1883 (mqtt), 9012 (websockets), 8080 (web) are listening"
|
|
182
|
+
fi
|
|
183
|
+
|
|
184
|
+
section "Frugal IoT server service"
|
|
185
|
+
if have systemctl; then
|
|
186
|
+
if systemctl list-unit-files 2>/dev/null | grep -q '^frugaliot.service'; then
|
|
187
|
+
item "enabled: $(systemctl is-enabled frugaliot 2>&1)"
|
|
188
|
+
item "active: $(systemctl is-active frugaliot 2>&1)"
|
|
189
|
+
print -r -- " --- last 20 journal lines ---"
|
|
190
|
+
journalctl -u frugaliot -n 20 --no-pager 2>&1 | sed 's/^/ /'
|
|
191
|
+
else
|
|
192
|
+
item "no frugaliot service installed yet (step A8 not reached)"
|
|
193
|
+
fi
|
|
194
|
+
else
|
|
195
|
+
item "systemctl not available on this machine"
|
|
196
|
+
fi
|
|
197
|
+
|
|
198
|
+
section "Name resolution (step A2, A4)"
|
|
199
|
+
# The broker URL in config.d/mqtt.yaml has to resolve from every machine that uses it - this one,
|
|
200
|
+
# and whatever browser or node talks to it.
|
|
201
|
+
BROKER_URL=$(grep -h '^broker:' config.d/mqtt.yaml 2>/dev/null | awk '{print $2}')
|
|
202
|
+
BROKER_HOST=${${BROKER_URL#*://}%%:*}
|
|
203
|
+
if [[ -n "$BROKER_HOST" ]]; then
|
|
204
|
+
item "broker host: $BROKER_HOST"
|
|
205
|
+
if have getent && BROKER_IP=$(getent hosts "$BROKER_HOST" 2>/dev/null | head -1 | awk '{print $1}') && [[ -n "$BROKER_IP" ]]; then
|
|
206
|
+
item " resolves to $BROKER_IP from this machine"
|
|
207
|
+
elif have ping && ping -c1 -W2 "$BROKER_HOST" >/dev/null 2>&1; then
|
|
208
|
+
item " answers ping from this machine"
|
|
209
|
+
else
|
|
210
|
+
item " DOES NOT RESOLVE from this machine"
|
|
211
|
+
problem "The broker host '$BROKER_HOST' does not resolve here - the server's own logger cannot connect either"
|
|
212
|
+
fi
|
|
213
|
+
[[ "$BROKER_HOST" == *.local ]] && item " note: .local names do not resolve on most Android phones - use an IP address there"
|
|
214
|
+
fi
|
|
215
|
+
MYADDRS=$( (have ip && ip -4 -o addr show scope global | awk '{print $2"="$4}') 2>/dev/null | tr '\n' ' ')
|
|
216
|
+
item "this machine's addresses: ${MYADDRS:-(could not determine)}"
|
|
217
|
+
|
|
218
|
+
section "Broker authentication (steps A5, A6)"
|
|
219
|
+
if have mosquitto_sub; then
|
|
220
|
+
# A wrong password must be refused - that is the check in step A5
|
|
221
|
+
WRONGOUT=$(mosquitto_sub -h localhost -u nobody -P wrong -t '#' -W 2 2>&1)
|
|
222
|
+
if [[ "$WRONGOUT" == *"not authorised"* ]]; then
|
|
223
|
+
item "wrong password: correctly refused"
|
|
224
|
+
elif [[ "$WRONGOUT" == *"Connection refused"* || "$WRONGOUT" == *"Error"* ]]; then
|
|
225
|
+
item "wrong password: broker did not answer - $WRONGOUT"
|
|
226
|
+
problem "The broker is not answering on localhost:1883"
|
|
227
|
+
else
|
|
228
|
+
item "wrong password: ACCEPTED - the broker is not requiring credentials"
|
|
229
|
+
problem "A wrong password was not refused - check password_file is set in the mosquitto config"
|
|
230
|
+
fi
|
|
231
|
+
# Then each configured organization should be able to connect with its own credentials.
|
|
232
|
+
# The password is read from the config and used, never printed.
|
|
233
|
+
for f in config.d/organizations/*.yaml(N); do
|
|
234
|
+
ORG=${f:t:r}
|
|
235
|
+
ORG_PW=$(sed -n 's/^mqtt_password:[[:space:]]*//p' "$f" 2>/dev/null | head -1 | tr -d '"'"'"'')
|
|
236
|
+
if [[ -z "$ORG_PW" ]]; then
|
|
237
|
+
item "organization $ORG: no mqtt_password in $f"
|
|
238
|
+
continue
|
|
239
|
+
fi
|
|
240
|
+
ORGOUT=$(mosquitto_sub -h localhost -u "$ORG" -P "$ORG_PW" -t '#' -W 2 2>&1)
|
|
241
|
+
if [[ "$ORGOUT" == *"not authorised"* ]]; then
|
|
242
|
+
item "organization $ORG: REFUSED by the broker"
|
|
243
|
+
problem "Organization '$ORG' cannot log in to the broker - its password in $f does not match the broker's password file (step A6)"
|
|
244
|
+
elif [[ "$ORGOUT" == *"Connection refused"* ]]; then
|
|
245
|
+
item "organization $ORG: broker not answering"
|
|
246
|
+
else
|
|
247
|
+
item "organization $ORG: authenticates, and saw $(print -r -- "$ORGOUT" | grep -c . ) message(s) in a 2 second sample"
|
|
248
|
+
fi
|
|
249
|
+
done
|
|
250
|
+
else
|
|
251
|
+
item "mosquitto_sub not installed - cannot test broker logins (sudo apt install mosquitto-clients)"
|
|
252
|
+
fi
|
|
253
|
+
|
|
254
|
+
section "Web server (step A7)"
|
|
255
|
+
WEBPORT=$(grep -h '^port:' config.d/server.yaml 2>/dev/null | awk '{print $2}')
|
|
256
|
+
[[ -z "$WEBPORT" ]] && WEBPORT=8080
|
|
257
|
+
if have curl; then
|
|
258
|
+
HOMECODE=$(curl -s -o /dev/null -w '%{http_code}' -m 5 http://localhost:${WEBPORT}/ 2>/dev/null)
|
|
259
|
+
if [[ "$HOMECODE" == "000" ]]; then
|
|
260
|
+
item "nothing is answering on port ${WEBPORT} - the server is not running"
|
|
261
|
+
item " (expected if you have not reached step A7 yet; otherwise start it, or see the frugaliot service below)"
|
|
262
|
+
else
|
|
263
|
+
item "GET / -> ${HOMECODE} (expect 200)"
|
|
264
|
+
item "GET /config.json -> $(curl -s -o /dev/null -w '%{http_code}' -m 5 http://localhost:${WEBPORT}/config.json 2>/dev/null) (expect 401 when not logged in)"
|
|
265
|
+
[[ "$HOMECODE" != "200" ]] && problem "The web server answered ${HOMECODE} rather than 200 on port ${WEBPORT}"
|
|
266
|
+
fi
|
|
267
|
+
else
|
|
268
|
+
item "curl not installed - cannot test the web server"
|
|
269
|
+
fi
|
|
270
|
+
|
|
271
|
+
section "Logged data (step A9)"
|
|
272
|
+
if [[ -d data ]]; then
|
|
273
|
+
DATADIRS=(data/*(N/))
|
|
274
|
+
if (( ${#DATADIRS} )); then
|
|
275
|
+
for d in $DATADIRS; do
|
|
276
|
+
item "${d}: $(find $d -type f 2>/dev/null | wc -l | tr -d ' ') files, newest $(ls -t $d/**/*(N.om[1]) 2>/dev/null | head -1)"
|
|
277
|
+
done
|
|
278
|
+
else
|
|
279
|
+
item "data/ is empty - no readings logged yet, which is expected until a node reports (step A9)"
|
|
280
|
+
fi
|
|
281
|
+
fi
|
|
282
|
+
|
|
283
|
+
section "Summary"
|
|
284
|
+
if (( ${#PROBLEMS} == 0 )); then
|
|
285
|
+
item "No problems detected by these checks."
|
|
286
|
+
else
|
|
287
|
+
for p in $PROBLEMS; do item "PROBLEM: $p"; done
|
|
288
|
+
fi
|
|
289
|
+
print -r -- ""
|
package/scripts/init.zsh
CHANGED
|
@@ -28,11 +28,24 @@ if [[ ! -f "${PKG}/frugal-iot-createdb.sql" ]]; then
|
|
|
28
28
|
fi
|
|
29
29
|
|
|
30
30
|
# ---- 1. Configuration files, copied only if not already here ----
|
|
31
|
-
COPIED=()
|
|
31
|
+
COPIED=() # which files this run actually created, so existing ones are never rewritten below
|
|
32
|
+
DIFFER_TO=() # files kept that no longer match what this release ships, and where to compare them
|
|
33
|
+
DIFFER_FROM=()
|
|
34
|
+
# copy_if_missing <packaged file> <local file> [compare]
|
|
35
|
+
# "compare" asks for a warning when the file exists but differs from the one this release ships.
|
|
36
|
+
# Only pass it for files nobody is expected to edit - config.yaml, mqtt.yaml and server.yaml are
|
|
37
|
+
# this installation's own settings, so of course they differ, and warning about them every time
|
|
38
|
+
# would train you to ignore the warning that matters.
|
|
32
39
|
copy_if_missing() {
|
|
33
|
-
local from=$1 to=$2
|
|
40
|
+
local from=$1 to=$2 compare=${3:-}
|
|
34
41
|
if [[ -e "$to" ]]; then
|
|
35
|
-
|
|
42
|
+
if [[ "$compare" == compare && -e "$from" ]] && ! cmp -s "$from" "$to"; then
|
|
43
|
+
DIFFER_TO+=("$to")
|
|
44
|
+
DIFFER_FROM+=("$from")
|
|
45
|
+
echo " kept ${to} (DIFFERS from this release - see below)"
|
|
46
|
+
else
|
|
47
|
+
echo " kept ${to} (already present)"
|
|
48
|
+
fi
|
|
36
49
|
elif [[ -e "$from" ]]; then
|
|
37
50
|
mkdir -p "${to:h}"
|
|
38
51
|
cp "$from" "$to"
|
|
@@ -46,12 +59,14 @@ copy_if_missing "${PKG}/config.yaml" "./config.yaml"
|
|
|
46
59
|
for f in logger.yaml mqtt.yaml server.yaml; do
|
|
47
60
|
copy_if_missing "${PKG}/config.d/${f}" "./config.d/${f}"
|
|
48
61
|
done
|
|
62
|
+
# The schema describes the sensor types the software understands, so a release changing it matters
|
|
49
63
|
for f in "${PKG}"/config.d/schema/*.yaml(N); do
|
|
50
|
-
copy_if_missing "$f" "./config.d/schema/${f:t}"
|
|
64
|
+
copy_if_missing "$f" "./config.d/schema/${f:t}" compare
|
|
51
65
|
done
|
|
52
|
-
# Copied so that mosquitto.conf and frugaliot.service can be edited and installed from here
|
|
66
|
+
# Copied so that mosquitto.conf and frugaliot.service can be edited and installed from here. Worth
|
|
67
|
+
# comparing: these get copied on somewhere else (/etc/...), where an old version lingers unnoticed.
|
|
53
68
|
for f in "${PKG}"/extras/*(N); do
|
|
54
|
-
copy_if_missing "$f" "./extras/${f:t}"
|
|
69
|
+
copy_if_missing "$f" "./extras/${f:t}" compare
|
|
55
70
|
done
|
|
56
71
|
|
|
57
72
|
# ---- 1a. Point server.yaml at the web client this instance actually has ----
|
|
@@ -104,6 +119,18 @@ else
|
|
|
104
119
|
echo " created ${DB}"
|
|
105
120
|
fi
|
|
106
121
|
|
|
122
|
+
if (( ${#DIFFER_TO} )); then
|
|
123
|
+
echo
|
|
124
|
+
echo "These files were left as you have them, but this release ships a different version:"
|
|
125
|
+
for i in {1..${#DIFFER_TO}}; do
|
|
126
|
+
echo " ${DIFFER_TO[$i]}"
|
|
127
|
+
echo " compare with: diff ${DIFFER_TO[$i]} ${DIFFER_FROM[$i]}"
|
|
128
|
+
done
|
|
129
|
+
echo "Usually that just means you edited it, and there is nothing to do. But a release can also"
|
|
130
|
+
echo "change one of these files - and anything installed elsewhere from it, such as"
|
|
131
|
+
echo "/etc/mosquitto/conf.d/frugal-iot.conf, keeps the old content until you copy it again."
|
|
132
|
+
fi
|
|
133
|
+
|
|
107
134
|
echo
|
|
108
135
|
echo "Ready. Next:"
|
|
109
136
|
echo " npx frugal-iot-setpassword superuser <password> # so you can log in as the administrator"
|