@@ -270,9 +270,10 @@ print(bridge, subnet)
270270}
271271
272272ensure_network () {
273- local owner=" $1 " installation network labels bridge_name
273+ local owner=" $1 " installation network labels bridge_name network_path
274274 installation=" $( owner_installation " $owner " ) "
275275 network=" 1helm-$installation "
276+ network_path=" $NETWORKS_ROOT /$network .json"
276277 # Linux IFNAMSIZ is 15 chars; keep a stable, installation-scoped bridge name.
277278 bridge_name=" 1h${installation: 0: 12} "
278279 if " ${PODMAN[@]} " network exists " $network " ; then
@@ -283,17 +284,43 @@ labels=json.loads(sys.argv[1] or "{}")
283284if labels.get("com.1helm.managed") != "true" or labels.get("com.1helm.installation") != sys.argv[2]:
284285 raise SystemExit("network ownership labels do not match")
285286PY
287+ # Netavark 1.4 starts aardvark-dns through a user systemd scope. A root
288+ # helper invoked by the system service has no user bus, so the advertised
289+ # gateway DNS listener never exists and every resident lookup times out.
290+ # These are 1Helm-owned JSON network definitions; disable only that broken
291+ # plugin and let containers inherit the host's working resolvers instead.
292+ if [[ -f " $network_path " && ! -L " $network_path " ]] \
293+ && grep -q ' "dns_enabled"[[:space:]]*:[[:space:]]*true' " $network_path " ; then
294+ python3 - " $network_path " << 'PY '
295+ import json, os, stat, sys, tempfile
296+ path=sys.argv[1]
297+ info=os.lstat(path)
298+ if not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode) or info.st_uid != 0 or info.st_gid != 0:
299+ raise SystemExit("owned network definition is not a safe root-owned file")
300+ with open(path, encoding="utf-8") as source: network=json.load(source)
301+ labels=network.get("labels") or network.get("Labels") or {}
302+ if labels.get("com.1helm.managed") != "true": raise SystemExit("refusing to change an unmanaged network")
303+ network["dns_enabled"]=False
304+ descriptor,candidate=tempfile.mkstemp(prefix=".1helm-network-",dir=os.path.dirname(path))
305+ try:
306+ with os.fdopen(descriptor,"w",encoding="utf-8") as destination:
307+ json.dump(network,destination,indent=5); destination.write("\n"); destination.flush(); os.fsync(destination.fileno())
308+ os.chmod(candidate,stat.S_IMODE(info.st_mode)); os.chown(candidate,0,0); os.replace(candidate,path)
309+ finally:
310+ if os.path.exists(candidate): os.unlink(candidate)
311+ PY
312+ fi
286313 else
287314 # Prefer a stable IFNAMSIZ-safe bridge name when the backend supports Docker-
288315 # style bridge options (netavark). Debian bookworm's CNI backend rejects
289316 # com.docker.network.bridge.name; fall back to a labeled default bridge and
290317 # let ensure_guest_egress read the real interface from network inspect.
291318 create_out=" "
292- if ! create_out=" $( " ${PODMAN[@]} " network create --disable-dns=false \
319+ if ! create_out=" $( " ${PODMAN[@]} " network create --disable-dns \
293320 --opt " com.docker.network.bridge.name=${bridge_name} " \
294321 --label com.1helm.managed=true --label " com.1helm.installation=$installation " " $network " 2>&1 ) " ; then
295322 if grep -qiE ' unsupported (bridge )?network option|unknown network option' <<< " $create_out" ; then
296- " ${PODMAN[@]} " network create --disable-dns=false \
323+ " ${PODMAN[@]} " network create --disable-dns \
297324 --label com.1helm.managed=true --label " com.1helm.installation=$installation " " $network " > /dev/null \
298325 || die " channel network could not be created"
299326 else
@@ -671,27 +698,51 @@ create_container() {
671698}
672699
673700start_container () {
674- local name=" $1 " owner=" $2 " state network
701+ local name=" $1 " owner=" $2 " state network root network_ready container_ok=0 network_ok=0
675702 verify_container " $name " " $owner "
676- network=" $( network_name " $owner " ) "
703+ root=" $( channel_root " $name " ) "
704+ network_ready=" $root /network-ready-v1"
705+ network=" $( ensure_network " $owner " ) "
677706 # Re-assert egress on every start: Docker and host firewall reloads can drop
678707 # our FORWARD/NAT inserts after boot or package updates.
679708 if " ${PODMAN[@]} " network exists " $network " ; then ensure_guest_egress " $network " ; fi
680709 state=" $( container_state " $name " ) "
710+ # An update from the earlier aardvark-backed contract must restart a running
711+ # retained container once so Podman regenerates /etc/resolv.conf from the
712+ # now host-inherited network definition.
713+ if [[ " $state " == running && ! -f " $network_ready " ]]; then
714+ " ${PODMAN[@]} " stop --time 90 " $name " > /dev/null
715+ state=" stopped"
716+ fi
681717 if [[ " $state " != running ]]; then " ${PODMAN[@]} " start " $name " > /dev/null; fi
682718 for _ in {1..100}; do
683719 # Podman can briefly fail name-based --user lookup while it reconstructs
684720 # its ephemeral runroot after a host reboot even though the persisted
685721 # image passwd database is intact. These identities are pinned by the
686722 # installed runtime manifest and image contract, so use their numeric form
687723 # for the readiness probe and every subsequent exec boundary.
688- if " ${PODMAN[@]} " exec --user 0:0 " $name " /bin/sh -c ' test "$(cat /var/lib/1helm/owner)" = "$1" && test -d /workspace && test -d /home/agent' 1helm-start " $owner " > /dev/null 2>&1 ; then
724+ if " ${PODMAN[@]} " exec --user 0:0 " $name " /bin/sh -c \
725+ ' test "$(cat /var/lib/1helm/owner)" = "$1" && test -d /workspace && test -d /home/agent' \
726+ 1helm-start " $owner " > /dev/null 2>&1 ; then
689727 verify_container " $name " " $owner "
690- return
728+ container_ok=1
729+ break
691730 fi
692731 sleep 0.1
693732 done
694- die " container did not pass its ownership and storage health check"
733+ [[ " $container_ok " -eq 1 ]] || die " container did not pass its ownership and storage health check"
734+ if [[ ! -f " $network_ready " ]]; then
735+ for _ in {1..5}; do
736+ if timeout 7 " ${PODMAN[@]} " exec --user " $AGENT_UID :$AGENT_GID " " $name " /usr/bin/python3 -c \
737+ ' import socket; s=socket.socket(socket.AF_INET,socket.SOCK_STREAM); s.close(); socket.getaddrinfo("example.com",443,type=socket.SOCK_STREAM); c=socket.create_connection(("example.com",443),3); c.close()' \
738+ > /dev/null 2>&1 ; then network_ok=1; break ; fi
739+ sleep 0.2
740+ done
741+ [[ " $network_ok " -eq 1 ]] || die " container did not pass its network socket, public DNS, and TCP egress health check"
742+ printf ' %s\n' ' host-dns-and-public-tcp-v1' > " $network_ready "
743+ chown root:root " $network_ready "
744+ chmod 0600 " $network_ready "
745+ fi
695746}
696747
697748stop_container () {
@@ -902,7 +953,7 @@ case "$operation" in
902953 ;;
903954 ready)
904955 (( $# == 0 )) || die " ready takes no arguments"
905- for command in find flock getfacl iptables podman python3 setfacl sha256sum stat tar; do command -v " $command " > /dev/null || die " missing $command " ; done
956+ for command in find flock getfacl iptables podman python3 setfacl sha256sum stat tar timeout ; do command -v " $command " > /dev/null || die " missing $command " ; done
906957 [[ -r " $CONTAINERFILE " ]] || die " installed OCI image recipe is missing"
907958 " ${PODMAN[@]} " info > /dev/null
908959 printf ' {"ready":true,"version":"%s","engine":"podman"}\n' " $RUNTIME_VERSION "
0 commit comments