|
1 | 1 | # FastNodeSync CLI |
2 | 2 |
|
3 | | -Command-line client for bidirectional, near real-time Obsidian note sync with [**Fast Note Sync Service**](https://github.com/haierkeys/fast-note-sync-service). Use it on headless Linux servers (e.g. OpenClaw) with the same sync model as the Obsidian plugin. |
| 3 | +[简体中文](doc/README.zh-CN.md) | [English](README.md) | [日本語](doc/README.ja.md) | [한국어](doc/README.ko.md) | [繁體中文](doc/README.zh-TW.md) |
4 | 4 |
|
5 | | -## Documentation |
| 5 | +--- |
6 | 6 |
|
7 | | -**English (default)** · [简体中文](doc/README.zh-CN.md) · [日本語](doc/README.ja.md) · [한국어](doc/README.ko.md) · [繁體中文](doc/README.zh-TW.md) |
| 7 | +## Give this to OpenClaw / any AI agent |
8 | 8 |
|
9 | | -Full guide (configuration, systemd, CLI, troubleshooting): **[doc/README.md](doc/README.md)** |
| 9 | +**Copy the line below** into **OpenClaw**, **Cursor**, **Copilot**, or any coding agent. The agent can clone this repo, install dependencies, edit `config.yaml`, and set up **systemd** using the instructions here — you only need to provide your **Fast Note Sync Service URL** and **API token** when asked. |
10 | 10 |
|
11 | | -## Quick start |
| 11 | +``` |
| 12 | +https://github.com/Go1c/FastNodeSync-CLI |
| 13 | +``` |
| 14 | + |
| 15 | +**Example prompt:** *“Clone this repo on my Linux server, install requirements, create a working `config.yaml` from my FNS credentials, and install a systemd unit so it survives reboot.”* |
| 16 | + |
| 17 | +--- |
| 18 | + |
| 19 | +A command-line client for bidirectional, near real-time Obsidian note sync. It works with [Fast Note Sync Service](https://github.com/haierkeys/fast-note-sync-service) and is intended for headless Linux servers (e.g. OpenClaw), offering sync capabilities comparable to the Obsidian desktop/mobile plugin. |
| 20 | + |
| 21 | +## Features |
| 22 | + |
| 23 | +- **Bidirectional real-time sync**: local changes are pushed to the server; remote changes (from Obsidian and other clients) are pulled to the local vault |
| 24 | +- **Full content**: `.md` notes, attachments (images, HTML, Canvas, etc.), and `.obsidian/` configuration |
| 25 | +- **Auto-reconnect**: exponential backoff when the connection drops; incremental catch-up after recovery |
| 26 | +- **Anti feedback loop**: writes from the server do not immediately re-trigger uploads |
| 27 | +- **Incremental sync**: uses `lastSyncTime` to sync only what changed |
| 28 | + |
| 29 | +## Project layout |
| 30 | + |
| 31 | +``` |
| 32 | +FastNodeSync-CLI/ |
| 33 | +├── doc/ # Translations (简体中文, 日本語, 한국어, 繁體中文) |
| 34 | +├── fns_cli/ # Python package |
| 35 | +├── tests/ # Smoke tests (unittest) |
| 36 | +├── .github/workflows/ # GitHub Actions CI |
| 37 | +├── config.yaml # Example configuration |
| 38 | +└── requirements.txt # Dependencies |
| 39 | +``` |
| 40 | + |
| 41 | +## Development & CI |
| 42 | + |
| 43 | +Run smoke tests locally (stdlib only): |
| 44 | + |
| 45 | +```bash |
| 46 | +# From the repository root |
| 47 | +export PYTHONPATH=. # Windows: set PYTHONPATH=. |
| 48 | +python -m unittest discover -s tests -v |
| 49 | +``` |
| 50 | + |
| 51 | +On push or PR to `main`, GitHub Actions installs dependencies, runs `compileall`, `python -m fns_cli.main --help`, and the unittest suite. |
| 52 | + |
| 53 | +## Deployment |
| 54 | + |
| 55 | +### 1. Requirements |
| 56 | + |
| 57 | +- Python 3.10+ |
| 58 | + |
| 59 | +### 2. Install dependencies |
12 | 60 |
|
13 | 61 | ```bash |
14 | 62 | pip install -r requirements.txt |
15 | | -cp config.yaml config.local.yaml # edit api / token / vault |
16 | | -python -m fns_cli.main run -c config.local.yaml |
17 | 63 | ``` |
18 | 64 |
|
19 | | -## Related |
| 65 | +### 3. Configuration |
| 66 | + |
| 67 | +Edit `config.yaml`: |
| 68 | + |
| 69 | +```yaml |
| 70 | +server: |
| 71 | + api: "https://your-server-address" # Fast Note Sync Service base URL |
| 72 | + token: "your_api_token" # API token from the admin panel |
| 73 | + vault: "notes" # Vault name; must match the Obsidian plugin |
| 74 | + |
| 75 | +sync: |
| 76 | + watch_path: "./vault" # Local vault path (relative or absolute) |
| 77 | + sync_notes: true |
| 78 | + sync_files: true |
| 79 | + sync_config: true |
| 80 | + exclude_patterns: |
| 81 | + - ".git/**" |
| 82 | + - ".trash/**" |
| 83 | + - "*.tmp" |
| 84 | + file_chunk_size: 524288 |
| 85 | + |
| 86 | +client: |
| 87 | + reconnect_max_retries: 15 |
| 88 | + reconnect_base_delay: 3 |
| 89 | + heartbeat_interval: 30 |
| 90 | + |
| 91 | +logging: |
| 92 | + level: "INFO" |
| 93 | + file: "" |
| 94 | +``` |
| 95 | +
|
| 96 | +**How to obtain a token** |
| 97 | +
|
| 98 | +1. Open the Fast Note Sync Service web UI (e.g. `https://your-server-address`) |
| 99 | +2. Sign in |
| 100 | +3. Click **"Copy API Config"** |
| 101 | +4. Copy `api`, `apiToken`, and `vault` from the JSON into `config.yaml` |
| 102 | + |
| 103 | +Optional environment variables (override when not set in the file): |
| 104 | + |
| 105 | +```bash |
| 106 | +export FNS_API="https://your-server-address" |
| 107 | +export FNS_TOKEN="your_api_token" |
| 108 | +``` |
| 109 | + |
| 110 | +### 4. Run |
| 111 | + |
| 112 | +```bash |
| 113 | +python -m fns_cli.main run -c config.yaml |
| 114 | +``` |
| 115 | + |
| 116 | +#### Quick background (not for production) |
| 117 | + |
| 118 | +```bash |
| 119 | +nohup python -m fns_cli.main run -c config.yaml > fns.log 2>&1 & |
| 120 | +screen -dmS fns python -m fns_cli.main run -c config.yaml |
| 121 | +``` |
| 122 | + |
| 123 | +--- |
| 124 | + |
| 125 | +## Daemon & boot (systemd, recommended) |
| 126 | + |
| 127 | +On Linux, **systemd** gives you **auto-restart on crash**, **start on boot**, and **centralized logs** via `journalctl`. |
| 128 | + |
| 129 | +Assume: |
| 130 | + |
| 131 | +- Install path: `/opt/FastNodeSync-CLI` |
| 132 | +- Config: `/opt/FastNodeSync-CLI/config.yaml` |
| 133 | +- Unix user: `your_user` (do **not** run as root) |
| 134 | +- Python: `/usr/bin/python3` (verify with `which python3`) |
| 135 | + |
| 136 | +Create a unit file: |
| 137 | + |
| 138 | +```bash |
| 139 | +sudo nano /etc/systemd/system/fns-cli.service |
| 140 | +``` |
| 141 | + |
| 142 | +Example: |
| 143 | + |
| 144 | +```ini |
| 145 | +[Unit] |
| 146 | +Description=FastNodeSync CLI - Obsidian vault sync |
| 147 | +Documentation=https://github.com/Go1c/FastNodeSync-CLI |
| 148 | +After=network-online.target |
| 149 | +Wants=network-online.target |
| 150 | +
|
| 151 | +[Service] |
| 152 | +Type=simple |
| 153 | +User=your_user |
| 154 | +Group=your_user |
| 155 | +WorkingDirectory=/opt/FastNodeSync-CLI |
| 156 | +Environment=PYTHONUNBUFFERED=1 |
| 157 | +# Optional: load secrets from a file (chmod 600) |
| 158 | +# EnvironmentFile=/opt/FastNodeSync-CLI/.env |
| 159 | +ExecStart=/usr/bin/python3 -m fns_cli.main run -c /opt/FastNodeSync-CLI/config.yaml |
| 160 | +Restart=always |
| 161 | +RestartSec=10 |
| 162 | +
|
| 163 | +[Install] |
| 164 | +WantedBy=multi-user.target |
| 165 | +``` |
| 166 | + |
| 167 | +Enable and start: |
| 168 | + |
| 169 | +```bash |
| 170 | +sudo systemctl daemon-reload |
| 171 | +sudo systemctl enable fns-cli |
| 172 | +sudo systemctl start fns-cli |
| 173 | +sudo systemctl status fns-cli |
| 174 | +``` |
| 175 | + |
| 176 | +Useful commands: |
| 177 | + |
| 178 | +```bash |
| 179 | +sudo systemctl stop fns-cli |
| 180 | +sudo systemctl restart fns-cli |
| 181 | +journalctl -u fns-cli -f |
| 182 | +journalctl -u fns-cli --since today |
| 183 | +``` |
| 184 | + |
| 185 | +**Notes** |
| 186 | + |
| 187 | +- `enable` registers the service for **automatic start after reboot**. `After=network-online.target` reduces races where the process starts before the network is ready. |
| 188 | +- Ensure `your_user` can read/write `watch_path` (the vault directory). |
| 189 | +- For deploying the upstream server, see [fast-note-sync-service](https://github.com/haierkeys/fast-note-sync-service) (Docker, install script, etc.). |
| 190 | + |
| 191 | +--- |
| 192 | + |
| 193 | +## CLI commands |
| 194 | + |
| 195 | +| Command | Description | |
| 196 | +|---------|-------------| |
| 197 | +| `run` | Long-running: initial sync + file watcher + receive remote updates | |
| 198 | +| `sync` | One-shot full bidirectional sync, then exit | |
| 199 | +| `pull` | Pull remote changes only, then exit | |
| 200 | +| `push` | Push all local files, then exit | |
| 201 | +| `status` | Show configuration and sync state | |
| 202 | + |
| 203 | +All commands accept `-c` / `--config` (default: `config.yaml`). |
| 204 | + |
| 205 | +```bash |
| 206 | +python -m fns_cli.main run -c config.yaml |
| 207 | +python -m fns_cli.main sync -c config.yaml |
| 208 | +python -m fns_cli.main pull -c config.yaml |
| 209 | +python -m fns_cli.main push -c config.yaml |
| 210 | +python -m fns_cli.main status -c config.yaml |
| 211 | +``` |
| 212 | + |
| 213 | +## Sync behavior |
| 214 | + |
| 215 | +### `run` flow |
| 216 | + |
| 217 | +``` |
| 218 | +1. WebSocket connect → authenticate |
| 219 | +2. Incremental pull (NoteSync + FileSync) |
| 220 | +3. Start watchdog on the local vault |
| 221 | +4. Continuous bidirectional sync (remote → local, local → server → other clients) |
| 222 | +5. On disconnect → reconnect with backoff → incremental catch-up |
| 223 | +``` |
| 224 | +
|
| 225 | +### State file |
| 226 | +
|
| 227 | +Progress is stored in `vault/.fns_state.json` (managed automatically). After a restart, sync resumes incrementally from the last checkpoint. |
| 228 | +
|
| 229 | +### Caveats |
| 230 | +
|
| 231 | +- The `vault` name must match the Obsidian plugin setting. |
| 232 | +- First `run` or `pull` may download the full vault; later runs are incremental. |
| 233 | +- Concurrent edits on multiple devices: last write to the server wins (server-side conflict handling). |
| 234 | +- `.fns_state.json` is not uploaded to the server. |
| 235 | +
|
| 236 | +## Related projects |
20 | 237 |
|
21 | | -- [Fast Note Sync Service](https://github.com/haierkeys/fast-note-sync-service) — server |
| 238 | +- [Fast Note Sync Service](https://github.com/haierkeys/fast-note-sync-service) — backend |
22 | 239 | - [obsidian-fast-note-sync](https://github.com/haierkeys/obsidian-fast-note-sync) — Obsidian plugin |
0 commit comments