matrix multi-account: isolate per-account stale-token failure so a bad secondary token doesn't kill the daemon
This commit is contained in:
parent
c27f915e47
commit
e5cd4e58f1
2 changed files with 44 additions and 26 deletions
|
|
@ -64,6 +64,7 @@ pub async fn build_and_restore(
|
||||||
homeserver: &str,
|
homeserver: &str,
|
||||||
token_file: &Path,
|
token_file: &Path,
|
||||||
state_dir: &Path,
|
state_dir: &Path,
|
||||||
|
is_primary: bool,
|
||||||
) -> Result<Client> {
|
) -> Result<Client> {
|
||||||
let token = fs::read_to_string(token_file)
|
let token = fs::read_to_string(token_file)
|
||||||
.await
|
.await
|
||||||
|
|
@ -80,31 +81,42 @@ pub async fn build_and_restore(
|
||||||
let msg = format!("{e:#}");
|
let msg = format!("{e:#}");
|
||||||
if msg.contains("M_UNKNOWN_TOKEN") {
|
if msg.contains("M_UNKNOWN_TOKEN") {
|
||||||
// Homeserver rejected our token — stale session after a homeserver
|
// Homeserver rejected our token — stale session after a homeserver
|
||||||
// state wipe or token expiry. Delete the token file (and the
|
// state wipe or token expiry. Delete the stale token file so we
|
||||||
// matrix-sdk sqlite state keyed to the now-invalid session) so
|
// don't loop on it. The rest of the handling depends on whether
|
||||||
// hive-c0re's periodic `ensure_all` sweep re-provisions the account.
|
// this is the primary (hive-internal) account or a secondary one,
|
||||||
// Exit 0: systemd's Restart=on-failure must not loop us here; the
|
// because a single bad secondary token must NOT take down the
|
||||||
// systemd.paths watcher restarts us once the new token file appears.
|
// whole daemon (and with it every healthy account).
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
path = %token_file.display(),
|
path = %token_file.display(),
|
||||||
"matrix token rejected (M_UNKNOWN_TOKEN); deleting stale token + \
|
is_primary,
|
||||||
sdk state for re-provisioning"
|
"matrix token rejected (M_UNKNOWN_TOKEN); removing stale token"
|
||||||
);
|
);
|
||||||
let _ = fs::remove_file(token_file).await;
|
let _ = fs::remove_file(token_file).await;
|
||||||
if let Err(re) = fs::remove_dir_all(state_dir).await {
|
if is_primary {
|
||||||
tracing::warn!(
|
// Primary: also drop the matrix-sdk sqlite state keyed to the
|
||||||
path = %state_dir.display(),
|
// now-invalid session, then exit 0 so hive-c0re's `ensure_all`
|
||||||
err = %re,
|
// re-provisions the account and the systemd.paths watcher
|
||||||
"failed to remove sdk state dir; next startup may fail with stale state"
|
// restarts us once the fresh token file appears. Exit 0 (not
|
||||||
);
|
// Err) keeps systemd's Restart=on-failure from looping; the
|
||||||
|
// call site is before any tasks are spawned so there's
|
||||||
|
// nothing to clean up.
|
||||||
|
if let Err(re) = fs::remove_dir_all(state_dir).await {
|
||||||
|
tracing::warn!(
|
||||||
|
path = %state_dir.display(),
|
||||||
|
err = %re,
|
||||||
|
"failed to remove sdk state dir; next startup may fail with stale state"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
std::process::exit(0);
|
||||||
}
|
}
|
||||||
// Exit 0 rather than returning Err: `hive-matrix-daemon` is a
|
// Secondary: token removed (so it's cleanly skipped next boot
|
||||||
// single-purpose process binary; the call site is before any
|
// rather than re-erroring); leave the sdk state in place in case
|
||||||
// tasks are spawned so there are no resources to clean up.
|
// the operator re-provisions a fresh token for the same device.
|
||||||
// Using exit(0) (not Err) keeps systemd's Restart=on-failure
|
// Return Err so the caller logs + skips this one account and the
|
||||||
// from looping — the systemd.paths watcher re-launches us
|
// daemon keeps serving the primary and any other healthy account.
|
||||||
// once hive-c0re writes a fresh token file.
|
return Err(anyhow!(
|
||||||
std::process::exit(0);
|
"matrix token rejected (M_UNKNOWN_TOKEN); removed stale token, skipping account"
|
||||||
|
));
|
||||||
}
|
}
|
||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,12 @@
|
||||||
//! provisions it. A SECONDARY account missing its token is skipped (the
|
//! provisions it. A SECONDARY account missing its token is skipped (the
|
||||||
//! daemon still serves the others).
|
//! daemon still serves the others).
|
||||||
//!
|
//!
|
||||||
//! Stale-token recovery: handled in `client::build_and_restore` — see
|
//! Stale-token recovery (`M_UNKNOWN_TOKEN`): handled in
|
||||||
//! that module for the `M_UNKNOWN_TOKEN` detection + cleanup flow.
|
//! `client::build_and_restore`, and it mirrors the missing-token policy
|
||||||
|
//! above — a rejected PRIMARY token drops the stale token + sdk state and
|
||||||
|
//! exits 0 for systemd re-provisioning, while a rejected SECONDARY token
|
||||||
|
//! is removed and that one account is skipped so the daemon keeps serving
|
||||||
|
//! the primary and any other healthy account.
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
|
@ -65,7 +69,7 @@ async fn main() -> Result<()> {
|
||||||
// Account-tag the wakes only in multi-account mode so single-
|
// Account-tag the wakes only in multi-account mode so single-
|
||||||
// account wake bodies stay byte-identical to the legacy format.
|
// account wake bodies stay byte-identical to the legacy format.
|
||||||
let tag = multi.then(|| cfg.name.clone());
|
let tag = multi.then(|| cfg.name.clone());
|
||||||
match bring_up_account(&cfg, &hyperhive_socket, tag).await {
|
match bring_up_account(&cfg, &hyperhive_socket, tag, is_primary).await {
|
||||||
Ok(Some((client, sync_loop))) => {
|
Ok(Some((client, sync_loop))) => {
|
||||||
registry.insert(cfg.name, client);
|
registry.insert(cfg.name, client);
|
||||||
sync_loops.push(sync_loop);
|
sync_loops.push(sync_loop);
|
||||||
|
|
@ -137,6 +141,7 @@ async fn bring_up_account(
|
||||||
cfg: &AccountCfg,
|
cfg: &AccountCfg,
|
||||||
hyperhive_socket: &std::path::Path,
|
hyperhive_socket: &std::path::Path,
|
||||||
tag: Option<String>,
|
tag: Option<String>,
|
||||||
|
is_primary: bool,
|
||||||
) -> Result<Option<(Client, SyncLoop)>> {
|
) -> Result<Option<(Client, SyncLoop)>> {
|
||||||
let homeserver = cfg.homeserver();
|
let homeserver = cfg.homeserver();
|
||||||
if !tokio::fs::try_exists(&cfg.token_file)
|
if !tokio::fs::try_exists(&cfg.token_file)
|
||||||
|
|
@ -152,9 +157,10 @@ async fn bring_up_account(
|
||||||
state_dir = %cfg.state_dir.display(),
|
state_dir = %cfg.state_dir.display(),
|
||||||
"bringing up matrix account"
|
"bringing up matrix account"
|
||||||
);
|
);
|
||||||
let client = client::build_and_restore(&homeserver, &cfg.token_file, &cfg.state_dir)
|
let client =
|
||||||
.await
|
client::build_and_restore(&homeserver, &cfg.token_file, &cfg.state_dir, is_primary)
|
||||||
.with_context(|| format!("build matrix client for account {}", cfg.name))?;
|
.await
|
||||||
|
.with_context(|| format!("build matrix client for account {}", cfg.name))?;
|
||||||
timeline::install_message_handler(&client, hyperhive_socket.to_path_buf(), tag.clone());
|
timeline::install_message_handler(&client, hyperhive_socket.to_path_buf(), tag.clone());
|
||||||
|
|
||||||
let sync_client = client.clone();
|
let sync_client = client.clone();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue