From ef079bb6b18bb3eba8577b781f66a4264164dd58 Mon Sep 17 00:00:00 2001 From: damocles Date: Sat, 27 Jun 2026 13:52:21 +0200 Subject: [PATCH] feat(#2050): sync matrix avatar for dashboard-provisioned extra accounts --- nix/templates/harness-base.nix | 178 ++++++++++++++++++++------------- 1 file changed, 109 insertions(+), 69 deletions(-) diff --git a/nix/templates/harness-base.nix b/nix/templates/harness-base.nix index b355e20a..af2fe2fb 100644 --- a/nix/templates/harness-base.nix +++ b/nix/templates/harness-base.nix @@ -1642,13 +1642,18 @@ in pathConfig.PathExistsGlob = "/agents/*/state/matrix-token*"; }; - # Path-trigger sibling: re-fires matrix-avatar-sync the moment - # `/matrix-token` appears. Same first-boot-ordering pattern - # as hive-matrix-daemon above. + # Path-trigger sibling: re-fires matrix-avatar-sync the moment a + # token appears - both the hive-internal `matrix-token` and any + # dashboard-provisioned extra `matrix-token-` (so an external + # account logged in after boot gets its avatar without a restart). + # Same first-boot-ordering pattern as hive-matrix-daemon above. systemd.paths.matrix-avatar-sync = { - description = "trigger matrix-avatar-sync when matrix-token appears"; + description = "trigger matrix-avatar-sync when a matrix token appears"; wantedBy = [ "multi-user.target" ]; - pathConfig.PathExistsGlob = "/agents/*/state/matrix-token"; + pathConfig.PathExistsGlob = [ + "/agents/*/state/matrix-token" + "/agents/*/state/matrix-token-*" + ]; }; # One-shot: hyperhive.icon → matrix profile avatar (two-step media @@ -1681,82 +1686,117 @@ in echo "matrix-avatar-sync: no icon configured; skipping" exit 0 fi - # Token written by `hive-c0re::matrix::ensure_user_for` to the - # agent's bind-mounted state dir. $HYPERHIVE_STATE_DIR is set - # system-wide by the meta flake (systemd.globalEnvironment) to - # `/agents//state`. - TOKEN_FILE="$HYPERHIVE_STATE_DIR/matrix-token" - if [ ! -f "$TOKEN_FILE" ]; then - echo "matrix-avatar-sync: no matrix-token at $TOKEN_FILE; skipping" - exit 0 - fi # Hash-based idempotency: skip the upload if the icon hasn't # changed since the last successful sync. Every upload mints a # new mxc:// URI which triggers a profile state event in every # joined room — uploading the same bytes again produces timeline - # spam without changing the visible avatar. The hash file lives - # in $HYPERHIVE_STATE_DIR (survives restart, wiped on purge so - # purge + re-provision gets a fresh upload). Delete to force - # re-upload. - HASH_FILE="$HYPERHIVE_STATE_DIR/matrix-avatar-icon-hash" + # spam without changing the visible avatar. Hash files live in + # $HYPERHIVE_STATE_DIR (survives restart, wiped on purge so + # purge + re-provision gets a fresh upload). One hash file per + # account (the mxc:// URI is homeserver-scoped, so each account + # uploads to its own homeserver independently). Delete to force + # a re-upload. CURRENT_HASH=$(sha256sum "$ICON" | cut -d' ' -f1) - if [ -f "$HASH_FILE" ] && [ "$(cat "$HASH_FILE" 2>/dev/null)" = "$CURRENT_HASH" ]; then - echo "matrix-avatar-sync: icon unchanged (hash matches); skipping" - exit 0 - fi - TOKEN=$(cat "$TOKEN_FILE") - # Local tuwunel reachable on shared host netns at the - # default matrix-spec port. Override via - # `hyperhive.matrix.url` if the operator runs the - # homeserver elsewhere. - MATRIX_URL=http://localhost:8008 - # whoami → user_id. Needed to scope the avatar set call. - # Tolerant of the homeserver being unreachable (`-f` makes - # curl fail on 4xx/5xx; `|| true` swallows the exit). - USER_ID=$(curl -sf --max-time 5 \ - -H "Authorization: Bearer $TOKEN" \ - "$MATRIX_URL/_matrix/client/v3/account/whoami" 2>/dev/null \ - | jq -r '.user_id // empty' || true) - if [ -z "$USER_ID" ]; then - echo "matrix-avatar-sync: whoami failed or homeserver unreachable; skipping" - exit 0 - fi - # Rasterize SVG → PNG (matrix media accepts any image type - # but we already standardise on PNG for the forge sync). + + # Rasterize SVG → PNG ONCE; the same bytes are reused for every + # account (matrix media accepts any image type but we already + # standardise on PNG for the forge sync). PNG=$(mktemp --suffix=.png) if ! rsvg-convert -f png -w 512 -h 512 "$ICON" -o "$PNG" 2>/dev/null; then echo "matrix-avatar-sync: rsvg-convert failed; skipping" rm -f "$PNG" exit 0 fi - # Step 1: upload bytes → mxc:// URI. - MXC=$(curl -sf --max-time 10 \ - -X POST "$MATRIX_URL/_matrix/media/v3/upload" \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: image/png" \ - --data-binary "@$PNG" 2>/dev/null \ - | jq -r '.content_uri // empty' || true) + + # sync_one