Skip to content
GitHub

Install HotLoop IoT 4.18.10

HotLoop IoT first shipped in 4.18.10. The image is ghcr.io/hotloop-io/hotloop-iot:4.18.10, built for amd64, arm64 and arm/v7, and the chart is hotloop-iot in the same Helm repository as the rest of the lineup. Both pull with no login. There is no Docker or Compose path, on purpose, and a bare podman run from a shell that dies with the session isn’t an install.

Two ways in. A cluster gets Helm. A Pi, a NUC or a small industrial PC with no cluster gets Podman Quadlet. Either way the database is a choice you make once: PostgreSQL 16 (the default), or a SQLite file with no database container at all.

Terminal window
helm repo add hotloop https://hotloop.io/hotloop
helm repo update
helm install hotloop hotloop/hotloop-iot --version 4.18.10 \
--namespace hotloop --create-namespace

Call the release hotloop whichever product it is. The release name is what the database’s volume, the Secrets and the Service are named after, and keeping it is what lets a site move up a product later.

That deploys IoT plus PostgreSQL 16.15 beside it on a 10 Gi volume, keeping 30 days of history. The chart generates the database password, the MCP token and an admin password into the release Secret, and reuses them on every upgrade, so a helm upgrade never rotates a password out from under you.

For a SQLite file and no database pod, ask for it when you install:

Terminal window
helm install hotloop hotloop/hotloop-iot --version 4.18.10 \
--namespace hotloop --create-namespace \
--set database.type=sqlite

SQLite is one writer on one local disk, so it is one replica, and the volume goes on local storage, never NFS. A site with years of history at one second wants PostgreSQL. The IoT chart takes postgresql or sqlite and nothing else: asking it for timescaledb fails the install. Moving between the two later is a backup and a restore, not a values change, because the new database starts empty. Backups has the restore.

The chart also generates <release>-secret-key (hotloop-secret-key with the commands above). It holds the key that encrypts every password HotLoop keeps for a device. The key is never in the database and never in a backup, so it is part of your data: a backup restores credentials only where that key is mounted. The chart keeps the Secret through a helm uninstall. Back it up somewhere other than next to your backups, because a key sitting beside the backups it protects protects nothing. Install the Gateway has the whole story, and it is the same key.

The user is admin. The install notes print the command that reads the password back:

Terminal window
kubectl -n hotloop get secret hotloop \
-o jsonpath='{.data.admin-password}' | base64 -d
kubectl -n hotloop port-forward svc/hotloop 8080:8080

Open http://localhost:8080. That admin account is a bootstrap, not a reset switch: once it exists, changing auth.admin.password and upgrading does nothing, so a password somebody set in the UI never gets clobbered by the next deploy.

Now add a device, discover its tags, and try to write to one. You can’t. Writes ship off, every tag is read-only until somebody arms it, and safety.allowWrites stays false until you decide this deployment gets to move machinery. The write gate has every check, in order.

Put these in a values file and install with -f.

Value Default Why you’d touch it
plant.name, plant.siteId HotLoop, empty What the plant is called on screens, in pages, and to other sites.
plant.latitude, plant.longitude empty Where the plant is, in decimal degrees. The sun is worked out from them on the box. Both or neither.
global.timezone America/New_York Schedules and shift changes run on it. It has to be a real zone name, because one that won’t load stops startup.
publicUrl empty The address a phone on the plant Wi-Fi reaches HotLoop at. Without it, notifications have no link back and ntfy gets no Acknowledge button.
safety.allowWrites false The master switch. Off, nothing writes to a machine, from anywhere.
safety.allowMcpWrites false Lets agents write through the same gate. It narrows the master switch and never goes around it.
historian.retentionDays 30 Days of history kept. The Gateway and Edge keep 90, because a Pi’s disk is small.
logbook.retentionDays 90 Days of entity state changes the logbook keeps.
database.postgresql.persistence.size 10Gi Your history lives here. Size it for your tag count and the retention above.
secretKey.existingSecret empty A Secret you manage holding the device password key. Set it if you render the chart with helm template, which can’t generate a stable key.
opcuaClient.persistence.enabled false Turn it on before you add a secured OPC UA device, so the client’s certificates survive a restart.
ingress off Only if you want it reachable from outside the cluster by a name.

For a box that isn’t in a cluster. Quadlet turns the containers into real systemd services: they start on boot with nobody logged in, restart when they die, and log to the journal. It needs Podman 5.0 or newer. Raspberry Pi OS Bookworm ships 4.3, which has no Quadlet at all, so a Pi wants Trixie or newer. Put the database volume on a USB SSD and not the SD card, because history writes all day and SD cards die of exactly that.

The units are in the iot/ directory of the HotLoop repository, under deploy/quadlet/. Podman Quadlet explains what each file is. The short version for PostgreSQL:

Terminal window
sudo cp hotloop.network hotloop.container hotloop-db.container \
hotloop-db.volume hotloop-data.volume /etc/containers/systemd/
openssl rand -hex 24 | sudo podman secret create hotloop-db-password -
sudo podman run --rm ghcr.io/hotloop-io/hotloop-iot:stable gen-secret-key \
| sudo podman secret create hotloop-secret-key -
printf '%s' 'a long admin password' \
| sudo podman run --rm -i ghcr.io/hotloop-io/hotloop-iot:stable hash-password \
| sudo podman secret create hotloop-admin-password-hash -
sudo systemctl daemon-reload
sudo systemctl start hotloop.service
sudo systemctl status hotloop.service hotloop-db.service
curl -s localhost:8080/api/status

Then open http://<this box>:8080 and log in as admin. The three secrets are the database password, the key that encrypts device passwords (lose it and every one has to be typed in again, so keep a copy somewhere that isn’t this box), and the bcrypt hash of the admin’s password, never the password itself.

For a SQLite file instead, use the files in iot/sqlite/ and nothing from the directory above. They have the same unit names, so it is one set or the other, never both. It is two files, hotloop.container and hotloop-data.volume, and two secrets, hotloop-secret-key and hotloop-admin-password-hash. The database is /data/hotloop.db on the hotloop-data volume.

The unit follows :stable, and Podman moves it once the timer is on:

Terminal window
sudo systemctl enable --now podman-auto-update.timer

A new image migrates the database on its way up, and if it can’t start, Podman puts the previous one back. The database container does not follow :stable, on purpose. The :stable auto-update says what an update does, what a rollback cannot undo, and how to hold updates until you say. Take a backup before you turn updates on.

IoT to Edge to the Gateway keeps every device, tag, entity, automation, alarm, user and hour of history, and the lineup has the how. The Quadlet move has been run on one set of volumes. The same move as a helm upgrade on a real cluster, and going back down a product, have not been run, so don’t take a box you care about down a product.

IoT’s SQLite Quadlet unit was run under systemd, rootless, from the committed files. The PostgreSQL unit, and the Edge and Gateway units, are generated from the Gateway’s, which was run for real, and compared to it. A real Pi under systemd as root, and the 32-bit arm images on real hardware, haven’t been done yet. This page will say so when they are.

HotLoop IoT is under HotLoop’s Business Source License 1.1. Home and lab use is free, and business use is free through EmberNET. See the License page and Using HotLoop in a business.