Synchronization
Simple IoT provides for synchronized upstream connections via NATS or NATS over WebSocket.
To create an upstream sync, add a sync node to the root node on the downstream
instance. If your upstream server has a name of myserver.com, then you can use
the following connections URIs:
nats://myserver.com:4222(4222 is the default NATS port)ws://myserver.com(WebSocket unencrypted connection)wss://myserver.com(WebSocket encrypted connection)
IP addresses can also be used for the server name.
Auth token is optional and needs to be configured in an environment variable for the upstream server. If your upstream is on the public internet, you should use an auth token, or better, a device credential, which limits each device to its own data and can be revoked on its own. If both devices are on an internal network, then you may not need either and you can connect without any authentication.
Typically, wss are simplest for servers that are fronted by a web server like
Caddy that has TLS certs. For internal connections, nats or ws connections
are typically used.
Occasionally, you might also have edge devices on networks where NATS outgoing
connections on port 4222 are blocked. In this case, it’s handy to be able to use
the wss connection, which just uses standard HTTP(S) ports.
How synchronization behaves
Synchronization works by replicating the JetStream streams that store each instance’s data — see the synchronization reference for how this works. The behavior you will observe:
- First connect: the device announces itself and appears under the upstream root node; its full tree (structure, configuration, and history) then arrives through replication. Configuration written on the upstream for a device that has not connected yet is delivered on first connect.
- Offline changes catch up. Changes made on either side while the connection is down are delivered when it comes back — replication resumes exactly where it left off, and only missed data is sent. See Queuing while offline below.
- Both sides can edit. Configuration can be changed on either instance; the newest change wins everywhere.
- Deleting a device on the upstream detaches it. The device keeps running standalone and does not add itself back; undelete the device node on the upstream to resume synchronization.
Queuing while offline
An edge instance does not need its upstream to keep working. It writes every point to its own local store first, and the sync client replicates that store upstream. When the connection drops, the instance keeps collecting data, running rules, and accepting local configuration changes, all of which queue on disk.
On reconnect:
- The backlog is sent in order, with the original timestamps, so history upstream has no gap.
- Only the missed messages are sent. Replication resumes at the position it reached before the outage, which keeps the recovery cheap on a metered or low bandwidth link.
- Clients that act on current values (rules, protocol clients, the UI) see one update per changed value once the backlog drains rather than a replay of every intermediate reading, so a device coming back online does not re-trigger rules on stale data.
- History consumers still receive every point. A Db client feeding a time-series database reads the stream with its own durable consumer, so the backlog reaches the database as well.
Configuration written upstream while a device is offline, or before it has ever connected, waits and is delivered on the next connect.
How long a device can be offline and still catch up in full depends on how much history the store keeps. The default is 20,000 points per value, which is adjustable per instance. See Store for the setting, and the synchronization reference for how the queuing works.
Schema
The configuration of a sync node:
nodes:
- sync:
authToken: your-auth-token
description: Cloud
disabled: 0
uri: wss://myserver.com
uri is the upstream connection, written as one of the forms described above.
authToken matches SIOT_AUTH_TOKEN on the upstream server. Leave it out to
connect with the instance’s device key instead; the
client then writes the public key on the node as pubKey, which is why an
export of a running node carries one.
A sync node belongs on the root node of the downstream instance, so a file that
carries one leaves parent out and it attaches to the device node this instance
runs as.
An export carries authToken as it was entered, so treat a file that contains
sync nodes the way you would treat the token itself.
The count of synchronizations is a point the client maintains, so an export of a running node carries it as well.
Device credentials
Every instance has a device key, generated the first time it starts and kept in
device.nkey under SIOT_DATA. The key is the instance’s identity when it
connects to an upstream: a sync node with no authToken signs the upstream’s
connection challenge with it, so the secret never leaves the device. The public
half is shown on the sync node as pubKey, and siot key show prints it.
An upstream accepts a device key when a deviceCred node under the device’s
node carries the matching pubKey. The credential limits the connection to that
one device: it can push its own data and pull the configuration written for it,
and nothing else. A device cannot publish as another device or read another
device’s configuration, and the upstream holds only public keys, so an export or
a copy of its store gives away nothing that could impersonate a device. The
security reference lists exactly what a credential
allows.
Revoking access is one action: disable the credential (or delete it, or delete
the device node) and the upstream closes the device’s connection and refuses it
from then on. Nothing else in the fleet is affected. The device keeps running on
its own, shows credential refused by upstream on its sync node, and tries
again every minute, so re-enabling the credential brings it back with everything
it queued while it was out.
The upstream records lastConnect and connected on each credential, which is
how to tell whether a device has connected. siot cred list shows every
credential with its device and state; siot cred disable ID,
siot cred enable ID, and siot cred rm ID change one, and
siot cred add -device ID -pubKey KEY enrolls a key by hand for a device node
that already exists. All of the siot cred commands take the usual
-natsServer and -token options, so they work against a remote upstream.
There are two ways to get a device connected. Pick one per fleet.
1. SIOT_AUTH_TOKEN
The simplest setup is one shared token: set SIOT_AUTH_TOKEN on the upstream
and put the same value in authToken on every device’s sync node. Nothing has
to be created per device. The trade-off is that the token grants full access to
the upstream, so every device can read and write everything, and locking one
device out means changing the token everywhere. This suits a handful of devices
on a private network. For a fleet on the public internet, use enrollment.
2. Devices that enroll themselves
Every instance generates its own key on first start, and an enrollment token lets it ask the upstream for a credential for that key. Nothing is copied by hand in either direction, and the device’s node appears on the upstream on its own:
-
On the upstream, set
SIOT_AUTH_TOKENandSIOT_DEVICE_AUTH=required(or start it withsiot serve -deviceAuth required). The upstream then accepts the shared token only from its own host, where its own client and thesiotcommands use it, and every remote connection needs a credential. Enrollment does not use the shared token, so nothing below depends on it. See configuration. -
On the upstream, add an Enrollment token node under the root and press Generate token, or run
siot cred token -description fleet. The token is shown once; only its hash is stored. Auto approve (-autoApprove) skips the approval step, and an expiry (-expires 720h) limits how long the token works. -
Put the token on each device’s sync node as
enrollToken, with noauthToken. In an image that is one line in the provisioning file:nodes: - sync: description: Cloud uri: wss://myserver.com enrollToken: ETXXXX... -
When the upstream refuses the device’s key, the device connects with the token, which allows exactly one thing, and asks for a credential for its key. The upstream creates the device node if it is new and a credential under it marked pending approval; the device’s sync node says
enrollment pending approval on upstreamand keeps trying every minute. -
Approve the credential: uncheck Pending on it, or run
siot cred approve ID(siot cred listshows pending ones). The device connects on its next try.
A fleet that already syncs with the shared token moves over the other way round:
enroll every device first, then set SIOT_DEVICE_AUTH=required, since a device
still carrying an authToken is refused from then on.
Revoking the enrollment token, by disabling or deleting its node, stops new enrollments and does not affect devices already enrolled. A device that enrolls again with a different key gets a second, pending credential; the approved one is never replaced without an operator.
Videos
There are also several videos that demonstrate upstream connections: