Watch
0
0
Fork
You've already forked hyperhive
0
Commit graph hyperhive/hive-matrix-mcp/src/client.rs
Author SHA1 Message Date
atlas
7b1435eaea hive-matrix-mcp: bound each secondary retry, warn once per outage
Three follow-ups to argus's review of #5237:

- A down secondary's retry runs `restore` under RESTORE_TIMEOUT (2 min).
  Tries run one per tick on one future, so a restore that never resolved
  held back every other down account's retries for good; a try that
  outlasts the timeout now counts as a failure like any other.
- `client::build_and_restore` warns about a rejected token only for the
  primary, which exits right after. For a secondary it logs at debug: the
  retry loop already warns once per outage (`DownAccounts::failed` is news
  on the first failure and on a changed reason, and an account that came
  up starts afresh), so the extra warn repeated on every try.
- `a_secondary_that_comes_up_late_does_not_wipe_the_todos` runs the late
  bring-up in a child process of the test binary with HIVE_AGENT_SOCKET
  pointed at the test's listener. In-process the variable was unset, so a
  wipe placed back in the bring-up path was a no-op and the test passed.

New tests: a_second_rejection_in_a_row_does_not_warn_again (captured logs, alone
in a child process since tracing caches callsite interest process-wide;
a stub whoami answering M_UNKNOWN_TOKEN, the real build_and_restore),
a_failure_after_the_account_came_up_is_news_again, and
an_account_whose_restore_hangs_does_not_hold_back_the_others (paused time).
2026-10-15 02:54:09 +02:00
atlas
53049962b8 hive-matrix-mcp: quieter retries, unique snapshot tmp file, accurate comments
Audit follow-ups on the secondary-account retry.

The accounts snapshot is written by the heartbeat task and, now, by the
retry loop. Both used one fixed tmp path, so concurrent writers could
clobber it or fail the rename. Each write now names its own tmp file
(pid + counter); a test hammers it from four threads.

A permanently bad secondary logged three warnings per attempt, every 16
minutes, for as long as the daemon ran. DownAccounts::failed now reports
whether a failure is news (first, or a different reason from the last);
only that is a warning, repeats are debug. The two per-attempt warnings
in client.rs and credential.rs are covered by that line, which names the
reason, so the store-token one drops to debug.

The comments in client.rs and a credential.rs test, and persistence.md,
still described the old skip-until-restart behaviour or implied that a
store-listed account without a credential is retried here; that case is
handled by the linked-name set changing, not by this loop.
2026-10-14 16:20:04 +02:00
atlas
7f26238c87 hive-matrix-mcp: retry a secondary account that failed to come up
The daemon's two-minute refresh compared the store's linked account
names with the names it booted with. A secondary that was named but did
not come up - no token at bring-up, a token the homeserver rejected
(M_UNKNOWN_TOKEN), or a homeserver still down after the ~52 s boot
retries - kept its name in the set, so nothing ever noticed it again:
it stayed down until the daemon restarted for some other reason, and
an operator re-linking a rejected account changed no name.

Each such secondary is now tried again on the same two-minute period,
one per tick, after one refresh and then doubling waits capped at
sixteen minutes. One that comes up joins the registry (now behind a
lock) and its sync loop joins the running set; the primary and the
other accounts are not restarted. The store is still only read.

The startup todo wipe moves out of bring_up_account to run once before
any sync loop: run per account, a late secondary would have cleared the
todos the running accounts' sweeps had already deduped.

Part of #5017 (slice M).
2026-10-14 16:20:04 +02:00
atlas
aece1c26d0 hive-matrix-mcp: never enable recovery over secret storage it did not create
`recovery().enable()` refuses only when a key backup exists. It then makes
a new secret-storage key and sets it as the account's default regardless
of the default already there, so an account with secret storage but no
backup (set up in another client, backup since deleted) would have its
default key replaced and its other clients' recovery key locked out.

`recover_or_enable` now asks `secret_storage().is_enabled()` before
`enable` and returns an error instead, which `ensure_recovery` logs at
WARN with the account and the passphrase's store path. The decision is
behind a small trait over the matrix-sdk calls so a fake can pin it: with
no backup and secret storage present, neither `enable` nor `recover` runs.

Review: argus on #5188.
2026-10-14 02:09:57 +02:00
atlas
45568f6e60 matrix: back up agents' room keys with a controller-minted recovery passphrase
An agent's matrix encryption keys lived only in its local crypto store, so
`destroy --purge` or a primary-account token reset lost every encrypted
room's history with no way back (#5022).

swarm-controller now mints a random passphrase (32 bytes from the kernel's
CSPRNG, base64url) for every account listed under each agent's `matrix/`
directory, at `swarm/agents/<agent>/matrix-recovery/<account>`, in a
five-minute pass. The write is a cas=0 create after a read; a path that
holds anything is never replaced, and a create that loses a race counts as
done. The controller's existing `data/swarm/agents/*` grant covers the
write and the agent's own `agents/<agent>/*` read grant covers the read, so
no policy changes.

hive-matrix-daemon reads the passphrase when it brings an account up, after
cross-signing: with no backup on the homeserver it enables recovery with it
(backup + secret storage); with one there it recovers, which imports the
backup key and the self-signing key, and room keys then download through
`BackupDownloadStrategy::OneShot`. No passphrase yet logs at INFO and the
account comes up without backup; any failure logs WARN and never stops the
daemon.

Recovery runs after `ensure_cross_signing`, not before: on a new account
cross-signing must exist locally for `enable` to export it into secret
storage, and on an existing identity `bootstrap_cross_signing_if_needed` is
a no-op, leaving `recover` to import and sign.

