# Overview This documents setting up a headless [[Obsidian]] instance on a Linux server for vault syncing via [[Obsidian Sync]], mostly based on [this guide](https://rolle.design/setting-up-a-headless-obsidian-instance-for-syncing). The setup uses: - **Xvfb**: Virtual framebuffer (fake display) - **[[Openbox]]**: Minimal window manager - **x11vnc**: VNC server for remote access - **[[Obsidian]]**: Running with GPU disabled # Installation ## Dependencies ```bash sudo apt-get install -y openbox xvfb python3-xdg x11vnc xdg-utils \ libnotify4 libnss3 libsecret-1-0 libasound2 ``` ## Obsidian ```bash # Download latest version (check https://obsidian.md/download for current release) wget https://github.com/obsidianmd/obsidian-releases/releases/download/v1.11.5/obsidian_1.11.5_amd64.deb sudo dpkg -i obsidian_1.11.5_amd64.deb ``` # Configuration ## VNC Password ```bash # Set VNC password (interactive) x11vnc -storepasswd chmod 600 ~/.vnc/passwd # Restart x11vnc after changing password sudo systemctl restart obsidian-headless-x11vnc.service ``` # Systemd Services Four services work together to run Obsidian headlessly. ## Xvfb (Virtual Framebuffer) **File**: `/etc/systemd/system/obsidian-headless-xvfb.service` ```ini [Unit] Description=Headless Xvfb for Obsidian GUI [Service] User=<YOUR_USER> Group=<YOUR_USER> WorkingDirectory=/home/<YOUR_USER>/ ExecStart=/usr/bin/Xvfb :5 -extension GLX -screen 0 800x600x16 KillSignal=SIGINT Environment="PATH=/usr/local/bin:/usr/bin:/bin" Type=exec Restart=on-failure RestartSec=30s [Install] WantedBy=multi-user.target ``` ## Openbox (Window Manager) **File**: `/etc/systemd/system/obsidian-headless-openboxsession.service` ```ini [Unit] Description=Headless openbox-session for Obsidian GUI After=obsidian-headless-xvfb.service [Service] User=<YOUR_USER> Group=<YOUR_USER> WorkingDirectory=/home/<YOUR_USER>/ ExecStart=/usr/bin/openbox-session KillSignal=SIGINT Environment="DISPLAY=:5" Environment="PATH=/usr/local/bin:/usr/bin:/bin" Type=exec Restart=on-failure RestartSec=30s [Install] WantedBy=multi-user.target ``` ## x11vnc (VNC Server) **File**: `/etc/systemd/system/obsidian-headless-x11vnc.service` ```ini [Unit] Description=Headless x11vnc for Obsidian GUI After=obsidian-headless-openboxsession.service [Service] User=<YOUR_USER> Group=<YOUR_USER> WorkingDirectory=/home/<YOUR_USER>/ ExecStart=/usr/bin/x11vnc -rfbport 5900 -display :5 -rfbauth /home/<YOUR_USER>/.vnc/passwd -forever -shared -noxdamage KillSignal=SIGINT Environment="PATH=/usr/local/bin:/usr/bin:/bin" Type=exec Restart=on-failure RestartSec=30s [Install] WantedBy=multi-user.target ``` Note: The `-forever -shared -noxdamage` flags are important: - `-forever`: Keep listening after client disconnects - `-shared`: Allow multiple simultaneous connections - `-noxdamage`: Disable X DAMAGE extension (fixes issues with some compositors) ## Obsidian **File**: `/etc/systemd/system/obsidian-headless.service` ```ini [Unit] Description=Headless Obsidian After=obsidian-headless-xvfb.service [Service] User=<YOUR_USER> Group=<YOUR_USER> WorkingDirectory=/home/<YOUR_USER>/ ExecStart=/usr/bin/obsidian --no-sandbox --disable-gpu --disable-software-rasterizer --disable-gpu-compositing KillSignal=SIGINT Environment="DISPLAY=:5" Environment="PATH=/usr/local/bin:/usr/bin:/bin" Type=exec Restart=on-failure RestartSec=30s [Install] WantedBy=multi-user.target ``` Note: GPU flags are required because Xvfb doesn't have GPU/GLX support: - `--disable-gpu`: Disable GPU hardware acceleration - `--disable-software-rasterizer`: Disable software GL fallback - `--disable-gpu-compositing`: Disable GPU compositing ## Global Display Configuration The [[Obsidian]] CLI (v1.12+) uses [[Electron]]'s single-instance IPC to communicate with the running headless instance. This requires the `DISPLAY` variable to be set, even for non-interactive SSH commands like `gcloud compute ssh ... -- obsidian help`. Shell profile files (`.profile`, `.bashrc`) are **not sourced** for non-interactive SSH sessions, so setting `DISPLAY` there is not sufficient. Instead, set it in `/etc/environment`, which is read by PAM for all session types: **File**: `/etc/environment` ```bash DISPLAY=:5 ``` This ensures `obsidian` CLI commands work transparently from any SSH context without needing to manually export `DISPLAY`. ## Service Management ```bash # Enable all services (auto-start on boot) sudo systemctl enable obsidian-headless-xvfb obsidian-headless-openboxsession sudo systemctl enable obsidian-headless-x11vnc obsidian-headless # Start all services sudo systemctl start obsidian-headless-xvfb sudo systemctl start obsidian-headless-openboxsession sudo systemctl start obsidian-headless-x11vnc sudo systemctl start obsidian-headless # Check status sudo systemctl status obsidian-headless-xvfb obsidian-headless-openboxsession sudo systemctl status obsidian-headless-x11vnc obsidian-headless # View logs sudo journalctl -u obsidian-headless -f sudo journalctl -u obsidian-headless-x11vnc -f ``` # Connecting via VNC ## Port Forwarding Forward port 5900 from the remote server to connect locally. **Standard SSH**: ```bash ssh -L 5900:localhost:5900 <user>@<host> ``` **Google Cloud**: ```bash gcloud compute ssh <instance-name> --zone=<zone> -- -L 5900:localhost:5900 ``` ## VNC Client Options ([[macOS]]) **TigerVNC (Recommended)**: ```bash # Install brew install tiger-vnc # Connect (use -SecurityTypes VncAuth for reliable authentication) vncviewer -SecurityTypes VncAuth localhost:5900 ``` **macOS built-in Screen Sharing**: - Finder → `⌘+K` → `vnc://localhost:5900` - Note: May have compatibility issues with x11vnc; TigerVNC is more reliable ## Troubleshooting VNC If connection hangs or fails: 1. Reset the VNC password on the server: ```bash x11vnc -storepasswd sudo systemctl restart obsidian-headless-x11vnc.service ``` 2. Use `-SecurityTypes VncAuth` flag with TigerVNC 3. Check x11vnc logs: `sudo journalctl -u obsidian-headless-x11vnc.service -f` # CLI The [[Obsidian]] CLI (v1.12+) can be enabled in Settings > General > Advanced. Once enabled, `obsidian help` lists all available commands. The CLI works by briefly launching an [[Electron]] process that connects to the already-running headless instance via single-instance IPC. It sends the command, pipes the output back, and exits. This means: - The headless Obsidian service **must be running** for the CLI to work - The `DISPLAY` variable **must point to the Xvfb display** (`:5`) -- see [[#Global Display Configuration]] - CLI commands can be run non-interactively over SSH, e.g.: ```bash gcloud compute ssh <instance> --zone=<zone> -- obsidian vault ```