// ─── Sync: pull vault + carry into container ────────────────────── // // vault: ob sync (Obsidian CLI) — needs auth token + vault config // carry: git pull from GitHub private repo (auth via CARRY_GITHUB_TOKEN) import { execSync } from "child_process"; import { existsSync, writeFileSync, mkdirSync } from "fs"; import { join } from "path"; const VAULT_PATH = process.env.VAULT_PATH || "/data/vault"; const CARRY_PATH = process.env.CARRY_PATH || "/data/carry"; const CARRY_GITHUB_TOKEN = process.env.CARRY_GITHUB_TOKEN || ""; const CARRY_BRANCH = process.env.CARRY_BRANCH || "main"; const CARRY_REPO = "codegod100/carry-vault.git"; const OBSIDIAN_AUTH_TOKEN = process.env.OBSIDIAN_AUTH_TOKEN || ""; const OBSIDIAN_VAULT_ID = process.env.OBSIDIAN_VAULT_ID || "6472c50492efb918c2878e61048218f2"; const OBSIDIAN_VAULT_NAME = process.env.OBSIDIAN_VAULT_NAME || "notes"; const OBSIDIAN_SYNC_HOST = process.env.OBSIDIAN_SYNC_HOST || "sync-53.obsidian.md"; const OBSIDIAN_ENCRYPTION_KEY = process.env.OBSIDIAN_ENCRYPTION_KEY || ""; const OBSIDIAN_ENCRYPTION_SALT = process.env.OBSIDIAN_ENCRYPTION_SALT || ""; function getCarryRemote(): string { if (CARRY_GITHUB_TOKEN) { return `https://${CARRY_GITHUB_TOKEN}@github.com/${CARRY_REPO}`; } return ""; } function setupObsidianAuth(): boolean { if (!OBSIDIAN_AUTH_TOKEN) { console.error("[sync] OBSIDIAN_AUTH_TOKEN not set, skipping vault sync"); return false; } try { // Write auth token const configDir = "/root/.config/obsidian-headless"; mkdirSync(configDir, { recursive: true }); writeFileSync(join(configDir, "auth_token"), OBSIDIAN_AUTH_TOKEN, { mode: 0o600 }); // Write vault sync config if (OBSIDIAN_ENCRYPTION_KEY && OBSIDIAN_ENCRYPTION_SALT) { const syncDir = join(configDir, "sync", OBSIDIAN_VAULT_ID); mkdirSync(syncDir, { recursive: true }); const vaultConfig = { vaultId: OBSIDIAN_VAULT_ID, vaultName: OBSIDIAN_VAULT_NAME, vaultPath: VAULT_PATH, host: OBSIDIAN_SYNC_HOST, encryptionVersion: 3, encryptionKey: OBSIDIAN_ENCRYPTION_KEY, encryptionSalt: OBSIDIAN_ENCRYPTION_SALT, conflictStrategy: "merge", deviceName: "strata-container", }; writeFileSync(join(syncDir, "config.json"), JSON.stringify(vaultConfig, null, 2), { mode: 0o600 }); } console.log("[sync] obsidian auth configured"); return true; } catch (e: any) { console.error("[sync] obsidian auth setup failed:", e.message); return false; } } export async function syncVault(): Promise { if (!setupObsidianAuth()) return; try { // First time: setup sync link if (!existsSync(join(VAULT_PATH, ".obsidian"))) { try { execSync(`ob sync-setup --vault ${OBSIDIAN_VAULT_ID} --path ${VAULT_PATH}`, { stdio: "pipe", timeout: 30000, }); } catch (e: any) { // sync-setup may fail if already linked; try sync directly console.error("[sync] sync-setup failed (may already be linked):", e.message?.slice(0, 100)); } } execSync(`ob sync --path ${VAULT_PATH}`, { stdio: "pipe", timeout: 120000 }); console.log("[sync] vault synced via ob"); } catch (e: any) { console.error("[sync] vault sync failed:", e.message?.slice(0, 200)); } } export async function syncCarry(): Promise { const remote = getCarryRemote(); if (!remote) { console.error("[sync] CARRY_GITHUB_TOKEN not set, skipping carry sync"); return; } try { // If not cloned yet, clone; otherwise pull try { execSync(`git -C ${CARRY_PATH} rev-parse --git-dir`, { stdio: "pipe" }); execSync(`git -C ${CARRY_PATH} pull origin ${CARRY_BRANCH}`, { stdio: "pipe", timeout: 30000, env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }, }); console.log("[sync] carry pulled via git"); } catch { // Not a git repo — clone execSync(`git clone --branch ${CARRY_BRANCH} --single-branch ${remote} ${CARRY_PATH}`, { stdio: "pipe", timeout: 60000, env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }, }); console.log("[sync] carry cloned via git"); } } catch (e: any) { console.error("[sync] carry sync failed:", e.message?.slice(0, 200)); } } function exportCarryJson(): void { try { const domains = [ { domain: "org.latha.entity", fields: "name url type description" }, { domain: "org.latha.citation", fields: "title url takeaway" }, { domain: "org.latha.connection", fields: "relation source target context" }, ]; for (const { domain, fields } of domains) { const outPath = join(CARRY_PATH, `${domain}.json`); const cmd = `carry query ${domain} ${fields} --format json`; const result = execSync(cmd, { cwd: CARRY_PATH, encoding: "utf-8", timeout: 30000, }); writeFileSync(outPath, result); const count = JSON.parse(result).length; console.log(`[sync] exported ${domain}: ${count} records`); } } catch (e: any) { console.error("[sync] carry export failed:", e.message?.slice(0, 200)); } } export async function syncAll(): Promise { await Promise.all([syncVault(), syncCarry()]); // Always export carry JSON if carry vault exists — even without git sync exportCarryJson(); }