(options)
| 212 | } |
| 213 | |
| 214 | export async function prepareObsidianProfile(options) { |
| 215 | // Fail closed if our profile tree is not a private directory we own |
| 216 | // (temp-squat / TOCTOU guard). Secure the root before the instance dir so the |
| 217 | // instance dir is created inside an already-validated 0o700 tree. |
| 218 | await ensureSecureDir(options.profileRoot); |
| 219 | await ensureSecureDir(options.instancePath); |
| 220 | |
| 221 | const userDataPath = path.join(options.obsidianHome, "Library", "Application Support", "obsidian"); |
| 222 | await fs.mkdir(userDataPath, { recursive: true, mode: 0o700 }); |
| 223 | await fs.mkdir(path.join(options.obsidianHome, "Library", "Logs"), { recursive: true, mode: 0o700 }); |
| 224 | await linkHostKeychains(options); |
| 225 | |
| 226 | const vaultId = stableVaultId(options.vaultPath); |
| 227 | const obsidianJsonPath = path.join(userDataPath, "obsidian.json"); |
| 228 | await writeJson(obsidianJsonPath, { |
| 229 | cli: true, |
| 230 | updateDisabled: true, |
| 231 | vaults: { |
| 232 | [vaultId]: { |
| 233 | open: true, |
| 234 | path: options.vaultPath, |
| 235 | ts: Date.now(), |
| 236 | }, |
| 237 | }, |
| 238 | }); |
| 239 | |
| 240 | // Record which worktree this instance belongs to so the teardown reaper can |
| 241 | // reap it once that worktree is removed (see INSTANCE_MARKER_FILE). |
| 242 | await writeJson(path.join(options.instancePath, INSTANCE_MARKER_FILE), { |
| 243 | worktreePath: options.worktreePath, |
| 244 | vaultName: options.vaultName, |
| 245 | vaultPath: options.vaultPath, |
| 246 | }); |
| 247 | |
| 248 | return { |
| 249 | obsidianJsonPath, |
| 250 | userDataPath, |
| 251 | vaultId, |
| 252 | }; |
| 253 | } |
| 254 | |
| 255 | async function linkHostKeychains(options) { |
| 256 | const realHome = process.env.HOME; |
no test coverage detected