matrix multi-account: isolate per-account stale-token failure so a bad secondary token doesn't kill the daemon

This commit is contained in:
damocles 2026-06-15 23:13:45 +02:00
commit e5cd4e58f1
2 changed files with 44 additions and 26 deletions

View file

@ -64,6 +64,7 @@ pub async fn build_and_restore(
homeserver: &str,
token_file: &Path,
state_dir: &Path,
is_primary: bool,
) -> Result<Client> {
let token = fs::read_to_string(token_file)
.await
@ -80,31 +81,42 @@ pub async fn build_and_restore(
let msg = format!("{e:#}");
if msg.contains("M_UNKNOWN_TOKEN") {
// Homeserver rejected our token — stale session after a homeserver
// state wipe or token expiry. Delete the token file (and the
// matrix-sdk sqlite state keyed to the now-invalid session) so
// hive-c0re's periodic `ensure_all` sweep re-provisions the account.
// Exit 0: systemd's Restart=on-failure must not loop us here; the
// systemd.paths watcher restarts us once the new token file appears.
// state wipe or token expiry. Delete the stale token file so we
// don't loop on it. The rest of the handling depends on whether
// this is the primary (hive-internal) account or a secondary one,
// because a single bad secondary token must NOT take down the
// whole daemon (and with it every healthy account).
tracing::warn!(
path = %token_file.display(),
"matrix token rejected (M_UNKNOWN_TOKEN); deleting stale token + \
sdk state for re-provisioning"
is_primary,
"matrix token rejected (M_UNKNOWN_TOKEN); removing stale token"
);
let _ = fs::remove_file(token_file).await;
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"
);
if is_primary {
// Primary: also drop the matrix-sdk sqlite state keyed to the
// now-invalid session, then exit 0 so hive-c0re's `ensure_all`
// re-provisions the account and the systemd.paths watcher
// 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
// single-purpose process binary; the call site is before any
// tasks are spawned so there are no resources to clean up.
// Using exit(0) (not Err) keeps systemd's Restart=on-failure
// from looping — the systemd.paths watcher re-launches us
// once hive-c0re writes a fresh token file.
std::process::exit(0);
// Secondary: token removed (so it's cleanly skipped next boot
// rather than re-erroring); leave the sdk state in place in case
// the operator re-provisions a fresh token for the same device.
// Return Err so the caller logs + skips this one account and the
// daemon keeps serving the primary and any other healthy account.
return Err(anyhow!(
"matrix token rejected (M_UNKNOWN_TOKEN); removed stale token, skipping account"
));
}
return Err(e);
}