about summary refs log tree commit diff
path: root/MatrixUtils.Web/Pages/HSAdmin/Synapse/SubTools/SynapseRoomStateResync.razor
diff options
context:
space:
mode:
Diffstat (limited to 'MatrixUtils.Web/Pages/HSAdmin/Synapse/SubTools/SynapseRoomStateResync.razor')
-rw-r--r--MatrixUtils.Web/Pages/HSAdmin/Synapse/SubTools/SynapseRoomStateResync.razor211
1 files changed, 211 insertions, 0 deletions
diff --git a/MatrixUtils.Web/Pages/HSAdmin/Synapse/SubTools/SynapseRoomStateResync.razor b/MatrixUtils.Web/Pages/HSAdmin/Synapse/SubTools/SynapseRoomStateResync.razor
new file mode 100644

index 0000000..3cc5a6a --- /dev/null +++ b/MatrixUtils.Web/Pages/HSAdmin/Synapse/SubTools/SynapseRoomStateResync.razor
@@ -0,0 +1,211 @@ +@page "/HSAdmin/Synapse/ResyncState" +@using ArcaneLibs.Extensions +@using LibMatrix +@using LibMatrix.EventTypes.Spec.State.RoomInfo +@using LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Requests + +<h3>Resync room state with other server</h3> +<hr/> + +@if (!Executing) { + <p>WARNING: Will likely not work on invite-only/knock rooms! May also mess with history visibility!</p> + <p>If the room is using mjolnir/draupnir, it's probably recommended to set the "via" to the server it's hosted on.</p> + <span>Room ID: </span> + <InputText @bind-Value="@RoomId"></InputText> + <br/> + <span>Via: </span> + <InputText @bind-Value="@Via"></InputText> + <br/> + <LinkButton OnClickAsync="@Execute">Execute</LinkButton> +} + +@if (Executing) { + <p>Execution in progress. DO NOT CLOSE THIS PAGE!</p> +} +@* stage 1 *@ +@if (Stage >= 1) { + @if (Members is null) { + <p>Loading members...</p> + } + else { + <p>Got @Members.Count local members</p> + } +} + +@* stage 2 *@ +@if (Stage == 2) { + <p>Purging room, please wait...</p> + <pre>@DeleteStatus.ToJson(ignoreNull: true)</pre> +} + +@* stage 3 *@ +@if (Stage == 3) { + <p>Rejoining room, please wait...</p> + <p>Members left to restore: </p> + string members = ""; + foreach (var member in Members) { + members += $"{member.StateKey} ({member.ContentAs<RoomMemberEventContent>()?.ToJson(indent: false, ignoreNull: true)})\n"; + } + + <pre> + @members + </pre> +} + +@if (Stage == 4) { + <p>Execution finished. You may now close the page :)</p> +} + +@if (Error is not null) { + <p style="color: red">Error: @Error.Message</p> + <pre> + @Error.ToString() + </pre> +} + +@code { + + [Parameter] + [SupplyParameterFromQuery] + public string? RoomId { get; set; } + + [Parameter] + [SupplyParameterFromQuery(Name = "via")] + public string? Via { get; set; } + + private AuthenticatedHomeserverSynapse? Homeserver { get; set; } + + // Execution flow + private int Stage { get; set; } + private bool Executing { get; set; } + private Exception? Error { get; set; } + + // Stage 1 + private List<MatrixEventResponse>? Members { get; set; } + + // Stage 2 + private SynapseAdminRoomDeleteStatus? DeleteStatus { get; set; } + + protected override async Task OnInitializedAsync() { + if (await sessionStore.GetCurrentHomeserver(navigateOnFailure: true) is not AuthenticatedHomeserverSynapse hs) return; + Homeserver = hs; + + StateHasChanged(); + } + + private Task Execute() => Execute(0); + + private async Task Execute(int startStage) { + if (string.IsNullOrWhiteSpace(RoomId)) return; + if (string.IsNullOrWhiteSpace(Via)) return; + Executing = true; + StateHasChanged(); + + await ExecuteStages(startStage); + + StateHasChanged(); + } + + private async Task ExecuteStages(int startStage) { + if (startStage <= 1) + if (!await TryGetRoomMembers()) + return; + if (startStage <= 2) + if (!await TryPurgeRoom()) + return; + if (startStage <= 3) + if (!await TryRestoreRoom()) + return; + + Stage = 4; + Executing = false; + StateHasChanged(); + } + + private async Task<bool> TryGetRoomMembers() { + Stage = 1; + try { + Members = (await Homeserver.Admin.GetRoomStateAsync(RoomId, type: RoomMemberEventContent.EventId)) + .Events.Where(m => (m.StateKey?.EndsWith(':' + Homeserver.ServerName) ?? false) && m.ContentAs<RoomMemberEventContent>()!.Membership == "join") + .ToList(); + Console.WriteLine(Members.ToJson(ignoreNull: true)); + StateHasChanged(); + return true; + } + catch (Exception e) { + Error = e; + return Executing = false; + } + } + + private async Task<bool> TryPurgeRoom() { + Stage = 2; + + try { + var resp = await Homeserver.Admin.DeleteRoom(RoomId, new SynapseAdminRoomDeleteRequest { + Block = true, + Purge = true, + // ForcePurge = true // This causes synapse to early return and not actually purge stuff... + }, waitForCompletion: false); + + while (true) { + // we dont want API failure to break this step + try { + DeleteStatus = await Homeserver.Admin.GetRoomDeleteStatus(resp.DeleteId); + StateHasChanged(); + if (DeleteStatus.Status == SynapseAdminRoomDeleteStatus.Complete) { + return true; + } + + if (DeleteStatus.Status == SynapseAdminRoomDeleteStatus.Failed) { + Error = new Exception("Failed to delete room: " + DeleteStatus.ToJson()); + return Executing = false; + } + + await Task.Delay(1000); + } + catch { } + } + + StateHasChanged(); + return true; + } + catch (Exception e) { + Error = e; + return Executing = false; + } + } + + private async Task<bool> TryRestoreRoom() { + Stage = 3; + try { + await Homeserver.Admin.BlockRoom(RoomId, block: false); + Members = Random.Shared.GetItems(Members.ToArray(), Members.Count).ToList(); + StateHasChanged(); + foreach (var member in Members) { + while (true) { + try { + var hs = member.StateKey == Homeserver.WhoAmI.UserId + ? Homeserver + : await Homeserver.Admin.GetHomeserverForUserAsync(member.StateKey!, TimeSpan.FromMinutes(120)); + await hs.GetRoom(RoomId).JoinAsync([Via], reason: "Reconciling state with " + Via, false); + await hs.GetRoom(RoomId).SendStateEventAsync(RoomMemberEventContent.EventId, member.StateKey, member.RawContent); + Members = Members.Skip(1).ToList(); + StateHasChanged(); + break; + } + catch (Exception e) { + Error = new Exception($"{DateTime.Now:u} Failed to join room: {member.StateKey}, retrying\n", e); + } + } + } + + return true; + } + catch (Exception e) { + Error = e; + return Executing = false; + } + } + +} \ No newline at end of file