fix(#1185): move matrix-password outside purgeable agent_state_root
The password file was stored at agent_notes_dir/matrix-password which lives inside agent_state_root — wiped by destroy --purge. On re-spawn with the same agent name, the matrix user still exists in the homeserver but the stored password is gone, making re-login impossible. Move password to /var/lib/hyperhive/matrix-creds/<name>-password which is not deleted by purge. On re-spawn, ensure_user_for finds M_USER_IN_USE, reads the preserved password, re-logins, and writes a fresh token. Also: - add one-time migration that moves existing passwords from the old path to the new location on first access after upgrade - remove chown_to_agent on the password file (it is now host-only, not inside the agent bind-mount tree) - fix the error message to give actionable recovery steps instead of suggesting hivectl matrix create-user --password which is rejected for agent accounts
This commit is contained in:
parent
181535650b
commit
47e9c1cc1b
1 changed files with 53 additions and 6 deletions
|
|
@ -45,10 +45,27 @@ fn token_path(name: &str) -> PathBuf {
|
|||
Coordinator::agent_notes_dir(name).join("matrix-token")
|
||||
}
|
||||
|
||||
/// Password file alongside the token. Persisted so we can fall back to
|
||||
/// `m.login.password` if the token file is deleted but the homeserver
|
||||
/// account still exists. Mode 0600, same dir as the token.
|
||||
/// Password file for the agent's matrix account. Stored OUTSIDE the
|
||||
/// purgeable `agent_state_root` tree so it survives `destroy --purge`
|
||||
/// and allows re-login recovery when the same agent name is re-spawned.
|
||||
///
|
||||
/// Path: `/var/lib/hyperhive/matrix-creds/<name>-password`
|
||||
///
|
||||
/// The token file lives inside the agent's bind-mounted state dir (under
|
||||
/// `agent_notes_dir`) so the agent container can read it; the password
|
||||
/// file is host-side only (agents never log in by password — they use
|
||||
/// the access token exclusively) and belongs with other hive-c0re
|
||||
/// credential state, not inside the purgeable per-agent tree.
|
||||
fn password_path(name: &str) -> PathBuf {
|
||||
PathBuf::from("/var/lib/hyperhive/matrix-creds").join(format!("{name}-password"))
|
||||
}
|
||||
|
||||
/// Legacy password path (inside the old purgeable `agent_notes_dir`).
|
||||
/// Used only during the one-time migration in [`ensure_user_for`] to
|
||||
/// move credentials from old deployments to the new location. Safe to
|
||||
/// call after `destroy --purge` — the path will simply not exist and
|
||||
/// the migration is a no-op.
|
||||
fn legacy_password_path(name: &str) -> PathBuf {
|
||||
Coordinator::agent_notes_dir(name).join("matrix-password")
|
||||
}
|
||||
|
||||
|
|
@ -276,6 +293,29 @@ pub async fn ensure_user_for(
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
// One-time migration: move the password from the old location inside
|
||||
// agent_notes_dir (purgeable) to the new location outside it.
|
||||
let new_pw_path = password_path(name);
|
||||
let old_pw_path = legacy_password_path(name);
|
||||
if !new_pw_path.exists() && old_pw_path.exists() {
|
||||
if let Some(parent) = new_pw_path.parent() {
|
||||
std::fs::create_dir_all(parent).ok();
|
||||
}
|
||||
if let Err(e) = std::fs::rename(&old_pw_path, &new_pw_path) {
|
||||
// Rename across filesystems or read-only src — copy + delete.
|
||||
if let Ok(content) = std::fs::read(&old_pw_path) {
|
||||
if std::fs::write(&new_pw_path, &content).is_ok() {
|
||||
let _ = std::fs::remove_file(&old_pw_path);
|
||||
tracing::info!(%name, "matrix: migrated password file to non-purgeable location");
|
||||
}
|
||||
} else {
|
||||
tracing::warn!(%name, error = ?e, "matrix: password migration failed (old path stays)");
|
||||
}
|
||||
} else {
|
||||
tracing::info!(%name, "matrix: migrated password file to non-purgeable location");
|
||||
}
|
||||
}
|
||||
|
||||
let password = random_password()?;
|
||||
let access_token = match register_user(client, name, register_token, &password).await {
|
||||
Ok(token) => {
|
||||
|
|
@ -289,7 +329,6 @@ pub async fn ensure_user_for(
|
|||
tracing::warn!(%name, error = ?e, "matrix: failed to persist password (token still saved)");
|
||||
} else {
|
||||
let _ = std::fs::set_permissions(&pw_path, std::fs::Permissions::from_mode(0o600));
|
||||
crate::lifecycle::chown_to_agent(name, &pw_path, "matrix");
|
||||
}
|
||||
token
|
||||
}
|
||||
|
|
@ -303,8 +342,16 @@ pub async fn ensure_user_for(
|
|||
.filter(|s| !s.is_empty())
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"matrix: user {name} already exists in homeserver but matrix-password \
|
||||
is missing — manual recovery: hivectl matrix create-user {name} --password <pw>"
|
||||
"matrix: user {name} already exists in homeserver but the stored \
|
||||
password is missing — manual recovery:\n\
|
||||
1. reset the password via the matrix admin API:\n\
|
||||
curl -X PUT http://localhost:8008/_synapse/admin/v2/users/@{name}:<server> \\\n\
|
||||
-H 'Authorization: Bearer <admin-token>' \\\n\
|
||||
-d '{{\"password\": \"<new-pw>\"}}'\n\
|
||||
2. write the new password to {pw_path}:\n\
|
||||
echo '<new-pw>' > {pw_path} && chmod 600 {pw_path}\n\
|
||||
3. run: hivectl matrix create-user {name}",
|
||||
pw_path = pw_path.display()
|
||||
)
|
||||
})?;
|
||||
login_user(client, name, &stored).await.with_context(|| {
|
||||
|
|
|
|||
Loading…
Reference in a new issue