data.json schema reference

Complete schema for <vault>/.obsidian/plugins/remote-ssh/data.json — the file that holds your profiles, host-key trust, and plugin settings. Useful for debugging, scripted multi-machine setup, or rebuilding state by hand.

Source of truth: plugin/src/types.ts (PluginSettings, SshProfile, JumpHostConfig) plus plugin/src/main.ts saveSettings() (around lines 374-377 — the hostKeyStore field is folded into the saved blob there) and loadSettings() (around lines 311-326 — where it’s read back). If anything below disagrees with the source, the source wins.

Top-level shape

{
  // PluginSettings fields (see below for each)
  "profiles": [...],
  "activeProfileId": "...",
  "enableDebugLog": false,
  "maxLogLines": 1000,
  "reconnectMaxRetries": 5,
  "clientId": "",
  "userName": "",
  "onboardingCompleted": true,
  "telemetryEnabled": false,
  "terminalShell": "/usr/bin/zsh -l",
  "terminalFontSize": 12,
  "terminalScrollback": 1000,
 
  // Added by main.ts at save time (NOT in PluginSettings)
  "hostKeyStore": {
    "192.168.1.50:22": "aa:bb:cc:dd:..."
  }
}

Top-level fields

FieldTypeDefaultNotes
profilesSshProfile[][]One entry per connection target; see profile schema below
activeProfileIdstring | nullnullLast-connected profile, restored on plugin reload
enableDebugLogbooleanfalseVerbose logging to console.log (UI: Settings → Advanced)
maxLogLinesnumber(legacy field; the size-rotated console.log makes it informational)Pre-Phase-D upper-bound; leave as default
reconnectMaxRetriesnumber5 (Backoff.DEFAULT_BACKOFF.maxRetries)0 disables auto-reconnect; range 0-100
clientIdstring"" (= sanitized OS hostname at runtime)Per-device id; surfaces as .obsidian/user/<id>/ on the remote
userNamestring"" (= OS username at runtime)Display name only; not used for SSH auth
onboardingCompletedboolean?falseFirst-launch flag; once true, the OnboardingModal won’t auto-open
telemetryEnabledboolean?falseOpt-in counters → <plugin>/telemetry.jsonl
terminalShellstring?(use remote $SHELL)Override for the terminal pane’s shell command
terminalFontSizenumber?12xterm.js font size, range 6-32
terminalScrollbacknumber?1000In-memory line buffer (not persisted across re-opens)
autoConnectProfileIdstring?(unset)Set by ShadowVaultBootstrap on shadow vaults; auto-connects on layout-ready. Don’t set on a regular vault.
pendingPluginSuggestionsPendingPluginSuggestion[]?(unset)Shadow-vault-only; suggestions for which community plugins to install on first bootstrap. Cleared once decided.
hostKeyStoreRecord<string, string>{}Map host:port → sha256-hex fingerprint. Added by main.ts at save. See Host-key trust.

SshProfile schema

Every entry in profiles[] matches:

{
  "id": "abc-123-uuid",          // generated on profile creation; do not change
  "name": "My Pi",               // display label
 
  // Identification
  "host": "192.168.1.50",
  "port": 22,
  "username": "pi",
 
  // Authentication
  "authMethod": "privateKey",    // "password" | "privateKey" | "agent"
  "privateKeyPath": "~/.ssh/id_ed25519",  // for "privateKey"
  "passphraseRef": "...",        // SecretStore handle for the passphrase
  "agentSocket": "/path/to/sock",        // override SSH_AUTH_SOCK; rare
  "passwordRef": "...",          // SecretStore handle if "password" auth
 
  // Remote vault
  "remotePath": "/home/pi/notes",
 
  // Transport
  "transport": "sftp",           // "sftp" (default) | "rpc"
  "rpcSocketPath": ".obsidian-remote/server.sock",
  "rpcTokenPath": ".obsidian-remote/token",
 
  // SSH session tuning (sane defaults; rarely changed)
  "connectTimeoutMs": 15000,
  "keepaliveIntervalMs": 30000,
  "keepaliveCountMax": 3,
 
  // Host key (Phase 2 only — superseded by hostKeyStore for v1)
  "hostKeyFingerprint": "...",   // legacy per-profile pin
 
  // Optional jump host
  "jumpHost": {
    "host": "bastion.example.com",
    "port": 22,
    "username": "jump-user",
    "authMethod": "agent",
    "privateKeyPath": "~/.ssh/bastion_key",  // if authMethod === "privateKey"
    "passwordRef": "..."         // if authMethod === "password"
  }
}

