SkyNode configuration
SkyNode's runtime configuration is one file: SkyNodeConfig, persisted
to disk and editable via the local GUI's configuration page. Its schema
lives at
apps/sky-node-desktop/backend/sky_node/schemas/sky_node_config.py.
You rarely need to edit the JSON directly — the GUI exposes everything important — but knowing the structure helps when you're diagnosing a configuration issue, scripting a deployment, or copying a config between hosts.
The top-level shape:
{
"version": 2,
"node": { /* install metadata */ },
"node_type": "telescope" | "observatory",
"observatory_id": "<uuid>",
"telescope_id": "<uuid>", // required when node_type=telescope
"hardware": { /* cached hub records */ },
"device_configs": { /* driver bindings */ },
"last_sync_at": "<iso-datetime>",
"hub": { /* api + auth endpoints */ },
"local_broker": { /* optional MQTT */ },
"ui": { /* control authority */ },
"controller_lifecycle": { /* startup/shutdown actions */ }
}
node
Static metadata about this install. Populated automatically at first run:
| Field | Meaning |
|---|---|
install_id |
Stable UUID identifying this SkyNode install — used in MQTT topic names and as the SkyNodeInstallation foreign key |
created_at |
First-run timestamp |
updated_at |
Last config update timestamp |
You normally don't edit this section.
node_type, observatory_id, telescope_id
These three fields decide what the install is bound to.
node_type: "telescope"requires bothobservatory_idandtelescope_id, and the telescope'sobservatory_uidmust match.node_type: "observatory"requires onlyobservatory_id.
The config has a model-level validator that rejects inconsistent combinations at load time, so if you've hand-edited the JSON and the backend won't start, this is the first place to check.
See SkyNode → Overview for which to choose.
hardware
A cached copy of the relevant hub records:
| Sub-field | What's in it |
|---|---|
observatories |
The bound observatory and any siblings the install can see |
telescopes |
The bound telescope (and siblings for observatory nodes) |
instruments |
Instruments at the telescope |
devices |
Every device in the install's scope |
sites |
The site record |
filters |
Filter records (referenced by filter wheels) |
filter_wheel_positions |
Per-filter-wheel slot mappings |
operational_constraints |
The constraint definitions to evaluate |
ip_cameras |
IP camera feeds attached to the telescope |
SkyNode refreshes this graph from the hub at the cadence the sync
monitor enforces (and on demand from the GUI). last_sync_at records
the last successful refresh.
You don't edit hardware directly — it's a cache. Edit the
underlying records in the website's telescope settings page; SkyNode
picks up the changes on the next sync.
device_configs
This is where each device gets bound to a concrete driver. The shape is a map keyed by device UID:
{
"device_configs": {
"device_configs_by_id": {
"<device-uid>": {
"device_type": "mount" | "camera" | "filter_wheel" | …,
"driver": "ascom" | "maxim" | "simulator" | "gbtwenty" | …,
"driver_configs": {
"<driver-id>": { /* driver-specific params */ }
},
/* device-type-specific fields */
}
}
}
}
Device-type-specific extras the schema layer adds today:
| Device type | Extra config |
|---|---|
camera |
temperature_setpoint_tolerance (°C, default 0.5) — drives "is the camera at temperature yet" checks |
enclosure |
connect_locally (default false), allow_control (default false) — distinguishes monitor-only enclosure bindings from actively-controlled ones |
weather_sensor |
connect_locally (default false) — same idea as enclosure, for sensor-only bindings |
The list of driver options per device type is in Driver interfaces.
hub
Hub endpoints and OAuth settings. Set during installation.
{
"hub": {
"hub_id": "Skynet Global Telescope Network",
"api_server": "https://api.skynetgo.org/",
"gateway_api_server": "https://node.skynetgo.org/",
"auth": {
"method": "oauth",
"auth_server": "https://auth.skynetgo.org/",
"client_id": "skynode-client",
"redirect_uri": "skynode://auth/callback",
"scopes": []
}
}
}
hub_id is a human-readable label shown in the local GUI; the hub
doesn't validate it. api_server and gateway_api_server are the two
URLs SkyNode talks to (public-api and
sky-node-gateway).
local_broker (MQTT)
Optional. Disabled by default. When enabled, SkyNode also publishes snapshots to a local MQTT broker for on-site subscribers (status displays, monitoring tools).
{
"local_broker": {
"enabled": true,
"host": "127.0.0.1",
"port": 1883,
"transport": "tcp", // or "websocket"
"websocket_path": "/mqtt",
"keepalive_s": 30,
"clean_session": true,
"client_id_prefix": "skynode",
"reconnect": {
"initial_backoff_s": 1,
"max_backoff_s": 30,
"jitter": true
},
"auth": { "username": null, "password": null },
"tls": {
"enabled": false,
"ca_file": null,
"cert_file": null,
"key_file": null,
"insecure_skip_verify": false
},
"publish": {
"device_snapshot": { "qos": 0, "retain": true },
"constraint_snapshot": { "qos": 1, "retain": true },
"operational_snapshot":{ "qos": 1, "retain": true }
},
"lwt": {
"enabled": true,
"topic": "skynode/{installation_uid}/status",
"online_payload": "{\"status\":\"online\"}",
"offline_payload": "{\"status\":\"offline\"}",
"qos": 1,
"retain": true
}
}
}
The Last Will and Testament (lwt) is what lets a subscriber detect
that the SkyNode process has died (broker publishes the offline
payload on the LWT topic when the keep-alive lapses).
ui
User-interface preferences. Today this is just:
{ "ui": { "controlAuthority": "remote" } }
Mirrors the telescope record's controlAuthority. Flipping it here
changes the local default; the authoritative value still lives on the
telescope record.
controller_lifecycle
Startup and shutdown actions the lifecycle supervisor performs.
{
"controller_lifecycle": {
"startupActions": {
"unparkMount": true,
"restoreCameras": false
},
"shutdownActions": {
"closeEnclosure": true,
"parkMount": true,
"warmCameras": false
}
}
}
- Startup actions run when SkyNode comes up healthy after a clean start or recovery (unpark the mount, restore cameras to their cooling setpoints).
- Shutdown actions run when SkyNode is asked to stop, when a shutdown interlock fires, or when the telescope is flipped to manual control mode (close the enclosure, park the mount, warm cameras for safe power-down).
The defaults are conservative — startup unparks the mount but doesn't restart camera cooling; shutdown parks and closes but doesn't warm cameras. Override these per-site based on your hardware.
Backwards-compatible aliases
A handful of fields accept both snake_case and camelCase to ease
migration between config versions. local_broker accepts the legacy
key mqtt and the camelCase localBroker; controller_lifecycle
accepts controllerLifecycle; ui.control_authority accepts
controlAuthority. The current canonical key is the snake_case one,
but existing configs using the old names continue to load.
Where the file lives
The default location is per-platform (Tauri picks a config dir based
on the host OS). You can override it via the SKY_NODE_CONFIG_PATH
environment variable — useful for ops that want the config in a
specific volume or for running multiple installs on one host.
config/skynet_config.json at the repo root is not a deployment
config — it's a per-developer runtime path for source builds, and
should be treated as gitignored runtime state.