Daemon CLI reference

Complete reference for the obsidian-remote-server binary. The plugin invokes this for you on every connect (see Plugin auto-deploy); this page is for operators running it manually under systemd, in containers, or for debugging.

Synopsis

obsidian-remote-server --vault-root=<path> [--socket=<path>] [--token-file=<path>] [--verbose]
obsidian-remote-server --version

Flags

Required

--vault-root <path>

Absolute path of the vault directory on this host. The daemon refuses to start if:

  • The flag is missing or empty
  • The path does not exist
  • The path is not a directory

The daemon will reject any RPC operation whose resolved path escapes this root (PathOutsideVault error, code -32015).

obsidian-remote-server --vault-root=/home/me/notes

Relative paths are accepted but resolved to absolute via filepath.Abs at startup. Symlinks in the vault root path are followed.

Optional

--socket <path>

Unix socket the daemon listens on. Default: <state-dir>/server.sock (state-dir is normally ~/.obsidian-remote/).

The daemon:

  1. Removes any existing file at this path (cleanup of dangling sockets from a crashed prior run)
  2. Creates the parent directory with mode 0700 if missing
  3. Binds the Unix socket
  4. Chmods the socket to 0600 so only the daemon’s user can connect
obsidian-remote-server --vault-root=/srv/vault --socket=/srv/vault-state/server.sock

--token-file <path>

Path the daemon writes its session token to at startup. Default: <state-dir>/token.

The token is 32 random bytes (hex-encoded → 64 chars), generated via crypto/rand. The file is written with mode 0600 and removed when the daemon exits cleanly. Plugins read this file via SFTP and present it on the auth RPC.

--verbose

Enable debug-level logging to stderr (slog text format). Without this, logging is discarded entirely (io.Discard).

The plugin’s auto-deploy passes --verbose so the daemon’s stderr ends up in ~/.obsidian-remote/server.log (the spawn redirects stderr → that log file). For systemd-managed setups, decide based on whether you want noise in journalctl.

--version

Print the daemon version (one line) and exit 0. Used by plugin-side diagnostics + script health checks. Bypasses all other flags.

obsidian-remote-server --version
# → 0.1.0

State directory layout

When --socket and --token-file are not given, both default into <state-dir>. State-dir resolves to:

OSPath
Linux / macOS / BSD$HOME/.obsidian-remote/
Windows(daemon doesn’t run on native Windows; see Server overview)

So a typical invocation with all defaults plants:

~/.obsidian-remote/
    server.sock     # the Unix socket the daemon listens on (mode 0600)
    token           # 32-byte hex token (mode 0600); auto-removed on clean exit

You can override either path independently — useful for multi-vault setups (one socket per vault on the same OS user; see Multi-user hosting).

Invocation patterns

Manual smoke test

~/.obsidian-remote/server \
  --vault-root=$HOME/notes \
  --verbose \
  > ~/.obsidian-remote/server.log 2>&1 &
disown

After this, the plugin’s reuse probe will attach without redeploying. To stop:

pkill -f obsidian-remote-server

Same as the plugin’s auto-deploy

nohup ~/.obsidian-remote/server \
  --vault-root=/home/pi/notes \
  --socket=/home/pi/.obsidian-remote/server.sock \
  --token-file=/home/pi/.obsidian-remote/token \
  --verbose \
  > /home/pi/.obsidian-remote/server.log 2>&1 < /dev/null &

Reverse-engineered from ServerDeployer. The plugin uses nohup … < /dev/null & so the daemon survives the SSH session that spawned it.

Under systemd (per-user)

See the full unit at systemd-managed daemon. Key fragment:

ExecStart=/usr/local/bin/obsidian-remote-server \
  --vault-root=%h/notes \
  --socket=%h/.obsidian-remote/server.sock \
  --token-file=%h/.obsidian-remote/token \
  --verbose

%h expands to the user’s home; lets one unit file work for any user via systemctl --user.

Multi-vault on the same user

Different daemons need different sockets:

# Daemon for the work vault
~/.obsidian-remote/server \
  --vault-root=$HOME/work-notes \
  --socket=$HOME/.obsidian-remote/work.sock \
  --token-file=$HOME/.obsidian-remote/work.token &
 
# Daemon for the personal vault
~/.obsidian-remote/server \
  --vault-root=$HOME/personal-notes \
  --socket=$HOME/.obsidian-remote/personal.sock \
  --token-file=$HOME/.obsidian-remote/personal.token &

In the plugin profile for each, set the matching socket + token paths. See multi-vault recipe.

Exit codes

CodeMeaning
0Clean shutdown (SIGINT / SIGTERM received and listener drained, or --version printed)
1Runtime error after startup (cannot bind socket, token write failed, vault root vanished, etc.) — message goes to stderr
2Argument parse error (missing required flag, invalid value) — flag.ContinueOnError surfaces this

Signal handling

The daemon installs a SIGINT + SIGTERM handler that closes the listener (which unwinds Serve), removes the socket file + token file, then exits 0. This is graceful shutdown.

kill -9 (SIGKILL) skips the cleanup. The next daemon start will find the stale socket file at <socket> and the existing handler removes it before binding fresh — so a hard kill doesn’t permanently jam the path.

Environment variables

The daemon does not read any environment variables for configuration — all config goes through CLI flags. Things that DO affect the daemon implicitly:

Env varEffect
HOMEDetermines the default state-dir ($HOME/.obsidian-remote/) when --socket / --token-file are not given
TZAffects slog’s timestamp output when --verbose is on; otherwise irrelevant

The plugin’s environment (Obsidian’s process.env) is irrelevant to the daemon — they’re separate processes on different hosts.

What the daemon does NOT do

  • It does not detach from its terminal on its own. Use nohup … & or run under systemd to background it. (Plugins do this for you.)
  • It does not rotate its own log file. When run via the plugin, stdout+stderr are piped to ~/.obsidian-remote/server.log and that file truncates only on daemon restart. Under systemd, journalctl handles retention.
  • It does not bind a TCP port. Unix socket only — see Firewall, ports & NAT.
  • It does not retry-on-fail. Process-level supervision (systemd Restart=on-failure, the plugin’s reuse-probe-then-redeploy fallback) handles that.

See also