Skip to content
 
 

Repository files navigation

LiveSync Bridge

screenshot

What is this?

This is a custom replicator between Self-hosted LiveSync remote vaults and storage. The Unified Version of filesystem-livesync and livesync-classroom.

A Vault or storage can be synchronised with vaults or storage. You can even combine them. Of course, different passphrases for each vault could be used. And, you can synchronize documents under the specified folder on the vault, to another vault's specified one.

Of course, it is multi-directional!

How to use

Prerequisites

Simply run

  1. Clone the GitHub Repository
git clone --recursive https://github.com/vrtmrz/livesync-bridge
  1. Open the config file dat/config.sample.json, edit and save to dat/config.json. (You do not have to worry, the sample is in the following section).
  2. Simply run like this.
$ deno install
$ deno task run

Note: If you want to scan all storage and databases from the beginning, please run with --reset.

Docker Instructions

  1. Clone the GitHub Repository
git clone https://github.com/vrtmrz/livesync-bridge
  1. Open the config file dat/config.sample.json, edit and save to dat/config.json. (The storage folder has to start with "data/" to be in the volume)

  2. Create the bind-mount directories and make them writable by the host user that the container will match:

mkdir -p data dat
sudo chown -R "$(id -u):$(id -g)" data dat

Compose deliberately uses create_host_path: false, so it fails instead of silently creating a root-owned host directory when either path is missing.

  1. Build and start with the host user's numeric IDs:
APP_UID="$(id -u)" APP_GID="$(id -g)" docker compose up -d --build

Container user and bind-mount permissions

The image runs as a non-root user with UID and GID 1000 by default. Compose passes APP_UID and APP_GID into the image build, so the command above matches the container user to the owner of the bind-mounted data and dat paths.

Numeric UID/GID matching is primarily intended for rootful Docker on Linux. Rootless Docker, user-namespace remapping, and Docker Desktop translate file ownership differently and may require platform-specific permissions. Custom IDs can also collide with accounts already present in the base image, in which case the image build fails rather than changing an unrelated account.

Existing bind mounts keep their current ownership. Adjust their ownership before switching IDs; do not make the vault world-writable.

For a bind-mounted vault that contains root-owned files and must be updated by the bridge, explicitly opt into root mode by setting both build arguments to 0:

APP_UID=0 APP_GID=0 docker compose up -d --build

Root mode is intentionally opt-in. It gives the bridge full write access to the mounted paths, so use it only for a trusted, dedicated vault mount. Mixed root and non-root IDs (for example, APP_UID=0 with APP_GID=1000) are rejected at build time.

Configuration

By default, LiveSync Bridge reads ./dat/config.json. Set LSB_CONFIG to use a different configuration file path.

The complete configuration can instead be supplied as JSON through LSB_CONFIG_JSON:

LSB_CONFIG_JSON='{"peers":[{"type":"storage","name":"storage","baseDir":"./data"}]}' deno task run

When LSB_CONFIG_JSON is set, it takes precedence over LSB_CONFIG and no configuration file is read. The same variable can be used with Docker Compose:

services:
  bridge:
    environment:
      LSB_CONFIG_JSON: '{"peers":[{"type":"storage","name":"storage","baseDir":"./data"}]}'

The configuration file consists of the following structure.

{
  "peers": [
    {
      "type": "couchdb", // Type should be `couchdb or storage`
      "name": "test1", // Should be unique
      "group": "main", // we can omit this.
      "database": "test",
      "username": "admin",
      "password": "password",
      "url": "http://localhost:5984",
      "customChunkSize": 100,
      "minimumChunkSize": 20,
      "passphrase": "passphrase", // E2EE passphrase, if you do not enabled, leave it blank.
      "obfuscatePassphrase": "passphrase", // Path obfuscation passphrase, if you do not enabled, leave it blank. if enabled, set the same value of passphrase.
      "baseDir": "blog/", // Sharing folder
      "includeInternal": [".claude/**"], // Opt-in glob patterns for internal/hidden files (see caution below). Omit to keep the default of skipping them.
      "useRemoteTweaks":true // Overwrite customChunkSize or minimumChunkSize, and check configuration matches
    },
    {
      "type": "couchdb",
      "name": "test2", // We can even synchronise the same databases as long as they have different names in here.
      "group": "main", // we can omit this.
      "database": "test2",
      "username": "admin",
      "passphrase": "passphrase",
      "password": "password",
      "url": "http://localhost:5984",
      "customChunkSize": 100,
      "minimumChunkSize": 20,
      "obfuscatePassphrase": "passphrase",
      "baseDir": "xxxx/",
    },
    {
      "type": "storage",
      "name": "storage-test1",
      "group": "main", // we can omit this.
      "baseDir": "./vault/", // The folder which have been synchronised.
      "processor": { // The processor configuration. You can omit this.
        "cmd": "script/test.sh",  // The programme which run at file modification or deletion.
        "args": [ "$filename", "$mode" ] 
        // The modified file is set to $filename. The mode is set to `deleted` or `modified`. 
        // $filename and $mode have been set also in environment variables.
      },
      "scanOfflineChanges": true,
      "useChokidar":false, // We are using `Deno.watch` now, if you have trouble in Linux, please enable this.
    }
  ]
}

