Skip to content

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 both observatory_id and telescope_id, and the telescope's observatory_uid must match.
  • node_type: "observatory" requires only observatory_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.