Skip to content

Node Topology

Sanctum is designed to run across multiple physical locations. A primary hub at your main home coordinates with satellite nodes at secondary properties, mobile nodes on laptops, and (in the future) sensor nodes for lightweight IoT devices. All nodes share a single instance.yaml configuration and communicate over Tailscale.

Hub

The full-stack primary node. Runs the Mac Mini host with the Ubuntu VM, all AI agents, Home Assistant, inference servers, and the complete service catalog. There is exactly one hub per Sanctum instance.

Satellite

A lighter deployment at a secondary home. Runs a subset of services (typically a gateway, Home Assistant, and a small local model). Syncs configuration and state with the hub over Tailscale.

Mobile

A MacBook Pro or similar portable device. Connects to the hub remotely via Tailscale for agent access, SSH, and API calls. Does not run persistent services.

Sensor

A future node type for dedicated IoT or monitoring hardware. Planned for low-power devices that report data to the hub without running the full Sanctum stack.

Each node knows who it is through a single-line file at ~/.sanctum/.node_id:

Terminal window
# On the hub:
cat ~/.sanctum/.node_id
# manoir
# On the satellite:
cat ~/.sanctum/.node_id
# chalet

The identity string must match a key in the nodes section of instance.yaml. Scripts and services use this to determine which configuration block applies to the current machine.

Terminal window
source ~/.sanctum/lib/config.sh
NODE=$(sanctum_whoami) # "manoir"
TYPE=$(sanctum_node_get "$NODE" type) # "hub"
IP=$(sanctum_node_get "$NODE" tailscale_ip) # "100.112.178.25"

Every node is declared under the nodes key with its network addresses, SSH user, node type, and the list of services it runs:

nodes:
manoir:
type: hub
host: 192.168.1.10
tailscale_ip: 100.112.178.25
ssh_user: bert
services:
- gateway
- home_assistant
- dashboard
- voice_agent
- lm_studio
- council_mlx
- firewalla_bridge
- cloudflare_tunnel
- watchdog
chalet:
type: satellite
host: null # Set during on-site install
tailscale_ip: 100.112.203.32
ssh_user: bert
services:
- gateway
- home_assistant
macbook:
type: mobile
host: null # DHCP, varies by network
tailscale_ip: 100.120.85.55
ssh_user: bert
services: [] # No persistent services
FieldRequiredDescription
typeYesOne of hub, satellite, mobile, sensor
hostNoLAN IP address. null if not on the home network or DHCP.
tailscale_ipYesStable Tailscale IP for cross-network access
ssh_userYesUsername for SSH connections to this node
servicesYesList of service keys from services.* that this node runs

The hub is the authoritative node. It runs every service, hosts the VM with the agent cluster, and is the source that satellites sync from.

  • Runs the full AI agent cluster (5 agents on the VM)
  • Hosts the command center dashboard
  • Manages the Cloudflare tunnel for external access
  • Runs inference servers (LM Studio, Council-27B MLX, XTTS)
  • Acts as the Git and rsync origin for skill updates
  • Stores the canonical copy of instance.yaml

These services only run on the hub and are not deployed to satellites:

ServiceReason
Council-27B MLXRequires Apple Silicon with sufficient memory
LM StudioLarge model inference, hub-only hardware
Firewalla BridgeDirect LAN access to the primary router
Cloudflare TunnelSingle ingress point for the instance
Orbi BridgeDirect LAN access to the access point
Voice AgentTied to local Sonos speakers and XTTS

A satellite is a smaller deployment at a secondary location. It runs a gateway with a lightweight local model and its own Home Assistant instance for location-specific devices.

  1. Install macOS on the satellite Mac and join it to Tailscale.
  2. Copy the install bundle (synced via iCloud) and run the setup scripts.
  3. Set the .node_id file to the satellite’s name (e.g., chalet).
  4. Install Docker Desktop and deploy Home Assistant.
  5. Install a local model in LM Studio (e.g., Qwen 3.5 3B 4-bit for limited hardware).
  6. Configure on-site integrations (cameras, sensors, smart devices).
  7. Update the satellite’s host field in instance.yaml on the hub.

Satellites pull updates from the hub over Tailscale:

Hub (manoir) Satellite (chalet)
| |
+-- instance.yaml ---- Tailscale ----> instance.yaml (subset)
+-- skills repo ---- Tailscale ----> skills repo (rsync)
+-- agent config ---- Tailscale ----> agent config

Mobile nodes are laptops that connect to the Sanctum instance remotely. They do not run persistent services but can SSH into the hub, query agents, and access dashboards over Tailscale.

ActionCommand / URL
SSH to hubssh bert@100.112.178.25
SSH to VMssh -J bert@100.112.178.25 ubuntu@10.10.10.10
Dashboardhttp://100.112.178.25:3001
Home Assistanthttps://ha.nepveu.name (via Cloudflare)
Agent queryVia gateway API at 100.112.178.25:18789

Both the shell and TypeScript libraries provide functions for working with the node topology:

Terminal window
source ~/.sanctum/lib/config.sh
# Who am I?
sanctum_whoami # "manoir"
# Get a field from any node
sanctum_node_get chalet tailscale_ip # "100.112.203.32"
sanctum_node_get macbook ssh_user # "bert"
import { whoami, nodeGet, getNodesByType } from './lib/config';
const me = whoami(); // "manoir"
const satellites = getNodesByType('satellite'); // ["chalet"]
const chaletIp = nodeGet('chalet', 'tailscale_ip'); // "100.112.203.32"
Tailscale Mesh (tail7c6d11.ts.net)
_______________________________________________
/ | \
Hub: manoir Satellite: chalet Mobile: macbook
100.112.178.25 100.112.203.32 100.120.85.55
| | |
[Mac Mini M4 Pro] [Mac Mini M1] [MacBook Pro M4 Max]
+-- Ubuntu VM +-- Gateway (no services)
+-- 5 AI Agents +-- Home Assistant
+-- Full service catalog +-- Local LLM (3B)
+-- Home Assistant
+-- Inference servers