fix(server+quadlet): app UIs work over the mesh — v6 relay + v4-pinned publishes
Rootless podman's wildcard publish claims [::] but BLACK-HOLES inbound v6 (accepts, forwards nothing — vaultwarden 'empty response' from the phone, 2026-07-26), while most apps got no v6 listener at all. Two halves: quadlets now publish unbound ports on 0.0.0.0 explicitly (frees the v6 side; the one-time drift/recreate at upgrade is the deploy vehicle), and the daemon runs a self-selecting V6ONLY relay on every catalog launch port forwarding raw TCP to the v4 loopback listener. Rescans every 60s so new installs bridge without a restart. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -248,10 +248,17 @@ impl QuadletUnit {
|
||||
} else {
|
||||
proto.as_str()
|
||||
};
|
||||
// Keep the rendered directive byte-identical for unbound
|
||||
// ports so existing units don't read as drifted.
|
||||
// Unbound publishes are pinned to 0.0.0.0: rootlessport's
|
||||
// wildcard bind claims [::] too but BLACK-HOLES inbound v6
|
||||
// (accepts, forwards nothing — "empty response" from the
|
||||
// mesh, confirmed 2026-07-26). Pinning v4 frees the port's
|
||||
// v6 side for the daemon's mesh relay (app_port_v6_relay_loop),
|
||||
// which forwards to the v4 loopback listener that works.
|
||||
// NOTE: this changes the rendered directive for unbound
|
||||
// ports on purpose — the one-time drift/recreate at upgrade
|
||||
// is the deploy vehicle for the fix.
|
||||
if bind.is_empty() {
|
||||
let _ = writeln!(s, "PublishPort={host}:{container}/{p}");
|
||||
let _ = writeln!(s, "PublishPort=0.0.0.0:{host}:{container}/{p}");
|
||||
} else {
|
||||
let _ = writeln!(s, "PublishPort={bind}:{host}:{container}/{p}");
|
||||
}
|
||||
@@ -1018,7 +1025,7 @@ mod tests {
|
||||
u.network = NetworkMode::Bridge("archy-net".into());
|
||||
u.ports = vec![(3000, 3000, "tcp".into(), String::new())];
|
||||
let s = u.render();
|
||||
assert!(s.contains("PublishPort=3000:3000/tcp"));
|
||||
assert!(s.contains("PublishPort=0.0.0.0:3000:3000/tcp"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1167,8 +1174,8 @@ mod tests {
|
||||
let s = u.render();
|
||||
assert!(s.contains("PublishPort=127.0.0.1:8332:8332/tcp"));
|
||||
assert!(s.contains("PublishPort=10.89.0.1:8332:8332/tcp"));
|
||||
assert!(s.contains("PublishPort=8333:8333/tcp"));
|
||||
assert!(!s.contains("PublishPort=8332:8332/tcp"));
|
||||
assert!(s.contains("PublishPort=0.0.0.0:8333:8333/tcp"));
|
||||
assert!(!s.contains("PublishPort=0.0.0.0:8332:8332/tcp"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1200,8 +1207,8 @@ mod tests {
|
||||
..QuadletUnit::default()
|
||||
};
|
||||
let s = u.render();
|
||||
assert!(s.contains("PublishPort=8332:8332/tcp"));
|
||||
assert!(s.contains("PublishPort=8333:8333/tcp"));
|
||||
assert!(s.contains("PublishPort=0.0.0.0:8332:8332/tcp"));
|
||||
assert!(s.contains("PublishPort=0.0.0.0:8333:8333/tcp"));
|
||||
assert!(s.contains("Environment=BITCOIN_RPC_USER=archipelago"));
|
||||
assert!(s.contains("Environment=BITCOIN_RPC_PASS=secret"));
|
||||
assert!(s.contains("Environment=\"RELAY_NAME=Archipelago Nostr Relay\""));
|
||||
@@ -1317,7 +1324,7 @@ app:
|
||||
let m = AppManifest::parse(yaml).expect("manifest must parse");
|
||||
let s = QuadletUnit::from_manifest(&m, "searxng").render();
|
||||
|
||||
assert!(s.contains("PublishPort=8888:8080/tcp"));
|
||||
assert!(s.contains("PublishPort=0.0.0.0:8888:8080/tcp"));
|
||||
assert!(!s.contains("Network=host"));
|
||||
}
|
||||
|
||||
@@ -1746,7 +1753,7 @@ app:
|
||||
assert!(body.contains("Network=archy-net"));
|
||||
assert!(body.contains("NetworkAlias=lnd"));
|
||||
assert!(body.contains("PodmanArgs=--network-alias=lnd"));
|
||||
assert!(body.contains("PublishPort=10009:10009/tcp"));
|
||||
assert!(body.contains("PublishPort=0.0.0.0:10009:10009/tcp"));
|
||||
assert!(body.contains("Volume=/var/lib/archipelago/lnd:/root/.lnd:Z"));
|
||||
assert!(body.contains("Environment=LND_NETWORK=mainnet"));
|
||||
assert!(body.contains("PodmanArgs=--memory=1024m"));
|
||||
|
||||
Reference in New Issue
Block a user