Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 22 additions & 8 deletions src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,12 @@ tar = "0.4"
[target.'cfg(target_os = "linux")'.dependencies]
webkit2gtk = "2"

[dev-dependencies]
# Filesystem fixtures for migration tests. `tempdir()` + scoped
# cleanup so a failed test doesn't leak directories under the
# user's actual home.
tempfile = "3"

[profile.release]
panic = "abort"
codegen-units = 1
Expand Down
39 changes: 29 additions & 10 deletions src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -838,17 +838,36 @@ fn main() {
#[cfg(target_os = "linux")]
quiet_alsa_diagnostics();

// Point myownmesh-core at our existing data directory before any
// mesh module is touched. The substrate defaults to `~/.myownmesh/`,
// but MyOwnLLM has shipped `~/.myownllm/.secrets/identity.json` and
// `~/.myownllm/mesh/rosters/*.json` for many releases — moving the
// anchor would orphan every user's Device ID and peer approvals.
// `MYOWNMESH_HOME` overrides the default at the source. Set
// unconditionally; explicit override wins if the user set one
// themselves (mostly relevant for cross-app test harnesses).
// Point myownmesh-core at a dedicated subdirectory under the LLM's
// tree (`~/.myownllm/.myownmesh/`) before any mesh module is
// touched. PRs #203/#205 set `MYOWNMESH_HOME=~/.myownllm` so the
// daemon shared identity + rosters with the LLM under one
// directory, but the daemon also writes its own
// `{MYOWNMESH_HOME}/config.json` — colliding with the LLM's
// `~/.myownllm/config.json` (different schemas). Any
// `NetworkAdd` IPC call triggered the daemon's
// `persist_network_add` → `MeshConfig::load() → push → save`,
// which silently dropped every LLM-only key from the loaded
// config and wrote the daemon shape back over the file. From the
// user's perspective: "I saved a network, restarted, and all my
// settings were gone."
//
// The subdirectory keeps the daemon's config.json + updates/
// isolated. Identity, rosters, and governance states get moved
// into the subdir on first launch so existing users keep their
// pubkey + peer approvals — losing identity continuity would
// orphan every user's Device ID. Migration is idempotent and
// best-effort; the function logs failures to stderr and the
// daemon falls back to default-empty state in the worst case.
if std::env::var_os("MYOWNMESH_HOME").is_none() {
if let Ok(dir) = myownllm_dir() {
std::env::set_var("MYOWNMESH_HOME", dir);
if let Ok(llm_dir) = myownllm_dir() {
let daemon_home = llm_dir.join(".myownmesh");
if let Err(e) =
mesh::migration::migrate_daemon_state_into_subdir(&llm_dir, &daemon_home)
{
eprintln!("mesh-migration: failed: {e:#}");
}
std::env::set_var("MYOWNMESH_HOME", &daemon_home);
}
}
// Pre-multi-network rosters lived at `~/.myownllm/mesh/roster.json`
Expand Down
48 changes: 36 additions & 12 deletions src-tauri/src/mesh/daemon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@
//! 1. **Shared daemon**: `~/.myownmesh/daemon.sock`. If MyOwnMesh GUI
//! is running, its daemon already binds this socket. Connecting
//! here shares identity, roster, and networks across both apps.
//! 2. **LLM-owned daemon**: `~/.myownllm/daemon.sock`. We spawn
//! `myownmesh serve` with `MYOWNMESH_HOME=~/.myownllm` so the
//! daemon reads/writes the LLM's existing on-disk layout
//! (`identity.json`, `mesh/rosters/...`) instead of the
//! MyOwnMesh default. This keeps existing users on their current
//! pubkey when no GUI is present.
//! 2. **LLM-owned daemon**: `~/.myownllm/.myownmesh/daemon.sock`.
//! We spawn `myownmesh serve` with
//! `MYOWNMESH_HOME=~/.myownllm/.myownmesh/` so the daemon's
//! `config.json` + `updates/` stay isolated from the LLM's
//! `~/.myownllm/config.json` + `~/.myownllm/updates/`. Identity,
//! rosters, and signed governance states get pre-migrated into
//! the subdir by `mesh::migration::migrate_daemon_state_into_subdir`
//! so existing users keep their pubkey + peer approvals.
//!
//! The choice is sticky for the app lifetime — we don't dynamically
//! switch sockets if the GUI starts mid-session. A future "merge
Expand Down Expand Up @@ -221,7 +223,8 @@ pub enum DaemonMode {
/// lifetime.
Shared,
/// We spawned the daemon ourselves with
/// `MYOWNMESH_HOME=~/.myownllm`. We own the child process.
/// `MYOWNMESH_HOME=~/.myownllm/.myownmesh/`. We own the child
/// process.
OwnLlm,
}

Expand All @@ -246,13 +249,22 @@ impl fmt::Display for SocketAddr {
/// On Windows the namespaced pipe segment is shared between modes —
/// we still spawn our own daemon if probing the existing one fails,
/// but the wire name is the same.
///
/// Per-mode Unix paths mirror the daemon's
/// `MYOWNMESH_HOME/daemon.sock` layout:
/// - `Shared`: `~/.myownmesh/daemon.sock` — the MyOwnMesh GUI's
/// default location.
/// - `OwnLlm`: `~/.myownllm/.myownmesh/daemon.sock` — the LLM
/// spawns its own daemon with `MYOWNMESH_HOME=~/.myownllm/.myownmesh/`
/// so the daemon's `config.json` + `updates/` don't collide with
/// the LLM's. See `mesh/migration.rs` for the why.
fn socket_for_mode(mode: DaemonMode) -> Result<SocketAddr> {
#[cfg(unix)]
{
let home = dirs::home_dir().context("no home dir")?;
let dir = match mode {
DaemonMode::Shared => home.join(".myownmesh"),
DaemonMode::OwnLlm => home.join(".myownllm"),
DaemonMode::OwnLlm => home.join(".myownllm").join(".myownmesh"),
};
Ok(SocketAddr::Path(dir.join("daemon.sock")))
}
Expand Down Expand Up @@ -757,16 +769,18 @@ pub async fn ensure_daemon_running() -> Result<(ControlClient, Option<DaemonChil
);
return Ok((client, None));
}
// 2. Own-LLM mode: existing daemon at ~/.myownllm/daemon.sock?
// 2. Own-LLM mode: existing daemon at
// ~/.myownllm/.myownmesh/daemon.sock?
if let Some(client) = probe(DaemonMode::OwnLlm).await {
eprintln!(
"daemon: attached to existing own-LLM daemon at {}",
client.socket_display()
);
return Ok((client, None));
}
// 3. No daemon up — spawn our own with MYOWNMESH_HOME=~/.myownllm
// so it reads/writes the LLM's existing on-disk layout. We
// 3. No daemon up — spawn our own with
// MYOWNMESH_HOME=~/.myownllm/.myownmesh/ so the daemon's
// config.json + updates/ stay isolated from the LLM's. We
// iterate every viable binary location (`daemon_binary_
// candidates`); a stale or broken file at one location is
// skipped in favour of a working binary at the next. The
Expand All @@ -781,7 +795,17 @@ pub async fn ensure_daemon_running() -> Result<(ControlClient, Option<DaemonChil
`cargo build -p myownmesh`."
));
}
let home = dirs::home_dir().context("no home dir")?.join(".myownllm");
// Daemon's MYOWNMESH_HOME — isolated under `~/.myownllm/.myownmesh/`
// so the daemon's `config.json` + `updates/` don't collide with
// the LLM's. The substrate reads/writes everything (identity,
// rosters, states, config, updates) under this dir; identity +
// rosters + states get pre-migrated into here by
// `mesh::migration::migrate_daemon_state_into_subdir` so existing
// users keep their pubkey + peer approvals.
let home = dirs::home_dir()
.context("no home dir")?
.join(".myownllm")
.join(".myownmesh");

let mut last_err: Option<String> = None;
for bin in &candidates {
Expand Down
Loading
Loading