Update docs
This commit is contained in:
parent
38810579ec
commit
00d2061627
20 changed files with 1149 additions and 569 deletions
5
.github/workflows/deploy-docs.yml
vendored
5
.github/workflows/deploy-docs.yml
vendored
|
|
@ -42,6 +42,11 @@ jobs:
|
||||||
cd docs
|
cd docs
|
||||||
npm ci
|
npm ci
|
||||||
|
|
||||||
|
- name: Check formatting
|
||||||
|
run: |
|
||||||
|
cd docs
|
||||||
|
npm run format:check
|
||||||
|
|
||||||
- name: Build documentation
|
- name: Build documentation
|
||||||
run: |
|
run: |
|
||||||
cd docs
|
cd docs
|
||||||
|
|
|
||||||
4
docs/.prettierignore
Normal file
4
docs/.prettierignore
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
node_modules/
|
||||||
|
.vitepress/dist/
|
||||||
|
.vitepress/cache/
|
||||||
|
package-lock.json
|
||||||
19
docs/.prettierrc
Normal file
19
docs/.prettierrc
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"printWidth": 120,
|
||||||
|
"tabWidth": 4,
|
||||||
|
"useTabs": true,
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": false,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"endOfLine": "lf",
|
||||||
|
"proseWrap": "preserve",
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": "*.md",
|
||||||
|
"options": {
|
||||||
|
"proseWrap": "preserve",
|
||||||
|
"printWidth": 120
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -1,62 +1,59 @@
|
||||||
import { defineConfig } from 'vitepress'
|
import { defineConfig } from "vitepress"
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
title: 'VaultLink',
|
title: "VaultLink",
|
||||||
description: 'Self-hosted real-time synchronization for Obsidian',
|
description: "Self-hosted real-time synchronization for Obsidian",
|
||||||
base: '/vault-link/',
|
base: "/vault-link/",
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
logo: '/logo.svg',
|
logo: "/logo.svg",
|
||||||
nav: [
|
nav: [
|
||||||
{ text: 'Home', link: '/' },
|
{ text: "Home", link: "/" },
|
||||||
{ text: 'Guide', link: '/guide/getting-started' },
|
{ text: "Guide", link: "/guide/getting-started" },
|
||||||
{ text: 'Architecture', link: '/architecture/' },
|
{ text: "Architecture", link: "/architecture/" },
|
||||||
{ text: 'GitHub', link: 'https://github.com/schmelczer/vault-link' }
|
{ text: "GitHub", link: "https://github.com/schmelczer/vault-link" }
|
||||||
],
|
],
|
||||||
sidebar: [
|
sidebar: [
|
||||||
{
|
{
|
||||||
text: 'Introduction',
|
text: "Introduction",
|
||||||
items: [
|
items: [
|
||||||
{ text: 'What is VaultLink?', link: '/guide/what-is-vaultlink' },
|
{ text: "What is VaultLink?", link: "/guide/what-is-vaultlink" },
|
||||||
{ text: 'Getting Started', link: '/guide/getting-started' }
|
{ text: "Getting Started", link: "/guide/getting-started" },
|
||||||
]
|
{ text: "Comparison with Alternatives", link: "/guide/alternatives" }
|
||||||
},
|
]
|
||||||
{
|
},
|
||||||
text: 'Setup',
|
{
|
||||||
items: [
|
text: "Setup",
|
||||||
{ text: 'Server Setup', link: '/guide/server-setup' },
|
items: [
|
||||||
{ text: 'Obsidian Plugin', link: '/guide/obsidian-plugin' },
|
{ text: "Server Setup", link: "/guide/server-setup" },
|
||||||
{ text: 'CLI Client', link: '/guide/cli-client' }
|
{ text: "Obsidian Plugin", link: "/guide/obsidian-plugin" },
|
||||||
]
|
{ text: "CLI Client", link: "/guide/cli-client" }
|
||||||
},
|
]
|
||||||
{
|
},
|
||||||
text: 'Configuration',
|
{
|
||||||
items: [
|
text: "Configuration",
|
||||||
{ text: 'Server Configuration', link: '/config/server' },
|
items: [
|
||||||
{ text: 'Authentication', link: '/config/authentication' },
|
{ text: "Server Configuration", link: "/config/server" },
|
||||||
{ text: 'Advanced Options', link: '/config/advanced' }
|
{ text: "Authentication", link: "/config/authentication" },
|
||||||
]
|
{ text: "Advanced Options", link: "/config/advanced" }
|
||||||
},
|
]
|
||||||
{
|
},
|
||||||
text: 'Architecture',
|
{
|
||||||
items: [
|
text: "Architecture",
|
||||||
{ text: 'Overview', link: '/architecture/' },
|
items: [
|
||||||
{ text: 'Sync Algorithm', link: '/architecture/sync-algorithm' },
|
{ text: "Overview", link: "/architecture/" },
|
||||||
{ text: 'Data Flow', link: '/architecture/data-flow' }
|
{ text: "Sync Algorithm", link: "/architecture/sync-algorithm" },
|
||||||
]
|
{ text: "Data Flow", link: "/architecture/data-flow" }
|
||||||
}
|
]
|
||||||
],
|
}
|
||||||
socialLinks: [
|
],
|
||||||
{ icon: 'github', link: 'https://github.com/schmelczer/vault-link' }
|
socialLinks: [{ icon: "github", link: "https://github.com/schmelczer/vault-link" }],
|
||||||
],
|
footer: {
|
||||||
footer: {
|
message: "Released under the MIT License.",
|
||||||
message: 'Released under the MIT License.',
|
copyright: "Copyright © 2024-present Andras Schmelczer"
|
||||||
copyright: 'Copyright © 2024-present Andras Schmelczer'
|
},
|
||||||
},
|
search: {
|
||||||
search: {
|
provider: "local"
|
||||||
provider: 'local'
|
}
|
||||||
}
|
},
|
||||||
},
|
head: [["link", { rel: "icon", type: "image/svg+xml", href: "/vault-link/logo.svg" }]]
|
||||||
head: [
|
|
||||||
['link', { rel: 'icon', type: 'image/svg+xml', href: '/vault-link/logo.svg' }]
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,20 @@ Preview the built site:
|
||||||
npm run preview
|
npm run preview
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Format
|
||||||
|
|
||||||
|
Format all markdown and TypeScript files:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run format
|
||||||
|
```
|
||||||
|
|
||||||
|
Check formatting without making changes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run format:check
|
||||||
|
```
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
The documentation is automatically deployed to GitHub Pages when changes are pushed to the `main` branch.
|
The documentation is automatically deployed to GitHub Pages when changes are pushed to the `main` branch.
|
||||||
|
|
@ -81,6 +95,7 @@ docs/
|
||||||
### Markdown Features
|
### Markdown Features
|
||||||
|
|
||||||
VitePress supports:
|
VitePress supports:
|
||||||
|
|
||||||
- GitHub Flavored Markdown
|
- GitHub Flavored Markdown
|
||||||
- Custom containers (tip, warning, danger)
|
- Custom containers (tip, warning, danger)
|
||||||
- Code syntax highlighting
|
- Code syntax highlighting
|
||||||
|
|
@ -112,7 +127,7 @@ npm install
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
server:
|
server:
|
||||||
port: 3000
|
port: 3000
|
||||||
```
|
```
|
||||||
````
|
````
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ sequenceDiagram
|
||||||
```
|
```
|
||||||
|
|
||||||
**Steps**:
|
**Steps**:
|
||||||
|
|
||||||
1. Client initiates WebSocket connection to server
|
1. Client initiates WebSocket connection to server
|
||||||
2. Server accepts connection
|
2. Server accepts connection
|
||||||
3. Client sends authentication message with token and vault name
|
3. Client sends authentication message with token and vault name
|
||||||
|
|
@ -72,6 +73,7 @@ sequenceDiagram
|
||||||
```
|
```
|
||||||
|
|
||||||
**Process**:
|
**Process**:
|
||||||
|
|
||||||
1. Client scans local filesystem
|
1. Client scans local filesystem
|
||||||
2. Client requests file list from server
|
2. Client requests file list from server
|
||||||
3. Server queries database and returns metadata
|
3. Server queries database and returns metadata
|
||||||
|
|
@ -106,6 +108,7 @@ sequenceDiagram
|
||||||
```
|
```
|
||||||
|
|
||||||
**Flow**:
|
**Flow**:
|
||||||
|
|
||||||
1. Filesystem watcher detects local change
|
1. Filesystem watcher detects local change
|
||||||
2. Client reads file content
|
2. Client reads file content
|
||||||
3. Client uploads file via WebSocket
|
3. Client uploads file via WebSocket
|
||||||
|
|
@ -325,6 +328,7 @@ CREATE TABLE cursors (
|
||||||
### Queries
|
### Queries
|
||||||
|
|
||||||
**Get files since version**:
|
**Get files since version**:
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
SELECT * FROM documents
|
SELECT * FROM documents
|
||||||
WHERE version > ? AND deleted = FALSE
|
WHERE version > ? AND deleted = FALSE
|
||||||
|
|
@ -332,6 +336,7 @@ ORDER BY version ASC;
|
||||||
```
|
```
|
||||||
|
|
||||||
**Store new version**:
|
**Store new version**:
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
INSERT INTO versions (document_id, version, content, created_at)
|
INSERT INTO versions (document_id, version, content, created_at)
|
||||||
VALUES (?, ?, ?, ?);
|
VALUES (?, ?, ?, ?);
|
||||||
|
|
@ -342,6 +347,7 @@ WHERE id = ?;
|
||||||
```
|
```
|
||||||
|
|
||||||
**Update cursor**:
|
**Update cursor**:
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
INSERT OR REPLACE INTO cursors (client_id, last_version, last_updated)
|
INSERT OR REPLACE INTO cursors (client_id, last_version, last_updated)
|
||||||
VALUES (?, ?, ?);
|
VALUES (?, ?, ?);
|
||||||
|
|
@ -352,87 +358,96 @@ VALUES (?, ?, ?);
|
||||||
### Client → Server Messages
|
### Client → Server Messages
|
||||||
|
|
||||||
**Upload File**:
|
**Upload File**:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "upload_file",
|
"type": "upload_file",
|
||||||
"path": "notes/example.md",
|
"path": "notes/example.md",
|
||||||
"content": "File content here...",
|
"content": "File content here...",
|
||||||
"base_version": 10,
|
"base_version": 10,
|
||||||
"timestamp": "2024-01-01T12:00:00Z"
|
"timestamp": "2024-01-01T12:00:00Z"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Download File**:
|
**Download File**:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "download_file",
|
"type": "download_file",
|
||||||
"path": "notes/example.md"
|
"path": "notes/example.md"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Delete File**:
|
**Delete File**:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "delete_file",
|
"type": "delete_file",
|
||||||
"path": "notes/old.md"
|
"path": "notes/old.md"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**List Files**:
|
**List Files**:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "list_files",
|
"type": "list_files",
|
||||||
"since_version": 0
|
"since_version": 0
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Server → Client Messages
|
### Server → Client Messages
|
||||||
|
|
||||||
**File Updated**:
|
**File Updated**:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "file_updated",
|
"type": "file_updated",
|
||||||
"path": "notes/example.md",
|
"path": "notes/example.md",
|
||||||
"version": 11,
|
"version": 11,
|
||||||
"size": 1024,
|
"size": 1024,
|
||||||
"hash": "abc123..."
|
"hash": "abc123..."
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**File Content**:
|
**File Content**:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "file_content",
|
"type": "file_content",
|
||||||
"path": "notes/example.md",
|
"path": "notes/example.md",
|
||||||
"content": "Updated content...",
|
"content": "Updated content...",
|
||||||
"version": 11
|
"version": 11
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**File Deleted**:
|
**File Deleted**:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "file_deleted",
|
"type": "file_deleted",
|
||||||
"path": "notes/old.md",
|
"path": "notes/old.md",
|
||||||
"version": 12
|
"version": 12
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Sync Complete**:
|
**Sync Complete**:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "sync_complete",
|
"type": "sync_complete",
|
||||||
"total_files": 150,
|
"total_files": 150,
|
||||||
"current_version": 200
|
"current_version": 200
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Error**:
|
**Error**:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "error",
|
"type": "error",
|
||||||
"message": "File too large",
|
"message": "File too large",
|
||||||
"code": "FILE_TOO_LARGE"
|
"code": "FILE_TOO_LARGE"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -441,18 +456,21 @@ VALUES (?, ?, ?);
|
||||||
### Client-Side Errors
|
### Client-Side Errors
|
||||||
|
|
||||||
**Network failure**:
|
**Network failure**:
|
||||||
|
|
||||||
1. Detect WebSocket disconnect
|
1. Detect WebSocket disconnect
|
||||||
2. Queue pending operations
|
2. Queue pending operations
|
||||||
3. Retry connection with exponential backoff
|
3. Retry connection with exponential backoff
|
||||||
4. Replay queued operations on reconnect
|
4. Replay queued operations on reconnect
|
||||||
|
|
||||||
**File read error**:
|
**File read error**:
|
||||||
|
|
||||||
1. Log error
|
1. Log error
|
||||||
2. Skip file
|
2. Skip file
|
||||||
3. Continue with other files
|
3. Continue with other files
|
||||||
4. Report to user
|
4. Report to user
|
||||||
|
|
||||||
**Write conflict**:
|
**Write conflict**:
|
||||||
|
|
||||||
1. Receive updated version from server
|
1. Receive updated version from server
|
||||||
2. Apply OT merge locally
|
2. Apply OT merge locally
|
||||||
3. Overwrite local file
|
3. Overwrite local file
|
||||||
|
|
@ -461,16 +479,19 @@ VALUES (?, ?, ?);
|
||||||
### Server-Side Errors
|
### Server-Side Errors
|
||||||
|
|
||||||
**Database error**:
|
**Database error**:
|
||||||
|
|
||||||
1. Log error
|
1. Log error
|
||||||
2. Return error to client
|
2. Return error to client
|
||||||
3. Client retries operation
|
3. Client retries operation
|
||||||
|
|
||||||
**Invalid operation**:
|
**Invalid operation**:
|
||||||
|
|
||||||
1. Validate message format
|
1. Validate message format
|
||||||
2. Return specific error code
|
2. Return specific error code
|
||||||
3. Client handles error appropriately
|
3. Client handles error appropriately
|
||||||
|
|
||||||
**Authentication failure**:
|
**Authentication failure**:
|
||||||
|
|
||||||
1. Reject connection
|
1. Reject connection
|
||||||
2. Send auth error
|
2. Send auth error
|
||||||
3. Client prompts for new credentials
|
3. Client prompts for new credentials
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ VaultLink is built as a distributed system with a central sync server and multip
|
||||||
The central authority for synchronization, written in Rust using Axum framework.
|
The central authority for synchronization, written in Rust using Axum framework.
|
||||||
|
|
||||||
**Responsibilities**:
|
**Responsibilities**:
|
||||||
|
|
||||||
- Accept WebSocket connections from clients
|
- Accept WebSocket connections from clients
|
||||||
- Authenticate users via token-based auth
|
- Authenticate users via token-based auth
|
||||||
- Store document versions in SQLite
|
- Store document versions in SQLite
|
||||||
|
|
@ -51,6 +52,7 @@ The central authority for synchronization, written in Rust using Axum framework.
|
||||||
- Manage vault access control
|
- Manage vault access control
|
||||||
|
|
||||||
**Technology**:
|
**Technology**:
|
||||||
|
|
||||||
- **Language**: Rust 1.89+
|
- **Language**: Rust 1.89+
|
||||||
- **Framework**: Axum (async web framework)
|
- **Framework**: Axum (async web framework)
|
||||||
- **Database**: SQLite with SQLx
|
- **Database**: SQLite with SQLx
|
||||||
|
|
@ -62,6 +64,7 @@ The central authority for synchronization, written in Rust using Axum framework.
|
||||||
TypeScript library providing core synchronization logic, used by both the Obsidian plugin and CLI client.
|
TypeScript library providing core synchronization logic, used by both the Obsidian plugin and CLI client.
|
||||||
|
|
||||||
**Responsibilities**:
|
**Responsibilities**:
|
||||||
|
|
||||||
- Manage WebSocket connection to server
|
- Manage WebSocket connection to server
|
||||||
- Watch local filesystem for changes
|
- Watch local filesystem for changes
|
||||||
- Upload and download files
|
- Upload and download files
|
||||||
|
|
@ -70,6 +73,7 @@ TypeScript library providing core synchronization logic, used by both the Obsidi
|
||||||
- Maintain sync metadata
|
- Maintain sync metadata
|
||||||
|
|
||||||
**Technology**:
|
**Technology**:
|
||||||
|
|
||||||
- **Language**: TypeScript
|
- **Language**: TypeScript
|
||||||
- **Build**: Webpack
|
- **Build**: Webpack
|
||||||
- **Protocol**: WebSocket client
|
- **Protocol**: WebSocket client
|
||||||
|
|
@ -80,12 +84,14 @@ TypeScript library providing core synchronization logic, used by both the Obsidi
|
||||||
Integration layer between sync client and Obsidian.
|
Integration layer between sync client and Obsidian.
|
||||||
|
|
||||||
**Responsibilities**:
|
**Responsibilities**:
|
||||||
|
|
||||||
- Provide UI for configuration
|
- Provide UI for configuration
|
||||||
- Bridge sync client with Obsidian's file system API
|
- Bridge sync client with Obsidian's file system API
|
||||||
- Handle Obsidian lifecycle events
|
- Handle Obsidian lifecycle events
|
||||||
- Display sync status to users
|
- Display sync status to users
|
||||||
|
|
||||||
**Technology**:
|
**Technology**:
|
||||||
|
|
||||||
- **Platform**: Obsidian Plugin API
|
- **Platform**: Obsidian Plugin API
|
||||||
- **Core**: sync-client library
|
- **Core**: sync-client library
|
||||||
- **UI**: Obsidian settings UI
|
- **UI**: Obsidian settings UI
|
||||||
|
|
@ -95,12 +101,14 @@ Integration layer between sync client and Obsidian.
|
||||||
Standalone executable for syncing vaults without Obsidian.
|
Standalone executable for syncing vaults without Obsidian.
|
||||||
|
|
||||||
**Responsibilities**:
|
**Responsibilities**:
|
||||||
|
|
||||||
- Command-line interface
|
- Command-line interface
|
||||||
- File system access via Node.js
|
- File system access via Node.js
|
||||||
- Daemon mode for continuous sync
|
- Daemon mode for continuous sync
|
||||||
- Health check endpoint for monitoring
|
- Health check endpoint for monitoring
|
||||||
|
|
||||||
**Technology**:
|
**Technology**:
|
||||||
|
|
||||||
- **Language**: TypeScript
|
- **Language**: TypeScript
|
||||||
- **Runtime**: Node.js
|
- **Runtime**: Node.js
|
||||||
- **CLI**: Commander.js
|
- **CLI**: Commander.js
|
||||||
|
|
@ -190,6 +198,7 @@ databases/
|
||||||
```
|
```
|
||||||
|
|
||||||
**Database Schema** (simplified):
|
**Database Schema** (simplified):
|
||||||
|
|
||||||
- **documents**: File metadata (path, size, modified time)
|
- **documents**: File metadata (path, size, modified time)
|
||||||
- **versions**: Document content with version history
|
- **versions**: Document content with version history
|
||||||
- **cursors**: Client sync state
|
- **cursors**: Client sync state
|
||||||
|
|
@ -213,6 +222,7 @@ The `.vaultlink` directory tracks which files have been synced and their version
|
||||||
Client-server communication uses JSON messages over WebSocket.
|
Client-server communication uses JSON messages over WebSocket.
|
||||||
|
|
||||||
**Message Types**:
|
**Message Types**:
|
||||||
|
|
||||||
- `upload_file`: Client → Server (file upload)
|
- `upload_file`: Client → Server (file upload)
|
||||||
- `download_file`: Client → Server (request file)
|
- `download_file`: Client → Server (request file)
|
||||||
- `file_updated`: Server → Client (file changed notification)
|
- `file_updated`: Server → Client (file changed notification)
|
||||||
|
|
@ -253,11 +263,13 @@ Token-based authentication on connection:
|
||||||
### Scaling Approaches
|
### Scaling Approaches
|
||||||
|
|
||||||
**Vertical Scaling**:
|
**Vertical Scaling**:
|
||||||
|
|
||||||
- Increase server resources (CPU, RAM, storage)
|
- Increase server resources (CPU, RAM, storage)
|
||||||
- Optimize database queries and indexing
|
- Optimize database queries and indexing
|
||||||
- Tune connection limits
|
- Tune connection limits
|
||||||
|
|
||||||
**Horizontal Scaling** (future):
|
**Horizontal Scaling** (future):
|
||||||
|
|
||||||
- Separate vault servers (vault sharding)
|
- Separate vault servers (vault sharding)
|
||||||
- Load balancer with sticky sessions
|
- Load balancer with sticky sessions
|
||||||
- Shared storage layer for SQLite databases
|
- Shared storage layer for SQLite databases
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,13 @@ Operational transformation is a technique for managing concurrent edits to the s
|
||||||
### Why OT?
|
### Why OT?
|
||||||
|
|
||||||
Traditional conflict resolution approaches:
|
Traditional conflict resolution approaches:
|
||||||
|
|
||||||
- **Last write wins**: Loses data, frustrating for users
|
- **Last write wins**: Loses data, frustrating for users
|
||||||
- **Manual merging**: Interrupts workflow, requires user intervention
|
- **Manual merging**: Interrupts workflow, requires user intervention
|
||||||
- **Version branching**: Complex, not suitable for real-time sync
|
- **Version branching**: Complex, not suitable for real-time sync
|
||||||
|
|
||||||
Operational transformation:
|
Operational transformation:
|
||||||
|
|
||||||
- **Automatic**: No user intervention required
|
- **Automatic**: No user intervention required
|
||||||
- **Preserves all edits**: No data loss
|
- **Preserves all edits**: No data loss
|
||||||
- **Real-time**: Changes appear immediately
|
- **Real-time**: Changes appear immediately
|
||||||
|
|
@ -23,6 +25,39 @@ Operational transformation:
|
||||||
|
|
||||||
VaultLink uses the [`reconcile-text`](https://crates.io/crates/reconcile-text) Rust library for operational transformation on text documents.
|
VaultLink uses the [`reconcile-text`](https://crates.io/crates/reconcile-text) Rust library for operational transformation on text documents.
|
||||||
|
|
||||||
|
### Why reconcile-text over CRDTs?
|
||||||
|
|
||||||
|
VaultLink faces a **differential synchronization** challenge: users edit Obsidian vaults with various editors (Obsidian desktop, Obsidian mobile, Vim, VS Code, or any text editor), often while offline. This means we only observe the **final state** of each document after editing, not the individual keystrokes or operations that produced it.
|
||||||
|
|
||||||
|
**The fundamental problem**:
|
||||||
|
|
||||||
|
- **CRDTs and traditional OT** require capturing every individual operation (each character insertion, deletion, cursor movement)
|
||||||
|
- **VaultLink's reality**: Users edit files with arbitrary tools, sync happens after the fact
|
||||||
|
- **What we know**: Parent version and two modified versions
|
||||||
|
- **What we don't know**: The sequence of operations that created those modifications
|
||||||
|
|
||||||
|
**Why reconcile-text wins for this use case**:
|
||||||
|
|
||||||
|
1. **Works with end states only**: reconcile-text performs conflict-free 3-way merging given just parent, left, and right versions—no operation history needed
|
||||||
|
|
||||||
|
2. **Editor-agnostic**: Users can edit with any tool without requiring VaultLink-specific plugins or operation tracking
|
||||||
|
|
||||||
|
3. **Offline-first**: Edits made while disconnected are merged cleanly when sync resumes, because we're diffing final states rather than replaying operations
|
||||||
|
|
||||||
|
4. **No conflict markers**: Unlike Git merge, produces clean merged output without `<<<<<<<` markers that interrupt note-taking flow
|
||||||
|
|
||||||
|
5. **Human text forgiveness**: For knowledge bases and documentation, a slightly imperfect merge (e.g., minor word order issues) is vastly preferable to manual conflict resolution
|
||||||
|
|
||||||
|
6. **Simpler infrastructure**: No need for complex operation capture, transformation logs, or tombstone management that CRDTs require
|
||||||
|
|
||||||
|
**The tradeoff**:
|
||||||
|
|
||||||
|
CRDTs excel when you control the entire editing infrastructure and can capture every operation. reconcile-text excels when you're synchronizing independently-edited files—exactly VaultLink's scenario. The merge quality depends on Myers' diff algorithm rather than operation history, which is the correct tradeoff for differential sync.
|
||||||
|
|
||||||
|
For note-taking workflows where users value editor freedom and offline editing, this approach provides superior user experience compared to either CRDTs (which would require operation tracking) or Git-style merging (which requires manual conflict resolution).
|
||||||
|
|
||||||
|
[Learn more about reconcile-text →](https://schmelczer.dev/reconcile)
|
||||||
|
|
||||||
### How It Works
|
### How It Works
|
||||||
|
|
||||||
Given a base document and two sets of changes, OT produces a merged result that includes both changes.
|
Given a base document and two sets of changes, OT produces a merged result that includes both changes.
|
||||||
|
|
@ -41,6 +76,7 @@ OT result: "Hello beautiful world!" (both changes applied)
|
||||||
### Operation Types
|
### Operation Types
|
||||||
|
|
||||||
The algorithm handles these operations:
|
The algorithm handles these operations:
|
||||||
|
|
||||||
- **Insert**: Add text at position
|
- **Insert**: Add text at position
|
||||||
- **Delete**: Remove text from position
|
- **Delete**: Remove text from position
|
||||||
- **Retain**: Keep existing text unchanged
|
- **Retain**: Keep existing text unchanged
|
||||||
|
|
@ -62,10 +98,12 @@ VaultLink maintains sync state to track which changes have been applied.
|
||||||
### Version Vectors
|
### Version Vectors
|
||||||
|
|
||||||
Each document has a version tracked by:
|
Each document has a version tracked by:
|
||||||
|
|
||||||
- **Server version**: Incremented on each change
|
- **Server version**: Incremented on each change
|
||||||
- **Client cursors**: Track which version each client has seen
|
- **Client cursors**: Track which version each client has seen
|
||||||
|
|
||||||
This enables:
|
This enables:
|
||||||
|
|
||||||
- Efficient syncing (only send changes since last sync)
|
- Efficient syncing (only send changes since last sync)
|
||||||
- Conflict detection (concurrent edits to same version)
|
- Conflict detection (concurrent edits to same version)
|
||||||
- Ordering of operations
|
- Ordering of operations
|
||||||
|
|
@ -84,6 +122,7 @@ struct Cursor {
|
||||||
```
|
```
|
||||||
|
|
||||||
On sync:
|
On sync:
|
||||||
|
|
||||||
1. Client sends cursor (last seen version)
|
1. Client sends cursor (last seen version)
|
||||||
2. Server returns all changes since that version
|
2. Server returns all changes since that version
|
||||||
3. Client applies changes and updates cursor
|
3. Client applies changes and updates cursor
|
||||||
|
|
@ -95,42 +134,47 @@ On sync:
|
||||||
Two users edit the same paragraph simultaneously.
|
Two users edit the same paragraph simultaneously.
|
||||||
|
|
||||||
**Initial state**:
|
**Initial state**:
|
||||||
|
|
||||||
```
|
```
|
||||||
Version 10: "The quick brown fox jumps over the lazy dog."
|
Version 10: "The quick brown fox jumps over the lazy dog."
|
||||||
```
|
```
|
||||||
|
|
||||||
**User A's edit** (version 11):
|
**User A's edit** (version 11):
|
||||||
|
|
||||||
```
|
```
|
||||||
"The quick brown fox jumps over the very lazy dog."
|
"The quick brown fox jumps over the very lazy dog."
|
||||||
```
|
```
|
||||||
*Inserts "very " at position 40*
|
|
||||||
|
_Inserts "very " at position 40_
|
||||||
|
|
||||||
**User B's edit** (also from version 10):
|
**User B's edit** (also from version 10):
|
||||||
|
|
||||||
```
|
```
|
||||||
"The quick red fox jumps over the lazy dog."
|
"The quick red fox jumps over the lazy dog."
|
||||||
```
|
```
|
||||||
*Replaces "brown" with "red" at position 10*
|
|
||||||
|
_Replaces "brown" with "red" at position 10_
|
||||||
|
|
||||||
### Server Processing
|
### Server Processing
|
||||||
|
|
||||||
1. **Receive User A's operation**:
|
1. **Receive User A's operation**:
|
||||||
- Base: version 10
|
- Base: version 10
|
||||||
- Operation: Insert("very ", position=40)
|
- Operation: Insert("very ", position=40)
|
||||||
- Apply to database → version 11
|
- Apply to database → version 11
|
||||||
|
|
||||||
2. **Receive User B's operation**:
|
2. **Receive User B's operation**:
|
||||||
- Base: version 10
|
- Base: version 10
|
||||||
- Operation: Replace("brown"→"red", position=10)
|
- Operation: Replace("brown"→"red", position=10)
|
||||||
- **Conflict detected**: Base is version 10, but current is version 11
|
- **Conflict detected**: Base is version 10, but current is version 11
|
||||||
|
|
||||||
3. **Transform User B's operation**:
|
3. **Transform User B's operation**:
|
||||||
- Transform against User A's operation
|
- Transform against User A's operation
|
||||||
- Adjust positions/content as needed
|
- Adjust positions/content as needed
|
||||||
- Apply transformed operation → version 12
|
- Apply transformed operation → version 12
|
||||||
|
|
||||||
4. **Broadcast updates**:
|
4. **Broadcast updates**:
|
||||||
- Send User A's operation to User B
|
- Send User A's operation to User B
|
||||||
- Send transformed User B's operation to User A
|
- Send transformed User B's operation to User A
|
||||||
|
|
||||||
### Final Result
|
### Final Result
|
||||||
|
|
||||||
|
|
@ -147,11 +191,13 @@ Both edits are preserved in the final document.
|
||||||
**Scenario**: User A deletes a paragraph while User B edits it.
|
**Scenario**: User A deletes a paragraph while User B edits it.
|
||||||
|
|
||||||
**Resolution**:
|
**Resolution**:
|
||||||
|
|
||||||
- OT algorithm prioritizes preservation of content
|
- OT algorithm prioritizes preservation of content
|
||||||
- Insert operation is transformed to account for deletion
|
- Insert operation is transformed to account for deletion
|
||||||
- Typically results in inserted content appearing nearby
|
- Typically results in inserted content appearing nearby
|
||||||
|
|
||||||
**Example**:
|
**Example**:
|
||||||
|
|
||||||
```
|
```
|
||||||
Base: "Line 1\nLine 2\nLine 3"
|
Base: "Line 1\nLine 2\nLine 3"
|
||||||
|
|
||||||
|
|
@ -160,6 +206,7 @@ User B: Edit Line 2 → "Line 1\nLine 2 modified\nLine 3"
|
||||||
|
|
||||||
Result: "Line 1\nLine 2 modified\nLine 3"
|
Result: "Line 1\nLine 2 modified\nLine 3"
|
||||||
```
|
```
|
||||||
|
|
||||||
(Insert takes precedence, preserving user content)
|
(Insert takes precedence, preserving user content)
|
||||||
|
|
||||||
### 2. Overlapping Edits
|
### 2. Overlapping Edits
|
||||||
|
|
@ -167,6 +214,7 @@ Result: "Line 1\nLine 2 modified\nLine 3"
|
||||||
**Scenario**: Two users edit overlapping regions.
|
**Scenario**: Two users edit overlapping regions.
|
||||||
|
|
||||||
**Resolution**:
|
**Resolution**:
|
||||||
|
|
||||||
- OT splits operations into non-overlapping segments
|
- OT splits operations into non-overlapping segments
|
||||||
- Applies each segment independently
|
- Applies each segment independently
|
||||||
- Merges results
|
- Merges results
|
||||||
|
|
@ -176,6 +224,7 @@ Result: "Line 1\nLine 2 modified\nLine 3"
|
||||||
**Scenario**: Two users delete overlapping text.
|
**Scenario**: Two users delete overlapping text.
|
||||||
|
|
||||||
**Resolution**:
|
**Resolution**:
|
||||||
|
|
||||||
- Deletes are merged
|
- Deletes are merged
|
||||||
- Final result has the union of deleted ranges removed
|
- Final result has the union of deleted ranges removed
|
||||||
|
|
||||||
|
|
@ -184,6 +233,7 @@ Result: "Line 1\nLine 2 modified\nLine 3"
|
||||||
**Scenario**: Client loses connection, makes edits offline, reconnects.
|
**Scenario**: Client loses connection, makes edits offline, reconnects.
|
||||||
|
|
||||||
**Resolution**:
|
**Resolution**:
|
||||||
|
|
||||||
1. Client queues edits locally
|
1. Client queues edits locally
|
||||||
2. On reconnect, sends all queued operations
|
2. On reconnect, sends all queued operations
|
||||||
3. Server applies OT against all operations that happened during partition
|
3. Server applies OT against all operations that happened during partition
|
||||||
|
|
@ -206,6 +256,7 @@ Result: "Line 1\nLine 2 modified\nLine 3"
|
||||||
### Optimization
|
### Optimization
|
||||||
|
|
||||||
VaultLink optimizes for:
|
VaultLink optimizes for:
|
||||||
|
|
||||||
- Small, frequent edits (typical typing patterns)
|
- Small, frequent edits (typical typing patterns)
|
||||||
- Text documents (not binary files)
|
- Text documents (not binary files)
|
||||||
- Real-time processing (no batching delay)
|
- Real-time processing (no batching delay)
|
||||||
|
|
@ -215,6 +266,7 @@ VaultLink optimizes for:
|
||||||
### Binary Files
|
### Binary Files
|
||||||
|
|
||||||
OT works best for text files. Binary files:
|
OT works best for text files. Binary files:
|
||||||
|
|
||||||
- Cannot be meaningfully merged
|
- Cannot be meaningfully merged
|
||||||
- Use last-write-wins strategy
|
- Use last-write-wins strategy
|
||||||
- May cause data loss on concurrent edits
|
- May cause data loss on concurrent edits
|
||||||
|
|
@ -224,6 +276,7 @@ OT works best for text files. Binary files:
|
||||||
### Large Documents
|
### Large Documents
|
||||||
|
|
||||||
Very large documents (> 1MB) may have:
|
Very large documents (> 1MB) may have:
|
||||||
|
|
||||||
- Higher transformation costs
|
- Higher transformation costs
|
||||||
- Slower sync times
|
- Slower sync times
|
||||||
- Increased memory usage
|
- Increased memory usage
|
||||||
|
|
@ -233,6 +286,7 @@ Very large documents (> 1MB) may have:
|
||||||
### Complex Formatting
|
### Complex Formatting
|
||||||
|
|
||||||
Markdown with complex structures may occasionally produce unexpected results:
|
Markdown with complex structures may occasionally produce unexpected results:
|
||||||
|
|
||||||
- Nested lists
|
- Nested lists
|
||||||
- Tables
|
- Tables
|
||||||
- Code blocks
|
- Code blocks
|
||||||
|
|
@ -244,6 +298,7 @@ Markdown with complex structures may occasionally produce unexpected results:
|
||||||
### Strong Consistency
|
### Strong Consistency
|
||||||
|
|
||||||
VaultLink provides **strong eventual consistency**:
|
VaultLink provides **strong eventual consistency**:
|
||||||
|
|
||||||
- All clients eventually converge to the same state
|
- All clients eventually converge to the same state
|
||||||
- Operations applied in causal order
|
- Operations applied in causal order
|
||||||
- No data loss under normal operation
|
- No data loss under normal operation
|
||||||
|
|
@ -264,32 +319,36 @@ VaultLink provides **strong eventual consistency**:
|
||||||
|
|
||||||
### Git-style Merging
|
### Git-style Merging
|
||||||
|
|
||||||
| Aspect | Git Merge | VaultLink OT |
|
| Aspect | Git Merge | VaultLink OT |
|
||||||
|--------|-----------|--------------|
|
| -------------------------- | ------------ | ----------------------- |
|
||||||
| Real-time | No | Yes |
|
| Real-time | No | Yes |
|
||||||
| Manual conflict resolution | Yes | No |
|
| Manual conflict resolution | Yes | No |
|
||||||
| Branching | Yes | No |
|
| Branching | Yes | No |
|
||||||
| Automatic merge | Limited | Always |
|
| Automatic merge | Limited | Always |
|
||||||
| Use case | Code changes | Collaborative documents |
|
| Use case | Code changes | Collaborative documents |
|
||||||
|
|
||||||
### CRDTs (Conflict-free Replicated Data Types)
|
### CRDTs (Conflict-free Replicated Data Types)
|
||||||
|
|
||||||
| Aspect | CRDTs | VaultLink OT |
|
| Aspect | CRDTs | VaultLink (reconcile-text) |
|
||||||
|--------|-------|--------------|
|
| ----------------------------- | ------------------------------------ | ------------------------------------------------- |
|
||||||
| Server required | No | Yes |
|
| **Operation tracking** | Required (every keystroke) | Not required (end states only) |
|
||||||
| Memory overhead | Higher | Lower |
|
| **Editor freedom** | Limited (must use CRDT-aware editor) | Unlimited (any text editor works) |
|
||||||
| Complexity | Higher | Lower |
|
| **Offline editing** | Requires operation log | Works with file comparison |
|
||||||
| Deletion handling | Complex (tombstones) | Simple |
|
| **Server required** | No | Yes |
|
||||||
| Best for | Distributed systems | Centralized sync |
|
| **Memory overhead** | Higher (tombstones, metadata) | Lower (versions only) |
|
||||||
|
| **Infrastructure complexity** | Higher | Lower |
|
||||||
|
| **Best for** | Controlled editing environments | Independent file editing (Obsidian, Vim, VS Code) |
|
||||||
|
|
||||||
|
**Key insight**: CRDTs are superior when you can capture every operation. reconcile-text is superior when users edit files independently with arbitrary tools—exactly VaultLink's scenario.
|
||||||
|
|
||||||
### Last Write Wins
|
### Last Write Wins
|
||||||
|
|
||||||
| Aspect | LWW | VaultLink OT |
|
| Aspect | LWW | VaultLink OT |
|
||||||
|--------|-----|--------------|
|
| --------------- | ---- | ------------ |
|
||||||
| Data loss | Yes | No |
|
| Data loss | Yes | No |
|
||||||
| Simplicity | High | Medium |
|
| Simplicity | High | Medium |
|
||||||
| User experience | Poor | Excellent |
|
| User experience | Poor | Excellent |
|
||||||
| Performance | Best | Good |
|
| Performance | Best | Good |
|
||||||
|
|
||||||
## Algorithm Details
|
## Algorithm Details
|
||||||
|
|
||||||
|
|
@ -298,20 +357,20 @@ VaultLink provides **strong eventual consistency**:
|
||||||
When transforming operation `A` against operation `B`:
|
When transforming operation `A` against operation `B`:
|
||||||
|
|
||||||
1. **Insert vs Insert**:
|
1. **Insert vs Insert**:
|
||||||
- If positions equal: Order by client ID
|
- If positions equal: Order by client ID
|
||||||
- If different positions: Adjust positions
|
- If different positions: Adjust positions
|
||||||
|
|
||||||
2. **Insert vs Delete**:
|
2. **Insert vs Delete**:
|
||||||
- If insert in deleted range: Shift insert position
|
- If insert in deleted range: Shift insert position
|
||||||
- If insert after delete: Adjust position by deleted length
|
- If insert after delete: Adjust position by deleted length
|
||||||
|
|
||||||
3. **Delete vs Delete**:
|
3. **Delete vs Delete**:
|
||||||
- If ranges overlap: Merge delete ranges
|
- If ranges overlap: Merge delete ranges
|
||||||
- If ranges disjoint: Adjust positions
|
- If ranges disjoint: Adjust positions
|
||||||
|
|
||||||
4. **Retain vs Any**:
|
4. **Retain vs Any**:
|
||||||
- Retain operations don't conflict
|
- Retain operations don't conflict
|
||||||
- Simply adjust positions
|
- Simply adjust positions
|
||||||
|
|
||||||
### Transformation Example
|
### Transformation Example
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,13 @@ While VaultLink handles most SQLite configuration automatically, you can optimiz
|
||||||
VaultLink uses Write-Ahead Logging (WAL) mode by default for better concurrency.
|
VaultLink uses Write-Ahead Logging (WAL) mode by default for better concurrency.
|
||||||
|
|
||||||
**Benefits**:
|
**Benefits**:
|
||||||
|
|
||||||
- Readers don't block writers
|
- Readers don't block writers
|
||||||
- Writers don't block readers
|
- Writers don't block readers
|
||||||
- Better performance for concurrent access
|
- Better performance for concurrent access
|
||||||
|
|
||||||
**Maintenance**:
|
**Maintenance**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Checkpoint WAL to main database (run periodically)
|
# Checkpoint WAL to main database (run periodically)
|
||||||
sqlite3 databases/vault.db "PRAGMA wal_checkpoint(TRUNCATE);"
|
sqlite3 databases/vault.db "PRAGMA wal_checkpoint(TRUNCATE);"
|
||||||
|
|
@ -39,6 +41,7 @@ sqlite3 databases/vault.db "ANALYZE;"
|
||||||
```
|
```
|
||||||
|
|
||||||
**Schedule maintenance**:
|
**Schedule maintenance**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# monthly-maintenance.sh
|
# monthly-maintenance.sh
|
||||||
|
|
@ -83,6 +86,7 @@ max_connections = (concurrent_users × avg_operations_per_user) + buffer
|
||||||
```
|
```
|
||||||
|
|
||||||
**Example**:
|
**Example**:
|
||||||
|
|
||||||
- 20 concurrent users
|
- 20 concurrent users
|
||||||
- 2 operations per user on average
|
- 2 operations per user on average
|
||||||
- 25% buffer
|
- 25% buffer
|
||||||
|
|
@ -96,30 +100,33 @@ max_connections = (20 × 2) × 1.25 = 50
|
||||||
Adjust timeouts based on network characteristics:
|
Adjust timeouts based on network characteristics:
|
||||||
|
|
||||||
**Fast local network**:
|
**Fast local network**:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
database:
|
database:
|
||||||
cursor_timeout_seconds: 30
|
cursor_timeout_seconds: 30
|
||||||
|
|
||||||
server:
|
server:
|
||||||
response_timeout_seconds: 30
|
response_timeout_seconds: 30
|
||||||
```
|
```
|
||||||
|
|
||||||
**Slow or unreliable network**:
|
**Slow or unreliable network**:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
database:
|
database:
|
||||||
cursor_timeout_seconds: 180
|
cursor_timeout_seconds: 180
|
||||||
|
|
||||||
server:
|
server:
|
||||||
response_timeout_seconds: 120
|
response_timeout_seconds: 120
|
||||||
```
|
```
|
||||||
|
|
||||||
**Mobile clients**:
|
**Mobile clients**:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
database:
|
database:
|
||||||
cursor_timeout_seconds: 300 # Longer for intermittent connections
|
cursor_timeout_seconds: 300 # Longer for intermittent connections
|
||||||
|
|
||||||
server:
|
server:
|
||||||
response_timeout_seconds: 180
|
response_timeout_seconds: 180
|
||||||
```
|
```
|
||||||
|
|
||||||
## Reverse Proxy Configuration
|
## Reverse Proxy Configuration
|
||||||
|
|
@ -232,16 +239,16 @@ Using Docker labels:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
vaultlink-server:
|
vaultlink-server:
|
||||||
image: ghcr.io/schmelczer/vault-link-server:latest
|
image: ghcr.io/schmelczer/vault-link-server:latest
|
||||||
labels:
|
labels:
|
||||||
- "traefik.enable=true"
|
- "traefik.enable=true"
|
||||||
- "traefik.http.routers.vaultlink.rule=Host(`sync.example.com`)"
|
- "traefik.http.routers.vaultlink.rule=Host(`sync.example.com`)"
|
||||||
- "traefik.http.routers.vaultlink.entrypoints=websecure"
|
- "traefik.http.routers.vaultlink.entrypoints=websecure"
|
||||||
- "traefik.http.routers.vaultlink.tls.certresolver=letsencrypt"
|
- "traefik.http.routers.vaultlink.tls.certresolver=letsencrypt"
|
||||||
- "traefik.http.services.vaultlink.loadbalancer.server.port=3000"
|
- "traefik.http.services.vaultlink.loadbalancer.server.port=3000"
|
||||||
# Middleware for timeouts
|
# Middleware for timeouts
|
||||||
- "traefik.http.middlewares.vaultlink-timeout.timeout.request=3600s"
|
- "traefik.http.middlewares.vaultlink-timeout.timeout.request=3600s"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Docker Optimizations
|
## Docker Optimizations
|
||||||
|
|
@ -252,16 +259,16 @@ Limit container resources:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
vaultlink-server:
|
vaultlink-server:
|
||||||
image: ghcr.io/schmelczer/vault-link-server:latest
|
image: ghcr.io/schmelczer/vault-link-server:latest
|
||||||
deploy:
|
deploy:
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
cpus: '2.0'
|
cpus: "2.0"
|
||||||
memory: 4G
|
memory: 4G
|
||||||
reservations:
|
reservations:
|
||||||
cpus: '1.0'
|
cpus: "1.0"
|
||||||
memory: 2G
|
memory: 2G
|
||||||
```
|
```
|
||||||
|
|
||||||
### Logging Configuration
|
### Logging Configuration
|
||||||
|
|
@ -270,13 +277,13 @@ Optimize Docker logging:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
vaultlink-server:
|
vaultlink-server:
|
||||||
image: ghcr.io/schmelczer/vault-link-server:latest
|
image: ghcr.io/schmelczer/vault-link-server:latest
|
||||||
logging:
|
logging:
|
||||||
driver: "json-file"
|
driver: "json-file"
|
||||||
options:
|
options:
|
||||||
max-size: "50m"
|
max-size: "50m"
|
||||||
max-file: "5"
|
max-file: "5"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Volume Optimization
|
### Volume Optimization
|
||||||
|
|
@ -285,21 +292,21 @@ Use named volumes for better performance:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
vaultlink-server:
|
vaultlink-server:
|
||||||
image: ghcr.io/schmelczer/vault-link-server:latest
|
image: ghcr.io/schmelczer/vault-link-server:latest
|
||||||
volumes:
|
volumes:
|
||||||
- vaultlink-data:/data
|
- vaultlink-data:/data
|
||||||
- vaultlink-logs:/data/logs
|
- vaultlink-logs:/data/logs
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
vaultlink-data:
|
vaultlink-data:
|
||||||
driver: local
|
driver: local
|
||||||
driver_opts:
|
driver_opts:
|
||||||
type: none
|
type: none
|
||||||
o: bind
|
o: bind
|
||||||
device: /mnt/fast-ssd/vaultlink
|
device: /mnt/fast-ssd/vaultlink
|
||||||
vaultlink-logs:
|
vaultlink-logs:
|
||||||
driver: local
|
driver: local
|
||||||
```
|
```
|
||||||
|
|
||||||
## High Availability
|
## High Availability
|
||||||
|
|
@ -310,14 +317,14 @@ Comprehensive health monitoring:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
vaultlink-server:
|
vaultlink-server:
|
||||||
image: ghcr.io/schmelczer/vault-link-server:latest
|
image: ghcr.io/schmelczer/vault-link-server:latest
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "curl -f http://localhost:3000/vaults/health/ping || exit 1"]
|
test: ["CMD-SHELL", "curl -f http://localhost:3000/vaults/health/ping || exit 1"]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 3
|
retries: 3
|
||||||
start_period: 30s
|
start_period: 30s
|
||||||
```
|
```
|
||||||
|
|
||||||
Monitor health in production:
|
Monitor health in production:
|
||||||
|
|
@ -375,6 +382,7 @@ find "$BACKUP_DIR" -name "vaultlink-*.tar.gz" -mtime +$RETENTION_DAYS -delete
|
||||||
```
|
```
|
||||||
|
|
||||||
Schedule with cron:
|
Schedule with cron:
|
||||||
|
|
||||||
```cron
|
```cron
|
||||||
0 2 * * * /opt/vaultlink/backup-vaultlink.sh
|
0 2 * * * /opt/vaultlink/backup-vaultlink.sh
|
||||||
```
|
```
|
||||||
|
|
@ -424,21 +432,21 @@ While VaultLink doesn't expose metrics natively, monitor Docker:
|
||||||
```yaml
|
```yaml
|
||||||
# docker-compose.yml
|
# docker-compose.yml
|
||||||
services:
|
services:
|
||||||
vaultlink-server:
|
vaultlink-server:
|
||||||
image: ghcr.io/schmelczer/vault-link-server:latest
|
image: ghcr.io/schmelczer/vault-link-server:latest
|
||||||
labels:
|
labels:
|
||||||
- "prometheus.io/scrape=true"
|
- "prometheus.io/scrape=true"
|
||||||
- "prometheus.io/port=3000"
|
- "prometheus.io/port=3000"
|
||||||
|
|
||||||
cadvisor:
|
cadvisor:
|
||||||
image: gcr.io/cadvisor/cadvisor:latest
|
image: gcr.io/cadvisor/cadvisor:latest
|
||||||
volumes:
|
volumes:
|
||||||
- /:/rootfs:ro
|
- /:/rootfs:ro
|
||||||
- /var/run:/var/run:ro
|
- /var/run:/var/run:ro
|
||||||
- /sys:/sys:ro
|
- /sys:/sys:ro
|
||||||
- /var/lib/docker/:/var/lib/docker:ro
|
- /var/lib/docker/:/var/lib/docker:ro
|
||||||
ports:
|
ports:
|
||||||
- 8080:8080
|
- 8080:8080
|
||||||
```
|
```
|
||||||
|
|
||||||
### Log Analysis
|
### Log Analysis
|
||||||
|
|
@ -484,17 +492,17 @@ Run VaultLink in isolated network:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
vaultlink-server:
|
vaultlink-server:
|
||||||
image: ghcr.io/schmelczer/vault-link-server:latest
|
image: ghcr.io/schmelczer/vault-link-server:latest
|
||||||
networks:
|
networks:
|
||||||
- vaultlink-internal
|
- vaultlink-internal
|
||||||
- proxy-external
|
- proxy-external
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
vaultlink-internal:
|
vaultlink-internal:
|
||||||
internal: true
|
internal: true
|
||||||
proxy-external:
|
proxy-external:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
```
|
```
|
||||||
|
|
||||||
### Read-Only Root Filesystem
|
### Read-Only Root Filesystem
|
||||||
|
|
@ -503,12 +511,12 @@ Run with read-only root (mount writable volumes for data):
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
vaultlink-server:
|
vaultlink-server:
|
||||||
image: ghcr.io/schmelczer/vault-link-server:latest
|
image: ghcr.io/schmelczer/vault-link-server:latest
|
||||||
read_only: true
|
read_only: true
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/data
|
- ./data:/data
|
||||||
- /tmp
|
- /tmp
|
||||||
```
|
```
|
||||||
|
|
||||||
### Drop Capabilities
|
### Drop Capabilities
|
||||||
|
|
@ -517,12 +525,12 @@ Run with minimal privileges:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
vaultlink-server:
|
vaultlink-server:
|
||||||
image: ghcr.io/schmelczer/vault-link-server:latest
|
image: ghcr.io/schmelczer/vault-link-server:latest
|
||||||
security_opt:
|
security_opt:
|
||||||
- no-new-privileges:true
|
- no-new-privileges:true
|
||||||
cap_drop:
|
cap_drop:
|
||||||
- ALL
|
- ALL
|
||||||
```
|
```
|
||||||
|
|
||||||
## Migration
|
## Migration
|
||||||
|
|
@ -530,19 +538,22 @@ services:
|
||||||
### Moving to New Server
|
### Moving to New Server
|
||||||
|
|
||||||
1. **Backup on old server**:
|
1. **Backup on old server**:
|
||||||
```bash
|
|
||||||
./backup-vaultlink.sh
|
```bash
|
||||||
```
|
./backup-vaultlink.sh
|
||||||
|
```
|
||||||
|
|
||||||
2. **Transfer backup**:
|
2. **Transfer backup**:
|
||||||
```bash
|
|
||||||
scp vaultlink-backup.tar.gz new-server:/tmp/
|
```bash
|
||||||
```
|
scp vaultlink-backup.tar.gz new-server:/tmp/
|
||||||
|
```
|
||||||
|
|
||||||
3. **Restore on new server**:
|
3. **Restore on new server**:
|
||||||
```bash
|
|
||||||
./restore-vaultlink.sh /tmp/vaultlink-backup.tar.gz
|
```bash
|
||||||
```
|
./restore-vaultlink.sh /tmp/vaultlink-backup.tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
4. **Update DNS/clients** to point to new server
|
4. **Update DNS/clients** to point to new server
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ VaultLink uses token-based authentication with per-user vault access control. Th
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
Authentication in VaultLink:
|
Authentication in VaultLink:
|
||||||
|
|
||||||
- **Token-based**: Users authenticate with secure tokens
|
- **Token-based**: Users authenticate with secure tokens
|
||||||
- **Configured in YAML**: All users defined in `config.yml`
|
- **Configured in YAML**: All users defined in `config.yml`
|
||||||
- **Vault-level access**: Control which vaults each user can access
|
- **Vault-level access**: Control which vaults each user can access
|
||||||
|
|
@ -14,11 +15,11 @@ Authentication in VaultLink:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
users:
|
users:
|
||||||
user_configs:
|
user_configs:
|
||||||
- name: alice
|
- name: alice
|
||||||
token: alice-secure-token-here
|
token: alice-secure-token-here
|
||||||
vault_access:
|
vault_access:
|
||||||
type: allow_access_to_all
|
type: allow_access_to_all
|
||||||
```
|
```
|
||||||
|
|
||||||
## User Configuration Fields
|
## User Configuration Fields
|
||||||
|
|
@ -35,6 +36,7 @@ Human-readable identifier for the user. Used in logs and auditing.
|
||||||
```
|
```
|
||||||
|
|
||||||
**Notes**:
|
**Notes**:
|
||||||
|
|
||||||
- Must be unique across all users
|
- Must be unique across all users
|
||||||
- Used for identification only, not authentication
|
- Used for identification only, not authentication
|
||||||
- Appears in server logs
|
- Appears in server logs
|
||||||
|
|
@ -52,6 +54,7 @@ Authentication token for the user. Must be kept secret.
|
||||||
```
|
```
|
||||||
|
|
||||||
**Best practices**:
|
**Best practices**:
|
||||||
|
|
||||||
- Generate with: `openssl rand -hex 32`
|
- Generate with: `openssl rand -hex 32`
|
||||||
- Minimum length: 32 characters
|
- Minimum length: 32 characters
|
||||||
- Use different token per user
|
- Use different token per user
|
||||||
|
|
@ -59,6 +62,7 @@ Authentication token for the user. Must be kept secret.
|
||||||
- Rotate periodically
|
- Rotate periodically
|
||||||
|
|
||||||
**Example token generation**:
|
**Example token generation**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Generate a secure token
|
# Generate a secure token
|
||||||
openssl rand -hex 32
|
openssl rand -hex 32
|
||||||
|
|
@ -73,6 +77,7 @@ openssl rand -hex 32
|
||||||
Defines which vaults the user can access.
|
Defines which vaults the user can access.
|
||||||
|
|
||||||
**Three modes**:
|
**Three modes**:
|
||||||
|
|
||||||
1. `allow_access_to_all`: Access to all vaults
|
1. `allow_access_to_all`: Access to all vaults
|
||||||
2. `allow_list`: Access to specific vaults only
|
2. `allow_list`: Access to specific vaults only
|
||||||
3. `deny_list`: Access to all vaults except specific ones
|
3. `deny_list`: Access to all vaults except specific ones
|
||||||
|
|
@ -85,14 +90,15 @@ Grant access to every vault:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
users:
|
users:
|
||||||
user_configs:
|
user_configs:
|
||||||
- name: admin
|
- name: admin
|
||||||
token: admin-token
|
token: admin-token
|
||||||
vault_access:
|
vault_access:
|
||||||
type: allow_access_to_all
|
type: allow_access_to_all
|
||||||
```
|
```
|
||||||
|
|
||||||
**Use cases**:
|
**Use cases**:
|
||||||
|
|
||||||
- Administrator accounts
|
- Administrator accounts
|
||||||
- Personal single-user deployments
|
- Personal single-user deployments
|
||||||
- Development/testing
|
- Development/testing
|
||||||
|
|
@ -103,23 +109,25 @@ Grant access only to specific vaults:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
users:
|
users:
|
||||||
user_configs:
|
user_configs:
|
||||||
- name: alice
|
- name: alice
|
||||||
token: alice-token
|
token: alice-token
|
||||||
vault_access:
|
vault_access:
|
||||||
type: allow_list
|
type: allow_list
|
||||||
allowed:
|
allowed:
|
||||||
- personal
|
- personal
|
||||||
- shared-team
|
- shared-team
|
||||||
- project-alpha
|
- project-alpha
|
||||||
```
|
```
|
||||||
|
|
||||||
**Use cases**:
|
**Use cases**:
|
||||||
|
|
||||||
- Multi-user deployments
|
- Multi-user deployments
|
||||||
- Restricted access scenarios
|
- Restricted access scenarios
|
||||||
- Separation of concerns
|
- Separation of concerns
|
||||||
|
|
||||||
**Notes**:
|
**Notes**:
|
||||||
|
|
||||||
- User can only access listed vaults
|
- User can only access listed vaults
|
||||||
- Attempting to access other vaults returns authentication error
|
- Attempting to access other vaults returns authentication error
|
||||||
- Empty list = no access to any vault
|
- Empty list = no access to any vault
|
||||||
|
|
@ -130,21 +138,23 @@ Grant access to all vaults except specific ones:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
users:
|
users:
|
||||||
user_configs:
|
user_configs:
|
||||||
- name: bob
|
- name: bob
|
||||||
token: bob-token
|
token: bob-token
|
||||||
vault_access:
|
vault_access:
|
||||||
type: deny_list
|
type: deny_list
|
||||||
denied:
|
denied:
|
||||||
- restricted
|
- restricted
|
||||||
- admin-only
|
- admin-only
|
||||||
```
|
```
|
||||||
|
|
||||||
**Use cases**:
|
**Use cases**:
|
||||||
|
|
||||||
- Users with broad access except sensitive vaults
|
- Users with broad access except sensitive vaults
|
||||||
- Simplify configuration when most vaults are accessible
|
- Simplify configuration when most vaults are accessible
|
||||||
|
|
||||||
**Notes**:
|
**Notes**:
|
||||||
|
|
||||||
- User can access any vault not in the deny list
|
- User can access any vault not in the deny list
|
||||||
- Attempting to access denied vaults returns authentication error
|
- Attempting to access denied vaults returns authentication error
|
||||||
|
|
||||||
|
|
@ -154,75 +164,75 @@ users:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
users:
|
users:
|
||||||
user_configs:
|
user_configs:
|
||||||
- name: me
|
- name: me
|
||||||
token: my-super-secret-token
|
token: my-super-secret-token
|
||||||
vault_access:
|
vault_access:
|
||||||
type: allow_access_to_all
|
type: allow_access_to_all
|
||||||
```
|
```
|
||||||
|
|
||||||
### Small Team (Shared Vaults)
|
### Small Team (Shared Vaults)
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
users:
|
users:
|
||||||
user_configs:
|
user_configs:
|
||||||
- name: alice
|
- name: alice
|
||||||
token: alice-token
|
token: alice-token
|
||||||
vault_access:
|
vault_access:
|
||||||
type: allow_list
|
type: allow_list
|
||||||
allowed:
|
allowed:
|
||||||
- personal-alice
|
- personal-alice
|
||||||
- team-shared
|
- team-shared
|
||||||
- name: bob
|
- name: bob
|
||||||
token: bob-token
|
token: bob-token
|
||||||
vault_access:
|
vault_access:
|
||||||
type: allow_list
|
type: allow_list
|
||||||
allowed:
|
allowed:
|
||||||
- personal-bob
|
- personal-bob
|
||||||
- team-shared
|
- team-shared
|
||||||
- name: charlie
|
- name: charlie
|
||||||
token: charlie-token
|
token: charlie-token
|
||||||
vault_access:
|
vault_access:
|
||||||
type: allow_list
|
type: allow_list
|
||||||
allowed:
|
allowed:
|
||||||
- personal-charlie
|
- personal-charlie
|
||||||
- team-shared
|
- team-shared
|
||||||
```
|
```
|
||||||
|
|
||||||
### Organization (Mixed Access)
|
### Organization (Mixed Access)
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
users:
|
users:
|
||||||
user_configs:
|
user_configs:
|
||||||
- name: admin
|
- name: admin
|
||||||
token: admin-token
|
token: admin-token
|
||||||
vault_access:
|
vault_access:
|
||||||
type: allow_access_to_all
|
type: allow_access_to_all
|
||||||
|
|
||||||
- name: developer
|
- name: developer
|
||||||
token: dev-token
|
token: dev-token
|
||||||
vault_access:
|
vault_access:
|
||||||
type: allow_list
|
type: allow_list
|
||||||
allowed:
|
allowed:
|
||||||
- engineering-docs
|
- engineering-docs
|
||||||
- api-specs
|
- api-specs
|
||||||
- shared
|
- shared
|
||||||
|
|
||||||
- name: designer
|
- name: designer
|
||||||
token: design-token
|
token: design-token
|
||||||
vault_access:
|
vault_access:
|
||||||
type: allow_list
|
type: allow_list
|
||||||
allowed:
|
allowed:
|
||||||
- design-docs
|
- design-docs
|
||||||
- brand-assets
|
- brand-assets
|
||||||
- shared
|
- shared
|
||||||
|
|
||||||
- name: readonly
|
- name: readonly
|
||||||
token: readonly-token
|
token: readonly-token
|
||||||
vault_access:
|
vault_access:
|
||||||
type: allow_list
|
type: allow_list
|
||||||
allowed:
|
allowed:
|
||||||
- public-wiki
|
- public-wiki
|
||||||
```
|
```
|
||||||
|
|
||||||
## Authentication Flow
|
## Authentication Flow
|
||||||
|
|
@ -231,23 +241,24 @@ users:
|
||||||
|
|
||||||
1. Client connects via WebSocket
|
1. Client connects via WebSocket
|
||||||
2. Client sends authentication message:
|
2. Client sends authentication message:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "auth",
|
"type": "auth",
|
||||||
"token": "user-token",
|
"token": "user-token",
|
||||||
"vault": "vault-name"
|
"vault": "vault-name"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
3. Server validates:
|
3. Server validates:
|
||||||
- Token exists in config
|
- Token exists in config
|
||||||
- User has access to requested vault
|
- User has access to requested vault
|
||||||
4. Server responds:
|
4. Server responds:
|
||||||
- Success: Connection established
|
- Success: Connection established
|
||||||
- Failure: Connection closed with error
|
- Failure: Connection closed with error
|
||||||
|
|
||||||
### Validation
|
### Validation
|
||||||
|
|
||||||
Server checks:
|
Server checks:
|
||||||
|
|
||||||
1. **Token match**: Token exists in `user_configs`
|
1. **Token match**: Token exists in `user_configs`
|
||||||
2. **Vault access**: User has permission for vault
|
2. **Vault access**: User has permission for vault
|
||||||
3. **Connection limits**: Not exceeding `max_clients_per_vault`
|
3. **Connection limits**: Not exceeding `max_clients_per_vault`
|
||||||
|
|
@ -255,16 +266,19 @@ Server checks:
|
||||||
### Errors
|
### Errors
|
||||||
|
|
||||||
**Invalid token**:
|
**Invalid token**:
|
||||||
|
|
||||||
```
|
```
|
||||||
Authentication failed: Invalid token
|
Authentication failed: Invalid token
|
||||||
```
|
```
|
||||||
|
|
||||||
**No vault access**:
|
**No vault access**:
|
||||||
|
|
||||||
```
|
```
|
||||||
Authentication failed: User does not have access to vault 'restricted'
|
Authentication failed: User does not have access to vault 'restricted'
|
||||||
```
|
```
|
||||||
|
|
||||||
**Connection limit**:
|
**Connection limit**:
|
||||||
|
|
||||||
```
|
```
|
||||||
Connection rejected: Maximum clients reached for vault
|
Connection rejected: Maximum clients reached for vault
|
||||||
```
|
```
|
||||||
|
|
@ -289,14 +303,16 @@ uuidgen
|
||||||
### Token Storage
|
### Token Storage
|
||||||
|
|
||||||
**In config file**:
|
**In config file**:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
users:
|
users:
|
||||||
user_configs:
|
user_configs:
|
||||||
- name: alice
|
- name: alice
|
||||||
token: !ENV ALICE_TOKEN # Read from environment variable
|
token: !ENV ALICE_TOKEN # Read from environment variable
|
||||||
```
|
```
|
||||||
|
|
||||||
**Load from environment**:
|
**Load from environment**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export ALICE_TOKEN="$(openssl rand -hex 32)"
|
export ALICE_TOKEN="$(openssl rand -hex 32)"
|
||||||
./sync_server config.yml
|
./sync_server config.yml
|
||||||
|
|
@ -314,11 +330,13 @@ Periodically change tokens:
|
||||||
### Token Revocation
|
### Token Revocation
|
||||||
|
|
||||||
To revoke access:
|
To revoke access:
|
||||||
|
|
||||||
1. Remove user from `config.yml`
|
1. Remove user from `config.yml`
|
||||||
2. Restart server
|
2. Restart server
|
||||||
3. User's connections will be rejected
|
3. User's connections will be rejected
|
||||||
|
|
||||||
For immediate revocation:
|
For immediate revocation:
|
||||||
|
|
||||||
- Remove user from config
|
- Remove user from config
|
||||||
- Restart server
|
- Restart server
|
||||||
- Existing connections are terminated
|
- Existing connections are terminated
|
||||||
|
|
@ -354,6 +372,7 @@ Grant temporary access:
|
||||||
4. Restart server
|
4. Restart server
|
||||||
|
|
||||||
For automation:
|
For automation:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Add user with expiry comment
|
# Add user with expiry comment
|
||||||
echo " - name: temp-user # EXPIRES: 2024-12-31" >> config.yml
|
echo " - name: temp-user # EXPIRES: 2024-12-31" >> config.yml
|
||||||
|
|
@ -363,6 +382,7 @@ echo " token: temp-token" >> config.yml
|
||||||
### Shared Tokens (Not Recommended)
|
### Shared Tokens (Not Recommended)
|
||||||
|
|
||||||
Multiple users sharing a token:
|
Multiple users sharing a token:
|
||||||
|
|
||||||
- All appear as same user in logs
|
- All appear as same user in logs
|
||||||
- Can't revoke individual access
|
- Can't revoke individual access
|
||||||
- Security risk if one person leaves
|
- Security risk if one person leaves
|
||||||
|
|
@ -432,25 +452,25 @@ Tokens for automated systems:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
users:
|
users:
|
||||||
user_configs:
|
user_configs:
|
||||||
- name: backup-service
|
- name: backup-service
|
||||||
token: backup-service-token
|
token: backup-service-token
|
||||||
vault_access:
|
vault_access:
|
||||||
type: allow_access_to_all
|
type: allow_access_to_all
|
||||||
|
|
||||||
- name: ci-pipeline
|
- name: ci-pipeline
|
||||||
token: ci-token
|
token: ci-token
|
||||||
vault_access:
|
vault_access:
|
||||||
type: allow_list
|
type: allow_list
|
||||||
allowed:
|
allowed:
|
||||||
- documentation
|
- documentation
|
||||||
|
|
||||||
- name: monitoring
|
- name: monitoring
|
||||||
token: monitoring-token
|
token: monitoring-token
|
||||||
vault_access:
|
vault_access:
|
||||||
type: allow_list
|
type: allow_list
|
||||||
allowed:
|
allowed:
|
||||||
- metrics
|
- metrics
|
||||||
```
|
```
|
||||||
|
|
||||||
### Dynamic Vault Access
|
### Dynamic Vault Access
|
||||||
|
|
@ -462,6 +482,7 @@ VaultLink doesn't support runtime user management. To change access:
|
||||||
3. Users reconnect automatically
|
3. Users reconnect automatically
|
||||||
|
|
||||||
For frequent changes, consider:
|
For frequent changes, consider:
|
||||||
|
|
||||||
- Over-provision access (deny list)
|
- Over-provision access (deny list)
|
||||||
- Use external authentication proxy
|
- Use external authentication proxy
|
||||||
- Script config updates + reload
|
- Script config updates + reload
|
||||||
|
|
@ -471,18 +492,21 @@ For frequent changes, consider:
|
||||||
### Can't connect
|
### Can't connect
|
||||||
|
|
||||||
**Check token**:
|
**Check token**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Verify token in config matches client
|
# Verify token in config matches client
|
||||||
grep "token:" config.yml
|
grep "token:" config.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
**Check vault name**:
|
**Check vault name**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Ensure vault is in allowed list
|
# Ensure vault is in allowed list
|
||||||
grep -A 5 "name: alice" config.yml
|
grep -A 5 "name: alice" config.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
**Check server logs**:
|
**Check server logs**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
tail -f logs/*.log | grep -i auth
|
tail -f logs/*.log | grep -i auth
|
||||||
```
|
```
|
||||||
|
|
@ -490,18 +514,20 @@ tail -f logs/*.log | grep -i auth
|
||||||
### Access denied
|
### Access denied
|
||||||
|
|
||||||
**Verify vault access**:
|
**Verify vault access**:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# Check user's vault_access configuration
|
# Check user's vault_access configuration
|
||||||
users:
|
users:
|
||||||
user_configs:
|
user_configs:
|
||||||
- name: alice
|
- name: alice
|
||||||
vault_access:
|
vault_access:
|
||||||
type: allow_list
|
type: allow_list
|
||||||
allowed:
|
allowed:
|
||||||
- vault-name # Must match exactly
|
- vault-name # Must match exactly
|
||||||
```
|
```
|
||||||
|
|
||||||
**Case sensitivity**:
|
**Case sensitivity**:
|
||||||
|
|
||||||
- Vault names are case-sensitive
|
- Vault names are case-sensitive
|
||||||
- `Vault` ≠ `vault`
|
- `Vault` ≠ `vault`
|
||||||
- Ensure exact match in config and client
|
- Ensure exact match in config and client
|
||||||
|
|
@ -509,11 +535,13 @@ users:
|
||||||
### Token not working
|
### Token not working
|
||||||
|
|
||||||
**Check for typos**:
|
**Check for typos**:
|
||||||
|
|
||||||
- Extra spaces
|
- Extra spaces
|
||||||
- Hidden characters
|
- Hidden characters
|
||||||
- Wrong quotes in YAML
|
- Wrong quotes in YAML
|
||||||
|
|
||||||
**Regenerate token**:
|
**Regenerate token**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Generate new token
|
# Generate new token
|
||||||
openssl rand -hex 32
|
openssl rand -hex 32
|
||||||
|
|
|
||||||
|
|
@ -14,40 +14,40 @@ The server is configured using a YAML file passed as a command-line argument:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
database:
|
database:
|
||||||
databases_directory_path: databases
|
databases_directory_path: databases
|
||||||
max_connections_per_vault: 12
|
max_connections_per_vault: 12
|
||||||
cursor_timeout_seconds: 60
|
cursor_timeout_seconds: 60
|
||||||
|
|
||||||
server:
|
server:
|
||||||
host: 0.0.0.0
|
host: 0.0.0.0
|
||||||
port: 3000
|
port: 3000
|
||||||
max_body_size_mb: 512
|
max_body_size_mb: 512
|
||||||
max_clients_per_vault: 256
|
max_clients_per_vault: 256
|
||||||
response_timeout_seconds: 60
|
response_timeout_seconds: 60
|
||||||
|
|
||||||
users:
|
users:
|
||||||
user_configs:
|
user_configs:
|
||||||
- name: admin
|
- name: admin
|
||||||
token: your-secure-random-token
|
token: your-secure-random-token
|
||||||
vault_access:
|
vault_access:
|
||||||
type: allow_access_to_all
|
type: allow_access_to_all
|
||||||
- name: alice
|
- name: alice
|
||||||
token: alice-token
|
token: alice-token
|
||||||
vault_access:
|
vault_access:
|
||||||
type: allow_list
|
type: allow_list
|
||||||
allowed:
|
allowed:
|
||||||
- personal
|
- personal
|
||||||
- shared
|
- shared
|
||||||
- name: bob
|
- name: bob
|
||||||
token: bob-token
|
token: bob-token
|
||||||
vault_access:
|
vault_access:
|
||||||
type: deny_list
|
type: deny_list
|
||||||
denied:
|
denied:
|
||||||
- restricted
|
- restricted
|
||||||
|
|
||||||
logging:
|
logging:
|
||||||
log_directory: logs
|
log_directory: logs
|
||||||
log_rotation: 7days
|
log_rotation: 7days
|
||||||
```
|
```
|
||||||
|
|
||||||
## Database Section
|
## Database Section
|
||||||
|
|
@ -62,10 +62,11 @@ Directory where SQLite database files are stored. One database file per vault.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
database:
|
database:
|
||||||
databases_directory_path: /data/databases
|
databases_directory_path: /data/databases
|
||||||
```
|
```
|
||||||
|
|
||||||
The directory structure:
|
The directory structure:
|
||||||
|
|
||||||
```
|
```
|
||||||
databases/
|
databases/
|
||||||
├── vault-1.db
|
├── vault-1.db
|
||||||
|
|
@ -74,6 +75,7 @@ databases/
|
||||||
```
|
```
|
||||||
|
|
||||||
**Notes**:
|
**Notes**:
|
||||||
|
|
||||||
- Path is relative to working directory or absolute
|
- Path is relative to working directory or absolute
|
||||||
- Directory must be writable by the server process
|
- Directory must be writable by the server process
|
||||||
- Ensure adequate disk space for vault data
|
- Ensure adequate disk space for vault data
|
||||||
|
|
@ -90,10 +92,11 @@ Maximum concurrent database connections per vault.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
database:
|
database:
|
||||||
max_connections_per_vault: 12
|
max_connections_per_vault: 12
|
||||||
```
|
```
|
||||||
|
|
||||||
**Tuning**:
|
**Tuning**:
|
||||||
|
|
||||||
- Higher values: Better performance under load
|
- Higher values: Better performance under load
|
||||||
- Lower values: Less memory usage
|
- Lower values: Less memory usage
|
||||||
- Typical range: 8-20
|
- Typical range: 8-20
|
||||||
|
|
@ -110,10 +113,11 @@ How long to keep database cursors alive for inactive clients.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
database:
|
database:
|
||||||
cursor_timeout_seconds: 60
|
cursor_timeout_seconds: 60
|
||||||
```
|
```
|
||||||
|
|
||||||
**Notes**:
|
**Notes**:
|
||||||
|
|
||||||
- Cursors track client sync state
|
- Cursors track client sync state
|
||||||
- Timeout too short: Clients may need to re-sync frequently
|
- Timeout too short: Clients may need to re-sync frequently
|
||||||
- Timeout too long: More memory usage
|
- Timeout too long: More memory usage
|
||||||
|
|
@ -139,6 +143,7 @@ server:
|
||||||
```
|
```
|
||||||
|
|
||||||
**Common values**:
|
**Common values**:
|
||||||
|
|
||||||
- `0.0.0.0`: Listen on all network interfaces (production)
|
- `0.0.0.0`: Listen on all network interfaces (production)
|
||||||
- `127.0.0.1`: Listen on localhost only (development/testing)
|
- `127.0.0.1`: Listen on localhost only (development/testing)
|
||||||
- Specific IP: Listen on specific interface
|
- Specific IP: Listen on specific interface
|
||||||
|
|
@ -154,10 +159,11 @@ TCP port to listen on.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
server:
|
server:
|
||||||
port: 3000
|
port: 3000
|
||||||
```
|
```
|
||||||
|
|
||||||
**Notes**:
|
**Notes**:
|
||||||
|
|
||||||
- Must be available (not in use)
|
- Must be available (not in use)
|
||||||
- Privileged ports (< 1024) require root
|
- Privileged ports (< 1024) require root
|
||||||
- Common ports: 3000, 8080, 8888
|
- Common ports: 3000, 8080, 8888
|
||||||
|
|
@ -174,16 +180,18 @@ Maximum size of HTTP request body in megabytes.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
server:
|
server:
|
||||||
max_body_size_mb: 512
|
max_body_size_mb: 512
|
||||||
```
|
```
|
||||||
|
|
||||||
**Usage**:
|
**Usage**:
|
||||||
|
|
||||||
- Limits file upload size
|
- Limits file upload size
|
||||||
- Prevents memory exhaustion attacks
|
- Prevents memory exhaustion attacks
|
||||||
- Must be larger than largest expected file
|
- Must be larger than largest expected file
|
||||||
- Consider client `max_file_size_mb` settings
|
- Consider client `max_file_size_mb` settings
|
||||||
|
|
||||||
**Tuning**:
|
**Tuning**:
|
||||||
|
|
||||||
- Small vaults (mostly text): 100 MB
|
- Small vaults (mostly text): 100 MB
|
||||||
- Medium vaults (some images): 512 MB
|
- Medium vaults (some images): 512 MB
|
||||||
- Large vaults (many images/PDFs): 1024+ MB
|
- Large vaults (many images/PDFs): 1024+ MB
|
||||||
|
|
@ -199,16 +207,18 @@ Maximum concurrent clients per vault.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
server:
|
server:
|
||||||
max_clients_per_vault: 256
|
max_clients_per_vault: 256
|
||||||
```
|
```
|
||||||
|
|
||||||
**Notes**:
|
**Notes**:
|
||||||
|
|
||||||
- Limits concurrent WebSocket connections
|
- Limits concurrent WebSocket connections
|
||||||
- Prevents resource exhaustion
|
- Prevents resource exhaustion
|
||||||
- Consider expected number of users
|
- Consider expected number of users
|
||||||
- Each client uses memory and file descriptors
|
- Each client uses memory and file descriptors
|
||||||
|
|
||||||
**Scaling**:
|
**Scaling**:
|
||||||
|
|
||||||
- Personal use: 10-50
|
- Personal use: 10-50
|
||||||
- Small team: 50-100
|
- Small team: 50-100
|
||||||
- Large team: 100-500
|
- Large team: 100-500
|
||||||
|
|
@ -224,15 +234,17 @@ Maximum time to wait for client responses.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
server:
|
server:
|
||||||
response_timeout_seconds: 60
|
response_timeout_seconds: 60
|
||||||
```
|
```
|
||||||
|
|
||||||
**Usage**:
|
**Usage**:
|
||||||
|
|
||||||
- Timeout for HTTP requests
|
- Timeout for HTTP requests
|
||||||
- Timeout for WebSocket operations
|
- Timeout for WebSocket operations
|
||||||
- Clients disconnected if unresponsive
|
- Clients disconnected if unresponsive
|
||||||
|
|
||||||
**Tuning**:
|
**Tuning**:
|
||||||
|
|
||||||
- Fast networks: 30 seconds
|
- Fast networks: 30 seconds
|
||||||
- Slow networks: 90-120 seconds
|
- Slow networks: 90-120 seconds
|
||||||
- Large file uploads: Increase proportionally
|
- Large file uploads: Increase proportionally
|
||||||
|
|
@ -259,6 +271,7 @@ logging:
|
||||||
```
|
```
|
||||||
|
|
||||||
**Notes**:
|
**Notes**:
|
||||||
|
|
||||||
- Path is relative to working directory or absolute
|
- Path is relative to working directory or absolute
|
||||||
- Directory must be writable
|
- Directory must be writable
|
||||||
- Logs are rotated based on `log_rotation`
|
- Logs are rotated based on `log_rotation`
|
||||||
|
|
@ -284,10 +297,12 @@ logging:
|
||||||
**Format**: `<number><unit>`
|
**Format**: `<number><unit>`
|
||||||
|
|
||||||
**Units**:
|
**Units**:
|
||||||
|
|
||||||
- `hours`: Hours (e.g., `12hours`, `24hours`)
|
- `hours`: Hours (e.g., `12hours`, `24hours`)
|
||||||
- `days`: Days (e.g., `7days`, `30days`)
|
- `days`: Days (e.g., `7days`, `30days`)
|
||||||
|
|
||||||
**Recommendations**:
|
**Recommendations**:
|
||||||
|
|
||||||
- Development: `24hours` or `7days`
|
- Development: `24hours` or `7days`
|
||||||
- Production: `7days` or `30days`
|
- Production: `7days` or `30days`
|
||||||
- High traffic: `24hours` (logs can be large)
|
- High traffic: `24hours` (logs can be large)
|
||||||
|
|
@ -298,55 +313,55 @@ logging:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
database:
|
database:
|
||||||
databases_directory_path: ./databases
|
databases_directory_path: ./databases
|
||||||
max_connections_per_vault: 8
|
max_connections_per_vault: 8
|
||||||
cursor_timeout_seconds: 30
|
cursor_timeout_seconds: 30
|
||||||
|
|
||||||
server:
|
server:
|
||||||
host: 127.0.0.1
|
host: 127.0.0.1
|
||||||
port: 3000
|
port: 3000
|
||||||
max_body_size_mb: 100
|
max_body_size_mb: 100
|
||||||
max_clients_per_vault: 10
|
max_clients_per_vault: 10
|
||||||
response_timeout_seconds: 30
|
response_timeout_seconds: 30
|
||||||
|
|
||||||
users:
|
users:
|
||||||
user_configs:
|
user_configs:
|
||||||
- name: dev
|
- name: dev
|
||||||
token: dev-token
|
token: dev-token
|
||||||
vault_access:
|
vault_access:
|
||||||
type: allow_access_to_all
|
type: allow_access_to_all
|
||||||
|
|
||||||
logging:
|
logging:
|
||||||
log_directory: logs
|
log_directory: logs
|
||||||
log_rotation: 24hours
|
log_rotation: 24hours
|
||||||
```
|
```
|
||||||
|
|
||||||
### Production
|
### Production
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
database:
|
database:
|
||||||
databases_directory_path: /data/databases
|
databases_directory_path: /data/databases
|
||||||
max_connections_per_vault: 16
|
max_connections_per_vault: 16
|
||||||
cursor_timeout_seconds: 120
|
cursor_timeout_seconds: 120
|
||||||
|
|
||||||
server:
|
server:
|
||||||
host: 0.0.0.0
|
host: 0.0.0.0
|
||||||
port: 3000
|
port: 3000
|
||||||
max_body_size_mb: 512
|
max_body_size_mb: 512
|
||||||
max_clients_per_vault: 256
|
max_clients_per_vault: 256
|
||||||
response_timeout_seconds: 90
|
response_timeout_seconds: 90
|
||||||
|
|
||||||
users:
|
users:
|
||||||
user_configs:
|
user_configs:
|
||||||
- name: admin
|
- name: admin
|
||||||
token: <strong-random-token>
|
token: <strong-random-token>
|
||||||
vault_access:
|
vault_access:
|
||||||
type: allow_access_to_all
|
type: allow_access_to_all
|
||||||
# Additional users...
|
# Additional users...
|
||||||
|
|
||||||
logging:
|
logging:
|
||||||
log_directory: /data/logs
|
log_directory: /data/logs
|
||||||
log_rotation: 7days
|
log_rotation: 7days
|
||||||
```
|
```
|
||||||
|
|
||||||
## Validation
|
## Validation
|
||||||
|
|
@ -362,6 +377,7 @@ tail -f logs/latest.log
|
||||||
```
|
```
|
||||||
|
|
||||||
**Common errors**:
|
**Common errors**:
|
||||||
|
|
||||||
- Missing required fields
|
- Missing required fields
|
||||||
- Invalid YAML syntax
|
- Invalid YAML syntax
|
||||||
- Invalid values (negative numbers, etc.)
|
- Invalid values (negative numbers, etc.)
|
||||||
|
|
@ -375,11 +391,11 @@ For many concurrent users:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
database:
|
database:
|
||||||
max_connections_per_vault: 20 # Increase
|
max_connections_per_vault: 20 # Increase
|
||||||
|
|
||||||
server:
|
server:
|
||||||
max_clients_per_vault: 500 # Increase
|
max_clients_per_vault: 500 # Increase
|
||||||
response_timeout_seconds: 120 # Increase for slow clients
|
response_timeout_seconds: 120 # Increase for slow clients
|
||||||
```
|
```
|
||||||
|
|
||||||
### Large Files
|
### Large Files
|
||||||
|
|
@ -388,8 +404,8 @@ For vaults with large files:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
server:
|
server:
|
||||||
max_body_size_mb: 1024 # Allow larger uploads
|
max_body_size_mb: 1024 # Allow larger uploads
|
||||||
response_timeout_seconds: 180 # More time for uploads
|
response_timeout_seconds: 180 # More time for uploads
|
||||||
```
|
```
|
||||||
|
|
||||||
### Resource-Constrained Systems
|
### Resource-Constrained Systems
|
||||||
|
|
@ -398,11 +414,11 @@ For limited CPU/memory:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
database:
|
database:
|
||||||
max_connections_per_vault: 6 # Reduce
|
max_connections_per_vault: 6 # Reduce
|
||||||
|
|
||||||
server:
|
server:
|
||||||
max_clients_per_vault: 50 # Reduce
|
max_clients_per_vault: 50 # Reduce
|
||||||
max_body_size_mb: 256 # Reduce
|
max_body_size_mb: 256 # Reduce
|
||||||
```
|
```
|
||||||
|
|
||||||
## Security Considerations
|
## Security Considerations
|
||||||
|
|
@ -431,12 +447,14 @@ server:
|
||||||
### Server won't start
|
### Server won't start
|
||||||
|
|
||||||
**Check YAML syntax**:
|
**Check YAML syntax**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Use a YAML validator
|
# Use a YAML validator
|
||||||
python -c 'import yaml, sys; yaml.safe_load(open("config.yml"))'
|
python -c 'import yaml, sys; yaml.safe_load(open("config.yml"))'
|
||||||
```
|
```
|
||||||
|
|
||||||
**Check file paths**:
|
**Check file paths**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Ensure directories exist and are writable
|
# Ensure directories exist and are writable
|
||||||
mkdir -p databases logs
|
mkdir -p databases logs
|
||||||
|
|
@ -444,6 +462,7 @@ chmod 755 databases logs
|
||||||
```
|
```
|
||||||
|
|
||||||
**Check port availability**:
|
**Check port availability**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Verify port is not in use
|
# Verify port is not in use
|
||||||
lsof -i :3000
|
lsof -i :3000
|
||||||
|
|
|
||||||
324
docs/guide/alternatives.md
Normal file
324
docs/guide/alternatives.md
Normal file
|
|
@ -0,0 +1,324 @@
|
||||||
|
# Comparison with Alternatives
|
||||||
|
|
||||||
|
VaultLink is one of several solutions for synchronizing Obsidian vaults. This page compares VaultLink with popular alternatives to help you choose the right tool.
|
||||||
|
|
||||||
|
## Key Differentiator: Editor Agnostic
|
||||||
|
|
||||||
|
**VaultLink is not tied to Obsidian.** While it includes an Obsidian plugin for convenience, VaultLink synchronizes plain text files and works with any editor:
|
||||||
|
|
||||||
|
- Edit with **Obsidian desktop** on your laptop
|
||||||
|
- Edit with **Vim** on your server
|
||||||
|
- Edit with **VS Code** on your workstation
|
||||||
|
- Edit with **Obsidian mobile** on your phone
|
||||||
|
- Use the **CLI client** for automated workflows
|
||||||
|
|
||||||
|
All changes merge automatically without conflict markers, regardless of which editor you use. This is possible because VaultLink uses [reconcile-text](/architecture/sync-algorithm#why-reconcile-text-over-crdts) for differential synchronization rather than requiring operation-level tracking.
|
||||||
|
|
||||||
|
## VaultLink's Core Strengths
|
||||||
|
|
||||||
|
Before diving into comparisons:
|
||||||
|
|
||||||
|
1. **Fully self-hosted**: Server and all components are open source
|
||||||
|
2. **Collaborative editing**: Real-time sync with operational transformation
|
||||||
|
3. **Automatic conflict resolution**: No manual intervention or paid features required
|
||||||
|
4. **Cursor tracking**: See where other users are editing
|
||||||
|
5. **Extensively tested**: Comprehensive test suite for server and client
|
||||||
|
6. **Editor freedom**: Use any text editor, not just Obsidian
|
||||||
|
7. **Production-ready**: Docker images, health checks, monitoring
|
||||||
|
|
||||||
|
## Obsidian Sync Alternatives
|
||||||
|
|
||||||
|
### Self-hosted LiveSync
|
||||||
|
|
||||||
|
**Downloads**: ~300,000
|
||||||
|
**Repository**: https://github.com/vrtmrz/obsidian-livesync
|
||||||
|
|
||||||
|
**Overview**: CouchDB/IBM Cloudant-based sync with end-to-end encryption.
|
||||||
|
|
||||||
|
| Aspect | Self-hosted LiveSync | VaultLink |
|
||||||
|
| ------------------------- | --------------------------- | -------------------------------------- |
|
||||||
|
| **Self-hosted** | Yes (CouchDB required) | Yes (single binary or Docker) |
|
||||||
|
| **Conflict resolution** | Manual or automatic (basic) | Automatic (operational transformation) |
|
||||||
|
| **Collaborative editing** | No | Yes (real-time with cursors) |
|
||||||
|
| **Editor support** | Obsidian only | Any text editor |
|
||||||
|
| **Infrastructure** | CouchDB database | SQLite (bundled) |
|
||||||
|
| **Deployment complexity** | Medium (external DB) | Low (single container) |
|
||||||
|
| **End-to-end encryption** | Yes | No (transport encryption only) |
|
||||||
|
| **Out-of-band edits** | Limited support | Full support (edit with any tool) |
|
||||||
|
|
||||||
|
**When to use LiveSync**:
|
||||||
|
|
||||||
|
- Need end-to-end encryption
|
||||||
|
- Already running CouchDB
|
||||||
|
- Only use Obsidian (no external editors)
|
||||||
|
|
||||||
|
**When to use VaultLink**:
|
||||||
|
|
||||||
|
- Want collaborative editing with multiple users
|
||||||
|
- Edit files with various tools (Vim, VS Code, etc.)
|
||||||
|
- Need simpler deployment (no external database)
|
||||||
|
- Want operational transformation for better merges
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Remotely Save
|
||||||
|
|
||||||
|
**Downloads**: ~1.1M
|
||||||
|
**Repository**: https://github.com/remotely-save/remotely-save
|
||||||
|
|
||||||
|
**Overview**: Sync to cloud storage providers (S3, Dropbox, OneDrive, WebDAV).
|
||||||
|
|
||||||
|
| Aspect | Remotely Save | VaultLink |
|
||||||
|
| ------------------------- | ---------------------------- | ------------------------ |
|
||||||
|
| **Self-hosted** | Partial (uses cloud storage) | Fully self-hosted |
|
||||||
|
| **Conflict resolution** | Paid Pro feature | Free and automatic |
|
||||||
|
| **Collaborative editing** | No | Yes |
|
||||||
|
| **Editor support** | Obsidian only | Any text editor |
|
||||||
|
| **Storage backend** | Cloud providers | Self-hosted SQLite |
|
||||||
|
| **Cost** | Free (basic) / Paid (Pro) | Free (open source) |
|
||||||
|
| **Code quality** | No tests, complex codebase | Comprehensive test suite |
|
||||||
|
| **Real-time sync** | No (periodic polling) | Yes (WebSocket) |
|
||||||
|
|
||||||
|
**When to use Remotely Save**:
|
||||||
|
|
||||||
|
- Already use cloud storage (S3, Dropbox)
|
||||||
|
- Don't need real-time sync
|
||||||
|
- Single-user scenario
|
||||||
|
|
||||||
|
**When to use VaultLink**:
|
||||||
|
|
||||||
|
- Want full control over data
|
||||||
|
- Need automatic conflict resolution without paying
|
||||||
|
- Want real-time collaborative editing
|
||||||
|
- Value code quality and testing
|
||||||
|
|
||||||
|
**Note**: Remotely Save's conflict resolution is a paid feature. VaultLink provides superior automatic merging for free.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Relay
|
||||||
|
|
||||||
|
**Downloads**: ~24,000
|
||||||
|
**Repository**: https://github.com/No-Instructions/Relay
|
||||||
|
|
||||||
|
**Overview**: CRDT-based sync with proprietary server component.
|
||||||
|
|
||||||
|
| Aspect | Relay | VaultLink |
|
||||||
|
| -------------------------- | ---------------------------- | ----------------------- |
|
||||||
|
| **Self-hosted** | No (proprietary server) | Yes (fully open source) |
|
||||||
|
| **Conflict resolution** | CRDT (automatic) | OT (automatic) |
|
||||||
|
| **Collaborative editing** | Yes | Yes |
|
||||||
|
| **Editor support** | Obsidian only | Any text editor |
|
||||||
|
| **Out-of-band edits** | No (breaks CRDT consistency) | Yes (differential sync) |
|
||||||
|
| **Server open source** | No | Yes |
|
||||||
|
| **Infrastructure control** | Limited | Full |
|
||||||
|
| **Per-file overhead** | High (CRDT metadata) | Low (version history) |
|
||||||
|
|
||||||
|
**When to use Relay**:
|
||||||
|
|
||||||
|
- Want hosted solution (don't self-host)
|
||||||
|
- Only edit within Obsidian
|
||||||
|
- Don't need out-of-band editing
|
||||||
|
|
||||||
|
**When to use VaultLink**:
|
||||||
|
|
||||||
|
- Need fully open source solution
|
||||||
|
- Want to self-host completely
|
||||||
|
- Edit files outside Obsidian (Vim, VS Code)
|
||||||
|
- Value infrastructure control
|
||||||
|
|
||||||
|
**Critical limitation**: Relay's CRDT approach requires tracking every operation within Obsidian. Editing files outside Obsidian breaks the CRDT state. VaultLink's differential sync works regardless of how files are edited.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Obsidian Git
|
||||||
|
|
||||||
|
**Downloads**: ~1.4M
|
||||||
|
**Repository**: https://github.com/denolehov/obsidian-git
|
||||||
|
|
||||||
|
**Overview**: Uses Git for version control and synchronization.
|
||||||
|
|
||||||
|
| Aspect | Obsidian Git | VaultLink |
|
||||||
|
| ------------------------- | ----------------------------- | ----------------------- |
|
||||||
|
| **Self-hosted** | Yes (Git server) | Yes (sync server) |
|
||||||
|
| **Conflict resolution** | Manual (conflict markers) | Automatic (no markers) |
|
||||||
|
| **Collaborative editing** | No | Yes (real-time) |
|
||||||
|
| **Editor support** | Any (it's Git) | Any (differential sync) |
|
||||||
|
| **Version history** | Full Git history | Document versions |
|
||||||
|
| **Real-time sync** | No (commit-based) | Yes (instant) |
|
||||||
|
| **Merge conflicts** | Manual resolution | Automatic |
|
||||||
|
| **Learning curve** | High (Git knowledge required) | Low |
|
||||||
|
| **Workflow interruption** | Yes (resolve conflicts) | No |
|
||||||
|
|
||||||
|
**When to use Obsidian Git**:
|
||||||
|
|
||||||
|
- Need full version control (branches, tags, etc.)
|
||||||
|
- Already familiar with Git workflows
|
||||||
|
- Want integration with existing Git repos
|
||||||
|
- Don't mind manual conflict resolution
|
||||||
|
|
||||||
|
**When to use VaultLink**:
|
||||||
|
|
||||||
|
- Want automatic conflict-free merging
|
||||||
|
- Need real-time collaborative editing
|
||||||
|
- Don't want workflow interruptions from merge conflicts
|
||||||
|
- Prefer simpler mental model (sync, not commits)
|
||||||
|
|
||||||
|
**Key difference**: Git requires manual conflict resolution with `<<<<<<<` markers. VaultLink automatically merges all changes using operational transformation, never interrupting your workflow.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Syncthing Integration
|
||||||
|
|
||||||
|
**Downloads**: ~22,600
|
||||||
|
**Repository**: https://github.com/LBF38/obsidian-syncthing-integration
|
||||||
|
|
||||||
|
**Overview**: Wrapper around Syncthing for file synchronization.
|
||||||
|
|
||||||
|
| Aspect | Syncthing Integration | VaultLink |
|
||||||
|
| ------------------------- | ------------------------------ | ----------------- |
|
||||||
|
| **Self-hosted** | Yes (Syncthing) | Yes (sync server) |
|
||||||
|
| **Conflict resolution** | Manual | Automatic |
|
||||||
|
| **Collaborative editing** | No | Yes |
|
||||||
|
| **Editor support** | Any | Any |
|
||||||
|
| **Status** | Unfinished | Production-ready |
|
||||||
|
| **Conflict files** | Creates `.sync-conflict` files | No conflict files |
|
||||||
|
| **Real-time sync** | Yes | Yes |
|
||||||
|
| **Automatic merging** | No | Yes |
|
||||||
|
|
||||||
|
**When to use Syncthing Integration**:
|
||||||
|
|
||||||
|
- Already use Syncthing for other files
|
||||||
|
- Don't need automatic conflict resolution
|
||||||
|
- Single-user with multiple devices
|
||||||
|
|
||||||
|
**When to use VaultLink**:
|
||||||
|
|
||||||
|
- Want automatic conflict resolution
|
||||||
|
- Need collaborative editing
|
||||||
|
- Want production-ready solution
|
||||||
|
- Don't want to manage conflict files
|
||||||
|
|
||||||
|
**Status note**: Syncthing Integration is marked as unfinished. VaultLink is production-ready with comprehensive testing.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Remotely Sync
|
||||||
|
|
||||||
|
**Downloads**: ~38,000
|
||||||
|
**Repository**: https://github.com/sboesen/remotely-sync
|
||||||
|
|
||||||
|
**Overview**: Similar to Remotely Save, syncs to cloud storage.
|
||||||
|
|
||||||
|
| Aspect | Remotely Sync | VaultLink |
|
||||||
|
| ----------------------- | ----------------------- | ------------------- |
|
||||||
|
| **Self-hosted** | Partial (cloud storage) | Fully self-hosted |
|
||||||
|
| **Conflict resolution** | Limited/Paid | Free and automatic |
|
||||||
|
| **Code quality** | No tests | Comprehensive tests |
|
||||||
|
| **Maintenance** | Low activity | Active development |
|
||||||
|
|
||||||
|
**Same concerns as Remotely Save**: No test suite, conflict resolution limitations, cloud storage dependency.
|
||||||
|
|
||||||
|
**When to use VaultLink**: See Remotely Save comparison above.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### SyncFTP
|
||||||
|
|
||||||
|
**Downloads**: ~5,000
|
||||||
|
**Repository**: https://github.com/alex-donnan/SyncFTP
|
||||||
|
|
||||||
|
**Overview**: Simple FTP-based file synchronization.
|
||||||
|
|
||||||
|
| Aspect | SyncFTP | VaultLink |
|
||||||
|
| ------------------------- | ---------------------- | ---------------- |
|
||||||
|
| **Conflict resolution** | None (last write wins) | Automatic (OT) |
|
||||||
|
| **Data loss risk** | High (overwrites) | None (merges) |
|
||||||
|
| **Collaborative editing** | No | Yes |
|
||||||
|
| **Sophistication** | Minimal | Production-grade |
|
||||||
|
|
||||||
|
**When to use SyncFTP**: Don't use SyncFTP for any scenario where data integrity matters.
|
||||||
|
|
||||||
|
**When to use VaultLink**: Any scenario requiring reliable synchronization.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feature Comparison Matrix
|
||||||
|
|
||||||
|
| Feature | VaultLink | LiveSync | Relay | Git | Remotely Save | Syncthing |
|
||||||
|
| --------------------------------- | --------- | -------- | ----- | --- | ------------- | --------- |
|
||||||
|
| **Fully open source** | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ |
|
||||||
|
| **Self-hosted** | ✅ | ✅ | ❌ | ✅ | Partial | ✅ |
|
||||||
|
| **Automatic conflict resolution** | ✅ | Basic | ✅ | ❌ | Paid | ❌ |
|
||||||
|
| **Real-time sync** | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ |
|
||||||
|
| **Collaborative editing** | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
||||||
|
| **Cursor tracking** | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
||||||
|
| **Editor agnostic** | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ |
|
||||||
|
| **Out-of-band edits** | ✅ | Limited | ❌ | ✅ | ❌ | ✅ |
|
||||||
|
| **No conflict markers** | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
|
||||||
|
| **Comprehensive tests** | ✅ | ❌ | ❌ | N/A | ❌ | N/A |
|
||||||
|
| **Simple deployment** | ✅ | ❌ | N/A | ❌ | ✅ | ❌ |
|
||||||
|
| **Low infrastructure** | ✅ | ❌ | N/A | ✅ | ✅ | ✅ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## VaultLink's Unique Position
|
||||||
|
|
||||||
|
VaultLink is the **only** solution that combines:
|
||||||
|
|
||||||
|
1. **Fully open source** self-hosted server
|
||||||
|
2. **Editor agnostic** operation (not locked to Obsidian)
|
||||||
|
3. **Automatic conflict-free merging** using operational transformation
|
||||||
|
4. **Real-time collaborative editing** with cursor tracking
|
||||||
|
5. **Differential synchronization** supporting out-of-band edits
|
||||||
|
6. **Comprehensive test coverage** ensuring reliability
|
||||||
|
7. **Simple deployment** via Docker or single binary
|
||||||
|
|
||||||
|
## Use Case Recommendations
|
||||||
|
|
||||||
|
### Choose VaultLink when you:
|
||||||
|
|
||||||
|
- Edit vaults with multiple editors (Obsidian + Vim + VS Code)
|
||||||
|
- Need real-time collaboration with teammates
|
||||||
|
- Want automatic conflict resolution without manual intervention
|
||||||
|
- Value full control over infrastructure
|
||||||
|
- Need production-ready reliability with comprehensive testing
|
||||||
|
- Want to edit files while offline and sync later seamlessly
|
||||||
|
|
||||||
|
### Consider alternatives when you:
|
||||||
|
|
||||||
|
- **LiveSync**: Need end-to-end encryption and only use Obsidian
|
||||||
|
- **Git**: Need full version control with branches and advanced Git features
|
||||||
|
- **Remotely Save**: Already committed to cloud storage providers
|
||||||
|
- **Syncthing**: Already use Syncthing and don't need automatic merging
|
||||||
|
|
||||||
|
## Migration from Other Solutions
|
||||||
|
|
||||||
|
VaultLink works with plain Markdown files, making migration simple:
|
||||||
|
|
||||||
|
1. **From Git**: Clone your repo, point VaultLink to the directory
|
||||||
|
2. **From cloud sync**: Download files, configure VaultLink client
|
||||||
|
3. **From LiveSync**: Export vault, import to VaultLink
|
||||||
|
4. **From Syncthing**: Point VaultLink to synced directory
|
||||||
|
|
||||||
|
All solutions work with the same Markdown files—VaultLink just syncs them better.
|
||||||
|
|
||||||
|
## Beyond Obsidian
|
||||||
|
|
||||||
|
Because VaultLink is editor-agnostic, you can use it for:
|
||||||
|
|
||||||
|
- **Documentation teams**: Sync technical docs edited in VS Code
|
||||||
|
- **Academic writing**: Collaborate on papers with various Markdown editors
|
||||||
|
- **Personal knowledge bases**: Use Obsidian on mobile, Vim on servers
|
||||||
|
- **Automated workflows**: CLI client for backup systems and CI/CD
|
||||||
|
- **Multi-tool workflows**: Different team members use different editors
|
||||||
|
|
||||||
|
VaultLink doesn't lock you into Obsidian—it's a general-purpose differential sync system that happens to work excellently with Obsidian vaults.
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
Ready to try VaultLink?
|
||||||
|
|
||||||
|
- [Get started →](/guide/getting-started)
|
||||||
|
- [Understand the architecture →](/architecture/)
|
||||||
|
- [See how sync works →](/architecture/sync-algorithm)
|
||||||
|
|
@ -67,20 +67,20 @@ Create `docker-compose.yml`:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
vaultlink-cli:
|
vaultlink-cli:
|
||||||
image: ghcr.io/schmelczer/vault-link-cli:latest
|
image: ghcr.io/schmelczer/vault-link-cli:latest
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
- ./vault:/vault
|
- ./vault:/vault
|
||||||
command:
|
command:
|
||||||
- "-l"
|
- "-l"
|
||||||
- "/vault"
|
- "/vault"
|
||||||
- "-r"
|
- "-r"
|
||||||
- "wss://sync.example.com"
|
- "wss://sync.example.com"
|
||||||
- "-t"
|
- "-t"
|
||||||
- "your-token"
|
- "your-token"
|
||||||
- "-v"
|
- "-v"
|
||||||
- "default"
|
- "default"
|
||||||
```
|
```
|
||||||
|
|
||||||
Start the client:
|
Start the client:
|
||||||
|
|
@ -93,22 +93,22 @@ docker compose up -d
|
||||||
|
|
||||||
### Required Arguments
|
### Required Arguments
|
||||||
|
|
||||||
| Argument | Short | Description | Example |
|
| Argument | Short | Description | Example |
|
||||||
|----------|-------|-------------|---------|
|
| -------------- | ----- | ----------------------- | ------------------------ |
|
||||||
| `--local-path` | `-l` | Local directory to sync | `/vault` |
|
| `--local-path` | `-l` | Local directory to sync | `/vault` |
|
||||||
| `--remote-uri` | `-r` | Server WebSocket URI | `wss://sync.example.com` |
|
| `--remote-uri` | `-r` | Server WebSocket URI | `wss://sync.example.com` |
|
||||||
| `--token` | `-t` | Authentication token | `abc123...` |
|
| `--token` | `-t` | Authentication token | `abc123...` |
|
||||||
| `--vault-name` | `-v` | Vault name on server | `default` |
|
| `--vault-name` | `-v` | Vault name on server | `default` |
|
||||||
|
|
||||||
### Optional Arguments
|
### Optional Arguments
|
||||||
|
|
||||||
| Argument | Default | Description |
|
| Argument | Default | Description |
|
||||||
|----------|---------|-------------|
|
| ------------------------------- | ------- | -------------------------------------- |
|
||||||
| `--sync-concurrency` | `1` | Concurrent file operations |
|
| `--sync-concurrency` | `1` | Concurrent file operations |
|
||||||
| `--max-file-size-mb` | `10` | Max file size in MB |
|
| `--max-file-size-mb` | `10` | Max file size in MB |
|
||||||
| `--ignore-pattern` | - | Glob pattern to ignore (repeatable) |
|
| `--ignore-pattern` | - | Glob pattern to ignore (repeatable) |
|
||||||
| `--websocket-retry-interval-ms` | `3500` | Reconnection interval |
|
| `--websocket-retry-interval-ms` | `3500` | Reconnection interval |
|
||||||
| `--log-level` | `INFO` | Log level: DEBUG, INFO, WARNING, ERROR |
|
| `--log-level` | `INFO` | Log level: DEBUG, INFO, WARNING, ERROR |
|
||||||
|
|
||||||
### Environment Variables
|
### Environment Variables
|
||||||
|
|
||||||
|
|
@ -228,6 +228,7 @@ docker inspect --format='{{json .State.Health}}' vaultlink-sync | jq
|
||||||
```
|
```
|
||||||
|
|
||||||
Health check verifies:
|
Health check verifies:
|
||||||
|
|
||||||
- Health file exists
|
- Health file exists
|
||||||
- Status updated within last 30 seconds
|
- Status updated within last 30 seconds
|
||||||
- WebSocket connection is active
|
- WebSocket connection is active
|
||||||
|
|
@ -236,14 +237,14 @@ Configure custom health check:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
vaultlink-cli:
|
vaultlink-cli:
|
||||||
image: ghcr.io/schmelczer/vault-link-cli:latest
|
image: ghcr.io/schmelczer/vault-link-cli:latest
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "node", "/app/healthcheck.js"]
|
test: ["CMD", "node", "/app/healthcheck.js"]
|
||||||
interval: 15s
|
interval: 15s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
start_period: 20s
|
start_period: 20s
|
||||||
```
|
```
|
||||||
|
|
||||||
### Read-Only Vault
|
### Read-Only Vault
|
||||||
|
|
@ -351,21 +352,25 @@ services:
|
||||||
### Client won't connect
|
### Client won't connect
|
||||||
|
|
||||||
**Check server accessibility**:
|
**Check server accessibility**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl https://sync.example.com/vaults/test/ping
|
curl https://sync.example.com/vaults/test/ping
|
||||||
```
|
```
|
||||||
|
|
||||||
**Verify WebSocket protocol**:
|
**Verify WebSocket protocol**:
|
||||||
|
|
||||||
- Use `ws://` for HTTP servers
|
- Use `ws://` for HTTP servers
|
||||||
- Use `wss://` for HTTPS servers
|
- Use `wss://` for HTTPS servers
|
||||||
|
|
||||||
**Check authentication**:
|
**Check authentication**:
|
||||||
|
|
||||||
- Token must match server config
|
- Token must match server config
|
||||||
- User must have access to the vault
|
- User must have access to the vault
|
||||||
|
|
||||||
### Permission errors
|
### Permission errors
|
||||||
|
|
||||||
**Docker volume permissions**:
|
**Docker volume permissions**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Ensure directory is writable
|
# Ensure directory is writable
|
||||||
chmod 755 /path/to/vault
|
chmod 755 /path/to/vault
|
||||||
|
|
@ -375,6 +380,7 @@ docker run --rm ghcr.io/schmelczer/vault-link-cli:latest id
|
||||||
```
|
```
|
||||||
|
|
||||||
**SELinux issues**:
|
**SELinux issues**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Add :z flag to volume mount
|
# Add :z flag to volume mount
|
||||||
docker run -v /path/to/vault:/vault:z ...
|
docker run -v /path/to/vault:/vault:z ...
|
||||||
|
|
@ -383,14 +389,17 @@ docker run -v /path/to/vault:/vault:z ...
|
||||||
### Files not syncing
|
### Files not syncing
|
||||||
|
|
||||||
**Check ignore patterns**:
|
**Check ignore patterns**:
|
||||||
|
|
||||||
- View logs to see which files are skipped
|
- View logs to see which files are skipped
|
||||||
- Ensure patterns don't match unintentionally
|
- Ensure patterns don't match unintentionally
|
||||||
|
|
||||||
**File size limits**:
|
**File size limits**:
|
||||||
|
|
||||||
- Check `--max-file-size-mb` setting
|
- Check `--max-file-size-mb` setting
|
||||||
- Large files are skipped with a warning
|
- Large files are skipped with a warning
|
||||||
|
|
||||||
**Check metadata**:
|
**Check metadata**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# View sync metadata
|
# View sync metadata
|
||||||
cat /path/to/vault/.vaultlink/metadata.json
|
cat /path/to/vault/.vaultlink/metadata.json
|
||||||
|
|
@ -399,33 +408,39 @@ cat /path/to/vault/.vaultlink/metadata.json
|
||||||
### High memory usage
|
### High memory usage
|
||||||
|
|
||||||
**Reduce concurrency**:
|
**Reduce concurrency**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
--sync-concurrency 1
|
--sync-concurrency 1
|
||||||
```
|
```
|
||||||
|
|
||||||
**Limit file sizes**:
|
**Limit file sizes**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
--max-file-size-mb 5
|
--max-file-size-mb 5
|
||||||
```
|
```
|
||||||
|
|
||||||
**Check vault size**:
|
**Check vault size**:
|
||||||
|
|
||||||
- Very large vaults may need more resources
|
- Very large vaults may need more resources
|
||||||
- Consider splitting into multiple vaults
|
- Consider splitting into multiple vaults
|
||||||
|
|
||||||
### Connection keeps dropping
|
### Connection keeps dropping
|
||||||
|
|
||||||
**Increase retry interval**:
|
**Increase retry interval**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
--websocket-retry-interval-ms 5000
|
--websocket-retry-interval-ms 5000
|
||||||
```
|
```
|
||||||
|
|
||||||
**Check network stability**:
|
**Check network stability**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Monitor connection
|
# Monitor connection
|
||||||
docker logs -f vaultlink-sync | grep -i websocket
|
docker logs -f vaultlink-sync | grep -i websocket
|
||||||
```
|
```
|
||||||
|
|
||||||
**Server timeout settings**:
|
**Server timeout settings**:
|
||||||
|
|
||||||
- Verify reverse proxy WebSocket timeout
|
- Verify reverse proxy WebSocket timeout
|
||||||
- Check server `response_timeout_seconds`
|
- Check server `response_timeout_seconds`
|
||||||
|
|
||||||
|
|
@ -503,6 +518,7 @@ WantedBy=multi-user.target
|
||||||
```
|
```
|
||||||
|
|
||||||
Enable and start:
|
Enable and start:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo systemctl daemon-reload
|
sudo systemctl daemon-reload
|
||||||
sudo systemctl enable vaultlink-cli
|
sudo systemctl enable vaultlink-cli
|
||||||
|
|
|
||||||
|
|
@ -74,9 +74,9 @@ You can connect to VaultLink using either the Obsidian plugin or the standalone
|
||||||
2. Browse community plugins and search for "VaultLink"
|
2. Browse community plugins and search for "VaultLink"
|
||||||
3. Install and enable the plugin
|
3. Install and enable the plugin
|
||||||
4. Configure the plugin:
|
4. Configure the plugin:
|
||||||
- **Server URL**: `ws://localhost:3000` (or your server address)
|
- **Server URL**: `ws://localhost:3000` (or your server address)
|
||||||
- **Token**: The token from your `config.yml`
|
- **Token**: The token from your `config.yml`
|
||||||
- **Vault Name**: `default` (or any name you choose)
|
- **Vault Name**: `default` (or any name you choose)
|
||||||
|
|
||||||
[Read the full Obsidian plugin guide →](/guide/obsidian-plugin)
|
[Read the full Obsidian plugin guide →](/guide/obsidian-plugin)
|
||||||
|
|
||||||
|
|
@ -119,20 +119,20 @@ To add more users or restrict vault access:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
users:
|
users:
|
||||||
user_configs:
|
user_configs:
|
||||||
- name: alice
|
- name: alice
|
||||||
token: alice-secure-token
|
token: alice-secure-token
|
||||||
vault_access:
|
vault_access:
|
||||||
type: allow_list
|
type: allow_list
|
||||||
allowed:
|
allowed:
|
||||||
- personal
|
- personal
|
||||||
- shared
|
- shared
|
||||||
- name: bob
|
- name: bob
|
||||||
token: bob-secure-token
|
token: bob-secure-token
|
||||||
vault_access:
|
vault_access:
|
||||||
type: allow_list
|
type: allow_list
|
||||||
allowed:
|
allowed:
|
||||||
- shared
|
- shared
|
||||||
```
|
```
|
||||||
|
|
||||||
[Learn about authentication configuration →](/config/authentication)
|
[Learn about authentication configuration →](/config/authentication)
|
||||||
|
|
@ -159,11 +159,13 @@ Want to understand how VaultLink works under the hood?
|
||||||
### Server won't start
|
### Server won't start
|
||||||
|
|
||||||
Check Docker logs:
|
Check Docker logs:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker logs vaultlink-server
|
docker logs vaultlink-server
|
||||||
```
|
```
|
||||||
|
|
||||||
Common issues:
|
Common issues:
|
||||||
|
|
||||||
- Port 3000 already in use: Change the port mapping `-p 3001:3000`
|
- Port 3000 already in use: Change the port mapping `-p 3001:3000`
|
||||||
- Config file errors: Validate YAML syntax
|
- Config file errors: Validate YAML syntax
|
||||||
- Permission issues: Ensure the volume mount is writable
|
- Permission issues: Ensure the volume mount is writable
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ After installation, configure the plugin in **Settings → VaultLink**.
|
||||||
### Required Settings
|
### Required Settings
|
||||||
|
|
||||||
#### Server URL
|
#### Server URL
|
||||||
|
|
||||||
The WebSocket URL of your sync server.
|
The WebSocket URL of your sync server.
|
||||||
|
|
||||||
- **Development/Local**: `ws://localhost:3000`
|
- **Development/Local**: `ws://localhost:3000`
|
||||||
|
|
@ -37,14 +38,17 @@ Use `ws://` for unencrypted connections and `wss://` for SSL connections (produc
|
||||||
:::
|
:::
|
||||||
|
|
||||||
#### Authentication Token
|
#### Authentication Token
|
||||||
|
|
||||||
Your authentication token from the server's `config.yml`.
|
Your authentication token from the server's `config.yml`.
|
||||||
|
|
||||||
Generate a secure token:
|
Generate a secure token:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
openssl rand -hex 32
|
openssl rand -hex 32
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Vault Name
|
#### Vault Name
|
||||||
|
|
||||||
The name of the vault on the server. Can be any string.
|
The name of the vault on the server. Can be any string.
|
||||||
|
|
||||||
Multiple Obsidian vaults can sync to the same server vault name (for shared vaults), or use unique names for separate vaults.
|
Multiple Obsidian vaults can sync to the same server vault name (for shared vaults), or use unique names for separate vaults.
|
||||||
|
|
@ -52,26 +56,34 @@ Multiple Obsidian vaults can sync to the same server vault name (for shared vaul
|
||||||
### Optional Settings
|
### Optional Settings
|
||||||
|
|
||||||
#### Sync Concurrency
|
#### Sync Concurrency
|
||||||
|
|
||||||
Number of files to sync simultaneously.
|
Number of files to sync simultaneously.
|
||||||
|
|
||||||
- **Default**: 1
|
- **Default**: 1
|
||||||
- **Range**: 1-10
|
- **Range**: 1-10
|
||||||
- Higher values = faster initial sync, more resource usage
|
- Higher values = faster initial sync, more resource usage
|
||||||
|
|
||||||
#### Max File Size
|
#### Max File Size
|
||||||
|
|
||||||
Maximum file size to sync (in MB).
|
Maximum file size to sync (in MB).
|
||||||
|
|
||||||
- **Default**: 10
|
- **Default**: 10
|
||||||
- Files larger than this are skipped
|
- Files larger than this are skipped
|
||||||
|
|
||||||
#### Ignore Patterns
|
#### Ignore Patterns
|
||||||
|
|
||||||
Glob patterns for files to exclude from sync.
|
Glob patterns for files to exclude from sync.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
- `*.tmp` - Ignore temporary files
|
- `*.tmp` - Ignore temporary files
|
||||||
- `.trash/**` - Ignore trash folder
|
- `.trash/**` - Ignore trash folder
|
||||||
- `private/**` - Ignore private directory
|
- `private/**` - Ignore private directory
|
||||||
|
|
||||||
#### WebSocket Retry Interval
|
#### WebSocket Retry Interval
|
||||||
|
|
||||||
Milliseconds between reconnection attempts when disconnected.
|
Milliseconds between reconnection attempts when disconnected.
|
||||||
|
|
||||||
- **Default**: 3500ms
|
- **Default**: 3500ms
|
||||||
- Increase for flaky networks to avoid connection spam
|
- Increase for flaky networks to avoid connection spam
|
||||||
|
|
||||||
|
|
@ -172,24 +184,26 @@ Share specific folders while keeping others private:
|
||||||
### Plugin won't connect
|
### Plugin won't connect
|
||||||
|
|
||||||
1. **Verify server is running**:
|
1. **Verify server is running**:
|
||||||
```bash
|
|
||||||
curl http://your-server:3000/vaults/test/ping
|
```bash
|
||||||
```
|
curl http://your-server:3000/vaults/test/ping
|
||||||
Should return `pong`
|
```
|
||||||
|
|
||||||
|
Should return `pong`
|
||||||
|
|
||||||
2. **Check URL format**:
|
2. **Check URL format**:
|
||||||
- Local: `ws://localhost:3000`
|
- Local: `ws://localhost:3000`
|
||||||
- Remote (SSL): `wss://sync.example.com`
|
- Remote (SSL): `wss://sync.example.com`
|
||||||
- Don't include `/vault/name` in the URL
|
- Don't include `/vault/name` in the URL
|
||||||
|
|
||||||
3. **Verify token**:
|
3. **Verify token**:
|
||||||
- Must match server config exactly
|
- Must match server config exactly
|
||||||
- No extra spaces or quotes
|
- No extra spaces or quotes
|
||||||
- Check server logs for authentication errors
|
- Check server logs for authentication errors
|
||||||
|
|
||||||
4. **Check firewall**:
|
4. **Check firewall**:
|
||||||
- Ensure port is accessible from your network
|
- Ensure port is accessible from your network
|
||||||
- For mobile, server must be publicly accessible or use VPN
|
- For mobile, server must be publicly accessible or use VPN
|
||||||
|
|
||||||
### Files not syncing
|
### Files not syncing
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,21 +35,21 @@ Create `docker-compose.yml`:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
vaultlink-server:
|
vaultlink-server:
|
||||||
image: ghcr.io/schmelczer/vault-link-server:latest
|
image: ghcr.io/schmelczer/vault-link-server:latest
|
||||||
container_name: vaultlink-server
|
container_name: vaultlink-server
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/data
|
- ./data:/data
|
||||||
command: ["/app/sync_server", "/data/config.yml"]
|
command: ["/app/sync_server", "/data/config.yml"]
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:3000/vaults/fake/ping"]
|
test: ["CMD", "curl", "-f", "http://localhost:3000/vaults/fake/ping"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 3
|
retries: 3
|
||||||
start_period: 10s
|
start_period: 10s
|
||||||
```
|
```
|
||||||
|
|
||||||
Start the server:
|
Start the server:
|
||||||
|
|
@ -76,6 +76,7 @@ chmod +x sync_server-linux-x86_64
|
||||||
### Build from Source
|
### Build from Source
|
||||||
|
|
||||||
Requirements:
|
Requirements:
|
||||||
|
|
||||||
- Rust 1.89.0 or later
|
- Rust 1.89.0 or later
|
||||||
- SQLite development headers
|
- SQLite development headers
|
||||||
- SQLx CLI
|
- SQLx CLI
|
||||||
|
|
@ -106,27 +107,27 @@ Create a `config.yml` file with your server configuration:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
database:
|
database:
|
||||||
databases_directory_path: databases
|
databases_directory_path: databases
|
||||||
max_connections_per_vault: 12
|
max_connections_per_vault: 12
|
||||||
cursor_timeout_seconds: 60
|
cursor_timeout_seconds: 60
|
||||||
|
|
||||||
server:
|
server:
|
||||||
host: 0.0.0.0
|
host: 0.0.0.0
|
||||||
port: 3000
|
port: 3000
|
||||||
max_body_size_mb: 512
|
max_body_size_mb: 512
|
||||||
max_clients_per_vault: 256
|
max_clients_per_vault: 256
|
||||||
response_timeout_seconds: 60
|
response_timeout_seconds: 60
|
||||||
|
|
||||||
users:
|
users:
|
||||||
user_configs:
|
user_configs:
|
||||||
- name: admin
|
- name: admin
|
||||||
token: your-secure-random-token-here
|
token: your-secure-random-token-here
|
||||||
vault_access:
|
vault_access:
|
||||||
type: allow_access_to_all
|
type: allow_access_to_all
|
||||||
|
|
||||||
logging:
|
logging:
|
||||||
log_directory: logs
|
log_directory: logs
|
||||||
log_rotation: 7days
|
log_rotation: 7days
|
||||||
```
|
```
|
||||||
|
|
||||||
### Configuration Fields
|
### Configuration Fields
|
||||||
|
|
@ -192,6 +193,7 @@ server {
|
||||||
```
|
```
|
||||||
|
|
||||||
Reload Nginx:
|
Reload Nginx:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo nginx -t
|
sudo nginx -t
|
||||||
sudo systemctl reload nginx
|
sudo systemctl reload nginx
|
||||||
|
|
@ -208,6 +210,7 @@ sync.example.com {
|
||||||
```
|
```
|
||||||
|
|
||||||
Start Caddy:
|
Start Caddy:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
caddy run --config Caddyfile
|
caddy run --config Caddyfile
|
||||||
```
|
```
|
||||||
|
|
@ -269,6 +272,7 @@ find /backup/vaultlink -type d -mtime +30 -exec rm -rf {} +
|
||||||
```
|
```
|
||||||
|
|
||||||
Run daily via cron:
|
Run daily via cron:
|
||||||
|
|
||||||
```cron
|
```cron
|
||||||
0 2 * * * /opt/vaultlink/backup.sh
|
0 2 * * * /opt/vaultlink/backup.sh
|
||||||
```
|
```
|
||||||
|
|
@ -293,12 +297,14 @@ For advanced monitoring, collect Docker stats or implement custom metrics.
|
||||||
#### Log Monitoring
|
#### Log Monitoring
|
||||||
|
|
||||||
Logs are written to the configured `log_directory`. Monitor for:
|
Logs are written to the configured `log_directory`. Monitor for:
|
||||||
|
|
||||||
- Connection failures
|
- Connection failures
|
||||||
- Authentication errors
|
- Authentication errors
|
||||||
- Database errors
|
- Database errors
|
||||||
- WebSocket disconnections
|
- WebSocket disconnections
|
||||||
|
|
||||||
Example log watching:
|
Example log watching:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
tail -f /data/logs/*.log | grep -i error
|
tail -f /data/logs/*.log | grep -i error
|
||||||
```
|
```
|
||||||
|
|
@ -316,11 +322,13 @@ VaultLink currently uses SQLite, which limits horizontal scaling. For multiple s
|
||||||
### Vertical Scaling
|
### Vertical Scaling
|
||||||
|
|
||||||
Increase resources for the server:
|
Increase resources for the server:
|
||||||
|
|
||||||
- More CPU for handling concurrent connections
|
- More CPU for handling concurrent connections
|
||||||
- More RAM for database caching
|
- More RAM for database caching
|
||||||
- Faster storage (SSD) for database operations
|
- Faster storage (SSD) for database operations
|
||||||
|
|
||||||
Tune configuration:
|
Tune configuration:
|
||||||
|
|
||||||
- Increase `max_clients_per_vault` for more concurrent users
|
- Increase `max_clients_per_vault` for more concurrent users
|
||||||
- Increase `max_connections_per_vault` for database performance
|
- Increase `max_connections_per_vault` for database performance
|
||||||
- Adjust `max_body_size_mb` based on typical file sizes
|
- Adjust `max_body_size_mb` based on typical file sizes
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ VaultLink consists of three main components:
|
||||||
### Sync Server
|
### Sync Server
|
||||||
|
|
||||||
A Rust-based WebSocket server that handles:
|
A Rust-based WebSocket server that handles:
|
||||||
|
|
||||||
- Real-time bidirectional synchronization
|
- Real-time bidirectional synchronization
|
||||||
- Document versioning with SQLite
|
- Document versioning with SQLite
|
||||||
- User authentication and vault access control
|
- User authentication and vault access control
|
||||||
|
|
@ -17,6 +18,7 @@ A Rust-based WebSocket server that handles:
|
||||||
### Obsidian Plugin
|
### Obsidian Plugin
|
||||||
|
|
||||||
A native Obsidian plugin that:
|
A native Obsidian plugin that:
|
||||||
|
|
||||||
- Integrates sync directly into your Obsidian workflow
|
- Integrates sync directly into your Obsidian workflow
|
||||||
- Provides real-time updates as you edit
|
- Provides real-time updates as you edit
|
||||||
- Handles file watching and automatic synchronization
|
- Handles file watching and automatic synchronization
|
||||||
|
|
@ -25,6 +27,7 @@ A native Obsidian plugin that:
|
||||||
### CLI Client
|
### CLI Client
|
||||||
|
|
||||||
A standalone synchronization client that:
|
A standalone synchronization client that:
|
||||||
|
|
||||||
- Syncs vaults without requiring Obsidian
|
- Syncs vaults without requiring Obsidian
|
||||||
- Perfect for servers, automation, or backup systems
|
- Perfect for servers, automation, or backup systems
|
||||||
- Provides file watching and bidirectional sync
|
- Provides file watching and bidirectional sync
|
||||||
|
|
@ -39,6 +42,7 @@ Changes are synchronized immediately via WebSocket connections. When multiple us
|
||||||
### Self-Hosted Architecture
|
### Self-Hosted Architecture
|
||||||
|
|
||||||
Run the sync server on your own infrastructure:
|
Run the sync server on your own infrastructure:
|
||||||
|
|
||||||
- Full control over data storage and access
|
- Full control over data storage and access
|
||||||
- No dependency on third-party services
|
- No dependency on third-party services
|
||||||
- Configurable authentication and authorization
|
- Configurable authentication and authorization
|
||||||
|
|
@ -47,6 +51,7 @@ Run the sync server on your own infrastructure:
|
||||||
### Operational Transformation
|
### Operational Transformation
|
||||||
|
|
||||||
VaultLink uses the `reconcile-text` library for intelligent conflict resolution:
|
VaultLink uses the `reconcile-text` library for intelligent conflict resolution:
|
||||||
|
|
||||||
- Simultaneous edits are automatically merged
|
- Simultaneous edits are automatically merged
|
||||||
- No manual conflict resolution required
|
- No manual conflict resolution required
|
||||||
- Preserves intent of all contributors
|
- Preserves intent of all contributors
|
||||||
|
|
@ -55,6 +60,7 @@ VaultLink uses the `reconcile-text` library for intelligent conflict resolution:
|
||||||
### Flexible Authentication
|
### Flexible Authentication
|
||||||
|
|
||||||
Configure user access per vault:
|
Configure user access per vault:
|
||||||
|
|
||||||
- Token-based authentication
|
- Token-based authentication
|
||||||
- Per-user vault access control
|
- Per-user vault access control
|
||||||
- Allow-list or deny-list patterns
|
- Allow-list or deny-list patterns
|
||||||
|
|
@ -65,6 +71,7 @@ Configure user access per vault:
|
||||||
### Personal Sync
|
### Personal Sync
|
||||||
|
|
||||||
Synchronize your Obsidian vault across multiple devices:
|
Synchronize your Obsidian vault across multiple devices:
|
||||||
|
|
||||||
- Laptop, desktop, and mobile in real-time
|
- Laptop, desktop, and mobile in real-time
|
||||||
- No cloud service subscription required
|
- No cloud service subscription required
|
||||||
- Full privacy and data control
|
- Full privacy and data control
|
||||||
|
|
@ -72,6 +79,7 @@ Synchronize your Obsidian vault across multiple devices:
|
||||||
### Team Collaboration
|
### Team Collaboration
|
||||||
|
|
||||||
Share knowledge bases with teammates:
|
Share knowledge bases with teammates:
|
||||||
|
|
||||||
- Real-time collaborative editing
|
- Real-time collaborative editing
|
||||||
- Granular access control per vault
|
- Granular access control per vault
|
||||||
- Self-hosted for enterprise security requirements
|
- Self-hosted for enterprise security requirements
|
||||||
|
|
@ -79,6 +87,7 @@ Share knowledge bases with teammates:
|
||||||
### Automated Backups
|
### Automated Backups
|
||||||
|
|
||||||
Use the CLI client for automated workflows:
|
Use the CLI client for automated workflows:
|
||||||
|
|
||||||
- Scheduled backups to remote servers
|
- Scheduled backups to remote servers
|
||||||
- Integration with existing backup systems
|
- Integration with existing backup systems
|
||||||
- Headless operation without Obsidian
|
- Headless operation without Obsidian
|
||||||
|
|
@ -86,6 +95,7 @@ Use the CLI client for automated workflows:
|
||||||
### Development & Testing
|
### Development & Testing
|
||||||
|
|
||||||
Synchronize documentation across environments:
|
Synchronize documentation across environments:
|
||||||
|
|
||||||
- Keep docs in sync with development environments
|
- Keep docs in sync with development environments
|
||||||
- Automated deployment of documentation
|
- Automated deployment of documentation
|
||||||
- Version control integration
|
- Version control integration
|
||||||
|
|
|
||||||
|
|
@ -2,39 +2,39 @@
|
||||||
layout: home
|
layout: home
|
||||||
|
|
||||||
hero:
|
hero:
|
||||||
name: VaultLink
|
name: VaultLink
|
||||||
text: Self-Hosted Sync for Obsidian
|
text: Self-Hosted Sync for Obsidian
|
||||||
tagline: Real-time collaborative file synchronization for your knowledge base
|
tagline: Real-time collaborative file synchronization for your knowledge base
|
||||||
image:
|
image:
|
||||||
src: /logo.svg
|
src: /logo.svg
|
||||||
alt: VaultLink
|
alt: VaultLink
|
||||||
actions:
|
actions:
|
||||||
- theme: brand
|
- theme: brand
|
||||||
text: Get Started
|
text: Get Started
|
||||||
link: /guide/getting-started
|
link: /guide/getting-started
|
||||||
- theme: alt
|
- theme: alt
|
||||||
text: View on GitHub
|
text: View on GitHub
|
||||||
link: https://github.com/schmelczer/vault-link
|
link: https://github.com/schmelczer/vault-link
|
||||||
|
|
||||||
features:
|
features:
|
||||||
- icon: 🚀
|
- icon: 🚀
|
||||||
title: Real-Time Synchronization
|
title: Real-Time Synchronization
|
||||||
details: Operational transformation-based conflict resolution ensures your files stay in sync across devices without data loss
|
details: Operational transformation-based conflict resolution ensures your files stay in sync across devices without data loss
|
||||||
- icon: 🔒
|
- icon: 🔒
|
||||||
title: Self-Hosted & Private
|
title: Self-Hosted & Private
|
||||||
details: Run your own sync server. Your data stays on your infrastructure with full control over access and privacy
|
details: Run your own sync server. Your data stays on your infrastructure with full control over access and privacy
|
||||||
- icon: 🎯
|
- icon: 🎯
|
||||||
title: Obsidian Plugin
|
title: Obsidian Plugin
|
||||||
details: Native integration with Obsidian for seamless synchronization directly within your favorite note-taking app
|
details: Native integration with Obsidian for seamless synchronization directly within your favorite note-taking app
|
||||||
- icon: 🖥️
|
- icon: 🖥️
|
||||||
title: CLI Client
|
title: CLI Client
|
||||||
details: Sync vaults to any system using the standalone CLI client. Perfect for servers, automation, or headless setups
|
details: Sync vaults to any system using the standalone CLI client. Perfect for servers, automation, or headless setups
|
||||||
- icon: ⚡
|
- icon: ⚡
|
||||||
title: Built for Performance
|
title: Built for Performance
|
||||||
details: Rust-powered WebSocket server with SQLite backend delivers blazing-fast sync performance
|
details: Rust-powered WebSocket server with SQLite backend delivers blazing-fast sync performance
|
||||||
- icon: 🛠️
|
- icon: 🛠️
|
||||||
title: Flexible Deployment
|
title: Flexible Deployment
|
||||||
details: Deploy via Docker, binary releases, or build from source. Configure authentication and access controls to fit your needs
|
details: Deploy via Docker, binary releases, or build from source. Configure authentication and access controls to fit your needs
|
||||||
---
|
---
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,15 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vitepress dev",
|
"dev": "vitepress dev",
|
||||||
"build": "vitepress build",
|
"build": "vitepress build",
|
||||||
"preview": "vitepress preview"
|
"preview": "vitepress preview",
|
||||||
|
"format": "prettier --write \"**/*.md\" \"**/*.mts\"",
|
||||||
|
"format:check": "prettier --check \"**/*.md\" \"**/*.mts\""
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"prettier": "^3.6.2",
|
||||||
"vitepress": "^1.6.4",
|
"vitepress": "^1.6.4",
|
||||||
"vue": "^3.5.24"
|
"vue": "^3.5.24"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,47 @@
|
||||||
<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
|
<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
|
<stop offset="0%" style="stop-color:#4A90E2;stop-opacity:1" />
|
||||||
|
<stop offset="100%" style="stop-color:#357ABD;stop-opacity:1" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
|
||||||
<!-- Background circle -->
|
<!-- Background circle -->
|
||||||
<circle cx="100" cy="100" r="95" fill="#4A90E2" opacity="0.1"/>
|
<circle cx="100" cy="100" r="90" fill="url(#grad1)" opacity="0.15"/>
|
||||||
|
|
||||||
<!-- Link chain symbol -->
|
<!-- Main vault icon -->
|
||||||
<g transform="translate(100, 100)">
|
<g transform="translate(100, 100)">
|
||||||
<!-- Left link -->
|
<!-- Vault body -->
|
||||||
<path d="M -60 -10 L -30 -10 C -20 -10 -15 -5 -15 5 L -15 5 C -15 15 -20 20 -30 20 L -60 20 C -70 20 -75 15 -75 5 L -75 -5 C -75 -15 -70 -20 -60 -20 Z"
|
<rect x="-45" y="-50" width="90" height="80" rx="8" fill="none" stroke="url(#grad1)" stroke-width="6"/>
|
||||||
fill="none" stroke="#4A90E2" stroke-width="8" stroke-linecap="round"/>
|
|
||||||
|
|
||||||
<!-- Right link -->
|
<!-- Vault door circle -->
|
||||||
<path d="M 60 -10 L 30 -10 C 20 -10 15 -5 15 5 L 15 5 C 15 15 20 20 30 20 L 60 20 C 70 20 75 15 75 5 L 75 -5 C 75 -15 70 -20 60 -20 Z"
|
<circle cx="0" cy="-10" r="22" fill="none" stroke="url(#grad1)" stroke-width="5"/>
|
||||||
fill="none" stroke="#4A90E2" stroke-width="8" stroke-linecap="round"/>
|
<circle cx="0" cy="-10" r="14" fill="none" stroke="url(#grad1)" stroke-width="3"/>
|
||||||
|
<circle cx="0" cy="-10" r="6" fill="url(#grad1)"/>
|
||||||
|
|
||||||
<!-- Center connecting bar -->
|
<!-- Vault handle -->
|
||||||
<rect x="-15" y="-6" width="30" height="12" rx="6" fill="#4A90E2"/>
|
<line x1="0" y1="-4" x2="18" y2="-4" stroke="url(#grad1)" stroke-width="3" stroke-linecap="round"/>
|
||||||
|
<circle cx="18" cy="-4" r="4" fill="url(#grad1)"/>
|
||||||
|
|
||||||
<!-- Vault door detail -->
|
<!-- Link chain -->
|
||||||
<circle cx="0" cy="0" r="12" fill="none" stroke="#4A90E2" stroke-width="3"/>
|
<g opacity="0.9">
|
||||||
<circle cx="0" cy="0" r="6" fill="#4A90E2"/>
|
<!-- Left link -->
|
||||||
|
<ellipse cx="-30" cy="40" rx="12" ry="8" fill="none" stroke="url(#grad1)" stroke-width="4"/>
|
||||||
|
<!-- Right link -->
|
||||||
|
<ellipse cx="30" cy="40" rx="12" ry="8" fill="none" stroke="url(#grad1)" stroke-width="4"/>
|
||||||
|
<!-- Center link connecting them -->
|
||||||
|
<ellipse cx="0" cy="40" rx="12" ry="8" fill="none" stroke="url(#grad1)" stroke-width="4"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
<!-- Sync arrows -->
|
<!-- Sync arrows (subtle) -->
|
||||||
<g opacity="0.6">
|
<g opacity="0.5">
|
||||||
<!-- Top arrow -->
|
<!-- Clockwise arrow top-right -->
|
||||||
<path d="M -5 -50 L 5 -50 L 0 -40 Z" fill="#4A90E2"/>
|
<path d="M 35 -35 Q 50 -35 50 -20 L 50 -15" fill="none" stroke="url(#grad1)" stroke-width="2.5" stroke-linecap="round"/>
|
||||||
<!-- Bottom arrow -->
|
<polygon points="50,-15 47,-22 53,-22" fill="url(#grad1)"/>
|
||||||
<path d="M 5 50 L -5 50 L 0 40 Z" fill="#4A90E2"/>
|
|
||||||
|
<!-- Counter-clockwise arrow bottom-left -->
|
||||||
|
<path d="M -35 25 Q -50 25 -50 10 L -50 5" fill="none" stroke="url(#grad1)" stroke-width="2.5" stroke-linecap="round"/>
|
||||||
|
<polygon points="-50,5 -47,12 -53,12" fill="url(#grad1)"/>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- Text (optional) -->
|
|
||||||
<text x="100" y="175" font-family="Arial, sans-serif" font-size="24" font-weight="bold"
|
|
||||||
text-anchor="middle" fill="#4A90E2">VaultLink</text>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2 KiB |
Loading…
Add table
Add a link
Reference in a new issue