This repository has no description
5.4 kB
151 lines
1// ─── Sync: pull vault + carry into container ──────────────────────
2//
3// vault: ob sync (Obsidian CLI) — needs auth token + vault config
4// carry: git pull from GitHub private repo (auth via CARRY_GITHUB_TOKEN)
5
6import { execSync } from "child_process";
7import { existsSync, writeFileSync, mkdirSync } from "fs";
8import { join } from "path";
9
10const VAULT_PATH = process.env.VAULT_PATH || "/data/vault";
11const CARRY_PATH = process.env.CARRY_PATH || "/data/carry";
12const CARRY_GITHUB_TOKEN = process.env.CARRY_GITHUB_TOKEN || "";
13const CARRY_BRANCH = process.env.CARRY_BRANCH || "main";
14const CARRY_REPO = "codegod100/carry-vault.git";
15const OBSIDIAN_AUTH_TOKEN = process.env.OBSIDIAN_AUTH_TOKEN || "";
16const OBSIDIAN_VAULT_ID = process.env.OBSIDIAN_VAULT_ID || "6472c50492efb918c2878e61048218f2";
17const OBSIDIAN_VAULT_NAME = process.env.OBSIDIAN_VAULT_NAME || "notes";
18const OBSIDIAN_SYNC_HOST = process.env.OBSIDIAN_SYNC_HOST || "sync-53.obsidian.md";
19const OBSIDIAN_ENCRYPTION_KEY = process.env.OBSIDIAN_ENCRYPTION_KEY || "";
20const OBSIDIAN_ENCRYPTION_SALT = process.env.OBSIDIAN_ENCRYPTION_SALT || "";
21
22function getCarryRemote(): string {
23 if (CARRY_GITHUB_TOKEN) {
24 return `https://${CARRY_GITHUB_TOKEN}@github.com/${CARRY_REPO}`;
25 }
26 return "";
27}
28
29function setupObsidianAuth(): boolean {
30 if (!OBSIDIAN_AUTH_TOKEN) {
31 console.error("[sync] OBSIDIAN_AUTH_TOKEN not set, skipping vault sync");
32 return false;
33 }
34
35 try {
36 // Write auth token
37 const configDir = "/root/.config/obsidian-headless";
38 mkdirSync(configDir, { recursive: true });
39 writeFileSync(join(configDir, "auth_token"), OBSIDIAN_AUTH_TOKEN, { mode: 0o600 });
40
41 // Write vault sync config
42 if (OBSIDIAN_ENCRYPTION_KEY && OBSIDIAN_ENCRYPTION_SALT) {
43 const syncDir = join(configDir, "sync", OBSIDIAN_VAULT_ID);
44 mkdirSync(syncDir, { recursive: true });
45 const vaultConfig = {
46 vaultId: OBSIDIAN_VAULT_ID,
47 vaultName: OBSIDIAN_VAULT_NAME,
48 vaultPath: VAULT_PATH,
49 host: OBSIDIAN_SYNC_HOST,
50 encryptionVersion: 3,
51 encryptionKey: OBSIDIAN_ENCRYPTION_KEY,
52 encryptionSalt: OBSIDIAN_ENCRYPTION_SALT,
53 conflictStrategy: "merge",
54 deviceName: "strata-container",
55 };
56 writeFileSync(join(syncDir, "config.json"), JSON.stringify(vaultConfig, null, 2), { mode: 0o600 });
57 }
58
59 console.log("[sync] obsidian auth configured");
60 return true;
61 } catch (e: any) {
62 console.error("[sync] obsidian auth setup failed:", e.message);
63 return false;
64 }
65}
66
67export async function syncVault(): Promise<void> {
68 if (!setupObsidianAuth()) return;
69
70 try {
71 // First time: setup sync link
72 if (!existsSync(join(VAULT_PATH, ".obsidian"))) {
73 try {
74 execSync(`ob sync-setup --vault ${OBSIDIAN_VAULT_ID} --path ${VAULT_PATH}`, {
75 stdio: "pipe",
76 timeout: 30000,
77 });
78 } catch (e: any) {
79 // sync-setup may fail if already linked; try sync directly
80 console.error("[sync] sync-setup failed (may already be linked):", e.message?.slice(0, 100));
81 }
82 }
83
84 execSync(`ob sync --path ${VAULT_PATH}`, { stdio: "pipe", timeout: 120000 });
85 console.log("[sync] vault synced via ob");
86 } catch (e: any) {
87 console.error("[sync] vault sync failed:", e.message?.slice(0, 200));
88 }
89}
90
91export async function syncCarry(): Promise<void> {
92 const remote = getCarryRemote();
93 if (!remote) {
94 console.error("[sync] CARRY_GITHUB_TOKEN not set, skipping carry sync");
95 return;
96 }
97
98 try {
99 // If not cloned yet, clone; otherwise pull
100 try {
101 execSync(`git -C ${CARRY_PATH} rev-parse --git-dir`, { stdio: "pipe" });
102 execSync(`git -C ${CARRY_PATH} pull origin ${CARRY_BRANCH}`, {
103 stdio: "pipe",
104 timeout: 30000,
105 env: { ...process.env, GIT_TERMINAL_PROMPT: "0" },
106 });
107 console.log("[sync] carry pulled via git");
108 } catch {
109 // Not a git repo — clone
110 execSync(`git clone --branch ${CARRY_BRANCH} --single-branch ${remote} ${CARRY_PATH}`, {
111 stdio: "pipe",
112 timeout: 60000,
113 env: { ...process.env, GIT_TERMINAL_PROMPT: "0" },
114 });
115 console.log("[sync] carry cloned via git");
116 }
117 } catch (e: any) {
118 console.error("[sync] carry sync failed:", e.message?.slice(0, 200));
119 }
120}
121
122function exportCarryJson(): void {
123 try {
124 const domains = [
125 { domain: "org.latha.entity", fields: "name url type description" },
126 { domain: "org.latha.citation", fields: "title url takeaway" },
127 { domain: "org.latha.connection", fields: "relation source target context" },
128 ];
129
130 for (const { domain, fields } of domains) {
131 const outPath = join(CARRY_PATH, `${domain}.json`);
132 const cmd = `carry query ${domain} ${fields} --format json`;
133 const result = execSync(cmd, {
134 cwd: CARRY_PATH,
135 encoding: "utf-8",
136 timeout: 30000,
137 });
138 writeFileSync(outPath, result);
139 const count = JSON.parse(result).length;
140 console.log(`[sync] exported ${domain}: ${count} records`);
141 }
142 } catch (e: any) {
143 console.error("[sync] carry export failed:", e.message?.slice(0, 200));
144 }
145}
146
147export async function syncAll(): Promise<void> {
148 await Promise.all([syncVault(), syncCarry()]);
149 // Always export carry JSON if carry vault exists — even without git sync
150 exportCarryJson();
151}