Synchronising internal/hidden files

By default, the bridge skips all of Self-hosted LiveSync's internal documents — the ones stored with an i: prefix, such as files inside hidden folders like .obsidian/, .claude/, and other tool configuration directories. These are normally left out of the sync.

The optional includeInternal field opts specific internal paths back in. It takes an array of minimatch glob patterns that are matched against the path after the i: prefix is stripped:

{
  "type": "couchdb",
  "name": "test1",
  // ...
  "baseDir": "blog/",
  "includeInternal": [".claude/**"] // Sync everything under .claude/
}

A document is included when its de-prefixed path matches any one of the patterns. Patterns are matched with the dot option, so leading-dot folders such as .claude/ are matched as expected.

Caution

This synchronises files that are normally hidden and internal. Such folders often hold tool configuration that can contain machine-specific paths, local settings, or secrets. Only include patterns you genuinely intend to share, and review what they match before enabling. The option is opt-in: leave it out to keep the default behaviour, where all internal/hidden files are skipped.

Realistic example

name database_uri / path CouchDB username CouchDB password vault E2EE passphrase baseDir
private vault of Cornbread http://localhost:5984/classroom_cornbread cornbread tackle glucose shared/
shared vault http://localhost:5984/classroom_shared common_user resu_nommoc cocoa
private vault of Vanilla http://localhost:5984/classroom_vanilla vanilla liberty smock kyouyuu/
storage ./vault/

Cornbread's every document under "shared" is synchronized with the top of the shared vault:

Cornbread shared
document1 Not transferred
document2 Not transferred
shared/shared_doc1 shared_doc1
shared/sub/sub_doc sub/sub_doc

Vanilla's every document under "kyouyuu" is synchronized with the top of the shared vault:

Vanilla shared
documentA Not transferred
documentB Not transferred
kyouyuu/some_doc some_doc
kyouyuu/sub/some_sub_doc sub/some_sub_doc

Totally, all files are synchronized like this:

Cornbread shared Vanilla
document1 Not transferred
document2 Not transferred
Not transferred documentA
Not transferred documentB
shared/shared_doc1 shared_doc1 kyouyuu/shared_doc1
shared/some_doc some_doc kyouyuu/some_doc
shared/sub/some_sub_doc sub/some_sub_doc kyouyuu/sub/some_sub_doc
shared/sub/sub_doc sub/sub_doc kyouyuu/sub/sub_doc

... with the configuration below:

{
  "peers": [
    {
      "type": "couchdb", // Type should be `couchdb or storage`
      "name": "cornbread", // Should be unique
      "url": "http://localhost:5984",
      "database": "classroom_cornbread",
      "username": "cornbread",
      "password": "tackle",
      "passphrase": "glucose", // E2EE passphrase, if you do not enabled, leave it blank.
      "obfuscatePassphrase": "glucose", // Path obfuscation passphrase, if you do not enabled, leave it blank. if enabled, set the same value of passphrase.
      "customChunkSize": 100,
      "minimumChunkSize": 20,
      "baseDir": "shared/" // Sharing folder
    },
    {
      "type": "couchdb", // Type should be `couchdb or storage`
      "name": "shared", // Should be unique
      "url": "http://localhost:5984",
      "database": "classroom_shared",
      "username": "common_user",
      "password": "resu_nommoc",
      "passphrase": "cocoa", // E2EE passphrase, if you do not enabled, leave it blank.
      "obfuscatePassphrase": "cocoa", // Path obfuscation passphrase, if you do not enabled, leave it blank. if enabled, set the same value of passphrase.
      "customChunkSize": 100,
      "minimumChunkSize": 20,
      "baseDir": "" // Sharing folder
    },
    {
      "type": "couchdb",
      "name": "vanilla", // We can even synchronise the same databases as long as they have different names in here.
      "url": "http://localhost:5984",
      "database": "classroom_vanilla",
      "username": "vanilla",
      "password": "liberty",
      "passphrase": "smock",
      "obfuscatePassphrase": "smock",
      "customChunkSize": 100,
      "minimumChunkSize": 20,
      "baseDir": "kyouyuu/"
    },
    {
      "type": "storage",
      "name": "storage-test1",
      "baseDir": "./vault/" // The folder which have been synchronised.
    }
  ]
}

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages