instance.yaml Reference

The central configuration file for a Sanctum instance lives at ~/.sanctum/instance.yaml. Every instance-specific value is defined here — services, networking, paths, family members, node topology, and secrets references.
One file. Everything your haus knows about itself is in this file. The name, the network layout, which services run, who lives there. If this file is wrong, everything downstream is wrong. If this file is right, everything downstream has a fighting chance.
A JSON cache is auto-regenerated at ~/.sanctum/.instance.json whenever the YAML changes, via lib/yaml2json.py.
Minimal Example
Section titled “Minimal Example”The absolute minimum to get Sanctum to acknowledge your existence:
instance: name: Sanctum Hub slug: sanctum-hub timezone: America/Montreal
users: mac: username: neo home: /Users/neo vm: username: ubuntu home: /home/ubuntu
network: mac_bridge_ip: 10.0.0.1 vm_ip: 10.0.0.10 mac_lan_ip: 192.0.2.10 bridge_interface: bridge100 vm_ssh_alias: openclawinstance
Section titled “instance”Top-level identity for this Sanctum deployment. Who you are. Where you are. When you are.
| Key | Type | Required | Description |
|---|---|---|---|
slug | string | Yes | URL-safe identifier used in hostnames, paths, and DNS. Example: sanctum-hub |
name | string | Yes | Human-readable display name. Example: Sanctum Hub |
timezone | string | Yes | IANA timezone for scheduling and logs. Example: America/Montreal |
instance: slug: sanctum-hub name: Sanctum Hub timezone: America/MontrealOS-level identities on the Mac host and the VM. Unglamorous but load-bearing — every SSH command, every file path, every LaunchAgent plist resolves through one of these. Get them wrong and nothing errors cleanly; things just silently don’t work, which is worse. Each side is a map of a username and a home directory, not a bare string — sanctum_home() and sanctum_expand() read the home path straight off users.mac.home.
| Key | Type | Required | Description |
|---|---|---|---|
mac.username | string | Yes | macOS username on the host machine |
mac.home | string | Yes | macOS home directory (absolute) |
vm.username | string | Yes | Linux username inside the VM |
vm.home | string | Yes | Linux home directory inside the VM |
users: mac: username: neo home: /Users/neo vm: username: ubuntu home: /home/ubuntunetwork
Section titled “network”The plumbing. Nobody admires plumbing until it breaks, and then it’s the only thing anyone can talk about. This section defines how the Mac, the VM, and the LAN find each other — a tiny private internet inside your haus, inside your actual internet.
| Key | Type | Required | Description |
|---|---|---|---|
mac_bridge_ip | string | Yes | Mac-side IP on the bridge interface |
vm_ip | string | Yes | Static IP of the VM on the host-only network |
mac_lan_ip | string | Yes | Mac Mini IP on the local network |
bridge_interface | string | Yes | macOS bridge interface name (e.g., bridge100) |
bridge_subnet | string | No | CIDR for the Mac↔VM bridge network |
lan_subnet | string | No | CIDR for the LAN |
vm_ssh_alias | string | Yes | SSH config alias for the VM (e.g., openclaw) |
network: mac_bridge_ip: 10.0.0.1 vm_ip: 10.0.0.10 mac_lan_ip: 192.0.2.10 bridge_interface: bridge100 bridge_subnet: 10.0.0.0/24 lan_subnet: 192.0.2.0/24 vm_ssh_alias: openclawWhere things live on disk. Every backup script, every log rotation, every skill loader reads from this section. Think of it as the haus’s filing cabinet — except the filing cabinet is also load-bearing, so don’t move it.
| Key | Type | Required | Description |
|---|---|---|---|
openclaw_config | string | Yes | Agent config directory. Default: ~/.openclaw |
openclaw_skills | string | Yes | Shared skills repo checkout |
logs | string | Yes | Centralized log directory |
projects | string | Yes | Projects root (Mac side) |
backups | string | Yes | Backup destination directory |
paths: openclaw_config: ~/.openclaw openclaw_skills: ~/Projects/openclaw-skills logs: ~/.openclaw/logs projects: ~/Projects backups: ~/Backupsservices
Section titled “services”Here’s where it gets real. Every service your haus runs — the agent gateway, the voice engine, the haus automation hub, the offline library — each one gets an enabled boolean and, if it listens on a port, a port key. Flip the boolean, regenerate plists, and a freshly enabled service gets its LaunchAgent. Configuration as incantation.
The enabled flag controls whether generate-plists.sh renders the corresponding LaunchAgent. Today a disabled service is simply skipped during generation — its plist isn’t rewritten. If it was already loaded, it keeps running until you launchctl bootout it by hand. “Disabled” means “not regenerated,” not yet “torn down.” Honest beats incantation.
Service Table
Section titled “Service Table”| Service | Key | Default Port(s) | Description |
|---|---|---|---|
| Agent Gateway | dench | — | OpenClaw/DenchClaw agent gateway (runs in the VM; reached on the host via the Lima-forwarded 127.0.0.1:1977) |
| Home Assistant | home_assistant | 8123 | Home automation hub (Docker) |
| Dashboard | dashboard | 3333, 1111 | Command center web (3333) + API (1111); the Holocron UI on 3344 has since become the front door |
| Firewalla Bridge | firewalla_bridge | 1984 | Firewalla P2P bridge |
| VM | vm | — | Ubuntu 24.04 guest under Lima |
| Voice Agent | voice_agent | 1138 | Yoda voice interaction agent |
| Qwen3-TTS | qwen3_tts | 8008 | Text-to-speech server (mlx-audio backend; XTTS-v2 retired 2026-04-19) |
| Council MLX | council_mlx | 1337 | Council MLX model server (Qwen3.6-35B-A3B, mTLS) |
| Cloudflare Tunnel | cloudflare | — | Cloudflare Zero Trust tunnel |
| iCloud Filer | icloud_filer | — | Automatic iCloud filing daemon |
| Health Center | health_center | 2222 | Health monitoring dashboard |
| Tailscale | tailscale | — | Mesh VPN |
| Memory Vault | memory_vault | 42069 | Cross-session message store |
| Watchdog | watchdog | — | Service health monitoring |
| Kiwix | kiwix | 8888 | Offline library server |
| Signal Proxy | signal_proxy | 5150 | Signal messaging bridge (host-forwarded from the VM) |
| Outline | outline | 3100 | Self-hosted wiki |
Seventeen of the services are shown above. The full catalogue is 28 keys under services:. On a Mac Mini. Under a desk. Running a haushold.
Full Example
Section titled “Full Example”services: dench: enabled: true
home_assistant: port: 8123 homekit_port: 21063
dashboard: port: 3333 backend_port: 1111
firewalla_bridge: enabled: true port: 1984
vm: enabled: true
voice_agent: enabled: true port: 1138
qwen3_tts: enabled: true port: 8008
council_mlx: enabled: true port: 1337
cloudflare: enabled: true
icloud_filer: enabled: true
health_center: enabled: true port: 2222
tailscale: enabled: true
memory_vault: enabled: true port: 42069
watchdog: enabled: true interval: 600
triage: enabled: true interval: 30
kiwix: enabled: true port: 8888
signal_proxy: enabled: true port: 5150 host: vm
outline: enabled: true port: 3100Checking Service Status
Section titled “Checking Service Status”From the shell library:
source ~/.sanctum/lib/config.sh
if sanctum_enabled voice_agent; then echo "Voice agent is enabled on port $(sanctum_get services.voice_agent.port)"fisanctum_enabled takes the bare service name — it prepends services. and appends .enabled itself. Pass it services.voice_agent and it dutifully looks up services.services.voice_agent.enabled, finds nothing, and reports disabled. sanctum_get, by contrast, wants the full dotted path. The asymmetry is a footgun; the rule of thumb is “name the service for enabled, name the path for get.”
From TypeScript:
import { isEnabled, get } from './lib/config.js';
if (isEnabled('voice_agent')) { const port = get('services.voice_agent.port');}secrets
Section titled “secrets”The only section of this file that’s about what isn’t here. Sanctum never stores secrets in instance.yaml directly — the config tells you where secrets live, not what they are. A treasure map that says “the chest is buried under the oak tree” without ever containing the treasure. Three secret stores, three levels of paranoia, all of them justified.
| Key | Type | Description |
|---|---|---|
keychain_account | string | macOS Keychain account name for stored tokens |
op_vault | string | 1Password vault name for credentials |
op_item_prefix | string | Prefix for 1Password item names (e.g. Sanctum - Home Assistant) |
sops_file | string | Path to the SOPS-encrypted secrets file |
secrets: keychain_account: sanctum op_vault: Private op_item_prefix: Sanctum sops_file: ~/.openclaw/secrets.enc.yamlThree stores, three trust models, zero plaintext on this disk. The treasure map can be committed to git. The chests cannot.
home_assistant
Section titled “home_assistant”Where the smart home meets reality. This section doesn’t configure Home Assistant itself — that’s configuration.yaml’s job. This section tells Sanctum what HA needs to know about the physical world: which speakers exist, which cameras are watching, which thermostat controls which zone. The kind of inventory you never think to write down until the third time you troubleshoot from memory.
| Key | Type | Description |
|---|---|---|
cameras | map | Camera entity IDs keyed by friendly name |
sonos_speakers | list | HA media_player.* entity IDs for the Sonos fleet |
hvac_zones | list | Thermostat zones (id, name, temp + humidity sensors) |
ecobee | map | Ecobee climate + sensor entity IDs |
floor_zones | list | In-floor heating zones |
carrier_sensors | map | Carrier furnace/airflow sensor entity IDs |
home_assistant: cameras: front_door: camera.front_door_live_view
sonos_speakers: - media_player.dining_room - media_player.living_room - media_player.master_bedroom
hvac_zones: - id: climate.main_floor name: Ground Floor temp_sensor: sensor.main_floor_temperature humidity_sensor: sensor.main_floor_humidity
ecobee: climate: climate.main_floor occupancy: binary_sensor.main_floor_occupancyfamily
Section titled “family”Your haus should know who lives in it. This section is what lets the agents greet a kid by name, route an alert to their bedroom speaker, and know which devices are theirs when curfew lands. A short list with outsized consequences — and the screen-time enforcer reads it directly.
| Key | Type | Description |
|---|---|---|
children | list | List of family member objects |
children[].name | string | Display name |
children[].sonos_speaker | string | HA media_player.* entity to address this person |
children[].devices | list | Per-device {mac, name, icon} entries (for presence + screen time) |
children[].screen_time | map | Wake/block schedule (wake_hour, block_hour, school_days) |
family: children: - name: Kid One sonos_speaker: media_player.vr_room devices: - mac: AA:BB:CC:DD:EE:XX name: Kid One's iPad icon: phone screen_time: wake_hour: 8 block_hour: 21Multi-site node topology. Each node represents a physical location running Sanctum infrastructure. Because one haus wasn’t enough — you needed a distributed system. The hub runs everything; satellites run what they can; mobile devices check in when they feel like it. It’s federation for people who own property in more than one postal code.
| Key | Type | Description |
|---|---|---|
<node_id> | map | Stable short node identifier (e.g., hub, satellite) |
.type | string | Node type: hub, satellite, mobile, or sensor |
.display_name | string | Human-readable label shown in the dashboard |
.location | string | Logical location tag (e.g., home, chalet) |
.host | string | LAN hostname or IP (empty if unreachable from the hub) |
.tailscale_ip | string | Tailscale mesh IP |
.tailscale_name | string | Tailscale device name |
.ssh_user | string | SSH username on this node (read by sanctum_node_user) |
.online | bool | Last-known reachability |
.vm | map | Hub only: nested host / ssh_user / ssh_alias for the guest |
.services | map | Optional per-node service overrides (satellites) |
nodes: hub: type: hub display_name: Sanctum Hub location: home host: 192.0.2.10 tailscale_ip: 100.0.0.20 tailscale_name: hub ssh_user: neo online: true vm: host: 10.0.0.10 ssh_user: ubuntu ssh_alias: openclaw
satellite: type: satellite display_name: Chalet location: chalet host: '' tailscale_ip: 100.0.0.30 tailscale_name: satellite ssh_user: neo online: falseNode Types
Section titled “Node Types”| Type | Description |
|---|---|
hub | Primary site with full infrastructure (VM, all services) |
satellite | Secondary site with reduced stack (no VM, lighter services) |
mobile | Laptop or portable device |
sensor | Headless monitoring device |
The satellite knows it’s not the hub. It doesn’t try to be. It runs what it needs, phones haus over Tailscale, and keeps the lights on when the internet goes out. Self-aware infrastructure is underrated.
Example Config File
Section titled “Example Config File”An annotated example is available at ~/.sanctum/instance.yaml.example and can be used as a starting point for new instances.
JSON Cache
Section titled “JSON Cache”The YAML config is automatically converted to a JSON cache at ~/.sanctum/.instance.json — a nested object tree mirroring the YAML, which the libraries walk with dotted-path lookups (services.voice_agent.port). Nothing is flattened; the dots in the query are traversal, not key names. The shell and TypeScript helpers both read this file for fast lookups. If you edit instance.yaml manually, regenerate the cache:
python3 ~/.sanctum/lib/yaml2json.py