* Brewfile for homebrew dependencies on macos
* Pull & build google-cloud-cpp/otel via bootstrap script rather than
keeping it all in the ext/ subfolder.
* Update controller CI build with new steps
writePending() did a synchronous ReadRow per queued node to avoid
rewriting unchanged os/arch/version. With thousands of check-ins per
cycle that's thousands of sequential round-trips every 10s, and the read
was nearly pointless since last_seen and the IP cell are written every
cycle anyway.
Replace it with an in-process cache of the last-written node_info per
row (keyed by a 64-bit hash of the row key). node_info is emitted only
when changed, new, or past its refresh window; check_in is still written
every cycle. Failed rows are dropped from the cache so they're rewritten
next cycle, and stale entries are evicted (24h TTL). Net result: one
BulkApply per cycle, no read RPCs.
- Default enableBroadcast to true on load (initializeNetworks and _getNetwork) to match DB::initNetwork. Networks whose JSONB lacks the field now get broadcast on instead of off, which is the documented ZeroTier default.
- Stop overwriting networks_ctl.frontend on every UPDATE. It should remain whatever was set at INSERT. The early-exit guard already prevents cross-frontend writes from applying, so dropping the SET preserves correctness. Members are unaffected — they source frontend from the network row, not the change payload.
- Add enableBroadcast, creationTime, and revision to PubSubListener's network toJson. The protobuf carries them, but the listener was dropping them on inbound, which caused revision to reset on every PubSub-delivered network update.
- Rename member version keys to vMajor/vMinor/vRev/vProto in both PubSubListener (incoming) and PubSubWriter (outgoing). CentralDB, EmbeddedNetworkController, and the rest of the codebase already use these names; the listener/writer were the only sites using versionMajor/Minor/Rev/Protocol, so member version info was being silently dropped through PubSub in both directions.
- Pass newMember by const reference in ControllerChangeNotifier::notifyMemberChange to match notifyNetworkChange. Avoids a full JSON copy on every member notify.
- Remove duplicate nlohmann::json::parse call in RedisNetworkListener.
ssoConfig checked wrong variable
DNS missing-branch wrote to input not output
SSO fields renamed to sso* and nested under ssoConfig (across CentralDB, DB defaults, migration_models)
Member identity populated into the right field; address = memberId
noAutoAssignIPs → noAutoAssignIps casing
Member version columns now SELECTed + populated (both sites)
removeTraceLevel typo in DB.cpp defaults
8. eraseMember uninitialized tmp2 — member deletes now actually delete
9. Connection leak on continue in both commit branches
10. Unsafe (uint64_t)config["revision"] cast → OSUtils::jsonInt
11. target = "NULL" literal text → std::optional<std::string> for real SQL NULL
~13 unsafe direct json→string casts → OSUtils::jsonString; dead vars removed from save(); change_source blocks use is_string() checks.
Heartbeat zadd literal "controllerId" → variable
Network revision double-increment removed
PubSub notifier now sees actual old state (DB query moved before INSERT in both branches)
_delete_network reads config["id"] instead of config["nwid"]
ip_assignments empty-string guards added at both sites (also closes G)
ensure last_modified is set on every write to networks & members
The 10-second session.cancel() loop raced with in-flight acks — when
cancel fired while the GCP client was processing messages, acks were
lost before reaching the server. With message ordering enabled, an
unacked message blocks all subsequent messages on that ordering key,
causing silent stalls with no error output.
Two fixes:
- Replace the cancel/reconnect timer with a blocking session.get(),
storing the session future so the destructor can cancel on shutdown.
- Always ack messages even when onNotification fails — permanent errors
(bad protobuf, missing fields) will never succeed on retry and would
otherwise poison the ordering key indefinitely.
Allow controllers to advertise which central version (cv1, cv2, or all)
they are assigned to handle via a new configurable field. The value is
persisted to the database on each heartbeat and validated at startup
against the DB CHECK constraint.
Publish CTL_NONCE_UPDATE to PubSub when nonces are created or reused in
getSSOAuthInfo(), with the network's frontend as a message attribute so
only the correct CV frontend receives it. Listen for ZT1_AUTH_UPDATE
messages and update sso_expiry.authentication_expiry_time accordingly,
with a network existence check before applying.
- Add sso_send_topic/sso_recv_topic to PubSubConfig
- Add PubSubWriter::publishSSONonceUpdate() with frontend param
- Add PubSubSSOListener class for inbound auth updates
- Rename CV1_AUTH_UPDATE to ZT1_AUTH_UPDATE in sso.proto
- Fix pre-existing connection pool leak in getSSOAuthInfo() catch block
Required to get the list of configs for a particular org that the controller has. Named it `linked_id` rather than `org_id` since we don't know what it will be linked to in CV2