ObSync
unlistedby K4nes
Local-first end-to-end encrypted backup plugin to Cloudflare R2
Updated 1mo ago
ObSync Plaintext Backup Plugin
A local-first, automated community plugin for Obsidian that provides direct, plaintext backup of notes and attachments to a Cloudflare R2 bucket.
Key Features
- Plaintext Storage: Files are uploaded exactly as they are on your local computer, allowing you to browse, search, and recover single files directly from the Cloudflare R2 web interface.
- Secure Credentials Storage: Cloudflare R2 credentials (AccountId, BucketName, AccessKeyId, SecretAccessKey) are stored in Obsidian's isolated
secretStorageAPI, which leverages OS-level keychains (like Windows Credential Manager) to keep them encrypted at rest. - Hashed Local Database: Local sync records (
data.json) index files by their SHA-256 path hashes rather than plaintext path strings, keeping your folder structure and note titles private from local file scanners. - Archive Sync by Default: Avoid accidental data loss; local deletions do not automatically delete remote backups unless configured otherwise.
- Lightweight & Dependency-Free: Sign requests using a custom AWS Signature Version 4 implementation without bloated S3 SDKs.
- Smart Idle Debounce: Automatically backs up files in the background after $X$ minutes of keyboard/mouse inactivity to prevent sync conflicts.
- Frictionless Recovery & Quick Controls: Fully accessible controls are built directly into a clickable status bar menu (in the bottom-right of your Obsidian workspace) for manual syncs, restoring backups, and settings access.
Configuration & Usage
1. Cloudflare R2 Setup
- Create a Cloudflare account and create an R2 bucket.
- Go to R2 -> Manage R2 API Tokens.
- Create a token with Edit (Read/Write) permissions. Note the Access Key ID, Secret Access Key, and Jurisdiction-specific Endpoint Account ID.
- Set up the CORS Policy for your bucket to allow Obsidian browser requests. Go to your bucket settings page in Cloudflare, select Settings -> CORS Policy -> Add CORS policy, and paste this JSON config:
[
{
"AllowedOrigins": ["app://obsidian.md"],
"AllowedMethods": ["GET", "PUT", "DELETE"],
"AllowedHeaders": [
"Content-Type",
"Authorization",
"X-Amz-Date",
"X-Amz-Content-Sha256",
"x-amz-date",
"x-amz-content-sha256"
],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3600
}
]
2. Plugin Installation & Setup
- Enable community plugins in Obsidian.
- Copy
main.jsandmanifest.jsoninto your vault's.obsidian/plugins/obsync/directory. - Reload Obsidian and enable the plugin in settings.
- Fill in your R2 credentials:
- Cloudflare Account ID (found in your Cloudflare dashboard URL)
- R2 Bucket Name
- Access Key ID
- Secret Access Key
- Click Test Connection to verify setup.
3. Usage
- Auto Sync: The plugin runs in the background and initiates backups after the vault has been idle for the configured time (default: 5 minutes).
- Status Bar Menu: Click the status indicator (e.g.
R2: Up to dateorR2: Idle) in the bottom-right corner to open a context menu with options to:Sync NowRestore Notes(downloads all notes from Cloudflare)Open Settings(jumps directly to ObSync settings)
- Safe Conflict Handling: During a restore, if a file already exists locally but has different content, it will be renamed to include
.conflictedso you never lose local edits.
Technical Structure
src/
├── main.ts # Entry point: registers commands, ribbon, status bar, and menu
├── settings.ts # Settings UI (user-friendly credentials config and connection tester)
├── crypto.ts # Local helper functions (SHA-256 content hashing)
├── s3.ts # Lightweight AWS SigV4 PUT/GET/DELETE signer & R2 client
└── sync.ts # Sync engine (queuing, diffing, restore vault, conflicts)
Running Unit Tests
The codebase includes comprehensive unit tests mocking the Obsidian environment and R2 client calls.
To run the test suite:
npm install
npm test
To build a production bundle:
npm run build
License
MIT
For plugin developers
Search results and similarity scores are powered by semantic analysis of your plugin's README. If your plugin isn't appearing for searches you'd expect, try updating your README to clearly describe your plugin's purpose, features, and use cases.