Commit graph hyperhive/hive-c0re/src/matrix.rs
Author SHA1 Message Date
damocles
187c364feb refactor(#2285): repoint all hive-c0re host-path consumers to paths.rs 2026-07-10 20:32:03 +02:00
damocles
3b0a914487 feat(#2035): auto-discover dashboard-provisioned matrix accounts via token+homeserver sidecar 2026-06-27 00:30:04 +02:00
damocles
9c480daf0a dashboard: POST /matrix-account-login + account-aware hive-priv matrix-token write (BE-2) 2026-06-16 11:18:18 +02:00
damocles
d090df3c36 matrix: skip room invite when the user is already invited or joined 2026-06-13 12:17:16 +02:00
atlas
a5ccb6048a fix(matrix): pin room_version=10 on the hive chat room createRoom
The restricted join rule needs room version >= 8. Pin it explicitly so a
future homeserver-default change can't silently invalidate the restricted
rule (which would fall back to invite-only and break the operator-joins-
from-the-Space path). Per review on the PR.
2026-06-06 07:59:24 +02:00
atlas
fe17b5f8a7 feat(matrix): auto-create a hive chat room as a child of the hive Space
The hive Space was created empty — joining it surfaced no rooms because
Matrix doesn't auto-join a Space's children. Provision a default
"hive-chat" room on the matrix sweep, wire it bidirectionally to the
Space (m.space.child on the Space, m.space.parent on the room), and
invite @hive + every agent. The room uses a restricted join rule
allowing any Space member to join, so the operator (a Space member) can
join it from the Space hierarchy without an explicit invite.

Idempotent, mirroring ensure_hive_space: persisted chat-room-id wins,
else rediscover a non-space room named hive-chat, else createRoom. The
space-child link is re-applied each sweep (idempotent PUT) so a
recovered room reconverges its hierarchy link. Room id persisted to
matrix/chat-room-id (0600, survives destroy --purge).
2026-06-06 07:59:24 +02:00
damocles
14c7b0d406 feat: group host-side /var/lib/hyperhive state into db/ forge/ matrix/ run/ subdirs with startup migration 2026-06-05 23:01:47 +02:00
damocles
e7a68472fd fix: reject bare --room value in hivectl matrix invite with a clear hint 2026-06-05 20:51:31 +02:00
damocles
e8d5eee659 feat: hivectl matrix invite — add a user to the hive Space or a room (closes #1402) 2026-06-05 20:51:31 +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
f201f04d4e fix(#1329): restart hive-matrix-daemon after token write so new credential is picked up immediately 2026-06-05 15:30:21 +02:00
atlas
d043b1ed4e fix: rediscover hive Space by hardcoded name, not a room alias
Per mara's review: drop the #hive:<server> room alias (special chars) and
rediscover the canonical Space by its hardcoded plain name instead.

ensure_hive_space dedup is now:
  1. room-id file present -> reuse it
  2. else scan the admin's joined rooms for the m.space named HIVE_SPACE_NAME
     ('hive') and adopt the first match (re-persisting the file) -> recovers
     the existing space after a state wipe instead of creating a duplicate
  3. else createRoom (plain name, no alias)

find_space_by_name walks /joined_rooms and checks each room's m.room.create
type == m.space and m.room.name == 'hive'. No alias, no special-char anchor.
server_name is no longer needed by ensure_hive_space (dropped the param).
2026-06-05 13:17:16 +02:00
atlas
134a40a5e2 fix: anchor hive Space to canonical #hive alias for dedup
ensure_hive_space relied solely on the persisted room-id file. If that file
is ever lost (a full /var/lib/hyperhive wipe), the next sweep blind-creates a
new m.space — the homeserver keeps the old one, so duplicate hive spaces
accumulate (observed: multiple 'hive'/'pr1ma' rooms on the live instance).

Anchor the Space to a stable canonical alias #hive:<server>:
- fast path (room-id file present): reuse it and heal the alias mapping so
  it keeps pointing at the canonical room
- no file: resolve #hive:<server> and adopt the existing room if present,
  re-persisting the file — recovers the space after a wipe instead of
  duplicating it
- only create (with the alias) when neither yields a room

server_name is now discovered before ensure_hive_space in ensure_all and
threaded through (the alias needs it). Existing deployments heal the alias
onto their current space on the next sweep; no new room is created when the
file is present.
2026-06-05 13:17:16 +02:00
atlas
20c5039156 style: treefmt main — fix CI formatting check
Full `nix flake check` (CI) runs the treefmt formatting derivation. While the
hive-ci runner was offline (#1221), PRs merged without it, leaving 5 files
unformatted: hive-ag3nt/src/web_ui.rs, hive-c0re/src/bin/hivectl.rs,
hive-c0re/src/knowledge.rs, hive-c0re/src/matrix.rs,
hive-forge/src/verbs/attachment_get.rs. `nix fmt` output, pure formatting.
2026-06-05 13:09:56 +02:00
atlas
3858740488 refactor: drop speculative prose markers, keep only backtick extraction
Per operator review on the PR: the prose marker list ('new password is:',
' to:', 'changed to:', etc.) was speculative — built from a misread
screenshot, not a real reply. The conduit admin bot always code-spans the
password, so the backtick anchor is the verified, complete format. Removing
the markers leaves a ~15-line function that's honest about what it parses.

If a non-code-span format ever appears, extract_new_password returns None and
the diagnostic logging in admin_room_send_and_poll records the raw body, so a
real format change is visible — far better than a speculative marker silently
mis-parsing it. Tests now cover the live format, symbol passwords, the
code-spanned-user-id error guard, and the no-codespan / non-password cases.
2026-06-05 01:53:05 +02:00
atlas
2d8a68e4d1 fix: extract matrix password from backtick code span (real conduit format)
The live conduit admin-room reply (observed directly in #admins) is:

  Successfully reset the password for user @x:server: `<password>`

The delimiter is ': ' after the user id, NOT ' to:' — so the ' to: ' marker
added earlier never matched the real format, and extract_new_password returned
None for every reply, timing out auto-recovery for every agent.

conduit always renders the password as a backtick code span, so anchor on
that directly: take the content of the first backtick pair when the message
is a password-reset success. Guard against a code-spanned matrix user id in
an error message (a real password has no whitespace and isn't @localpart:server).
Prose markers stay as a fallback for hypothetical non-code-span builds.

Tests cover the exact live format and the code-spanned-user-id error case.
2026-06-05 01:49:31 +02:00
atlas
93d28ce0b1 fix: strip code-span backticks from extracted matrix password
mara observed the tuwunel reply renders the new password as a code span,
so the plain message body carries literal backticks:

  Successfully reset password for @user:server to: `<password>`

The previous extraction stopped at the first whitespace, capturing the
surrounding backticks ("`<password>`") and producing a login string that
doesn't match the password the bot actually set — recovery would still
fail after parsing.

Strip a leading backtick after the marker and stop the token at the first
whitespace OR closing backtick. Generated passwords contain neither, so a
real password is never truncated mid-token. Two regression tests cover the
backtick-wrapped form, including trailing prose after the closing backtick.
2026-06-05 00:47:37 +02:00
atlas
defface0a5 fix: parse tuwunel 'reset password for X to: <pw>' admin reply
extract_new_password had markers for 'is:', 'changed to:', 'reset to:',
'set to:' etc. but none match tuwunel's actual reset-password reply:

  Successfully reset password for @user:server to: <password>

Here the verb ('reset') is not adjacent to 'to:', so every marker missed
and the function returned None for every polled event. The admin-room poll
then ran its full 15s loop without a match and the auto-recovery timed out
on every agent — even though the bot replied correctly and the backward
poll found the message. This is why matrix password auto-recovery kept
looping despite the poll-strategy fix.

Add a generic ' to: ' marker (with surrounding spaces) placed after the
specific verb markers and before the bare 'password:' last resort. Matrix
user ids and server names can't contain ' to: ', so it only ever anchors
on the prose delimiter. Two regression tests cover the exact production
wording.
2026-06-05 00:47:37 +02:00
atlas
54f7c0facc docs: comment on empty event_id fallback in admin-room poll 2026-06-04 20:13:47 +02:00
atlas
e20a311786 tidy: is_whitespace() already covers newline, remove redundant arm 2026-06-04 20:13:47 +02:00
atlas
254fd1f9f1 fix: admin-room poll strategy — backward fetch anchored on sent event_id
The forward-pagination approach (dir=b anchor → dir=f poll) fails in
production: commands sent as @hive time out consistently even though
tuwunel responds. Root cause is likely a pagination-token direction
incompatibility in some tuwunel builds where 'end' from dir=b cannot
be used as 'from' for dir=f.

New strategy: send the command, capture the event_id from the PUT
response, then poll dir=b&limit=20 each tick. Events come back
newest-first; walk until we hit our own event_id, then stop — anything
before that marker arrived after our command. Simpler, avoids stored
tokens entirely.

Also:
- check formatted_body in addition to body (some admin bots put content
  only in HTML formatted_body)
- add more extract_new_password patterns: 'changed to:', 'reset to:',
  'set to:', 'new password:', 'password:' to handle different tuwunel
  version response formats
- add unit tests for new patterns

Fixes #1283.
2026-06-04 20:13:47 +02:00
atlas
1fa398e99e fix: use correct tuwunel admin room command prefix for reset-password 2026-06-04 16:43:53 +02:00
atlas
85de83bd49 fix: use correct tuwunel admin room command prefix for make-user-admin
tuwunel requires '!admin users <cmd>' — bare 'make-user-admin @user:server'
is not recognised. Update command string and doc comments.
2026-06-04 16:35:59 +02:00
atlas
0f505d5c95 fix: increase admin-room poll timeout from 5s to 15s
tuwunel can take longer than 5 seconds to process admin-room commands
during startup when the homeserver is under load. Bump both the poll
count (5→15) and the timeout message strings to match.
2026-06-04 16:28:47 +02:00
atlas
e06d3e3a8b refactor: extract admin_room_send_and_poll helper — deduplicate poll loop 2026-06-04 16:28:47 +02:00
atlas
2252eac650 fix: replace Synapse admin API in promote_user_to_admin with admin-room command 2026-06-04 16:26:59 +02:00
atlas
74253a9f71 fix: increase admin-room poll timeout from 5s to 15s
tuwunel can take longer than 5 seconds to process admin-room commands
during startup when the homeserver is under load. Bump poll count 5→15
and update timeout message strings to match.
2026-06-04 16:26:04 +02:00
atlas
a56badd004 fix: remove Synapse admin API path — use admin-room reset exclusively (tuwunel only) 2026-06-04 15:16:22 +02:00
atlas
97a78cc5d6 fix: address argus review on matrix admin-room reset — ascii lowercase, tighter markers, unit tests 2026-06-04 15:16:22 +02:00
atlas
b2913bc656 fix: admin-room fallback for matrix password reset when Synapse API absent
tuwunel 1.6.x does not implement the Synapse admin REST API.
reset_user_password() now falls back to the Matrix admin room
(#admins:<server>) when PUT /_synapse/admin/v2/users returns 404:

1. Discover admin room ID via #admins:<server> alias
2. Get current messages end-token (pagination anchor)
3. Send 'reset-password @<localpart>:<server>' as @hive admin user
4. Poll for bot response up to 5 x 1s; extract password from message
5. Persist the new password and return it

The function signature changes from Result<()> to Result<String> so the
caller can use the effective password (which may be server-generated on
the admin-room path) for subsequent login calls.

Closes #1267.
2026-06-04 15:16:22 +02:00
atlas
7022cd3826 fix: split WriteAgentStateFile into WriteAgentForgeToken + WriteAgentMatrixToken
Addresses mara's review: each credential type gets its own PrivRequest
variant, making the exact priv surface visible in the wire protocol.
No runtime filename dispatch — the operation name is the gate.

- WriteAgentForgeToken { agent_name, token } → state/forge-token
- WriteAgentMatrixToken { agent_name, token } → state/matrix-token
- priv_client: two typed fns (write_agent_forge_token, write_agent_matrix_token)
- forge.rs: split mint_and_persist_token into mint_and_persist_agent_token
  (priv) + mint_and_persist_core_token (direct write); drop dead token_path fn
- matrix.rs: call write_agent_matrix_token directly
2026-06-04 14:30:01 +02:00
atlas
eb51362d50 fix: route forge/matrix token writes through hive-priv
hive-c0re runs as the unprivileged hive-core user (privsep from #702)
and cannot write to agent-owned state directories. forge-token and
matrix-token writes were failing with EACCES on every startup sweep.

Add WriteAgentStateFile to PrivRequest: hive-priv (root) writes the
file 0600 and chowns it to the agent user so the agent can read it.

- hive-sh4re: add AGENT_STATE_ROOT constant + WriteAgentStateFile variant
- hive-priv: validate agent name + filename (no traversal), write via root
- priv_client: add write_agent_state_file helper
- forge: mint_and_persist_token routes agent paths through priv
- matrix: ensure_user_for routes matrix-token through priv

Closes #1257
2026-06-04 14:30:01 +02:00
atlas
34bc4c0b06 fix: route forge_admin through hive-priv; auto-recover matrix passwords
forge_admin() spawned nixos-container run hive-forge directly from the
hive-core process. nixos-container run uses nsenter to enter the container
namespaces, which requires root. hive-core is unprivileged, so every call
failed with: nsenter: stat of /proc/<pid>/ns/user failed: Permission denied

Fix: add RunForgeAdmin { args } to PrivRequest. hive-priv (root) handles
it by spawning nixos-container run hive-forge -- runuser -u forgejo --
forgejo --work-path /var/lib/forgejo admin <args>. forge_admin() now calls
priv_client::run_forge_admin().

matrix: ensure_user_for hit M_USER_IN_USE then failed when the stored
password file was missing (state dirs wiped but homeserver kept accounts).
Previously required manual hivectl matrix reset-password <name>.

Fix: add auto_reset_password() — calls the admin API (PUT
/_synapse/admin/v2/users/@<name>:<server> with the hive admin token) to
set a new random password, then proceeds with login. Falls back to the
existing manual-recovery error if the admin token is unavailable.

Closes #1234
2026-06-03 23:21:45 +02:00
atlas
e7785b4948 docs: add # Errors doc to ensure_hive_space 2026-06-03 22:01:37 +02:00
atlas
60582c0be4 feat: create hive Matrix Space on boot and invite all agents 2026-06-03 21:56:13 +02:00
atlas
a604fbf197 fix: address argus review nits on matrix admin additions
- drop stale --server flag reference from ensure_admin_user doc comment
- simplify M_USER_IN_USE recovery message: point at hivectl commands
- add #[must_use] to admin_token_path()
- rename tracing field rename_error in migration warn log (was error,
  which held rename err but fired on read failure — misleading)
2026-06-03 21:32:23 +02:00
atlas
c4a8b90236 fix: drop --server flag from hivectl matrix; discover server_name from homeserver
Add matrix::discover_server_name() via GET /_matrix/key/v2/server
(unauthenticated federation endpoint, always returns server_name).
hivectl is always talking to the local hive — no reason to require
the operator to spell out the server_name.
2026-06-03 21:32:23 +02:00
atlas
8757dc615d feat: hive matrix admin user + hivectl matrix promote-user/reset-password
- provision @hive:<server> as the first matrix account in ensure_all()
  (Conduit/tuwunel makes the first registered user admin automatically)
- add hivectl matrix sync-admin — manual re-provision of the admin token
- add hivectl matrix promote-user <name> --server <name> — promote via
  Synapse-compat admin API using the hive admin token
- add hivectl matrix reset-password <name> --server <name> — reset an
  agent's password + persist it so ensure_user_for can re-login; follow
  with hivectl matrix create-user <name> to mint a fresh access token
- both commands fall back to HYPERHIVE_MATRIX_SERVER_NAME env var for
  --server when omitted
2026-06-03 21:32:23 +02:00
atlas
47e9c1cc1b 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
2026-06-03 21:32:23 +02:00
müde
e7b6896e4c fix(#702): give hive-priv a PATH; route forge/matrix list through priv
hive-priv had no PATH, so the nixos-container it runs for every container
op (incl. lifecycle::list) failed ENOENT. build_all swallows that into an
empty list — the 'no managed containers' symptom. Give the helper the
minimal set nixos-container shells out to (nixos-container, nix, util-linux,
e2fsprogs) on top of the systemd/coreutils/findutils already in the unit
PATH, rather than the whole system profile.

Also route forge/matrix is_present() through priv_client::list_containers
instead of spawning nixos-container directly from unprivileged hive-c0re.
2026-06-03 00:05:17 +02:00
damocles
89665b94de feat(#1014): rename manager agent root→ruth across all crates + frontend 2026-06-02 18:18:06 +02:00
atlas
e3b4d38565 fix(#922): fall back to m.login.password when matrix user already exists
When the token file is deleted but the homeserver account still exists,
register_user returns M_USER_IN_USE (HTTP 400) and the provisioning
sweep hard-fails, leaving the agent without a working matrix token.

Fix: persist the random password to matrix-password alongside the
access token on first registration. On subsequent attempts where
M_USER_IN_USE is returned, fall back to login_user (m.login.password)
using the stored password. If both files are gone, the error message
guides the operator to `hivectl matrix create-user <name> --password`.
2026-06-01 19:28:50 +02:00
damocles
044ff5523c fix: call matrix::sync_agent on rebuild, mirrors startup sweep 2026-06-01 14:37:22 +02:00
damocles
d266889263 fix: matrix registration token 0o640 -> 0o600 2026-05-31 23:35:00 +02:00
damocles
91e50d843c hive-c0re/matrix.rs: trim UIAA prose to docs pointer (#715 batch 1) 2026-05-31 16:55:55 +02:00
damocles
ef968c11dd c0re: dedupe chown_to_agent into lifecycle (argus #678) 2026-05-31 00:40:40 +02:00
damocles
0cf703a939 c0re: chown per-agent state writes to agent uid:gid (#673) 2026-05-31 00:40:40 +02:00
damocles
97797cf790 hivectl: extend --password to forge; clarify matrix M_USER_IN_USE asymmetry (#663 / argus) 2026-05-30 23:27:26 +02:00
damocles
5d1909bb5c hivectl matrix create-user: --password / --password-stdin for operator accounts (#663) 2026-05-30 21:55:58 +02:00
damocles
18253bf2f5 hivectl: print token to stdout for non-agent users (#662) 2026-05-30 21:55:58 +02:00