From 43778d7bca9ca333754a1f6522f0c92cdfc9bbf4 Mon Sep 17 00:00:00 2001 From: Nystik <236107-Nystik@users.noreply.gitlab.com> Date: Sun, 17 May 2026 22:03:36 +0200 Subject: [PATCH 01/13] solve bug in demo vault query --- server/demo/demo-middleware.js | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/server/demo/demo-middleware.js b/server/demo/demo-middleware.js index 0371f01..68748e4 100644 --- a/server/demo/demo-middleware.js +++ b/server/demo/demo-middleware.js @@ -272,16 +272,36 @@ function trackVaultLifecycle(req, res, next) { if (s) { if (req.path === "/create" && body.id) { - s.vaults.add(body.id); + // body.id is storage-prefixed at this point (outboundTranslator runs after us). + // Translate to the user-visible name so it matches what pageLoadHandler queries with. + const userName = tryParseUserVaultName(sessionId, body.id); + + if (userName !== null) { + s.vaults.add(userName); + } else { + console.warn( + "[demo] trackVaultLifecycle: could not parse user name from create response id:", + body.id, + ); + } } else if (req.path === "/rename") { - const oldName = req.body && req.body._origVault; + const oldName = req._demoOriginalVault; if (oldName) { s.vaults.delete(oldName); } if (body.id) { - s.vaults.add(body.id); + const userName = tryParseUserVaultName(sessionId, body.id); + + if (userName !== null) { + s.vaults.add(userName); + } else { + console.warn( + "[demo] trackVaultLifecycle: could not parse user name from rename response id:", + body.id, + ); + } } } else if (req.method === "DELETE" && req.path === "/remove") { const removed = req._demoOriginalVault; From 23306ff68e9d6d8ff5eca42b6c926ecbb978d0bd Mon Sep 17 00:00:00 2001 From: Nystik <236107-Nystik@users.noreply.gitlab.com> Date: Sun, 17 May 2026 22:11:17 +0200 Subject: [PATCH 02/13] improve token management for headless sync cli --- server/plugins/headless-sync/auth.js | 26 ++++++++++++-------- server/plugins/headless-sync/index.js | 4 +++ server/plugins/headless-sync/ob-cli.js | 34 ++++++++++++++++++++++++-- 3 files changed, 52 insertions(+), 12 deletions(-) diff --git a/server/plugins/headless-sync/auth.js b/server/plugins/headless-sync/auth.js index 4f56c3e..88e66cc 100644 --- a/server/plugins/headless-sync/auth.js +++ b/server/plugins/headless-sync/auth.js @@ -1,10 +1,10 @@ const fs = require("fs"); const path = require("path"); -const os = require("os"); +const { getObHome } = require("./ob-cli"); -function getObAuthFile() { +function getObAuthFile(dataDir) { return path.join( - os.homedir(), + getObHome(dataDir), ".config", "obsidian-headless", "auth_token", @@ -23,14 +23,14 @@ function loadToken(dataDir) { const data = JSON.parse(fs.readFileSync(internalFile, "utf-8")); if (data && data.token) { - syncToObCli(data.token); + syncToObCli(dataDir, data.token); return data; } } } catch {} // Fall back to ob CLI's own auth file - const obAuthFile = getObAuthFile(); + const obAuthFile = getObAuthFile(dataDir); try { if (fs.existsSync(obAuthFile)) { @@ -49,7 +49,7 @@ function loadToken(dataDir) { function saveToken(dataDir, tokenData) { saveInternal(dataDir, tokenData); - syncToObCli(tokenData.token); + syncToObCli(dataDir, tokenData.token); } function clearToken(dataDir) { @@ -61,7 +61,7 @@ function clearToken(dataDir) { } } catch {} - const obAuthFile = getObAuthFile(); + const obAuthFile = getObAuthFile(dataDir); try { if (fs.existsSync(obAuthFile)) { @@ -94,8 +94,8 @@ function saveInternal(dataDir, tokenData) { fs.writeFileSync(internalFile, JSON.stringify(tokenData, null, 2), "utf-8"); } -function syncToObCli(token) { - const obAuthFile = getObAuthFile(); +function syncToObCli(dataDir, token) { + const obAuthFile = getObAuthFile(dataDir); try { const dir = path.dirname(obAuthFile); @@ -124,4 +124,10 @@ function getTokenInfo(dataDir) { return null; } -module.exports = { loadToken, saveToken, clearToken, isAuthenticated, getTokenInfo }; +module.exports = { + loadToken, + saveToken, + clearToken, + isAuthenticated, + getTokenInfo, +}; diff --git a/server/plugins/headless-sync/index.js b/server/plugins/headless-sync/index.js index 55b45a0..4a4e679 100644 --- a/server/plugins/headless-sync/index.js +++ b/server/plugins/headless-sync/index.js @@ -29,6 +29,10 @@ module.exports = { ctx.log("ob CLI not found. Install obsidian-headless to enable sync."); } + // Redirect ob's HOME under the plugin's data dir so its config (per-vault sync setups, etc.) + // survives container recreates. Must happen before auth.loadToken since loadToken pushes the token into ob's config location via syncToObCli. + obCli.configure({ dataDir: ctx.dataDir }); + const token = auth.loadToken(ctx.dataDir); if (token) { diff --git a/server/plugins/headless-sync/ob-cli.js b/server/plugins/headless-sync/ob-cli.js index 5bcd51f..5001905 100644 --- a/server/plugins/headless-sync/ob-cli.js +++ b/server/plugins/headless-sync/ob-cli.js @@ -1,8 +1,28 @@ const { spawn, execSync } = require("child_process"); +const fs = require("fs"); const os = require("os"); +const path = require("path"); const isWindows = process.platform === "win32"; +// When set via configure(), HOME for the spawned ob points under the plugin's data dir so +// ob's config dir (~/.config/obsidian-headless/) survives container recreates. +let configuredDataDir = null; + +function getObHome(dataDir) { + return path.join(dataDir, "ob-home"); +} + +function configure(opts) { + configuredDataDir = opts && opts.dataDir ? opts.dataDir : null; + + if (configuredDataDir) { + try { + fs.mkdirSync(getObHome(configuredDataDir), { recursive: true }); + } catch {} + } +} + function checkInstalled() { try { const output = execSync("ob --version", { @@ -19,8 +39,12 @@ function checkInstalled() { } function spawnOb(args, opts = {}) { + const home = configuredDataDir + ? getObHome(configuredDataDir) + : os.homedir(); + return spawn("ob", args, { - env: { ...process.env, HOME: os.homedir() }, + env: { ...process.env, HOME: home }, shell: isWindows, windowsHide: true, ...opts, @@ -58,4 +82,10 @@ function runCommand(args, opts = {}) { }); } -module.exports = { checkInstalled, spawnOb, runCommand }; +module.exports = { + checkInstalled, + spawnOb, + runCommand, + configure, + getObHome, +}; From 64073968d4cad757fc3acf275f89b098ea7fc931 Mon Sep 17 00:00:00 2001 From: Nystik <236107-Nystik@users.noreply.gitlab.com> Date: Mon, 18 May 2026 22:40:34 +0200 Subject: [PATCH 03/13] scaffold new structure and packages --- apps/ignis-server/package.json | 5 +++ package-lock.json | 61 ++++++++++++++++++++++++++++- package.json | 8 +++- packages/bridge-plugin/package.json | 5 +++ packages/server-core/package.json | 5 +++ packages/services/package.json | 5 +++ packages/shim/package.json | 6 +++ packages/ui/package.json | 5 +++ 8 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 apps/ignis-server/package.json create mode 100644 packages/bridge-plugin/package.json create mode 100644 packages/server-core/package.json create mode 100644 packages/services/package.json create mode 100644 packages/shim/package.json create mode 100644 packages/ui/package.json diff --git a/apps/ignis-server/package.json b/apps/ignis-server/package.json new file mode 100644 index 0000000..36ba745 --- /dev/null +++ b/apps/ignis-server/package.json @@ -0,0 +1,5 @@ +{ + "name": "@ignis/app", + "version": "0.0.0-internal", + "private": true +} diff --git a/package-lock.json b/package-lock.json index 5bbeeea..aa625d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,16 @@ { - "name": "ignis", + "name": "ignis-monorepo", "version": "0.8.1", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "ignis", + "name": "ignis-monorepo", "version": "0.8.1", + "workspaces": [ + "packages/*", + "apps/*" + ], "dependencies": { "archiver": "^7.0.1", "chokidar": "^3.6.0", @@ -26,6 +30,15 @@ "vitest": "^3.2.4" } }, + "apps/ignis": { + "name": "@ignis/app", + "version": "0.8.1", + "extraneous": true + }, + "apps/ignis-server": { + "name": "@ignis/app", + "version": "0.0.0-internal" + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -482,6 +495,30 @@ "node": ">=12" } }, + "node_modules/@ignis/app": { + "resolved": "apps/ignis-server", + "link": true + }, + "node_modules/@ignis/bridge-plugin": { + "resolved": "packages/bridge-plugin", + "link": true + }, + "node_modules/@ignis/server-core": { + "resolved": "packages/server-core", + "link": true + }, + "node_modules/@ignis/services": { + "resolved": "packages/services", + "link": true + }, + "node_modules/@ignis/shim": { + "resolved": "packages/shim", + "link": true + }, + "node_modules/@ignis/ui": { + "resolved": "packages/ui", + "link": true + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -4412,6 +4449,26 @@ "engines": { "node": ">= 14" } + }, + "packages/bridge-plugin": { + "name": "@ignis/bridge-plugin", + "version": "0.0.0-internal" + }, + "packages/server-core": { + "name": "@ignis/server-core", + "version": "0.0.0-internal" + }, + "packages/services": { + "name": "@ignis/services", + "version": "0.0.0-internal" + }, + "packages/shim": { + "name": "@ignis/shim", + "version": "0.0.0-internal" + }, + "packages/ui": { + "name": "@ignis/ui", + "version": "0.0.0-internal" } } } diff --git a/package.json b/package.json index 467a09f..6f7f5ea 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,12 @@ { - "name": "ignis", + "name": "ignis-monorepo", "version": "0.8.1", "private": true, - "description": "An Electron shim and server bridge for running Obsidian in a browser.", + "description": "Monorepo for Ignis: a browser-based Obsidian client. Self-hosted server in apps/ignis-server; shim, UI, and shared libraries in packages/.", + "workspaces": [ + "packages/*", + "apps/*" + ], "scripts": { "build": "node build.js", "dev:server": "node server/index.js", diff --git a/packages/bridge-plugin/package.json b/packages/bridge-plugin/package.json new file mode 100644 index 0000000..8ecbea9 --- /dev/null +++ b/packages/bridge-plugin/package.json @@ -0,0 +1,5 @@ +{ + "name": "@ignis/bridge-plugin", + "version": "0.0.0-internal", + "private": true +} diff --git a/packages/server-core/package.json b/packages/server-core/package.json new file mode 100644 index 0000000..a3181c5 --- /dev/null +++ b/packages/server-core/package.json @@ -0,0 +1,5 @@ +{ + "name": "@ignis/server-core", + "version": "0.0.0-internal", + "private": true +} diff --git a/packages/services/package.json b/packages/services/package.json new file mode 100644 index 0000000..3a23448 --- /dev/null +++ b/packages/services/package.json @@ -0,0 +1,5 @@ +{ + "name": "@ignis/services", + "version": "0.0.0-internal", + "private": true +} diff --git a/packages/shim/package.json b/packages/shim/package.json new file mode 100644 index 0000000..f72c9f8 --- /dev/null +++ b/packages/shim/package.json @@ -0,0 +1,6 @@ +{ + "name": "@ignis/shim", + "version": "0.0.0-internal", + "private": true, + "main": "src/loader.js" +} diff --git a/packages/ui/package.json b/packages/ui/package.json new file mode 100644 index 0000000..d6256db --- /dev/null +++ b/packages/ui/package.json @@ -0,0 +1,5 @@ +{ + "name": "@ignis/ui", + "version": "0.0.0-internal", + "private": true +} From 4da91d017be0a6d052f06acaf7defbaf04982974 Mon Sep 17 00:00:00 2001 From: Nystik <236107-Nystik@users.noreply.gitlab.com> Date: Tue, 19 May 2026 01:39:29 +0200 Subject: [PATCH 04/13] implement ui-registry for dynamic UI handler registration --- server/assets/index.html | 2 +- src/shims/electron/ipc-renderer.js | 2 +- src/shims/electron/remote/dialog.js | 2 +- src/shims/globals.js | 2 +- src/shims/init.js | 2 +- src/shims/loader.js | 2 ++ src/shims/ui-registry.js | 26 ++++++++++++++++++++++++++ src/ui/bootstrap.js | 24 +++++++++++++++++++----- src/ui/index.js | 2 ++ 9 files changed, 54 insertions(+), 10 deletions(-) create mode 100644 src/shims/ui-registry.js diff --git a/server/assets/index.html b/server/assets/index.html index 36493ed..c3e7578 100644 --- a/server/assets/index.html +++ b/server/assets/index.html @@ -41,8 +41,8 @@