Field-by-field

FieldTypeRequired?Notes
idstringyesUUID-shape; the plugin generates this on Add profile. Don’t edit by hand — it keys the shadow vault directory under ~/.obsidian-remote/vaults/<id>/.
namestringyesDisplay only; safe to rename anytime
hoststringyesHostname / IP / SSH-config alias
portnumberyesDefaults to 22 in the UI
usernamestringyesRemote SSH user
authMethodenumyesOne of password, privateKey, agent
privateKeyPathstringwhen authMethod === 'privateKey'~-expanded at runtime
passphraseRefstringwhen key has a passphrase + you stored itOpaque handle from SecretStore (OS keychain)
agentSocketstringrarelyOverride of SSH_AUTH_SOCK; usually leave unset
passwordRefstringwhen authMethod === 'password' + you stored itSame SecretStore mechanism
remotePathstringyesVault root on the remote; ~-expanded
transportenumno, default sftpSwitch to rpc to opt into the daemon
rpcSocketPathstringno, default .obsidian-remote/server.sockHome-relative path on the remote
rpcTokenPathstringno, default .obsidian-remote/tokenHome-relative path on the remote
connectTimeoutMsnumbernoSSH handshake timeout
keepaliveIntervalMsnumbernoSSH keepalive cadence
keepaliveCountMaxnumbernoDrop after N missed keepalives
hostKeyFingerprintstringnoLegacy. Pre-1.0 per-profile pin; superseded by the global hostKeyStore. New profiles don’t set this.
jumpHostJumpHostConfignoSingle jump host for now (multi-hop tracked separately)

JumpHostConfig schema

{
  "host": "bastion.example.com",
  "port": 22,
  "username": "jump-user",
  "authMethod": "agent",          // same enum as profile
  "privateKeyPath": "...",        // if applicable
  "passwordRef": "..."            // if applicable
}

Each jump host authenticates independently. See Jump hosts for the model.

hostKeyStore

The plugin’s own known-host store, separate from ~/.ssh/known_hosts. Map of "<host>:<port>" → sha256-hex fingerprint:

{
  "hostKeyStore": {
    "192.168.1.50:22": "aa:bb:cc:dd:ee:ff:01:02:03:04:05:06:07:08:09:0a:0b:0c:0d:0e:0f:10:11:12:13:14:15:16:17:18:19:1a",
    "bastion.example.com:22": "ab:cd:ef:..."
  }
}

The fingerprint format is the same colon-separated bytes shown in the host-key trust dialog (no SHA256: prefix). See Host-key trust for the trust model + manual edits.

Hand-editing safely

Obsidian must be closed when you edit data.json directly — the plugin saves over it on shutdown.

After edits, re-open Obsidian. The plugin’s load path (main.ts:311+) reads the file as a Partial<PluginSettings> & { hostKeyStore? }, so missing fields revert to defaults rather than crashing.

Common safe operations:

  • Reorder profiles[]
  • Rename profiles[i].name
  • Update profiles[i].host / port / remotePath
  • Remove a stale hostKeyStore entry to force re-trust

Unsafe operations (don’t do these):

  • Change profiles[i].id (would orphan the shadow vault directory)
  • Edit hostKeyStore to a fingerprint you didn’t actually verify (defeats the purpose)
  • Add fields not in this schema (silently dropped on next save)

What’s NOT in data.json

  • Passwords / passphrases themselves — stored in OS keychain via SecretStore; data.json only has opaque handles
  • The actual SSH private keys — read fresh from ~/.ssh/... on every connect
  • Logs / telemetry — separate files (console.log, telemetry.jsonl)
  • The shadow vault contents — physically separate vaults under ~/.obsidian-remote/vaults/<id>/

See also