fix(install): mesh app-port relay no longer kills the daemon on install
The v6 app-port relay preemptively bound [::]:<port> for ALL catalog ports, even apps not installed. Installing such an app (grafana:3000, photoprism, uptime-kuma, jellyfin — framework-pt 2026-07-27) then hit 'address already in use' from the relay, which triggered cleanup_stale_pasta_port -> -> killed archipelago itself (it held the port) mid-install. The daemon crash-looped and the half-created apps were rolled back and vanished. Two fixes: - relay only bridges a port that a running app already answers on over IPv4 (probe 127.0.0.1:port first) — an uninstalled app's port is never held, so its install sees a free port and never triggers the cleanup. - cleanup_stale_pasta_port excludes our own PID from both the ss-based kill and the fuser kill, so freeing a port can never terminate the daemon even when the relay legitimately holds it (reinstall case). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1103,12 +1103,32 @@ async fn app_port_v6_relay_loop(mut shutdown_rx: tokio::sync::watch::Receiver<bo
|
||||
if bridged.contains(&port) {
|
||||
continue;
|
||||
}
|
||||
// ONLY bridge a port that a running app already answers on
|
||||
// over IPv4. Binding [::]:port for an app that isn't
|
||||
// installed is actively harmful: it makes that app's
|
||||
// later install hit "address already in use", and the
|
||||
// install's port-free step (`fuser -k <port>/tcp`) then
|
||||
// kills THIS daemon, which holds the port — the exact
|
||||
// cause of installs failing + apps vanishing on
|
||||
// framework-pt 2026-07-27. No v4 listener → skip; the
|
||||
// next rescan picks it up once the app is up.
|
||||
let v4_up = tokio::time::timeout(
|
||||
std::time::Duration::from_millis(300),
|
||||
tokio::net::TcpStream::connect(("127.0.0.1", port)),
|
||||
)
|
||||
.await
|
||||
.ok()
|
||||
.and_then(|r| r.ok())
|
||||
.is_some();
|
||||
if !v4_up {
|
||||
continue;
|
||||
}
|
||||
let addr = SocketAddr::new(
|
||||
std::net::IpAddr::V6(std::net::Ipv6Addr::UNSPECIFIED),
|
||||
port,
|
||||
);
|
||||
// EADDRINUSE = the app itself (or a prior relay) already
|
||||
// answers on v6 — exactly when we must stay out of the way.
|
||||
// EADDRINUSE = the app itself already answers on v6 —
|
||||
// exactly when we must stay out of the way.
|
||||
let Ok(listener) = bind_v6_only(addr) else { continue };
|
||||
bridged.insert(port);
|
||||
debug!("v6 relay bridging [::]:{port} -> 127.0.0.1:{port}");
|
||||
|
||||
Reference in New Issue
Block a user