Files
daniel156161 902a3df8b5
Build and Push Docker Container / build-and-push (push) Successful in 4m26s
feat: add Obsidian CLI API service
- Add Docker image for the official Obsidian desktop app and CLI.

- Start Obsidian headlessly with Xvfb and DBus setup.

- Expose a safe structured HTTP command API without shell execution.

- Add JWT-based vault, path, and command authorization.

- Support single-vault and multi-vault container mounts.

- Add TypeScript SDK helpers for Obsidian CLI commands.

- Add n8n community node package with Obsidian operations.

- Add docs, compose config, tests, and production image workflow.
2026-06-24 09:11:13 +02:00

134 lines
3.6 KiB
Markdown

# n8n usage notes
Use the TypeScript SDK from an n8n **Code** node when n8n can import local or bundled npm packages. In self-hosted n8n this usually means installing the SDK where n8n can resolve it and allowing external modules, for example with `NODE_FUNCTION_ALLOW_EXTERNAL=@obsidian-api/sdk` or `NODE_FUNCTION_ALLOW_EXTERNAL=*`.
The SDK only sends structured `args` arrays to the HTTP API. It never sends shell command strings.
## Environment variables
Recommended n8n env vars:
```env
OBSIDIAN_API_URL=http://obsidian-api:8080
OBSIDIAN_API_JWT=<jwt-from-scripts/create_jwt.py>
OBSIDIAN_VAULT=work
```
Recommended: use a restricted JWT for n8n. Create it with:
```bash
OBSIDIAN_JWT_SECRET=change-this-long-random-secret \
python scripts/create_jwt.py \
--sub n8n \
--vault work \
--path Inbox/n8n/ \
--path Automation.md \
--command create \
--command read \
--command append \
--command search \
--command search:context \
--days 365
```
The JWT contains claims like:
```json
{
"sub": "n8n",
"vaults": ["work"],
"paths": ["Inbox/n8n/", "Automation.md"],
"commands": ["create", "read", "append", "search", "search:context"],
"exp": 1791536000
}
```
If n8n runs outside the Docker network, use the exposed host URL instead:
```env
OBSIDIAN_API_URL=http://localhost:8080
```
## Basic Code node
```ts
import { ObsidianCliClient } from '@obsidian-api/sdk';
const obsidian = new ObsidianCliClient({
baseUrl: process.env.OBSIDIAN_API_URL!,
token: process.env.OBSIDIAN_API_JWT!,
vault: process.env.OBSIDIAN_VAULT,
});
const title = $json.title ?? 'n8n note';
const content = `# ${title}\n\nCreated from n8n.\n`;
const result = await obsidian.create({
path: `Inbox/${title}.md`,
content,
overwrite: true,
});
return [{ json: result }];
```
## Append incoming webhook payload to a daily note
```ts
import { ObsidianCliClient } from '@obsidian-api/sdk';
const obsidian = new ObsidianCliClient({
baseUrl: process.env.OBSIDIAN_API_URL!,
token: process.env.OBSIDIAN_API_JWT!,
vault: process.env.OBSIDIAN_VAULT,
});
const payload = JSON.stringify($json, null, 2).replace(/```/g, '``\\`');
const result = await obsidian.dailyAppend(
`\n## n8n event\n\n\`\`\`json\n${payload}\n\`\`\`\n`,
);
return [{ json: result }];
```
## Search notes
```ts
import { ObsidianCliClient } from '@obsidian-api/sdk';
const obsidian = new ObsidianCliClient({
baseUrl: process.env.OBSIDIAN_API_URL!,
token: process.env.OBSIDIAN_API_JWT!,
vault: process.env.OBSIDIAN_VAULT,
});
const result = await obsidian.search($json.query, { format: 'json' });
return [{ json: { matches: JSON.parse(result.stdout) } }];
```
## Multi-vault usage
If the API container registers multiple vaults, use a scoped client per vault:
```ts
const base = new ObsidianCliClient({
baseUrl: process.env.OBSIDIAN_API_URL!,
token: process.env.OBSIDIAN_API_JWT!,
});
const work = base.inVault('work');
const personal = base.inVault('personal');
await work.create({ path: 'Inbox/Work.md', content: 'Work note', overwrite: true });
await personal.create({ path: 'Inbox/Personal.md', content: 'Personal note', overwrite: true });
```
## Without SDK
If package imports are not available in your n8n environment, call the HTTP API directly:
```ts
const response = await fetch(`${process.env.OBSIDIAN_API_URL}/commands`, {
method: 'POST',
headers: {
authorization: `Bearer ${process.env.OBSIDIAN_API_JWT}`,
'content-type': 'application/json',
},
body: JSON.stringify({
args: ['vault=work', 'create', 'path=Inbox/n8n.md', 'content=Hello from n8n', 'overwrite'],
}),
});
return [{ json: await response.json() }];
```