Skip to content

Database

The Bridge and the Hub each keep their own database. They are different engines for different jobs, and nothing is shared between them: what the Hub knows about a bridge arrived over MQTT, not out of a shared table.

Bridge

One SQLite file, by default /cubeos/data/meshsat.db, overridable with MESHSAT_DB_PATH.

The driver is modernc.org/sqlite, a pure Go implementation, so the binary builds with CGO_ENABLED=0 and cross compiles to every board MeshSat supports. The connection opens with:

PragmaValueWhy
journal_modeWALreaders do not block the writer
busy_timeout5000 msa slow SD card is a wait, not an error
synchronousNORMALsurvives a process crash; a power cut can lose the last commit

synchronous(NORMAL) is the deliberate trade for flash endurance on a Raspberry Pi. If your deployment can lose power without warning and cannot lose the last few seconds of messages, put the database on storage that will not, rather than changing the pragma.

What is in it

Around 70 tables. The groups worth knowing:

GroupTables
Trafficmessages, positions, telemetry, message_deliveries, dead_letters
Routinginterfaces, access_rules, object_groups, failover_groups, failover_members, bond_groups, bond_members, routing_destinations, routing_links
Satelliteiridium_tle_cache, iridium_locations, pass_quality_log, credit_balance, credit_usage, satellite_usage, satellite_rate_limits
Contacts and keyscontacts, contact_addresses, directory_contacts, directory_contact_keys, key_bundles, sms_contacts
Devicesdevices, device_config_versions, sim_cards, zigbee_devices, neighbor_info
Evidenceaudit_log, signal_history, spectrum_scans, webhook_log, oob_log

Migrations

internal/database/migrations.go holds an append-only list, 54 entries at the time of writing. The current version lives in the schema_version table and the bridge applies everything above it at startup.

Append-only is a rule, not a convention: editing an existing migration changes what a fresh install builds while leaving every existing install alone, and the two silently diverge. Add a new entry instead.

Retention

A worker runs daily and deletes rows older than MESHSAT_RETENTION_DAYS, 30 by default, from messages, telemetry and positions, plus dead letters that have been sent or have expired. Set it to 0 to keep everything, and then watch the disk.

Nothing else is pruned. Audit entries, configuration, contacts and keys are kept until you remove them.

Backing it up

Copy the file while the bridge is stopped, or use sqlite3 meshsat.db ".backup out.db" while it is running. The WAL means a plain cp of a live database can be a torn read.

GET /api/config/export is the other half: it returns the running configuration in a readable form, which is what you actually need to rebuild a bridge. The database carries history; the export carries intent.

Hub

The hosted Hub runs PostgreSQL, managed by CloudNativePG: three instances with a synchronous replica, so a committed write exists on two machines before the Hub is told it succeeded.

A self-hosted standalone Hub uses SQLite instead, at /data/hub.db, set with HUB_SQLITE_PATH. Both implementations pass the same conformance suite, so the Hub behaves identically on either.

Migrations

Numbered, append-only, and checksum verified: the recorded hash of every applied migration is compared against the text in the binary at startup, and a mismatch refuses to start rather than running against a schema nobody can reason about. Migrations run under a Postgres advisory lock, so several replicas starting at once queue rather than race.

Your data, in the hosted Hub

EndpointWhat it does
GET /api/tenant/exporta ZIP with one JSON file per table
DELETE /api/tenantmarks the account for deletion

Deletion is soft for 30 days, then a purge job removes the rows for good. The list of tables to export and erase is read from the database catalogue rather than written out in code, because a hardcoded list is one forgotten migration away from an erasure that leaves rows behind.

Columns holding secrets are withheld from exports. Your API keys and provider credentials are not in the ZIP, by design.

See your data for the tenant-facing version of this.

Made by MeshSat