feat(#2035): auto-discover dashboard-provisioned matrix accounts via token+homeserver sidecar
This commit is contained in:
parent
cc73bc7cd0
commit
3b0a914487
6 changed files with 112 additions and 11 deletions
|
|
@ -78,14 +78,13 @@ pub fn configured() -> anyhow::Result<Vec<AccountCfg>> {
|
|||
state_dir: paths::matrix_state_dir(),
|
||||
homeserver: None,
|
||||
};
|
||||
let Some(raw) = std::env::var_os("HIVE_MATRIX_ACCOUNTS") else {
|
||||
return Ok(vec![hive]);
|
||||
};
|
||||
let extras: Vec<AccountCfg> = serde_json::from_str(&raw.to_string_lossy())
|
||||
.map_err(|e| anyhow::anyhow!("parse HIVE_MATRIX_ACCOUNTS as JSON array: {e}"))?;
|
||||
let mut accounts = Vec::with_capacity(extras.len() + 1);
|
||||
accounts.push(hive);
|
||||
accounts.extend(extras);
|
||||
let mut accounts = vec![hive];
|
||||
if let Some(raw) = std::env::var_os("HIVE_MATRIX_ACCOUNTS") {
|
||||
let extras: Vec<AccountCfg> = serde_json::from_str(&raw.to_string_lossy())
|
||||
.map_err(|e| anyhow::anyhow!("parse HIVE_MATRIX_ACCOUNTS as JSON array: {e}"))?;
|
||||
accounts.extend(extras);
|
||||
}
|
||||
// Reject duplicate names among the *configured* set (hive + extras).
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
for a in &accounts {
|
||||
if !seen.insert(a.name.as_str()) {
|
||||
|
|
@ -95,9 +94,84 @@ pub fn configured() -> anyhow::Result<Vec<AccountCfg>> {
|
|||
);
|
||||
}
|
||||
}
|
||||
// Append dashboard-provisioned accounts (a `matrix-token-<name>` file +
|
||||
// its `matrix-account-<name>.json` homeserver sidecar) that aren't
|
||||
// already declared in config, so an account logged in via the dashboard
|
||||
// form works without a `matrixAccounts` edit + rebuild. Explicit config
|
||||
// wins on name collision.
|
||||
let configured: std::collections::HashSet<String> =
|
||||
accounts.iter().map(|a| a.name.clone()).collect();
|
||||
for disc in discover_token_accounts() {
|
||||
if !configured.contains(&disc.name) {
|
||||
accounts.push(disc);
|
||||
}
|
||||
}
|
||||
Ok(accounts)
|
||||
}
|
||||
|
||||
/// Scan the agent state dir for extra matrix accounts provisioned via the
|
||||
/// dashboard login form: each is a `matrix-token-<name>` file plus a
|
||||
/// `matrix-account-<name>.json` sidecar carrying the homeserver. Returns one
|
||||
/// [`AccountCfg`] per discovered account that has BOTH files — a token
|
||||
/// without a sidecar is skipped, because the homeserver is then unknown and
|
||||
/// defaulting to the hive homeserver would be wrong for an external account.
|
||||
/// Best-effort: an unreadable dir or malformed sidecar yields fewer
|
||||
/// accounts, never an error — explicit `HIVE_MATRIX_ACCOUNTS` config stays
|
||||
/// authoritative.
|
||||
fn discover_token_accounts() -> Vec<AccountCfg> {
|
||||
let token_path = paths::token_file();
|
||||
let Some(state_dir) = token_path.parent() else {
|
||||
return Vec::new();
|
||||
};
|
||||
let Ok(rd) = std::fs::read_dir(state_dir) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let mut out = Vec::new();
|
||||
for entry in rd.flatten() {
|
||||
let fname = entry.file_name();
|
||||
let Some(fname) = fname.to_str() else {
|
||||
continue;
|
||||
};
|
||||
// `matrix-token` (no suffix) is the hive account, handled separately;
|
||||
// only `matrix-token-<name>` files are extra accounts.
|
||||
let Some(name) = fname.strip_prefix("matrix-token-") else {
|
||||
continue;
|
||||
};
|
||||
if name.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let sidecar = state_dir.join(format!("matrix-account-{name}.json"));
|
||||
let Some(homeserver) = read_account_homeserver(&sidecar) else {
|
||||
tracing::warn!(
|
||||
account = name,
|
||||
sidecar = %sidecar.display(),
|
||||
"matrix: discovered token but no homeserver sidecar; skipping account \
|
||||
(re-login via the dashboard to write it)"
|
||||
);
|
||||
continue;
|
||||
};
|
||||
out.push(AccountCfg {
|
||||
name: name.to_owned(),
|
||||
token_file: entry.path(),
|
||||
state_dir: state_dir.join(format!("matrix-sdk-state-{name}")),
|
||||
homeserver: Some(homeserver),
|
||||
});
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Read `{"homeserver": "<url>"}` from a sidecar file. `None` when the file
|
||||
/// is missing, unreadable, not valid JSON, or the `homeserver` field is
|
||||
/// absent / empty.
|
||||
fn read_account_homeserver(path: &Path) -> Option<String> {
|
||||
let raw = std::fs::read_to_string(path).ok()?;
|
||||
let json: serde_json::Value = serde_json::from_str(&raw).ok()?;
|
||||
json.get("homeserver")?
|
||||
.as_str()
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(ToOwned::to_owned)
|
||||
}
|
||||
|
||||
/// Live status of one matrix account, as reported by [`Registry::list`]
|
||||
/// (the `list_accounts` daemon op). Only accounts that successfully
|
||||
/// restored a session appear, so `live` is always `true` today; the
|
||||
|
|
|
|||
Loading…
Reference in a new issue