Part of #5022 (S1+S2, with the S4 docs).
2026-10-14 02:09:57 +02:00
atlas
ab153bda2f hive-matrix-mcp: read the main account's token from the store too
The daemon reads each account's token from `swarm/agents/<agent>/matrix/`
as the agent itself, inside its own container, and falls back to the file
only when the store has none. This is #4519's read, without its `main`
carve-out: the swarm now mints `main` there and no hive writes the file.

The daemon unit gets the agent's store identity, spelled the way
forge-token.nix spells it. A timer re-starts it while it is down: a token
the swarm mints or replaces in the store changes no file, so the path
watcher never fires for it, and a daemon that exited on a replaced token
would otherwise stay down until the container restarts.
2026-09-25 08:31:01 +02:00
atlas
7ee7080b21 matrix: remove the registration token
Nothing reads it any more: hive-c0re creates accounts as the hive's
appservice, so the mint, the host file, the bind mount, the
`LoadCredential` entry and tuwunel's `registration_token_file` all go.

⚠️ `allow_registration` has to go to `false` in the same change, and not
as hardening. tuwunel refuses to START when registration is allowed with
no token configured — it demands
`yes_i_am_very_very_sure_…_open_registration_…` instead — so dropping the
token and leaving the flag true is not a lax homeserver, it is one that
does not boot. The flag is checked only for requests arriving without an
appservice token, so hive-c0re provisions exactly as before and everyone
else is refused outright.

The swarm secret store keeps its role, repointed at the credential that
replaced the token (`swarm/hives/<hive>/matrix/appservice-token`). Its
unit now also re-runs hive-matrix's own registration renderer after
writing the file: the token is half an agreement, and a registration
still naming the previous value authenticates nobody. The renderer is
shared through an internal option rather than copied, so the
registration's shape has one home.

Both spellings of `registrationTokenFile` become
`mkRemovedOptionModule` with a message naming what replaced them. A hive
that never set the option — the default — is unaffected; one that pinned
it fails to evaluate with instructions instead of a silent no-op.

An upgraded hive needs no intervention: the activation script has both
halves in place before the homeserver restarts, existing agents keep the
tokens their devices already hold, and the old token file is left on
disk read by nothing. docs/integrations/matrix.md spells the path out.

Refs #4402
2026-09-15 19:58:10 +02:00
damocles
0d40583991 feat(#2482): bootstrap cross-signing so agent matrix devices show verified 2026-07-15 18:17:40 +02:00
atlas
317e545d7c fix: wrap M_UNKNOWN_TOKEN in backticks in doc comment (clippy::doc_markdown) 2026-07-09 00:47:32 +02:00
atlas
a1cd50610a fix(#2095): retry secondary matrix account bring-up on transient failure
On a transient error (network blip, DNS not ready, homeserver 5xx) a
secondary account's bring-up was immediately skipped for the entire
daemon lifetime. This bit janet's catgirl account repeatedly when the
host DNS resolver wasn't ready at daemon start — the account would
silently disappear until the next restart.

Add a PermanentBringUpError sentinel in client.rs so callers can
distinguish M_UNKNOWN_TOKEN (stale/expired token — permanent, don't
retry) from transient network/homeserver errors.

In main.rs, replace the immediate skip with a bounded retry loop for
secondary accounts: up to 4 attempts with 2s/5s/15s/30s backoffs
(~52s total wait). On a transient error the daemon now stays alive
serving the primary and any other healthy accounts while the failing
secondary gets another chance. Permanent failures (PermanentBringUpError)
still skip immediately with no retry.

The primary account keep its existing behaviour: fatal on non-permanent
error so systemd restarts the whole daemon (systemd is the right retry
mechanism for primary bring-up failure).
2026-07-09 00:47:32 +02:00
damocles
db3249a390 fix(#2050): use stable fnv-1a for avatar idempotency hash 2026-06-28 01:38:21 +02:00
damocles
c99fa714d9 feat(#2050): set matrix avatar in the daemon over the live client 2026-06-28 01:38:21 +02:00
damocles
e5cd4e58f1 matrix multi-account: isolate per-account stale-token failure so a bad secondary token doesn't kill the daemon 2026-06-15 23:13:45 +02:00
damocles
f51402a867 feat(#551): enable matrix e2ee -- add e2e-encryption feature, wire EncryptionSettings, flip tuwunel allow_encryption 2026-06-10 19:12:36 +02:00
damocles
fb726197ea fix(#1375): clean up pedantic warnings and re-enable -D warnings without pedantic bypass 2026-06-05 16:55:09 +02:00
damocles
186ee430b5 docs: comment std::process::exit(0) intent in M_UNKNOWN_TOKEN recovery 2026-06-04 12:12:20 +02:00
damocles
92ed989f8c refactor: move M_UNKNOWN_TOKEN recovery into client::build_and_restore 2026-06-04 12:12:20 +02:00
atlas
4bff450343 feat(gateway): hivectl gateway user management + fix htpasswdFile assertion
Add `hivectl gateway {create-user,delete-user,list-users}` subcommands for
managing htpasswd files used by gateway Basic auth. Pure Rust bcrypt
(cost 12, $2y$ prefix nginx accepts). No external htpasswd binary required.

Also fix the NixOS module assertion: `cfg.auth ? htpasswdFile` is always
true in the module system (declared options always exist as keys); switch
to `nullOr path; default = null` + `!= null` check so the assertion
actually fires with a useful error when enable=true but no file is set.
Guard bind-mount and nginx config against null to prevent eval errors.

Update docs/gateway.md to show hivectl commands instead of raw htpasswd.
2026-06-01 23:25:28 +02:00
damocles
e6c53045ad matrix: hive-matrix-mcp crate (daemon+stdio bridge) + harness wiring (#548 phase 3) 2026-05-29 20:51:01 +02:00