From 0069f8adefd8bdb448b8d56ad06814009de8ce20 Mon Sep 17 00:00:00 2001 From: PopSolutions Date: Tue, 24 Mar 2026 16:44:24 +0000 Subject: [PATCH 1/7] Add DHCPv6 support and fix non-blocking DHCP client compatibility - ifutil.py: Add retry loop (10s timeout) in set_dhcp() for non-blocking DHCP clients like dhcpcd. The previous code checked for an IP address immediately after ifup returned, but dhcpcd forks and assigns the address asynchronously (~3s delay). - ifutil.py: Add get_ipv6conf() to retrieve global-scope IPv6 address and prefix length via 'ip -6 addr show'. - confconsole.py: Display IPv6 address in both the main services screen and the networking configuration screen when available. Tested on Proxmox LXC unprivileged container (Moodle v19, Trixie) with dhcpcd as the sole DHCP client (dual-stack DHCPv4/DHCPv6). --- confconsole.py | 9 ++++++++- ifutil.py | 26 ++++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/confconsole.py b/confconsole.py index a6353b2..8652434 100755 --- a/confconsole.py +++ b/confconsole.py @@ -437,7 +437,11 @@ def _get_ifconftext(self, ifname: str) -> str: text = f"IP Address: {addr}\n" text += f"Netmask: {netmask}\n" text += f"Default Gateway: {gateway}\n" - text += f"Name Server(s): {' '.join(nameservers)}\n\n" + text += f"Name Server(s): {' '.join(nameservers)}\n" + ipv6_addr, ipv6_prefix = ifutil.get_ipv6conf(ifname) + if ipv6_addr: + text += f"IPv6 Address: {ipv6_addr}/{ipv6_prefix}\n" + text += "\n" ifmethod = ifutil.get_ifmethod(ifname) if ifmethod: @@ -513,6 +517,9 @@ def usage(self) -> str: t = "" text = Template(t).substitute(ipaddr=ip_addr) + ipv6_addr, ipv6_prefix = ifutil.get_ipv6conf(ifname) + if ipv6_addr: + text += f"\nIPv6: {ipv6_addr}/{ipv6_prefix}\n" text += f"\n\n{tklbam_status}\n\n" text += "\n" * (self.height - len(text.splitlines()) - 7) text += " TurnKey Backups and Cloud Deployment\n" diff --git a/ifutil.py b/ifutil.py index 62356ae..9537897 100644 --- a/ifutil.py +++ b/ifutil.py @@ -413,8 +413,11 @@ def set_dhcp(ifname: str) -> str | None: raise e finally: output = ifup(ifname, True) - - net = InterfaceInfo(ifname) + for _retry in range(10): + net = InterfaceInfo(ifname) + if net.address: + break + sleep(1) if not net.address: raise IfError(f"Error obtaining IP address\n\n{output}") return None @@ -437,6 +440,25 @@ def get_ipconf( return (None, None, net.get_gateway(error), get_nameservers(ifname)) + +def get_ipv6conf(ifname: str) -> tuple[str | None, str | None]: + """Get IPv6 global address and prefix for an interface.""" + try: + out = subprocess.check_output( + ["ip", "-6", "addr", "show", ifname, "scope", "global"], + text=True, stderr=subprocess.DEVNULL + ) + for line in out.splitlines(): + line = line.strip() + if line.startswith("inet6"): + parts = line.split() + addr_prefix = parts[1] + addr, prefix = addr_prefix.split("/") + return (addr, prefix) + except Exception: + pass + return (None, None) + def get_ifmethod(ifname: str) -> str | None: interfaces = NetworkInterfaces() interfaces.read() From 44e0edfbf7c09e45626bba45e98ba9127e465305 Mon Sep 17 00:00:00 2001 From: PopSolutions Date: Tue, 24 Mar 2026 17:40:11 +0000 Subject: [PATCH 2/7] fix: recognize IPv6 global address as valid network in confconsole _get_default_nic() only checked IPv4 via get_ipconf(), causing confconsole to report 'Networking is not yet configured' on IPv6-only hosts despite having a valid global address. Add get_ipv6conf() fallback in _validip(): if no valid IPv4 is found, check for a global-scope IPv6 address before declaring the interface unconfigured. This enables IPv6-first deployments to pass the network check without requiring an IPv4 address. --- confconsole.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/confconsole.py b/confconsole.py index 8652434..b974bcf 100755 --- a/confconsole.py +++ b/confconsole.py @@ -331,6 +331,9 @@ def _validip(ifname: str) -> bool: ip = ifutil.get_ipconf(ifname)[0] if ip and not ip.startswith("169"): return True + ip6 = ifutil.get_ipv6conf(ifname)[0] + if ip6: + return True return False defifname = conf.Conf().default_nic From 2fb87c87db048e60a76862361f00c70ca0cc1ac5 Mon Sep 17 00:00:00 2001 From: PopSolutions Date: Thu, 26 Mar 2026 23:10:05 +0000 Subject: [PATCH 3/7] Use safe_substitute with ip6addr template variable Pass IPv6 address as $ip6addr to services.txt template instead of appending it after substitution. Uses safe_substitute so appliances without $ip6addr in their services.txt are unaffected. Ref: turnkeylinux/tracker#1658 --- confconsole.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/confconsole.py b/confconsole.py index b974bcf..bfa61a6 100755 --- a/confconsole.py +++ b/confconsole.py @@ -510,19 +510,25 @@ def usage(self) -> str: hostname = netinfo.get_hostname().upper() + ipv6_addr, ipv6_prefix = ifutil.get_ipv6conf(ifname) + ip6_display = f"{ipv6_addr}/{ipv6_prefix}" if ipv6_addr else "not configured" + try: with open(conf.path("services.txt")) as fob: t = fob.read().rstrip() - text = Template(t).substitute(appname=self.appname, - hostname=hostname, - ipaddr=ip_addr) + text = Template(t).safe_substitute( + appname=self.appname, + hostname=hostname, + ipaddr=ip_addr, + ip6addr=ip6_display, + ) except conf.ConfconsoleConfError: t = "" - text = Template(t).substitute(ipaddr=ip_addr) + text = Template(t).safe_substitute( + ipaddr=ip_addr, + ip6addr=ip6_display, + ) - ipv6_addr, ipv6_prefix = ifutil.get_ipv6conf(ifname) - if ipv6_addr: - text += f"\nIPv6: {ipv6_addr}/{ipv6_prefix}\n" text += f"\n\n{tklbam_status}\n\n" text += "\n" * (self.height - len(text.splitlines()) - 7) text += " TurnKey Backups and Cloud Deployment\n" From 8644380bdaf9f1072f8fb45e23cb9e188d597873 Mon Sep 17 00:00:00 2001 From: PopSolutions Date: Fri, 27 Mar 2026 00:00:30 +0000 Subject: [PATCH 4/7] Fix Identation & update gitignore --- .gitignore | 25 ++++++++++++++++++++++++- confconsole.py | 12 +++++++----- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 6fa524e..6622899 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,25 @@ -**/*.pyc +# cached dev files +**/__pycache__/ +.mypy_cache/ +.ruff_cache/ **/*.swp + +# python build assets +.pybuild/ +build/ +*.egg-info/ + +# deb package build assests +debian/confconsole/ + +debian/.debhelper/ +debian/debhelper-build-stamp +debian/*.debhelper.log +debian/*.debhelper +debian/*.substvars +debian/files + +# other files/dirs +tmp +tags +*.tar.bz2 diff --git a/confconsole.py b/confconsole.py index bfa61a6..f532327 100755 --- a/confconsole.py +++ b/confconsole.py @@ -520,14 +520,16 @@ def usage(self) -> str: appname=self.appname, hostname=hostname, ipaddr=ip_addr, - ip6addr=ip6_display, ) except conf.ConfconsoleConfError: t = "" - text = Template(t).safe_substitute( - ipaddr=ip_addr, - ip6addr=ip6_display, - ) + text = Template(t).safe_substitute(ipaddr=ip_addr) + + ipv6_addr, _ipv6_prefix = ifutil.get_ipv6conf(ifname) + if ipv6_addr: + text += f"\n" + text += f"\nIPv6 Web: http://[{ipv6_addr}]" + text += f"\nIPv6 SSH: 'root@[{ipv6_addr}]'" text += f"\n\n{tklbam_status}\n\n" text += "\n" * (self.height - len(text.splitlines()) - 7) From a50b92824e45f193f96cede82e5be3e34ea3c97b Mon Sep 17 00:00:00 2001 From: Jeremy Davis Date: Tue, 14 Apr 2026 12:25:10 +1000 Subject: [PATCH 5/7] Increase default size of window for nicer ipv6 display --- confconsole.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/confconsole.py b/confconsole.py index f532327..85bd3eb 100755 --- a/confconsole.py +++ b/confconsole.py @@ -77,8 +77,8 @@ class Console: def __init__( self, title: str | None = None, - width: int = 60, - height: int = 20, + width: int = 65, + height: int = 25, ) -> None: self.width = width self.height = height @@ -275,8 +275,8 @@ def __init__( advanced_enabled: bool = True, ) -> None: title = "TurnKey GNU/Linux Configuration Console" - self.width = 60 - self.height = 20 + self.width = 65 + self.height = 25 self.console = Console(title, self.width, self.height) From 3cd8c20f42d745d9860e63366e2c991fb7f3b19e Mon Sep 17 00:00:00 2001 From: Jeremy Davis Date: Tue, 14 Apr 2026 14:22:06 +1000 Subject: [PATCH 6/7] Reformatting for nicer layout --- confconsole.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/confconsole.py b/confconsole.py index 85bd3eb..1a6c6b6 100755 --- a/confconsole.py +++ b/confconsole.py @@ -443,7 +443,7 @@ def _get_ifconftext(self, ifname: str) -> str: text += f"Name Server(s): {' '.join(nameservers)}\n" ipv6_addr, ipv6_prefix = ifutil.get_ipv6conf(ifname) if ipv6_addr: - text += f"IPv6 Address: {ipv6_addr}/{ipv6_prefix}\n" + text += f"IPv6 Address: {ipv6_addr}/{ipv6_prefix}\n" text += "\n" ifmethod = ifutil.get_ifmethod(ifname) @@ -511,7 +511,6 @@ def usage(self) -> str: hostname = netinfo.get_hostname().upper() ipv6_addr, ipv6_prefix = ifutil.get_ipv6conf(ifname) - ip6_display = f"{ipv6_addr}/{ipv6_prefix}" if ipv6_addr else "not configured" try: with open(conf.path("services.txt")) as fob: @@ -527,12 +526,15 @@ def usage(self) -> str: ipv6_addr, _ipv6_prefix = ifutil.get_ipv6conf(ifname) if ipv6_addr: - text += f"\n" - text += f"\nIPv6 Web: http://[{ipv6_addr}]" - text += f"\nIPv6 SSH: 'root@[{ipv6_addr}]'" + text += "\n" + text += f"\nIPv6 Web: https://[{ipv6_addr}]" + text += f"\nIPv6 SSH: root@{ipv6_addr}" - text += f"\n\n{tklbam_status}\n\n" - text += "\n" * (self.height - len(text.splitlines()) - 7) + gap = self.height - len(text.splitlines()) - 11 + gap = gap if gap >= 1 else 1 + + text += f"\n\n{tklbam_status}" + text += "\n" * gap text += " TurnKey Backups and Cloud Deployment\n" text += " https://hub.turnkeylinux.org" From d67ab4284280fbb9a5936457f9511564e5a7ec22 Mon Sep 17 00:00:00 2001 From: Jeremy Davis Date: Tue, 14 Apr 2026 16:10:02 +1000 Subject: [PATCH 7/7] remove duplicate/redundant call to ifutil.get_ipv6conf() --- confconsole.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/confconsole.py b/confconsole.py index 1a6c6b6..02034d4 100755 --- a/confconsole.py +++ b/confconsole.py @@ -507,10 +507,8 @@ def usage(self) -> str: ip_addr = self._get_public_ipaddr() if not ip_addr: ip_addr = ifutil.get_ipconf(ifname)[0] - - hostname = netinfo.get_hostname().upper() - ipv6_addr, ipv6_prefix = ifutil.get_ipv6conf(ifname) + hostname = netinfo.get_hostname().upper() try: with open(conf.path("services.txt")) as fob: @@ -524,7 +522,6 @@ def usage(self) -> str: t = "" text = Template(t).safe_substitute(ipaddr=ip_addr) - ipv6_addr, _ipv6_prefix = ifutil.get_ipv6conf(ifname) if ipv6_addr: text += "\n" text += f"\nIPv6 Web: https://[{ipv6_addr}]"