- Update trystero to v0.23.0

- Add dockerfile for CLI
- Change relay image for testing on arm64
This commit is contained in:
vorotamoroz
2026-03-31 07:17:51 +00:00
parent 837a828cec
commit cda27fb7f8
24 changed files with 1459 additions and 3488 deletions
+31
View File
@@ -0,0 +1,31 @@
# Git history
.git/
.gitignore
# Dependencies — re-installed inside Docker
node_modules/
src/apps/cli/node_modules/
# Pre-built CLI output — rebuilt inside Docker
src/apps/cli/dist/
# Obsidian plugin build outputs
main.js
main_org.js
pouchdb-browser.js
production/
# Test coverage and reports
coverage/
# Local environment / secrets
.env
*.env
.test.env
# local config files
*.local
# OS artefacts
.DS_Store
Thumbs.db
+575 -3078
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -132,17 +132,17 @@
"@smithy/middleware-apply-body-checksum": "^4.3.9", "@smithy/middleware-apply-body-checksum": "^4.3.9",
"@smithy/protocol-http": "^5.3.9", "@smithy/protocol-http": "^5.3.9",
"@smithy/querystring-builder": "^4.2.9", "@smithy/querystring-builder": "^4.2.9",
"@trystero-p2p/nostr": "^0.23.0",
"commander": "^14.0.3", "commander": "^14.0.3",
"diff-match-patch": "^1.0.5", "diff-match-patch": "^1.0.5",
"fflate": "^0.8.2", "fflate": "^0.8.2",
"idb": "^8.0.3", "idb": "^8.0.3",
"markdown-it": "^14.1.1", "markdown-it": "^14.1.1",
"minimatch": "^10.2.2", "minimatch": "^10.2.2",
"node-datachannel": "^0.32.1",
"octagonal-wheels": "^0.1.45", "octagonal-wheels": "^0.1.45",
"pouchdb-adapter-leveldb": "^9.0.0", "pouchdb-adapter-leveldb": "^9.0.0",
"qrcode-generator": "^1.4.4", "qrcode-generator": "^1.4.4",
"trystero": "^0.22.0", "werift": "^0.22.9",
"xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2" "xxhash-wasm-102": "npm:xxhash-wasm@^1.0.2"
} }
} }
+5 -4
View File
@@ -1,5 +1,6 @@
.livesync .livesync
test/* test/*
!test/*.sh !test/*.sh
node_modules test/test-init.local.sh
node_modules
.*.json .*.json
+111
View File
@@ -0,0 +1,111 @@
# syntax=docker/dockerfile:1
#
# Self-hosted LiveSync CLI — Docker image
#
# Build (from the repository root):
# docker build -f src/apps/cli/Dockerfile -t livesync-cli .
#
# Run:
# docker run --rm -v /path/to/your/vault:/data livesync-cli sync
# docker run --rm -v /path/to/your/vault:/data livesync-cli ls
# docker run --rm -v /path/to/your/vault:/data livesync-cli init-settings
# docker run --rm -v /path/to/your/vault:/data livesync-cli --help
#
# The first positional argument (database-path) is automatically set to /data.
# Mount your vault at /data, or override with: -e LIVESYNC_DB_PATH=/other/path
#
# P2P (WebRTC) networking — important notes
# -----------------------------------------
# The P2P replicator (p2p-host / p2p-sync / p2p-peers) uses WebRTC, which
# generates ICE candidates of three kinds:
#
# host — the container's bridge IP (172.17.x.x). Unreachable from outside
# the Docker bridge, so LAN peers cannot connect via this candidate.
# srflx — the host's public IP, obtained via STUN reflection. Works fine
# over the internet even with the default bridge network.
# relay — traffic relayed through a TURN server. Always reachable regardless
# of network mode.
#
# Recommended network modes per use-case:
#
# LAN P2P (Linux only)
# docker run --network host ...
# This exposes the real host IP as the 'host' candidate so LAN peers can
# connect directly. --network host is not available on Docker Desktop for
# macOS or Windows.
#
# LAN P2P (macOS / Windows Docker Desktop)
# Configure a TURN server in settings (P2P_turnServers / P2P_turnUsername /
# P2P_turnCredential). All data is then relayed through the TURN server,
# bypassing the bridge-network limitation.
#
# Internet P2P
# Default bridge network is sufficient; the srflx candidate carries the
# host's public IP and peers can connect normally.
#
# CouchDB sync only (no P2P)
# Default bridge network. No special configuration required.
# ─────────────────────────────────────────────────────────────────────────────
# Stage 1 — builder
# Full Node.js environment to compile native modules and bundle the CLI.
# ─────────────────────────────────────────────────────────────────────────────
FROM node:22-slim AS builder
# Build tools required by native Node.js addons (mainly leveldown)
RUN apt-get update \
&& apt-get install -y --no-install-recommends python3 make g++ \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /build
# Install workspace dependencies first (layer-cache friendly)
COPY package.json ./
RUN npm install
# Copy the full source tree and build the CLI bundle
COPY . .
RUN cd src/apps/cli && npm run build
# ─────────────────────────────────────────────────────────────────────────────
# Stage 2 — runtime-deps
# Install only the external (unbundled) packages that the CLI requires at
# runtime. Native addons are compiled here against the same base image that
# the final runtime stage uses.
# ─────────────────────────────────────────────────────────────────────────────
FROM node:22-slim AS runtime-deps
# Build tools required to compile native addons
RUN apt-get update \
&& apt-get install -y --no-install-recommends python3 make g++ \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /deps
# runtime-package.json lists only the packages that Vite leaves external
COPY src/apps/cli/runtime-package.json ./package.json
RUN npm install --omit=dev
# ─────────────────────────────────────────────────────────────────────────────
# Stage 3 — runtime
# Minimal image: pre-compiled native modules + CLI bundle only.
# No build tools are included, keeping the image small.
# ─────────────────────────────────────────────────────────────────────────────
FROM node:22-slim
WORKDIR /app
# Copy pre-compiled external node_modules from runtime-deps stage
COPY --from=runtime-deps /deps/node_modules ./node_modules
# Copy the built CLI bundle from builder stage
COPY --from=builder /build/src/apps/cli/dist ./dist
# Install entrypoint wrapper
COPY src/apps/cli/docker-entrypoint.sh /usr/local/bin/livesync-cli
RUN chmod +x /usr/local/bin/livesync-cli
# Mount your vault / local database directory here
VOLUME ["/data"]
ENTRYPOINT ["livesync-cli"]
+416 -362
View File
@@ -1,362 +1,416 @@
# Self-hosted LiveSync CLI # Self-hosted LiveSync CLI
Command-line version of Self-hosted LiveSync plugin for syncing vaults without Obsidian. Command-line version of Self-hosted LiveSync plugin for syncing vaults without Obsidian.
## Features ## Features
- ✅ Sync Obsidian vaults using CouchDB without running Obsidian - ✅ Sync Obsidian vaults using CouchDB without running Obsidian
- ✅ Compatible with Self-hosted LiveSync plugin settings - ✅ Compatible with Self-hosted LiveSync plugin settings
- ✅ Supports all core sync features (encryption, conflict resolution, etc.) - ✅ Supports all core sync features (encryption, conflict resolution, etc.)
- ✅ Lightweight and headless operation - ✅ Lightweight and headless operation
- ✅ Cross-platform (Windows, macOS, Linux) - ✅ Cross-platform (Windows, macOS, Linux)
## Architecture ## Architecture
This CLI version is built using the same core as the Obsidian plugin: This CLI version is built using the same core as the Obsidian plugin:
``` ```
CLI Main CLI Main
└─ LiveSyncBaseCore<ServiceContext, IMinimumLiveSyncCommands> └─ LiveSyncBaseCore<ServiceContext, IMinimumLiveSyncCommands>
├─ NodeServiceHub (All services without Obsidian dependencies) ├─ NodeServiceHub (All services without Obsidian dependencies)
└─ ServiceModules (wired by initialiseServiceModulesCLI) └─ ServiceModules (wired by initialiseServiceModulesCLI)
├─ FileAccessCLI (Node.js FileSystemAdapter) ├─ FileAccessCLI (Node.js FileSystemAdapter)
├─ StorageEventManagerCLI ├─ StorageEventManagerCLI
├─ ServiceFileAccessCLI ├─ ServiceFileAccessCLI
├─ ServiceDatabaseFileAccessCLI ├─ ServiceDatabaseFileAccessCLI
├─ ServiceFileHandler ├─ ServiceFileHandler
└─ ServiceRebuilder └─ ServiceRebuilder
``` ```
### Key Components ### Key Components
1. **Node.js FileSystem Adapter** (`adapters/`) 1. **Node.js FileSystem Adapter** (`adapters/`)
- Platform-agnostic file operations using Node.js `fs/promises` - Platform-agnostic file operations using Node.js `fs/promises`
- Implements same interface as Obsidian's file system - Implements same interface as Obsidian's file system
2. **Service Modules** (`serviceModules/`) 2. **Service Modules** (`serviceModules/`)
- Initialised by `initialiseServiceModulesCLI` - Initialised by `initialiseServiceModulesCLI`
- All core sync functionality preserved - All core sync functionality preserved
3. **Service Hub and Settings Services** (`services/`) 3. **Service Hub and Settings Services** (`services/`)
- `NodeServiceHub` provides the CLI service context - `NodeServiceHub` provides the CLI service context
- Node-specific settings and key-value services are provided without Obsidian dependencies - Node-specific settings and key-value services are provided without Obsidian dependencies
4. **Main Entry Point** (`main.ts`) 4. **Main Entry Point** (`main.ts`)
- Command-line interface - Command-line interface
- Settings management (JSON file) - Settings management (JSON file)
- Graceful shutdown handling - Graceful shutdown handling
## Installation ## Docker
```bash A Docker image is provided for headless / server deployments. Build from the repository root:
# Install dependencies (ensure you are in repository root directory, not src/apps/cli)
# due to shared dependencies with webapp and main library ```bash
npm install docker build -f src/apps/cli/Dockerfile -t livesync-cli .
# Build the project (ensure you are in `src/apps/cli` directory) ```
npm run build
``` Run:
## Usage ```bash
# Sync with CouchDB
### Basic Usage docker run --rm -v /path/to/your/vault:/data livesync-cli sync
As you know, the CLI is designed to be used in a headless environment. Hence all operations are performed against a local vault directory and a settings file. Here are some example commands: # List files in the local database
docker run --rm -v /path/to/your/vault:/data livesync-cli ls
```bash
# Sync local database with CouchDB (no files will be changed). # Generate a default settings file
npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json sync docker run --rm -v /path/to/your/vault:/data livesync-cli init-settings
```
# Push files to local database
npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json push /your/storage/file.md /vault/path/file.md The vault directory is mounted at `/data` by default. Override with `-e LIVESYNC_DB_PATH=/other/path`.
# Pull files from local database ### P2P (WebRTC) and Docker networking
npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json pull /vault/path/file.md /your/storage/file.md
The P2P replicator (`p2p-host`, `p2p-sync`, `p2p-peers`) uses WebRTC and generates
# Verbose logging three kinds of ICE candidates. The default Docker bridge network affects which
npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json --verbose candidates are usable:
# Apply setup URI to settings file (settings only; does not run synchronisation) | Candidate type | Description | Bridge network |
npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json setup "obsidian://setuplivesync?settings=..." |---|---|---|
| `host` | Container bridge IP (`172.17.x.x`) | Unreachable from LAN peers |
# Put text from stdin into local database | `srflx` | Host public IP via STUN reflection | Works over the internet |
echo "Hello from stdin" | npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json put /vault/path/file.md | `relay` | Traffic relayed via TURN server | Always reachable |
# Output a file from local database to stdout **LAN P2P on Linux** — use `--network host` so that the real host IP is
npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json cat /vault/path/file.md advertised as the `host` candidate:
# Output a specific revision of a file from local database ```bash
npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json cat-rev /vault/path/file.md 3-abcdef docker run --rm --network host -v /path/to/your/vault:/data livesync-cli p2p-host
```
# Pull a specific revision of a file from local database to local storage
npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json pull-rev /vault/path/file.md /your/storage/file.old.md 3-abcdef > `--network host` is not available on Docker Desktop for macOS or Windows.
# List files in local database **LAN P2P on macOS / Windows Docker Desktop** — configure a TURN server in the
npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json ls /vault/path/ settings file (`P2P_turnServers`, `P2P_turnUsername`, `P2P_turnCredential`).
All P2P traffic will then be relayed through the TURN server, bypassing the
# Show metadata for a file in local database bridge-network limitation.
npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json info /vault/path/file.md
**Internet P2P** — the default bridge network is sufficient. The `srflx`
# Mark a file as deleted in local database candidate carries the host's public IP and peers can connect normally.
npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json rm /vault/path/file.md
**CouchDB sync only (no P2P)** — no special network configuration is required.
# Resolve conflict by keeping a specific revision
npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json resolve /vault/path/file.md 3-abcdef ## Installation
```
```bash
### Configuration # Install dependencies (ensure you are in repository root directory, not src/apps/cli)
# due to shared dependencies with webapp and main library
The CLI uses the same settings format as the Obsidian plugin. Create a `.livesync/settings.json` file in your vault directory: npm install
# Build the project (ensure you are in `src/apps/cli` directory)
```json npm run build
{ ```
"couchDB_URI": "http://localhost:5984",
"couchDB_USER": "admin", ## Usage
"couchDB_PASSWORD": "password",
"couchDB_DBNAME": "obsidian-livesync", ### Basic Usage
"liveSync": true,
"syncOnSave": true, As you know, the CLI is designed to be used in a headless environment. Hence all operations are performed against a local vault directory and a settings file. Here are some example commands:
"syncOnStart": true,
"encrypt": true, ```bash
"passphrase": "your-encryption-passphrase", # Sync local database with CouchDB (no files will be changed).
"usePluginSync": false, npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json sync
"isConfigured": true
} # Push files to local database
``` npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json push /your/storage/file.md /vault/path/file.md
**Minimum required settings:** # Pull files from local database
npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json pull /vault/path/file.md /your/storage/file.md
- `couchDB_URI`: CouchDB server URL
- `couchDB_USER`: CouchDB username # Verbose logging
- `couchDB_PASSWORD`: CouchDB password npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json --verbose
- `couchDB_DBNAME`: Database name
- `isConfigured`: Set to `true` after configuration # Apply setup URI to settings file (settings only; does not run synchronisation)
npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json setup "obsidian://setuplivesync?settings=..."
### Command-line Reference
# Put text from stdin into local database
``` echo "Hello from stdin" | npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json put /vault/path/file.md
Usage:
livesync-cli [database-path] [options] [command] [command-args] # Output a file from local database to stdout
npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json cat /vault/path/file.md
Arguments:
database-path Path to the local database directory (required except for init-settings) # Output a specific revision of a file from local database
npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json cat-rev /vault/path/file.md 3-abcdef
Options:
--settings, -s <path> Path to settings file (default: .livesync/settings.json in local database directory) # Pull a specific revision of a file from local database to local storage
--force, -f Overwrite existing file on init-settings npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json pull-rev /vault/path/file.md /your/storage/file.old.md 3-abcdef
--verbose, -v Enable verbose logging
--help, -h Show this help message # List files in local database
npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json ls /vault/path/
Commands:
init-settings [path] Create settings JSON from DEFAULT_SETTINGS # Show metadata for a file in local database
sync Run one replication cycle and exit npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json info /vault/path/file.md
p2p-peers <timeout> Show discovered peers as [peer]<TAB><peer-id><TAB><peer-name>
p2p-sync <peer> <timeout> Synchronise with specified peer-id or peer-name # Mark a file as deleted in local database
p2p-host Start P2P host mode and wait until interrupted (Ctrl+C) npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json rm /vault/path/file.md
push <src> <dst> Push local file <src> into local database path <dst>
pull <src> <dst> Pull file <src> from local database into local file <dst> # Resolve conflict by keeping a specific revision
pull-rev <src> <dst> <revision> Pull specific revision into local file <dst> npm run --silent cli -- /path/to/your-local-database --settings /path/to/settings.json resolve /vault/path/file.md 3-abcdef
setup <setupURI> Apply setup URI to settings file ```
put <vaultPath> Read text from standard input and write to local database
cat <vaultPath> Write latest file content from local database to standard output ### Configuration
cat-rev <vaultPath> <revision> Write specific revision content from local database to standard output
ls [prefix] List files as path<TAB>size<TAB>mtime<TAB>revision[*] The CLI uses the same settings format as the Obsidian plugin. Create a `.livesync/settings.json` file in your vault directory:
info <vaultPath> Show file metadata including current and past revisions, conflicts, and chunk list
rm <vaultPath> Mark file as deleted in local database ```json
resolve <vaultPath> <revision> Resolve conflict by keeping the specified revision {
mirror <storagePath> <vaultPath> Mirror local file into local database. "couchDB_URI": "http://localhost:5984",
``` "couchDB_USER": "admin",
"couchDB_PASSWORD": "password",
Run via npm script: "couchDB_DBNAME": "obsidian-livesync",
"liveSync": true,
```bash "syncOnSave": true,
npm run --silent cli -- [database-path] [options] [command] [command-args] "syncOnStart": true,
``` "encrypt": true,
"passphrase": "your-encryption-passphrase",
#### Detailed Command Descriptions "usePluginSync": false,
"isConfigured": true
##### ls }
`ls` lists files in the local database with optional prefix filtering. Output format is: ```
```vault/path/file.md<TAB>size<TAB>mtime<TAB>revision[*] **Minimum required settings:**
```
Note: `*` indicates if the file has conflicts. - `couchDB_URI`: CouchDB server URL
- `couchDB_USER`: CouchDB username
##### p2p-peers - `couchDB_PASSWORD`: CouchDB password
- `couchDB_DBNAME`: Database name
`p2p-peers <timeout>` waits for the specified number of seconds, then prints each discovered peer on a separate line: - `isConfigured`: Set to `true` after configuration
```text ### Command-line Reference
[peer]<TAB><peer-id><TAB><peer-name>
``` ```
Usage:
Use this command to select a target for `p2p-sync`. livesync-cli [database-path] [options] [command] [command-args]
##### p2p-sync Arguments:
database-path Path to the local database directory (required except for init-settings)
`p2p-sync <peer> <timeout>` discovers peers up to the specified timeout and synchronises with the selected peer.
Options:
- `<peer>` accepts either `peer-id` or `peer-name` from `p2p-peers` output. --settings, -s <path> Path to settings file (default: .livesync/settings.json in local database directory)
- On success, the command prints a completion message to standard error and exits with status code `0`. --force, -f Overwrite existing file on init-settings
- On failure, the command prints an error message and exits non-zero. --verbose, -v Enable verbose logging
--help, -h Show this help message
##### p2p-host
Commands:
`p2p-host` starts the local P2P host and keeps running until interrupted. init-settings [path] Create settings JSON from DEFAULT_SETTINGS
sync Run one replication cycle and exit
- Other peers can discover and synchronise with this host while it is running. p2p-peers <timeout> Show discovered peers as [peer]<TAB><peer-id><TAB><peer-name>
- Stop the host with `Ctrl+C`. p2p-sync <peer> <timeout> Synchronise with specified peer-id or peer-name
- In CLI mode, behaviour is non-interactive and acceptance follows settings. p2p-host Start P2P host mode and wait until interrupted (Ctrl+C)
push <src> <dst> Push local file <src> into local database path <dst>
##### info pull <src> <dst> Pull file <src> from local database into local file <dst>
pull-rev <src> <dst> <revision> Pull specific revision into local file <dst>
`info` output fields: setup <setupURI> Apply setup URI to settings file
put <vaultPath> Read text from standard input and write to local database
- `id`: Document ID cat <vaultPath> Write latest file content from local database to standard output
- `revision`: Current revision cat-rev <vaultPath> <revision> Write specific revision content from local database to standard output
- `conflicts`: Conflicted revisions, or `N/A` ls [prefix] List files as path<TAB>size<TAB>mtime<TAB>revision[*]
- `filename`: Basename of path info <vaultPath> Show file metadata including current and past revisions, conflicts, and chunk list
- `path`: Vault-relative path rm <vaultPath> Mark file as deleted in local database
- `size`: Size in bytes resolve <vaultPath> <revision> Resolve conflict by keeping the specified revision
- `revisions`: Available non-current revisions mirror <storagePath> <vaultPath> Mirror local file into local database.
- `chunks`: Number of chunk IDs ```
- `children`: Chunk ID list
Run via npm script:
##### mirror
```bash
`mirror` is a command that synchronises your storage with your local vault. It is essentially a process that runs upon startup in Obsidian. npm run --silent cli -- [database-path] [options] [command] [command-args]
```
In other words, it performs the following actions:
#### Detailed Command Descriptions
1. **Precondition checks** — Aborts early if any of the following conditions are not met:
- Settings must be configured (`isConfigured: true`). ##### ls
- File watching must not be suspended (`suspendFileWatching: false`). `ls` lists files in the local database with optional prefix filtering. Output format is:
- Remediation mode must be inactive (`maxMTimeForReflectEvents: 0`).
```vault/path/file.md<TAB>size<TAB>mtime<TAB>revision[*]
2. **State restoration** — On subsequent runs (after the first successful scan), restores the previous storage state before proceeding. ```
Note: `*` indicates if the file has conflicts.
3. **Expired deletion cleanup** — If `automaticallyDeleteMetadataOfDeletedFiles` is set to a positive number of days, any document that is marked deleted and whose `mtime` is older than the retention period is permanently removed from the local database.
##### p2p-peers
4. **File collection** — Enumerates files from two sources:
- **Storage**: all files under the vault path that pass `isTargetFile`. `p2p-peers <timeout>` waits for the specified number of seconds, then prints each discovered peer on a separate line:
- **Local database**: all normal documents (fetched with conflict information) whose paths are valid and pass `isTargetFile`.
- Both collections build case-insensitive ↔ case-sensitive path maps, controlled by `handleFilenameCaseSensitive`. ```text
[peer]<TAB><peer-id><TAB><peer-name>
5. **Categorisation and synchronisation** — The union of both file sets is split into three groups and processed concurrently (up to 10 files at a time): ```
| Group | Condition | Action | Use this command to select a target for `p2p-sync`.
|---|---|---|
| **UPDATE DATABASE** | File exists in storage only | Store the file into the local database. | ##### p2p-sync
| **UPDATE STORAGE** | File exists in database only | If the entry is active (not deleted) and not conflicted, restore the file from the database to storage. Deleted entries and conflicted entries are skipped. |
| **SYNC DATABASE AND STORAGE** | File exists in both | Compare `mtime` freshness. If storage is newer → write to database (`STORAGE → DB`). If database is newer → restore to storage (`STORAGE ← DB`). If equal → do nothing. Conflicted documents and files exceeding the size limit are always skipped. | `p2p-sync <peer> <timeout>` discovers peers up to the specified timeout and synchronises with the selected peer.
6. **Initialisation flag** — On the very first successful run, writes `initialized = true` to the key-value database so that subsequent runs can restore state in step 2. - `<peer>` accepts either `peer-id` or `peer-name` from `p2p-peers` output.
- On success, the command prints a completion message to standard error and exits with status code `0`.
Note: `mirror` does not respect file deletions. If a file is deleted in storage, it will be restored on the next `mirror` run. To delete a file, use the `rm` command instead. This is a little inconvenient, but it is intentional behaviour (if we handle this automatically in `mirror`, we should be against a ton of edge cases). - On failure, the command prints an error message and exits non-zero.
### Planned options: ##### p2p-host
- `--immediate`: Perform sync after the command (e.g. `push`, `pull`, `put`, `rm`). `p2p-host` starts the local P2P host and keeps running until interrupted.
- `serve`: Start CLI in server mode, exposing REST APIs for remote, and batch operations.
- `cause-conflicted <vaultPath>`: Mark a file as conflicted without changing its content, to trigger conflict resolution in Obsidian. - Other peers can discover and synchronise with this host while it is running.
- Stop the host with `Ctrl+C`.
## Use Cases - In CLI mode, behaviour is non-interactive and acceptance follows settings.
### 1. Bootstrap a new headless vault ##### info
Create default settings, apply a setup URI, then run one sync cycle. `info` output fields:
```bash - `id`: Document ID
npm run --silent cli -- init-settings /data/livesync-settings.json - `revision`: Current revision
printf '%s\n' "$SETUP_PASSPHRASE" | npm run --silent cli -- /data/vault --settings /data/livesync-settings.json setup "$SETUP_URI" - `conflicts`: Conflicted revisions, or `N/A`
npm run --silent cli -- /data/vault --settings /data/livesync-settings.json sync - `filename`: Basename of path
``` - `path`: Vault-relative path
- `size`: Size in bytes
### 2. Scripted import and export - `revisions`: Available non-current revisions
- `chunks`: Number of chunk IDs
Push local files into the database from automation, and pull them back for export or backup. - `children`: Chunk ID list
```bash ##### mirror
npm run --silent cli -- /data/vault --settings /data/livesync-settings.json push ./note.md notes/note.md
npm run --silent cli -- /data/vault --settings /data/livesync-settings.json pull notes/note.md ./exports/note.md `mirror` is a command that synchronises your storage with your local vault. It is essentially a process that runs upon startup in Obsidian.
```
In other words, it performs the following actions:
### 3. Revision inspection and restore
1. **Precondition checks** — Aborts early if any of the following conditions are not met:
List metadata, find an older revision, then restore it by content (`cat-rev`) or file output (`pull-rev`). - Settings must be configured (`isConfigured: true`).
- File watching must not be suspended (`suspendFileWatching: false`).
```bash - Remediation mode must be inactive (`maxMTimeForReflectEvents: 0`).
npm run --silent cli -- /data/vault --settings /data/livesync-settings.json info notes/note.md
npm run --silent cli -- /data/vault --settings /data/livesync-settings.json cat-rev notes/note.md 3-abcdef 2. **State restoration** — On subsequent runs (after the first successful scan), restores the previous storage state before proceeding.
npm run --silent cli -- /data/vault --settings /data/livesync-settings.json pull-rev notes/note.md ./restore/note.old.md 3-abcdef
``` 3. **Expired deletion cleanup** — If `automaticallyDeleteMetadataOfDeletedFiles` is set to a positive number of days, any document that is marked deleted and whose `mtime` is older than the retention period is permanently removed from the local database.
### 4. Conflict and cleanup workflow 4. **File collection** — Enumerates files from two sources:
- **Storage**: all files under the vault path that pass `isTargetFile`.
Inspect conflicted revisions, resolve by keeping one revision, then delete obsolete files. - **Local database**: all normal documents (fetched with conflict information) whose paths are valid and pass `isTargetFile`.
- Both collections build case-insensitive ↔ case-sensitive path maps, controlled by `handleFilenameCaseSensitive`.
```bash
npm run --silent cli -- /data/vault --settings /data/livesync-settings.json info notes/note.md 5. **Categorisation and synchronisation** — The union of both file sets is split into three groups and processed concurrently (up to 10 files at a time):
npm run --silent cli -- /data/vault --settings /data/livesync-settings.json resolve notes/note.md 3-abcdef
npm run --silent cli -- /data/vault --settings /data/livesync-settings.json rm notes/obsolete.md | Group | Condition | Action |
``` |---|---|---|
| **UPDATE DATABASE** | File exists in storage only | Store the file into the local database. |
### 5. CI smoke test for content round-trip | **UPDATE STORAGE** | File exists in database only | If the entry is active (not deleted) and not conflicted, restore the file from the database to storage. Deleted entries and conflicted entries are skipped. |
| **SYNC DATABASE AND STORAGE** | File exists in both | Compare `mtime` freshness. If storage is newer → write to database (`STORAGE → DB`). If database is newer → restore to storage (`STORAGE ← DB`). If equal → do nothing. Conflicted documents and files exceeding the size limit are always skipped. |
Validate that `put`/`cat` is behaving as expected in a pipeline.
6. **Initialisation flag** — On the very first successful run, writes `initialized = true` to the key-value database so that subsequent runs can restore state in step 2.
```bash
echo "hello-ci" | npm run --silent cli -- /data/vault --settings /data/livesync-settings.json put ci/test.md Note: `mirror` does not respect file deletions. If a file is deleted in storage, it will be restored on the next `mirror` run. To delete a file, use the `rm` command instead. This is a little inconvenient, but it is intentional behaviour (if we handle this automatically in `mirror`, we should be against a ton of edge cases).
npm run --silent cli -- /data/vault --settings /data/livesync-settings.json cat ci/test.md
``` ### Planned options:
## Development - `--immediate`: Perform sync after the command (e.g. `push`, `pull`, `put`, `rm`).
- `serve`: Start CLI in server mode, exposing REST APIs for remote, and batch operations.
### Project Structure - `cause-conflicted <vaultPath>`: Mark a file as conflicted without changing its content, to trigger conflict resolution in Obsidian.
``` ## Use Cases
src/apps/cli/
├── commands/ # Command dispatcher and command utilities ### 1. Bootstrap a new headless vault
│ ├── runCommand.ts
│ ├── runCommand.unit.spec.ts Create default settings, apply a setup URI, then run one sync cycle.
│ ├── types.ts
│ ├── utils.ts ```bash
│ └── utils.unit.spec.ts npm run --silent cli -- init-settings /data/livesync-settings.json
├── adapters/ # Node.js FileSystem Adapter printf '%s\n' "$SETUP_PASSPHRASE" | npm run --silent cli -- /data/vault --settings /data/livesync-settings.json setup "$SETUP_URI"
│ ├── NodeConversionAdapter.ts npm run --silent cli -- /data/vault --settings /data/livesync-settings.json sync
│ ├── NodeFileSystemAdapter.ts ```
│ ├── NodePathAdapter.ts
│ ├── NodeStorageAdapter.ts ### 2. Scripted import and export
│ ├── NodeStorageAdapter.unit.spec.ts
│ ├── NodeTypeGuardAdapter.ts Push local files into the database from automation, and pull them back for export or backup.
│ ├── NodeTypes.ts
│ └── NodeVaultAdapter.ts ```bash
├── lib/ npm run --silent cli -- /data/vault --settings /data/livesync-settings.json push ./note.md notes/note.md
│ └── pouchdb-node.ts npm run --silent cli -- /data/vault --settings /data/livesync-settings.json pull notes/note.md ./exports/note.md
├── managers/ # CLI-specific managers ```
│ ├── CLIStorageEventManagerAdapter.ts
│ └── StorageEventManagerCLI.ts ### 3. Revision inspection and restore
├── serviceModules/ # Service modules (ported from main.ts)
│ ├── CLIServiceModules.ts List metadata, find an older revision, then restore it by content (`cat-rev`) or file output (`pull-rev`).
│ ├── DatabaseFileAccess.ts
│ ├── FileAccessCLI.ts ```bash
│ └── ServiceFileAccessImpl.ts npm run --silent cli -- /data/vault --settings /data/livesync-settings.json info notes/note.md
├── services/ npm run --silent cli -- /data/vault --settings /data/livesync-settings.json cat-rev notes/note.md 3-abcdef
│ ├── NodeKeyValueDBService.ts npm run --silent cli -- /data/vault --settings /data/livesync-settings.json pull-rev notes/note.md ./restore/note.old.md 3-abcdef
│ ├── NodeServiceHub.ts ```
│ └── NodeSettingService.ts
├── test/ ### 4. Conflict and cleanup workflow
│ ├── test-e2e-two-vaults-common.sh
│ ├── test-e2e-two-vaults-matrix.sh Inspect conflicted revisions, resolve by keeping one revision, then delete obsolete files.
│ ├── test-e2e-two-vaults-with-docker-linux.sh
│ ├── test-push-pull-linux.sh ```bash
│ ├── test-setup-put-cat-linux.sh npm run --silent cli -- /data/vault --settings /data/livesync-settings.json info notes/note.md
│ └── test-sync-two-local-databases-linux.sh npm run --silent cli -- /data/vault --settings /data/livesync-settings.json resolve notes/note.md 3-abcdef
├── .gitignore npm run --silent cli -- /data/vault --settings /data/livesync-settings.json rm notes/obsolete.md
├── entrypoint.ts # CLI executable entry point (shebang) ```
├── main.ts # CLI entry point
├── main.unit.spec.ts ### 5. CI smoke test for content round-trip
├── package.json
├── README.md # This file Validate that `put`/`cat` is behaving as expected in a pipeline.
├── tsconfig.json
├── util/ # Test and local utility scripts ```bash
└── vite.config.ts echo "hello-ci" | npm run --silent cli -- /data/vault --settings /data/livesync-settings.json put ci/test.md
``` npm run --silent cli -- /data/vault --settings /data/livesync-settings.json cat ci/test.md
```
## Development
### Project Structure
```
src/apps/cli/
├── commands/ # Command dispatcher and command utilities
│ ├── runCommand.ts
│ ├── runCommand.unit.spec.ts
│ ├── types.ts
│ ├── utils.ts
│ └── utils.unit.spec.ts
├── adapters/ # Node.js FileSystem Adapter
│ ├── NodeConversionAdapter.ts
│ ├── NodeFileSystemAdapter.ts
│ ├── NodePathAdapter.ts
│ ├── NodeStorageAdapter.ts
│ ├── NodeStorageAdapter.unit.spec.ts
│ ├── NodeTypeGuardAdapter.ts
│ ├── NodeTypes.ts
│ └── NodeVaultAdapter.ts
├── lib/
│ └── pouchdb-node.ts
├── managers/ # CLI-specific managers
│ ├── CLIStorageEventManagerAdapter.ts
│ └── StorageEventManagerCLI.ts
├── serviceModules/ # Service modules (ported from main.ts)
│ ├── CLIServiceModules.ts
│ ├── DatabaseFileAccess.ts
│ ├── FileAccessCLI.ts
│ └── ServiceFileAccessImpl.ts
├── services/
│ ├── NodeKeyValueDBService.ts
│ ├── NodeServiceHub.ts
│ └── NodeSettingService.ts
├── test/
│ ├── test-e2e-two-vaults-common.sh
│ ├── test-e2e-two-vaults-matrix.sh
│ ├── test-e2e-two-vaults-with-docker-linux.sh
│ ├── test-push-pull-linux.sh
│ ├── test-setup-put-cat-linux.sh
│ └── test-sync-two-local-databases-linux.sh
├── .gitignore
├── entrypoint.ts # CLI executable entry point (shebang)
├── main.ts # CLI entry point
├── main.unit.spec.ts
├── package.json
├── README.md # This file
├── tsconfig.json
├── util/ # Test and local utility scripts
└── vite.config.ts
```
+25
View File
@@ -0,0 +1,25 @@
#!/bin/sh
# Entrypoint wrapper for the Self-hosted LiveSync CLI Docker image.
#
# By default, /data is used as the database-path (the vault mount point).
# Override this via the LIVESYNC_DB_PATH environment variable.
#
# Examples:
# docker run -v /path/to/vault:/data livesync-cli sync
# docker run -v /path/to/vault:/data livesync-cli --settings /data/.livesync/settings.json sync
# docker run -v /path/to/vault:/data livesync-cli init-settings
# docker run -e LIVESYNC_DB_PATH=/vault -v /path/to/vault:/vault livesync-cli sync
set -e
case "${1:-}" in
init-settings | --help | -h | "")
# Commands that do not require a leading database-path argument
exec node /app/dist/index.cjs "$@"
;;
*)
# All other commands: prepend the database-path so users only need
# to supply the command and its options.
exec node /app/dist/index.cjs "${LIVESYNC_DB_PATH:-/data}" "$@"
;;
esac
+5 -4
View File
@@ -1,10 +1,11 @@
#!/usr/bin/env node #!/usr/bin/env node
import polyfill from "node-datachannel/polyfill"; import * as polyfill from "werift";
import { main } from "./main"; import { main } from "./main";
for (const prop in polyfill) { const rtcPolyfillCtor = (polyfill as any).RTCPeerConnection;
// @ts-ignore Applying polyfill to globalThis if (typeof (globalThis as any).RTCPeerConnection === "undefined" && typeof rtcPolyfillCtor === "function") {
globalThis[prop] = (polyfill as any)[prop]; // Fill only the standard WebRTC global in Node CLI runtime.
(globalThis as any).RTCPeerConnection = rtcPolyfillCtor;
} }
main().catch((error) => { main().catch((error) => {
+40 -31
View File
@@ -1,31 +1,40 @@
{ {
"name": "self-hosted-livesync-cli", "name": "self-hosted-livesync-cli",
"private": true, "private": true,
"version": "0.0.0", "version": "0.0.0",
"main": "dist/index.cjs", "main": "dist/index.cjs",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",
"preview": "vite preview", "preview": "vite preview",
"cli": "node dist/index.cjs", "cli": "node dist/index.cjs",
"buildRun": "npm run build && npm run cli --", "buildRun": "npm run build && npm run cli --",
"check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json", "build:docker": "docker build -f Dockerfile -t livesync-cli ../../..",
"test:unit": "cd ../../.. && npx vitest run --config vitest.config.unit.ts src/apps/cli/main.unit.spec.ts src/apps/cli/commands/utils.unit.spec.ts src/apps/cli/commands/runCommand.unit.spec.ts src/apps/cli/commands/p2p.unit.spec.ts", "check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json",
"test:e2e:two-vaults": "bash test/test-e2e-two-vaults-with-docker-linux.sh", "test:unit": "cd ../../.. && npx vitest run --config vitest.config.unit.ts src/apps/cli/main.unit.spec.ts src/apps/cli/commands/utils.unit.spec.ts src/apps/cli/commands/runCommand.unit.spec.ts src/apps/cli/commands/p2p.unit.spec.ts",
"test:e2e:two-vaults:common": "bash test/test-e2e-two-vaults-common.sh", "test:e2e:two-vaults": "bash test/test-e2e-two-vaults-with-docker-linux.sh",
"test:e2e:two-vaults:matrix": "bash test/test-e2e-two-vaults-matrix.sh", "test:e2e:two-vaults:common": "bash test/test-e2e-two-vaults-common.sh",
"test:e2e:push-pull": "bash test/test-push-pull-linux.sh", "test:e2e:two-vaults:matrix": "bash test/test-e2e-two-vaults-matrix.sh",
"test:e2e:setup-put-cat": "bash test/test-setup-put-cat-linux.sh", "test:e2e:push-pull": "bash test/test-push-pull-linux.sh",
"test:e2e:sync-two-local": "bash test/test-sync-two-local-databases-linux.sh", "test:e2e:setup-put-cat": "bash test/test-setup-put-cat-linux.sh",
"test:e2e:p2p": "bash test/test-p2p-three-nodes-conflict-linux.sh", "test:e2e:sync-two-local": "bash test/test-sync-two-local-databases-linux.sh",
"test:e2e:p2p-upload-download-repro": "bash test/test-p2p-upload-download-repro-linux.sh", "test:e2e:p2p": "bash test/test-p2p-three-nodes-conflict-linux.sh",
"test:e2e:p2p-host": "bash test/test-p2p-host-linux.sh", "test:e2e:p2p-upload-download-repro": "bash test/test-p2p-upload-download-repro-linux.sh",
"test:e2e:p2p-sync": "bash test/test-p2p-sync-linux.sh", "test:e2e:p2p-host": "bash test/test-p2p-host-linux.sh",
"test:e2e:mirror": "bash test/test-mirror-linux.sh", "test:e2e:p2p-sync": "bash test/test-p2p-sync-linux.sh",
"pretest:e2e:all": "npm run build", "test:e2e:mirror": "bash test/test-mirror-linux.sh",
"test:e2e:all": " export RUN_BUILD=0 && npm run test:e2e:setup-put-cat && npm run test:e2e:push-pull && npm run test:e2e:sync-two-local && npm run test:e2e:p2p && npm run test:e2e:mirror && npm run test:e2e:two-vaults && npm run test:e2e:p2p" "pretest:e2e:all": "npm run build",
}, "test:e2e:all": " export RUN_BUILD=0 && npm run test:e2e:setup-put-cat && npm run test:e2e:push-pull && npm run test:e2e:sync-two-local && npm run test:e2e:p2p && npm run test:e2e:mirror && npm run test:e2e:two-vaults && npm run test:e2e:p2p",
"dependencies": {}, "pretest:e2e:docker:all": "npm run build:docker",
"devDependencies": {} "test:e2e:docker:push-pull": "RUN_BUILD=0 LIVESYNC_TEST_DOCKER=1 bash test/test-push-pull-linux.sh",
} "test:e2e:docker:setup-put-cat": "RUN_BUILD=0 LIVESYNC_TEST_DOCKER=1 bash test/test-setup-put-cat-linux.sh",
"test:e2e:docker:mirror": "RUN_BUILD=0 LIVESYNC_TEST_DOCKER=1 bash test/test-mirror-linux.sh",
"test:e2e:docker:sync-two-local": "RUN_BUILD=0 LIVESYNC_TEST_DOCKER=1 bash test/test-sync-two-local-databases-linux.sh",
"test:e2e:docker:p2p": "RUN_BUILD=0 LIVESYNC_TEST_DOCKER=1 bash test/test-p2p-three-nodes-conflict-linux.sh",
"test:e2e:docker:p2p-sync": "RUN_BUILD=0 LIVESYNC_TEST_DOCKER=1 bash test/test-p2p-sync-linux.sh",
"test:e2e:docker:all": "export RUN_BUILD=0 && npm run test:e2e:docker:setup-put-cat && npm run test:e2e:docker:push-pull && npm run test:e2e:docker:sync-two-local && npm run test:e2e:docker:mirror"
},
"dependencies": {},
"devDependencies": {}
}
+24
View File
@@ -0,0 +1,24 @@
{
"name": "livesync-cli-runtime",
"private": true,
"version": "0.0.0",
"description": "Runtime dependencies for Self-hosted LiveSync CLI Docker image",
"dependencies": {
"commander": "^14.0.3",
"werift": "^0.22.9",
"pouchdb-adapter-http": "^9.0.0",
"pouchdb-adapter-idb": "^9.0.0",
"pouchdb-adapter-indexeddb": "^9.0.0",
"pouchdb-adapter-leveldb": "^9.0.0",
"pouchdb-adapter-memory": "^9.0.0",
"pouchdb-core": "^9.0.0",
"pouchdb-errors": "^9.0.0",
"pouchdb-find": "^9.0.0",
"pouchdb-mapreduce": "^9.0.0",
"pouchdb-merge": "^9.0.0",
"pouchdb-replication": "^9.0.0",
"pouchdb-utils": "^9.0.0",
"pouchdb-wrappers": "*",
"transform-pouch": "^2.0.0"
}
}
View File
View File
View File
+150
View File
@@ -0,0 +1,150 @@
#!/usr/bin/env bash
# test-helpers-docker.sh
#
# Docker-mode overrides for test-helpers.sh.
# Sourced automatically at the end of test-helpers.sh when
# LIVESYNC_TEST_DOCKER=1 is set, replacing run_cli (and related helpers)
# with a Docker-based implementation.
#
# The Docker container and the host share a common directory layout:
# $WORK_DIR (host) <-> /workdir (container)
# $CLI_DIR (host) <-> /clidir (container)
#
# Usage (run an existing test against the Docker image):
# LIVESYNC_TEST_DOCKER=1 bash test/test-push-pull-linux.sh
# LIVESYNC_TEST_DOCKER=1 bash test/test-mirror-linux.sh
# LIVESYNC_TEST_DOCKER=1 bash test/test-sync-two-local-databases-linux.sh
# LIVESYNC_TEST_DOCKER=1 bash test/test-setup-put-cat-linux.sh
#
# Optional environment variables:
# DOCKER_IMAGE Image name/tag to use (default: livesync-cli)
# RUN_BUILD Set to 1 to rebuild the Docker image before the test
# (default: 0 — assumes the image is already built)
# Build command: npm run build:docker (from src/apps/cli/)
#
# Notes:
# - The container is started with --network host so that it can reach
# CouchDB / P2P relay containers that are also using the host network.
# - On macOS / Windows Docker Desktop --network host behaves differently
# (it is not a true host-network bridge); tests that rely on localhost
# connectivity to other containers may fail on those platforms.
# Ensure Docker-mode tests do not trigger host-side `npm run build` unless
# explicitly requested by the caller.
RUN_BUILD="${RUN_BUILD:-0}"
# Override the standard implementation.
# In Docker mode the CLI_CMD array is a no-op sentinel; run_cli is overridden
# directly.
cli_test_init_cli_cmd() {
DOCKER_IMAGE="${DOCKER_IMAGE:-livesync-cli}"
# CLI_CMD is unused in Docker mode; set a sentinel so existing code
# that references it will not error.
CLI_CMD=(__docker__)
}
# ─── display_test_info ────────────────────────────────────────────────────────
display_test_info() {
local image="${DOCKER_IMAGE:-livesync-cli}"
local image_id
image_id="$(docker inspect --format='{{slice .Id 7 19}}' "$image" 2>/dev/null || echo "N/A")"
echo "======================"
echo "Script: ${BASH_SOURCE[1]:-$0}"
echo "Date: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo "Commit: $(git -C "${SCRIPT_DIR:-.}" rev-parse --short HEAD 2>/dev/null || echo "N/A")"
echo "Mode: Docker image=${image} id=${image_id}"
echo "======================"
}
# ─── _docker_translate_arg ───────────────────────────────────────────────────
# Translate a single host filesystem path to its in-container equivalent.
# Paths under WORK_DIR → /workdir/...
# Paths under CLI_DIR → /clidir/...
# Everything else is returned unchanged (relative paths, URIs, plain names).
_docker_translate_arg() {
local arg="$1"
if [[ -n "${WORK_DIR:-}" && "$arg" == "$WORK_DIR"* ]]; then
printf '%s' "/workdir${arg#$WORK_DIR}"
return
fi
if [[ -n "${CLI_DIR:-}" && "$arg" == "$CLI_DIR"* ]]; then
printf '%s' "/clidir${arg#$CLI_DIR}"
return
fi
printf '%s' "$arg"
}
# ─── run_cli ─────────────────────────────────────────────────────────────────
# Drop-in replacement for run_cli that executes the CLI inside a Docker
# container, translating host paths to container paths automatically.
#
# Calling convention is identical to the native run_cli:
# run_cli <vault-path> [options] <command> [command-args]
# run_cli init-settings [options] <settings-file>
#
# The vault path (first positional argument for regular commands) is forwarded
# via the LIVESYNC_DB_PATH environment variable so that docker-entrypoint.sh
# can inject it before the remaining CLI arguments.
run_cli() {
local args=("$@")
# ── 1. Translate all host paths to container paths ────────────────────
local translated=()
for arg in "${args[@]}"; do
translated+=("$(_docker_translate_arg "$arg")")
done
# ── 2. Split vault path from the rest of the arguments ───────────────
local first="${translated[0]:-}"
local env_args=()
local cli_args=()
# These tokens are commands or flags that appear before any vault path.
case "$first" in
"" | --help | -h \
| init-settings \
| -v | --verbose | -d | --debug | -f | --force | -s | --settings)
# No leading vault path — pass all translated args as-is.
cli_args=("${translated[@]}")
;;
*)
# First arg is the vault path; hand it to docker-entrypoint.sh
# via LIVESYNC_DB_PATH so the entrypoint prepends it correctly.
env_args+=(-e "LIVESYNC_DB_PATH=$first")
cli_args=("${translated[@]:1}")
;;
esac
# ── 3. Inject verbose / debug flags ──────────────────────────────────
if [[ "${VERBOSE_TEST_LOGGING:-0}" == "1" ]]; then
cli_args=(-v "${cli_args[@]}")
fi
# ── 4. Volume mounts ──────────────────────────────────────────────────
local vol_args=()
if [[ -n "${WORK_DIR:-}" ]]; then
vol_args+=(-v "${WORK_DIR}:/workdir")
fi
# Mount CLI_DIR (src/apps/cli) for two-vault tests that store vault data
# under $CLI_DIR/.livesync/.
if [[ -n "${CLI_DIR:-}" ]]; then
vol_args+=(-v "${CLI_DIR}:/clidir")
fi
# ── 5. stdin forwarding ───────────────────────────────────────────────
# Attach stdin only when it is a pipe (the 'put' command reads from stdin).
# Without -i the pipe data would never reach the container process.
local stdin_flags=()
if [[ ! -t 0 ]]; then
stdin_flags=(-i)
fi
docker run --rm \
"${stdin_flags[@]}" \
--network host \
--user "$(id -u):$(id -g)" \
"${vol_args[@]}" \
"${env_args[@]}" \
"${DOCKER_IMAGE:-livesync-cli}" \
"${cli_args[@]}"
}
+17 -1
View File
@@ -1,5 +1,15 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# ─── local init hook ────────────────────────────────────────────────────────
# If test-init.local.sh exists alongside this file, source it before anything
# else. Use it to set up your local environment (e.g. activate nvm, set
# DOCKER_IMAGE, ...). The file is git-ignored so it is safe to put personal
# or machine-specific configuration there.
_TEST_HELPERS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=/dev/null
[[ -f "$_TEST_HELPERS_DIR/test-init.local.sh" ]] && source "$_TEST_HELPERS_DIR/test-init.local.sh"
unset _TEST_HELPERS_DIR
cli_test_init_cli_cmd() { cli_test_init_cli_cmd() {
if [[ "${VERBOSE_TEST_LOGGING:-0}" == "1" ]]; then if [[ "${VERBOSE_TEST_LOGGING:-0}" == "1" ]]; then
CLI_CMD=(npm --silent run cli -- -v) CLI_CMD=(npm --silent run cli -- -v)
@@ -343,4 +353,10 @@ display_test_info(){
echo "Date: $(date -u +%Y-%m-%dT%H:%M:%SZ)" echo "Date: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo "Git commit: $(git -C "$SCRIPT_DIR/.." rev-parse --short HEAD 2>/dev/null || echo "N/A")" echo "Git commit: $(git -C "$SCRIPT_DIR/.." rev-parse --short HEAD 2>/dev/null || echo "N/A")"
echo "======================" echo "======================"
} }
# Docker-mode hook — source overrides when LIVESYNC_TEST_DOCKER=1.
if [[ "${LIVESYNC_TEST_DOCKER:-0}" == "1" ]]; then
# shellcheck source=/dev/null
source "$(dirname "${BASH_SOURCE[0]}")/test-helpers-docker.sh"
fi
View File
View File
View File
View File
View File
+29 -1
View File
@@ -1,2 +1,30 @@
#!/bin/bash #!/bin/bash
docker run -d --name relay-test -p 4000:8080 scsibug/nostr-rs-relay:latest set -e
docker run -d --name relay-test -p 4000:7777 \
--tmpfs /app/strfry-db:rw,size=256m \
--entrypoint sh \
ghcr.io/hoytech/strfry:latest \
-lc 'cat > /tmp/strfry.conf <<"EOF"
db = "./strfry-db/"
relay {
bind = "0.0.0.0"
port = 7777
nofiles = 100000
info {
name = "livesync test relay"
description = "local relay for livesync p2p tests"
}
maxWebsocketPayloadSize = 131072
autoPingSeconds = 55
writePolicy {
plugin = ""
}
}
EOF
exec /app/strfry --config /tmp/strfry.conf relay'
+2 -3
View File
@@ -12,8 +12,7 @@ const defaultExternal = [
"pouchdb-adapter-leveldb", "pouchdb-adapter-leveldb",
"commander", "commander",
"punycode", "punycode",
"node-datachannel", "werift",
"node-datachannel/polyfill",
]; ];
export default defineConfig({ export default defineConfig({
plugins: [svelte()], plugins: [svelte()],
@@ -52,7 +51,7 @@ export default defineConfig({
if (id === "fs" || id === "fs/promises" || id === "path" || id === "crypto" || id === "worker_threads") if (id === "fs" || id === "fs/promises" || id === "path" || id === "crypto" || id === "worker_threads")
return true; return true;
if (id.startsWith("pouchdb-")) return true; if (id.startsWith("pouchdb-")) return true;
if (id.startsWith("node-datachannel")) return true; if (id.startsWith("werift")) return true;
if (id.startsWith("node:")) return true; if (id.startsWith("node:")) return true;
return false; return false;
}, },
+1 -1
Submodule src/lib updated: 3d6d9603bf...bed8afd2dc
+26 -1
View File
@@ -3,6 +3,31 @@ set -e
script_dir=$(dirname "$0") script_dir=$(dirname "$0")
webpeer_dir=$script_dir/../../src/apps/webpeer webpeer_dir=$script_dir/../../src/apps/webpeer
docker run -d --name relay-test -p 4000:8080 scsibug/nostr-rs-relay:latest docker run -d --name relay-test -p 4000:7777 \
--tmpfs /app/strfry-db:rw,size=256m \
--entrypoint sh \
ghcr.io/hoytech/strfry:latest \
-lc 'cat > /tmp/strfry.conf <<"EOF"
db = "./strfry-db/"
relay {
bind = "0.0.0.0"
port = 7777
nofiles = 100000
info {
name = "livesync test relay"
description = "local relay for livesync p2p tests"
}
maxWebsocketPayloadSize = 131072
autoPingSeconds = 55
writePolicy {
plugin = ""
}
}
EOF
exec /app/strfry --config /tmp/strfry.conf relay'
npm run --prefix $webpeer_dir build npm run --prefix $webpeer_dir build
docker run -d --name webpeer-test -p 8081:8043 -v $webpeer_dir/dist:/srv/http pierrezemb/gostatic docker run -d --name webpeer-test -p 8081:8043 -v $webpeer_dir/dist:/srv/http pierrezemb/gostatic