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:
@@ -2324,17 +2324,28 @@ async fn cleanup_start_conflict(package_id: &str, stderr: &str) -> bool {
|
||||
}
|
||||
|
||||
async fn cleanup_stale_pasta_port(port: &str) {
|
||||
// NEVER kill our own process. The daemon holds catalog app ports over
|
||||
// IPv6 (the mesh app-port relay), so a blunt `fuser -k <port>/tcp` would
|
||||
// terminate archipelago itself mid-install — installs failed and apps
|
||||
// vanished on framework-pt 2026-07-27. Kill every listener on the port
|
||||
// EXCEPT our PID (and our process group), leaving the relay/daemon alive.
|
||||
let self_pid = std::process::id();
|
||||
let kill_listener = format!(
|
||||
"ss -ltnp 'sport = :{}' 2>/dev/null | sed -n 's/.*pid=\\([0-9]*\\).*/\\1/p' | xargs -r kill 2>/dev/null || true",
|
||||
port
|
||||
"ss -ltnp 'sport = :{port}' 2>/dev/null | sed -n 's/.*pid=\\([0-9]*\\).*/\\1/p' | \
|
||||
while read p; do [ \"$p\" = \"{self_pid}\" ] || kill \"$p\" 2>/dev/null; done || true",
|
||||
);
|
||||
let _ = tokio::process::Command::new("sh")
|
||||
.args(["-c", &kill_listener])
|
||||
.output()
|
||||
.await;
|
||||
|
||||
let _ = tokio::process::Command::new("sudo")
|
||||
.args(["fuser", "-k", &format!("{}/tcp", port)])
|
||||
// sudo fuser -k, but exclude our own PID: fuser prints the PIDs holding
|
||||
// the port; kill each except self. (`fuser -k` has no exclusion flag.)
|
||||
let fuser_kill = format!(
|
||||
"for p in $(sudo fuser {port}/tcp 2>/dev/null); do [ \"$p\" = \"{self_pid}\" ] || sudo kill \"$p\" 2>/dev/null; done || true",
|
||||
);
|
||||
let _ = tokio::process::Command::new("sh")
|
||||
.args(["-c", &fuser_kill])
|
||||
.output()
|
||||
.await;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user