frugal-iot-server 0.3.5 → 0.3.6

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 CHANGED
@@ -5,40 +5,61 @@ an MQTT broker (Mosquitto), the Frugal IoT server, the logger that writes sensor
5
5
  and the web UI. Once installed, the Pi works **offline** — your sensor nodes talk to the Pi,
6
6
  and you view the data from a phone or laptop on the same Wi-Fi. No internet needed after setup.
7
7
 
8
- Two hardware paths are covered:
9
-
10
- * [Raspberry Pi 4](#part-a--raspberry-pi-4) — the recommended, tested path.
11
- * [Raspberry Pi Zero W](#part-b--raspberry-pi-zero-w) — smaller and cheaper, **not yet tested**, see the open questions in that section.
12
-
13
8
  If you already have a working Linux server (not a Pi), you do not need this document —
14
9
  see [README.md](https://github.com/mitra42/frugal-iot-server/blob/main/README.md) instead.
15
10
 
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.
11
+ **Read the steps in order.** Anything specific to one board is marked in the step it belongs to,
12
+ like this:
13
+
14
+ > **Pi Zero W:** extra detail that only applies to a Zero W. Skip these on a Pi 4.
15
+
16
+ > **Status:** followed on hardware — a Raspberry Pi 4 through step 9, and a Pi Zero W through step 8
17
+ > (see [Tested on](#tested-on)) — and corrected from what those runs found. HTTPS/OTA in step 10 is
18
+ > not written yet. Remaining uncertainties are under [Open questions](#open-questions) — please add
19
+ > your findings there as you go.
20
20
 
21
21
  **Already have a Frugal IoT server running and just want a newer version?** Skip everything below
22
22
  and go to [Upgrading](#upgrading).
23
23
 
24
+ **If a step does not do what it says here**, once you reach step 4 you can run
25
+ `npx --no frugal-iot-diagnostic` from your install directory — it inspects the whole installation and
26
+ reports what looks wrong. See [When something does not work](#when-something-does-not-work).
27
+
24
28
  ---
25
29
 
26
- ## Part A — Raspberry Pi 4
30
+ ## Which Raspberry Pi
27
31
 
28
- ### A0. What you need before you start
32
+ Any of these work. What differs is only how long the install takes, and how much of a fight it is:
33
+
34
+ | Board | Install | Notes |
35
+ | --- | --- | --- |
36
+ | **Pi 4** (or Pi 5, Pi 3) | About half an hour | The straightforward path. 64-bit, everything installs as ready-made binaries. |
37
+ | **Pi Zero 2 W** | Expect longer | 64-bit, so it should avoid the long compile below, but shares the Zero's 512 MB. Not yet tested. |
38
+ | **Pi Zero W** (the original) | Allow an afternoon | 32-bit ARMv6. One dependency has to be compiled — about 40 minutes — and 512 MB of RAM is not enough without adding swap. Tested and works. |
39
+
40
+ A Zero W runs the server perfectly well once installed. It is *installing* that is slow, because a
41
+ single 1 GHz core has to unpack several hundred packages and compile one of them.
42
+
43
+ ### 0. What you need before you start
29
44
 
30
45
  **Hardware**
31
46
 
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.)
47
+ * A Raspberry Pi, per the table above. Any RAM size — a running server uses around 250 MB.
48
+ (Measured on a 4 GB Pi 4: 231 MB in use with the server running.)
34
49
  * A microSD card. 8 GB is enough for the system and software — a working install occupies about
35
50
  5 GB — but sensor data accumulates on this card for as long as the server runs, so 16 GB or larger
36
51
  is the safer choice. Class 10 / A1 or better.
37
- * The official Raspberry Pi USB-C power supply (5 V / 3 A). Phone chargers frequently cause
38
- random reboots and corrupted SD cards — this is the single most common cause of "it doesn't work".
52
+ * The official Raspberry Pi power supply for your board USB-C on a Pi 4, micro-USB on a Zero.
53
+ Phone chargers frequently cause random reboots and corrupted SD cards — this is the single most
54
+ common cause of "it doesn't work".
39
55
  * A way to write the SD card from your laptop: a built-in SD slot or a USB card reader.
40
56
  * Note that you will often need an adapter from the SD format the Pi uses to the SD format of most laptop readers.
41
57
 
58
+ > **Pi Zero W:** its Wi-Fi is 2.4 GHz only, so it cannot see a 5 GHz-only network. It has no
59
+ > Ethernet socket either, so the cable trick in step 2 is unavailable — have the micro-HDMI cable
60
+ > and a keyboard to hand instead. Note micro-USB for power and a micro-USB OTG adapter for the
61
+ > keyboard: different cables from a Pi 4.
62
+
42
63
  **Software and information**
43
64
 
44
65
  * A laptop or desktop (Mac, Windows or Linux) with [Raspberry Pi Imager](https://www.raspberrypi.com/software/) installed.
@@ -56,18 +77,20 @@ and go to [Upgrading](#upgrading).
56
77
  **Have these to hand in case the headless setup does not come up**
57
78
 
58
79
  * 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).
80
+ the Wi-Fi, and you can then sort the Wi-Fi out over SSH (step 2).
60
81
  * A micro-HDMI to HDMI cable plus a monitor, and a USB keyboard — for when the Pi is nowhere near the
61
82
  router, or you want to see boot messages.
62
83
 
63
- ### A1. Write the operating system to the SD card
84
+ ### 1. Write the operating system to the SD card
64
85
 
65
- We use **Raspberry Pi OS Lite (64-bit)** — the version with no desktop. The Pi is a server;
86
+ We use **Raspberry Pi OS Lite** — the version with no desktop. The Pi is a server;
66
87
  a desktop would only consume memory and SD card space.
67
88
 
68
89
  1. Insert the SD card into your laptop and start Raspberry Pi Imager.
69
- 2. **Choose Device** → *Raspberry Pi 4*.
90
+ 2. **Choose Device** → your board.
70
91
  3. **Choose OS** → *Raspberry Pi OS (other)* → *Raspberry Pi OS Lite (64-bit)*.
92
+ * **On a Pi Zero W choose *Raspberry Pi OS Lite (32-bit)*** — the 64-bit images will not boot on
93
+ its ARMv6 processor. (A Pi Zero **2** W is 64-bit, so it takes the 64-bit image like a Pi 4.)
71
94
  4. **Choose Storage** → your SD card. Check the size shown matches your card — this erases it.
72
95
  5. Click **Next**. When asked *"Would you like to apply OS customisation settings?"*, choose
73
96
  **Edit Settings** — everything below depends on it.
@@ -81,7 +104,7 @@ a desktop would only consume memory and SD card space.
81
104
  produces a key that fails even though the password was right.
82
105
  * If the Pi does not join the network, a mistyped SSID or password is much the likeliest cause -
83
106
  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`.
107
+ looks the same later. Step 2 fixes it in a couple of minutes with `nmtui`.
85
108
  * A WPA3 network cannot use the key Imager derives at all, so on one of those expect to finish the
86
109
  Wi-Fi setup with `nmtui` regardless.
87
110
  10. Enable SSH, either choose *Use password authentication* or paste your public key if you already use SSH keys.
@@ -95,7 +118,7 @@ Eject the card, put it in the Pi, connect power.
95
118
  The first boot resizes the filesystem and reboots itself. Give it **two to three minutes** before
96
119
  expecting it to answer.
97
120
 
98
- ### A2. Log in over the network
121
+ ### 2. Log in over the network
99
122
 
100
123
  From your laptop's terminal:
101
124
 
@@ -105,6 +128,8 @@ ssh pi@frugaliot.local
105
128
 
106
129
  Say `yes` to the fingerprint question, then give the password you set in Imager.
107
130
 
131
+ **If that logged you in, go straight to step 3.** The rest of this step is for when it did not.
132
+
108
133
  **If `frugaliot.local` is not found**, the `.local` (mDNS) name is not reaching you. In order of ease:
109
134
 
110
135
  * Wait another minute and try again — the Pi may still be on its first boot.
@@ -114,7 +139,7 @@ Say `yes` to the fingerprint question, then give the password you set in Imager.
114
139
  up an address, and `ssh pi@frugaliot.local` then works over the cable. This is the least effort
115
140
  way in if the Pi is within reach of the router, and once you are logged in you can sort the Wi-Fi
116
141
  out over SSH with `nmtui` as below, no monitor or keyboard needed. Unplug the cable afterwards and
117
- check that Wi-Fi alone still gets you in.
142
+ check that Wi-Fi alone still gets you in. (Not an option on a Pi Zero — no Ethernet socket.)
118
143
  * Plug in the HDMI and keyboard, log in at the console, and run `ip addr` to read the IP address,
119
144
  and `sudo journalctl -b | grep -i wpa` to see why Wi-Fi failed. Use this when the Pi is nowhere
120
145
  near the router, or when you want to see boot messages.
@@ -150,7 +175,9 @@ the password — either way `nmtui` is the fix, and it is not worth more time th
150
175
  (`sudo nmcli device wifi list` shows each nearby network's SSID and whether it is WPA2 or WPA3;
151
176
  WPA3 cannot use a derived key at all.)
152
177
 
153
- Once logged in, bring the system up to date and reboot:
178
+ ### 3. Update the operating system and install the prerequisites
179
+
180
+ Logged in, bring the system up to date and reboot:
154
181
 
155
182
  ```
156
183
  sudo apt update
@@ -174,49 +201,138 @@ which you may not have the password for.
174
201
  address does not change. Sensor nodes and phones then have something stable to talk to even where
175
202
  `.local` names do not work.
176
203
 
177
- ### A3. Install the prerequisites
204
+ Now the packages the server needs.
205
+
206
+ **On a Pi 4** (or any 64-bit board):
178
207
 
179
208
  ```
180
209
  sudo apt install -y nodejs npm sqlite3 zsh
181
210
  node -v
182
211
  ```
183
212
 
213
+ **On a Pi Zero W**, three more packages, because a 32-bit machine has to compile part of the server
214
+ in step 4 and a Lite image has no compiler:
215
+
216
+ ```
217
+ sudo apt install -y nodejs npm sqlite3 zsh build-essential python3-dev python3-setuptools
218
+ node -v
219
+ ```
220
+
221
+ What each is for:
222
+
184
223
  * `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
224
+ provides 20.19.2, which is fine, on 32-bit as well as 64-bit. Older images shipped Node 18, also
225
+ fine. If `node -v` reports anything below 18, install a current version from
187
226
  [NodeSource](https://github.com/nodesource/distributions) instead.
188
227
  * `npm` — installs the server; it is a separate package from `nodejs` on Debian.
189
228
  * `sqlite3` — the database the server keeps its accounts in.
190
- * `zsh` — the setup commands in step A6 are zsh scripts.
191
-
192
- ### A4. Install the Frugal IoT server
229
+ * `zsh` — the setup commands in step 6 are zsh scripts.
230
+ * `build-essential`, `python3-dev`, `python3-setuptools` — only needed where something has to be
231
+ compiled. `python3-setuptools` is the non-obvious one: the build uses node-gyp 8, which imports
232
+ Python's `distutils`, removed in Python 3.12, and setuptools puts an importable `distutils` back.
233
+ Without it step 4 ends in `ModuleNotFoundError: No module named 'distutils'`.
234
+
235
+ Now two settings that make the SD card last longer. A card wears out from being written to, and
236
+ these are the two places the system writes constantly without being asked to:
237
+
238
+ ```
239
+ # Cap the systemd journal, which by default is allowed to grow to a tenth of the card
240
+ sudo mkdir -p /etc/systemd/journald.conf.d
241
+ printf '[Journal]\nSystemMaxUse=16M\nSystemMaxFileSize=4M\n' | sudo tee /etc/systemd/journald.conf.d/frugal-iot.conf
242
+ sudo systemctl restart systemd-journald
243
+
244
+ # Swap out to the card only when there is genuinely no alternative
245
+ echo 'vm.swappiness=1' | sudo tee /etc/sysctl.d/99-frugal-iot-swappiness.conf
246
+ sudo sysctl --system | grep swappiness
247
+ ```
248
+
249
+ The `grep` should print `vm.swappiness = 1`. Both survive a reboot. There is more about what wears a
250
+ card out, and how to see whether yours is being written to hard, under
251
+ [Wear and tear on the SD card](#wear-and-tear-on-the-sd-card) — but nothing else there needs doing
252
+ during the install.
253
+
254
+ > **Pi Zero W: add swap before going on.** 512 MB is not enough to unpack what step 4 downloads, and
255
+ > running out does not fail cleanly — the board stops answering SSH and ping, and has to have its
256
+ > power pulled. Raspberry Pi OS enables zram, which is **not** sufficient here, because compressed
257
+ > RAM does not help when the working set is genuinely large. Add a real swap file (`dphys-swapfile`
258
+ > is not on the Trixie Lite image, so make it directly):
259
+ >
260
+ > ```
261
+ > sudo fallocate -l 2G /swapfile
262
+ > sudo chmod 600 /swapfile
263
+ > sudo mkswap /swapfile
264
+ > sudo swapon /swapfile
265
+ > free -h # should show 2.0Gi of swap
266
+ > echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
267
+ > ```
268
+ >
269
+ > **No reboot needed** — `swapon` takes effect at once, which the `free -h` line confirms. The
270
+ > `/etc/fstab` entry only matters later, so that the swap comes back after a reboot rather than
271
+ > having to be turned on by hand. Go straight on to step 4.
272
+ >
273
+ > Swapping to an SD card is slow, and wears it. That is the trade: the install takes longer, but it
274
+ > finishes instead of hanging. The `vm.swappiness=1` set above keeps the swap file available for
275
+ > emergencies like this one without it being used routinely afterwards.
276
+
277
+ ### 4. Install the Frugal IoT server
193
278
 
194
279
  The server is an npm package. Make a directory for this server to live in and install it there —
195
280
  that directory will hold your configuration, your data, and your database, while npm looks after
196
281
  the software itself underneath it in `node_modules`.
197
282
 
283
+ **On a Pi 4** (or any 64-bit board) — a few minutes, everything arrives as ready-made binaries and
284
+ nothing is compiled:
285
+
198
286
  ```
199
287
  mkdir ~/frugal-iot
200
288
  cd ~/frugal-iot
201
289
  npm install frugal-iot-server
202
290
  ```
203
291
 
204
- That pulls in the web UI (`frugal-iot-client`) and the logger (`frugal-iot-logger`) as well.
205
- Expect a few minutes on a Pi. Then set the directory up:
292
+ **On a Pi Zero W** — the extra flags make npm do one thing at a time, which lowers the peak memory
293
+ as well as being kinder to the SD card. This is the slow part; leave it running:
206
294
 
207
295
  ```
208
- npx frugal-iot-init
296
+ mkdir ~/frugal-iot
297
+ cd ~/frugal-iot
298
+ npm install --maxsockets 1 --no-audit --no-fund frugal-iot-server
299
+ ```
300
+
301
+ That pulls in the web UI (`frugal-iot-client`) and the logger (`frugal-iot-logger`) as well.
302
+
303
+ > **Pi Zero W: check that the compiled part actually built.** npm can report success while leaving
304
+ > it out, and nothing else looks wrong when that happens — the server just refuses to start later.
305
+ >
306
+ > ```
307
+ > node -e "require('sqlite3'); console.log('sqlite3 native module loads OK')"
308
+ > ```
309
+ >
310
+ > If that throws `Could not locate the bindings file`, build just that package:
311
+ >
312
+ > ```
313
+ > time npm rebuild sqlite3 --foreground-scripts
314
+ > ```
315
+ >
316
+ > **This takes around 40 minutes on a Zero W** — it is compiling SQLite itself on one slow core. As
317
+ > long as it is producing output it is working; `--foreground-scripts` is what lets you see that. If
318
+ > the board locks up, pull the power, boot it, and run the same command again: work already done is
319
+ > kept, so each attempt gets further.
320
+ >
321
+ > Most of what is being installed is not Frugal IoT. The logger depends on `firebase-admin`, which
322
+ > brings in the Google Cloud SDK — around 40 packages and 30 MB — and that is what the board
323
+ > struggles with. It is only used by organizations that configure a `firebase:` section.
324
+ > TODO make that dependency optional in frugal-iot-logger, so small boards can skip it.
325
+
326
+ Then set the directory up:
327
+
328
+ ```
329
+ npx --no frugal-iot-init
209
330
  ```
210
331
 
211
332
  This copies in the configuration files, creates the `data`, `ota` and `config.d/organizations`
212
333
  directories, and creates the database. It never overwrites anything already there, so it is also
213
334
  what you run after an upgrade to pick up newly added configuration.
214
335
 
215
- > On a 64-bit Raspberry Pi OS this installs ready-made binaries and compiles nothing. If it ever
216
- > does stop while building `sqlite3`, no binary was available for your platform, so install the
217
- > compiler toolchain and try again: `sudo apt install -y build-essential python3` then
218
- > `npm install frugal-iot-server`.
219
-
220
336
  Everything from here on is run from `~/frugal-iot`, and `npx` is how you run the server's commands
221
337
  without having to know where npm put them.
222
338
 
@@ -240,10 +356,10 @@ works on the Pi.
240
356
  > `frugaliot.local` is known to work from a laptop and from an iPhone. Android phones generally
241
357
  > cannot resolve `.local` names, so if you will view the dashboard on Android, put the Pi's IP
242
358
  > address here instead — `broker: ws://192.168.1.42:9012`, using the address `ping -c1
243
- > frugaliot.local` reported in step A2 — and reserve that address in your router if you can, so it
359
+ > frugaliot.local` reported in step 3 — and reserve that address in your router if you can, so it
244
360
  > does not change under you.
245
361
 
246
- ### A5. Install and configure the MQTT broker (Mosquitto)
362
+ ### 5. Install and configure the MQTT broker (Mosquitto)
247
363
 
248
364
  Sensor nodes publish their readings to an MQTT broker; the Frugal IoT logger subscribes to it and
249
365
  writes the readings to disk; the web UI subscribes to it to show live values. On an offline Pi,
@@ -265,6 +381,12 @@ sudo cp extras/mosquitto.conf /etc/mosquitto/conf.d/frugal-iot.conf
265
381
  (Everything in `/etc/mosquitto/conf.d/` is read in addition to the packaged
266
382
  `/etc/mosquitto/mosquitto.conf`, which keeps its own settings for logging and persistence.)
267
383
 
384
+ That file also turns off the broker's per-connection logging, because a node on a weak signal
385
+ reconnects constantly and each of those lines is a write to the SD card. Nothing is lost — the
386
+ readings themselves are recorded by the server, and everything else the broker says, including why
387
+ it refused to start, still goes to `/var/log/mosquitto/mosquitto.log`. If you are chasing a node
388
+ that keeps dropping off the network, comment out `connection_messages false` and restart the broker.
389
+
268
390
  That configuration names a password file, and Mosquitto will not start if the file is missing, so
269
391
  create an empty one. The accounts inside it get created for you in the next step:
270
392
 
@@ -279,7 +401,7 @@ instead of a `touch`, a `chown` and a `chmod`.
279
401
  > to whoever opened it, and the broker runs as the `mosquitto` user — so the file belongs to
280
402
  > `mosquitto`, and it lives under `/var/lib/mosquitto` (which that user owns) rather than
281
403
  > `/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
404
+ > alongside it, so it needs to write to that directory too, not just to the file. Step 6 runs it as
283
405
  > the right user for you.
284
406
 
285
407
  Start the broker and have it start at every boot:
@@ -309,19 +431,24 @@ mosquitto_sub -h localhost -u nobody -P wrong -t '#'
309
431
  ```
310
432
 
311
433
  `Connection Refused: not authorised` is the **success** case here — the broker answered and
312
- requires an account. There are no accounts yet; step A6 creates the first one, and there is a
434
+ requires an account. There are no accounts yet; step 6 creates the first one, and there is a
313
435
  fuller test at the end of it.
314
436
 
315
- ### A6. Create your accounts and your organization
437
+ > If Mosquitto did not start, `npx --no frugal-iot-diagnostic` will tell you why in one step — most often
438
+ > that the password file its configuration names does not exist. Note that `systemctl status` and
439
+ > the journal only show an exit code; the actual reason is in Mosquitto's own log, which the
440
+ > diagnostic reads for you. See [When something does not work](#when-something-does-not-work).
316
441
 
317
- The database was created by `npx frugal-iot-init` in step A4, holding two accounts. One is
442
+ ### 6. Create your accounts and your organization
443
+
444
+ The database was created by `npx --no frugal-iot-init` in step 4, holding two accounts. One is
318
445
  `everyone`, which nobody logs in as — it exists so that permissions granted to all logged-in users
319
446
  have somewhere to live. The other is `superuser`, this server's administrator, which is given admin
320
447
  rights over every organization you create. It starts with no password and cannot be logged into
321
448
  until you give it one:
322
449
 
323
450
  ```
324
- npx frugal-iot-setpassword superuser "<a-good-password>"
451
+ npx --no frugal-iot-setpassword superuser "<a-good-password>"
325
452
  ```
326
453
 
327
454
  Use the same command later if you ever need to reset a password — for `superuser` or for any
@@ -330,19 +457,18 @@ other account.
330
457
  Frugal IoT groups devices as **organization → project → device**. Create yours:
331
458
 
332
459
  ```
333
- npx frugal-iot-addorganization dev "My Farm" you@example.com +61123456789 "<broker-password>"
460
+ npx --no frugal-iot-addorganization myfarm "My Farm" you@example.com +61123456789 "<broker-password>"
334
461
  ```
335
462
 
336
463
  The arguments are: organization id, display name, your email, your phone (`+` and digits only),
337
- and a password. That one command writes `config.d/organizations/dev.yaml`, creates a login account
338
- named after the organization (`dev`), grants it admin rights, creates its OTA directory, **and adds
464
+ and a password. That one command writes `config.d/organizations/myfarm.yaml`, creates a login account
465
+ named after the organization (`myfarm`), grants it admin rights, creates its OTA directory, **and adds
339
466
  the organization's account to the broker's password file** — which is why the broker had to be
340
467
  installed first.
341
468
 
342
- * **Organization id `dev`** — must be 1–10 lower-case letters or digits. It becomes the first part
469
+ * **Organization id `myfarm`** — must be 1–10 lower-case letters or digits. It becomes the first part
343
470
  of every MQTT topic, so it must match what your sensor nodes are configured to publish to.
344
- `dev` is the default in the Frugal IoT node firmware, so it is the safe choice for a first install.
345
- * **The password** goes into `config.d/organizations/dev.yaml` as `mqtt_password` and into the
471
+ * **The password** goes into `config.d/organizations/myfarm.yaml` as `mqtt_password` and into the
346
472
  broker's password file, so it is a *machine* credential: the server's logger, your sensor nodes,
347
473
  and any browser showing this organization's dashboard all authenticate to the broker with it.
348
474
  Treat it as shared, not personal.
@@ -353,45 +479,47 @@ Tell Mosquitto to re-read the password file, so the new account works:
353
479
  sudo systemctl restart mosquitto
354
480
  ```
355
481
 
356
- **Give your login its own password.** That command set the `dev` *web login* password to the same
482
+ **Give your login its own password.** That command set the `myfarm` *web login* password to the same
357
483
  string as the broker password. They serve completely different purposes, so change the login one
358
484
  now to something only you know — the broker credential is unaffected, and nothing needs to be kept
359
485
  in step:
360
486
 
361
487
  ```
362
- npx frugal-iot-setpassword dev "<your-own-login-password>"
488
+ npx --no frugal-iot-setpassword myfarm "<your-own-login-password>"
363
489
  ```
364
490
 
365
491
  **Now the full broker test.** In your SSH session subscribe as the organization, using the broker
366
492
  password you chose above:
367
493
 
368
494
  ```
369
- mosquitto_sub -h localhost -u dev -P '<broker-password>' -t '#' -v
495
+ mosquitto_sub -h localhost -u myfarm -P '<broker-password>' -t '#' -v
370
496
  ```
371
497
 
372
498
  It should sit there silently — no error, no exit. Open a **second** SSH session to the Pi and
373
499
  publish something:
374
500
 
375
501
  ```
376
- mosquitto_pub -h localhost -u dev -P '<broker-password>' -t 'dev/test/hello' -m '42'
502
+ mosquitto_pub -h localhost -u myfarm -P '<broker-password>' -t 'myfarm/test/hello' -m '42'
377
503
  ```
378
504
 
379
- `dev/test/hello 42` appearing in the first window proves the account, the password file and the
505
+ `myfarm/test/hello 42` appearing in the first window proves the account, the password file and the
380
506
  port 1883 listener your sensor nodes use are all working. The WebSocket listener on 9012 gets
381
- exercised by the browser in step A7. Leave the subscriber running if you like — it is a useful
507
+ exercised by the browser in step 7. Leave the subscriber running if you like — it is a useful
382
508
  window onto what your nodes are doing. (`Ctrl-C` stops it.)
383
509
 
384
510
  If you get `Connection Refused: not authorised`, the password does not match the one in
385
- `config.d/organizations/dev.yaml`, or Mosquitto has not re-read the file since it changed.
511
+ `config.d/organizations/myfarm.yaml`, or Mosquitto has not re-read the file since it changed.
512
+ `npx --no frugal-iot-diagnostic` checks this for every organization you have, and reports which ones the
513
+ broker actually accepts.
386
514
 
387
- ### A7. Start the server by hand and check it
515
+ ### 7. Start the server by hand and check it
388
516
 
389
517
  From your install directory — `npx` looks for the server in the current directory's `node_modules`,
390
518
  so this only works there:
391
519
 
392
520
  ```
393
521
  cd ~/frugal-iot
394
- npx frugal-iot-server
522
+ npx --no frugal-iot-server
395
523
  ```
396
524
 
397
525
  > If npx answers with `Need to install the following packages: frugal-iot-server` and asks to
@@ -404,7 +532,7 @@ It lists each configuration file as it reads it, then:
404
532
  readYamlConfigFile ./config.yaml
405
533
  readYamlConfigDir ./config.d
406
534
  ... one line per configuration file ...
407
- Broker ws://frugaliot.local:9012 - organizations: dev
535
+ Broker ws://frugaliot.local:9012 - organizations: myfarm
408
536
  Doing OTA updates at /ota_update from /home/pi/frugal-iot/ota
409
537
  Serving /node_modules from ./node_modules
410
538
  User Database exists
@@ -416,16 +544,16 @@ Mounted API routes at /api
416
544
  Added API error handler
417
545
  Serving /data from ./data
418
546
  Server starting on port 8080
419
- mqtt dev connecting
420
- mqtt dev connect
421
- Subscribing topic dev/# 0
547
+ mqtt myfarm connecting
548
+ mqtt myfarm connect
549
+ Subscribing topic myfarm/# 0
422
550
  ```
423
551
 
424
552
  Check the `Broker` line names your own broker and your organization. The lines that matter most are
425
- the last three: `mqtt dev connect` means the server reached the broker and authenticated, and
426
- `Subscribing topic dev/#` means it is listening for your nodes. If instead you see repeated
427
- `mqtt dev close`, `offline`, or `Not authorized`, the broker URL or the password is wrong — recheck
428
- `config.d/mqtt.yaml` and that the password in `config.d/organizations/dev.yaml` matches the `dev`
553
+ the last three: `mqtt myfarm connect` means the server reached the broker and authenticated, and
554
+ `Subscribing topic myfarm/#` means it is listening for your nodes. If instead you see repeated
555
+ `mqtt myfarm close`, `offline`, or `Not authorized`, the broker URL or the password is wrong — recheck
556
+ `config.d/mqtt.yaml` and that the password in `config.d/organizations/myfarm.yaml` matches the `myfarm`
429
557
  broker account.
430
558
 
431
559
  Once nodes are reporting, each reading is logged as it arrives, so this output keeps scrolling.
@@ -442,7 +570,7 @@ http://frugaliot.local:8080
442
570
  > local networks"* the first time. **Allow it** — without that permission the browser cannot look up
443
571
  > `frugaliot.local`, nor reach the broker at that name, so the page and the live data both fail.
444
572
 
445
- You land on the Frugal IoT home page. Click **Dashboard**, and log in as username `dev` with the
573
+ You land on the Frugal IoT home page. Click **Dashboard**, and log in as username `myfarm` with the
446
574
  login password you set with `frugal-iot-setpassword` — not the broker password. (`superuser` and its
447
575
  password work too.)
448
576
 
@@ -454,12 +582,17 @@ actually resolve: a phone that cannot look up `.local` names needs the Pi's IP a
454
582
 
455
583
  Until a sensor node reports in there will be no data to look at, but the dashboard should load.
456
584
 
585
+ > Anything wrong here — the server not starting, `mqtt myfarm close` instead of `connect`, the page not
586
+ > loading, the MQTT status not reaching *connected* — is worth a `npx --no frugal-iot-diagnostic` in
587
+ > another terminal before digging in by hand. It tests the same chain from the Pi's side: broker
588
+ > logins, the three ports, and the web server.
589
+
457
590
  Stop the server with `Ctrl-C` before continuing.
458
591
 
459
- ### A8. Run the server as a service
592
+ ### 8. Run the server as a service
460
593
 
461
594
  So that it starts automatically at boot and restarts if it crashes. The file that
462
- `npx frugal-iot-init` put in `extras/` already describes this installation — user `pi`, installed
595
+ `npx --no frugal-iot-init` put in `extras/` already describes this installation — user `pi`, installed
463
596
  into `/home/pi/frugal-iot` — so it needs no editing:
464
597
 
465
598
  ```
@@ -478,7 +611,7 @@ systemctl status frugaliot
478
611
  > `sudo systemctl restart frugaliot`. `WorkingDirectory` is the important one: it is where the
479
612
  > server looks for its configuration and database.
480
613
 
481
- To watch its log output, which is where the `mqtt dev connect` and incoming-reading messages now go:
614
+ To watch its log output, which is where the `mqtt myfarm connect` and incoming-reading messages now go:
482
615
 
483
616
  ```
484
617
  journalctl -u frugaliot -f
@@ -487,7 +620,7 @@ journalctl -u frugaliot -f
487
620
  Reboot the Pi (`sudo reboot`), wait a couple of minutes, and check `http://frugaliot.local:8080`
488
621
  still answers. Your server is now installed.
489
622
 
490
- ### A9. Point your sensor nodes at the Pi
623
+ ### 9. Point your sensor nodes at the Pi
491
624
 
492
625
  Your ESP8266/ESP32 nodes are told which broker to use in their sketch — `main.cpp`, or the `.ino`
493
626
  file if you build in the Arduino IDE. Look for a line like:
@@ -499,29 +632,29 @@ frugal_iot.configure_mqtt("frugaliot.naturalinnovation.org", "dev", "public");
499
632
  and point it at your Pi instead:
500
633
 
501
634
  ```cpp
502
- frugal_iot.configure_mqtt("frugaliot.local", "dev", "<broker-password>");
635
+ frugal_iot.configure_mqtt("frugaliot.local", "myfarm", "<broker-password>");
503
636
  ```
504
637
 
505
638
  The three arguments are the broker's host, the organization, and that organization's broker
506
- password. The organization must be the one you created in step A6, because it is the first part of
639
+ password. The organization must be the one you created in step 6, because it is the first part of
507
640
  every topic the node publishes to, and the password is the *broker* password from that step — not
508
641
  the login password. Then rebuild and flash the node as usual.
509
642
 
510
643
  > If the node does not connect, try the Pi's IP address in place of `frugaliot.local`. Resolving
511
644
  > `.local` names needs mDNS support in the firmware, which is not something this guide has
512
- > confirmed; an IP address avoids the question entirely, which is why step A2 suggests reserving one
645
+ > confirmed; an IP address avoids the question entirely, which is why step 3 suggests reserving one
513
646
  > for the Pi in your router.
514
647
 
515
- You can confirm nodes are reporting without involving the UI, using the subscriber from step A6:
648
+ You can confirm nodes are reporting without involving the UI, using the subscriber from step 6:
516
649
 
517
650
  ```
518
- mosquitto_sub -h localhost -u dev -P '<broker-password>' -t '#' -v
651
+ mosquitto_sub -h localhost -u myfarm -P '<broker-password>' -t '#' -v
519
652
  ```
520
653
 
521
654
  Every reading from every node should scroll past. Seeing anything here also proves the broker's
522
655
  port 1883 is reachable from off the Pi, which is what the nodes need.
523
656
 
524
- ### A10. HTTPS and over-the-air firmware updates
657
+ ### 10. HTTPS and over-the-air firmware updates
525
658
 
526
659
  **To be written.** Everything above gives you a plain HTTP server on your local network, which is
527
660
  all an offline installation needs. HTTPS matters for two things:
@@ -563,6 +696,80 @@ the time of the phone or laptop viewing it, so it could hand that to the server,
563
696
  it whenever its own clock looks implausible (a date far in the past). Not accurate to the second,
564
697
  but close enough for sensor data.
565
698
 
699
+ ---
700
+ ### Wear and tear on the SD card
701
+
702
+ SD cards wear out from being written to, and a server recording sensor readings writes all the time.
703
+ We have not yet seen a card fail, but plan for one eventually: keep a copy of `config.d/` and
704
+ `frugal-iot.db` somewhere else, and treat the readings in `data/` as valuable but not irreplaceable.
705
+
706
+ A new installation is set up to write as little as it reasonably can, and the settings are worth
707
+ knowing about, because turning one of them back on for debugging and forgetting is easy to do:
708
+
709
+ | What | Where | Shipped as |
710
+ | ---- | ----- | ---------- |
711
+ | A line logged for every MQTT message received | `verbose:` in `config.d/logger.yaml` | `false` |
712
+ | Readings held in memory and written out periodically instead of one at a time | `flushseconds:` in `config.d/logger.yaml` | `300` (5 minutes) |
713
+ | A line logged for every web request | `morgan:` in `config.d/server.yaml` | `false` |
714
+ | Old readings compressed, and deleted if the disk fills | `housekeeping:` in `config.d/server.yaml` | compress after 2 days, never delete, keep 10% free |
715
+ | A line logged for every device connect and disconnect | `connection_messages` in `extras/mosquitto.conf` | `false` |
716
+ | A capped systemd journal, and swapping only as a last resort | `/etc/systemd/journald.conf.d/` and `/etc/sysctl.d/`, both set in step 3 | 16 MB journal, `vm.swappiness=1` |
717
+
718
+ Nothing in that table needs doing — step 3 and step 5 set all of it up. It is here because turning
719
+ one of them back on for debugging and then forgetting is easy to do.
720
+
721
+ `flushseconds` is the one with a cost attached: readings that have not been written out yet are only
722
+ in memory, so pulling the power loses up to five minutes of them. Stopping the server properly
723
+ (`sudo service frugaliot stop`, or a restart) writes them out first, and so does looking at a graph.
724
+
725
+ On a Pi 4 there should be very little swapping in any case — the server and Mosquitto together are a
726
+ small load for 1 GB or more. The Pi Zero W is the one to watch, since step 3 adds a 2 GB swap file to
727
+ get through the install. To see what is actually happening on your board:
728
+
729
+ ```
730
+ cd ~/frugal-iot
731
+ npx --no frugal-iot-diagnostic
732
+ ```
733
+
734
+ and read the **Wear on the SD card** section, which reports how much has been written since boot,
735
+ whether the machine is swapping, and whether each of the settings above is in force. Run it twice a
736
+ few days apart — the interesting number is the rate, not the total.
737
+
738
+ ## When something does not work
739
+
740
+ From your install directory:
741
+
742
+ ```
743
+ cd ~/frugal-iot
744
+ npx --no frugal-iot-diagnostic
745
+ ```
746
+
747
+ It only looks — it changes nothing — and it ends with a **Summary** of anything it recognises as
748
+ broken. It covers most of the checks scattered through this guide, in one pass:
749
+
750
+ * the machine, the OS, free memory and disk, and whether the clock is synchronized
751
+ * the versions of node, npm, sqlite3, mosquitto, and of the three Frugal IoT packages
752
+ * what this directory holds: configuration, database, accounts, organizations
753
+ * Mosquitto's configuration, **which password file it names and whether that file exists**, who owns
754
+ it, and the accounts in it
755
+ * whether Mosquitto and the `frugaliot` service are running, with the last lines of the journal
756
+ **and of Mosquitto's own log**, which is where its startup errors actually appear
757
+ * whether the broker refuses a wrong password, and accepts each organization's real one
758
+ * whether ports 1883, 9012 and 8080 are listening, and whether the web server answers
759
+ * whether the broker host in `config.d/mqtt.yaml` resolves from this machine
760
+ * how much data the logger has written
761
+
762
+ Two things to know:
763
+
764
+ * Mosquitto's log belongs to the `mosquitto` user. The script reads it with `sudo` where it can; if
765
+ it says it could not, run the whole thing as `sudo npx --no frugal-iot-diagnostic`.
766
+ * The output is safe to paste into a bug report. Passwords are deliberately not printed — the
767
+ password file is listed by account name only, and organizations by name.
768
+
769
+ If you have not reached step 4 yet, the command does not exist. Copy
770
+ [scripts/diagnostic.zsh](https://github.com/mitra42/frugal-iot-server/blob/main/scripts/diagnostic.zsh)
771
+ to the Pi and run `zsh diagnostic.zsh` instead.
772
+
566
773
  ---
567
774
 
568
775
  ## Upgrading
@@ -572,7 +779,7 @@ To move an existing server to a newer release, from the directory you installed
572
779
  ```
573
780
  cd ~/frugal-iot
574
781
  npm update frugal-iot-server
575
- npx frugal-iot-init
782
+ npx --no frugal-iot-init
576
783
  sudo systemctl restart frugaliot
577
784
  ```
578
785
 
@@ -603,68 +810,43 @@ sudo cp extras/mosquitto.conf /etc/mosquitto/conf.d/frugal-iot.conf && sudo syst
603
810
  sudo cp extras/frugaliot.service /etc/systemd/system/ && sudo systemctl daemon-reload && sudo systemctl restart frugaliot
604
811
  ```
605
812
 
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:
813
+ > It does not compare `config.yaml`, `config.d/mqtt.yaml`, `config.d/logger.yaml` or
814
+ > `config.d/server.yaml`, because those hold your own settings and would differ every time. If a
815
+ > release note mentions a new setting in one of them, compare it yourself:
609
816
  > `diff config.d/server.yaml node_modules/frugal-iot-server/config.d/server.yaml`
610
817
 
818
+ Because your own configuration is left alone, an upgraded server goes on behaving exactly as it did,
819
+ which for the settings under [Wear and tear on the SD card](#wear-and-tear-on-the-sd-card) means it
820
+ goes on writing as often as it did. A server that has been running since before those settings
821
+ existed keeps logging every message and every web request, and writing every reading as it arrives —
822
+ deliberately, since quietening somebody's server without being asked is not a thing an upgrade should
823
+ do. On a Pi, compare the two files above and copy across the settings you want.
824
+
611
825
  Your organizations, accounts, database and logged data are untouched by an upgrade — they live in
612
826
  this directory, not in `node_modules`.
613
827
 
614
828
  ---
615
829
 
616
- ## Part B — Raspberry Pi Zero W
617
-
618
- **Not yet tested — do not follow this section expecting it to work.** It records what is known
619
- and what has to be checked.
620
-
621
- First, work out which board you have, because they are very different:
622
-
623
- * **Raspberry Pi Zero 2 W** — 64-bit ARM (Cortex-A53), 512 MB RAM. Should follow **Part A**
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.
627
- * **Raspberry Pi Zero W** (the original) — 32-bit ARMv6 (BCM2835), 512 MB RAM. This is the
628
- awkward one: it needs the 32-bit Raspberry Pi OS, and **the official Node.js builds no longer
629
- support ARMv6**. Unofficial ARMv6 builds exist at
630
- [unofficial-builds.nodejs.org](https://unofficial-builds.nodejs.org/download/release/), but
631
- which versions are available, and whether one recent enough for this server exists, needs checking.
632
-
633
- Differences to expect on either Zero:
634
-
635
- * Wi-Fi is 2.4 GHz only on the original Zero W — it cannot see a 5 GHz-only network.
636
- * 512 MB of RAM is tight for `npm install`. Increasing the swap file is likely to be necessary.
637
- * Micro-USB power and micro-USB OTG, not USB-C and USB-A — different cables from the Pi 4.
638
- * No Ethernet socket, so the Ethernet fallback in Part A is unavailable. (The original Zero W can
639
- be reached as a USB gadget over the data port, which is an alternative worth documenting.)
640
- * Everything will be slow. `npm install` may take a long time, especially if `sqlite3` has to be
641
- compiled from source — which is likely on ARMv6, since no ready-made binaries are published.
642
-
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.
646
-
647
- ---
648
-
649
830
  ## Open questions
650
831
 
651
832
  Things this guide currently states with less confidence than the rest, to be resolved by
652
833
  following it on real hardware. Please correct them in place, and delete them from this list, as
653
834
  they get settled.
654
835
 
655
- **Part A, to check while installing**
836
+ **To check while installing**
656
837
 
657
- 1. **`.local` from an Android phone** (step A2, A4) - confirmed working from a laptop (with the
838
+ 1. **`.local` from an Android phone** (steps 3, 4) - confirmed working from a laptop (with the
658
839
  local-network permission granted on a Mac) and from an iPhone. Android is expected to fail, which
659
- is what the note in A4 assumes; worth confirming on a real Android phone.
840
+ is what the note in step 4 assumes; worth confirming on a real Android phone.
660
841
 
661
842
  **Needs information I do not have**
662
843
 
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.
844
+ 2. **Pi Zero 2 W** - the Zero W notes come from a real install; the Zero 2 W is untested. Being
845
+ 64-bit it should take the 64-bit image and avoid the `sqlite3` compile entirely, which is the slow
846
+ part, but it has the same 512 MB and so probably still needs the swap file in step 3.
665
847
  3. **Bridging to the shared server** — the local broker could optionally bridge to
666
848
  naturalinnovation.org so data also reaches the shared server. Not covered here; a later task.
667
- 4. **Organization naming** — this guide sets up exactly one organization named `dev`, because that
849
+ 4. **Organization naming** — this guide sets up exactly one organization named `myfarm`, because that
668
850
  is the node firmware's default. Is that the right default for a farm installation, or should the
669
851
  guide encourage a meaningful organization id (which then has to be set in the node firmware too)?
670
852
 
@@ -674,4 +856,5 @@ What the guide has actually been proven against — add a row for each run:
674
856
 
675
857
  | Date | Board | OS image | Node | Server | Result |
676
858
  | --- | --- | --- | --- | --- | --- |
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. |
859
+ | 2026-08-17 | Pi Zero W (original), 512 MB | Raspberry Pi OS Lite **32-bit**, Debian 13 (trixie), kernel 6.18.39+rpt-rpi-v6 | 20.19.2 and npm 9.2.0, both from `apt` — they run on ARMv6 | frugal-iot-server 0.3.5 from npm | Steps 1-8 completed. `sqlite3` had to be compiled: about 40 minutes, after `build-essential python3-dev python3-setuptools` (without setuptools it fails on `distutils`). A 2 GB swap file was required zram alone was not enough and the board locked up hard without it and `npm install` was run with `--maxsockets 1`. Step 9 (sensor nodes) not exercised on this board, but the broker publish/subscribe tests in step 6 passed. |
860
+ | 2026-08-16 | Pi 4 Model B, 4GB | Raspberryy 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.5 from npm | Steps 1-9 completed, plus upgrade over top. Dashboard reached over `frugaliot.local` from a Mac (Chrome, after allowing local network access) and from an iPhone, MQTT status *connected*. 10 (HTTPS/OTA) not exercised.