mindoodb 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README-server.md +1001 -0
- package/README.md +89 -28
- package/dist/__mocks__/expo-standard-web-crypto.d.ts +10 -0
- package/dist/__mocks__/expo-standard-web-crypto.d.ts.map +1 -0
- package/dist/__mocks__/expo-standard-web-crypto.js +22 -0
- package/dist/__mocks__/expo-standard-web-crypto.js.map +1 -0
- package/dist/appendonlystores/BasicOnDiskContentAddressedStoreFactory.d.ts +8 -0
- package/dist/appendonlystores/BasicOnDiskContentAddressedStoreFactory.d.ts.map +1 -0
- package/dist/appendonlystores/BasicOnDiskContentAddressedStoreFactory.js +16 -0
- package/dist/appendonlystores/BasicOnDiskContentAddressedStoreFactory.js.map +1 -0
- package/dist/appendonlystores/InMemoryContentAddressedStoreFactory.d.ts +1 -1
- package/dist/appendonlystores/InMemoryContentAddressedStoreFactory.d.ts.map +1 -1
- package/dist/appendonlystores/InMemoryContentAddressedStoreFactory.js +2 -2
- package/dist/appendonlystores/InMemoryContentAddressedStoreFactory.js.map +1 -1
- package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.d.ts +30 -1
- package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.d.ts.map +1 -1
- package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.js +152 -5
- package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.js.map +1 -1
- package/dist/appendonlystores/network/HttpTransport.d.ts +17 -2
- package/dist/appendonlystores/network/HttpTransport.d.ts.map +1 -1
- package/dist/appendonlystores/network/HttpTransport.js +129 -0
- package/dist/appendonlystores/network/HttpTransport.js.map +1 -1
- package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.d.ts +22 -2
- package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.d.ts.map +1 -1
- package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.js +110 -0
- package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.js.map +1 -1
- package/dist/appendonlystores/network/index.d.ts +1 -1
- package/dist/appendonlystores/network/index.d.ts.map +1 -1
- package/dist/appendonlystores/network/index.js.map +1 -1
- package/dist/browser/appendonlystores/IndexedDBContentAddressedStore.d.ts +65 -0
- package/dist/browser/appendonlystores/IndexedDBContentAddressedStore.d.ts.map +1 -0
- package/dist/browser/appendonlystores/IndexedDBContentAddressedStore.js +741 -0
- package/dist/browser/appendonlystores/IndexedDBContentAddressedStore.js.map +1 -0
- package/dist/browser/appendonlystores/IndexedDBContentAddressedStoreFactory.d.ts +25 -0
- package/dist/browser/appendonlystores/IndexedDBContentAddressedStoreFactory.d.ts.map +1 -0
- package/dist/browser/appendonlystores/IndexedDBContentAddressedStoreFactory.js +41 -0
- package/dist/browser/appendonlystores/IndexedDBContentAddressedStoreFactory.js.map +1 -0
- package/dist/browser/cache/IndexedDBLocalCacheStore.d.ts +21 -0
- package/dist/browser/cache/IndexedDBLocalCacheStore.d.ts.map +1 -0
- package/dist/browser/cache/IndexedDBLocalCacheStore.js +106 -0
- package/dist/browser/cache/IndexedDBLocalCacheStore.js.map +1 -0
- package/dist/browser/index.d.ts +8 -0
- package/dist/browser/index.d.ts.map +1 -1
- package/dist/browser/index.js +21 -1
- package/dist/browser/index.js.map +1 -1
- package/dist/core/BaseMindooDB.d.ts +115 -13
- package/dist/core/BaseMindooDB.d.ts.map +1 -1
- package/dist/core/BaseMindooDB.js +1232 -269
- package/dist/core/BaseMindooDB.js.map +1 -1
- package/dist/core/BaseMindooTenant.d.ts +69 -6
- package/dist/core/BaseMindooTenant.d.ts.map +1 -1
- package/dist/core/BaseMindooTenant.js +302 -109
- package/dist/core/BaseMindooTenant.js.map +1 -1
- package/dist/core/BaseMindooTenantDirectory.d.ts +13 -10
- package/dist/core/BaseMindooTenantDirectory.d.ts.map +1 -1
- package/dist/core/BaseMindooTenantDirectory.js +144 -27
- package/dist/core/BaseMindooTenantDirectory.js.map +1 -1
- package/dist/core/BaseMindooTenantFactory.d.ts +27 -29
- package/dist/core/BaseMindooTenantFactory.d.ts.map +1 -1
- package/dist/core/BaseMindooTenantFactory.js +185 -112
- package/dist/core/BaseMindooTenantFactory.js.map +1 -1
- package/dist/core/MindooDBServerAdmin.d.ts +238 -0
- package/dist/core/MindooDBServerAdmin.d.ts.map +1 -0
- package/dist/core/MindooDBServerAdmin.js +345 -0
- package/dist/core/MindooDBServerAdmin.js.map +1 -0
- package/dist/core/appendonlystores/BasicOnDiskContentAddressedStore.d.ts +68 -0
- package/dist/core/appendonlystores/BasicOnDiskContentAddressedStore.d.ts.map +1 -0
- package/dist/core/appendonlystores/BasicOnDiskContentAddressedStore.js +659 -0
- package/dist/core/appendonlystores/BasicOnDiskContentAddressedStore.js.map +1 -0
- package/dist/core/appendonlystores/InMemoryContentAddressedStore.d.ts +45 -3
- package/dist/core/appendonlystores/InMemoryContentAddressedStore.d.ts.map +1 -1
- package/dist/core/appendonlystores/InMemoryContentAddressedStore.js +184 -23
- package/dist/core/appendonlystores/InMemoryContentAddressedStore.js.map +1 -1
- package/dist/core/appendonlystores/MaterializationPlanner.d.ts +49 -0
- package/dist/core/appendonlystores/MaterializationPlanner.d.ts.map +1 -0
- package/dist/core/appendonlystores/MaterializationPlanner.js +325 -0
- package/dist/core/appendonlystores/MaterializationPlanner.js.map +1 -0
- package/dist/core/appendonlystores/bloom.d.ts +85 -0
- package/dist/core/appendonlystores/bloom.d.ts.map +1 -0
- package/dist/core/appendonlystores/bloom.js +228 -0
- package/dist/core/appendonlystores/bloom.js.map +1 -0
- package/dist/core/appendonlystores/network/NetworkTransport.d.ts +37 -2
- package/dist/core/appendonlystores/network/NetworkTransport.d.ts.map +1 -1
- package/dist/core/appendonlystores/network/types.d.ts +12 -0
- package/dist/core/appendonlystores/network/types.d.ts.map +1 -1
- package/dist/core/appendonlystores/network/types.js.map +1 -1
- package/dist/core/appendonlystores/types.d.ts +206 -0
- package/dist/core/appendonlystores/types.d.ts.map +1 -1
- package/dist/core/automerge-adapter.d.ts +15 -0
- package/dist/core/automerge-adapter.d.ts.map +1 -0
- package/dist/core/automerge-adapter.js +73 -0
- package/dist/core/automerge-adapter.js.map +1 -0
- package/dist/core/cache/CacheManager.d.ts +75 -0
- package/dist/core/cache/CacheManager.d.ts.map +1 -0
- package/dist/core/cache/CacheManager.js +101 -0
- package/dist/core/cache/CacheManager.js.map +1 -0
- package/dist/core/cache/EncryptedLocalCacheStore.d.ts +41 -0
- package/dist/core/cache/EncryptedLocalCacheStore.d.ts.map +1 -0
- package/dist/core/cache/EncryptedLocalCacheStore.js +120 -0
- package/dist/core/cache/EncryptedLocalCacheStore.js.map +1 -0
- package/dist/core/cache/LocalCacheStore.d.ts +50 -0
- package/dist/core/cache/LocalCacheStore.d.ts.map +1 -0
- package/dist/core/cache/LocalCacheStore.js +39 -0
- package/dist/core/cache/LocalCacheStore.js.map +1 -0
- package/dist/core/crypto/pbkdf2Iterations.d.ts +12 -0
- package/dist/core/crypto/pbkdf2Iterations.d.ts.map +1 -0
- package/dist/core/crypto/pbkdf2Iterations.js +55 -0
- package/dist/core/crypto/pbkdf2Iterations.js.map +1 -0
- package/dist/core/crypto/privateKeyEncryption.d.ts +5 -0
- package/dist/core/crypto/privateKeyEncryption.d.ts.map +1 -0
- package/dist/core/crypto/privateKeyEncryption.js +87 -0
- package/dist/core/crypto/privateKeyEncryption.js.map +1 -0
- package/dist/core/index.d.ts +18 -6
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +42 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.d.ts +4 -1
- package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.d.ts.map +1 -1
- package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.js +39 -11
- package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.js.map +1 -1
- package/dist/core/indexing/virtualviews/ViewEntrySortKeyComparator.d.ts +1 -0
- package/dist/core/indexing/virtualviews/ViewEntrySortKeyComparator.d.ts.map +1 -1
- package/dist/core/indexing/virtualviews/ViewEntrySortKeyComparator.js +3 -0
- package/dist/core/indexing/virtualviews/ViewEntrySortKeyComparator.js.map +1 -1
- package/dist/core/indexing/virtualviews/VirtualView.d.ts +43 -0
- package/dist/core/indexing/virtualviews/VirtualView.d.ts.map +1 -1
- package/dist/core/indexing/virtualviews/VirtualView.js +173 -0
- package/dist/core/indexing/virtualviews/VirtualView.js.map +1 -1
- package/dist/core/indexing/virtualviews/VirtualViewEntryData.d.ts +7 -0
- package/dist/core/indexing/virtualviews/VirtualViewEntryData.d.ts.map +1 -1
- package/dist/core/indexing/virtualviews/VirtualViewEntryData.js +28 -0
- package/dist/core/indexing/virtualviews/VirtualViewEntryData.js.map +1 -1
- package/dist/core/keys/KeyBag.d.ts +56 -12
- package/dist/core/keys/KeyBag.d.ts.map +1 -1
- package/dist/core/keys/KeyBag.js +156 -70
- package/dist/core/keys/KeyBag.js.map +1 -1
- package/dist/core/keys/KeyContext.d.ts +4 -0
- package/dist/core/keys/KeyContext.d.ts.map +1 -0
- package/dist/core/keys/KeyContext.js +17 -0
- package/dist/core/keys/KeyContext.js.map +1 -0
- package/dist/core/types.d.ts +512 -60
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +9 -1
- package/dist/core/types.js.map +1 -1
- package/dist/core/uri/MindooURI.d.ts +58 -0
- package/dist/core/uri/MindooURI.d.ts.map +1 -0
- package/dist/core/uri/MindooURI.js +169 -0
- package/dist/core/uri/MindooURI.js.map +1 -0
- package/dist/core/userid/index.d.ts +0 -10
- package/dist/core/userid/index.d.ts.map +1 -1
- package/dist/core/utils/idGeneration.d.ts.map +1 -1
- package/dist/core/utils/idGeneration.js +37 -3
- package/dist/core/utils/idGeneration.js.map +1 -1
- package/dist/node/appendonlystores/BasicOnDiskContentAddressedStore.d.ts +544 -0
- package/dist/node/appendonlystores/BasicOnDiskContentAddressedStore.d.ts.map +1 -0
- package/dist/node/appendonlystores/BasicOnDiskContentAddressedStore.js +1358 -0
- package/dist/node/appendonlystores/BasicOnDiskContentAddressedStore.js.map +1 -0
- package/dist/node/attachments/writeAttachmentToFile.d.ts +15 -0
- package/dist/node/attachments/writeAttachmentToFile.d.ts.map +1 -0
- package/dist/node/attachments/writeAttachmentToFile.js +73 -0
- package/dist/node/attachments/writeAttachmentToFile.js.map +1 -0
- package/dist/node/cache/FileSystemLocalCacheStore.d.ts +17 -0
- package/dist/node/cache/FileSystemLocalCacheStore.d.ts.map +1 -0
- package/dist/node/cache/FileSystemLocalCacheStore.js +128 -0
- package/dist/node/cache/FileSystemLocalCacheStore.js.map +1 -0
- package/dist/node/cli/change-identity-password.d.ts +7 -0
- package/dist/node/cli/change-identity-password.d.ts.map +1 -0
- package/dist/node/cli/change-identity-password.js +67 -0
- package/dist/node/cli/change-identity-password.js.map +1 -0
- package/dist/node/cli/cli-utils.d.ts +16 -0
- package/dist/node/cli/cli-utils.d.ts.map +1 -0
- package/dist/node/cli/cli-utils.js +113 -0
- package/dist/node/cli/cli-utils.js.map +1 -0
- package/dist/node/cli/identity-export-public.d.ts +4 -0
- package/dist/node/cli/identity-export-public.d.ts.map +1 -0
- package/dist/node/cli/identity-export-public.js +55 -0
- package/dist/node/cli/identity-export-public.js.map +1 -0
- package/dist/node/cli/identity-info.d.ts +4 -0
- package/dist/node/cli/identity-info.d.ts.map +1 -0
- package/dist/node/cli/identity-info.js +57 -0
- package/dist/node/cli/identity-info.js.map +1 -0
- package/dist/node/index.d.ts +2 -0
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +8 -1
- package/dist/node/index.js.map +1 -1
- package/dist/node/server/CapabilityMatcher.d.ts +45 -0
- package/dist/node/server/CapabilityMatcher.d.ts.map +1 -0
- package/dist/node/server/CapabilityMatcher.js +117 -0
- package/dist/node/server/CapabilityMatcher.js.map +1 -0
- package/dist/node/server/MindooDBServer.d.ts +87 -0
- package/dist/node/server/MindooDBServer.d.ts.map +1 -0
- package/dist/node/server/MindooDBServer.js +1040 -0
- package/dist/node/server/MindooDBServer.js.map +1 -0
- package/dist/node/server/ServerSync.d.ts +41 -0
- package/dist/node/server/ServerSync.d.ts.map +1 -0
- package/dist/node/server/ServerSync.js +187 -0
- package/dist/node/server/ServerSync.js.map +1 -0
- package/dist/node/server/StoreFactory.d.ts +45 -0
- package/dist/node/server/StoreFactory.d.ts.map +1 -0
- package/dist/node/server/StoreFactory.js +89 -0
- package/dist/node/server/StoreFactory.js.map +1 -0
- package/dist/node/server/SystemAdminAuth.d.ts +65 -0
- package/dist/node/server/SystemAdminAuth.d.ts.map +1 -0
- package/dist/node/server/SystemAdminAuth.js +200 -0
- package/dist/node/server/SystemAdminAuth.js.map +1 -0
- package/dist/node/server/SystemIpAllowlist.d.ts +21 -0
- package/dist/node/server/SystemIpAllowlist.d.ts.map +1 -0
- package/dist/node/server/SystemIpAllowlist.js +210 -0
- package/dist/node/server/SystemIpAllowlist.js.map +1 -0
- package/dist/node/server/TenantManager.d.ts +85 -0
- package/dist/node/server/TenantManager.d.ts.map +1 -0
- package/dist/node/server/TenantManager.js +493 -0
- package/dist/node/server/TenantManager.js.map +1 -0
- package/dist/node/server/add-to-network.d.ts +20 -0
- package/dist/node/server/add-to-network.d.ts.map +1 -0
- package/dist/node/server/add-to-network.js +275 -0
- package/dist/node/server/add-to-network.js.map +1 -0
- package/dist/node/server/config.d.ts +51 -0
- package/dist/node/server/config.d.ts.map +1 -0
- package/dist/node/server/config.js +218 -0
- package/dist/node/server/config.js.map +1 -0
- package/dist/node/server/resolveServerPassword.d.ts +7 -0
- package/dist/node/server/resolveServerPassword.d.ts.map +1 -0
- package/dist/node/server/resolveServerPassword.js +25 -0
- package/dist/node/server/resolveServerPassword.js.map +1 -0
- package/dist/node/server/server.d.ts +22 -0
- package/dist/node/server/server.d.ts.map +1 -0
- package/dist/node/server/server.js +225 -0
- package/dist/node/server/server.js.map +1 -0
- package/dist/node/server/serverinit.d.ts +21 -0
- package/dist/node/server/serverinit.d.ts.map +1 -0
- package/dist/node/server/serverinit.js +317 -0
- package/dist/node/server/serverinit.js.map +1 -0
- package/dist/node/server/types.d.ts +162 -0
- package/dist/node/server/types.d.ts.map +1 -0
- package/dist/node/server/types.js +24 -0
- package/dist/node/server/types.js.map +1 -0
- package/dist/node/server/validation.d.ts +47 -0
- package/dist/node/server/validation.d.ts.map +1 -0
- package/dist/node/server/validation.js +114 -0
- package/dist/node/server/validation.js.map +1 -0
- package/dist/reactnative/cache/MMKVLocalCacheStore.d.ts +48 -0
- package/dist/reactnative/cache/MMKVLocalCacheStore.d.ts.map +1 -0
- package/dist/reactnative/cache/MMKVLocalCacheStore.js +113 -0
- package/dist/reactnative/cache/MMKVLocalCacheStore.js.map +1 -0
- package/dist/reactnative/crypto/AESAdapter.d.ts +18 -0
- package/dist/reactnative/crypto/AESAdapter.d.ts.map +1 -0
- package/dist/reactnative/crypto/AESAdapter.js +112 -0
- package/dist/reactnative/crypto/AESAdapter.js.map +1 -0
- package/dist/reactnative/crypto/Ed25519Adapter.d.ts +34 -0
- package/dist/reactnative/crypto/Ed25519Adapter.d.ts.map +1 -0
- package/dist/reactnative/crypto/Ed25519Adapter.js +386 -0
- package/dist/reactnative/crypto/Ed25519Adapter.js.map +1 -0
- package/dist/reactnative/crypto/ExpoGoCryptoAdapter.d.ts +23 -0
- package/dist/reactnative/crypto/ExpoGoCryptoAdapter.d.ts.map +1 -0
- package/dist/reactnative/crypto/ExpoGoCryptoAdapter.js +51 -0
- package/dist/reactnative/crypto/ExpoGoCryptoAdapter.js.map +1 -0
- package/dist/reactnative/crypto/HMACAdapter.d.ts +13 -0
- package/dist/reactnative/crypto/HMACAdapter.d.ts.map +1 -0
- package/dist/reactnative/crypto/HMACAdapter.js +50 -0
- package/dist/reactnative/crypto/HMACAdapter.js.map +1 -0
- package/dist/reactnative/crypto/KeyFormatConverter.d.ts +21 -0
- package/dist/reactnative/crypto/KeyFormatConverter.d.ts.map +1 -0
- package/dist/reactnative/crypto/KeyFormatConverter.js +72 -0
- package/dist/reactnative/crypto/KeyFormatConverter.js.map +1 -0
- package/dist/reactnative/crypto/PBKDF2Adapter.d.ts +19 -0
- package/dist/reactnative/crypto/PBKDF2Adapter.d.ts.map +1 -0
- package/dist/reactnative/crypto/PBKDF2Adapter.js +67 -0
- package/dist/reactnative/crypto/PBKDF2Adapter.js.map +1 -0
- package/dist/reactnative/crypto/QuickCryptoAdapter.d.ts +37 -0
- package/dist/reactnative/crypto/QuickCryptoAdapter.d.ts.map +1 -0
- package/dist/reactnative/crypto/QuickCryptoAdapter.js +72 -0
- package/dist/reactnative/crypto/QuickCryptoAdapter.js.map +1 -0
- package/dist/reactnative/crypto/RSAAdapter.d.ts +38 -0
- package/dist/reactnative/crypto/RSAAdapter.d.ts.map +1 -0
- package/dist/reactnative/crypto/RSAAdapter.js +184 -0
- package/dist/reactnative/crypto/RSAAdapter.js.map +1 -0
- package/dist/reactnative/crypto/ReactNativeCryptoAdapter.d.ts +33 -0
- package/dist/reactnative/crypto/ReactNativeCryptoAdapter.d.ts.map +1 -0
- package/dist/reactnative/crypto/ReactNativeCryptoAdapter.js +61 -0
- package/dist/reactnative/crypto/ReactNativeCryptoAdapter.js.map +1 -0
- package/dist/reactnative/crypto/SHA256Adapter.d.ts +9 -0
- package/dist/reactnative/crypto/SHA256Adapter.d.ts.map +1 -0
- package/dist/reactnative/crypto/SHA256Adapter.js +29 -0
- package/dist/reactnative/crypto/SHA256Adapter.js.map +1 -0
- package/dist/reactnative/crypto/SubtleCryptoPolyfill.d.ts +63 -0
- package/dist/reactnative/crypto/SubtleCryptoPolyfill.d.ts.map +1 -0
- package/dist/reactnative/crypto/SubtleCryptoPolyfill.js +350 -0
- package/dist/reactnative/crypto/SubtleCryptoPolyfill.js.map +1 -0
- package/dist/reactnative/index.d.ts +9 -0
- package/dist/reactnative/index.d.ts.map +1 -0
- package/dist/reactnative/index.js +41 -0
- package/dist/reactnative/index.js.map +1 -0
- package/package.json +63 -7
- package/patches/PATCHES.md +111 -0
- package/patches/react-native+0.76.9.patch +34 -0
- package/patches/react-native-quick-crypto+1.0.7.patch +42 -0
- package/scripts/cli.js +21 -0
- package/scripts/setup-react-native.js +228 -0
- package/templates/reactnative/App.js +252 -0
- package/templates/reactnative/app.json +29 -0
- package/templates/reactnative/babel.config.js +6 -0
- package/templates/reactnative/index.js +26 -0
- package/templates/reactnative/metro.config.snippet.js +39 -0
- package/templates/reactnative/mindoodb-polyfills.js +116 -0
package/README-server.md
ADDED
|
@@ -0,0 +1,1001 @@
|
|
|
1
|
+
# MindooDB reference server
|
|
2
|
+
|
|
3
|
+
Node.js/Express sources: `src/node/server/`. Docker: `src/node/server/Dockerfile`.
|
|
4
|
+
|
|
5
|
+
A Node.js/Express server implementing the MindooDB sync API with:
|
|
6
|
+
|
|
7
|
+
- **Client-to-server sync** — clients push/pull encrypted entries
|
|
8
|
+
- **Server-to-server mirroring** — servers relay ciphertext without decryption
|
|
9
|
+
- **Capabilities-based system admin security** — challenge/response auth with JWT and fine-grained route authorization
|
|
10
|
+
- **Multi-tenant** — each tenant is isolated with its own config, keybag, and stores
|
|
11
|
+
|
|
12
|
+
## Prerequisites
|
|
13
|
+
|
|
14
|
+
- **Docker** — the recommended way to build and run the server
|
|
15
|
+
- Node.js 20 or later (only needed for local development without Docker)
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
The fastest way to get a server running is the interactive setup script. It prompts for a server name, password, data directory, and bind address, then builds the Docker image and initialises the server identity (including optional system admin creation):
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# 1. Clone and enter the repo
|
|
23
|
+
git clone https://github.com/klehmann/MindooDB.git && cd MindooDB
|
|
24
|
+
|
|
25
|
+
# 2. Run the interactive setup
|
|
26
|
+
bash serversetup.sh
|
|
27
|
+
|
|
28
|
+
# 3. Start the server
|
|
29
|
+
docker compose up -d
|
|
30
|
+
|
|
31
|
+
# 4. Verify
|
|
32
|
+
curl http://localhost:1661/health
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The setup script:
|
|
36
|
+
- builds the `mindoodb-server` Docker image
|
|
37
|
+
- creates the data directory (`../mindoodb-data/server`) and password file (`../mindoodb-data/.server_unlock`, mode 600)
|
|
38
|
+
- initialises the server identity and optionally creates a system admin keypair interactively
|
|
39
|
+
- writes a `docker-compose.override.yml` with your current host uid/gid, bind mounts, and one or more published port bindings based on your chosen bind address
|
|
40
|
+
|
|
41
|
+
After setup, manage the server with:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
docker compose up -d # start
|
|
45
|
+
docker compose down # stop
|
|
46
|
+
docker compose logs -f # follow logs
|
|
47
|
+
docker compose up -d --build # rebuild image and restart
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
For identity utilities on a Docker-deployed server, use the wrapper script from the repository root:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
./mindoodb-cli.sh identity:info server.identity.json
|
|
54
|
+
./mindoodb-cli.sh identity:change-password server.identity.json
|
|
55
|
+
./mindoodb-cli.sh identity:export-public system-admin-cn-sysadmin-o-myorg.identity.json --output ./system-admin.public-identity.json
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Files inside the mounted server data directory can be referenced by filename alone. Running `./mindoodb-cli.sh` without arguments prints a command overview, and each `identity:*` command prints its own help when called without the required parameters.
|
|
59
|
+
|
|
60
|
+
### What was created on disk?
|
|
61
|
+
|
|
62
|
+
After running `serversetup.sh` (with system admin creation), the data directory contains:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
../mindoodb-data/server/
|
|
66
|
+
├── server.identity.json # Server keypair (Ed25519 + RSA, password-encrypted)
|
|
67
|
+
├── config.json # Admin access rules — who can call which /system/* endpoints
|
|
68
|
+
├── trusted-servers.json # Public keys of remote servers trusted for sync (initially empty)
|
|
69
|
+
└── system-admin-cn-sysadmin-o-myorg.identity.json # System admin keypair (password-encrypted)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The most important file for day-to-day operations is **`config.json`**. After a fresh setup with one system admin, it looks like this:
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"capabilities": {
|
|
77
|
+
"ALL:/system/*": [
|
|
78
|
+
{
|
|
79
|
+
"username": "cn=sysadmin/o=myorg",
|
|
80
|
+
"publicsignkey": "-----BEGIN PUBLIC KEY-----\nMCow...base64...\n-----END PUBLIC KEY-----"
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
This single rule grants the admin `cn=sysadmin/o=myorg` full access to **all** `/system/*` endpoints -- tenant management, trusted server management, config updates, and everything else. The next section explains how to customize these rules.
|
|
88
|
+
|
|
89
|
+
### What's next?
|
|
90
|
+
|
|
91
|
+
Once the server is healthy, a nice real-world workflow is:
|
|
92
|
+
|
|
93
|
+
1. Create the tenant locally. This generates the tenant's own admin identity (`result.adminUser`).
|
|
94
|
+
2. Share the tenant admin's **public** identity with the server owner.
|
|
95
|
+
3. The server owner grants that admin access to `POST /system/tenants/...` via `MindooDBServerAdmin`, after which the tenant admin can publish the tenant directly.
|
|
96
|
+
|
|
97
|
+
### 1. Create the tenant locally and export a public identity
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
import { writeFileSync } from "fs";
|
|
101
|
+
import { BaseMindooTenantFactory, InMemoryContentAddressedStoreFactory } from "mindoodb";
|
|
102
|
+
|
|
103
|
+
const factory = new BaseMindooTenantFactory(new InMemoryContentAddressedStoreFactory());
|
|
104
|
+
|
|
105
|
+
const result = await factory.createTenant({
|
|
106
|
+
tenantId: "acme",
|
|
107
|
+
adminName: "cn=admin/o=acme",
|
|
108
|
+
adminPassword: "admin-pass",
|
|
109
|
+
userName: "cn=alice/o=acme",
|
|
110
|
+
userPassword: "alice-pass",
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const tenantAdminPublic = factory.toPublicUserId(result.adminUser);
|
|
114
|
+
|
|
115
|
+
writeFileSync(
|
|
116
|
+
"./acme-admin.public-identity.json",
|
|
117
|
+
JSON.stringify(tenantAdminPublic, null, 2),
|
|
118
|
+
"utf-8",
|
|
119
|
+
);
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The file `acme-admin.public-identity.json` contains only the tenant admin's public keys, so it can be shared with the server owner.
|
|
123
|
+
|
|
124
|
+
### 2. Server owner grants tenant-creation access
|
|
125
|
+
|
|
126
|
+
The server owner loads the tenant admin's public identity and grants access for one tenant, a prefix, or all tenants:
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
import { readFileSync } from "fs";
|
|
130
|
+
import {
|
|
131
|
+
BaseMindooTenantFactory,
|
|
132
|
+
InMemoryContentAddressedStoreFactory,
|
|
133
|
+
MindooDBServerAdmin,
|
|
134
|
+
} from "mindoodb";
|
|
135
|
+
import { NodeCryptoAdapter } from "mindoodb/node";
|
|
136
|
+
|
|
137
|
+
const factory = new BaseMindooTenantFactory(new InMemoryContentAddressedStoreFactory());
|
|
138
|
+
const tenantAdminPublic = JSON.parse(
|
|
139
|
+
readFileSync("./acme-admin.public-identity.json", "utf-8"),
|
|
140
|
+
);
|
|
141
|
+
const systemAdminIdentity = JSON.parse(
|
|
142
|
+
readFileSync(
|
|
143
|
+
"../mindoodb-data/server/system-admin-cn-sysadmin-o-myorg.identity.json",
|
|
144
|
+
"utf-8",
|
|
145
|
+
),
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
const serverAdmin = new MindooDBServerAdmin({
|
|
149
|
+
serverUrl: "http://localhost:1661",
|
|
150
|
+
systemAdminUser: systemAdminIdentity,
|
|
151
|
+
systemAdminPassword: "sysadmin-pass",
|
|
152
|
+
cryptoAdapter: new NodeCryptoAdapter(),
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
await serverAdmin.grantSystemAdminAccess(
|
|
156
|
+
{
|
|
157
|
+
username: tenantAdminPublic.username,
|
|
158
|
+
publicsignkey: tenantAdminPublic.userSigningPublicKey,
|
|
159
|
+
},
|
|
160
|
+
[
|
|
161
|
+
"POST:/system/tenants/acme", // exactly one tenant
|
|
162
|
+
// "POST:/system/tenants/customer-*", // or a naming prefix
|
|
163
|
+
// "POST:/system/tenants/*", // or all tenants
|
|
164
|
+
],
|
|
165
|
+
);
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
At that point the tenant admin is recognized by `/system/auth/challenge` and is allowed to create only the tenants covered by the granted rules.
|
|
169
|
+
|
|
170
|
+
### 3. Tenant admin publishes the tenant
|
|
171
|
+
|
|
172
|
+
Now the tenant admin can authenticate with their own private identity and publish the tenant:
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
await result.tenant.publishToServer("http://localhost:1661", {
|
|
176
|
+
systemAdminUser: result.adminUser,
|
|
177
|
+
systemAdminPassword: "admin-pass",
|
|
178
|
+
adminUsername: result.adminUser.username,
|
|
179
|
+
registerUsers: [factory.toPublicUserId(result.appUser)],
|
|
180
|
+
});
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Here, `systemAdminUser` is the delegated tenant admin identity, while `adminUsername: result.adminUser.username` tells the server which tenant admin identity should be stored in the new tenant's `config.json`.
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
const remoteStore = await result.tenant.connectToServer(
|
|
187
|
+
"http://localhost:1661",
|
|
188
|
+
"main",
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
const db = await result.tenant.openDB("main");
|
|
192
|
+
|
|
193
|
+
await db.pushChangesTo(remoteStore);
|
|
194
|
+
|
|
195
|
+
await db.pullChangesFrom(remoteStore);
|
|
196
|
+
await db.syncStoreChanges();
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## How `config.json` controls admin access
|
|
200
|
+
|
|
201
|
+
MindooDB uses a **capabilities-based model** for server administration. Instead of a single shared API key, each admin has their own Ed25519 keypair, and `config.json` declares exactly which endpoints each admin is allowed to call.
|
|
202
|
+
|
|
203
|
+
The server has two authentication tiers:
|
|
204
|
+
|
|
205
|
+
1. **System admin** — `config.json` capabilities + Ed25519 challenge/response + short-lived JWT for `/system/*`. Optional **`MINDOODB_ADMIN_ALLOWED_IPS`** restricts which client IPs may use the `/system/*` HTTP surface.
|
|
206
|
+
2. **User (per-tenant)** — Ed25519 challenge-response for sync endpoints (`/:tenantId/sync/*`). Users are authenticated against the tenant directory (admin-signed).
|
|
207
|
+
|
|
208
|
+
This section covers tier 1 -- system admin access. For the design rationale behind this model, see [Server Security](docs/server-security.md).
|
|
209
|
+
|
|
210
|
+
### Rule format
|
|
211
|
+
|
|
212
|
+
Each rule in `config.json` is a key-value pair where:
|
|
213
|
+
|
|
214
|
+
- The **key** is `METHOD:PATHPATTERN` — an HTTP method (or `ALL`) paired with a URL path pattern
|
|
215
|
+
- The **value** is an array of **principals** (admin identities), each identified by `username` + `publicsignkey`
|
|
216
|
+
|
|
217
|
+
```json
|
|
218
|
+
{
|
|
219
|
+
"capabilities": {
|
|
220
|
+
"METHOD:PATHPATTERN": [
|
|
221
|
+
{ "username": "<admin-username>", "publicsignkey": "<ed25519-public-key-pem>" }
|
|
222
|
+
]
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
**METHOD** can be `GET`, `POST`, `PUT`, `DELETE`, `PATCH`, or `ALL` (matches any HTTP method).
|
|
228
|
+
|
|
229
|
+
**PATHPATTERN** is a URL path that may end with `*` to match any remaining characters.
|
|
230
|
+
|
|
231
|
+
### Wildcard patterns
|
|
232
|
+
|
|
233
|
+
| Rule | What it grants |
|
|
234
|
+
|------|---------------|
|
|
235
|
+
| `ALL:/system/*` | Full access to all system endpoints |
|
|
236
|
+
| `POST:/system/tenants/*` | Can create any tenant (but not delete, update, or list) |
|
|
237
|
+
| `POST:/system/tenants/acme` | Can only create the specific tenant `acme` |
|
|
238
|
+
| `PUT:/system/tenants/company-*` | Can update any tenant whose ID starts with `company-` |
|
|
239
|
+
| `GET:/system/tenants` | Read-only: can list tenants but not modify anything |
|
|
240
|
+
| `DELETE:/system/tenants/*` | Can delete any tenant |
|
|
241
|
+
|
|
242
|
+
### Multiple admins with different roles
|
|
243
|
+
|
|
244
|
+
A realistic production config might look like this:
|
|
245
|
+
|
|
246
|
+
```json
|
|
247
|
+
{
|
|
248
|
+
"capabilities": {
|
|
249
|
+
"ALL:/system/*": [
|
|
250
|
+
{ "username": "cn=admin/o=myorg", "publicsignkey": "<key-admin>" }
|
|
251
|
+
],
|
|
252
|
+
"POST:/system/tenants/*": [
|
|
253
|
+
{ "username": "cn=provisioner/o=myorg", "publicsignkey": "<key-provisioner>" }
|
|
254
|
+
],
|
|
255
|
+
"GET:/system/tenants": [
|
|
256
|
+
{ "username": "cn=auditor/o=myorg", "publicsignkey": "<key-auditor>" }
|
|
257
|
+
]
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
- **`cn=admin`** is the super-admin with full access
|
|
263
|
+
- **`cn=provisioner`** can create tenants but cannot delete them, manage trusted servers, or change the config
|
|
264
|
+
- **`cn=auditor`** can list tenants (read-only) but has no write access
|
|
265
|
+
|
|
266
|
+
To delegate tenant management for a specific prefix to a team, add a scoped rule:
|
|
267
|
+
|
|
268
|
+
```json
|
|
269
|
+
"PUT:/system/tenants/team-alpha-*": [
|
|
270
|
+
{ "username": "cn=lead/o=alpha", "publicsignkey": "<key-lead>" }
|
|
271
|
+
]
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### How matching works at request time
|
|
275
|
+
|
|
276
|
+
On each `/system/*` request the server:
|
|
277
|
+
|
|
278
|
+
1. Extracts the HTTP method and path from the request
|
|
279
|
+
2. Validates the JWT and extracts `username` + `publicsignkey` from its payload
|
|
280
|
+
3. Finds all capability rules whose method and path pattern match
|
|
281
|
+
4. Unions all principal entries from matching rules
|
|
282
|
+
5. **Allows** the request if any entry has **both** a matching `username` AND `publicsignkey`
|
|
283
|
+
|
|
284
|
+
A principal is identified by the combination of username + public key. Two admins may share a username but have different keys -- they are treated as distinct identities.
|
|
285
|
+
|
|
286
|
+
### Updating config at runtime
|
|
287
|
+
|
|
288
|
+
You do not need to restart the server to change access rules. Use the system admin API:
|
|
289
|
+
|
|
290
|
+
- **`GET /system/config`** — read the current config
|
|
291
|
+
- **`PUT /system/config`** — replace the config (takes effect immediately)
|
|
292
|
+
- **`GET /system/config/backups`** — list previous config snapshots created by runtime updates
|
|
293
|
+
- **`GET /system/config/backups/:backupFile`** — read one previous validated config snapshot
|
|
294
|
+
|
|
295
|
+
Before overwriting, the server creates a timestamped backup (e.g., `config.2026-03-27T16-30-45.123Z.json`). Self-lockout protection rejects any change that would remove the calling admin's own `PUT /system/config` access.
|
|
296
|
+
|
|
297
|
+
In Node you can use `MindooDBServerAdmin` instead of raw HTTP:
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
const admin = new MindooDBServerAdmin({
|
|
301
|
+
serverUrl: "http://localhost:1661",
|
|
302
|
+
systemAdminUser: adminIdentity,
|
|
303
|
+
systemAdminPassword: "your-admin-password",
|
|
304
|
+
cryptoAdapter: new NodeCryptoAdapter(),
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
await admin.grantSystemAdminAccess(
|
|
308
|
+
{
|
|
309
|
+
username: "cn=newauditor/o=myorg",
|
|
310
|
+
publicsignkey: "<new-auditor-key>",
|
|
311
|
+
},
|
|
312
|
+
["GET:/system/tenants"],
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
const access = await admin.findSystemAdminAccess({
|
|
316
|
+
username: "cn=newauditor/o=myorg",
|
|
317
|
+
publicsignkey: "<new-auditor-key>",
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
const backups = await admin.listConfigBackups();
|
|
321
|
+
const previousConfig = await admin.getConfigBackup(backups[0].file);
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
#### Demo server `config.json` examples
|
|
325
|
+
|
|
326
|
+
For isolated demo environments, MindooDB Server also supports a special wildcard principal:
|
|
327
|
+
|
|
328
|
+
```json
|
|
329
|
+
{ "username": "*", "publicsignkey": "*" }
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
This wildcard is intentionally narrow:
|
|
333
|
+
|
|
334
|
+
- It is only valid on `POST:/system/tenants/...` capability rules
|
|
335
|
+
- It allows any username + any signing key to authenticate for tenant creation
|
|
336
|
+
- It does **not** unlock `GET /system/tenants`, `PUT /system/config`, or any other `/system/*` route
|
|
337
|
+
|
|
338
|
+
Open demo server (allow creation of any tenant name):
|
|
339
|
+
|
|
340
|
+
```json
|
|
341
|
+
{
|
|
342
|
+
"capabilities": {
|
|
343
|
+
"POST:/system/tenants/*": [
|
|
344
|
+
{ "username": "*", "publicsignkey": "*" }
|
|
345
|
+
]
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
Prefix-restricted demo server (allow only tenant names starting with `demo_`):
|
|
351
|
+
|
|
352
|
+
```json
|
|
353
|
+
{
|
|
354
|
+
"capabilities": {
|
|
355
|
+
"POST:/system/tenants/demo_*": [
|
|
356
|
+
{ "username": "*", "publicsignkey": "*" }
|
|
357
|
+
]
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
These examples are useful for public demos or temporary onboarding servers, but they are intentionally less strict than normal production setups. For regular servers, prefer explicit principals with real public keys.
|
|
363
|
+
|
|
364
|
+
For key rotation, adding/removing admins, and the full authentication flow (challenge/response, JWT lifecycle), see [Server Security](docs/server-security.md).
|
|
365
|
+
|
|
366
|
+
## Walkthrough: Multi-Server Setup
|
|
367
|
+
|
|
368
|
+
MindooDB servers can mirror encrypted data between each other. This section covers the full workflow: initializing servers, establishing trust, and configuring per-tenant sync.
|
|
369
|
+
|
|
370
|
+
### 1. Initialize servers
|
|
371
|
+
|
|
372
|
+
Run `bash serversetup.sh` on each machine (or in separate data directories for local testing):
|
|
373
|
+
|
|
374
|
+
```bash
|
|
375
|
+
# Server 1
|
|
376
|
+
bash serversetup.sh
|
|
377
|
+
# Choose: name=server1, data dir=../mindoodb-data-s1, bind 127.0.0.1
|
|
378
|
+
|
|
379
|
+
# Server 2
|
|
380
|
+
bash serversetup.sh
|
|
381
|
+
# Choose: name=server2, data dir=../mindoodb-data-s2, bind 127.0.0.1
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
Both runs print the server's public keys. Start both servers:
|
|
385
|
+
|
|
386
|
+
```bash
|
|
387
|
+
docker compose up -d
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### 2. Server discovery
|
|
391
|
+
|
|
392
|
+
Every initialized server exposes its public identity at a well-known URL:
|
|
393
|
+
|
|
394
|
+
```bash
|
|
395
|
+
curl https://server1.example.com/.well-known/mindoodb-server-info
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
```json
|
|
399
|
+
{
|
|
400
|
+
"name": "CN=server1",
|
|
401
|
+
"signingPublicKey": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----",
|
|
402
|
+
"encryptionPublicKey": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
This eliminates the need to manually copy public keys between servers.
|
|
407
|
+
|
|
408
|
+
### 3. Establish trust
|
|
409
|
+
|
|
410
|
+
The `add-to-network` CLI automates mutual trust exchange. It fetches each server's public keys from `/.well-known/mindoodb-server-info` and calls `POST /system/trusted-servers` on each side using your system admin identity.
|
|
411
|
+
|
|
412
|
+
```bash
|
|
413
|
+
printf '%s' 'admin-pass' > ./.admin-password && chmod 600 ./.admin-password
|
|
414
|
+
npm run server:add-to-network -- \
|
|
415
|
+
--new-server http://localhost:3001 \
|
|
416
|
+
--servers http://localhost:1661 \
|
|
417
|
+
--identity ../mindoodb-data-s1/system-admin-cn-sysadmin-o-myorg.identity.json \
|
|
418
|
+
--password-file ./.admin-password
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
The CLI authenticates with the `--identity` file against every server it contacts (`--new-server` **and** each URL in `--servers`), so that identity's principal must be listed in each server's `config.json`. If a server pair is already trusted, the CLI skips it instead of failing.
|
|
422
|
+
|
|
423
|
+
**Secrets:** Prefer **`--password-file`** so secrets are not placed in the process environment. Use **`MINDOODB_SYSTEM_ADMIN_PASSWORD`** only when you must script without files. A leading space before the command or `HISTCONTROL=ignorespace` reduces shell-history leakage.
|
|
424
|
+
|
|
425
|
+
Alternatively, you can exchange trust manually via curl:
|
|
426
|
+
|
|
427
|
+
```bash
|
|
428
|
+
curl -X POST http://server1:1661/system/trusted-servers \
|
|
429
|
+
-H "Content-Type: application/json" \
|
|
430
|
+
-H "Authorization: Bearer $SYSTEM_ADMIN_JWT" \
|
|
431
|
+
-d '{
|
|
432
|
+
"name": "CN=server2",
|
|
433
|
+
"signingPublicKey": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----",
|
|
434
|
+
"encryptionPublicKey": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"
|
|
435
|
+
}'
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
Trust changes take effect immediately -- no restart required.
|
|
439
|
+
|
|
440
|
+
### 4. Publish tenant to servers
|
|
441
|
+
|
|
442
|
+
Reuse the same system admin identity that is authorized in each server's `config.json`:
|
|
443
|
+
|
|
444
|
+
```typescript
|
|
445
|
+
await result.tenant.publishToServer("http://server1:1661", {
|
|
446
|
+
systemAdminUser: systemAdminIdentity,
|
|
447
|
+
systemAdminPassword: "sysadmin-pass",
|
|
448
|
+
adminUsername: result.adminUser.username,
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
await result.tenant.publishToServer("http://server2:3001", {
|
|
452
|
+
systemAdminUser: systemAdminIdentity,
|
|
453
|
+
systemAdminPassword: "sysadmin-pass",
|
|
454
|
+
adminUsername: result.adminUser.username,
|
|
455
|
+
});
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
### 5. Configure per-tenant sync
|
|
459
|
+
|
|
460
|
+
After trust is established, configure which tenants each server syncs. This gives full control over sync topology -- not every server needs to sync every tenant.
|
|
461
|
+
|
|
462
|
+
**Add a sync server for a tenant:**
|
|
463
|
+
|
|
464
|
+
```bash
|
|
465
|
+
curl -X POST http://server1:1661/system/tenants/acme/sync-servers \
|
|
466
|
+
-H "Content-Type: application/json" \
|
|
467
|
+
-H "Authorization: Bearer $SYSTEM_ADMIN_JWT" \
|
|
468
|
+
-d '{
|
|
469
|
+
"name": "CN=server2",
|
|
470
|
+
"url": "http://server2:3001",
|
|
471
|
+
"syncIntervalMs": 60000,
|
|
472
|
+
"databases": ["directory", "main"]
|
|
473
|
+
}'
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
The `databases` field is required and controls which databases are synced with the remote server. The `name` field identifies the remote server (must match the trusted server name). If a server with the same name already exists for the tenant, it is updated.
|
|
477
|
+
|
|
478
|
+
**List sync servers for a tenant:**
|
|
479
|
+
|
|
480
|
+
```bash
|
|
481
|
+
curl http://server1:1661/system/tenants/acme/sync-servers \
|
|
482
|
+
-H "Authorization: Bearer $SYSTEM_ADMIN_JWT"
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
**Remove a sync server from a tenant:**
|
|
486
|
+
|
|
487
|
+
```bash
|
|
488
|
+
curl -X DELETE http://server1:1661/system/tenants/acme/sync-servers/CN%3Dserver2 \
|
|
489
|
+
-H "Authorization: Bearer $SYSTEM_ADMIN_JWT"
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
Note: the server name in the URL must be percent-encoded (e.g., `CN%3Dserver2` for `CN=server2`).
|
|
493
|
+
|
|
494
|
+
### 6. Enable auto-sync
|
|
495
|
+
|
|
496
|
+
Add `--auto-sync` via the `command` key in each server's `docker-compose.override.yml`:
|
|
497
|
+
|
|
498
|
+
```yaml
|
|
499
|
+
services:
|
|
500
|
+
mindoodb:
|
|
501
|
+
command: ["--auto-sync"]
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
Then restart:
|
|
505
|
+
|
|
506
|
+
```bash
|
|
507
|
+
docker compose up -d
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
The servers will periodically sync all configured tenant databases, relaying encrypted entries without decrypting them. Sync config changes take effect on the next server restart or when auto-sync timers are restarted.
|
|
511
|
+
|
|
512
|
+
## Data Directory Layout
|
|
513
|
+
|
|
514
|
+
```
|
|
515
|
+
data/
|
|
516
|
+
├── server.identity.json # Server keypair (Ed25519 + RSA, password-encrypted)
|
|
517
|
+
├── config.json # Admin access rules (capabilities) — see "How config.json controls admin access"
|
|
518
|
+
├── trusted-servers.json # Public keys of remote servers trusted for sync
|
|
519
|
+
├── system-admin-*.identity.json # System admin keypair (password-encrypted)
|
|
520
|
+
├── acme/ # Tenant "acme"
|
|
521
|
+
│ ├── config.json # Tenant config (admin keys, users, sync servers)
|
|
522
|
+
│ └── stores/ # Content-addressed entry stores
|
|
523
|
+
└── other-tenant/
|
|
524
|
+
├── config.json
|
|
525
|
+
└── stores/
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
## Configuration File Formats
|
|
529
|
+
|
|
530
|
+
### `config.json` (global — capabilities)
|
|
531
|
+
|
|
532
|
+
Controls which system admins can call which `/system/*` endpoints. See [How `config.json` controls admin access](#how-configjson-controls-admin-access) for a full explanation of the rule format and wildcard patterns.
|
|
533
|
+
|
|
534
|
+
```json
|
|
535
|
+
{
|
|
536
|
+
"capabilities": {
|
|
537
|
+
"ALL:/system/*": [
|
|
538
|
+
{
|
|
539
|
+
"username": "cn=sysadmin/o=myorg",
|
|
540
|
+
"publicsignkey": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"
|
|
541
|
+
}
|
|
542
|
+
],
|
|
543
|
+
"POST:/system/tenants/*": [
|
|
544
|
+
{
|
|
545
|
+
"username": "cn=provisioner/o=myorg",
|
|
546
|
+
"publicsignkey": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"
|
|
547
|
+
}
|
|
548
|
+
]
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
### `server.identity.json` (global)
|
|
554
|
+
|
|
555
|
+
Generated by `npm run server:init`. Contains a `PrivateUserId` with encrypted private keys:
|
|
556
|
+
|
|
557
|
+
```json
|
|
558
|
+
{
|
|
559
|
+
"username": "CN=server1",
|
|
560
|
+
"userSigningKeyPair": {
|
|
561
|
+
"publicKey": "-----BEGIN PUBLIC KEY-----\n...",
|
|
562
|
+
"privateKey": {
|
|
563
|
+
"ciphertext": "...",
|
|
564
|
+
"iv": "...",
|
|
565
|
+
"tag": "...",
|
|
566
|
+
"salt": "...",
|
|
567
|
+
"iterations": 100000
|
|
568
|
+
}
|
|
569
|
+
},
|
|
570
|
+
"userEncryptionKeyPair": {
|
|
571
|
+
"publicKey": "-----BEGIN PUBLIC KEY-----\n...",
|
|
572
|
+
"privateKey": {
|
|
573
|
+
"ciphertext": "...",
|
|
574
|
+
"iv": "...",
|
|
575
|
+
"tag": "...",
|
|
576
|
+
"salt": "...",
|
|
577
|
+
"iterations": 100000
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
### `trusted-servers.json` (global)
|
|
584
|
+
|
|
585
|
+
```json
|
|
586
|
+
[
|
|
587
|
+
{
|
|
588
|
+
"name": "CN=server2",
|
|
589
|
+
"signingPublicKey": "-----BEGIN PUBLIC KEY-----\n...",
|
|
590
|
+
"encryptionPublicKey": "-----BEGIN PUBLIC KEY-----\n..."
|
|
591
|
+
}
|
|
592
|
+
]
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
### `<tenantId>/config.json`
|
|
596
|
+
|
|
597
|
+
```json
|
|
598
|
+
{
|
|
599
|
+
"adminUsername": "cn=admin/o=acme",
|
|
600
|
+
"adminSigningPublicKey": "-----BEGIN PUBLIC KEY-----\n...",
|
|
601
|
+
"adminEncryptionPublicKey": "-----BEGIN PUBLIC KEY-----\n...",
|
|
602
|
+
"publicInfosKey": "base64-encoded-aes-key",
|
|
603
|
+
"defaultStoreType": "file",
|
|
604
|
+
"users": [
|
|
605
|
+
{
|
|
606
|
+
"username": "alice",
|
|
607
|
+
"signingPublicKey": "-----BEGIN PUBLIC KEY-----\n...",
|
|
608
|
+
"encryptionPublicKey": "-----BEGIN PUBLIC KEY-----\n..."
|
|
609
|
+
}
|
|
610
|
+
],
|
|
611
|
+
"remoteServers": [
|
|
612
|
+
{
|
|
613
|
+
"name": "CN=server2",
|
|
614
|
+
"url": "https://server2.example.com",
|
|
615
|
+
"syncIntervalMs": 60000,
|
|
616
|
+
"databases": ["directory", "main"]
|
|
617
|
+
}
|
|
618
|
+
]
|
|
619
|
+
}
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
## Environment Variables
|
|
623
|
+
|
|
624
|
+
| Variable | Required | Description |
|
|
625
|
+
|----------|----------|-------------|
|
|
626
|
+
| `MINDOODB_SERVER_PASSWORD` | If `server.identity.json` exists and `MINDOODB_SERVER_PASSWORD_FILE` is unset | Password to decrypt server identity and per-tenant keybags |
|
|
627
|
+
| `MINDOODB_SERVER_PASSWORD_FILE` | No | Path to a file whose contents are the server password (trimmed). If set, **used instead of** `MINDOODB_SERVER_PASSWORD`. Prefer for Docker: env only holds the path, not the secret. |
|
|
628
|
+
| `MINDOODB_SYSTEM_ADMIN_PASSWORD` | No | For `server:add-to-network`: password for `--identity` (if not using `--password-file` or interactive prompt). May be visible in `ps` on shared hosts. |
|
|
629
|
+
| `MINDOODB_CORS_ORIGIN` | No | Allowed CORS origin (e.g., `https://app.example.com`). If not set, CORS is disabled. |
|
|
630
|
+
| `MINDOODB_ADMIN_ALLOWED_IPS` | No | Optional comma-separated client IPs/CIDRs allowed to call **`/system/*`** (all system admin routes, including `/system/auth/*`). If unset or `*`, any source IP may reach `/system/*` (JWT + `config.json` capabilities still apply). Example: `127.0.0.1,::1,172.23.248.0/24,2001:db8::/32`. Behind a reverse proxy, configure Express `trust proxy` so `req.ip` is the real client. |
|
|
631
|
+
|
|
632
|
+
> **Note:** The old `MINDOODB_ADMIN_API_KEY` variable has been removed. System admin **authorization** is enforced by `config.json` capabilities and JWTs. **`MINDOODB_ADMIN_ALLOWED_IPS`** is an optional **network** layer for `/system/*` only. See [Server Security](docs/server-security.md).
|
|
633
|
+
|
|
634
|
+
## CLI Reference
|
|
635
|
+
|
|
636
|
+
### `npm run server:dev` / `npm run server:start` — Start the server
|
|
637
|
+
|
|
638
|
+
Launches the MindooDB server process. `server:dev` runs via `ts-node` for development; `server:start` runs the compiled JavaScript. The server loads its identity from `server.identity.json` and the capabilities-based authorization config from `config.json` in the data directory.
|
|
639
|
+
|
|
640
|
+
| Option | Alias | Description | Default |
|
|
641
|
+
|--------|-------|-------------|---------|
|
|
642
|
+
| `--data-dir` | `-d` | Data directory path | `./data` |
|
|
643
|
+
| `--port` | `-p` | Server port | `1661` |
|
|
644
|
+
| `--auto-sync` | `-s` | Enable automatic sync with remote servers | disabled |
|
|
645
|
+
| `--static-dir` | `-w` | Serve static files at `/statics/` (e.g. bootstrap UI) | — |
|
|
646
|
+
| `--tls-cert` | — | Path to TLS certificate file (PEM) | — |
|
|
647
|
+
| `--tls-key` | — | Path to TLS private key file (PEM) | — |
|
|
648
|
+
| `--help` | `-h` | Show help message | — |
|
|
649
|
+
|
|
650
|
+
### `npm run server:init` — Initialize server identity
|
|
651
|
+
|
|
652
|
+
One-time setup that generates the server's Ed25519/RSA keypair (`server.identity.json`) and an empty `trusted-servers.json`. It also interactively offers to create a first system admin keypair and writes the initial `config.json` with that admin's public key in the `capabilities` section.
|
|
653
|
+
|
|
654
|
+
| Option | Alias | Description | Default |
|
|
655
|
+
|--------|-------|-------------|---------|
|
|
656
|
+
| `--name` | `-n` | Server name (e.g., "server1") | **required** |
|
|
657
|
+
| `--data-dir` | `-d` | Data directory path | `./data` |
|
|
658
|
+
| `--force` | `-f` | Overwrite existing identity | — |
|
|
659
|
+
| `--help` | `-h` | Show help message | — |
|
|
660
|
+
|
|
661
|
+
### `npm run server:add-to-network` — Add a server to the network
|
|
662
|
+
|
|
663
|
+
Automates mutual trust exchange when adding a new server to an existing network. For each existing server it fetches public keys from `/.well-known/mindoodb-server-info` and calls `POST /system/trusted-servers` in both directions so that the new server and every existing server trust each other.
|
|
664
|
+
|
|
665
|
+
| Option | Description | Default |
|
|
666
|
+
|--------|-------------|---------|
|
|
667
|
+
| `--new-server` | URL of the server being added | **required** |
|
|
668
|
+
| `--servers` | Comma-separated URLs of existing servers | **required** |
|
|
669
|
+
| `--identity` | Path to system admin `*.identity.json` (`PrivateUserId`) | **required** |
|
|
670
|
+
| `--password-file` | File containing the system admin password (optional if env or TTY) | — |
|
|
671
|
+
| `--help` | Show help message | — |
|
|
672
|
+
|
|
673
|
+
Password resolution order: read `MINDOODB_SYSTEM_ADMIN_PASSWORD`, then `--password-file`, then a hidden prompt on an interactive TTY. The CLI authenticates with the `--identity` file against every server it contacts (`--new-server` **and** each URL in `--servers`), so that identity's principal (username + public signing key) must be listed in the `capabilities` section of each server's `config.json`.
|
|
674
|
+
|
|
675
|
+
### `npm run identity:info` — Show public information from an identity file
|
|
676
|
+
|
|
677
|
+
| Option | Description | Default |
|
|
678
|
+
|--------|-------------|---------|
|
|
679
|
+
| `--identity` | Path to an `*.identity.json` file | **required** |
|
|
680
|
+
| `--help` | Show help message | — |
|
|
681
|
+
|
|
682
|
+
Prints the username, a stable SHA-256 username hash (`Public user ID (hex)`), both public keys, the fixed salt string names (`signing`, `encryption`), and whether encrypted private keys are present.
|
|
683
|
+
|
|
684
|
+
### `npm run identity:change-password` — Re-encrypt an identity with a new password
|
|
685
|
+
|
|
686
|
+
| Option | Description | Default |
|
|
687
|
+
|--------|-------------|---------|
|
|
688
|
+
| `--identity` | Path to an `*.identity.json` file | **required** |
|
|
689
|
+
| `--help` | Show help message | — |
|
|
690
|
+
|
|
691
|
+
Prompts for the current password, the new password, and confirmation using hidden input. The file is updated atomically in place.
|
|
692
|
+
|
|
693
|
+
### `npm run identity:export-public` — Export only the public portion of an identity
|
|
694
|
+
|
|
695
|
+
| Option | Description | Default |
|
|
696
|
+
|--------|-------------|---------|
|
|
697
|
+
| `--identity` | Path to an `*.identity.json` file | **required** |
|
|
698
|
+
| `--output` | Write JSON to a file instead of stdout | stdout |
|
|
699
|
+
| `--help` | Show help message | — |
|
|
700
|
+
|
|
701
|
+
This emits the corresponding `PublicUserId` JSON object:
|
|
702
|
+
|
|
703
|
+
```json
|
|
704
|
+
{
|
|
705
|
+
"username": "cn=sysadmin/o=myorg",
|
|
706
|
+
"userSigningPublicKey": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----",
|
|
707
|
+
"userEncryptionPublicKey": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"
|
|
708
|
+
}
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
## API Reference
|
|
712
|
+
|
|
713
|
+
### System Admin Endpoints (`/system/*`)
|
|
714
|
+
|
|
715
|
+
All system admin endpoints require a JWT obtained via challenge/response authentication.
|
|
716
|
+
See [Server Security](docs/server-security.md) for full details.
|
|
717
|
+
|
|
718
|
+
The **`curl`** examples in this document use **`$SYSTEM_ADMIN_JWT`** as a placeholder for that token. In Node you can use **`MindooDBServerAdmin`** (with your `CryptoAdapter`) instead of raw `curl`.
|
|
719
|
+
|
|
720
|
+
#### Authentication
|
|
721
|
+
|
|
722
|
+
| Method | Endpoint | Auth | Description |
|
|
723
|
+
|--------|----------|------|-------------|
|
|
724
|
+
| `POST` | `/system/auth/challenge` | None | Request a challenge (body: `{ username, publicsignkey }`) |
|
|
725
|
+
| `POST` | `/system/auth/authenticate` | None | Submit signed challenge |
|
|
726
|
+
|
|
727
|
+
#### Tenant Management
|
|
728
|
+
|
|
729
|
+
| Method | Endpoint | Auth | Description |
|
|
730
|
+
|--------|----------|------|-------------|
|
|
731
|
+
| `POST` | `/system/tenants/:tenantId` | JWT | Create a tenant |
|
|
732
|
+
| `GET` | `/system/tenants` | JWT | List all registered tenants |
|
|
733
|
+
| `PUT` | `/system/tenants/:tenantId` | JWT | Update tenant config |
|
|
734
|
+
| `DELETE` | `/system/tenants/:tenantId` | JWT | Remove a tenant |
|
|
735
|
+
|
|
736
|
+
#### Trusted Server Management
|
|
737
|
+
|
|
738
|
+
| Method | Endpoint | Auth | Description |
|
|
739
|
+
|--------|----------|------|-------------|
|
|
740
|
+
| `GET` | `/system/trusted-servers` | JWT | List trusted servers |
|
|
741
|
+
| `POST` | `/system/trusted-servers` | JWT | Add a trusted server |
|
|
742
|
+
| `DELETE` | `/system/trusted-servers/:serverName` | JWT | Remove a trusted server |
|
|
743
|
+
|
|
744
|
+
#### Per-Tenant Sync Server Management
|
|
745
|
+
|
|
746
|
+
| Method | Endpoint | Auth | Description |
|
|
747
|
+
|--------|----------|------|-------------|
|
|
748
|
+
| `GET` | `/system/tenants/:tenantId/sync-servers` | JWT | List sync servers for a tenant |
|
|
749
|
+
| `POST` | `/system/tenants/:tenantId/sync-servers` | JWT | Add or update a sync server |
|
|
750
|
+
| `DELETE` | `/system/tenants/:tenantId/sync-servers/:serverName` | JWT | Remove a sync server |
|
|
751
|
+
| `POST` | `/system/tenants/:tenantId/trigger-sync` | JWT | Trigger sync for a tenant |
|
|
752
|
+
|
|
753
|
+
#### Server Config Management
|
|
754
|
+
|
|
755
|
+
| Method | Endpoint | Auth | Description |
|
|
756
|
+
|--------|----------|------|-------------|
|
|
757
|
+
| `GET` | `/system/config` | JWT | Read the current server config |
|
|
758
|
+
| `PUT` | `/system/config` | JWT | Replace the server config (no restart needed) |
|
|
759
|
+
|
|
760
|
+
### Authentication Endpoints
|
|
761
|
+
|
|
762
|
+
| Method | Endpoint | Description |
|
|
763
|
+
|--------|----------|-------------|
|
|
764
|
+
| `POST` | `/:tenantId/auth/challenge` | Request authentication challenge |
|
|
765
|
+
| `POST` | `/:tenantId/auth/authenticate` | Authenticate with signed challenge |
|
|
766
|
+
|
|
767
|
+
### Sync Endpoints (requires JWT token)
|
|
768
|
+
|
|
769
|
+
| Method | Endpoint | Description |
|
|
770
|
+
|--------|----------|-------------|
|
|
771
|
+
| `POST` | `/:tenantId/sync/findNewEntries` | Find entries we don't have |
|
|
772
|
+
| `POST` | `/:tenantId/sync/findNewEntriesForDoc` | Find entries for specific document |
|
|
773
|
+
| `POST` | `/:tenantId/sync/getEntries` | Get specific entries |
|
|
774
|
+
| `POST` | `/:tenantId/sync/putEntries` | Push entries to server |
|
|
775
|
+
| `POST` | `/:tenantId/sync/hasEntries` | Check which entry IDs exist |
|
|
776
|
+
| `GET` | `/:tenantId/sync/getAllIds` | Get all entry IDs |
|
|
777
|
+
| `POST` | `/:tenantId/sync/resolveDependencies` | Resolve dependency chain |
|
|
778
|
+
| `POST` | `/:tenantId/sync/findEntries` | Find entries by type/date |
|
|
779
|
+
| `POST` | `/:tenantId/sync/scanEntriesSince` | Cursor-based entry scanning |
|
|
780
|
+
| `POST` | `/:tenantId/sync/getIdBloomSummary` | Get Bloom filter summary |
|
|
781
|
+
| `GET` | `/:tenantId/sync/capabilities` | Get server capabilities |
|
|
782
|
+
|
|
783
|
+
### Server Info & Health
|
|
784
|
+
|
|
785
|
+
| Method | Endpoint | Description |
|
|
786
|
+
|--------|----------|-------------|
|
|
787
|
+
| `GET` | `/health` | Server health status |
|
|
788
|
+
| `GET` | `/.well-known/mindoodb-server-info` | Server name and public keys (unauthenticated) |
|
|
789
|
+
|
|
790
|
+
## Security
|
|
791
|
+
|
|
792
|
+
The server includes the following hardening measures:
|
|
793
|
+
|
|
794
|
+
- **Input validation** -- all identifiers (tenantId, dbId, serverName) are validated to prevent path traversal. Only lowercase alphanumeric characters and hyphens are allowed, max 64 characters. Tenant IDs that collide with server route prefixes (`admin`, `health`, `statics`) are rejected as reserved names.
|
|
795
|
+
- **Rate limiting** -- tiered per-IP rate limits: auth endpoints (20/min), admin endpoints (30/min), sync endpoints (200/min), global fallback (500/min). Returns `429 Too Many Requests` when exceeded.
|
|
796
|
+
- **Security headers** -- `helmet` middleware sets X-Content-Type-Options, X-Frame-Options, Content-Security-Policy, Strict-Transport-Security, and others.
|
|
797
|
+
- **CORS** -- disabled by default. Set `MINDOODB_CORS_ORIGIN` to allow a specific origin.
|
|
798
|
+
- **Constant-time key comparison** -- tenant creation API keys are compared using `crypto.timingSafeEqual()` where applicable.
|
|
799
|
+
- **Error sanitization** -- internal errors never leak file paths or stack traces to clients. Only known auth/validation errors return specific messages.
|
|
800
|
+
- **Request size limits** -- JSON body limited to 5MB. Array sizes capped (100k IDs, 10k entries for putEntries).
|
|
801
|
+
- **Connection timeouts** -- idle connections are closed after 30 seconds.
|
|
802
|
+
- **Optional `/system/*` IP allowlist** — set `MINDOODB_ADMIN_ALLOWED_IPS` to a comma-separated list of IPs or IPv4/IPv6 CIDRs (e.g., `127.0.0.1,::1,10.0.0.0/8,2001:db8::/32`) to restrict which client addresses may call **any** `/system/*` route (including auth). If unset or `*`, there is no IP restriction at this layer. IPv4-mapped IPv6 (`::ffff:127.0.0.1`) is normalized. Behind a reverse proxy, configure Express `trust proxy` so `req.ip` is accurate.
|
|
803
|
+
|
|
804
|
+
For production deployments, also consider:
|
|
805
|
+
|
|
806
|
+
- Enabling TLS (see below) or running behind a reverse proxy (nginx, Caddy) with TLS termination
|
|
807
|
+
- Setting `MINDOODB_ADMIN_ALLOWED_IPS` if `/system/*` should only be reachable from operator networks
|
|
808
|
+
- Using a process manager (PM2, systemd) for automatic restarts
|
|
809
|
+
|
|
810
|
+
## Static File Serving
|
|
811
|
+
|
|
812
|
+
The server can serve static files from a local directory, which is useful for hosting a bootstrap UI for [distributed web applications](docs/distributed-webapps.md). When `--static-dir` is provided, two additional routes are available:
|
|
813
|
+
|
|
814
|
+
| Method | Endpoint | Description |
|
|
815
|
+
|--------|----------|-------------|
|
|
816
|
+
| `GET` | `/` | Redirects to `/statics/index.html` if the file exists (302), otherwise returns 404 |
|
|
817
|
+
| `GET` | `/statics/*` | Serves files from the configured static directory |
|
|
818
|
+
|
|
819
|
+
### Usage
|
|
820
|
+
|
|
821
|
+
```bash
|
|
822
|
+
MINDOODB_SERVER_PASSWORD_FILE=./.server-password npm run server:dev -- --static-dir ./webapp-bootstrap
|
|
823
|
+
```
|
|
824
|
+
|
|
825
|
+
The static directory might contain a minimal bootstrap page that registers a service worker and triggers the initial MindooDB sync for a distributed web application:
|
|
826
|
+
|
|
827
|
+
```
|
|
828
|
+
webapp-bootstrap/
|
|
829
|
+
├── index.html # Minimal HTML shell, registers the service worker
|
|
830
|
+
├── bootstrap.js # Sync logic: check pointer DB, sync UI DB
|
|
831
|
+
├── sw.js # Service worker: serves UI assets from IndexedDB
|
|
832
|
+
└── style.css # Loading indicator styles
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
Path traversal is prevented by an explicit guard that rejects requests containing `..`, in addition to the built-in protection provided by Express's static middleware. Dotfiles (`.env`, `.git`, etc.) are not served.
|
|
836
|
+
|
|
837
|
+
## TLS / HTTPS
|
|
838
|
+
|
|
839
|
+
The server supports TLS directly via `--tls-cert` and `--tls-key` flags. No additional dependencies are required.
|
|
840
|
+
|
|
841
|
+
### Starting with TLS
|
|
842
|
+
|
|
843
|
+
```bash
|
|
844
|
+
MINDOODB_SERVER_PASSWORD_FILE=./.server-password npm run server:dev -- \
|
|
845
|
+
--tls-cert /etc/letsencrypt/live/sync.example.com/fullchain.pem \
|
|
846
|
+
--tls-key /etc/letsencrypt/live/sync.example.com/privkey.pem \
|
|
847
|
+
-p 443
|
|
848
|
+
```
|
|
849
|
+
|
|
850
|
+
Both flags must be provided together. The certificate file should be the full chain (PEM format).
|
|
851
|
+
|
|
852
|
+
### Free certificates with Let's Encrypt
|
|
853
|
+
|
|
854
|
+
#### Method A: Standalone (HTTP-01)
|
|
855
|
+
|
|
856
|
+
Certbot briefly binds port 80 to prove domain ownership. Best when port 80 is available.
|
|
857
|
+
|
|
858
|
+
```bash
|
|
859
|
+
# Install certbot (Ubuntu/Debian)
|
|
860
|
+
sudo apt install certbot
|
|
861
|
+
|
|
862
|
+
# Obtain certificate
|
|
863
|
+
sudo certbot certonly --standalone -d sync.example.com
|
|
864
|
+
```
|
|
865
|
+
|
|
866
|
+
Auto-renewal is handled by certbot's systemd timer, which runs automatically on most Linux distributions. After renewal, restart the server to pick up the new certificate.
|
|
867
|
+
|
|
868
|
+
#### Method B: DNS (DNS-01)
|
|
869
|
+
|
|
870
|
+
Prove ownership via a DNS TXT record. No port 80 required -- works behind firewalls and on non-standard ports.
|
|
871
|
+
|
|
872
|
+
**Automated (recommended for production)** -- use a DNS provider plugin so renewal is fully unattended:
|
|
873
|
+
|
|
874
|
+
```bash
|
|
875
|
+
# Example with Cloudflare
|
|
876
|
+
sudo apt install certbot python3-certbot-dns-cloudflare
|
|
877
|
+
|
|
878
|
+
# Create credentials file
|
|
879
|
+
cat > /etc/letsencrypt/cloudflare.ini << EOF
|
|
880
|
+
dns_cloudflare_api_token = your-cloudflare-api-token
|
|
881
|
+
EOF
|
|
882
|
+
chmod 600 /etc/letsencrypt/cloudflare.ini
|
|
883
|
+
|
|
884
|
+
# Obtain certificate
|
|
885
|
+
sudo certbot certonly --dns-cloudflare \
|
|
886
|
+
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
|
|
887
|
+
-d sync.example.com
|
|
888
|
+
```
|
|
889
|
+
|
|
890
|
+
With a DNS plugin, certbot creates and removes the TXT record via the provider's API automatically. The systemd timer handles renewal with zero manual intervention.
|
|
891
|
+
|
|
892
|
+
**Manual (testing only)** -- requires updating the DNS TXT record by hand every 90 days:
|
|
893
|
+
|
|
894
|
+
```bash
|
|
895
|
+
sudo certbot certonly --manual --preferred-challenges dns -d sync.example.com
|
|
896
|
+
# Certbot will ask you to create: _acme-challenge.sync.example.com TXT "..."
|
|
897
|
+
```
|
|
898
|
+
|
|
899
|
+
Available DNS plugins include Cloudflare, Route53, Google Cloud DNS, DigitalOcean, Linode, and OVH. See the [certbot documentation](https://eff-certbot.readthedocs.io/en/latest/using.html#dns-plugins) for the full list.
|
|
900
|
+
|
|
901
|
+
## Docker
|
|
902
|
+
|
|
903
|
+
The server ships with a multi-stage `Dockerfile` under `src/node/server/` that produces a minimal Alpine-based image. The build context must be the **repository root** so the library and server compile together.
|
|
904
|
+
|
|
905
|
+
**Recommended:** use `bash serversetup.sh` (see [Quick Start](#quick-start)) to build, initialise, and configure everything interactively. The rest of this section covers manual Docker commands for advanced use cases.
|
|
906
|
+
|
|
907
|
+
### docker-compose.yml
|
|
908
|
+
|
|
909
|
+
The repository includes a `docker-compose.yml` that uses the default data paths (`../mindoodb-data`). `serversetup.sh` writes a `docker-compose.override.yml` that docker compose merges automatically. It pins the container to your current host uid/gid so the non-root container can read the password file and write to the mounted data directory. On SELinux hosts it also adds the required mount suffixes.
|
|
910
|
+
|
|
911
|
+
The generated override also contains the published port bindings. This supports all three common setups:
|
|
912
|
+
- bind all interfaces (`0.0.0.0`)
|
|
913
|
+
- bind a single specific IP (for example a VPN address)
|
|
914
|
+
- bind both `127.0.0.1` and one specific extra IP
|
|
915
|
+
|
|
916
|
+
### Manual Docker commands (without serversetup.sh)
|
|
917
|
+
|
|
918
|
+
If you prefer not to use the setup script, here are the individual steps:
|
|
919
|
+
|
|
920
|
+
```bash
|
|
921
|
+
# Build the image
|
|
922
|
+
docker build -f src/node/server/Dockerfile -t mindoodb-server .
|
|
923
|
+
|
|
924
|
+
# Create data directory and password file
|
|
925
|
+
mkdir -p ../mindoodb-data/server
|
|
926
|
+
printf '%s' 'your-secret' > ../mindoodb-data/.server_unlock
|
|
927
|
+
chmod 600 ../mindoodb-data/.server_unlock
|
|
928
|
+
|
|
929
|
+
# Initialize server identity (interactive — prompts for system admin creation)
|
|
930
|
+
# Run as your current host uid/gid so the container can read/write the bind mounts.
|
|
931
|
+
docker run --rm -it \
|
|
932
|
+
--user "$(id -u):$(id -g)" \
|
|
933
|
+
-v "$(pwd)/../mindoodb-data/server:/data" \
|
|
934
|
+
-v "$(pwd)/../mindoodb-data/.server_unlock:/run/secrets/server_unlock:ro" \
|
|
935
|
+
-e MINDOODB_SERVER_PASSWORD_FILE=/run/secrets/server_unlock \
|
|
936
|
+
--entrypoint node \
|
|
937
|
+
mindoodb-server dist/node/server/serverinit.js --data-dir /data --name server1
|
|
938
|
+
|
|
939
|
+
# Start the server
|
|
940
|
+
docker compose up -d
|
|
941
|
+
|
|
942
|
+
# Verify
|
|
943
|
+
curl http://localhost:1661/health
|
|
944
|
+
```
|
|
945
|
+
|
|
946
|
+
On SELinux hosts, append `:Z` to the `/data` bind mount and `,Z` to the read-only password-file mount.
|
|
947
|
+
|
|
948
|
+
### Bind to a specific IP
|
|
949
|
+
|
|
950
|
+
To restrict the server to a specific network interface (e.g. a VPN), rerun `bash serversetup.sh` and provide the bind address when prompted.
|
|
951
|
+
|
|
952
|
+
If you also want local checks from the same host, answer `y` when asked whether to also bind `127.0.0.1`. The generated `docker-compose.override.yml` will then contain both mappings.
|
|
953
|
+
|
|
954
|
+
### Additional flags
|
|
955
|
+
|
|
956
|
+
Environment variables and CLI flags can be added to `docker-compose.override.yml`:
|
|
957
|
+
|
|
958
|
+
```yaml
|
|
959
|
+
services:
|
|
960
|
+
mindoodb:
|
|
961
|
+
environment:
|
|
962
|
+
MINDOODB_ADMIN_ALLOWED_IPS: "127.0.0.1,10.0.0.0/8"
|
|
963
|
+
command: ["--port", "8443", "--auto-sync"]
|
|
964
|
+
ports:
|
|
965
|
+
- "0.0.0.0:8443:8443"
|
|
966
|
+
```
|
|
967
|
+
|
|
968
|
+
### Password handling
|
|
969
|
+
|
|
970
|
+
Prefer **`MINDOODB_SERVER_PASSWORD_FILE`** over **`MINDOODB_SERVER_PASSWORD`** so the plaintext secret is not stored in the process environment block (visible via `docker inspect`, `/proc/<pid>/environ`). The setup script and `docker-compose.yml` use file-based passwords by default. See [Server Security](docs/server-security.md) for details on Docker secrets.
|
|
971
|
+
|
|
972
|
+
## Development
|
|
973
|
+
|
|
974
|
+
From the **repository root** (where `README-server.md` lives):
|
|
975
|
+
|
|
976
|
+
### Build
|
|
977
|
+
|
|
978
|
+
```bash
|
|
979
|
+
npm run build
|
|
980
|
+
```
|
|
981
|
+
|
|
982
|
+
### Run in development mode
|
|
983
|
+
|
|
984
|
+
```bash
|
|
985
|
+
MINDOODB_SERVER_PASSWORD_FILE=./.server-password npm run server:dev -- -d ./data -p 1661
|
|
986
|
+
```
|
|
987
|
+
|
|
988
|
+
### Run built version
|
|
989
|
+
|
|
990
|
+
```bash
|
|
991
|
+
MINDOODB_SERVER_PASSWORD_FILE=./.server-password npm run server:start -- -d ./data -p 1661
|
|
992
|
+
```
|
|
993
|
+
|
|
994
|
+
## Testing
|
|
995
|
+
|
|
996
|
+
The integration tests are located in the main MindooDB package at `src/__tests__/ExampleServer.test.ts`. Run them with:
|
|
997
|
+
|
|
998
|
+
```bash
|
|
999
|
+
# From the root mindoodb directory
|
|
1000
|
+
npm test -- ExampleServer
|
|
1001
|
+
```
|