Version compatibility
Each repository carries its own semantic version and publishes its own GitHub Release; a
control-plane release names the daemon and UI versions it was tested against. Both wires
carry versions and hold the peer against a floor. A peer below the floor stays connected
and is flagged. Commands to an old daemon fail with daemon_unsupported. An old control
plane is logged and the daemon keeps reconnecting. A missing version is unknown and is
allowed. Mixed versions stay connected: that is how a managed upgrade reaches a daemon
that is not on the new build yet.
A platform upgrade (Admin → Updates, or an automatic run) does impose an order on self-hosted: this host's daemon, then the control plane, then the other servers, and the the servers step does not start until the first two are on target. That order is the upgrade run. It is not a version floor, and it does not drop a socket. See Upgrade and rollback.
Channels
canary, rc, and release are GitHub Releases on each package's own repository
(redirects, no API calls). Daemon trunk is the per-merge CDN drop at
dl.trbp.nl/channels/trunk/manifest.json. The control plane and the UI publish only
on canary, rc, and release — they have no CDN drop — so a control plane whose
channel is trunk has no control-plane package target. Admin → Updates leaves
that row's Upgrade disabled and still resolves the daemon from the CDN. See
Canary environment.
| Channel | What it points at | Daemon | Control plane and UI |
|---|---|---|---|
canary | The newest green build of trunk | releases/download/canary/manifest.json on turbopaneld | The same path on turbopanel and ui |
rc | The current release candidate — a rolling pre-release tag | releases/download/rc/manifest.json | The same path on each repository |
release | The latest promoted release | releases/latest/download/manifest.json | The same path on each repository |
trunk | Every merge to trunk — contributors only | dl.trbp.nl/channels/trunk/manifest.json | No package |
Promotion is a pointer move — the bytes that soaked on rc are the bytes release serves.
Tested pairings
| Control plane | Daemon | UI | Notes |
|---|---|---|---|
| 0.2.x | 0.2.x | 0.2.x | Current target; not yet released. |
| 0.1.6 | 0.1.6 | 0.1.5 | The UI had no 0.1.6 build; 0.1.5 is the newest UI. |
| 0.1.5 | 0.1.5 | 0.1.5 | |
| 0.1.4 | 0.1.4 | 0.1.4 | |
| 0.1.3 | 0.1.3 | 0.1.3 | |
| 0.1.2 | 0.1.2 | 0.1.2 | First release through the promote pipeline. |
| 0.1.0 | 0.1.0 | 0.1.0 | First release. Every 0.1.0 build understands managed.ingress.reconcile, the dockerNetworkAddressing[] deploy field and the GET /host/docker-networking pull. |
Version floors
| Side | Floor | What happens below it |
|---|---|---|
| Control plane → daemon | 0.1.0 | The daemon reports its version on hello. A supported daemon runs commands; an unsupported one keeps its connection but every command fails with daemon_unsupported until it updates. A daemon that reports no version passes as unknown. |
| Daemon → control plane | 0.1.0 | Every daemon REST response, including a non-OK response and the 401 before a session refresh, carries x-turbopanel-version. A socket-only session carries { type: "version", instanceVersion } on attach. A response or frame that omits the version clears the last observation back to unknown, and unknown passes. An unsupported control plane is logged (instance-version:) and the daemon keeps reconnecting. The daemon also refuses to install a control-plane package below this floor. A missing or non-semver target is unknown and is allowed. The control plane accepts instance-update from any connected daemon. |
| App → control plane | 0.1.0 | The control plane stamps x-turbopanel-version on every response and the app sends x-turbopanel-client-version. The native app refuses to connect to an older control plane and says which version it needs; a control plane that sends no header is accepted. The bundled web UI always matches its control plane. |
Both floors are 0.1.0, so any 0.1.x peer is in window and a 0.0.x peer is not. The window may trail the current release by
several minor versions. Raise both floors in the same change, and record why.
The floors are unchanged by managed upgrades. A platform upgrade follows the order
above; a daemon and a control plane that are not on the same build stay connected
either way.
Wire features
The cell attach frame { type: "version", instanceVersion, features } lists what this
control plane can speak. A daemon hello lists what that daemon can speak. Today both
sides advertise four features:
managed-upgrade-v1: pinned backup and rollback on a platform upgrade.update-progress-v1: fire-and-forget stage reports (preparing, downloading, installing, restarting, verifying, done, failed, rolled-back).sealed-instance-secrets-v1: the daemon opens sealed envelopes for the control plane TLS keys and the tunnel token, so the control plane sends those secrets sealed to that one daemon instead of as plaintext. A daemon that does not list it still gets the legacy plaintext fields until the floor moves; one that lists it but cannot be sealed to is refused, never sent plaintext.managed-health-v1: the on-demand managed-database replica health probe. The request goes only to a daemon that lists it.
A peer checks the list
before it treats the other side as able to speak the message. A daemon that does not
list managed-upgrade-v1 still receives a plain update, which is how a co-located
daemon from before this release is refreshed; the control-plane step refuses to start
until that feature is present, because the backup has to exist.
An unknown message is ignored. A well-formed frame whose type this process does not
know leaves the socket up — the peer learned a message this build has not. The daemon
logs ignored unknown websocket message type and continues. The control plane records
the inbound frame as ignored and does not close the connection. Oversized frames,
invalid JSON, and a known type that fails field validation are still rejected.
A panel feature that depends on a daemon-rendered artifact has its own minimum,
separate from the floor. Unknown is unsupported for a feature. The first entry
is instance-cert-sources-per-hostname at daemon 0.1.1: per-hostname
certificate sources on the control-plane Caddyfile. Older daemons stay
connected and receive the flat public-URL list only. The app reads
resolveDaemonCapabilities and does not compare versions itself.
Mixing channels (a canary control plane with release daemons, or the reverse)
is unsupported outside active development. A trunk channel updates the daemon
from the CDN and has no control-plane package to mix.
Check versions
| Surface | How |
|---|---|
| Daemon | turbopaneld --version → turbopaneld v0.1.0 <commit> (…) |
| Control plane (self-hosted) | GET /api/health → version and revision.commit (the release job stamps the exact commit) |
| Control plane (TurboPanel High Availability) | GET /api/health on the TurboPanel High Availability origin, or the x-turbopanel-version response header |
| UI | The About screen; the web export ships as turbopanel-ui-<version>.tar.gz and reports itself in x-turbopanel-client-version |
| Server page | The daemon panel shows the running version and commit and names the channel target by version |
Breaking changes
Breaking API or enrollment changes are called out in:
- Changelog
- GitHub Releases for turbopanel, turbopaneld and ui
Related
Last updated on