English | 中文
A Linux/headless Go CLI daemon that syncs a local Obsidian vault with a self-hosted fast-note-sync-service instance over WebSocket — no desktop, no GUI required.
- Bidirectional sync — notes, attachments, settings, and folders
- Live file watching — incremental push on local change via fsnotify, with debounce and echo suppression
- Binary file chunked transfer — resumable multi-chunk upload/download with checkpoints
- Startup sync — full vault reconciliation on connect, then incremental deltas
- Automatic reconnect — on-failure reconnect with configurable restart
- systemd-ready — ships a user-service unit file for unattended background operation
- Config via YAML — environment variable expansion (
${VAR}) supported - Persistent state — atomic JSON state file; crash-safe pending hashes and upload checkpoints
- Go 1.24.5+
- A reachable
fast-note-sync-serviceinstance with a valid API token - Linux (primary target); other POSIX systems may work but are untested
Download the pre-built binary for your platform from the GitHub Releases page. Each release includes platform archives and a checksums.txt for verification.
Linux x86-64 example:
# Replace v0.x.y with the release tag
curl -LO https://github.com/erichll/go-fast-note-sync/releases/download/v0.x.y/go-fast-note-sync_linux_amd64.tar.gz
tar -xzf go-fast-note-sync_linux_amd64.tar.gz
install -m 755 go-fast-note-sync ~/.local/bin/go-fast-note-syncVerify checksum:
sha256sum -c checksums.txt --ignore-missingSupported platforms for binary releases: linux/amd64, linux/arm64, darwin/amd64, darwin/arm64, windows/amd64. The primary supported runtime target is Linux/headless. darwin and windows artifacts are provided as release binaries only and are not guaranteed to work in production.
docker pull ghcr.io/erichll/go-fast-note-sync:latestThe GHCR package is public — no docker login required. Replace latest with a specific tag (e.g. v0.1.0) for pinned deployments.
See deploy/docker/README.md for the full operator quickstart.
git clone https://github.com/erichll/go-fast-note-sync.git
cd go-fast-note-sync
go build -o ~/.local/bin/go-fast-note-sync ./cmdgo-fast-note-sync --help1. Generate default config
go-fast-note-sync init-config
# writes ~/.config/go-fast-note-sync/config.yaml2. Edit config
# ~/.config/go-fast-note-sync/config.yaml
api: https://your-fast-note-sync-service.example.com
api_token: ${FAST_NOTE_TOKEN} # or paste token directly
vault: MyVault
vault_path: /home/you/vault/MyVault
sync_enabled: true
config_sync_enabled: true3. Start the daemon
go-fast-note-sync start
# or with explicit config path:
go-fast-note-sync start --config /path/to/config.yamlCheck sync status (offline)
go-fast-note-sync status
# prints vault, api, sync switches, last sync times, cache counts, and pending countsTrigger a one-shot sync and exit
go-fast-note-sync sync
# connects, waits for the startup sync round to complete, then exits 0
# use --timeout to override the default 60s wait:
go-fast-note-sync sync --timeout 120s| Key | Default | Description |
|---|---|---|
api |
— | Base URL of the sync service |
api_token |
— | Auth token (supports ${ENV_VAR}) |
vault |
— | Vault name on the server |
vault_path |
— | Absolute local path to the vault |
client_type |
GoFastNoteSync |
Client identifier sent to server; override when the token scope restricts c:<value> |
sync_enabled |
true |
Enable note/attachment sync |
config_sync_enabled |
true |
Enable .obsidian settings sync |
offline_delete_sync_enabled |
false |
Push local deletes that happened while offline |
sync_update_delay |
500 |
Debounce delay in ms for local file events |
binary_sync_limit_enabled |
true |
Skip files larger than 128 MiB |
concurrency_control_enabled |
true |
Enable upload slot control |
max_concurrent_uploads |
3 |
Max parallel file uploads |
sync_exclude_folders |
[] |
Vault-relative folder paths to exclude |
sync_exclude_extensions |
[] |
File extensions to exclude |
startup_delay |
0 |
Seconds to wait before first connect |
sync_timeout_seconds |
60 |
Max seconds to wait for a sync round |
state_file |
auto | Override default state file path |
Default state path: ~/.local/share/go-fast-note-sync/state.json
client_type is sent to the server as the ?client= query parameter in the WebSocket URL and as the type field in the ClientInfo handshake message. The default value is GoFastNoteSync.
When creating or editing a token, the console shows a Client restriction field (客户端限制). Whatever you enter there must exactly match the client_type in your config file, or the WebSocket handshake will be rejected with AuthorizationFaild (error code 315).
| Management console "Client restriction" | Config client_type |
Result |
|---|---|---|
GoFastNoteSync |
GoFastNoteSync (default) |
✅ connects |
| (left empty / no restriction) | any value | ✅ connects |
GoFastNoteSync |
MyCustomClient |
❌ rejected |
Recommended: set the Client restriction to GoFastNoteSync when issuing a token for this daemon — no config change needed since that is the default.
If you need a custom identifier, set both sides to the same value:
client_type: MyCustomClientCopy the unit file and enable it:
mkdir -p ~/.config/systemd/user
cp deploy/systemd/go-fast-note-sync.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now go-fast-note-syncThe service reads ~/.config/go-fast-note-sync/config.yaml by default. Optionally drop secrets into ~/.config/go-fast-note-sync/sync.env:
# sync.env
FAST_NOTE_TOKEN=your_token_hereCheck status and logs:
systemctl --user status go-fast-note-sync
journalctl --user -u go-fast-note-sync -fA multi-stage Dockerfile and Compose file are provided in deploy/docker/. See deploy/docker/README.md for the full operator quickstart covering build, configuration, volume layout, and inotify tuning.
# Format
go fmt ./...
# Lint (requires golangci-lint)
golangci-lint run ./...
# Test with race detector
go test -race ./... -coverprofile=coverage.out -covermode=atomic
# Build
go build ./...Coverage gate: total and internal/sync statement coverage must both stay ≥ 80%.
cmd/ CLI entry point (start / status / sync / init-config)
internal/
config/ YAML config loading and defaults
state/ Atomic JSON state persistence
hash/ SHA-256 content and path hashing
local/ Watcher-to-sync event contract
sync/ WebSocket client, startup sync, protocol handlers
watcher/ fsnotify watcher integration
deploy/systemd/ systemd user-service unit
deploy/docker/ Docker multi-stage build and Compose deployment
docs/ Design documents and protocol reference
test/smoke/ Integration smoke test harness
The daemon implements the WebSocket sync protocol used by the obsidian-fast-note-sync plugin:
- Health check:
GET /api/health - WebSocket:
ws(s)://[api]/api/user/sync?lang=&count=&client=&clientName=&clientVersion= - Text frames:
ACTION|JSON - Binary frames: 2-byte prefix (
00) + 36-byte session ID + 4-byte chunk index + data
Sync modules: NoteSync, FileSync, SettingSync, FolderSync — each with a *SyncEnd completion signal.