Menu

#271 feat(observability): UI-managed, opt-in OpenObserve — canonical settings path, service lifecycle, deprecate MERIDIAN_OO_AUTH

closed
nobody
None
2026-06-13
2026-06-12
Anonymous
No

Originally created by: Akarsh-Hegde

Why

The UI's OpenObserve settings never reached an installed daemon, so OTLP export stayed disabled despite valid credentials entered in the UI.

Root cause: settings.json was resolved relative to each process's cwd. The UI (next dev, cwd <repo>/ui) wrote <repo>/settings.json, while a bundle-installed daemon runs with cwd ~/.meridian/app and read ~/.meridian/app/settings.json (absent) → fell back to defaults with no credentials. The two only ever agreed when the daemon was cargo run from the repo root. The daemon logged observability initialised (no OTLP exporter) … otel="disabled" even though the UI looked fully configured.

What changed

1. Fixed, install-independent settings path (src/config.rs, ui/lib/settings.ts)

  • Both sides now resolve ~/.meridian/settings.json (next to meridian.db), overridable via MERIDIAN_SETTINGS_PATH.
  • The repo-local settings.json is kept only as a read-time migration fallback; the canonical path takes precedence so a UI "Apply" always reaches the daemon regardless of install type.

2. Opt-in OpenObserve export with a top-level toggle (ui/components/views/SettingsView.tsx, defaults in both settings.ts and config.rs)

  • The "OpenObserve Export" switch moves to the top of the Observability section.
  • otlp_enabled now defaults to off (consistent default in the TS and Rust sources).
  • The endpoint / email / password fields and the "Open OpenObserve" link render only when export is enabled.

Restart behaviour

Apply is unchanged and already correctly scoped: it SIGHUPs only the daemon PID, and launchd's KeepAlive relaunches that single service — screenpipe / MLX / UI are left running. (Verified the daemon is launchd-supervised; note ThrottleInterval=30 only throttles restarts within 30 s of a start.)

⚠️ Migration note for deployers

After this ships, the daemon reads ~/.meridian/settings.json. On an existing machine that file may be the older, credential-less one — seed it from the current repo file before deploying, e.g.:

cp <repo>/settings.json ~/.meridian/settings.json

Otherwise OTLP stays disabled and a bare "Apply" preserves an empty password (the GET would read the credless canonical file).

Verification

  • cargo fmt ✓ · cargo clippy --bin meridian -- -D warnings ✓ · cargo test --lib config (17 passed) ✓
  • settings.json confirmed gitignored + untracked (no plaintext credentials in history).
  • UI build verified at pre-push only (no ui/node_modules in the worktree); the TS changes are mechanical.

🤖 Generated with Claude Code

Related

Tickets: #272
Tickets: #273
Tickets: #279

Discussion

  • Anonymous

    Anonymous - 2026-06-13

    Originally posted by: Akarsh-Hegde

    Code review — findings

    CI is green and the feature works end-to-end (verified locally: toggle → OpenObserve starts → authenticated OTLP POST 200). Below is what I'd fix before merge, by severity.

    🟠 Medium — running: true can be reported while OpenObserve isn't actually serving

    ui/app/api/openobserve/route.ts:88 treats launchctl print succeeding as "up". But print returns success as soon as the job is bootstrapped, not when the process has bound :5080. OpenObserve takes a second or two to start listening (and can crash-loop after load). This is essentially the failure the toggle is meant to make obvious — "Apply succeeded" while OO is still down. Recommend polling the real endpoint before returning:

    // after kickstart, confirm OO is actually serving (not just loaded)
    let serving = false
    for (let i = 0; i < 20; i++) {
      try {
        const r = await fetch('http://localhost:5080/healthz', { signal: AbortSignal.timeout(1000) })
        if (r.ok) { serving = true; break }
      } catch { /* not up yet */ }
      await new Promise(r => setTimeout(r, 500))
    }
    if (!serving) return Response.json({ error: 'OpenObserve did not become reachable — see ~/.meridian/logs/openobserve-error.log' }, { status: 500 })
    

    🟠 Medium — changing credentials in the UI after first boot silently has no effect

    The cred→plist sync is gated on !initialised (route.ts:67). Correct for OpenObserve's first-boot account creation, but it means a user who later edits email/password in Settings and clicks Apply gets {ok:true} while OO keeps the old login — and they'll be locked out at :5080. At minimum, detect this (creds in settings differ from what OO has) and surface "password changes require resetting the OpenObserve data dir," rather than reporting success.

    🟡 Low — secret handling

    • The password is passed as a plutil argv (route.ts:78) and written to the plist in plaintext. execFile avoids shell injection, but the value is briefly visible in ps to other local users and the plist is world-readable (0644), same as settings.json. Consider chmod 600 on both the plist and settings.json on write. (Pre-existing for the plist, but this PR is where the UI starts writing it.)

    🟡 Low — PR scope / reviewability

    16 files spanning daemon resolution, new UI route, installer, a breaking deprecation, plus a repo-wide cargo fmt reformat of 6 unrelated files (ac1c2a6) and a product-wide CSS regression fix (d4f31c0). The fmt commit and the switch-CSS fix are independently valuable and block other PRs — strongly recommend extracting both into their own small PRs so this one is a focused, reviewable "OpenObserve opt-in" change. (The fmt fix in particular is what's currently red-CI-ing #272/#273/#274.)

    ✅ Looks good

    • Canonical ~/.meridian/settings.json resolution with MERIDIAN_SETTINGS_PATH override, repo path as read-only migration fallback, canonical-precedence — matches on both Rust and TS sides.
    • MERIDIAN_OO_AUTH deprecation logs a clear pointer to Settings; installer falls back with a warning and no longer hard-fails without creds.
    • The no-bounce fix (skip restart when ~/.openobserve/data is populated) is the right call and is verified (same pid before/after).
     
  • Anonymous

    Anonymous - 2026-06-13

    Ticket changed by: Akarsh-Hegde

    • status: open --> closed
     

Log in to post a comment.