From b7da7cd59fa601d2194a13f3916b6a9900a5b681 Mon Sep 17 00:00:00 2001 From: Aiden McClelland Date: Mon, 23 Feb 2026 17:56:15 -0700 Subject: [PATCH 1/2] fix(core): preserve plugin URLs across binding updates BindInfo::update was replacing addresses with a new DerivedAddressInfo that cleared the available set, wiping plugin-exported URLs whenever bind() was called. Also simplify update_addresses plugin preservation to use retain in place rather than collecting into a separate set. --- core/src/net/host/binding.rs | 6 +----- core/src/net/host/mod.rs | 12 +++--------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/core/src/net/host/binding.rs b/core/src/net/host/binding.rs index 975c95390..81c14615f 100644 --- a/core/src/net/host/binding.rs +++ b/core/src/net/host/binding.rs @@ -204,11 +204,7 @@ impl BindInfo { enabled: true, options, net: lan, - addresses: DerivedAddressInfo { - enabled: addresses.enabled, - disabled: addresses.disabled, - available: BTreeSet::new(), - }, + addresses, }) } pub fn disable(&mut self) { diff --git a/core/src/net/host/mod.rs b/core/src/net/host/mod.rs index aa23bdaca..bec7854de 100644 --- a/core/src/net/host/mod.rs +++ b/core/src/net/host/mod.rs @@ -92,15 +92,10 @@ impl Model { for (_, bind) in this.bindings.as_entries_mut()? { let net = bind.as_net().de()?; let opt = bind.as_options().de()?; + // Preserve existing plugin-provided addresses across recomputation - let plugin_addrs: BTreeSet = bind - .as_addresses() - .as_available() - .de()? - .into_iter() - .filter(|h| matches!(h.metadata, HostnameMetadata::Plugin { .. })) - .collect(); - let mut available = BTreeSet::new(); + let mut available = bind.as_addresses().as_available().de()?; + available.retain(|h| matches!(h.metadata, HostnameMetadata::Plugin { .. })); for (gid, g) in gateways { let Some(ip_info) = &g.ip_info else { continue; @@ -290,7 +285,6 @@ impl Model { }); } } - available.extend(plugin_addrs); bind.as_addresses_mut().as_available_mut().ser(&available)?; } From 5294e8f444897fbbfe9888993817ec2b631f2224 Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Mon, 23 Feb 2026 18:13:33 -0700 Subject: [PATCH 2/2] minor cleanup from patch-db audit --- .../ui/src/app/services/os.service.ts | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/web/projects/ui/src/app/services/os.service.ts b/web/projects/ui/src/app/services/os.service.ts index 0c5fb57cb..a34931915 100644 --- a/web/projects/ui/src/app/services/os.service.ts +++ b/web/projects/ui/src/app/services/os.service.ts @@ -2,10 +2,11 @@ import { inject, Injectable } from '@angular/core' import { PatchDB } from 'patch-db-client' import { BehaviorSubject, - distinctUntilChanged, - map, combineLatest, + distinctUntilChanged, firstValueFrom, + map, + shareReplay, } from 'rxjs' import { ApiService } from 'src/app/services/api/embassy-api.service' import { getServerInfo } from 'src/app/utils/get-server-info' @@ -22,17 +23,19 @@ export class OSService { osUpdate?: T.OsVersionInfoMap readonly updateAvailable$ = new BehaviorSubject(false) - readonly updating$ = this.patch.watch$('serverInfo', 'statusInfo').pipe( + private readonly statusInfo$ = this.patch + .watch$('serverInfo', 'statusInfo') + .pipe(shareReplay({ bufferSize: 1, refCount: true })) + + readonly updating$ = this.statusInfo$.pipe( map(status => status.updateProgress ?? status.updated), distinctUntilChanged(), ) - readonly backingUp$ = this.patch - .watch$('serverInfo', 'statusInfo', 'backupProgress') - .pipe( - map(obj => !!obj), - distinctUntilChanged(), - ) + readonly backingUp$ = this.statusInfo$.pipe( + map(status => !!status.backupProgress), + distinctUntilChanged(), + ) readonly updatingOrBackingUp$ = combineLatest([ this.updating$,