902a3df8b5
Build and Push Docker Container / build-and-push (push) Successful in 4m26s
- 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.
84 lines
2.7 KiB
Python
84 lines
2.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Render the global Obsidian desktop config (~/.config/obsidian/obsidian.json).
|
|
|
|
Enables the official CLI and registers every mounted vault so the official
|
|
`obsidian` CLI can target them with `vault=<name>`.
|
|
|
|
Vaults are read from the environment:
|
|
|
|
* ``OBSIDIAN_VAULTS`` (preferred, multi-vault):
|
|
- JSON object: {"work": "/vaults/work", "personal": "/vaults/personal"}
|
|
- or pairs: work=/vaults/work,personal=/vaults/personal
|
|
* ``OBSIDIAN_VAULT_PATH`` (single-vault fallback): /vault
|
|
|
|
The first registered vault becomes the default (highest ``ts``), which is the
|
|
vault used when a CLI command omits ``vault=<name>``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
def _parse_vaults() -> "list[tuple[str, str]]":
|
|
raw = os.environ.get("OBSIDIAN_VAULTS", "").strip()
|
|
pairs: list[tuple[str, str]] = []
|
|
|
|
if raw:
|
|
# Try JSON object first.
|
|
try:
|
|
data = json.loads(raw)
|
|
if isinstance(data, dict):
|
|
return [(str(name), str(path)) for name, path in data.items()]
|
|
except json.JSONDecodeError:
|
|
pass
|
|
|
|
# Fall back to comma/newline separated name=path pairs.
|
|
for chunk in raw.replace("\n", ",").split(","):
|
|
chunk = chunk.strip()
|
|
if not chunk:
|
|
continue
|
|
if "=" not in chunk:
|
|
print(f"WARNING: ignoring malformed OBSIDIAN_VAULTS entry: {chunk!r}", file=sys.stderr)
|
|
continue
|
|
name, path = chunk.split("=", 1)
|
|
pairs.append((name.strip(), path.strip()))
|
|
|
|
if not pairs:
|
|
single = os.environ.get("OBSIDIAN_VAULT_PATH", "/vault").strip() or "/vault"
|
|
name = Path(single).name or "vault"
|
|
pairs.append((name, single))
|
|
|
|
return pairs
|
|
|
|
def main() -> int:
|
|
pairs = _parse_vaults()
|
|
|
|
vaults: dict[str, dict] = {}
|
|
# Descending timestamps so the first declared vault sorts as most-recent
|
|
# (the desktop app treats the highest ts as the active/default vault).
|
|
base_ts = 1893456000000
|
|
for index, (name, path) in enumerate(pairs):
|
|
vaults[name] = {
|
|
"path": path,
|
|
"ts": base_ts - index,
|
|
"open": True,
|
|
}
|
|
|
|
config = {"cli": True, "vaults": vaults}
|
|
|
|
config_path = Path(os.environ.get("OBSIDIAN_CONFIG_PATH", "/root/.config/obsidian/obsidian.json"))
|
|
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
config_path.write_text(json.dumps(config, indent=2) + "\n", encoding="utf-8")
|
|
|
|
names = ", ".join(f"{name} -> {data['path']}" for name, data in vaults.items())
|
|
print(f"Registered {len(vaults)} vault(s): {names}")
|
|
# First declared vault is the default for commands without vault=<name>.
|
|
print(pairs[0][1]) # default vault path, consumed by the entrypoint
|
|
return 0
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|