README file from
GithubHermes R2 Vault
Russian version: README.ru.md
Encrypted Obsidian vault with Cloudflare R2 sync.
Client-side encrypted note publishing to Cloudflare R2 via a control API (Hermes VPS). Supports bidirectional sync, revision history, safe deletion, and vault restore.
Architecture
Obsidian vault → client-side encryption (XChaCha20-Poly1305) → Cloudflare R2
↕
Hermes control API (VPS)
↓
Hermes‑R2‑File‑Resolver (Python)
- Cloudflare R2 is the only persistent knowledge store.
- The control API never receives plaintext Markdown content.
- Plaintext exists only in Obsidian and briefly in plugin memory during encryption.
- Bidirectional sync with LWW (Last-Writer-Wins) conflict resolution.
Features
| Feature | Status |
|---|---|
| Client-side encryption (XChaCha20-Poly1305 + Argon2id) | ✅ |
| Bidirectional sync | ✅ |
| Content-addressable storage (CAS) for manifests | ✅ |
| Multi-device enrollment | ✅ |
| Setup URI for device pairing | ✅ |
| File revision history | ✅ |
| Single-file restore | ✅ |
| Point-in-time vault restore | ✅ |
| History integrity scanner | ✅ |
| Retention planner | ✅ |
| Safe garbage collection | ✅ |
| Mass deletion protection | ✅ |
| Conflict Center UI | ✅ |
| Audit log | ✅ |
| Russian and English localization | ✅ |
Project Structure
hermes-r2-vault/
├── src/ # Plugin source code
│ ├── main.ts # Entry point, commands, modals
│ ├── sync.ts # Sync orchestrator (core)
│ ├── models.ts # Interfaces and state types
│ ├── constants.ts # Constants and defaults
│ ├── crypto.ts # XChaCha20-Poly1305, Argon2id, X25519
│ ├── envelope.ts # Binary envelope with AAD
│ ├── transport.ts # HTTP client for presigned URLs
│ ├── cas-store-r2.ts # Content-addressable storage (R2)
│ ├── manifest-snapshot.ts # Manifest snapshots
│ ├── file-history.ts # File revision history
│ ├── reference-graph.ts # Revision reference graph
│ ├── retention-planner.ts # Retention planner
│ ├── vault-restore.ts # Vault restore
│ ├── integrity-scanner.ts # Integrity scanner
│ ├── download.ts # Download and decrypt
│ ├── conflict-center.ts # Conflict resolution UI
│ ├── mass-deletion.ts # Mass deletion protection
│ ├── deletion-metrics.ts # Deletion counters
│ ├── audit-log.ts # Event logging
│ ├── fault-handler.ts # Error handling
│ ├── safety.ts # Empty manifest + stale device guards
│ ├── key-envelope.ts # Key management
│ ├── path-utils.ts # Path normalization
│ ├── filter.ts # File filtering
│ ├── queue.ts # Change queue
│ ├── ulid.ts # ULID generation
│ ├── settings.ts # Settings UI
│ ├── log.ts # Structured logging
│ ├── errors.ts # Typed errors
│ ├── i18n/ # Internationalization
│ │ └── index.ts # Russian + English
│ └── protocol/ # Protocol types
│ ├── manifest.ts # SyncManifestV1
│ ├── manifest-snapshot.ts # ManifestSnapshotV1
│ ├── revision.ts # RevisionMetadataV1
│ ├── tombstone.ts # TombstoneV1
│ ├── file-history.ts # FileHistoryIndexV1
│ ├── differ.ts # Diff + LWW resolution
│ ├── conflict.ts # Conflict classification
│ ├── validation.ts # Type validation
│ ├── identifiers.ts # UUID/ULID validation
│ ├── paths.ts # Path encoding
│ ├── canonical-json.ts # Canonical JSON
│ ├── cas-store.ts # CasStore interface
│ ├── device.ts # Device metadata
│ ├── device-envelope.ts # Device key envelope
│ ├── setup-uri.ts # Setup URI
│ ├── setup-token.ts # Setup token
│ └── index.ts # Re-exports
├── tests/ # Tests (379 tests, Vitest)
│ ├── revision-history.test.ts
│ ├── retention-planner.test.ts
│ ├── vault-restore.test.ts
│ ├── integrity-scanner.test.ts
│ ├── protocol.test.ts
│ ├── deletion-safety.test.ts
│ ├── crypto.test.ts
│ ├── key-envelope.test.ts
│ ├── device-identity.test.ts
│ ├── transport.test.ts
│ ├── queue.test.ts
│ ├── i18n.test.ts
│ ├── differ.test.ts
│ └── ... (21 files)
├── hermes-r2-file-resolver/ # Python resolver (Hermes VPS)
├── docs/ # Documentation
│ ├── architecture.md
│ ├── sync-protocol-v1.md
│ ├── security-model.md
│ ├── threat-model.md
│ ├── revision-model-v1.md
│ ├── r2-layout-v1.md
│ ├── manifest-v1.md
│ ├── deletion-safety.md
│ └── ... (16 files)
├── package.json
├── tsconfig.json
├── vitest.config.ts
├── build.mjs
└── manifest.json
Installation
-
Community plugin store (once approved): Settings → Community plugins → Browse → search for "Hermes R2 Vault" → Install → Enable.
-
Manual install: copy
main.js,manifest.json, andstyles.cssto<vault>/.obsidian/plugins/hermes-rvault/. -
Enable the plugin in Obsidian settings.
-
Configure the API Base URL, Vault ID, and secret names.
-
Run "Initialize encrypted vault" to generate the master key.
-
Store the access token via the settings tab (SecretStorage).
-
Run "Test connection" to verify connectivity.
-
Run "Run full scan" to upload existing notes.
Note: This plugin is desktop-only. The Obsidian mobile app does not expose the SecretStorage API required for keys and tokens.
Configuration
Plugin settings are available under Hermes R2 Vault Settings in Obsidian settings.
General Settings
| Setting | Description | Default |
|---|---|---|
| API Base URL | Control API URL (HTTPS) | — |
| Vault ID | Vault UUID | — |
| Device ID | Device identifier (auto-generated) | randomUUID() |
| Access Token Secret Name | SecretStorage key name | — |
| Auto-upload | Automatically upload changes | false |
| Debounce delay (ms) | Change processing delay | 1500 |
| Max concurrent uploads | Parallel upload limit | 2 |
Deletion Safety
| Setting | Description | Default |
|---|---|---|
| Enable remote trash | Keep deleted files in manifest | true |
| Deleted retention (days) | Deleted metadata retention period | 90 |
| Mass deletion threshold | Deletion count threshold | 10 |
| Require deletion approval | Prompt for confirmation | true |
| Empty manifest protection | Block sync on empty remote manifest | true |
| Stale device guard | Block stale device sync | true |
| Pre-deletion snapshot | Capture hash before deletion | false |
Revision History
| Setting | Description | Default |
|---|---|---|
| Revision history enabled | Enable revision history | false |
| Max revisions per file | Max revisions per file | 100 |
| Max snapshot generations | Max snapshot count | 50 |
| Revision TTL (days) | Revision time-to-live | 365 |
| Revision retention (days) | Guaranteed retention period | 90 |
| Minimum retained revisions | Minimum revisions to keep | 20 |
Commands
| Command | Description |
|---|---|
| Test connection | Verify API connectivity |
| Initialize encrypted vault | Generate vault master key |
| Recover vault key | Recover key from passphrase |
| Change recovery passphrase | Change recovery passphrase |
| Upload current note | Upload active note |
| Run full scan | Full file scan |
| Pause automatic uploads | Pause upload queue |
| Resume automatic uploads | Resume upload queue |
| Show sync status | Display sync status |
| View sync conflicts | Show sync conflicts |
| Open Conflict Center | Open conflict resolution UI |
| Undo latest deletion | Undo last deletion |
| Purge deleted file | Safe permanent purge |
| Preview retention plan | Retention plan preview |
| Apply retention plan | Execute retention GC |
| List vault restore points | List available snapshots |
| Preview vault restore | Preview vault state restore |
| Scan history integrity | Check history integrity |
| Show deletion safety metrics | Display deletion statistics |
| Show recent faults | Show recent fault records |
| Clear local sync metadata | Clear local sync state |
Sync Protocol
Full protocol specification: docs/sync-protocol-v1.md.
Protocol version: 1 (marker: docs/protocol-version-v1.json)
Feature Flags
| Flag | Status |
|---|---|
revisionHistory |
✅ true |
fileRevisionRestore |
✅ true |
pointInTimeRestore |
✅ true |
historyIntegrityScan |
✅ true |
retentionPlanner |
✅ true |
safeGarbageCollection |
✅ true |
resumableRestore |
✅ true |
hermesReadWrite |
❌ false |
R2 Layout
vaults/{vaultId}/
├── manifests/
│ ├── current.json # Manifest pointer
│ ├── by-hash/{sha256}.json # Content-addressable manifests
│ └── snapshots/{generation}.json # Per-generation snapshots
├── history/
│ ├── manifests/index.v1.json # Snapshot index
│ └── files/{fileId}/index.v1.json # File history indices
├── revisions/{fileId}/{revisionId}.hvr # Immutable revisions
├── tombstones/{fileId}.hvr # Tombstones
└── config/
├── owner-key-envelope.v1.json
└── recipients/{recipientId}.key-envelope.v1.json
Security
- Encryption: XChaCha20-Poly1305 with 24-byte nonce
- KDF: Argon2id (64MB memory, 3 iterations)
- Key derivation: HKDF-SHA256 (path key, content key)
- Key exchange: X25519 ECDH
- Authentication: HMAC-SHA256 for path identity
Details: docs/security-model.md, docs/threat-model.md.
Development
Scripts
| Command | Description |
|---|---|
npm run build |
Build plugin (esbuild) |
npm run test |
Run tests (Vitest) |
npm run test:coverage |
Coverage report |
npm run typecheck |
TypeScript type check |
npm run lint |
ESLint |
Dependencies
- Runtime:
@noble/ciphers,@noble/curves,@noble/hashes - Dev: TypeScript, esbuild, Vitest, ESLint, Obsidian API
Python Resolver
The hermes-r2-file-resolver/ subproject implements the protocol in Python for the Hermes VPS backend.
Testing
Before committing:
npm run typecheck # TypeScript — clean
npm run lint # ESLint — clean
npm run test # 379 tests, 21 files
npm run build # esbuild — success
License
MIT