Use Commonlib for self-hosted setup tools

This commit is contained in:
vorotamoroz
2026-07-20 15:47:12 +00:00
parent c49536acc9
commit 44784d4865
15 changed files with 1474 additions and 409 deletions
+52
View File
@@ -23,6 +23,8 @@ on:
- 'version-bump.mjs'
- 'utils/release-*.mjs'
- 'utils/release-*.unit.spec.ts'
- 'utils/couchdb/**'
- 'utils/flyio/**'
- '.github/workflows/prepare-release.yml'
- '.github/workflows/finalise-release.yml'
- '.github/workflows/release.yml'
@@ -44,6 +46,8 @@ on:
- 'version-bump.mjs'
- 'utils/release-*.mjs'
- 'utils/release-*.unit.spec.ts'
- 'utils/couchdb/**'
- 'utils/flyio/**'
- '.github/workflows/prepare-release.yml'
- '.github/workflows/finalise-release.yml'
- '.github/workflows/release.yml'
@@ -53,6 +57,54 @@ permissions:
contents: read
jobs:
setup-tools:
name: Self-hosted setup tools
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '24.x'
cache: 'npm'
- name: Setup Deno
uses: denoland/setup-deno@v2
with:
deno-version: v2.x
- name: Install dependencies
run: npm ci
- name: Run setup-tool contract tests
run: npm run test:setup-tools
- name: Create CouchDB test configuration
run: |
cat <<EOF > .test.env
hostname=http://127.0.0.1:5989/
dbname=livesync-test-db
username=admin
password=testpassword
EOF
- name: Start CouchDB
run: npm run test:docker-couchdb:start
- name: Provision a versioned LiveSync database
run: npx dotenv-cli -e .test.env -- env database=setup-tools-ci retry_count=1 retry_delay_ms=0 ./utils/couchdb/couchdb-init.sh
- name: Verify the Commonlib database version
run: |
npx dotenv-cli -e .test.env -- bash -lc 'curl --fail --silent --show-error --user "${username}:${password}" "${hostname}/setup-tools-ci/obsydian_livesync_version" | node --input-type=module -e "import { VER } from \"@vrtmrz/livesync-commonlib/compat/common/types\";let input=\"\";process.stdin.on(\"data\",chunk=>input+=chunk).on(\"end\",()=>{const document=JSON.parse(input);if(document.type!==\"versioninfo\"||document.version!==VER)throw new Error(\"Unexpected LiveSync database version\");})"'
- name: Stop CouchDB
if: always()
run: npm run test:docker-couchdb:stop || true
unit-test:
name: Unit Tests
runs-on: ubuntu-latest
+2
View File
@@ -36,6 +36,7 @@ npm run dev # Development build with auto-rebuild (uses .env for test v
npm run build # Production build
npm run buildDev # Development build (one-time)
npm run test:integration # Run CouchDB-backed integration tests
npm run test:setup-tools # Check provisioning and Setup URI package contracts
npm run test:e2e:cli:p2p # Run canonical P2P validation in Compose
npm run test:e2e:obsidian:local-suite # Run the real Obsidian local suite
```
@@ -67,6 +68,7 @@ To facilitate development and testing, the build process can automatically copy
- If you add a feature that interacts with the remote database (e.g., replication changes, custom changes feed parameters, or custom HTTP queries), you strongly expected to write an integration test to verify the behaviour against a real CouchDB server.
- **Commonlib Tests**: Commonlib owns unit and package tests for shared RPC, storage, replication, and platform contracts. LiveSync CI verifies the exact packed dependency as a downstream consumer.
- **CLI E2E** (`src/apps/cli/testdeno/`): Host-independent consumer workflows. The canonical Compose P2P suite covers ordinary two-peer synchronisation, replacement of the current replicator followed by transfer with the same peer, and explicit relay disconnection followed by paused and resumed reconnection. Its lifecycle entry point is included only in the Docker test build and does not add a public CLI command. Run `npm run test:e2e:cli` for the ordinary suite or `npm run test:e2e:cli:p2p` for P2P validation.
- **Self-hosted setup tools** (`utils/couchdb/` and `utils/flyio/`): Deno contract tests consume the exact locked Commonlib registry package, verify current self-hosted Setup URI defaults and remote profiles, and keep CouchDB administration separate from package-owned LiveSync database-version negotiation. `unit-ci` also provisions a real temporary CouchDB database and verifies its version document against the installed Commonlib package. Run `npm run test:setup-tools` for the local contract gate.
- **Real Obsidian E2E** (`test/e2e-obsidian/`): Local-first scripts that launch real Obsidian with temporary vaults and the built Self-hosted LiveSync plug-in. Use these for boot-up sequence, vault reflection, RedFlag flows, Fast Setup (Simple Fetch), settings dialogues, restart-sensitive workflows, Object Storage regressions, and other behaviour that depends on Obsidian itself. Run focused scripts such as `npm run test:e2e:obsidian:two-vault-sync`, or use `npm run test:e2e:obsidian:local-suite:services` to run the broader local suite with CouchDB and MinIO fixtures managed by the wrapper.
- **Docker Services**: Service-backed tests use CouchDB and MinIO (S3). Canonical P2P validation owns its relay through the CLI Compose runner:
+17 -21
View File
@@ -33,7 +33,7 @@
```bash
# Adding environment variables.
export hostname=localhost:5984
export hostname=http://localhost:5984
export username=goojdasjdas #Please change as you like.
export password=kpkdasdosakpdsa #Please change as you like
@@ -115,31 +115,27 @@ Congrats, move on to [step 2](#2-run-couchdb-initsh-for-initialise)
Please refer to the [official document](https://docs.couchdb.org/en/stable/install/index.html). However, we do not have to configure it fully. Just the administrator needs to be configured.
## 2. Run couchdb-init.sh for initialise
Deno 2 is required. Export the CouchDB connection and database details, then run the provisioning wrapper:
```
export hostname=http://localhost:5984
export username=<INSERT USERNAME HERE>
export password=<INSERT PASSWORD HERE>
export database=obsidiannotes
curl -s https://raw.githubusercontent.com/vrtmrz/obsidian-livesync/main/utils/couchdb/couchdb-init.sh | bash
```
If it results like the following:
```
-- Configuring CouchDB by REST APIs... -->
{"ok":true}
""
""
""
""
""
""
""
""
""
<-- Configuring CouchDB by REST APIs Done!
CouchDB provisioning completed.
```
Your CouchDB has been initialised successfully. If you want this manually, please read the script.
The wrapper runs the exact registry-pinned Commonlib consumer. When `database` is supplied, it creates the database and initialises its LiveSync database-version document through Commonlib. Without `database`, it configures only the CouchDB server.
If you are using Docker Compose and the above command does not work or displays `ERROR: Hostname missing`, you can try running the following command, replacing the placeholders with your own values:
```
curl -s https://raw.githubusercontent.com/vrtmrz/obsidian-livesync/main/utils/couchdb/couchdb-init.sh | hostname=http://<YOUR SERVER IP>:5984 username=<INSERT USERNAME HERE> password=<INSERT PASSWORD HERE> bash
curl -s https://raw.githubusercontent.com/vrtmrz/obsidian-livesync/main/utils/couchdb/couchdb-init.sh | hostname=http://<YOUR SERVER IP>:5984 username=<INSERT USERNAME HERE> password=<INSERT PASSWORD HERE> database=obsidiannotes bash
```
## 3. Expose CouchDB to the Internet
@@ -177,10 +173,10 @@ Now `https://tiles-photograph-routine-groundwater.trycloudflare.com` is our serv
```bash
export hostname=https://tiles-photograph-routine-groundwater.trycloudflare.com #Point to your vault
export database=obsidiannotes #Please change as you like
export passphrase=dfsapkdjaskdjasdas #Please change as you like
export passphrase=<INSERT A STRONG VAULT ENCRYPTION PASSPHRASE>
export username=johndoe
export password=abc123
deno run -A https://raw.githubusercontent.com/vrtmrz/obsidian-livesync/main/utils/flyio/generate_setupuri.ts
export password=<INSERT THE COUCHDB PASSWORD>
deno run --minimum-dependency-age=0 --allow-env https://raw.githubusercontent.com/vrtmrz/obsidian-livesync/main/utils/flyio/generate_setupuri.ts
```
> [!TIP]
@@ -188,14 +184,14 @@ deno run -A https://raw.githubusercontent.com/vrtmrz/obsidian-livesync/main/util
> Yes, the `passphrase` we have exported now is for an End-to-End Encryption passphrase.
> And, `uri_passphrase` that used in the `generate_setupuri.ts` is a different one; for decrypting Set-up URI at using that.
> Why: I (vorotamoroz) think that the passphrase of the Setup-URI should be different from the E2EE passphrase to prevent exposure caused by operational errors or the possibility of evil in our environment. On top of that, I believe that it is desirable for the Setup-URI to be random. Setup-URI is inevitably long, so it goes through the clipboard. I think that its passphrase should not go through the same path, so it should essentially be typed manually.
> Hence, if we keep empty for uri_passphrase, generate_setupuri.ts generates an adjective-noun-randomnumber passphrase so that we can remember it without going through the clipboard.
> If `uri_passphrase` is empty, `generate_setupuri.ts` generates a cryptographically random passphrase and prints it once.
You will then get the following output:
```bash
obsidian://setuplivesync?settings=%5B%22tm2DpsOE74nJAryprZO2M93wF%2Fvg.......4b26ed33230729%22%5D
Your passphrase of Setup-URI is: patient-haze
Your passphrase for the Setup URI is: H7vX...a-random-32-character-value
This passphrase is never shown again, so please note it in a safe place.
```
@@ -205,7 +201,7 @@ Please keep your passphrase of Setup-URI.
[This video](https://youtu.be/7sa_I1832Xc?t=146) may help us.
1. Install Self-hosted LiveSync
2. Choose `Use the copied setup URI` from the command palette and paste the setup URI. (obsidian://setuplivesync?settings=.....).
3. Type the previously displayed passphrase (`patient-haze`) for setup-uri passphrase.
3. Type the Setup URI passphrase printed by the generator.
4. Answer `yes` and `Set it up...`, and finish the first dialogue with `Keep them disabled`.
5. `Reload app without save` once.
+1
View File
@@ -25,6 +25,7 @@
"check": "npm run tsc-check && npm run tsc-check:apps && npm run lint && npm run lint:community -- --quiet && npm run svelte-check && npm run check:compatibility",
"unittest": "deno test -A --no-check --coverage=cov_profile --v8-flags=--expose-gc --trace-leaks ./src/",
"test:unit": "vitest run --config vitest.config.unit.ts",
"test:setup-tools": "bash utils/flyio/setenv.test.sh && deno test -A --config=utils/flyio/deno.jsonc --frozen --lock=utils/flyio/deno.lock utils/couchdb/provision.test.ts utils/flyio/generate_setupuri.test.ts",
"test:contract:contexts": "vitest run --config vitest.config.unit.ts src/modules/services/ObsidianServiceContext.unit.spec.ts src/apps/cli/services/NodeServiceContext.unit.spec.ts src/apps/browser/createLiveSyncBrowserServiceHub.unit.spec.ts",
"test:contract:context:webapp": "vitest run --config vitest.config.unit.ts src/apps/browser/createLiveSyncBrowserServiceHub.unit.spec.ts",
"test:contract:context:cli": "npm run build --workspace self-hosted-livesync-cli && deno task --cwd src/apps/cli/testdeno test:setup-put-cat",
+5
View File
@@ -17,6 +17,11 @@ Earlier releases remain available in the [0.25 release history](docs/releases/0.
- Wizard-driven new-device and existing-device setup now reserves Rebuild or Fetch before enabling imported settings, preventing ordinary start-up work from running ahead of the selected initialisation.
- Manual onboarding now creates and selects CouchDB, Object Storage, and P2P remote profiles directly while preserving existing profiles. Current Setup URIs retain profile names and selections, while older flat settings remain supported through compatibility migration.
- P2P panes and explicit rebuild actions now use the current transport after settings or database replacement. Disconnecting leaves the LiveSync room and closes signalling relay sockets without destroying Trystero-owned shared peers.
- Self-hosted CouchDB provisioning and Setup URI generation now consume the immutable Commonlib registry package. New database provisioning records the package-owned database version, and generated URIs use current new-Vault defaults and a selected CouchDB profile instead of legacy embedded settings.
### Security
- Fly.io setup now generates CouchDB and Vault encryption secrets from cryptographically secure randomness instead of short word combinations.
## 1.0.0-rc.0
+20 -27
View File
@@ -1,33 +1,26 @@
#!/bin/bash
if [[ -z "$hostname" ]]; then
echo "ERROR: Hostname missing"
exit 1
fi
if [[ -z "$username" ]]; then
echo "ERROR: Username missing"
set -euo pipefail
if ! command -v deno >/dev/null 2>&1; then
echo "ERROR: Deno is required to run the Commonlib-backed CouchDB provisioning tool." >&2
exit 1
fi
if [[ -z "$password" ]]; then
echo "ERROR: Password missing"
exit 1
fi
if [[ -z "$node" ]]; then
echo "INFO: defaulting to _local"
node=_local
script_url="${provision_script_url:-https://raw.githubusercontent.com/vrtmrz/obsidian-livesync/main/utils/couchdb/provision.ts}"
script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" 2>/dev/null && pwd || true)"
deno_dependency_options=()
if [[ -n "$script_dir" && -f "$script_dir/provision.ts" ]]; then
script_url="$script_dir/provision.ts"
lockfile="$script_dir/../flyio/deno.lock"
deno_config="$script_dir/../flyio/deno.jsonc"
if [[ -f "$lockfile" && -f "$deno_config" ]]; then
deno_dependency_options+=("--config=$deno_config" --frozen "--lock=$lockfile")
fi
fi
echo "-- Configuring CouchDB by REST APIs... -->"
until (curl -X POST "${hostname}/_cluster_setup" -H "Content-Type: application/json" -d "{\"action\":\"enable_single_node\",\"username\":\"${username}\",\"password\":\"${password}\",\"bind_address\":\"0.0.0.0\",\"port\":5984,\"singlenode\":true}" --user "${username}:${password}"); do sleep 5; done
until (curl -X PUT "${hostname}/_node/${node}/_config/chttpd/require_valid_user" -H "Content-Type: application/json" -d '"true"' --user "${username}:${password}"); do sleep 5; done
until (curl -X PUT "${hostname}/_node/${node}/_config/chttpd_auth/require_valid_user" -H "Content-Type: application/json" -d '"true"' --user "${username}:${password}"); do sleep 5; done
until (curl -X PUT "${hostname}/_node/${node}/_config/httpd/WWW-Authenticate" -H "Content-Type: application/json" -d '"Basic realm=\"couchdb\""' --user "${username}:${password}"); do sleep 5; done
until (curl -X PUT "${hostname}/_node/${node}/_config/httpd/enable_cors" -H "Content-Type: application/json" -d '"true"' --user "${username}:${password}"); do sleep 5; done
until (curl -X PUT "${hostname}/_node/${node}/_config/chttpd/enable_cors" -H "Content-Type: application/json" -d '"true"' --user "${username}:${password}"); do sleep 5; done
until (curl -X PUT "${hostname}/_node/${node}/_config/chttpd/max_http_request_size" -H "Content-Type: application/json" -d '"4294967296"' --user "${username}:${password}"); do sleep 5; done
until (curl -X PUT "${hostname}/_node/${node}/_config/couchdb/max_document_size" -H "Content-Type: application/json" -d '"50000000"' --user "${username}:${password}"); do sleep 5; done
until (curl -X PUT "${hostname}/_node/${node}/_config/cors/credentials" -H "Content-Type: application/json" -d '"true"' --user "${username}:${password}"); do sleep 5; done
until (curl -X PUT "${hostname}/_node/${node}/_config/cors/origins" -H "Content-Type: application/json" -d '"app://obsidian.md,capacitor://localhost,http://localhost"' --user "${username}:${password}"); do sleep 5; done
echo "<-- Configuring CouchDB by REST APIs Done!"
exec deno run \
--minimum-dependency-age=0 \
"${deno_dependency_options[@]}" \
--allow-env \
--allow-net \
"$script_url"
+83
View File
@@ -0,0 +1,83 @@
import { provisionCouchDB } from "./provision.ts";
function assert(condition: unknown, message: string): asserts condition {
if (!condition) throw new Error(message);
}
Deno.test("configures CouchDB and delegates database-version initialisation", async () => {
const requests: Array<{ url: string; method: string; body: string }> = [];
const initialisations: Array<[string, string, string]> = [];
await provisionCouchDB(
{
hostname: "https://couch.example.test/",
username: "alice",
password: "secret",
database: "notes",
retryCount: 1,
retryDelayMs: 0,
},
{
fetch: async (input, init) => {
requests.push({
url: String(input),
method: init?.method ?? "GET",
body: String(init?.body ?? ""),
});
return new Response("{}", { status: 201 });
},
sleep: async () => {},
initialiseDatabaseVersion: async (...args) => {
initialisations.push(args);
},
},
);
assert(
requests[0].url.endsWith("/_cluster_setup"),
"cluster setup was not first",
);
assert(
requests.some((request) =>
request.url.endsWith("/_config/cors/origins") &&
request.body.includes("app://obsidian.md")
),
"the Obsidian CORS origins were not configured",
);
assert(
requests.at(-1)?.url === "https://couch.example.test/notes",
"the requested database was not created last",
);
assert(
initialisations.length === 1,
"database-version initialisation was not delegated once",
);
assert(
initialisations[0][0] === "https://couch.example.test/notes",
"database-version initialisation used the wrong URL",
);
});
Deno.test("leaves database creation to the client when no database is supplied", async () => {
let initialised = false;
await provisionCouchDB(
{
hostname: "http://127.0.0.1:5984",
username: "admin",
password: "secret",
retryCount: 1,
retryDelayMs: 0,
},
{
fetch: async () => new Response("{}", { status: 200 }),
sleep: async () => {},
initialiseDatabaseVersion: async () => {
initialised = true;
},
},
);
assert(
!initialised,
"database-version initialisation ran without a database",
);
});
+250
View File
@@ -0,0 +1,250 @@
import { PouchDB } from "npm:@vrtmrz/livesync-commonlib@0.1.0-rc.4/compat/pouchdb/pouchdb-browser";
import { checkRemoteVersion } from "npm:@vrtmrz/livesync-commonlib@0.1.0-rc.4/compat/pouchdb/negotiation";
export interface CouchDBProvisioningOptions {
hostname: string;
username: string;
password: string;
node?: string;
database?: string;
origins?: string;
retryCount?: number;
retryDelayMs?: number;
}
interface ProvisioningDependencies {
fetch: typeof fetch;
sleep: (milliseconds: number) => Promise<void>;
initialiseDatabaseVersion: (
databaseURL: string,
username: string,
password: string,
) => Promise<void>;
}
const DEFAULT_ORIGINS =
"app://obsidian.md,capacitor://localhost,http://localhost";
function requireValue(value: string, name: string): string {
const trimmed = value.trim();
if (!trimmed) throw new Error(`${name} is required`);
return trimmed;
}
function normaliseHostname(hostname: string): string {
const parsed = new URL(requireValue(hostname, "hostname"));
parsed.pathname = parsed.pathname.replace(/\/+$/, "");
parsed.search = "";
parsed.hash = "";
return parsed.href.replace(/\/$/, "");
}
function validateDatabaseName(database: string): string {
const trimmed = database.trim();
if (!/^[a-z][a-z0-9_$()+-]*$/.test(trimmed)) {
throw new Error(
"database must begin with a lower-case letter and contain only lower-case letters, digits, _, $, (, ), +, or -",
);
}
return trimmed;
}
function basicAuthorisation(username: string, password: string): string {
return `Basic ${btoa(`${username}:${password}`)}`;
}
async function requestWithRetry(
dependencies: ProvisioningDependencies,
label: string,
url: string,
init: RequestInit,
accept: (response: Response, body: string) => boolean,
retryCount: number,
retryDelayMs: number,
): Promise<void> {
let lastError: unknown;
for (let attempt = 1; attempt <= retryCount; attempt++) {
try {
const response = await dependencies.fetch(url, init);
const body = await response.text();
if (accept(response, body)) return;
const error = new Error(
`${label} failed with HTTP ${response.status}: ${body}`,
);
if (response.status < 500) throw error;
lastError = error;
} catch (error) {
lastError = error;
if (
error instanceof Error &&
error.message.startsWith(`${label} failed with HTTP 4`)
) {
throw error;
}
}
if (attempt < retryCount) await dependencies.sleep(retryDelayMs);
}
throw lastError instanceof Error
? lastError
: new Error(`${label} failed after ${retryCount} attempts`);
}
export async function initialiseLiveSyncDatabaseVersion(
databaseURL: string,
username: string,
password: string,
): Promise<void> {
const database = new PouchDB(databaseURL, {
adapter: "http",
auth: { username, password },
skip_setup: true,
});
try {
const compatible = await checkRemoteVersion(
database,
async () => false,
);
if (!compatible) {
throw new Error(
"the remote database uses an incompatible LiveSync database version",
);
}
} finally {
await database.close();
}
}
export async function provisionCouchDB(
options: CouchDBProvisioningOptions,
overrides: Partial<ProvisioningDependencies> = {},
): Promise<void> {
const hostname = normaliseHostname(options.hostname);
const username = requireValue(options.username, "username");
const password = requireValue(options.password, "password");
const node = encodeURIComponent(options.node?.trim() || "_local");
const origins = options.origins?.trim() || DEFAULT_ORIGINS;
const retryCount = options.retryCount ?? 12;
const retryDelayMs = options.retryDelayMs ?? 5_000;
if (!Number.isInteger(retryCount) || retryCount < 1) {
throw new Error("retryCount must be a positive integer");
}
if (!Number.isFinite(retryDelayMs) || retryDelayMs < 0) {
throw new Error("retryDelayMs must be zero or greater");
}
const dependencies: ProvisioningDependencies = {
fetch,
sleep: (milliseconds) =>
new Promise((resolve) => setTimeout(resolve, milliseconds)),
initialiseDatabaseVersion: initialiseLiveSyncDatabaseVersion,
...overrides,
};
const headers = {
"Content-Type": "application/json",
Authorization: basicAuthorisation(username, password),
};
const configure = async (
label: string,
path: string,
body: string,
method = "PUT",
accept: (response: Response, body: string) => boolean = (response) =>
response.ok,
) =>
await requestWithRetry(
dependencies,
label,
`${hostname}${path}`,
{ method, headers, body },
accept,
retryCount,
retryDelayMs,
);
await configure(
"single-node cluster setup",
"/_cluster_setup",
JSON.stringify({
action: "enable_single_node",
username,
password,
bind_address: "0.0.0.0",
port: 5984,
singlenode: true,
}),
"POST",
(response, body) =>
response.ok ||
((response.status === 400 || response.status === 409) &&
/already|finished/i.test(body)),
);
const settings: Array<[string, string, string]> = [
["require authenticated HTTP users", "chttpd/require_valid_user", '"true"'],
[
"require authenticated HTTP users for authentication",
"chttpd_auth/require_valid_user",
'"true"',
],
[
"set the HTTP authentication challenge",
"httpd/WWW-Authenticate",
'"Basic realm=\\"couchdb\\""',
],
["enable HTTP CORS", "httpd/enable_cors", '"true"'],
["enable clustered HTTP CORS", "chttpd/enable_cors", '"true"'],
[
"set the maximum HTTP request size",
"chttpd/max_http_request_size",
'"4294967296"',
],
[
"set the maximum document size",
"couchdb/max_document_size",
'"50000000"',
],
["enable CORS credentials", "cors/credentials", '"true"'],
["set allowed CORS origins", "cors/origins", JSON.stringify(origins)],
];
for (const [label, key, body] of settings) {
await configure(label, `/_node/${node}/_config/${key}`, body);
}
if (options.database?.trim()) {
const database = validateDatabaseName(options.database);
const databaseURL = `${hostname}/${encodeURIComponent(database)}`;
await requestWithRetry(
dependencies,
"create database",
databaseURL,
{ method: "PUT", headers },
(response) => response.ok || response.status === 412,
retryCount,
retryDelayMs,
);
await dependencies.initialiseDatabaseVersion(
databaseURL,
username,
password,
);
}
}
function optionalNumber(name: string): number | undefined {
const value = Deno.env.get(name)?.trim();
return value ? Number(value) : undefined;
}
if (import.meta.main) {
await provisionCouchDB({
hostname: Deno.env.get("hostname") ?? "",
username: Deno.env.get("username") ?? "",
password: Deno.env.get("password") ?? "",
node: Deno.env.get("node"),
database: Deno.env.get("database"),
origins: Deno.env.get("origins"),
retryCount: optionalNumber("retry_count"),
retryDelayMs: optionalNumber("retry_delay_ms"),
});
console.log("CouchDB provisioning completed.");
}
+844 -14
View File
@@ -1,25 +1,855 @@
{
"version": "4",
"version": "5",
"specifiers": {
"npm:octagonal-wheels@0.1.11": "0.1.11"
"npm:@vrtmrz/livesync-commonlib@0.1.0-rc.4": "0.1.0-rc.4"
},
"npm": {
"idb@8.0.0": {
"integrity": "sha512-l//qvlAKGmQO31Qn7xdzagVPPaHTxXx199MhrAFuVBTPqydcPYBWjkrbv4Y0ktB+GmWOiwHl237UUOrLmQxLvw=="
},
"octagonal-wheels@0.1.11": {
"integrity": "sha512-KsXfpziFHmlLEBe5VAXFz9OyyjJEEdSg7xxASqdzmbe5oo9dhcOeWGrQfyipRJwHAhlFkI4vEf8JCSgkcyRxYg==",
"@aws-sdk/checksums@3.1000.18": {
"integrity": "sha512-IImkbEyXdV6/uaF5r6Wkk+8718mQw1ll83j0a4a30R3JM/rHVFdWAiT4jtJpFjJiIwM/oJ6SxIxr0z2TaQUGqw==",
"dependencies": [
"idb",
"xxhash-wasm@0.4.2",
"xxhash-wasm-102@npm:xxhash-wasm@1.0.2"
"@aws-sdk/core",
"@aws-sdk/types",
"@smithy/core",
"@smithy/types",
"tslib"
]
},
"xxhash-wasm@0.4.2": {
"integrity": "sha512-/eyHVRJQCirEkSZ1agRSCwriMhwlyUcFkXD5TPVSLP+IPzjsqMVzZwdoczLp1SoQU0R3dxz1RpIK+4YNQbCVOA=="
"@aws-sdk/client-s3@3.1090.0": {
"integrity": "sha512-R6GX9cd1jljwzZ8xFmgAI/hHCuX1MobIKBdsymv7WL9SENvO9Vgz9KOR6avTnu0Ao+w1LmxnTe+jqmZXEn7Q/Q==",
"dependencies": [
"@aws-sdk/checksums",
"@aws-sdk/core",
"@aws-sdk/credential-provider-node",
"@aws-sdk/middleware-sdk-s3",
"@aws-sdk/signature-v4-multi-region",
"@aws-sdk/types",
"@smithy/core",
"@smithy/fetch-http-handler",
"@smithy/node-http-handler",
"@smithy/types",
"tslib"
]
},
"xxhash-wasm@1.0.2": {
"integrity": "sha512-ibF0Or+FivM9lNrg+HGJfVX8WJqgo+kCLDc4vx6xMeTce7Aj+DLttKbxxRR/gNLSAelRc1omAPlJ77N/Jem07A=="
"@aws-sdk/core@3.975.3": {
"integrity": "sha512-7ur3kCKuvPLqlsZ2XlvnNBVQ7KkpSu6Y6dOTwSPHLrFpTEfZM8isLBJc4cgv96WB7GifeVM436mpycwxBd2vEA==",
"dependencies": [
"@aws-sdk/types",
"@aws-sdk/xml-builder",
"@aws/lambda-invoke-store",
"@smithy/core",
"@smithy/signature-v4",
"@smithy/types",
"bowser",
"tslib"
]
},
"@aws-sdk/credential-provider-env@3.972.59": {
"integrity": "sha512-Ny5e4Mfh3QPmiAc0AiUe+cbTXDlxkU3Rc+EpWOfyWeWEy6yp7Fa1KmfNeCc+1a8by9zQ9gtohmiQUkMPScF3ng==",
"dependencies": [
"@aws-sdk/core",
"@aws-sdk/types",
"@smithy/core",
"@smithy/types",
"tslib"
]
},
"@aws-sdk/credential-provider-http@3.972.61": {
"integrity": "sha512-8jAjgStl5Ytq4+HF3X/9f+EmRinaRbGRRtQGktlPfBRVx73H+R1y48vIeXerQtYGFaUqkEp3fT6jP854rVO2yQ==",
"dependencies": [
"@aws-sdk/core",
"@aws-sdk/types",
"@smithy/core",
"@smithy/fetch-http-handler",
"@smithy/node-http-handler",
"@smithy/types",
"tslib"
]
},
"@aws-sdk/credential-provider-ini@3.973.4": {
"integrity": "sha512-e6ZvVsj90aRALf1kHP+J4iqC1496ZpVgqI/+u0LJ5HL7q7ATauGy4gdDvRCP13L1pN/fMiZLah162PGIYkbUVQ==",
"dependencies": [
"@aws-sdk/core",
"@aws-sdk/credential-provider-env",
"@aws-sdk/credential-provider-http",
"@aws-sdk/credential-provider-login",
"@aws-sdk/credential-provider-process",
"@aws-sdk/credential-provider-sso",
"@aws-sdk/credential-provider-web-identity",
"@aws-sdk/nested-clients",
"@aws-sdk/types",
"@smithy/core",
"@smithy/credential-provider-imds",
"@smithy/types",
"tslib"
]
},
"@aws-sdk/credential-provider-login@3.972.66": {
"integrity": "sha512-g2fsqm87r/nKthLZ0VkkDBElkGg0PvSa8d97HQ6EilMbJTZ6hxa8FxkSZyJfgPfFdZn0TTmkOffQmTSUcAHIng==",
"dependencies": [
"@aws-sdk/core",
"@aws-sdk/nested-clients",
"@aws-sdk/types",
"@smithy/core",
"@smithy/types",
"tslib"
]
},
"@aws-sdk/credential-provider-node@3.972.70": {
"integrity": "sha512-3xzvkGdykBunxqh8WudmUpSyLWvIhfI6aBQo1b5rb3mDO5mNLadK+0hiI0qBQBMVynJbfLO+Ajy9dztMwy9O8w==",
"dependencies": [
"@aws-sdk/credential-provider-env",
"@aws-sdk/credential-provider-http",
"@aws-sdk/credential-provider-ini",
"@aws-sdk/credential-provider-process",
"@aws-sdk/credential-provider-sso",
"@aws-sdk/credential-provider-web-identity",
"@aws-sdk/types",
"@smithy/core",
"@smithy/credential-provider-imds",
"@smithy/types",
"tslib"
]
},
"@aws-sdk/credential-provider-process@3.972.59": {
"integrity": "sha512-DlZF2/MhLlatDdlrIy3CUCpfdbLrKx+3SMjVo+WyHnPpwzkc/M3vwAHw4OVJf7DMvO+4vfRqSCMc/E9I1auN0g==",
"dependencies": [
"@aws-sdk/core",
"@aws-sdk/types",
"@smithy/core",
"@smithy/types",
"tslib"
]
},
"@aws-sdk/credential-provider-sso@3.973.3": {
"integrity": "sha512-hmdDHoy2G5Es2e8IgelNMYUuSQI6uCIAKZMJ2u2PdKDhxvbk1uWD/g4+R7R5c/tJfKEB1+KjjWiaoCr/S+ZTiQ==",
"dependencies": [
"@aws-sdk/core",
"@aws-sdk/nested-clients",
"@aws-sdk/token-providers",
"@aws-sdk/types",
"@smithy/core",
"@smithy/types",
"tslib"
]
},
"@aws-sdk/credential-provider-web-identity@3.972.65": {
"integrity": "sha512-gHQb/Kt0chjk/JQDa/GJDqmAvEuVn8n7z10wK2h0LFM9TUDRkohgOO4aEF+s2sBLM0br7Cl5W6P7phgjrrJvLQ==",
"dependencies": [
"@aws-sdk/core",
"@aws-sdk/nested-clients",
"@aws-sdk/types",
"@smithy/core",
"@smithy/types",
"tslib"
]
},
"@aws-sdk/middleware-sdk-s3@3.972.64": {
"integrity": "sha512-RBi43anhDBUv+HCfxCOXwGOE7GmT4n7ChV04Mwr22RhXTNcamW/iWnJlOotDPCZSrJ4dEvhZSiWWQMwLX+ZhFA==",
"dependencies": [
"@aws-sdk/core",
"@aws-sdk/signature-v4-multi-region",
"@aws-sdk/types",
"@smithy/core",
"@smithy/types",
"tslib"
]
},
"@aws-sdk/nested-clients@3.997.33": {
"integrity": "sha512-dVZOroI/r3/ENvqNGgjMPul+jjlz9GddfVusgTXlVjfZj5isibOxecLkGQbRPp8XOuX+RAfjXLFgPkD1JS5xrw==",
"dependencies": [
"@aws-sdk/core",
"@aws-sdk/signature-v4-multi-region",
"@aws-sdk/types",
"@smithy/core",
"@smithy/fetch-http-handler",
"@smithy/node-http-handler",
"@smithy/types",
"tslib"
]
},
"@aws-sdk/signature-v4-multi-region@3.996.41": {
"integrity": "sha512-QMUytg+FQMGouc8gHS00KoYih3+N6cqmVI/pQGOIo7Nr7OpQaiXjSYOuL+vsPZ1tymY4LAQ8MYcHJmws5LRxng==",
"dependencies": [
"@aws-sdk/types",
"@smithy/signature-v4",
"@smithy/types",
"tslib"
]
},
"@aws-sdk/token-providers@3.1088.0": {
"integrity": "sha512-4ObatWt2qpJg5FBk4LOOKrTQYzaqeewAtdO3r9ZO8lH9YqLtpTzLyIdy0mJ+nVdfYOnqISkKNfmzP22bNDhwyw==",
"dependencies": [
"@aws-sdk/core",
"@aws-sdk/nested-clients",
"@aws-sdk/types",
"@smithy/core",
"@smithy/types",
"tslib"
]
},
"@aws-sdk/types@3.974.2": {
"integrity": "sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA==",
"dependencies": [
"@smithy/types",
"tslib"
]
},
"@aws-sdk/xml-builder@3.972.36": {
"integrity": "sha512-RdGmS1GLrtaTOLE1ElSluMldNrpk9Emq6uYs8SS8iHlu5xTAmM9rRkM91o48+rIRryBtyO9t+uLYCoMG6jVMVA==",
"dependencies": [
"@smithy/types",
"tslib"
]
},
"@aws/lambda-invoke-store@0.3.0": {
"integrity": "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ=="
},
"@noble/secp256k1@3.1.0": {
"integrity": "sha512-+F7iS7tUMaNGXcc9X3PjmjvuQnXEuSjCRNzVVA2xAcKXgCaP0dHYz4SFyt4FKNHef7sOP//xihowcySSS7PK9g=="
},
"@smithy/core@3.29.5": {
"integrity": "sha512-i0dk2t5B+CwV/dcJdUHILYkOQF5lof8f44dFCfDWToGCxjT9YQ+CgHqTAvJxzc3+zqQwm2QtVoJ5IqiNar/CnQ==",
"dependencies": [
"@smithy/types",
"tslib"
]
},
"@smithy/credential-provider-imds@4.4.10": {
"integrity": "sha512-MJenAe4OKRZUo1LdYYFDCsSHxaHvInIU/z52GsheO9vl1/VSySVCr0zkyKD6TFiGkSUaWGxvKZ/70OvgUZR5HQ==",
"dependencies": [
"@smithy/core",
"@smithy/types",
"tslib"
]
},
"@smithy/fetch-http-handler@5.6.7": {
"integrity": "sha512-3zpg8yqqyXzoK2TsRDdkqVOj2RDBFfLXwCczOZ5c7TWB4eiaebfSCsbMjDPYB3PJ9ihV62QaeadZ+wLadZtNGA==",
"dependencies": [
"@smithy/core",
"@smithy/types",
"tslib"
]
},
"@smithy/md5-js@4.4.10": {
"integrity": "sha512-XI5xhWxRkWuiLNj0/Z30vLTPpZe9UQcg57Ox/n4vzGmFiHSrD+xAmx6ubEcWt6u69IOH+4LAucpaZk5AMxB8oQ==",
"dependencies": [
"@smithy/core",
"tslib"
]
},
"@smithy/middleware-apply-body-checksum@4.5.10": {
"integrity": "sha512-1qFwlILFq+QnI1oqZIBpLda1W6oCkI667GPTyAn0eRan72ddQ/zA1CoKiIezTHLQKxINpPvDKVwsw/znLmKEbg==",
"dependencies": [
"@smithy/core",
"@smithy/types",
"tslib"
]
},
"@smithy/node-http-handler@4.9.7": {
"integrity": "sha512-wCU8HCLjAtAVqxxe0j2xff9LcEPw3yjBbg5IdQDIYFnxnPxbxcSLc7rgex7kqm9L/WYOnJEgaWQlfDkZleozMA==",
"dependencies": [
"@smithy/core",
"@smithy/types",
"tslib"
]
},
"@smithy/signature-v4@5.6.6": {
"integrity": "sha512-efP6DN3UTFrzIsGO42/xcabv8jU7+9nwEdphFUH7yL0k010ERyAWaO41KFQIDLcFZLZ8xzIQr4wplFxNzslSGQ==",
"dependencies": [
"@smithy/core",
"@smithy/types",
"tslib"
]
},
"@smithy/types@4.16.1": {
"integrity": "sha512-0JFs3V2y2M9tKW5na/qxe69Zv+uxLMO7QBbhxF/FHu/Gp2NFZAAL9tWl9PU02xxo07pb3G9FTyjNc6D5uZrJIg==",
"dependencies": [
"tslib"
]
},
"@smithy/util-retry@4.5.10": {
"integrity": "sha512-hYu5ieq8myuO29xCQV0IIhicRm1aO0lcNBeMcF1mVVAVQC0oylPGKFliMq5NbxtdOvRxJWCMuir2gwKI91f1lw==",
"dependencies": [
"@smithy/core",
"tslib"
]
},
"@trystero-p2p/core@0.25.3": {
"integrity": "sha512-lQKNq/ha+vF6kQZrpaXJGzlzxLF/Fhizoy5dwJUYQlkqRrbPup/Jov2EpPX/CPr2X+f79HxVzonkeni3MxmCuQ=="
},
"@trystero-p2p/nostr@0.25.3": {
"integrity": "sha512-nZV9Fl/GXuhIkJSQ+wIkdMoRLO1oSTUL/+vZorukaojeAdOpT/61e8b9Vzbt8L1ktMTaGPEJHw2BG/B5BCwBuw==",
"dependencies": [
"@noble/secp256k1",
"@trystero-p2p/core"
]
},
"@vrtmrz/livesync-commonlib@0.1.0-rc.4": {
"integrity": "sha512-u4FdbjnYg7lAf38z7eUv4eq4vxEdrl4rFMxiDZiJ7T701awKiflkXGIJQnaHdLaMa3zkRBU15qqEo7GtextmlA==",
"dependencies": [
"@aws-sdk/client-s3",
"@smithy/fetch-http-handler",
"@smithy/md5-js",
"@smithy/middleware-apply-body-checksum",
"@smithy/types",
"@smithy/util-retry",
"@trystero-p2p/nostr",
"diff-match-patch",
"events",
"fflate",
"idb",
"markdown-it",
"minimatch",
"octagonal-wheels",
"pouchdb-adapter-http",
"pouchdb-adapter-idb",
"pouchdb-adapter-indexeddb",
"pouchdb-adapter-memory",
"pouchdb-core",
"pouchdb-errors",
"pouchdb-find",
"pouchdb-mapreduce",
"pouchdb-merge",
"pouchdb-replication",
"pouchdb-utils",
"qrcode-generator",
"transform-pouch",
"xxhash-wasm-102@npm:xxhash-wasm@1.1.0"
]
},
"abstract-leveldown@2.7.2": {
"integrity": "sha512-+OVvxH2rHVEhWLdbudP6p0+dNMXu8JA1CbhP19T8paTYAcX7oJ4OVjT+ZUVpv7mITxXHqDMej+GdqXBmXkw09w==",
"dependencies": [
"xtend"
],
"deprecated": true
},
"abstract-leveldown@6.2.3": {
"integrity": "sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==",
"dependencies": [
"buffer",
"immediate",
"level-concat-iterator",
"level-supports",
"xtend"
],
"deprecated": true
},
"argparse@2.0.1": {
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
"balanced-match@4.0.4": {
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="
},
"base64-js@1.5.1": {
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
},
"bowser@2.14.1": {
"integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="
},
"brace-expansion@5.0.7": {
"integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==",
"dependencies": [
"balanced-match"
]
},
"buffer@5.7.1": {
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"dependencies": [
"base64-js",
"ieee754"
]
},
"core-util-is@1.0.3": {
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
},
"deferred-leveldown@5.3.0": {
"integrity": "sha512-a59VOT+oDy7vtAbLRCZwWgxu2BaCfd5Hk7wxJd48ei7I+nsg8Orlb9CLG0PMZienk9BSUKgeAqkO2+Lw+1+Ukw==",
"dependencies": [
"abstract-leveldown@6.2.3",
"inherits"
],
"deprecated": true
},
"diff-match-patch@1.0.5": {
"integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw=="
},
"double-ended-queue@2.1.0-0": {
"integrity": "sha512-+BNfZ+deCo8hMNpDqDnvT+c0XpJ5cUa6mqYq89bho2Ifze4URTqRkcwR399hWoTrTkbZ/XJYDgP6rc7pRgffEQ=="
},
"entities@4.5.0": {
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="
},
"errno@0.1.8": {
"integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==",
"dependencies": [
"prr"
],
"bin": true
},
"events@3.3.0": {
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="
},
"fetch-cookie@2.2.0": {
"integrity": "sha512-h9AgfjURuCgA2+2ISl8GbavpUdR+WGAM2McW/ovn4tVccegp8ZqCKWSBR8uRdM8dDNlx5WdKRWxBYUwteLDCNQ==",
"dependencies": [
"set-cookie-parser",
"tough-cookie"
]
},
"fflate@0.8.3": {
"integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA=="
},
"functional-red-black-tree@1.0.1": {
"integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g=="
},
"idb@8.0.3": {
"integrity": "sha512-LtwtVyVYO5BqRvcsKuB2iUMnHwPVByPCXFXOpuU96IZPPoPN6xjOGxZQ74pgSVVLQWtUOYgyeL4GE98BY5D3wg=="
},
"ieee754@1.2.1": {
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
},
"immediate@3.3.0": {
"integrity": "sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q=="
},
"inherits@2.0.4": {
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"isarray@0.0.1": {
"integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="
},
"level-codec@9.0.2": {
"integrity": "sha512-UyIwNb1lJBChJnGfjmO0OR+ezh2iVu1Kas3nvBS/BzGnx79dv6g7unpKIDNPMhfdTEGoc7mC8uAu51XEtX+FHQ==",
"dependencies": [
"buffer"
],
"deprecated": true
},
"level-concat-iterator@2.0.1": {
"integrity": "sha512-OTKKOqeav2QWcERMJR7IS9CUo1sHnke2C0gkSmcR7QuEtFNLLzHQAvnMw8ykvEcv0Qtkg0p7FOwP1v9e5Smdcw==",
"deprecated": true
},
"level-errors@2.0.1": {
"integrity": "sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw==",
"dependencies": [
"errno"
],
"deprecated": true
},
"level-iterator-stream@4.0.2": {
"integrity": "sha512-ZSthfEqzGSOMWoUGhTXdX9jv26d32XJuHz/5YnuHZzH6wldfWMOVwI9TBtKcya4BKTyTt3XVA0A3cF3q5CY30Q==",
"dependencies": [
"inherits",
"readable-stream@3.6.2",
"xtend"
]
},
"level-supports@1.0.1": {
"integrity": "sha512-rXM7GYnW8gsl1vedTJIbzOrRv85c/2uCMpiiCzO2fndd06U/kUXEEU9evYn4zFggBOg36IsBW8LzqIpETwwQzg==",
"dependencies": [
"xtend"
]
},
"levelup@4.4.0": {
"integrity": "sha512-94++VFO3qN95cM/d6eBXvd894oJE0w3cInq9USsyQzzoJxmiYzPAocNcuGCPGGjoXqDVJcr3C1jzt1TSjyaiLQ==",
"dependencies": [
"deferred-leveldown",
"level-errors",
"level-iterator-stream",
"level-supports",
"xtend"
],
"deprecated": true
},
"linkify-it@5.0.2": {
"integrity": "sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==",
"dependencies": [
"uc.micro"
]
},
"ltgt@2.2.1": {
"integrity": "sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA=="
},
"markdown-it@14.3.0": {
"integrity": "sha512-RCEsPjR+sr0x+AuYp601tKTkgFG4YEPLCzHST3cQ/fhlJkqAkz1L2/Qbp1j9qw5SBwQHFBoW8+hoN5xssOF0Tw==",
"dependencies": [
"argparse",
"entities",
"linkify-it",
"mdurl",
"punycode.js",
"uc.micro"
],
"bin": true
},
"mdurl@2.0.0": {
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w=="
},
"memdown@1.4.1": {
"integrity": "sha512-iVrGHZB8i4OQfM155xx8akvG9FIj+ht14DX5CQkCTG4EHzZ3d3sgckIf/Lm9ivZalEsFuEVnWv2B2WZvbrro2w==",
"dependencies": [
"abstract-leveldown@2.7.2",
"functional-red-black-tree",
"immediate",
"inherits",
"ltgt",
"safe-buffer@5.1.2"
],
"deprecated": true
},
"minimatch@10.2.5": {
"integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
"dependencies": [
"brace-expansion"
]
},
"node-fetch@2.6.9": {
"integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==",
"dependencies": [
"whatwg-url"
]
},
"octagonal-wheels@0.1.51": {
"integrity": "sha512-KTlfqKPjobHJg/t3A539srnFf+VHr1aXkHSmsNDDpiI5UFC7FamZ95dWpJfGE2EI/HULR5hveQDgkazmz8SAcg==",
"dependencies": [
"idb"
]
},
"pouchdb-abstract-mapreduce@9.0.0": {
"integrity": "sha512-SnTtqwAEiAa3uxKbc1J7LfiBViwEkKe2xkK92zxyTXPqWBvMnh4UU3GXxx7GrXTM4L9llsQ3lSjpbH4CNqG1Mw==",
"dependencies": [
"pouchdb-binary-utils",
"pouchdb-collate",
"pouchdb-errors",
"pouchdb-fetch",
"pouchdb-mapreduce-utils",
"pouchdb-md5",
"pouchdb-utils"
]
},
"pouchdb-adapter-http@9.0.0": {
"integrity": "sha512-2eL008XeRZkdyp3hMHHOhdIPqK9H6Mn4SLlQvit4zCbqnOFfAswzPjUmHULGMbDUCrQBTu6y82FnV6NHXv9kgw==",
"dependencies": [
"pouchdb-binary-utils",
"pouchdb-errors",
"pouchdb-fetch",
"pouchdb-utils"
]
},
"pouchdb-adapter-idb@9.0.0": {
"integrity": "sha512-2oLlgwMyOQwdKuzrEmOv8T7jFVgX7JgT4Cr81zX3eiiRClp7xXGgjv41ZRdVCAbM530sIN8BudafaQRVFKRVmA==",
"dependencies": [
"pouchdb-adapter-utils",
"pouchdb-binary-utils",
"pouchdb-errors",
"pouchdb-json",
"pouchdb-merge",
"pouchdb-utils"
]
},
"pouchdb-adapter-indexeddb@9.0.0": {
"integrity": "sha512-/mcCbnVR0VKwtVZWKf8lVSdADLD0yApjFudu4d+0jeLWAeBSGZBRKYlogz2PGs4uTA7GVc2TXjVCNGUdkCM9ZQ==",
"dependencies": [
"pouchdb-adapter-utils",
"pouchdb-binary-utils",
"pouchdb-errors",
"pouchdb-md5",
"pouchdb-merge",
"pouchdb-utils"
]
},
"pouchdb-adapter-leveldb-core@9.0.0": {
"integrity": "sha512-b3ZGPtVXyivGL5SK3AIDG7PrNsZdoDpGFkmTytDTtctkVhxOg71gnXXP+CrupENPqSNG/eGbKW4w+bbMpxy6aA==",
"dependencies": [
"double-ended-queue",
"levelup",
"pouchdb-adapter-utils",
"pouchdb-binary-utils",
"pouchdb-core",
"pouchdb-errors",
"pouchdb-json",
"pouchdb-md5",
"pouchdb-merge",
"pouchdb-utils",
"sublevel-pouchdb",
"through2"
]
},
"pouchdb-adapter-memory@9.0.0": {
"integrity": "sha512-XbCwJ5f5U9dGdkiDikzYjTebdPHuA6Ghylx1Pq0lDe4y6l8R9xhjDSUy56pJ8G2F4Z+8QdB5FBY9EQoFlFSXWQ==",
"dependencies": [
"memdown",
"pouchdb-adapter-leveldb-core"
]
},
"pouchdb-adapter-utils@9.0.0": {
"integrity": "sha512-hmbm4ey0HL0vtoY1tRTPIt2FfYjvMh3DWoGGSxXDTS73qTFQ+Fhhi5I0AnN9PcD2omfKQAVXiYks4kkMvlAHqA==",
"dependencies": [
"pouchdb-binary-utils",
"pouchdb-errors",
"pouchdb-md5",
"pouchdb-merge",
"pouchdb-utils"
]
},
"pouchdb-binary-utils@9.0.0": {
"integrity": "sha512-2OMtgDZi82vqs+zNDE0YiYjOaWkYCUcZJZKK3WkRr+XYRu+2B7umJrnygJFhUwoGedBbHSrlQBLhdNV3F1AX1A=="
},
"pouchdb-changes-filter@9.0.0": {
"integrity": "sha512-ig0fo0WLgIjAniFJ19Uw1Y+oxiypqC+Skhd8BCETRVXOhLBzueRwEQR4thffyo0UayYVqldJfSR5wHSDvEVk/A==",
"dependencies": [
"pouchdb-errors",
"pouchdb-selector-core",
"pouchdb-utils"
]
},
"pouchdb-checkpointer@9.0.0": {
"integrity": "sha512-yu1OlWw78oTHKOkg1GoxxF2qB7YUsjK3rUDJOChMs/sVlZwOTZ4mGdWFPBr3udxSGvR77E+g89kpdmAWhPpHvA==",
"dependencies": [
"pouchdb-collate",
"pouchdb-utils"
]
},
"pouchdb-collate@9.0.0": {
"integrity": "sha512-TrnEDNZEmIIl+W3xKUO8h+geqVLQ90oZe5ujPkl8myUzpREULWXWQBnV5EzPXVEKDBpJlb8T3I6oy/zdWGQpdA=="
},
"pouchdb-core@9.0.0": {
"integrity": "sha512-98SJgs8bqXhr4gMGuOTR8yVeLlMYy797zlOtdlvlXIxIicvocyA8ColhVVhdBXPNOGxT2HwReIMywdIVAgibpg==",
"dependencies": [
"pouchdb-changes-filter",
"pouchdb-errors",
"pouchdb-fetch",
"pouchdb-merge",
"pouchdb-utils",
"uuid"
]
},
"pouchdb-errors@9.0.0": {
"integrity": "sha512-961PSMLhW0UqqdJ566g+CdLZ5pkBJRd6l4WWpCDdD0USvE4xYfYGzv43w7nZZBw1k3Xdy092yqPge7yX/tfnyw=="
},
"pouchdb-fetch@9.0.0": {
"integrity": "sha512-TbE3cUcAJQrwb9kr44tDP0X+NAbcqgjsTvcL30L4xzBNJeCPTIRjukYX80s154SHJUXBxcWRiPsMmNqpXsjfCA==",
"dependencies": [
"fetch-cookie",
"node-fetch"
]
},
"pouchdb-find@9.0.0": {
"integrity": "sha512-vvVhq4eEOmSkwSRwf2NBYtdhURB7ryJ7sUI4WDN00GuLUj2g8jAXBJuZIryVgdYt/5S5cfn70iRL6Eow+LFhpA==",
"dependencies": [
"pouchdb-abstract-mapreduce",
"pouchdb-collate",
"pouchdb-errors",
"pouchdb-fetch",
"pouchdb-md5",
"pouchdb-selector-core",
"pouchdb-utils"
]
},
"pouchdb-generate-replication-id@9.0.0": {
"integrity": "sha512-wetxjU0W/qNYtfHIoKwBO73ddUr0/eqzYOkoKHSFXCgOzYmTglDeqXiVY9LPysRXTgaHUJPKC5LoknZZw7e+Dw==",
"dependencies": [
"pouchdb-collate",
"pouchdb-md5"
]
},
"pouchdb-json@9.0.0": {
"integrity": "sha512-aI41mYVyI195GXuT1Ys7mLIB/Mvrz11ihoTP6km6hYqVgSuaUxuZcFUozlyTJiZXr7H5kdhNgclhlVnjir4JAA==",
"dependencies": [
"vuvuzela"
]
},
"pouchdb-mapreduce-utils@9.0.0": {
"integrity": "sha512-Bjh8W6QXqp1j7MKmHhYYp5cYlcQsm5drD8Jd/F+ZlfNt18uiD2SQXWzGM5797+tiW/LszFGb8ttw0uHWjxufCQ==",
"dependencies": [
"pouchdb-utils"
]
},
"pouchdb-mapreduce@9.0.0": {
"integrity": "sha512-ZD8PleQ9atzQAzT2LZWsvooUVEfsen5QGv/SDfci20IleCaFW2A2q7OERrqY0YWKDCCNRsWhPWPmsFvZC9K8DQ==",
"dependencies": [
"pouchdb-abstract-mapreduce",
"pouchdb-mapreduce-utils",
"pouchdb-utils"
]
},
"pouchdb-md5@9.0.0": {
"integrity": "sha512-58xUYBvW3/s+aH0j4uOhhN8yCk0LQ254cxBzI/gbKA9PrfwHpe4zrr0L/ia5ml3A30oH1f8aTnuVMwWDkFcuww==",
"dependencies": [
"pouchdb-binary-utils",
"spark-md5"
]
},
"pouchdb-merge@9.0.0": {
"integrity": "sha512-Xh+TgOZCkGoZpI589btKf/cTiuQ5CsnPl9YpdW4h0cAPusniN6XNsR62F+/HbL9wirI6XTEPHUrk7MsQbk3S3A==",
"dependencies": [
"pouchdb-utils"
]
},
"pouchdb-replication@9.0.0": {
"integrity": "sha512-EZ68KJ3ZUWuPe35NxP6WnRw8J6Zudf0j/tZ/6mOSrCcp3EbtBNt8Ke2FaAThUgiFahVnHD5Y8nd53EGs2DLygg==",
"dependencies": [
"pouchdb-checkpointer",
"pouchdb-errors",
"pouchdb-generate-replication-id",
"pouchdb-utils"
]
},
"pouchdb-selector-core@9.0.0": {
"integrity": "sha512-ZYHYsdoedwm8j5tYofz+3+uUSK8i+7tRCBb01T0OuqDQb17+w5mzjHF8Ppi160xdPUPaWCo1Un+nLWGJzkmA3g==",
"dependencies": [
"pouchdb-collate",
"pouchdb-utils"
]
},
"pouchdb-utils@9.0.0": {
"integrity": "sha512-xWZE5c+nAslgmLC8JBZbky8AYgdz7pKtv7KTSi6CD2tuQD0WyNKib0YnhZndeE84dksTeZlqlg56RQHsHoB2LQ==",
"dependencies": [
"pouchdb-errors",
"pouchdb-md5",
"uuid"
]
},
"pouchdb-wrappers@5.0.0": {
"integrity": "sha512-fXqsVn+rmlPtxaAIGaQP5TkiaT39OMwvMk+ScLLtHrmfXD2KBO6fe/qBl38N/rpTn0h/A058dPN4fLAHt550zA=="
},
"prr@1.0.1": {
"integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw=="
},
"psl@1.15.0": {
"integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==",
"dependencies": [
"punycode"
]
},
"punycode.js@2.3.1": {
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA=="
},
"punycode@2.3.1": {
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="
},
"qrcode-generator@1.5.2": {
"integrity": "sha512-pItrW0Z9HnDBnFmgiNrY1uxRdri32Uh9EjNYLPVC2zZ3ZRIIEqBoDgm4DkvDwNNDHTK7FNkmr8zAa77BYc9xNw=="
},
"querystringify@2.2.0": {
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="
},
"readable-stream@1.1.14": {
"integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==",
"dependencies": [
"core-util-is",
"inherits",
"isarray",
"string_decoder@0.10.31"
]
},
"readable-stream@3.6.2": {
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"dependencies": [
"inherits",
"string_decoder@1.3.0",
"util-deprecate"
]
},
"requires-port@1.0.0": {
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
},
"safe-buffer@5.1.2": {
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"safe-buffer@5.2.1": {
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
},
"set-cookie-parser@2.7.2": {
"integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="
},
"spark-md5@3.0.2": {
"integrity": "sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw=="
},
"string_decoder@0.10.31": {
"integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ=="
},
"string_decoder@1.3.0": {
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"dependencies": [
"safe-buffer@5.2.1"
]
},
"sublevel-pouchdb@9.0.0": {
"integrity": "sha512-pX4r8+F7wuts0C81kUJ341h4bl2aRe7qV572FE8X1FMz9VkKlmi2nPD1vfeiOJXz5Y09I4MHjGULAbqvTfQZEQ==",
"dependencies": [
"level-codec",
"ltgt",
"readable-stream@1.1.14"
]
},
"through2@3.0.2": {
"integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==",
"dependencies": [
"inherits",
"readable-stream@3.6.2"
]
},
"tough-cookie@4.1.4": {
"integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
"dependencies": [
"psl",
"punycode",
"universalify",
"url-parse"
]
},
"tr46@0.0.3": {
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"transform-pouch@2.0.0": {
"integrity": "sha512-nDZovo0U5o0UdMNL93fMQgGjrwH9h4F/a7qqRTnF6cVA+FfgyXiJPTrSuD+LmWSO7r2deZt0P0oeCD8hkgxl5g==",
"dependencies": [
"pouchdb-wrappers"
]
},
"tslib@2.8.1": {
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
},
"uc.micro@2.1.0": {
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="
},
"universalify@0.2.0": {
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg=="
},
"url-parse@1.5.10": {
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
"dependencies": [
"querystringify",
"requires-port"
]
},
"util-deprecate@1.0.2": {
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"uuid@8.3.2": {
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"deprecated": true,
"bin": true
},
"vuvuzela@1.0.3": {
"integrity": "sha512-Tm7jR1xTzBbPW+6y1tknKiEhz04Wf/1iZkcTJjSFcpNko43+dFW6+OOeQe9taJIug3NdfUAjFKgUSyQrIKaDvQ=="
},
"webidl-conversions@3.0.1": {
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"whatwg-url@5.0.0": {
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"dependencies": [
"tr46",
"webidl-conversions"
]
},
"xtend@4.0.2": {
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
},
"xxhash-wasm@1.1.0": {
"integrity": "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA=="
}
}
}
+15 -22
View File
@@ -1,8 +1,15 @@
#!/bin/bash
## Script for deploy and automatic setup CouchDB onto fly.io.
## We need Deno for generating the Setup-URI.
## Deno is used for Commonlib-backed provisioning and Setup URI generation.
source setenv.sh $@
set -euo pipefail
if ! command -v deno >/dev/null 2>&1; then
echo "ERROR: Deno 2 is required for CouchDB provisioning and Setup URI generation." >&2
exit 1
fi
source setenv.sh "$@"
export hostname="https://$appname.fly.dev"
@@ -14,30 +21,16 @@ echo "region : $region"
echo ""
echo "-- START DEPLOYING --> "
set -e
fly launch --name=$appname --env="COUCHDB_USER=$username" --copy-config=true --detach --no-deploy --region ${region} --yes
fly secrets set COUCHDB_PASSWORD=$password
fly deploy
set +e
../couchdb/couchdb-init.sh
# flyctl deploy
echo "OK!"
if command -v deno >/dev/null 2>&1; then
echo "Setup finished! Also, we can set up Self-hosted LiveSync instantly, by the following setup uri."
echo "Passphrase of setup-uri will be printed only one time. Keep it safe!"
echo "--- configured ---"
echo "database : ${database}"
echo "E2EE passphrase: ${passphrase}"
echo "--- setup uri ---"
deno run -A generate_setupuri.ts
else
echo "Setup finished! Here is the configured values (reprise)!"
echo "-- YOUR CONFIGURATION --"
echo "URL : $hostname"
echo "username: $username"
echo "password: $password"
echo "-- YOUR CONFIGURATION --"
echo "If we had Deno, we would got the setup uri directly!"
fi
echo "Setup finished. The Commonlib-generated Setup URI follows."
echo "Its passphrase is printed only once, so store it safely."
echo "--- configured ---"
echo "database: ${database}"
echo "--- setup URI ---"
deno run --minimum-dependency-age=0 --allow-env generate_setupuri.ts
+66
View File
@@ -0,0 +1,66 @@
import { decodeSettingsFromSetupURI } from "npm:@vrtmrz/livesync-commonlib@0.1.0-rc.4/compat/API/processSetting";
import { DEFAULT_SETTINGS } from "npm:@vrtmrz/livesync-commonlib@0.1.0-rc.4/settings";
function assert(condition: unknown, message: string): asserts condition {
if (!condition) throw new Error(message);
}
Deno.test("generates a current self-hosted Setup URI through the published Commonlib contract", async () => {
const scriptPath = new URL("./generate_setupuri.ts", import.meta.url);
const command = new Deno.Command(Deno.execPath(), {
args: ["run", "-A", scriptPath.pathname],
env: {
hostname: "https://couch.example.test",
username: "alice",
password: "couch-secret",
database: "notes",
passphrase: "vault-secret",
uri_passphrase: "setup-secret",
},
stdout: "piped",
stderr: "piped",
});
const result = await command.output();
const stdout = new TextDecoder().decode(result.stdout);
const stderr = new TextDecoder().decode(result.stderr);
assert(result.success, `generator failed:\n${stdout}\n${stderr}`);
const setupURI = stdout.match(/obsidian:\/\/setuplivesync\?settings=\S+/)
?.[0];
assert(setupURI, `generator did not print a Setup URI:\n${stdout}`);
const decoded = await decodeSettingsFromSetupURI(setupURI, "setup-secret");
assert(decoded, "Commonlib could not decode the generated Setup URI");
const effectiveSettings = { ...DEFAULT_SETTINGS, ...decoded };
assert(
effectiveSettings.customChunkSize === 60,
"the Setup URI did not use the current self-hosted chunk-size recommendation",
);
assert(
effectiveSettings.chunkSplitterVersion === "v3-rabin-karp",
"the Setup URI did not use the current chunk splitter",
);
assert(
effectiveSettings.E2EEAlgorithm === "v2",
"the Setup URI did not use the current E2EE algorithm",
);
assert(
!Object.hasOwn(decoded, "doNotUseFixedRevisionForChunks"),
"the Setup URI serialised the obsolete fixed-revision compatibility setting",
);
const profiles = Object.values(decoded.remoteConfigurations ?? {});
assert(
profiles.length === 1,
"the Setup URI did not contain exactly one CouchDB remote profile",
);
assert(
decoded.activeConfigurationId === profiles[0].id,
"the CouchDB remote profile was not selected",
);
assert(
profiles[0].uri.startsWith("sls+https://"),
"the selected remote profile was not a CouchDB connection URI",
);
});
+45 -168
View File
@@ -1,175 +1,52 @@
import { encrypt } from "npm:octagonal-wheels@0.1.30/encryption/encryption";
import { encodeSettingsToSetupURI } from "npm:@vrtmrz/livesync-commonlib@0.1.0-rc.4/compat/API/processSetting";
import { upsertRemoteConfigurationInPlace } from "npm:@vrtmrz/livesync-commonlib@0.1.0-rc.4/remote-configurations";
import {
createNewVaultSettings,
PREFERRED_SETTING_SELF_HOSTED,
} from "npm:@vrtmrz/livesync-commonlib@0.1.0-rc.4/settings";
const noun = [
"waterfall",
"river",
"breeze",
"moon",
"rain",
"wind",
"sea",
"morning",
"snow",
"lake",
"sunset",
"pine",
"shadow",
"leaf",
"dawn",
"glitter",
"forest",
"hill",
"cloud",
"meadow",
"sun",
"glade",
"bird",
"brook",
"butterfly",
"bush",
"dew",
"dust",
"field",
"fire",
"flower",
"firefly",
"feather",
"grass",
"haze",
"mountain",
"night",
"pond",
"darkness",
"snowflake",
"silence",
"sound",
"sky",
"shape",
"surf",
"thunder",
"violet",
"water",
"wildflower",
"wave",
"water",
"resonance",
"sun",
"log",
"dream",
"cherry",
"tree",
"fog",
"frost",
"voice",
"paper",
"frog",
"smoke",
"star",
];
const adjectives = [
"autumn",
"hidden",
"bitter",
"misty",
"silent",
"empty",
"dry",
"dark",
"summer",
"icy",
"delicate",
"quiet",
"white",
"cool",
"spring",
"winter",
"patient",
"twilight",
"dawn",
"crimson",
"wispy",
"weathered",
"blue",
"billowing",
"broken",
"cold",
"damp",
"falling",
"frosty",
"green",
"long",
"late",
"lingering",
"bold",
"little",
"morning",
"muddy",
"old",
"red",
"rough",
"still",
"small",
"sparkling",
"thrumming",
"shy",
"wandering",
"withered",
"wild",
"black",
"young",
"holy",
"solitary",
"fragrant",
"aged",
"snowy",
"proud",
"floral",
"restless",
"divine",
"polished",
"ancient",
"purple",
"lively",
"nameless",
];
function friendlyString() {
return `${adjectives[Math.floor(Math.random() * adjectives.length)]}-${noun[Math.floor(Math.random() * noun.length)]}`;
function requireEnvironment(name: string): string {
const value = Deno.env.get(name)?.trim();
if (!value) throw new Error(`${name} is required`);
return value;
}
const uri_passphrase = `${Deno.env.get("uri_passphrase") ?? friendlyString()}`;
function generateSetupPassphrase(): string {
const bytes = crypto.getRandomValues(new Uint8Array(24));
return btoa(String.fromCharCode(...bytes)).replaceAll("+", "-").replaceAll(
"/",
"_",
).replace(/=+$/, "");
}
const URIBASE = "obsidian://setuplivesync?settings=";
async function main() {
const conf = {
couchDB_URI: `${Deno.env.get("hostname")}`,
couchDB_USER: `${Deno.env.get("username")}`,
couchDB_PASSWORD: `${Deno.env.get("password")}`,
couchDB_DBNAME: `${Deno.env.get("database")}`,
syncOnStart: true,
gcDelay: 0,
periodicReplication: true,
syncOnFileOpen: true,
encrypt: true,
passphrase: `${Deno.env.get("passphrase")}`,
usePathObfuscation: true,
batchSave: true,
batch_size: 50,
batches_limit: 50,
useHistory: true,
disableRequestURI: true,
customChunkSize: 50,
syncAfterMerge: false,
concurrencyOfReadChunksOnline: 100,
minimumIntervalOfReadChunksOnline: 100,
handleFilenameCaseSensitive: false,
doNotUseFixedRevisionForChunks: false,
settingVersion: 10,
notifyThresholdOfRemoteStorageSize: 800,
};
const encryptedConf = encodeURIComponent(await encrypt(JSON.stringify(conf), uri_passphrase, false));
const theURI = `${URIBASE}${encryptedConf}`;
console.log("\nYour passphrase of Setup-URI is: ", uri_passphrase);
console.log("This passphrase is never shown again, so please note it in a safe place.");
console.log(theURI);
const setupPassphrase = Deno.env.get("uri_passphrase")?.trim() ||
generateSetupPassphrase();
const settings = createNewVaultSettings();
Object.assign(settings, PREFERRED_SETTING_SELF_HOSTED, {
couchDB_URI: requireEnvironment("hostname"),
couchDB_USER: requireEnvironment("username"),
couchDB_PASSWORD: requireEnvironment("password"),
couchDB_DBNAME: requireEnvironment("database"),
batchSave: true,
periodicReplication: true,
syncOnStart: true,
syncOnFileOpen: true,
syncAfterMerge: true,
encrypt: true,
passphrase: requireEnvironment("passphrase"),
usePathObfuscation: true,
});
upsertRemoteConfigurationInPlace(settings, "couchdb", { activate: true });
const setupURI = await encodeSettingsToSetupURI(settings, setupPassphrase, [
"pluginSyncExtendedSetting",
"doNotUseFixedRevisionForChunks",
], true);
console.log("\nYour passphrase for the Setup URI is:", setupPassphrase);
console.log("This passphrase is never shown again, so store it safely.");
console.log(setupURI.trim());
}
await main();
+14 -10
View File
@@ -1,19 +1,23 @@
random_num() {
echo $RANDOM
echo "$RANDOM"
}
random_noun() {
nouns=("waterfall" "river" "breeze" "moon" "rain" "wind" "sea" "morning" "snow" "lake" "sunset" "pine" "shadow" "leaf" "dawn" "glitter" "forest" "hill" "cloud" "meadow" "sun" "glade" "bird" "brook" "butterfly" "bush" "dew" "dust" "field" "fire" "flower" "firefly" "feather" "grass" "haze" "mountain" "night" "pond" "darkness" "snowflake" "silence" "sound" "sky" "shape" "surf" "thunder" "violet" "water" "wildflower" "wave" "water" "resonance" "sun" "log" "dream" "cherry" "tree" "fog" "frost" "voice" "paper" "frog" "smoke" "star")
echo ${nouns[$(($RANDOM % ${#nouns[*]}))]}
echo "${nouns[$((RANDOM % ${#nouns[*]}))]}"
}
random_adjective() {
adjectives=("autumn" "hidden" "bitter" "misty" "silent" "empty" "dry" "dark" "summer" "icy" "delicate" "quiet" "white" "cool" "spring" "winter" "patient" "twilight" "dawn" "crimson" "wispy" "weathered" "blue" "billowing" "broken" "cold" "damp" "falling" "frosty" "green" "long" "late" "lingering" "bold" "little" "morning" "muddy" "old" "red" "rough" "still" "small" "sparkling" "thrumming" "shy" "wandering" "withered" "wild" "black" "young" "holy" "solitary" "fragrant" "aged" "snowy" "proud" "floral" "restless" "divine" "polished" "ancient" "purple" "lively" "nameless")
echo ${adjectives[$(($RANDOM % ${#adjectives[*]}))]}
echo "${adjectives[$((RANDOM % ${#adjectives[*]}))]}"
}
random_secret() {
deno eval 'const bytes=crypto.getRandomValues(new Uint8Array(24));console.log(btoa(String.fromCharCode(...bytes)).replaceAll("+","-").replaceAll("/","_").replace(/=+$/, ""));'
}
cp ./fly.template.toml ./fly.toml
if [ "$1" = "renew" ]; then
if [ "${1:-}" = "renew" ]; then
unset appname
unset username
unset password
@@ -22,9 +26,9 @@ if [ "$1" = "renew" ]; then
unset region
fi
[ -z $appname ] && export appname=$(random_adjective)-$(random_noun)-$(random_num)
[ -z $username ] && export username=$(random_adjective)-$(random_noun)-$(random_num)
[ -z $password ] && export password=$(random_adjective)-$(random_noun)-$(random_num)
[ -z $database ] && export database="obsidiannotes"
[ -z $passphrase ] && export passphrase=$(random_adjective)-$(random_noun)-$(random_num)
[ -z $region ] && export region="nrt"
[ -z "${appname:-}" ] && export appname="$(random_adjective)-$(random_noun)-$(random_num)"
[ -z "${username:-}" ] && export username="$(random_adjective)-$(random_noun)-$(random_num)"
[ -z "${password:-}" ] && export password="$(random_secret)"
[ -z "${database:-}" ] && export database="obsidiannotes"
[ -z "${passphrase:-}" ] && export passphrase="$(random_secret)"
[ -z "${region:-}" ] && export region="nrt"
+26
View File
@@ -0,0 +1,26 @@
#!/bin/bash
set -euo pipefail
fixture_dir="$(mktemp -d)"
trap 'rm -rf "$fixture_dir"' EXIT
cp "$(dirname "$0")/setenv.sh" "$(dirname "$0")/fly.template.toml" "$fixture_dir/"
(
cd "$fixture_dir"
appname=""
username=""
password=""
database=""
passphrase=""
region=""
source ./setenv.sh keep
[[ "$password" =~ ^[A-Za-z0-9_-]{32}$ ]] || {
echo "generated CouchDB password is not a 32-character base64url secret" >&2
exit 1
}
[[ "$passphrase" =~ ^[A-Za-z0-9_-]{32}$ ]] || {
echo "generated Vault encryption passphrase is not a 32-character base64url secret" >&2
exit 1
}
)
+34 -147
View File
@@ -1,167 +1,54 @@
<!-- For translation: 20240206r0 -->
# Utilities
Here are some useful things.
## couchdb
These utilities support self-hosted CouchDB provisioning and Setup URI generation. They consume the immutable `@vrtmrz/livesync-commonlib@0.1.0-rc.4` registry package directly; the utility lockfile records its resolved package integrity.
### couchdb-init.sh
This script can configure CouchDB with the necessary settings by REST APIs.
## CouchDB provisioning
#### Materials
- Mandatory: curl
`couchdb/couchdb-init.sh` is a Bash wrapper for the Deno provisioning tool. Deno 2 is required. The tool configures single-node CouchDB, authenticated access, CORS for Obsidian, and the request and document size limits used by Self-hosted LiveSync.
#### Usage
Set `database` to create a database as part of provisioning. The tool then uses Commonlib's database negotiation contract to initialise and verify the LiveSync database version. If `database` is omitted, only the CouchDB server is configured and the first LiveSync client remains responsible for creating its database.
```sh
export hostname=http://localhost:5984/
export hostname=http://localhost:5984
export username=couchdb-admin-username
export password=couchdb-admin-password
./couchdb-init.sh
export database=obsidiannotes
./couchdb/couchdb-init.sh
```
curl result will be shown, however, all of them can be ignored if the script has been run completely.
Optional variables are:
## fly.io
- `node`, which defaults to `_local`;
- `origins`, which defaults to the supported Obsidian desktop, mobile, and local origins;
- `retry_count`, which defaults to `12`; and
- `retry_delay_ms`, which defaults to `5000`.
### deploy-server.sh
Authentication and other non-retryable HTTP failures stop immediately. Network and server failures are retried within the configured bound.
A fully automated CouchDB deployment script. We can deploy CouchDB onto fly.io. The only we need is an account of it.
## Setup URI generation
All omitted configurations will be determined at random. (And, it is preferred). The region is configured to `nrt`.
If Japan is not close to you, please choose a region closer to you. However, the deployed database will work if you leave it at all.
`flyio/generate_setupuri.ts` creates a current self-hosted configuration from Commonlib's new-Vault and self-hosted presets, stores the CouchDB connection as the selected remote profile, and encodes it with Commonlib's Setup URI contract.
#### Materials
- Mandatory: curl, flyctl
- Recommended: deno
#### Usage
```sh
#export appname=
#export username=
#export password=
#export database=
#export passphrase=
export region=nrt #pick your nearest location
export hostname=https://couch.example.com
export username=couchdb-admin-username
export password=couchdb-admin-password
export database=obsidiannotes
export passphrase=a-strong-vault-encryption-passphrase
export uri_passphrase=a-separate-setup-uri-passphrase # Optional
deno run --minimum-dependency-age=0 --allow-env ./flyio/generate_setupuri.ts
```
If `uri_passphrase` is omitted, the tool generates and prints a cryptographically random one. Store the Setup URI and its passphrase separately. The `passphrase` value protects synchronised Vault data and must also be stored safely.
## Fly.io deployment
`flyio/deploy-server.sh` deploys CouchDB through `flyctl`, provisions the selected database through the tool above, and prints a Commonlib-generated Setup URI. Both `flyctl` and Deno 2 are required.
```sh
export region=nrt # Choose a nearby Fly.io region.
cd flyio
./deploy-server.sh
```
The result of this command is as follows.
```
-- YOUR CONFIGURATION --
URL : https://young-darkness-25342.fly.dev
username: billowing-cherry-22580
password: misty-dew-13571
region : nrt
-- START DEPLOYING -->
An existing fly.toml file was found
Using build strategies '[the "couchdb:latest" docker image]'. Remove [build] from fly.toml to force a rescan
Creating app in /home/vorotamoroz/dev/obsidian-livesync/utils/flyio
We're about to launch your app on Fly.io. Here's what you're getting:
Organization: vorotamoroz (fly launch defaults to the personal org)
Name: young-darkness-25342 (specified on the command line)
Region: Tokyo, Japan (specified on the command line)
App Machines: shared-cpu-1x, 256MB RAM (specified on the command line)
Postgres: <none> (not requested)
Redis: <none> (not requested)
Created app 'young-darkness-25342' in organization 'personal'
Admin URL: https://fly.io/apps/young-darkness-25342
Hostname: young-darkness-25342.fly.dev
Wrote config file fly.toml
Validating /home/vorotamoroz/dev/obsidian-livesync/utils/flyio/fly.toml
Platform: machines
✓ Configuration is valid
Your app is ready! Deploy with `flyctl deploy`
Secrets are staged for the first deployment
==> Verifying app config
Validating /home/vorotamoroz/dev/obsidian-livesync/utils/flyio/fly.toml
Platform: machines
✓ Configuration is valid
--> Verified app config
==> Building image
Searching for image 'couchdb:latest' remotely...
image found: img_ox20prk63084j1zq
Watch your deployment at https://fly.io/apps/young-darkness-25342/monitoring
Provisioning ips for young-darkness-25342
Dedicated ipv6: 2a09:8280:1::37:fde9
Shared ipv4: 66.241.124.163
Add a dedicated ipv4 with: fly ips allocate-v4
Creating a 1 GB volume named 'couchdata' for process group 'app'. Use 'fly vol extend' to increase its size
This deployment will:
* create 1 "app" machine
No machines in group app, launching a new machine
WARNING The app is not listening on the expected address and will not be reachable by fly-proxy.
You can fix this by configuring your app to listen on the following addresses:
- 0.0.0.0:5984
Found these processes inside the machine with open listening sockets:
PROCESS | ADDRESSES
-----------------*---------------------------------------
/.fly/hallpass | [fdaa:0:73b9:a7b:22e:3851:7f28:2]:22
Finished launching new machines
NOTE: The machines for [app] have services with 'auto_stop_machines = true' that will be stopped when idling
-------
Checking DNS configuration for young-darkness-25342.fly.dev
Visit your newly deployed app at https://young-darkness-25342.fly.dev/
-- Configuring CouchDB by REST APIs... -->
curl: (35) OpenSSL SSL_connect: Connection reset by peer in connection to young-darkness-25342.fly.dev:443
{"ok":true}
""
""
""
""
""
""
""
""
""
<-- Configuring CouchDB by REST APIs Done!
OK!
Setup finished! Also, we can set up Self-hosted LiveSync instantly, by the following setup uri.
Passphrase of setup-uri will be printed only one time. Keep it safe!
--- configured ---
database : obsidiannotes
E2EE passphrase: dark-wildflower-26467
--- setup uri ---
obsidian://setuplivesync?settings=%5B%22gZkBwjFbLqxbdSIbJymU%2FmTPBPAKUiHVGDRKYiNnKhW0auQeBgJOfvnxexZtMCn8sNiIUTAlxNaMGF2t%2BCEhpJoeCP%2FO%2BrwfN5LaNDQyky1Uf7E%2B64A5UWyjOYvZDOgq4iCKSdBAXp9oO%2BwKh4MQjUZ78vIVvJp8Mo6NWHfm5fkiWoAoddki1xBMvi%2BmmN%2FhZatQGcslVb9oyYWpZocduTl0a5Dv%2FQviGwlYQ%2F4NY0dVDIoOdvaYS%2FX4GhNAnLzyJKMXhPEJHo9FvR%2FEOBuwyfMdftV1SQUZ8YDCuiR3T7fh7Kn1c6OFgaFMpFm%2BWgIJ%2FZpmAyhZFpEcjpd7ty%2BN9kfd9gQsZM4%2BYyU9OwDd2DahVMBWkqoV12QIJ8OlJScHHdcUfMW5ex%2F4UZTWKNEHJsigITXBrtq11qGk3rBfHys8O0vY6sz%2FaYNM3iAOsR1aoZGyvwZm4O6VwtzK8edg0T15TL4O%2B7UajQgtCGxgKNYxb8EMOGeskv7NifYhjCWcveeTYOJzBhnIDyRbYaWbkAXQgHPBxzJRkkG%2FpBPfBBoJarj7wgjMvhLJ9xtL4FbP6sBNlr8jtAUCoq4L7LJcRNF4hlgvjJpL2BpFZMzkRNtUBcsRYR5J%2BM1X2buWi2BHncbSiRRDKEwNOQkc%2FmhMJjbAn%2F8eNKRuIICOLD5OvxD7FZNCJ0R%2BWzgrzcNV%22%2C%22ec7edc900516b4fcedb4c7cc01000000%22%2C%22fceb5fe54f6619ee266ed9a887634e07%22%5D
Your passphrase of Setup-URI is: patient-haze
This passphrase is never shown again, so please note it in a safe place.
```
All we have to do is copy the setup-URI (`obsidian`://...`) and open it from Self-hosted LiveSync on Obsidian.
If you did not install Deno, configurations will be printed again, instead of the setup-URI. In this case, we should configure it manually.
### delete-server.sh
The pair script of `deploy-server.sh`. We can delete the deployed server by this with fly.toml.
#### Materials
- Mandatory: flyctl, jq
- Recommended: none
#### Usage
```sh
./delete-server.sh
```
```
App 'young-darkness-25342 is going to be scaled according to this plan:
-1 machines for group 'app' on region 'nrt' of size 'shared-cpu-1x'
Executing scale plan
Destroyed e28667eec57158 group:app region:nrt size:shared-cpu-1x
Destroyed app young-darkness-25342
```
Set `appname`, `username`, `password`, `database`, `passphrase`, or `region` before running the script to override its generated values. Use `delete-server.sh` to remove the Fly.io application described by the generated `fly.toml`.