From 0069f8adefd8bdb448b8d56ad06814009de8ce20 Mon Sep 17 00:00:00 2001 From: PopSolutions Date: Tue, 24 Mar 2026 16:44:24 +0000 Subject: [PATCH 1/4] 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/4] 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/4] 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 3fe572306d6eacdef6b823a9149d72c1e987021b Mon Sep 17 00:00:00 2001 From: PopSolutions Date: Fri, 27 Mar 2026 00:00:30 +0000 Subject: [PATCH 4/4] Fix Identation --- confconsole.py | 12 +- .../dh_installchangelogs.dch.trimmed | 5 + .../confconsole/installed-by-dh_install | 12 + .../confconsole/installed-by-dh_installdocs | 17 + debian/confconsole.debhelper.log | 1 + debian/confconsole.postinst.debhelper | 23 + debian/confconsole.postrm.debhelper | 5 + debian/confconsole.prerm.debhelper | 15 + debian/confconsole.substvars | 3 + debian/confconsole/DEBIAN/conffiles | 3 + debian/confconsole/DEBIAN/control | 11 + debian/confconsole/DEBIAN/md5sums | 65 ++ debian/confconsole/DEBIAN/postinst | 25 + debian/confconsole/DEBIAN/postrm | 7 + debian/confconsole/DEBIAN/prerm | 17 + .../etc/confconsole/confconsole.conf | 20 + .../confconsole/etc/confconsole/services.txt | 5 + .../confconsole/etc/logrotate.d/confconsole | 11 + .../lib/systemd/system/add-water.service | 7 + debian/confconsole/usr/bin/confconsole | 1 + debian/confconsole/usr/bin/turnkey-lexicon | 109 +++ debian/confconsole/usr/lib/confconsole/conf | 1 + .../confconsole/usr/lib/confconsole/conf.py | 71 ++ .../usr/lib/confconsole/confconsole.py | 919 ++++++++++++++++++ .../confconsole/usr/lib/confconsole/ifutil.py | 467 +++++++++ .../confconsole/usr/lib/confconsole/ipaddr.py | 105 ++ .../confconsole/usr/lib/confconsole/plugin.py | 308 ++++++ .../plugins.d/Lets_Encrypt/add-water-client | 44 + .../plugins.d/Lets_Encrypt/add-water-srv | 118 +++ .../plugins.d/Lets_Encrypt/cert_auto_renew.py | 60 ++ .../plugins.d/Lets_Encrypt/dehydrated-wrapper | 349 +++++++ .../plugins.d/Lets_Encrypt/description | 1 + .../plugins.d/Lets_Encrypt/dns_01.py | 202 ++++ .../plugins.d/Lets_Encrypt/get_certificate.py | 444 +++++++++ .../plugins.d/Mail_Relaying/description | 1 + .../plugins.d/Mail_Relaying/mail_relay.py | 173 ++++ .../plugins.d/Mail_Relaying/mail_relay.sh | 78 ++ .../plugins.d/Proxy_Settings/apt.py | 81 ++ .../plugins.d/Proxy_Settings/description | 1 + .../plugins.d/Region_Config/description | 1 + .../plugins.d/Region_Config/keyboard.py | 56 ++ .../plugins.d/Region_Config/locales.py | 32 + .../plugins.d/Region_Config/tzdata.py | 21 + .../System_Settings/Confconsole_auto_start.py | 57 ++ .../System_Settings/Secupdates_adv_conf.py | 120 +++ .../System_Settings/Security_Update.py | 10 + .../plugins.d/System_Settings/description | 1 + .../plugins.d/System_Settings/hostname.py | 129 +++ .../usr/lib/confconsole/plugins.d/example.py | 43 + .../confconsole/autostart/confconsole-auto | 45 + .../letsencrypt/dehydrated-confconsole.config | 29 + .../letsencrypt/dehydrated-confconsole.cron | 34 + .../dehydrated-confconsole.domains | 5 + .../dehydrated-confconsole.hook-dns-01.sh | 99 ++ .../dehydrated-confconsole.hook-http-01.sh | 66 ++ .../share/confconsole/letsencrypt/index.html | 29 + ...exicon-confconsole-provider_cloudflare.yml | 22 + .../lexicon-confconsole-provider_example.yml | 18 + .../lexicon-confconsole-provider_route53.yml | 23 + .../confconsole/Lets_encrypt#advanced.rst.gz | 1 + .../share/doc/confconsole/Lets_encrypt.rst.gz | Bin 0 -> 3773 bytes .../usr/share/doc/confconsole/Mail_relay.rst | 65 ++ .../usr/share/doc/confconsole/Networking.rst | 82 ++ .../usr/share/doc/confconsole/Plugins.rst.gz | Bin 0 -> 2940 bytes .../share/doc/confconsole/Proxy_settings.rst | 32 + .../usr/share/doc/confconsole/README.gz | Bin 0 -> 1881 bytes .../share/doc/confconsole/Region_config.rst | 83 ++ .../share/doc/confconsole/RelNotes-0.9.1.txt | 16 + .../share/doc/confconsole/RelNotes-0.9.2.txt | 40 + .../share/doc/confconsole/RelNotes-0.9.3.txt | 42 + .../share/doc/confconsole/RelNotes-0.9.4.txt | 99 ++ .../share/doc/confconsole/RelNotes-0.9.txt | 8 + .../share/doc/confconsole/RelNotes-1.0.0.txt | 24 + .../share/doc/confconsole/RelNotes-2.1.0.txt | 5 + .../share/doc/confconsole/System_settings.rst | 52 + .../usr/share/doc/confconsole/changelog.gz | Bin 0 -> 167 bytes .../usr/share/doc/confconsole/copyright | 22 + .../images/00_confconsole_core_main.png | Bin 0 -> 36130 bytes .../images/01_confconsole_core_advanced.png | Bin 0 -> 48044 bytes .../images/02_confconsole_core_networking.png | Bin 0 -> 36302 bytes .../images/03_confconsole_lets_encrypt.png | Bin 0 -> 24678 bytes .../images/04_confconsole_mail_relay.png | Bin 0 -> 21225 bytes .../images/05_confconsole_proxy_settings.png | Bin 0 -> 21548 bytes .../images/06_confconsole_region_config.png | Bin 0 -> 26935 bytes .../images/07_confconsole_system_settings.png | Bin 0 -> 25217 bytes .../python3/runtime.d/confconsole.rtupdate | 7 + debian/debhelper-build-stamp | 1 + debian/files | 2 + 88 files changed, 5213 insertions(+), 5 deletions(-) create mode 100644 debian/.debhelper/generated/confconsole/dh_installchangelogs.dch.trimmed create mode 100644 debian/.debhelper/generated/confconsole/installed-by-dh_install create mode 100644 debian/.debhelper/generated/confconsole/installed-by-dh_installdocs create mode 100644 debian/confconsole.debhelper.log create mode 100644 debian/confconsole.postinst.debhelper create mode 100644 debian/confconsole.postrm.debhelper create mode 100644 debian/confconsole.prerm.debhelper create mode 100644 debian/confconsole.substvars create mode 100644 debian/confconsole/DEBIAN/conffiles create mode 100644 debian/confconsole/DEBIAN/control create mode 100644 debian/confconsole/DEBIAN/md5sums create mode 100755 debian/confconsole/DEBIAN/postinst create mode 100755 debian/confconsole/DEBIAN/postrm create mode 100755 debian/confconsole/DEBIAN/prerm create mode 100644 debian/confconsole/etc/confconsole/confconsole.conf create mode 100644 debian/confconsole/etc/confconsole/services.txt create mode 100644 debian/confconsole/etc/logrotate.d/confconsole create mode 100644 debian/confconsole/lib/systemd/system/add-water.service create mode 120000 debian/confconsole/usr/bin/confconsole create mode 100755 debian/confconsole/usr/bin/turnkey-lexicon create mode 120000 debian/confconsole/usr/lib/confconsole/conf create mode 100644 debian/confconsole/usr/lib/confconsole/conf.py create mode 100755 debian/confconsole/usr/lib/confconsole/confconsole.py create mode 100644 debian/confconsole/usr/lib/confconsole/ifutil.py create mode 100644 debian/confconsole/usr/lib/confconsole/ipaddr.py create mode 100644 debian/confconsole/usr/lib/confconsole/plugin.py create mode 100755 debian/confconsole/usr/lib/confconsole/plugins.d/Lets_Encrypt/add-water-client create mode 100755 debian/confconsole/usr/lib/confconsole/plugins.d/Lets_Encrypt/add-water-srv create mode 100755 debian/confconsole/usr/lib/confconsole/plugins.d/Lets_Encrypt/cert_auto_renew.py create mode 100755 debian/confconsole/usr/lib/confconsole/plugins.d/Lets_Encrypt/dehydrated-wrapper create mode 100644 debian/confconsole/usr/lib/confconsole/plugins.d/Lets_Encrypt/description create mode 100755 debian/confconsole/usr/lib/confconsole/plugins.d/Lets_Encrypt/dns_01.py create mode 100755 debian/confconsole/usr/lib/confconsole/plugins.d/Lets_Encrypt/get_certificate.py create mode 100644 debian/confconsole/usr/lib/confconsole/plugins.d/Mail_Relaying/description create mode 100755 debian/confconsole/usr/lib/confconsole/plugins.d/Mail_Relaying/mail_relay.py create mode 100755 debian/confconsole/usr/lib/confconsole/plugins.d/Mail_Relaying/mail_relay.sh create mode 100755 debian/confconsole/usr/lib/confconsole/plugins.d/Proxy_Settings/apt.py create mode 100644 debian/confconsole/usr/lib/confconsole/plugins.d/Proxy_Settings/description create mode 100644 debian/confconsole/usr/lib/confconsole/plugins.d/Region_Config/description create mode 100755 debian/confconsole/usr/lib/confconsole/plugins.d/Region_Config/keyboard.py create mode 100755 debian/confconsole/usr/lib/confconsole/plugins.d/Region_Config/locales.py create mode 100755 debian/confconsole/usr/lib/confconsole/plugins.d/Region_Config/tzdata.py create mode 100755 debian/confconsole/usr/lib/confconsole/plugins.d/System_Settings/Confconsole_auto_start.py create mode 100755 debian/confconsole/usr/lib/confconsole/plugins.d/System_Settings/Secupdates_adv_conf.py create mode 100755 debian/confconsole/usr/lib/confconsole/plugins.d/System_Settings/Security_Update.py create mode 100644 debian/confconsole/usr/lib/confconsole/plugins.d/System_Settings/description create mode 100755 debian/confconsole/usr/lib/confconsole/plugins.d/System_Settings/hostname.py create mode 100644 debian/confconsole/usr/lib/confconsole/plugins.d/example.py create mode 100755 debian/confconsole/usr/share/confconsole/autostart/confconsole-auto create mode 100644 debian/confconsole/usr/share/confconsole/letsencrypt/dehydrated-confconsole.config create mode 100644 debian/confconsole/usr/share/confconsole/letsencrypt/dehydrated-confconsole.cron create mode 100644 debian/confconsole/usr/share/confconsole/letsencrypt/dehydrated-confconsole.domains create mode 100644 debian/confconsole/usr/share/confconsole/letsencrypt/dehydrated-confconsole.hook-dns-01.sh create mode 100644 debian/confconsole/usr/share/confconsole/letsencrypt/dehydrated-confconsole.hook-http-01.sh create mode 100644 debian/confconsole/usr/share/confconsole/letsencrypt/index.html create mode 100644 debian/confconsole/usr/share/confconsole/letsencrypt/lexicon-confconsole-provider_cloudflare.yml create mode 100644 debian/confconsole/usr/share/confconsole/letsencrypt/lexicon-confconsole-provider_example.yml create mode 100644 debian/confconsole/usr/share/confconsole/letsencrypt/lexicon-confconsole-provider_route53.yml create mode 120000 debian/confconsole/usr/share/doc/confconsole/Lets_encrypt#advanced.rst.gz create mode 100644 debian/confconsole/usr/share/doc/confconsole/Lets_encrypt.rst.gz create mode 100644 debian/confconsole/usr/share/doc/confconsole/Mail_relay.rst create mode 100644 debian/confconsole/usr/share/doc/confconsole/Networking.rst create mode 100644 debian/confconsole/usr/share/doc/confconsole/Plugins.rst.gz create mode 100644 debian/confconsole/usr/share/doc/confconsole/Proxy_settings.rst create mode 100644 debian/confconsole/usr/share/doc/confconsole/README.gz create mode 100644 debian/confconsole/usr/share/doc/confconsole/Region_config.rst create mode 100644 debian/confconsole/usr/share/doc/confconsole/RelNotes-0.9.1.txt create mode 100644 debian/confconsole/usr/share/doc/confconsole/RelNotes-0.9.2.txt create mode 100644 debian/confconsole/usr/share/doc/confconsole/RelNotes-0.9.3.txt create mode 100644 debian/confconsole/usr/share/doc/confconsole/RelNotes-0.9.4.txt create mode 100644 debian/confconsole/usr/share/doc/confconsole/RelNotes-0.9.txt create mode 100644 debian/confconsole/usr/share/doc/confconsole/RelNotes-1.0.0.txt create mode 100644 debian/confconsole/usr/share/doc/confconsole/RelNotes-2.1.0.txt create mode 100644 debian/confconsole/usr/share/doc/confconsole/System_settings.rst create mode 100644 debian/confconsole/usr/share/doc/confconsole/changelog.gz create mode 100644 debian/confconsole/usr/share/doc/confconsole/copyright create mode 100644 debian/confconsole/usr/share/doc/confconsole/images/00_confconsole_core_main.png create mode 100644 debian/confconsole/usr/share/doc/confconsole/images/01_confconsole_core_advanced.png create mode 100644 debian/confconsole/usr/share/doc/confconsole/images/02_confconsole_core_networking.png create mode 100644 debian/confconsole/usr/share/doc/confconsole/images/03_confconsole_lets_encrypt.png create mode 100644 debian/confconsole/usr/share/doc/confconsole/images/04_confconsole_mail_relay.png create mode 100644 debian/confconsole/usr/share/doc/confconsole/images/05_confconsole_proxy_settings.png create mode 100644 debian/confconsole/usr/share/doc/confconsole/images/06_confconsole_region_config.png create mode 100644 debian/confconsole/usr/share/doc/confconsole/images/07_confconsole_system_settings.png create mode 100755 debian/confconsole/usr/share/python3/runtime.d/confconsole.rtupdate create mode 100644 debian/debhelper-build-stamp create mode 100644 debian/files 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) diff --git a/debian/.debhelper/generated/confconsole/dh_installchangelogs.dch.trimmed b/debian/.debhelper/generated/confconsole/dh_installchangelogs.dch.trimmed new file mode 100644 index 0000000..09d5eab --- /dev/null +++ b/debian/.debhelper/generated/confconsole/dh_installchangelogs.dch.trimmed @@ -0,0 +1,5 @@ +confconsole (2.2.1) stable; urgency=medium + + * fix: recognize IPv6 global address as valid network + + -- PopSolutions Tue, 24 Mar 2026 19:03:04 +0000 diff --git a/debian/.debhelper/generated/confconsole/installed-by-dh_install b/debian/.debhelper/generated/confconsole/installed-by-dh_install new file mode 100644 index 0000000..18ba31a --- /dev/null +++ b/debian/.debhelper/generated/confconsole/installed-by-dh_install @@ -0,0 +1,12 @@ +./plugins.d/ +./conf.py +./confconsole.py +./ifutil.py +./ipaddr.py +./plugin.py +./conf/confconsole.conf +./conf/services.txt +./share/autostart +./share/letsencrypt +./add-water/add-water.service +./turnkey-lexicon diff --git a/debian/.debhelper/generated/confconsole/installed-by-dh_installdocs b/debian/.debhelper/generated/confconsole/installed-by-dh_installdocs new file mode 100644 index 0000000..43ffa84 --- /dev/null +++ b/debian/.debhelper/generated/confconsole/installed-by-dh_installdocs @@ -0,0 +1,17 @@ +./docs/images +./docs/Lets_encrypt#advanced.rst +./docs/Lets_encrypt.rst +./docs/Mail_relay.rst +./docs/Networking.rst +./docs/Plugins.rst +./docs/Proxy_settings.rst +./docs/README +./docs/Region_config.rst +./docs/RelNotes-0.9.1.txt +./docs/RelNotes-0.9.2.txt +./docs/RelNotes-0.9.3.txt +./docs/RelNotes-0.9.4.txt +./docs/RelNotes-0.9.txt +./docs/RelNotes-1.0.0.txt +./docs/RelNotes-2.1.0.txt +./docs/System_settings.rst diff --git a/debian/confconsole.debhelper.log b/debian/confconsole.debhelper.log new file mode 100644 index 0000000..c856529 --- /dev/null +++ b/debian/confconsole.debhelper.log @@ -0,0 +1 @@ +dh_installinit diff --git a/debian/confconsole.postinst.debhelper b/debian/confconsole.postinst.debhelper new file mode 100644 index 0000000..62e9285 --- /dev/null +++ b/debian/confconsole.postinst.debhelper @@ -0,0 +1,23 @@ + +# Automatically added by dh_python3 +if command -v py3compile >/dev/null 2>&1; then + py3compile -p confconsole /usr/lib/confconsole +fi +if command -v pypy3compile >/dev/null 2>&1; then + pypy3compile -p confconsole /usr/lib/confconsole || true +fi + +# End automatically added section +# Automatically added by dh_systemd_start/13.24.2 +if [ "$1" = "configure" ] || [ "$1" = "abort-upgrade" ] || [ "$1" = "abort-deconfigure" ] || [ "$1" = "abort-remove" ] ; then + if [ -d /run/systemd/system ]; then + systemctl --system daemon-reload >/dev/null || true + if [ -n "$2" ]; then + _dh_action=restart + else + _dh_action=start + fi + deb-systemd-invoke $_dh_action 'add-water.service' >/dev/null || true + fi +fi +# End automatically added section diff --git a/debian/confconsole.postrm.debhelper b/debian/confconsole.postrm.debhelper new file mode 100644 index 0000000..b8c4b2d --- /dev/null +++ b/debian/confconsole.postrm.debhelper @@ -0,0 +1,5 @@ +# Automatically added by dh_systemd_start/13.24.2 +if [ "$1" = remove ] && [ -d /run/systemd/system ] ; then + systemctl --system daemon-reload >/dev/null || true +fi +# End automatically added section diff --git a/debian/confconsole.prerm.debhelper b/debian/confconsole.prerm.debhelper new file mode 100644 index 0000000..664bdde --- /dev/null +++ b/debian/confconsole.prerm.debhelper @@ -0,0 +1,15 @@ +# Automatically added by dh_systemd_start/13.24.2 +if [ -z "$DPKG_ROOT" ] && [ "$1" = remove ] && [ -d /run/systemd/system ] ; then + deb-systemd-invoke stop 'add-water.service' >/dev/null || true +fi +# End automatically added section + +# Automatically added by dh_python3 +if command -v py3clean >/dev/null 2>&1; then + py3clean -p confconsole +else + dpkg -L confconsole | sed -En -e '/^(.*)\/(.+)\.py$/s,,rm "\1/__pycache__/\2".*,e' + find /usr/lib/python3/dist-packages/ -type d -name __pycache__ -empty -print0 | xargs --null --no-run-if-empty rmdir +fi + +# End automatically added section diff --git a/debian/confconsole.substvars b/debian/confconsole.substvars new file mode 100644 index 0000000..13de292 --- /dev/null +++ b/debian/confconsole.substvars @@ -0,0 +1,3 @@ +python3:Depends=python3:any +misc:Depends= +misc:Pre-Depends= diff --git a/debian/confconsole/DEBIAN/conffiles b/debian/confconsole/DEBIAN/conffiles new file mode 100644 index 0000000..1291cc8 --- /dev/null +++ b/debian/confconsole/DEBIAN/conffiles @@ -0,0 +1,3 @@ +/etc/confconsole/confconsole.conf +/etc/confconsole/services.txt +/etc/logrotate.d/confconsole diff --git a/debian/confconsole/DEBIAN/control b/debian/confconsole/DEBIAN/control new file mode 100644 index 0000000..54e9267 --- /dev/null +++ b/debian/confconsole/DEBIAN/control @@ -0,0 +1,11 @@ +Package: confconsole +Version: 2.2.1 +Architecture: all +Maintainer: Stefan Davis +Installed-Size: 469 +Depends: python3:any, python3-dialog (>= 3.4), turnkey-netinfo, turnkey-conffile, libsasl2-modules, python3-requests +Recommends: authbind, dehydrated, kbd, python3-bottle, resolvconf +Suggests: di-live +Section: misc +Priority: optional +Description: TurnKey GNU/Linux Configuration Console diff --git a/debian/confconsole/DEBIAN/md5sums b/debian/confconsole/DEBIAN/md5sums new file mode 100644 index 0000000..3dcceb8 --- /dev/null +++ b/debian/confconsole/DEBIAN/md5sums @@ -0,0 +1,65 @@ +a2a435b858602013cd250023ab2bf88e lib/systemd/system/add-water.service +e6c6f46b8154ddc64149592d64ffc2f8 usr/bin/turnkey-lexicon +be10d9f9a800e467a207888a5144fbeb usr/lib/confconsole/conf.py +97d17f2a4fc63a6a72330f25afd753f0 usr/lib/confconsole/confconsole.py +9a8bf227c87211531a1674870c7d3364 usr/lib/confconsole/ifutil.py +9c6653b9b082e15ea6834273c3a93347 usr/lib/confconsole/ipaddr.py +91378615d94b4f0b96e6caf866e40d11 usr/lib/confconsole/plugin.py +d103bb218ddb8f08b9b41ad34ef10af7 usr/lib/confconsole/plugins.d/Lets_Encrypt/add-water-client +a0e4d15a34ed847cfda2fe5335a5ea8c usr/lib/confconsole/plugins.d/Lets_Encrypt/add-water-srv +3f1b243623b6f4738eabc9cf94cf3366 usr/lib/confconsole/plugins.d/Lets_Encrypt/cert_auto_renew.py +5d073db60c03d50fb8cddc323cd03f36 usr/lib/confconsole/plugins.d/Lets_Encrypt/dehydrated-wrapper +5f6f2ddf556f1506a30e0b73314d8100 usr/lib/confconsole/plugins.d/Lets_Encrypt/description +df807162181c62ca071b97e0f3af1e45 usr/lib/confconsole/plugins.d/Lets_Encrypt/dns_01.py +29c1998bfc1bedd46207426ce7fc0cf6 usr/lib/confconsole/plugins.d/Lets_Encrypt/get_certificate.py +fb6a43e3048894d4d465ae8200c1f2f5 usr/lib/confconsole/plugins.d/Mail_Relaying/description +45083377baf7650b395e461bdaa787df usr/lib/confconsole/plugins.d/Mail_Relaying/mail_relay.py +9ff1045794ba0ff0dfd05eececdfd455 usr/lib/confconsole/plugins.d/Mail_Relaying/mail_relay.sh +259f6685a6a000e2269be5a3ec501d02 usr/lib/confconsole/plugins.d/Proxy_Settings/apt.py +e6515a40dc270d8a711727159896f908 usr/lib/confconsole/plugins.d/Proxy_Settings/description +892bd2f92647c1680a5908bbcd221f6e usr/lib/confconsole/plugins.d/Region_Config/description +dd88963e7aba81f49bef3483973896b1 usr/lib/confconsole/plugins.d/Region_Config/keyboard.py +c296bf3cde865f46d98481d63da79517 usr/lib/confconsole/plugins.d/Region_Config/locales.py +dbe696c1ecbc4411c9feac5d0297ec21 usr/lib/confconsole/plugins.d/Region_Config/tzdata.py +74971f785aa0c5a972183f210fcb0462 usr/lib/confconsole/plugins.d/System_Settings/Confconsole_auto_start.py +54b9292db446a05a34465f29d778ab5c usr/lib/confconsole/plugins.d/System_Settings/Secupdates_adv_conf.py +eb22c06d5868f885431ea8582dda08e4 usr/lib/confconsole/plugins.d/System_Settings/Security_Update.py +a295e21705e8a59a9d3506b9a9590f80 usr/lib/confconsole/plugins.d/System_Settings/description +46e67e027b3a236889c0efa062a8b7a0 usr/lib/confconsole/plugins.d/System_Settings/hostname.py +c4b5d3e418a4790689a9ae454bb74749 usr/lib/confconsole/plugins.d/example.py +eb6c611dbaf5afa300d49755b571152a usr/share/confconsole/autostart/confconsole-auto +e031d1f60700e951cf311dc1bb6cf427 usr/share/confconsole/letsencrypt/dehydrated-confconsole.config +dc694e701c15d9c83ed270a7c37c61b3 usr/share/confconsole/letsencrypt/dehydrated-confconsole.cron +80bd968880faf4404eb6cc28bf359593 usr/share/confconsole/letsencrypt/dehydrated-confconsole.domains +ac9137e06d48bdc0ca9a789dc87d06fc usr/share/confconsole/letsencrypt/dehydrated-confconsole.hook-dns-01.sh +57c480ac97601f2b510473bc3aa16ac0 usr/share/confconsole/letsencrypt/dehydrated-confconsole.hook-http-01.sh +a3209b4db7d08de9feca8793c9af1a48 usr/share/confconsole/letsencrypt/index.html +a355092c3bd43623982be816d7791982 usr/share/confconsole/letsencrypt/lexicon-confconsole-provider_cloudflare.yml +96c52a96c85c620b6e2d8ac7ea55e117 usr/share/confconsole/letsencrypt/lexicon-confconsole-provider_example.yml +5b2e8105b7aeae706762aeee82ee7336 usr/share/confconsole/letsencrypt/lexicon-confconsole-provider_route53.yml +e257660aa2cc7b1eaeb6aedf7432ac59 usr/share/doc/confconsole/Lets_encrypt.rst.gz +a3669376af9ba16caa311032d48b7b47 usr/share/doc/confconsole/Mail_relay.rst +32fe69fb884b36498f642170299bdf6e usr/share/doc/confconsole/Networking.rst +97798f774fdb8e41cefa9adda61e9300 usr/share/doc/confconsole/Plugins.rst.gz +36f800426b5187434462f5b06ec7d301 usr/share/doc/confconsole/Proxy_settings.rst +f8388f428723005621023628ac3031f2 usr/share/doc/confconsole/README.gz +c9ea765c96c4bc58062bc7072e0f1524 usr/share/doc/confconsole/Region_config.rst +29a4ba329f4d42eb7b4ffec7949e0c7f usr/share/doc/confconsole/RelNotes-0.9.1.txt +f3f950743532991ade629a836c7380ca usr/share/doc/confconsole/RelNotes-0.9.2.txt +e5a06032b5dc076c13b2823654722d51 usr/share/doc/confconsole/RelNotes-0.9.3.txt +078f02d816e128030c5d8841b02de06b usr/share/doc/confconsole/RelNotes-0.9.4.txt +1fcfab7291c76dd596cc0ac89312a875 usr/share/doc/confconsole/RelNotes-0.9.txt +1450fbe51e3f2ffbe66f679f495d0bbf usr/share/doc/confconsole/RelNotes-1.0.0.txt +4bd4491332687ca48a1d93ace94c6937 usr/share/doc/confconsole/RelNotes-2.1.0.txt +e83bc3e078770ff0233089f407b2cf69 usr/share/doc/confconsole/System_settings.rst +3598cc212de59acaeeb4b4aee07f9974 usr/share/doc/confconsole/changelog.gz +8f8660a6a383b3bcfb47cbe7910af6ed usr/share/doc/confconsole/copyright +72bbf571b07b95bf530cd6941d3b0fcb usr/share/doc/confconsole/images/00_confconsole_core_main.png +24915b1bcb36902de7ee4e345eea920d usr/share/doc/confconsole/images/01_confconsole_core_advanced.png +ff887beec0e146d0ac556d487e16e4f4 usr/share/doc/confconsole/images/02_confconsole_core_networking.png +bc81127718a40bb72247e5005994f825 usr/share/doc/confconsole/images/03_confconsole_lets_encrypt.png +11134981a46654655ec4696c44fba49c usr/share/doc/confconsole/images/04_confconsole_mail_relay.png +02b840ee6a2be899616da5d55cccee03 usr/share/doc/confconsole/images/05_confconsole_proxy_settings.png +bbc3a0b0f1c3f0af2f64cc5e810a3daf usr/share/doc/confconsole/images/06_confconsole_region_config.png +1b11d3ad61cf27538419a8181a0c2859 usr/share/doc/confconsole/images/07_confconsole_system_settings.png +98e0b696b2c13aefd5601d305b414297 usr/share/python3/runtime.d/confconsole.rtupdate diff --git a/debian/confconsole/DEBIAN/postinst b/debian/confconsole/DEBIAN/postinst new file mode 100755 index 0000000..341971e --- /dev/null +++ b/debian/confconsole/DEBIAN/postinst @@ -0,0 +1,25 @@ +#!/bin/sh +set -e + +# Automatically added by dh_python3 +if command -v py3compile >/dev/null 2>&1; then + py3compile -p confconsole /usr/lib/confconsole +fi +if command -v pypy3compile >/dev/null 2>&1; then + pypy3compile -p confconsole /usr/lib/confconsole || true +fi + +# End automatically added section +# Automatically added by dh_systemd_start/13.24.2 +if [ "$1" = "configure" ] || [ "$1" = "abort-upgrade" ] || [ "$1" = "abort-deconfigure" ] || [ "$1" = "abort-remove" ] ; then + if [ -d /run/systemd/system ]; then + systemctl --system daemon-reload >/dev/null || true + if [ -n "$2" ]; then + _dh_action=restart + else + _dh_action=start + fi + deb-systemd-invoke $_dh_action 'add-water.service' >/dev/null || true + fi +fi +# End automatically added section diff --git a/debian/confconsole/DEBIAN/postrm b/debian/confconsole/DEBIAN/postrm new file mode 100755 index 0000000..eebf305 --- /dev/null +++ b/debian/confconsole/DEBIAN/postrm @@ -0,0 +1,7 @@ +#!/bin/sh +set -e +# Automatically added by dh_systemd_start/13.24.2 +if [ "$1" = remove ] && [ -d /run/systemd/system ] ; then + systemctl --system daemon-reload >/dev/null || true +fi +# End automatically added section diff --git a/debian/confconsole/DEBIAN/prerm b/debian/confconsole/DEBIAN/prerm new file mode 100755 index 0000000..1df73ea --- /dev/null +++ b/debian/confconsole/DEBIAN/prerm @@ -0,0 +1,17 @@ +#!/bin/sh +set -e +# Automatically added by dh_systemd_start/13.24.2 +if [ -z "$DPKG_ROOT" ] && [ "$1" = remove ] && [ -d /run/systemd/system ] ; then + deb-systemd-invoke stop 'add-water.service' >/dev/null || true +fi +# End automatically added section + +# Automatically added by dh_python3 +if command -v py3clean >/dev/null 2>&1; then + py3clean -p confconsole +else + dpkg -L confconsole | sed -En -e '/^(.*)\/(.+)\.py$/s,,rm "\1/__pycache__/\2".*,e' + find /usr/lib/python3/dist-packages/ -type d -name __pycache__ -empty -print0 | xargs --null --no-run-if-empty rmdir +fi + +# End automatically added section diff --git a/debian/confconsole/etc/confconsole/confconsole.conf b/debian/confconsole/etc/confconsole/confconsole.conf new file mode 100644 index 0000000..e0b242b --- /dev/null +++ b/debian/confconsole/etc/confconsole/confconsole.conf @@ -0,0 +1,20 @@ +# Confconsole config file +# +# Commented lines are ignored and default values used. +# If value declared multiple times, the last one will be applied. + +# default network interface to display in usage +#default_nic eth0 + +# disable Networking config in Advanced menu +#networking false + +# command to get public ipaddress to display in usage +#publicip_cmd curl -s https://api.ipify.org +#publicip_cmd ec2metadata --public-ipv4 + +# autostart on login - one of true|once|false +#autostart once + +# enable copy/paste +#copy_paste true diff --git a/debian/confconsole/etc/confconsole/services.txt b/debian/confconsole/etc/confconsole/services.txt new file mode 100644 index 0000000..badb882 --- /dev/null +++ b/debian/confconsole/etc/confconsole/services.txt @@ -0,0 +1,5 @@ +Web: http://$ipaddr + https://$ipaddr +Web shell: https://$ipaddr:12320 +Webmin: https://$ipaddr:12321 +SSH/SFTP: root@$ipaddr (port 22) diff --git a/debian/confconsole/etc/logrotate.d/confconsole b/debian/confconsole/etc/logrotate.d/confconsole new file mode 100644 index 0000000..f185efd --- /dev/null +++ b/debian/confconsole/etc/logrotate.d/confconsole @@ -0,0 +1,11 @@ +/var/log/confconsole/*.log { + monthly + missingok + rotate 6 + compress + delaycompress + notifempty + create 640 root root + +} + diff --git a/debian/confconsole/lib/systemd/system/add-water.service b/debian/confconsole/lib/systemd/system/add-water.service new file mode 100644 index 0000000..f4731bc --- /dev/null +++ b/debian/confconsole/lib/systemd/system/add-water.service @@ -0,0 +1,7 @@ +[Unit] +Description=Add Water +After=network.target + +[Service] +Type=simple +ExecStart=/usr/bin/python3 /usr/lib/confconsole/plugins.d/Lets_Encrypt/add-water-srv -l /var/log/confconsole/letsencrypt.log diff --git a/debian/confconsole/usr/bin/confconsole b/debian/confconsole/usr/bin/confconsole new file mode 120000 index 0000000..edecbae --- /dev/null +++ b/debian/confconsole/usr/bin/confconsole @@ -0,0 +1 @@ +../lib/confconsole/confconsole.py \ No newline at end of file diff --git a/debian/confconsole/usr/bin/turnkey-lexicon b/debian/confconsole/usr/bin/turnkey-lexicon new file mode 100755 index 0000000..e6fd9f0 --- /dev/null +++ b/debian/confconsole/usr/bin/turnkey-lexicon @@ -0,0 +1,109 @@ +#!/bin/bash -e + +# fallback defaults - adjust as desired +VENV_FALLBACK=/usr/local/src/venv + +export VENV_BASE="${VENV_BASE:-$VENV_FALLBACK}" + +[[ -z "$DEBUG" ]] || set -x + +no_venv() { + cat <> Let's Encrypt >> Get Certificate >> DNS-01 + +If you encounter problems, please report to TurnKey, either via our forums: + + https://www.turnkeylinux.org/forum/support + +Or open an issue on our tracker: + + https://github.com/turnkeylinux/tracker/issues +EOF + exit 1 +} + +usage() { + cat <] [] + +Lexicon is a tool to manipulate DNS records on various DNS providers in a +standardized way. + +TurnKey Confconsole leverages lexicon to support Let's Encrypt +DNS challenges, via Dehydrated. This script ensures that lexicon is run +within it's required virtual environment. + +This wrapper script runs lexicon from a predetermined virtual environment, +with and/or . + +This wrapper script is shipped as part of confconsole and expects lexicon to +already be installed via pip to a venv, located at $VENV_FALLBACK/lexicon. +If lexicon is not already installed into the venv, then this script will +fail. + +To install/setup lexicon in venv as this wrapper expects, please run: + + Confconsole >> Advanced >> Let's Encrypt >> Get Certificate >> DNS-01 + +Once lexicon is installed as expected, this wrapper can be run independantly +of Confconsole. + +Args:: +------ + + Note that if you pass more that one argument/option to $(basename "$0"), all + args are passed directly to lexicon. + + -h|--help Display this help and exit - nothing passed to lexicon + -h|--help PROVIDER Display lexicon help for PROVIDER - all args passed to lexicon + -l|--lexicon-help Show lexicon -h|--help - -h|--help passed to lexicon + +Env vars:: +---------- + + VENV_BASE Base dir to find lexicon venv dir. + Default: $VENV_FALLBACK + DEBUG Set to enable verbose output - useful for debugging +EOF + exit 1 +} + +lexicon_bin() { + source "$VENV_BASE/lexicon/bin/activate" + "$VENV_BASE/lexicon/bin/lexicon" $(printf '%q ' "$@") +} + +if [[ "$(id -u)" -ne 0 ]]; then + echo "FATAL: $(basename "$0") must be run as root, please re-run with sudo" +fi + +if [[ ! -e "$VENV_BASE" ]]; then + no_venv "VENV_BASE ($VENV_BASE) does not exist" +elif [[ ! -d "$VENV_BASE" ]]; then + no_venv "VENV_BASE ($VENV_BASE) exists but is a file - please remove first" +elif [[ ! -d "$VENV_BASE/lexicon" ]]; then + no_venv "lexicon venv ($VENV_BASE/lexicon) does not exist (or is not a directory)" +elif [[ ! -f "$VENV_BASE/lexicon/bin/activate" ]] \ + || [[ ! -x "$VENV_BASE/lexicon/bin/lexicon" ]]; then + no_venv "lexicon venv executables missing" +fi +chown -R root:root "$VENV_BASE" + +if [[ "$#" == 1 ]]; then + case $1 in + -h|--help) usage;; + -l|--lexicon-help) lexicon_bin --help;; + *) lexicon_bin "$1";; + esac +else + # note: double quotes around $@ prevents globbing and word splitting of + # individual elements, while still expanding to multiple separate args + lexicon_bin "$@" +fi diff --git a/debian/confconsole/usr/lib/confconsole/conf b/debian/confconsole/usr/lib/confconsole/conf new file mode 120000 index 0000000..43bcbf7 --- /dev/null +++ b/debian/confconsole/usr/lib/confconsole/conf @@ -0,0 +1 @@ +/etc/confconsole \ No newline at end of file diff --git a/debian/confconsole/usr/lib/confconsole/conf.py b/debian/confconsole/usr/lib/confconsole/conf.py new file mode 100644 index 0000000..509a4f4 --- /dev/null +++ b/debian/confconsole/usr/lib/confconsole/conf.py @@ -0,0 +1,71 @@ +# Copyright (c) 2008-2019 Alon Swartz +# - all rights reserved +# Copyright (c) 2020 TurnKey GNU/Linux +# - all rights reserved + +import re +import os + + +class ConfconsoleConfError(Exception): + pass + + +def path(filename: str) -> str: + for dir in ("conf", "/etc/confconsole"): + path = os.path.join(dir, filename) + if os.path.exists(path): + return path + + raise ConfconsoleConfError( + f"could not find configuration file: {filename}" + ) + + +class Conf: + default_nic: str | None + publicip_cmd: str | None + networking: bool + copy_paste: bool + conf_file: str + + def _load_conf(self) -> None: + if not self.conf_file or not os.path.exists(self.conf_file): + return + + with open(self.conf_file) as fob: + for line in fob: + line = line.strip() + + if not line or line.startswith("#"): + continue + + op, val = re.split(r"\s+", line, 1) + if op == "default_nic": + self.default_nic = val + elif op == "publicip_cmd": + self.publicip_cmd = val + elif op == "networking" and val in ("true", "false"): + self.networking = True if val == "true" else False + elif op == "autostart": + pass + elif op == "copy_paste" and val.lower() in ("true", "false"): + self.copy_paste = True if val.lower() == "true" else False + else: + raise ConfconsoleConfError( + f"illegal configuration line: {line}" + ) + + def __init__(self) -> None: + self.default_nic = None + self.publicip_cmd = None + self.networking = True + self.copy_paste = True + self.conf_file = path("confconsole.conf") + self._load_conf() + + def set_default_nic(self, ifname: str) -> None: + self.default_nic = ifname + + with open(self.conf_file, "w") as fob: + fob.write(f"default_nic {ifname}\n") diff --git a/debian/confconsole/usr/lib/confconsole/confconsole.py b/debian/confconsole/usr/lib/confconsole/confconsole.py new file mode 100755 index 0000000..276d0af --- /dev/null +++ b/debian/confconsole/usr/lib/confconsole/confconsole.py @@ -0,0 +1,919 @@ +#! /usr/bin/python3 +# Copyright (c) 2008 Alon Swartz - all rights reserved +"""TurnKey Configuration Console + +Options: + -h, --help Display this help and exit + --usage Display usage screen without Advanced Menu + --nointeractive Do not display interactive dialog + --plugin= Run plugin directly + +""" + +import os +import sys +import subprocess +from subprocess import CalledProcessError +import getopt +import shlex +from string import Template +from io import StringIO +import traceback + +import dialog +from dialog import DialogError +import netinfo + +import ipaddr +import ifutil +import conf +import plugin + +from typing import NoReturn, Iterable, Any + +USAGE: str = __doc__ if __doc__ else "" +PLUGIN_PATH = os.path.join( + os.path.dirname(os.path.realpath(__file__)), "plugins.d" +) + + +class ConfconsoleError(Exception): + pass + + +def fatal(msg: str) -> NoReturn: + print(f"Error: {msg}", file=sys.stderr) + sys.exit(1) + + +def usage(msg: str | getopt.GetoptError = "") -> NoReturn: + if msg: + print(f"Error: {msg}", file=sys.stderr) + + print(f"Syntax: {sys.argv[0]}", file=sys.stderr) + print(USAGE.strip(), file=sys.stderr) + sys.exit(1) + + +def format_fields( + fields: Iterable[tuple[str, str, int, int]], +) -> list[tuple[str, int, int, str, int, int, int, int]]: + """Takes fields in format (label, field, label_length, field_length) and + outputs fields in format (label, ly, lx, item, iy, ix, field_length, + input_length) + """ + out = [] + for i, (label, field, l_length, f_length) in enumerate(fields): + out.append( + (label, i + 1, 1, field, i + 1, l_length + 1, l_length, f_length) + ) + return out + + +WrapperReturn = str | tuple[str, str] + + +class Console: + def __init__( + self, + title: str | None = None, + width: int = 60, + height: int = 20, + ) -> None: + self.width = width + self.height = height + + self.console = dialog.Dialog(dialog="dialog") + self.console.add_persistent_args(["--no-collapse"]) + self.console.add_persistent_args(["--ok-label", "Select"]) + self.console.add_persistent_args(["--cancel-label", "Back"]) + self.console.add_persistent_args(["--colors"]) + if conf.Conf().copy_paste: + self.console.add_persistent_args(["--no-mouse"]) + if title: + self.console.add_persistent_args(["--backtitle", title]) + + def _handle_exitcode(self, retcode: str) -> bool: + if retcode == "esc": + text = "Do you really want to quit?" + if self.console.yesno(text) == self.console.OK: + sys.exit(0) + return False + return True + + def _wrapper( + self, + dialog: str, + text: str, + *args: Any, + **kws: Any, + ) -> WrapperReturn: + try: + method = getattr(self.console, dialog) + except AttributeError: + raise ConfconsoleError(f"dialog not supported: {dialog}") + + ret: WrapperReturn = "" + + while 1: + try: + ret = method(f"\n{text}", *args, **kws) + except DialogError as e: + if "Can't make new window" in e.message: + self.console.msgbox( + "Terminal too small for UI, resize terminal and" + " press OK", + ok_label="OK", + ) + continue + else: + raise + + if type(ret) is str: + retcode = ret + else: + retcode = ret[0] + + if self._handle_exitcode(retcode): + break + + return ret + + def infobox(self, text: str) -> str: + v = self._wrapper("infobox", text) + assert isinstance(v, str) + return v + + def yesno(self, text: str, autosize: bool = False) -> str: + if autosize: + text += "\n " + height, width = 0, 0 + else: + height, width = 10, 30 + v = self._wrapper("yesno", text, height, width) + assert isinstance(v, str) + return v + + def msgbox( + self, + title: str, + text: str, + button_label: str = "ok", + autosize: bool = False, + ) -> str: + if autosize: + text += "\n " + height, width = 0, 0 + else: + height, width = self.height, self.width + + v = self._wrapper( + "msgbox", text, height, width, title=title, ok_label=button_label + ) + assert isinstance(v, str) + return v + + def inputbox( + self, + title: str, + text: str, + init: str = "", + ok_label: str = "OK", + cancel_label: str = "Cancel", + ) -> tuple[str, str]: + no_cancel = True if cancel_label == "" else False + v = self._wrapper( + "inputbox", + text, + self.height, + self.width, + title=title, + init=init, + ok_label=ok_label, + cancel_label=cancel_label, + no_cancel=no_cancel, + ) + assert isinstance(v, tuple) + return v + + def menu( + self, + title: str, + text: str, + choices: list[tuple[str, str]], + no_cancel: bool = False, + ) -> tuple[str, str]: + v = self._wrapper( + "menu", + text, + self.height, + self.width, + menu_height=len(choices) + 1, + title=title, + choices=choices, + no_cancel=no_cancel, + ) + assert isinstance(v, tuple) + return v + + def form( + self, + title: str, + text: str, + fields: list[tuple[str, int, int, str, int, int, int, int]], + ok_label: str = "Apply", + cancel_label: str = "Cancel", + autosize: bool = False, + ) -> tuple[str, str]: + if autosize: + text += "\n " + height, width = 0, 0 + else: + height, width = self.height, self.width + v = self._wrapper( + "form", + text, + fields, + height=height, + width=width, + form_height=len(fields) + 1, + title=title, + ok_label=ok_label, + cancel_label=cancel_label, + ) + assert isinstance(v, tuple) + return v + + +class Installer: + def __init__(self, path: str) -> None: + self.path: str = path + self.available: bool = self._is_available() + + def _is_available(self) -> bool: + if not os.path.exists(self.path): + return False + + with open("/proc/cmdline") as fob: + return "boot=live" in fob.readline().split() + + def execute(self) -> None: + if not self.available: + raise ConfconsoleError("installer is not available to be executed") + + subprocess.run([self.path]) + + +class TurnkeyConsole: + OK = "ok" + CANCEL = 1 + + def __init__( + self, + pluginManager: plugin.PluginManager, + eventManager: plugin.EventManager, + advanced_enabled: bool = True, + ) -> None: + title = "TurnKey GNU/Linux Configuration Console" + self.width = 60 + self.height = 20 + + self.console = Console(title, self.width, self.height) + + # sometimes it would be nice to have the appname be something other + # than the hostname. Allow developers to create file containing the + # appname in /etc/appname + try: + with open("/etc/appname", 'r') as fob: + self.appname = fob.read().rstrip() + except FileNotFoundError: + self.appname = f"TurnKey Linux {netinfo.get_hostname().upper()}" + + self.installer = Installer(path="/usr/bin/di-live") + + self.advanced_enabled = advanced_enabled + + self.eventManager = eventManager + self.pluginManager = pluginManager + self.pluginManager.updateGlobals({"console": self.console}) + + @staticmethod + def _get_filtered_ifnames() -> list[str]: + ifnames = [] + for ifname in netinfo.get_ifnames(): + if ifname.startswith( + ("lo", "tap", "br", "natbr", "tun", "vmnet", "veth", "wmaster") + ): + continue + ifnames.append(ifname) + + # handle bridged LXC where br0 is the default outward-facing interface + defifname = conf.Conf().default_nic + if defifname and defifname.startswith("br"): + ifnames.append(defifname) + bridgedif = ( + subprocess.run( + ["brctl", "show", defifname], + capture_output=True, + text=True, + ) + .stdout.split("\n")[1] + .split("\t")[-1] + ) + ifnames.remove(bridgedif) + + ifnames.sort() + return ifnames + + @classmethod + def _get_default_nic(cls) -> str | None: + 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 + if defifname and _validip(defifname): + return defifname + + for ifname in cls._get_filtered_ifnames(): + if _validip(ifname): + return ifname + + return None + + @classmethod + def _get_public_ipaddr(cls) -> str | None: + publicip_cmd = conf.Conf().publicip_cmd + if publicip_cmd: + command = subprocess.run( + shlex.split(publicip_cmd), + capture_output=True, + text=True, + ) + if command.returncode == 0: + return command.stdout.strip() + + return None + + def _get_advmenu( + self, + ) -> tuple[ + list[tuple[str, str]], dict[str, plugin.Plugin | plugin.PluginDir] + ]: + items = [] + if conf.Conf().networking: + items.append(("Networking", "Configure appliance networking")) + + if self.installer.available: + items.append(("Install", "Install to hard disk")) + + plugin_map = {} + + for path in self.pluginManager.path_map: + plug = self.pluginManager.path_map[path] + if os.path.dirname(path) == PLUGIN_PATH: + if isinstance(plug, plugin.Plugin) and hasattr( + plug.module, "run" + ): + items.append( + ( + plug.module_name.capitalize(), + str(plug.module.__doc__), + ) + ) + elif isinstance(plug, plugin.PluginDir): + items.append( + (plug.module_name.capitalize(), plug.description) + ) + plugin_map[plug.module_name.capitalize()] = plug + + items.append(("Reboot", "Reboot the appliance")) + items.append(("Shutdown", "Shutdown the appliance")) + items.append(("Quit", "Quit the configuration console")) + + return items, plugin_map + + def _get_netmenu(self) -> list[tuple[str, str]]: + menu = [] + for ifname in self._get_filtered_ifnames(): + addr = ifutil.get_ipconf(ifname)[0] + ifmethod = ifutil.get_ifmethod(ifname) + + if addr: + desc = addr + if ifmethod: + desc += f" ({ifmethod})" + + if ifname == self._get_default_nic(): + desc += " [*]" + else: + desc = "not configured" + + menu.append((ifname, desc)) + + return menu + + def _get_ifconfmenu(self, ifname: str) -> list[tuple[str, str]]: + menu = [] + menu.append(("DHCP", "Configure networking automatically")) + menu.append(("StaticIP", "Configure networking manually")) + + if ( + not ifname == self._get_default_nic() + and len(self._get_filtered_ifnames()) > 1 + and ifutil.get_ipconf(ifname)[0] is not None + ): + menu.append(("Default", "Show this adapter's IP address in Usage")) + + return menu + + def _get_ifconftext(self, ifname: str) -> str: + addr, netmask, gateway, nameservers = ifutil.get_ipconf(ifname) + if addr is None: + return "Network adapter is not configured\n" + + 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" + 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: + text += f"Networking configuration method: {ifmethod}\n" + + if len(self._get_filtered_ifnames()) > 1: + text += "Is this adapter's IP address displayed in Usage: " + if ifname == self._get_default_nic(): + text += "yes\n" + else: + text += "no\n" + + return text + + def usage(self) -> str: + if self.advanced_enabled: + default_button_label = "Advanced Menu" + default_return_value = "advanced" + else: + default_button_label = "Quit" + default_return_value = "quit" + + # if no interfaces at all - display error and go to advanced + if len(self._get_filtered_ifnames()) == 0: + error = "No network adapters detected" + if not self.advanced_enabled: + fatal(error) + + self.console.msgbox("Error", error) + return "advanced" + + # if interfaces but no default - display error and go to networking + ifname = self._get_default_nic() + if not ifname: + error = "Networking is not yet configured" + if not self.advanced_enabled: + fatal(error) + + self.console.msgbox("Error", error) + return "networking" + + # tklbam integration + tklbamstatus_cmd = subprocess.run( + ["which", "tklbam-status"], + capture_output=True, + text=True, + ).stdout.strip() + if tklbamstatus_cmd: + tklbam_status = subprocess.run( + [tklbamstatus_cmd, "--short"], + capture_output=True, + text=True, + ).stdout + else: + tklbam_status = ( + "TKLBAM not found - please check that it's installed." + ) + + # display usage + ip_addr = self._get_public_ipaddr() + if not ip_addr: + ip_addr = ifutil.get_ipconf(ifname)[0] + + hostname = netinfo.get_hostname().upper() + + 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) + except conf.ConfconsoleConfError: + 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" + text += " https://hub.turnkeylinux.org" + + retcode = self.console.msgbox( + f"{hostname} appliance services", + text, + button_label=default_button_label, + ) + + if retcode is not self.OK: + self.running = False + + return default_return_value + + def advanced(self) -> str: + # dont display cancel button when no interfaces at all + no_cancel = False + if len(self._get_filtered_ifnames()) == 0: + no_cancel = True + + items, plugin_map = self._get_advmenu() + + retcode, choice = self.console.menu( + "Advanced Menu", + self.appname + " Advanced Menu\n", + items, + no_cancel=no_cancel, + ) + + if retcode is not self.OK: + return "usage" + + if choice in plugin_map: + return plugin_map[choice].path + + return "_adv_" + choice.lower() + + def networking(self) -> str: + ifnames = self._get_filtered_ifnames() + + # if no interfaces at all - display error and go to advanced + if len(ifnames) == 0: + self.console.msgbox("Error", "No network adapters detected") + return "advanced" + + # if only 1 interface, dont display menu - just configure it + if len(ifnames) == 1: + self.ifname = ifnames[0] + return "ifconf" + + # display networking + text = "Choose network adapter to configure\n" + if self._get_default_nic(): + text += "[*] This adapter's IP address is displayed in Usage" + + retcode, self.ifname = self.console.menu( + "Networking configuration", text, self._get_netmenu() + ) + + if retcode is not self.OK: + return "advanced" + + return "ifconf" + + def ifconf(self) -> str: + retcode, choice = self.console.menu( + f"{self.ifname} configuration", + self._get_ifconftext(self.ifname), + self._get_ifconfmenu(self.ifname), + ) + + if retcode is not self.OK: + # if multiple interfaces go back to networking + if len(self._get_filtered_ifnames()) > 1: + return "networking" + + return "advanced" + + return "_ifconf_" + choice.lower() + + def _ifconf_staticip(self) -> str: + def _validate( + addr: str, netmask: str, gateway: str, nameservers: list[str] + ) -> list[str]: + """Validate Static IP form parameters. Returns an empty array on + success, an array of strings describing errors otherwise""" + + errors = [] + if not addr: + errors.append("No IP address provided") + elif not ipaddr.is_legal_ip(addr): + errors.append(f"Invalid IP address: {addr}") + + if not netmask: + errors.append("No netmask provided") + elif not ipaddr.is_legal_ip(netmask): + errors.append(f"Invalid netmask: {netmask}") + + for nameserver in nameservers: + if nameserver and not ipaddr.is_legal_ip(nameserver): + errors.append(f"Invalid nameserver: {nameserver}") + + if len(nameservers) != len(set(nameservers)): + errors.append("Duplicate nameservers specified") + + if errors: + return errors + + if gateway: + if not ipaddr.is_legal_ip(gateway): + return [f"Invalid gateway: {gateway}"] + else: + iprange = ipaddr.IPRange(addr, netmask) + if gateway not in iprange: + return [ + f"Gateway ({gateway}) not in IP range ({iprange})" + ] + return [] + + warnings = [] + addr = None + netmask = None + gateway = None + nameservers = None + try: + addr, netmask, gateway, nameservers = ifutil.get_ipconf( + self.ifname, True + ) + except CalledProcessError: + warnings.append( + "`route -n` returned non-0 exit code! (unable to get gateway)" + ) + except netinfo.NetInfoError: + warnings.append("failed to find default gateway!") + addr, netmask, gateway, nameservers = ifutil.get_ipconf( + self.ifname, False + ) + + if addr is None: + warnings.append("failed to ascertain current address!") + addr = "" + if netmask is None: + warnings.append("failed to ascertain current netmask!") + netmask = "" + if gateway is None: + gateway = "" + if nameservers is None: + nameservers = [] + + if warnings: + warnings.append("\nWill leave relevant fields blank") + + if warnings: + self.console.msgbox("Warning", "\n".join(warnings)) + + value = [addr, netmask, gateway] + value.extend(nameservers) + + # include minimum 2 nameserver fields and 1 blank one + if len(value) < 4: + value.append("") + + if value[-1]: + value.append("") + + field_width = 30 + field_limit = 15 + + while 1: + pre_fields: list[tuple[str, str, int, int]] = [ + ("IP Address", value[0], field_width, field_limit), + ("Netmask", value[1], field_width, field_limit), + ("Default Gateway", value[2], field_width, field_limit), + ] + + for i in range(len(value[3:])): + pre_fields.append( + ("Name Server", value[3 + i], field_width, field_limit) + ) + + fields: list[tuple[str, int, int, str, int, int, int, int]] = ( + format_fields(pre_fields) + ) + text = f"Static IP configuration ({self.ifname})" + retcode, input = self.console.form( + "Network settings", text, fields + ) + + if retcode is not self.OK: + break + + # remove any whitespaces the user might of included + input = list(map(str.strip, input)) + + # unconfigure the nic if all entries are empty + if not input[0] and not input[1] and not input[2] and not input[3]: + ifutil.unconfigure_if(self.ifname) + break + + addr, netmask, gateway = input[:3] + nameservers = input[3:] + for i in range(nameservers.count("")): + nameservers.remove("") + + err_parts = _validate(addr, netmask, gateway, nameservers) + if err_parts: + err: str = "\n".join(err_parts) + self.console.msgbox("Error", err) + else: + in_ssh = "SSH_CONNECTION" in os.environ + if not in_ssh or ( + in_ssh + and self.console.yesno( + "Warning: Changing ip while an ssh session is active" + " will drop said ssh session!", + autosize=True, + ) + == self.OK + ): + maybe_err: str | None = ifutil.set_static( + self.ifname, addr, netmask, gateway, nameservers + ) + if maybe_err is None: + break + self.console.msgbox("Error", maybe_err) + else: + break + + return "ifconf" + + def _ifconf_dhcp(self) -> str: + in_ssh = "SSH_CONNECTION" in os.environ + if not in_ssh or ( + in_ssh + and self.console.yesno( + "Warning: Changing ip while an ssh session is active will" + " drop said ssh session!", + autosize=True, + ) + == self.OK + ): + self.console.infobox(f"Requesting DHCP for {self.ifname}...") + err = ifutil.set_dhcp(self.ifname) + if err: + self.console.msgbox("Error", err) + + return "ifconf" + + def _ifconf_default(self) -> str: + conf.Conf().set_default_nic(self.ifname) + return "ifconf" + + def _adv_install(self) -> str: + text = "Please note that any changes you may have made to the\n" + text += "live system will *not* be installed to the hard disk.\n\n" + self.console.msgbox("Installer", text) + + self.installer.execute() + return "advanced" + + def _shutdown(self, text: str, opt: str) -> str: + if self.console.yesno(text) == self.OK: + self.running = False + cmd = f"shutdown {opt} now" + fgvt = os.environ.get("FGVT") + if fgvt: + cmd = f"chvt {fgvt}; " + cmd + os.system(cmd) + + return "advanced" + + def _adv_reboot(self) -> str: + return self._shutdown("Reboot the appliance?", "-r") + + def _adv_shutdown(self) -> str: + return self._shutdown("Shutdown the appliance?", "-h") + + def _adv_quit(self) -> str: + if not self.advanced_enabled: + self.running = False + return "usage" + + if ( + self.console.yesno("Do you really want to quit?", autosize=True) + == self.OK + ): + self.running = False + + return "advanced" + + _adv_networking = networking + quit = _adv_quit + + def loop(self, dialog: str | Any | None = "usage") -> None: + self.running = True + prev_dialog = dialog + standalone = dialog != "usage" # no "back" for plugins + + while dialog and self.running: + try: + if not dialog.startswith(PLUGIN_PATH): + try: + method = getattr(self, dialog) + except AttributeError: + raise ConfconsoleError( + f"dialog not supported: {dialog}" + ) + else: + try: + method = self.pluginManager.path_map[dialog].run + except KeyError: + raise ConfconsoleError( + f"could not find plugin dialog: {dialog}" + ) + + new_dialog = method() + if standalone: # XXX This feels dirty + break + prev_dialog = dialog + dialog = new_dialog + + except Exception: # TODO should only catch specific errors + sio = StringIO() + traceback.print_exc(file=sio) + + self.console.msgbox("Caught exception", sio.getvalue()) + dialog = prev_dialog + + +def main() -> None: + interactive = True + advanced_enabled = True + plugin_name = None + + if os.geteuid() != 0: + fatal("confconsole needs root privileges to run") + + try: + l_opts = ["help", "usage", "nointeractive", "plugin="] + opts, _ = getopt.gnu_getopt(sys.argv[1:], "hn", l_opts) + except getopt.GetoptError as e: + usage(e) + + for opt, val in opts: + if opt in ("-h", "--help"): + usage() + elif opt == "--usage": + advanced_enabled = False + elif opt == "--nointeractive": + interactive = False + elif opt == "--plugin": + plugin_name = val + else: + usage() + + em = plugin.EventManager() + pm = plugin.PluginManager( + PLUGIN_PATH, {"eventManager": em, "interactive": interactive} + ) + + if plugin_name: + ps = list( + filter( + lambda x: isinstance(x, plugin.Plugin), + pm.getByName(plugin_name), + ) + ) + + if len(ps) > 1: + fatal(f"plugin name ambiguous, matches all of {ps}") + elif len(ps) == 1: + p = ps[0] + + if interactive: + tc = TurnkeyConsole(pm, em, advanced_enabled) + tc.loop(dialog=p.path) # calls .run() + else: + assert isinstance(p, plugin.Plugin) + p.module.run() + else: + fatal("no such plugin") + else: + tc = TurnkeyConsole(pm, em, advanced_enabled) + tc.loop() + + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + subprocess.run(["stty", "sane"]) + traceback.print_exc() diff --git a/debian/confconsole/usr/lib/confconsole/ifutil.py b/debian/confconsole/usr/lib/confconsole/ifutil.py new file mode 100644 index 0000000..9537897 --- /dev/null +++ b/debian/confconsole/usr/lib/confconsole/ifutil.py @@ -0,0 +1,467 @@ +from dataclasses import dataclass, field +import subprocess +from time import sleep +import os +import re + +from netinfo import InterfaceInfo +from netinfo import get_hostname + + +class IfError(Exception): + pass + + +class InvalidIPv4Error(IfError): + pass + + +class ManuallyConfiguredError(IfError): + pass + + +class InterfaceNotFoundError(IfError): + pass + + +class BadIfConfigError(IfError): + pass + + +IPV4_RE = r"^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})(.*)$" +IPV4_CIDR = r"^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})(.*)$" + + +def _preprocess_interface_config(config: str) -> list[str]: + """Process and Validate Networking Interface""" + lines = config.splitlines() + new_lines = [] + hostname = get_hostname() + + for line in lines: + _line = line.strip() + + if _line.startswith(("allow-hotplug", "auto", "iface", "wpa-conf")): + new_lines.append(line) + elif _line.startswith("hostname"): + if hostname: + new_lines.append(f" hostname {hostname}") + else: + continue + elif _line.startswith("post-up"): + new_lines.append(f" {_line}") + elif _line.startswith( + ("address", "netmask", "gateway", "dns-nameserver") + ): + continue + else: + raise BadIfConfigError(f"Unexpected config line: {line}") + if len(new_lines) == 2 and hostname: + new_lines.append(f" hostname {hostname}") + return new_lines + + +@dataclass +class IPv4: + p0: int + p1: int + p2: int + p3: int + + @classmethod + def parse(cls, value: str) -> "IPv4": + matches = re.match(IPV4_RE, value.strip()) + if not matches: + raise InvalidIPv4Error(f"{value!r} is not a valid IPv4") + if matches.group(5): + raise InvalidIPv4Error( + f"{value!r} is not a valid IPv4 (junk after ip segments)" + ) + + ip = cls( + int(matches.group(1)), + int(matches.group(2)), + int(matches.group(3)), + int(matches.group(4)), + ) + + if ip.p0 < 0 or ip.p0 > 255: + raise InvalidIPv4Error( + f"{value!r} is not a valid IPv4 ({ip.p0} not in range 0-255" + ) + if ip.p1 < 0 or ip.p1 > 255: + raise InvalidIPv4Error( + f"{value!r} is not a valid IPv4 ({ip.p1} not in range 0-255" + ) + if ip.p2 < 0 or ip.p2 > 255: + raise InvalidIPv4Error( + f"{value!r} is not a valid IPv4 ({ip.p2} not in range 0-255" + ) + if ip.p3 < 0 or ip.p3 > 255: + raise InvalidIPv4Error( + f"{value!r} is not a valid IPv4 ({ip.p3} not in range 0-255" + ) + return ip + + def __str__(self) -> str: + return f"{self.p0}.{self.p1}.{self.p2}.{self.p3}" + + +class NetworkInterfaces: + HEADER_UNCONFIGURED = "# UNCONFIGURED INTERFACES" + CONF_FILE = "/etc/network/interfaces" + + conf: dict[str, list[str]] = {} + unconfigured: bool = True + + _iface_opts = ["pre-up", "up", "post-up", "pre-down", "down", "post-down"] + + _bridge_opts = [ + "bridge_ports", + "bridge_ageing", + "bridge_bridgeprio", + "bridge_fd", + "bridge_gcinit", + "bridge_hello", + "bridge_hw", + "bridge_maxage", + "bridge_maxwait", + "bridge_pathcost", + "bridge_portprio", + "bridge_stp", + "bridge_waitport", + ] + + def _get_opts_subset(self, ifname: str, opts: list[str]) -> list[str]: + if ifname not in self.conf: + raise InterfaceNotFoundError(f"no existing config for {ifname}") + return [ + line.strip() + for line in self.conf[ifname] + if line.strip().split()[0] in opts + ] + + def get_iface_opts(self, ifname: str) -> list[str]: + return self._get_opts_subset(ifname, self._iface_opts) + + def get_bridge_opts(self, ifname: str) -> list[str]: + return self._get_opts_subset(ifname, self._bridge_opts) + + def duplicate(self) -> "NetworkInterfaces": + interfaces = NetworkInterfaces() + interfaces.unconfigured = self.unconfigured + interfaces.conf = { + key: [i for i in value] for key, value in self.conf.items() + } + return interfaces + + def read(self) -> None: + # clear config + self.conf = {} + self.unconfigured = False + + ifname: str | None = None + + with open(self.CONF_FILE) as fob: + for line in fob: + line = line.rstrip() + + if line == self.HEADER_UNCONFIGURED: + self.unconfigured = True + + if not line or line.startswith("#"): + continue + + if line.startswith("auto") or line.startswith("allow-hotplug"): + ifname = line.split()[1] + self.conf[ifname] = [line] + elif ifname: + self.conf[ifname].append(line) + + def write(self) -> None: + if not self.unconfigured: + raise ManuallyConfiguredError( + f"refusing to write to {self.CONF_FILE}\n" + f"header not found: {self.HEADER_UNCONFIGURED}" + ) + + with open(self.CONF_FILE, "w") as fob: + fob.write(self.HEADER_UNCONFIGURED + "\n") + for iface in self.conf.keys(): + fob.write("\n\n") + fob.write("\n".join(self.conf[iface])) + fob.write("\n") + + def gen_default_if_config(self, ifname: str) -> None: + if ifname.startswith("e"): + self.conf[ifname] = _preprocess_interface_config( + f"auto {ifname}\niface {ifname} inet dhcp" + ) + else: + raise InterfaceNotFoundError(f"no existing config for {ifname}") + + def set_dhcp(self, ifname: str) -> None: + if ifname not in self.conf: + self.gen_default_if_config(ifname) + + ifconf = _preprocess_interface_config("\n".join(self.conf[ifname])) + ifconf[1] = f"iface {ifname} inet dhcp" + self.conf[ifname] = ifconf + + self.write() + + def set_manual(self, ifname: str) -> None: + if ifname not in self.conf: + self.gen_default_if_config(ifname) + + ifconf = _preprocess_interface_config("\n".join(self.conf[ifname])) + ifconf[1] = f"iface {ifname} inet manual" + self.conf[ifname] = ifconf + + self.write() + + def set_static( + self, + ifname: str, + addr: str, + netmask: str, + gateway: str | None = None, + nameservers: list[str] | None = None, + ) -> None: + if ifname not in self.conf: + self.gen_default_if_config(ifname) + + ifconf = _preprocess_interface_config("\n".join(self.conf[ifname])) + ifconf[1] = f"iface {ifname} inet static" + + ifconf.extend([f" address {addr}", f" netmask {netmask}"]) + + if gateway: + ifconf.append(f" gateway {gateway}") + if nameservers: + joined_nameservers = " ".join(nameservers) + ifconf.append(f" dns-nameservers {joined_nameservers}") + + self.conf[ifname] = ifconf + self.write() + + def get_if_conf(self, ifname: str, key: str) -> list[str] | None: + if ifname in self.conf: + for line in self.conf: + line_list = line.strip().split() + if line_list[0] == key: + return line_list[1:] + + def get_nameservers(self, ifname: str) -> list[str] | None: + return self.get_if_conf(ifname, "dns-nameservers") or [] + + def get_address(self, ifname: str) -> str | None: + addr = self.get_if_conf(ifname, "address") + if addr: + return addr[0] + + def get_netmask(self, ifname: str) -> str | None: + addr = self.get_if_conf(ifname, "netmask") + if addr: + return addr[0] + + +def _parse_resolv(path: str) -> list[str]: + nameservers = [] + with open(path) as fob: + for line in fob: + if line.startswith("nameserver"): + nameservers.append(line.strip().split()[1]) + return nameservers + + +def get_nameservers(ifname: str) -> list[str]: + # /etc/network/interfaces + interfaces = NetworkInterfaces() + interfaces.read() + + nameservers = interfaces.get_nameservers(ifname) + if nameservers: + return nameservers + + # resolvconf (dhcp) + path = "/etc/resolvconf/run/interface" + if os.path.exists(path): + for f in os.listdir(path): + if not f.startswith(ifname) or f.endswith(".inet"): + continue + + nameservers = _parse_resolv(os.path.join(path, f)) + if nameservers: + return nameservers + + # /etc/resolv.conf (fallback) + return _parse_resolv("/etc/resolv.conf") + + +def ifup(ifname: str, force: bool = False) -> str: + # force is not the same as --force. Here force will configure regardless of + # errors + + if force: + ifup_args = ["/usr/sbin/ifup", "--force", "--ignore-errors", ifname] + else: + ifup_args = ["/usr/sbin/ifup", "--force", ifname] + + ifup_cmd = subprocess.run(ifup_args, capture_output=True, text=True) + + if not force and ifup_cmd.returncode != 0: + raise BadIfConfigError( + f"failed to bring up interface {ifname!r} error:" + f" {ifup_cmd.stderr!r}" + ) + return ifup_cmd.stderr + + +def ifdown(ifname: str, force: bool = False) -> str: + # force is not the same as --force. Here force will configure regardless of + # errors + + if force: + ifdown_args = [ + "/usr/sbin/ifdown", "--force", "--ignore-errors", ifname + ] + else: + ifdown_args = ["/usr/sbin/ifdown", "--force", ifname] + + ifdown_cmd = subprocess.run(ifdown_args, capture_output=True, text=True) + + if ifdown_cmd.returncode != 0: + raise BadIfConfigError( + f"failed to bring down interface {ifname!r}" + f" error: {ifdown_cmd.stderr!r}" + ) + return ifdown_cmd.stderr + + +def unconfigure_if(ifname: str) -> str | None: + try: + ifdown(ifname) + except Exception as e: + return str(e) + + interfaces = NetworkInterfaces() + interfaces.read() + backup_interfaces = interfaces.duplicate() + interfaces.set_manual(ifname) + + try: + subprocess.check_output(["/usr/sbin/ifconfig", ifname, "0.0.0.0"]) + except subprocess.CalledProcessError as e: + return str(e) + + try: + ifup(ifname) + except Exception as e: + backup_interfaces.write() + ifup(ifname, force=True) + + return str(e) + + +def set_static( + ifname: str, addr: str, netmask: str, gateway: str, nameservers: list[str] +) -> str | None: + try: + addr = str(IPv4.parse(addr)) + netmask = str(IPv4.parse(netmask)) + gateway = str(IPv4.parse(gateway)) + nameservers = [ + str(IPv4.parse(nameserver)) for nameserver in nameservers + ] + + ifdown(ifname, True) + + interfaces = NetworkInterfaces() + interfaces.read() + backup_interfaces = interfaces.duplicate() + + try: + interfaces.set_static(ifname, addr, netmask, gateway, nameservers) + sleep(0.5) + except Exception as e: + backup_interfaces.write() + raise e + finally: + output = ifup(ifname, True) + + net = InterfaceInfo(ifname) + if not net.address: + raise IfError(f"Error obtaining IP address\n\n{output}") + + return None + except Exception as e: # TODO - this is essentially a bare except + return str(e) + + +def set_dhcp(ifname: str) -> str | None: + try: + ifdown(ifname, True) + + interfaces = NetworkInterfaces() + interfaces.read() + backup_interfaces = interfaces.duplicate() + try: + interfaces.set_dhcp(ifname) + except Exception as e: + backup_interfaces.write() + raise e + finally: + output = ifup(ifname, True) + 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 + except Exception as e: + return str(e) + + +def get_ipconf( + ifname: str, error: bool = False +) -> tuple[str | None, str | None, str | None, list[str]]: + net = InterfaceInfo(ifname) + for _ in range(6): + net = InterfaceInfo(ifname) + if net.address is not None and net.netmask is not None: + gateway = net.get_gateway(error) + return (net.address, net.netmask, gateway, get_nameservers(ifname)) + sleep(0.1) + + # no interfaces up + 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() + conf_line = interfaces.get_if_conf(ifname, "iface") + if conf_line: + return conf_line[3] diff --git a/debian/confconsole/usr/lib/confconsole/ipaddr.py b/debian/confconsole/usr/lib/confconsole/ipaddr.py new file mode 100644 index 0000000..4999015 --- /dev/null +++ b/debian/confconsole/usr/lib/confconsole/ipaddr.py @@ -0,0 +1,105 @@ +# Copyright (c) 2009 Liraz Siri - all rights reserved + +import struct +import socket +import math +from typing import Type, Union + + +def is_legal_ip(ip: str) -> bool: + try: + if ( + len([octet for octet in ip.split(".") if 255 >= int(octet) >= 0]) + != 4 + ): + return False + except ValueError: + return False + + try: + _ = socket.inet_aton(ip) + except socket.error: + return False + + return True + + +AnyIP = Union[int, str, "IP"] + + +def _str2int(ip: str) -> int: + bytes = list(map(int, ip.split("."))) + out: int = struct.unpack("!L", struct.pack("BBBB", *bytes))[0] + return out + + +def _int2str(num: int) -> str: + bytes = struct.unpack("BBBB", struct.pack("!L", num)) + return ".".join(list(map(str, bytes))) + + +class Error(Exception): + pass + + +class IP(int): + def __new__(cls: Type["IP"], arg: AnyIP) -> "IP": + if isinstance(arg, IP): + return int.__new__(cls, int(arg)) + + elif isinstance(arg, int): + return int.__new__(cls, arg) + + else: + if not is_legal_ip(arg): + raise Error(f"illegal ip ({arg})") + + return int.__new__(cls, _str2int(arg)) + + def __str__(self) -> str: + return _int2str(self) + + def __repr__(self) -> str: + return f"IP({str(self)})" + + def __add__(self, other: int) -> "IP": + return IP(int.__add__(self, other)) + + def __sub__(self, other: int) -> "IP": + return IP(int.__sub__(self, other)) + + def __and__(self, other: int) -> "IP": + return IP(int.__and__(self, other)) + + def __or__(self, other: int) -> "IP": + return IP(int.__or__(self, other)) + + def __xor__(self, other: int) -> "IP": + return IP(int.__xor__(self, other)) + + +class IPRange: + @classmethod + def from_cidr(cls: Type["IPRange"], arg: str) -> "IPRange": + address, cidr = arg.split("/") + netmask = 2**32 - (2 ** (32 - int(cidr))) + return cls(address, netmask) + + def __init__(self, ip: AnyIP, netmask: AnyIP): + self.ip = IP(ip) + self.netmask = IP(netmask) + self.network = self.ip & self.netmask + self.broadcast = self.network + 2**32 - self.netmask - 1 + self.cidr = int(32 - math.log(2**32 - self.netmask, 2)) + + def __contains__(self, ip: AnyIP) -> bool: + return self.network < IP(ip) < self.broadcast + + def __repr__(self) -> str: + return f"IPRange('{self.ip}', '{self.netmask}')" + + def fmt_cidr(self) -> str: + return f"{self.ip}/{self.cidr}" + + def __str__(self) -> str: + return self.fmt_cidr() diff --git a/debian/confconsole/usr/lib/confconsole/plugin.py b/debian/confconsole/usr/lib/confconsole/plugin.py new file mode 100644 index 0000000..73d5144 --- /dev/null +++ b/debian/confconsole/usr/lib/confconsole/plugin.py @@ -0,0 +1,308 @@ +#!/usr/bin/python +import re +import os +import sys +import importlib.util +import importlib.abc +from collections import OrderedDict + +from types import ModuleType +from typing import Callable, Any, Iterable +import typing + + +class PluginError(Exception): + pass + + +class EventError(Exception): + pass + + +class ModuleInterface(ModuleType): + # this is a hack, we pretend all plugin modules are derived of this + module_name: str + + def run(self) -> str | None: ... + + def doOnce(self) -> None: ... + + +class EventManager: + _handlers: dict[str, list[Callable[[], None]]] + _events: set[str] + + """ Object to handle event/handler interaction """ + + def __init__(self) -> None: + self._handlers = {} + self._events = set() + + def add_event(self, event: str) -> Callable[[], None]: + """Adds event and returns callback function to `fire` event""" + self._events.add(event) + if event not in self._handlers: + self._handlers[event] = [] + + def fire() -> None: + self.fire_event(event) + + fire.__doc__ = f" Function to fire the `{event}` event " + return fire + + def add_handler(self, event: str, handler: Callable[[], None]) -> None: + """Adds a handler to an event""" + if event not in self._handlers: + self._events.add(event) + self._handlers[event] = [] + self._handlers[event].append(handler) + + def fire_event(self, event: str) -> None: + """Fire event, calling all handlers in order""" + if event not in self._events: + return # if event hasn't been registered, don't attempt to fire it + + if event not in self._handlers: + return # if event has no handlers, don't attempt to fire it + + for handler in self._handlers[event]: + try: + handler() # handler passed no arguments; can change if needed + except: # TODO don't use bare except! + sys.stderr.write( + "An Exception has occured within an event handler whilst" + " attempting to handle event '{event}'\n{format_exc()}" + ) + + +class Plugin: + """Object that holds various information about a `plugin`""" + + parent: str | None + + def __init__(self, path: str) -> None: + self.path = path + # for weighted ordering + self.real_name = os.path.basename(path) + # for menu entry + self.name = re.sub(r"^[\d]*", "", self.real_name).replace("_", " ") + + self.parent = None + + # used for imp.find_module + self.module_name = os.path.splitext(self.real_name)[0] + + spec = importlib.util.spec_from_file_location( + self.module_name, self.path + ) + assert spec is not None + assert spec.loader is not None + self.module = typing.cast( + ModuleInterface, importlib.util.module_from_spec(spec) + ) + + setattr(self.module, "PLUGIN_PATH", self.path) + + # XXX this assert had previously been commented due to issues + # - it may need to be commented out again after further testing + assert isinstance(spec.loader, importlib.abc.Loader) + spec.loader.exec_module(self.module) + + # after module is found, it's safe to use pretty name + self.module_name = os.path.splitext(self.name)[0] + + def doOnce(self): + if hasattr(self.module, "doOnce"): + self.module.doOnce() + + def updateGlobals(self, newglobals: dict[str, Any]) -> None: + for k in newglobals.keys(): + setattr(self.module, k, newglobals[k]) + + def run(self) -> str | None: + assert hasattr(self.module, "run") + ret: str | None = self.module.run() + assert ret is None or isinstance(ret, str) + + # default behaviour is to go to previous + # menu after exiting if not otherwise specified + if hasattr(self, "parent"): + return ret or self.parent + else: + return ret or "advanced" + + +class PluginDir: + """Object that mimics behaviour of a plugin but acts only as a menu node""" + + parent: str | None + plugins: list["Plugin | PluginDir"] + + def __init__(self, path: str) -> None: + self.path = path + self.real_name = os.path.basename(path) + self.name = re.sub(r"^[\d]*", "", self.real_name).replace("_", " ") + + self.parent = None + + self.module_name = self.name + + self.module_globals: dict[str, Any] = {} + + if os.path.isfile(os.path.join(path, "description")): + with open(os.path.join(path, "description"), "r") as fob: + self.description = fob.read() + else: + self.description = "" + + def updateGlobals(self, newglobals: dict[str, Any]) -> None: + self.module_globals.update(newglobals) + + def doOnce(self): ... + + def run(self) -> str | None: + items = [] + plugin_map: dict[str, Plugin | PluginDir] = {} + for plugin in self.plugins: + if isinstance(plugin, Plugin) and hasattr(plugin.module, "run"): + items.append( + ( + plugin.module_name.capitalize(), + str(plugin.module.__doc__), + ) + ) + plugin_map[plugin.module_name.capitalize()] = plugin + elif isinstance(plugin, PluginDir): + items.append( + (plugin.module_name.capitalize(), plugin.description) + ) + plugin_map[plugin.module_name.capitalize()] = plugin + + retcode, choice = self.module_globals["console"].menu( + self.module_name.capitalize(), + self.module_name.capitalize() + "\n", + items, + no_cancel=False, + ) + + if retcode != "ok": + if not self.parent: + return "advanced" + else: + return self.parent + + if choice in plugin_map: + return plugin_map[choice].path + else: + v: str = "_adv_" + choice.lower() + return v + + +class PluginManager: + """Object that holds various information about multiple `plugins`""" + + path_map: OrderedDict[str, Plugin | PluginDir] = OrderedDict() + + def __init__(self, path: str, module_globals: dict[str, Any]) -> None: + path = os.path.realpath(path) # Just in case + path_map: dict[str, Plugin | PluginDir] = {} + self.plugin_path = path + + module_globals.update( + { + "impByName": lambda *a, **k: self.impByName(*a, **k), + "impByDir": lambda *a, **k: self.impByDir(*a, **k), + "impByPath": lambda *a, **k: self.impByPath(*a, **k), + } + ) + + self.module_globals = module_globals + + if not os.path.isdir(path): + raise PluginError(f"Plugin directory '{path}' does not exist!") + + for root, dirs, files in os.walk(path): + for file_name in files: + if not file_name.endswith(".py"): + continue + + file_path = os.path.join(root, file_name) + if os.path.isfile(file_path): + if not os.stat(file_path).st_mode & 0o111 == 0: + path_map[file_path] = Plugin(file_path) + + for dir_name in dirs: + if dir_name == "__pycache__": + continue + dir_path = os.path.join(root, dir_name) + + if os.path.isdir(dir_path): + path_map[dir_path] = PluginDir(dir_path) + + self.path_map = OrderedDict( + sorted(path_map.items(), key=lambda x: x[0]) + ) + for key in path_map.keys(): + plugin = path_map[key] + if isinstance(plugin, Plugin): + # Run plugin init + plugin.updateGlobals(module_globals) + plugin.doOnce() + + for key in self.path_map: + if os.path.isdir(key): + sub_plugins = self.getByDir(key) + for plugin in sub_plugins: + plugin.parent = key + v = self.path_map[key] + assert isinstance(v, PluginDir) + v.plugins = list(sub_plugins) + + def updateGlobals(self, newglobals: dict[str, Any]) -> None: + for plugin in self.path_map.values(): + plugin.updateGlobals(newglobals) + # self.module_globals.update(newglobals) + + def getByName(self, name: str) -> Iterable[Plugin | PluginDir]: + """Return list of plugin objects matching given name""" + return filter(lambda x: x.module_name == name, self.path_map.values()) + + def getByDir(self, path: str) -> Iterable[Plugin | PluginDir]: + """Return a list of plugin objects in given directory""" + plugins = [] + for path_key in self.path_map: + if os.path.dirname(path_key) == path: + plugins.append(self.path_map[path_key]) + return plugins + + def getByPath(self, path: str) -> Plugin | PluginDir | None: + """Return plugin object with exact given path or None""" + return self.path_map[os.path.join(self.plugin_path, path)] + + # -- Used by plugins + def impByName(self, name: str) -> Iterable[ModuleInterface]: + """Return a list of python modules (from plugins excluding PluginDirs) + matching given name""" + + modules = [ + x.module for x in self.getByName(name) if isinstance(x, Plugin) + ] + + return list(filter(None, modules)) + + def impByDir(self, path: str) -> Iterable[ModuleInterface]: + """Return a list of python modules (from plugins excluding PluginDirs) + in given directory""" + + modules = [ + x.module for x in self.getByDir(path) if isinstance(x, Plugin) + ] + + return list(filter(None, modules)) + + def impByPath(self, path: str) -> ModuleInterface | None: + """Return a python module from plugin at given path or None""" + out = self.getByPath(path) + if out and isinstance(out, Plugin): + return out.module + return None diff --git a/debian/confconsole/usr/lib/confconsole/plugins.d/Lets_Encrypt/add-water-client b/debian/confconsole/usr/lib/confconsole/plugins.d/Lets_Encrypt/add-water-client new file mode 100755 index 0000000..b9a89a8 --- /dev/null +++ b/debian/confconsole/usr/lib/confconsole/plugins.d/Lets_Encrypt/add-water-client @@ -0,0 +1,44 @@ +#! /usr/bin/python3 + +# Copyright (c) 2017-2020 TurnKey GNU/Linux - http://www.turnkeylinux.org +# +# Add-Water-Client - Agent to pass tokens to Add Water to serve +# Dehydrated Let's Encrypt challenges +# +# This file is part of Confconsole. +# +# Confconsole is free software; you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at your +# option) any later version. + +from argparse import ArgumentParser +import socket +import sys + +if __name__ == "__main__": + parser = ArgumentParser( + description="add-water-client - Agent to pass tokens to add-water" + "server" + ) + token_group = parser.add_mutually_exclusive_group() + token_group.add_argument("--deploy", help="path to token file to serve") + token_group.add_argument("--clean", help="path to token file to serve") + args = parser.parse_args() + + if args.deploy: + op = "deploy" + token_path = args.deploy + elif args.clean: + op = "clean" + token_path = args.clean + else: + print("Nothing to do!") + sys.exit(1) + + host = "127.0.0.1" + port = 9977 + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect((host, port)) + sock.sendall((op + " " + token_path).encode(sys.stdin.encoding)) diff --git a/debian/confconsole/usr/lib/confconsole/plugins.d/Lets_Encrypt/add-water-srv b/debian/confconsole/usr/lib/confconsole/plugins.d/Lets_Encrypt/add-water-srv new file mode 100755 index 0000000..505e3fc --- /dev/null +++ b/debian/confconsole/usr/lib/confconsole/plugins.d/Lets_Encrypt/add-water-srv @@ -0,0 +1,118 @@ +#! /usr/bin/python3 + +# Copyright (c) 2017-2019 TurnKey GNU/Linux - http://www.turnkeylinux.org +# +# Add-Water - Bottle based python HTTP server to serve +# Dehydrated Let's Encrypt challenges +# +# This file is part of Confconsole. +# +# Confconsole is free software; you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at your +# option) any later version. + +import socket +from datetime import datetime +from argparse import ArgumentParser +from queue import Queue, Empty +from threading import Thread +from os.path import isfile, dirname, basename, abspath +from bottle import get, static_file, run, route, redirect + +# "Maintence" page to serve for all requested content, other than LE token +DEFAULT_INDEX = "/usr/share/confconsole/letsencrypt/index.html" +CUSTOM_INDEX = "/var/lib/confconsole/letsencrypt/index.html" + +if isfile(CUSTOM_INDEX): + INDEX_PATH = CUSTOM_INDEX +else: + INDEX_PATH = DEFAULT_INDEX + +INDEX_FILE = basename(INDEX_PATH) +INDEX_WEBROOT = dirname(INDEX_PATH) + +tokens = {} +token_queue = Queue() + + +def update_tokens(): + # pull in new tokens from token queue + while True: + try: + new_token = token_queue.get_nowait() + except Empty: + break + else: + + op, token = new_token.split(" ", 1) + + token_path = abspath(token) + token_file = basename(token_path) + token_webroot = dirname(token_path) + + if op == "deploy": + tokens[token_file] = token_webroot + elif op == "clean": + del tokens[token_file] + else: + raise ValueError("Unknown operation specified!") + + +@get("/.well-known/acme-challenge/") +def challenge(filename): + update_tokens() + if filename in tokens: + token_webroot = tokens[filename] + return static_file(filename, root=token_webroot) + else: + redirect("/") + + +@route("/") +def index(): + update_tokens() + return static_file(INDEX_FILE, root=INDEX_WEBROOT) + + +@route("") +def test(randompath): + _ = randompath + redirect("/") + + +def handle_token_input(): + host = "127.0.0.1" + port = 9977 + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.bind((host, port)) + sock.listen(1) + + # client will only send 1 token per connection, might be + # more effecient way of doing this, but unsure how with dehydrated + while True: + conn, addr = sock.accept() + + token = conn.recv(4096).decode("utf8") + print(f"Got token {token} from {addr}: serving") + token_queue.put(token) + + conn.close() + + sock.close() + + +if __name__ == "__main__": + parser = ArgumentParser( + description="add-water - Bottle based python HTTP server to serve" + " Dehydrated Let's Encrypt challenges" + ) + parser.add_argument("-l", "--logfile", help="path to logfile") + args = parser.parse_args() + + input_handler = Thread(target=handle_token_input) + input_handler.start() + + now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + print(f"[{now}] Starting Server") + run(host="::", port=80) diff --git a/debian/confconsole/usr/lib/confconsole/plugins.d/Lets_Encrypt/cert_auto_renew.py b/debian/confconsole/usr/lib/confconsole/plugins.d/Lets_Encrypt/cert_auto_renew.py new file mode 100755 index 0000000..d5ee3ab --- /dev/null +++ b/debian/confconsole/usr/lib/confconsole/plugins.d/Lets_Encrypt/cert_auto_renew.py @@ -0,0 +1,60 @@ +"""Enable/Disable cert auto-renew""" + +from os import chmod, stat, path + +CRON_PATH = "/etc/cron.daily/confconsole-dehydrated" + + +def enable_cron(): + st = stat(CRON_PATH) + chmod(CRON_PATH, st.st_mode | 0o111) + + +def disable_cron(): + st = stat(CRON_PATH) + chmod(CRON_PATH, st.st_mode ^ 0o111) + + +def check_cron(): + if path.isfile(CRON_PATH): + st = stat(CRON_PATH) + return st.st_mode & 0o111 == 0o111 + else: + return "fail" + + +def run(): + enabled = check_cron() + if enabled == "fail": + msg = ( + "Cron job for dehydrated does not exist.\n" + "Please 'Get certificate' first." + ) + # console is inherited so doesn't need to be defined + r = console.msgbox("Error", msg) + else: + status = "enabled" if enabled else "disabled" + msg = """Automatic certificate renewal is currently {}""" + r = console._wrapper( + "yesno", + msg.format(status), + 10, + 30, + yes_label="Toggle", + no_label="Ok", + ) + while r == "ok": + if enabled: + disable_cron() + else: + enable_cron() + enabled = check_cron() + status = "enabled" if enabled else "disabled" + r = console._wrapper( + "yesno", + msg.format(status), + 10, + 30, + yes_label="Toggle", + no_label="Ok", + ) diff --git a/debian/confconsole/usr/lib/confconsole/plugins.d/Lets_Encrypt/dehydrated-wrapper b/debian/confconsole/usr/lib/confconsole/plugins.d/Lets_Encrypt/dehydrated-wrapper new file mode 100755 index 0000000..a5421a3 --- /dev/null +++ b/debian/confconsole/usr/lib/confconsole/plugins.d/Lets_Encrypt/dehydrated-wrapper @@ -0,0 +1,349 @@ +#!/bin/bash -e + +# Copyright (c) 2016-2023 TurnKey GNU/Linux - https://www.turnkeylinux.org +# +# dehydrated-wrapper - A wrapper script for the Dehydrated +# Let's Encrypt client +# +# This file is part of Confconsole. +# +# Confconsole is free software; you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at your +# option) any later version. + +### initial setup of vars and functions ### + +[[ "$DEBUG" = "y" ]] && set -x + +APP="$(basename "$0")" +DEHYD_ETC=/etc/dehydrated +SHARE=/usr/share/confconsole/letsencrypt +CONFIG="$DEHYD_ETC/confconsole.config" +CC_HOOK="$DEHYD_ETC/confconsole.hook.sh" +CC_DOMAINS="$DEHYD_ETC/confconsole.domains.txt" +FREQ=daily +CRON=/etc/cron.$FREQ/confconsole-dehydrated +LOG=/var/log/confconsole/letsencrypt.log +AUTHBIND80=/etc/authbind/byport/80 +[[ -f "$AUTHBIND80" ]] || touch "$AUTHBIND80" +AUTHBIND_USR=$(stat --format '%U' $AUTHBIND80) +EXIT_CODE=0 + +# space separated list of systemd services to restart +SERVICES_TO_RESTART="webmin.service" + +LE_TOS_URL=${LE_TOS_URL:-https://acme-v02.api.letsencrypt.org/directory} +LICENSE=$(curl "$LE_TOS_URL" 2>/dev/null | grep termsOfService \ + | sed 's|^.*Service": "||; s|",$||') + +SH_CONFIG=$SHARE/dehydrated-confconsole.config +SH_HOOK_HTTP=$SHARE/dehydrated-confconsole.hook-http-01.sh +SH_HOOK_DNS=$SHARE/dehydrated-confconsole.hook-dns-01.sh +SH_CRON=$SHARE/dehydrated-confconsole.cron +SH_DOMAINS=$SHARE/dehydrated-confconsole.domains +export LEXICON_CONFIG_DIR=$SHARE + +export TKL_CERTFILE="/usr/local/share/ca-certificates/cert.crt" +export TKL_KEYFILE="/etc/ssl/private/cert.key" +export TKL_COMBINED="/etc/ssl/private/cert.pem" +export TKL_DHPARAM="/etc/ssl/private/dhparams.pem" +cp $TKL_CERTFILE $TKL_CERTFILE.bak +cp $TKL_KEYFILE $TKL_KEYFILE.bak +cp $TKL_COMBINED $TKL_COMBINED.bak + +BASE_BIN_PATH="/usr/lib/confconsole/plugins.d/Lets_Encrypt" +export HTTP="add-water-client" +export HTTP_USR="www-data" +export HTTP_BIN="$BASE_BIN_PATH/$HTTP" +export HTTP_PID=/var/run/$HTTP/pid +export HTTP_LOG=$LOG +mkdir -p "$(dirname $HTTP_PID)" "$(dirname $LOG)" "$DEHYD_ETC" +touch $LOG +chown -R $HTTP_USR "$(dirname $HTTP_PID)" "$(dirname $LOG)" + +usage() { + echo "$@" + cat<] [--provider|-p ] [--log-info|-i] [--help|-h] + +TurnKey Linux wrapper script for dehydrated. + +Provides an easy and reliable way to get SSL/TLS certificates from an ACME +provider (Let's Encrypt by default), regardless of which webserver is being +used or how it is configured. + +This file is part of confconsole. + +Environment variables: + + DEBUG=y + + - $APP will be very verbose (set -x) + - INFO will be logged (default logging is WARNING & FATAL only) + +Options: + + --force|-f - Pass --force switch to dehydrated. + + This will force dehydrated to update certs + regardless of expiry. The included cron job does + this by default (after checking the expiry of + /etc/ssl/private/cert.pem). + + --register|-r - Accept Terms of Service (ToS) and register a + Let's Encrypt account. (Note if an LE account + already registered, this option makes no difference + so is safe to always use). + + Let's Encrypt ToS can currently be found here: + $LICENSE + + --challenge|-c - Use specific challenge type. + Valid types: http-01 | dns-01 + Will override and update value of CHALLENGETYPE in + $CONFIG + + --provider|-p - Specify DNS provider name to use with dns-01 + challenge. Refer to lexicon documentation for the + list of supported providers. + + --log-info|-i - INFO will be logged (default logging is + WARNING & FATAL only). + + --help|-h - Print this information and exit. + +For more info on advanced usage, please see + + https://www.turnkeylinux.org/docs/letsencrypt#advanced + +EOF + exit 1 +} + +fatal() { + echo "[$(date "+%F %T")] $APP: FATAL: $*" >&2 > >(tee -a $LOG >&2) + exit 1 +} + +warning() { + echo "[$(date "+%F %T")] $APP: WARNING: $*" | tee -a $LOG +} + +info() { + echo "[$(date "+%F %T")] $APP: INFO: $*" | tee -a "$DEBUG_LOG" +} + +copy_if_not_found() { + if [[ ! -f "$1" ]]; then + warning "$1 not found; copying default from $2" + cp "$2" "$1" + fi +} + +check_port() { + case $1 in + 80|443) local port=$1;; + *) fatal "Unexpected port: $1" + esac + netstat -ltpn | grep ":$port " | head -1 | cut -d/ -f2 \ + | sed -e 's|[[:space:]].*$||; s|[^a-zA-Z0-9]||g' +} + +stop_server() { + [[ -z "$*" ]] && return + info "stopping $1" + systemctl stop "$1" 2>&1 | tee -a $LOG + EXIT_CODE=${PIPESTATUS[0]} + while [[ "$(check_port "$PORT")" != "" ]] && [[ $EXIT_CODE -eq 0 ]]; do + info "waiting 1 second for $1 to stop" + sleep 1 + done +} + +# shellcheck disable=SC2317 # Errant "unreachable commands" warning because of trap +restart_servers() { + # intended splitting on space + for servicename in "$@"; do + info "(Re)starting $servicename" + systemctl restart "$servicename" | tee -a "$LOG" + [[ "${PIPESTATUS[0]}" -eq 0 ]] || EXIT_CODE=1 + done +} + +# shellcheck disable=SC2317 # Errant "unreachable commands" because of trap +clean_exit() { + # ideally do not use 'fatal' in this function + EXIT_CODE=$1 + if [[ "$PORT" == "80" ]]; then + check_port_80=$(check_port 80) + if [[ "$check_port_80" == 'python' ]] || [[ "$check_port_80" == 'python3' ]]; then + warning "Python is still listening on port $PORT" + info "attempting to kill add-water server" + systemctl stop add-water + fi + fi + [[ "$AUTHBIND_USR" = "$HTTP_USR" ]] || chown "$AUTHBIND_USR" "$AUTHBIND80" + if [[ $EXIT_CODE -ne 0 ]]; then + warning "Something went wrong, restoring original cert, key and combined files." + + mv "$TKL_CERTFILE.bak" "$TKL_CERTFILE" + mv "$TKL_KEYFILE.bak" "$TKL_KEYFILE" + mv "$TKL_COMBINED.bak" "$TKL_COMBINED" + else + info "Cleaning backup cert & key" + rm -f "$TKL_CERTFILE.bak" "$TKL_KEYFILE.bak" "$TKL_COMBINED.bak" + fi + if [[ "$WEBSERVER" == "tomcat"* ]]; then + update_tomcat_cert=/usr/lib/inithooks/firstboot.d/16tomcat-sslcert + if [[ -x "$update_tomcat_cert" ]]; then + $update_tomcat_cert + else + warning "Tomcat webserver found ($WEBSERVER) but can't run cert update ($update_tomcat_cert)." + fi + fi + # don't quote these service names as some values may be empty - which is anticipated + # shellcheck disable=SC2086 + restart_servers $WEBSERVER $SERVICES_TO_RESTART + if [[ $EXIT_CODE -ne 0 ]]; then + warning "Check today's previous log entries for details of error." + else + info "$APP completed successfully." + fi + systemctl stop add-water + # don't quote exit code as it may be empty (exit 0) + # shellcheck disable=SC2086 + exit $EXIT_CODE +} + +### some intial checks & set up trap ### + +trap '(exit 130)' INT +trap '(exit 143)' TERM +trap 'clean_exit' EXIT + +[[ "$EUID" = "0" ]] || fatal "$APP must be run as root" +[[ $(which dehydrated) ]] || fatal "Dehydrated not installed, or not in the \$PATH" +[[ $(which authbind) ]] || fatal "Authbind not installed" + +### read args & check config - set up whats needed ### +force= +while [[ $# -gt 0 ]]; do + arg="$1" + case $arg in + -f|--force) force="--force";; + -r|--register) REGISTER=y;; + -c|--challenge) if [[ -n $2 && ! $2 =~ ^- ]]; then + CTYPE=${2,,} + shift + fi;; + -p|--provider) if [[ -n $2 && ! $2 =~ ^- ]]; then + export PROVIDER=${2,,} + shift + fi;; + -i|--log-info) LOG_INFO=y;; + -h|--help) usage;; + *) usage "FATAL: unsupported or unknown argument: $1";; + esac + shift +done + +if [[ "$DEBUG" = "y" ]] || [[ "$LOG_INFO" = "y" ]]; then + DEBUG_LOG="$LOG" +else + DEBUG_LOG="/dev/null" + export HTTP_LOG=$DEBUG_LOG +fi + +info "started" + +copy_if_not_found "$CONFIG" "$SH_CONFIG" + +# shellcheck source=/dev/null +source "$CONFIG" + +copy_if_not_found "$DOMAINS_TXT" "$SH_DOMAINS" + +[[ "$DOMAINS_TXT" != "$CC_DOMAINS" ]] && warning "$CONFIG is not using $CC_DOMAINS" +[[ -z "$HOOK" ]] && fatal "hook script not defined in $CONFIG" + +export CHALLENGETYPE="${CTYPE:-$CHALLENGETYPE}" + +case $CHALLENGETYPE in + http-01) cp "$SH_HOOK_HTTP" "$CC_HOOK" + PORT=80 + sed -i '\|^CHALLENGETYPE=|s|=.*|="http-01"|' "$CONFIG";; + dns-01) cp "$SH_HOOK_DNS" "$CC_HOOK" + PORT=443 + sed -i '\|^CHALLENGETYPE=|s|=.*|="dns-01"|' "$CONFIG";; + *) fatal "Unexpected challenge type: $CHALLENGETYPE" +esac + +[[ "$HOOK" != "$CC_HOOK" ]] && warning "$CONFIG is not using $CC_HOOK" +chmod +x "$HOOK" + +copy_if_not_found "$CRON" "$SH_CRON" + +if [[ "$REGISTER" = 'y' ]]; then + DEHYDRATED_REGISTER="dehydrated --register --accept-terms --config $CONFIG" + if [[ "$DEBUG" = "y" ]] || [[ "$LOG_INFO" = "y" ]]; then + $DEHYDRATED_REGISTER 2>&1 | tee -a $DEBUG_LOG + EXIT_CODE=${PIPESTATUS[0]} + else + ($DEHYDRATED_REGISTER 3>&2 2>&1 1>&3) 2>/dev/null | tee -a $LOG + EXIT_CODE=${PIPESTATUS[0]} + fi + [[ $EXIT_CODE -eq 0 ]] || fatal "dehydrated failed to register account." +fi + +### main script ### + +WEBSERVER=$(check_port $PORT) +if [[ -n "$WEBSERVER" ]]; then + info "found $WEBSERVER listening on port $PORT" + if [[ "$CHALLENGETYPE" == 'http-01' ]]; then + case $WEBSERVER in + apache2 | lighttpd | nginx ) + stop_server "$WEBSERVER";; + java ) + TOMCAT=/etc/init.d/tomcat; + if [[ -x "${TOMCAT}8" ]]; then + WEBSERVER=tomcat8; + elif [[ -f "/lib/systemd/system/tomcat9.service" ]]; then + WEBSERVER=tomcat9; + elif [[ -f "/lib/systemd/system/tomcat10.service" ]]; then + WEBSERVER=tomcat10; + else + unset WEBSERVER; + fatal "An unknown Java app is listening on port $PORT"; + fi; + stop_server $WEBSERVER;; + python | python3 ) + unset WEBSERVER; + fatal "An unknown/unexpected Python app is listening on port $PORT";; + * ) + unknown="$WEBSERVER"; + unset WEBSERVER; + fatal "An unexpected service is listening on port $PORT: $unknown";; + esac + [[ "$AUTHBIND_USR" = "$HTTP_USR" ]] || chown $HTTP_USR $AUTHBIND80 + fi +else + info "No process found listening on port $PORT; continuing" +fi + +[[ "$CHALLENGETYPE" != "dns-01" ]] && systemctl start add-water +info "running dehydrated" +if [[ "$DEBUG" = "y" ]] || [[ "$LOG_INFO" = "y" ]]; then + dehydrated --cron $force --config $CONFIG 2>&1 | tee -a $DEBUG_LOG + EXIT_CODE=${PIPESTATUS[0]} +else + (dehydrated --cron $force --config $CONFIG 3>&2 2>&1 1>&3) 2>/dev/null | tee -a $LOG + EXIT_CODE=${PIPESTATUS[0]} +fi +if [[ $EXIT_CODE -ne 0 ]]; then + fatal "dehydrated exited with a non-zero exit code." +else + info "dehydrated complete" + exit 0 +fi diff --git a/debian/confconsole/usr/lib/confconsole/plugins.d/Lets_Encrypt/description b/debian/confconsole/usr/lib/confconsole/plugins.d/Lets_Encrypt/description new file mode 100644 index 0000000..f330e75 --- /dev/null +++ b/debian/confconsole/usr/lib/confconsole/plugins.d/Lets_Encrypt/description @@ -0,0 +1 @@ +Let's Encrypt free SSL certificates diff --git a/debian/confconsole/usr/lib/confconsole/plugins.d/Lets_Encrypt/dns_01.py b/debian/confconsole/usr/lib/confconsole/plugins.d/Lets_Encrypt/dns_01.py new file mode 100755 index 0000000..cd841dd --- /dev/null +++ b/debian/confconsole/usr/lib/confconsole/plugins.d/Lets_Encrypt/dns_01.py @@ -0,0 +1,202 @@ +#! /usr/bin/python3 +import sys +import subprocess +import re + +from os import makedirs, chmod, chown +from os.path import isfile, join, exists, dirname +from shutil import which, copy + +from typing import Optional + +LEXICON_SHARE_DIR = "/usr/share/confconsole/letsencrypt" +LEXICON_CONF_DIR = "/etc/dehydrated" + + +def load_config(provider: str) -> tuple[str, list[str]]: + """Loads lexicon config if present, loads example if not, + returns tuple(conf_file, list(config)) + """ + if provider in ["route53", "cloudflare"]: + example_conf = join( + LEXICON_SHARE_DIR, f"lexicon-confconsole-provider_{provider}.yml" + ) + else: + example_conf = join( + LEXICON_SHARE_DIR, "lexicon-confconsole-provider_example.yml" + ) + conf_file = join(LEXICON_CONF_DIR, f"lexicon_{provider}.yml") + config = [] + if not isfile(conf_file): + copy(example_conf, conf_file) + # ensure root read/write only as file contains sensitive info + chown(conf_file, 0, 0) # chown root:root + chmod(conf_file, 0o600) # chmod 600 (owner read/write only) + with open(conf_file) as fob: + for line in fob: + config.append(line.rstrip()) + + return conf_file, config + + +def save_config(conf_file: str, config: list[str]) -> None: + """Saves lexicon configuration""" + with open(conf_file, "w") as fob: + for line in config: + if line: + fob.write(line.rstrip() + "\n") + # ensure root read/write only as file contains sensitive info + chown(conf_file, 0, 0) # chown root:root + chmod(conf_file, 0o600) # chmod 600 (owner read/write only) + + +def run_command( + command: list[str], env: Optional[dict[str, str]] = None +) -> tuple[int, str]: + """Simple subprocess wrapper for running commands""" + if env is None: + env = {} + proc = subprocess.run(command, env=env, capture_output=True, text=True) + if proc.returncode != 0: + com = " ".join(command) + return ( + proc.returncode, + f"Something went wrong when running '{com}':\n\n{proc.stderr}", + ) + else: + return 0, "success" + + +def apt_install(pkgs: list[str]) -> tuple[int, str]: + """Takes a list of package names, updates apt and installs packages, + returns tuple(exit_code, message) + """ + string = "" + exit_code = 0 + env = {"DEBIAN_FRONTEND": "noninteractive"} + for command in [ + ["apt-get", "update"], + ["apt-get", "install", *pkgs, "--yes"], + ]: + exit_code, string = run_command(command, env=env) + if exit_code != 0: + return exit_code, string + return exit_code, string + + +def check_pkg(pkg: str) -> bool: + """Takes a package name and returns True if installed, otherwise False""" + p = subprocess.run( + ["dpkg", "-s", pkg], + capture_output=True, + text=True, + ) + if p.returncode == 0: + return True # package installed + return False # package not installed + + +def initial_setup() -> None: + """Check lexicon and deps are installed and ready to go + + Returns a tuple of exit code (0 = success) and message + """ + msg_start = "lexicon tool is required for dns-01 challenge, " + msg_mid = "" + msg_end = "\n\nDo you wish to continue?" + msg: str | None = "" + install_venv = False + unexpected = False + + lexicon_bin = which("turnkey-lexicon") + venv = "/usr/local/src/venv/lexicon" + if not exists(venv): + # turnkey lexicon venv wrapper not found - offer to install + install_venv = True + msg_mid = "however it is not found on your system, so installing." + elif exists(f"{venv}/bin/lexicon"): + # lexicon venv bin found - no message required + msg = None + else: + msg_mid = "but your system is in an unexpected state" + unexpected = True + if msg is not None: + msg = msg_start + msg_mid + msg_end + # console is inherited so doesn't need to be defined + ret = console.yesno(msg, autosize=True) + if ret != "ok": + return + if install_venv or unexpected: + pkgs = [] + pip = which("pip") + python3_venv = check_pkg("python3-venv") + if not pip: + pkgs.append("python3-pip") + if not python3_venv: + pkgs.append("python3-venv") + if pkgs: + print("Please wait while required packages are installed") + exit_code, msg = apt_install(pkgs) + if exit_code != 0: + pkgs_l = " ".join(pkgs) + console.msgbox( + "Error", f"Apt installing {pkgs_l} failed:\n\n{msg}" + ) + makedirs(dirname(venv), exist_ok=True) + venv_pip = join(venv, "bin/pip") + for comment_command in [ + ("venv is set up", ["/usr/bin/python3", "-m", "venv", venv]), + ( + "lexicon is installed (into venv)", + [venv_pip, "install", "dns-lexicon[full]"], + ), + ]: + comment, command = comment_command + assert isinstance(command, list) + if comment: + print(f"Please wait while {comment}") + exit_code, msg = run_command(command) + if exit_code != 0: + com = " ".join(command) + console.msgbox("Error", f"Command '{com}' failed:\n\n{msg}") + return None + + lexicon_bin = which("turnkey-lexicon") + if not lexicon_bin: + console.msgbox( + "Error", + "Could not find 'turnkey-lexicon'? Should be installed with" + " Confconsole.", + ) + return None + + +def get_providers() -> tuple[list[tuple[str, str]] | None, str | None]: + """Get list of supported DNS providers from lexicon""" + lexicon_bin = which("turnkey-lexicon") + if not lexicon_bin: + return ( + None, + "turnkey-lexicon is not found on your system, is it installed?", + ) + print("Please wait while list of supported DNS providers is downloaded") + proc = subprocess.run( + [lexicon_bin, "--lexicon-help"], + encoding=sys.stdin.encoding, + capture_output=True, + ) + if proc.returncode != 0: + return None, proc.stderr.strip() + + match = re.search(r"(?<={).*(?=})", proc.stdout.strip()) + if not match: + return None, "Could not obtain DNS providers list from lexicon!" + + providers = [] + for provider in match.group().split(","): + if len(provider) > 0: + providers.append((provider, f"{provider} provider")) + + if providers: + return providers, None + return None, "DNS providers list is empty!" diff --git a/debian/confconsole/usr/lib/confconsole/plugins.d/Lets_Encrypt/get_certificate.py b/debian/confconsole/usr/lib/confconsole/plugins.d/Lets_Encrypt/get_certificate.py new file mode 100755 index 0000000..179ecc4 --- /dev/null +++ b/debian/confconsole/usr/lib/confconsole/plugins.d/Lets_Encrypt/get_certificate.py @@ -0,0 +1,444 @@ +"""Get Let's Encrypt SSl cert""" + +import requests +import subprocess + +from os import remove +from os.path import join, exists, isfile, isdir, basename, dirname +from shutil import copyfile, which +from json import JSONDecodeError +from glob import glob + +LE_INFO_URL = "https://acme-v02.api.letsencrypt.org/directory" + +TITLE = "Certificate Creation Wizard" + +DESC = """Please enter domain(s) to generate certificate for. + +To generate a single certificate for up to five domains (including subdomains), +enter each domain into a box, one domain per box. Empty boxes will be ignored. + +Wildcard domains are supported, but only when using DNS-01 challenge. Alias +will be auto generated, so should not be entered here. See: +https://www.turnkeylinux.org/docs/confconsole/letsencrypt#wildcard + +To generate multiple certificates, please consult the advanced docs: +https://www.turnkeylinux.org/docs/letsencrypt#advanced +""" + +dehydrated_conf = "/etc/dehydrated" +domain_path = join(dehydrated_conf, "confconsole.domains.txt") +d_conf_path = join(dehydrated_conf, "confconsole.config") +share = "/usr/share/confconsole" +d_conf_example = join(share, "letsencrypt/dehydrated-confconsole.config") +d_dom_example = join(share, "letsencrypt/dehydrated-confconsole.domains") + +example_domain = "example.com" +# XXX Debug paths + + +def doOnce() -> None: + global dns_01 + # impByPath inherited so doesn't need to be defined + dns_01 = impByPath("Lets_Encrypt/dns_01.py") + + +def read_conf(path: str) -> list[str]: + """Read config from path and return as a list (line to an item)""" + with open(path) as fob: + return fob.read().split("\n") + + +def write_conf(conf: list[str]) -> None: + """Writes (list of) config lines to dehydrated conf path""" + with open(d_conf_path, "w") as fob: + for line in conf: + fob.write(line.rstrip() + "\n") + + +def update_conf(conf: list[str], new_values: dict[str, str]) -> list[str]: + """Given a list of conf lines, lines which match keys from new_values + {K: V} will be updated to 'K=V' - if K does not exist, will be ignored""" + new_conf = [] + new_val_keys = list(new_values.keys()) + for line in conf: + if line is None: + continue + if "=" in line and not line.startswith("#"): + key = line.split("=", 1)[0] + if key in new_val_keys: + line = f'{key}="{new_values[key]}"' + new_conf.append(line) + write_conf(new_conf) + return new_conf + + +def get_conf_value(conf: list[str], key: str) -> str | None: + """Given a list of config lines and a key, returns the (first) + corresponding value (non case sensititive). If nothing found, returns None. + """ + for line in conf: + if not line: + continue + if "=" in line and not line.startswith("#"): + if key.lower() == line.split("=", 1)[0].lower(): + return line.split("=", 1)[1].strip('"').strip("'") + return None + + +def initial_load_conf(provider: str | None = None) -> list[str]: + """Create or update Dehydrated conf file, if not passed provider, assumes + http-01 challenge, otherwise assume dns-01. Also returns conf as list of + lines""" + src = d_conf_path + if not exists(d_conf_path): + src = d_conf_example + conf = read_conf(src) + if provider is None: # assume http-01 + new_conf = {"CHALLENGETYPE": "http-01"} + else: # assume dns-01 + new_conf = {"CHALLENGETYPE": "dns-01", "PROVIDER": provider} + conf = update_conf(conf, new_conf) + write_conf(conf) + return conf + + +def gen_alias(line: str) -> str: + return line.split(" ")[0].replace("*", "star").replace(".", "_") + + +def load_domains() -> tuple[list[str], str | None]: + """Loads domain conf, writes default config if non-existent. Expects + "/etc/dehydrated" to exist + returns a tuple of list(domains) and alias""" + if not isfile(domain_path): + copyfile(d_dom_example, domain_path) + return [example_domain, "", "", "", ""], None + else: + backup_domain_path = ".".join([domain_path, "bak"]) + copyfile(domain_path, backup_domain_path) + domains = [] + alias = None + with open(domain_path) as fob: + for line in fob: + line = line.strip() + # only read first uncommented line that contains text + if line and not line.startswith("#"): + if ">" in line: + line, alias = line.split(">", 1) + if line.startswith("*") and not alias: + alias = gen_alias(line) + domains = line.split(" ") + break + if alias: + alias = alias.strip() + while len(domains) > 5: + domains.pop() + while len(domains) < 5: + domains.append("") + return domains, alias + + +def save_domains(domains: list[str], alias: str | None = None) -> None: + """Saves domain configuration""" + for index, domain in enumerate(domains): + if ">" in domain and not alias: + domains[index], alias = map(str.strip, domain.split(">", 1)) + elif ">" in domain: + domains[index], _ = map(str.strip, domain.split(">", 1)) + new_line = " ".join(domains) + if not alias: + if new_line.startswith("*"): + alias = gen_alias(new_line) + elif "*" in new_line: + for domain in domains: + if domain.startswith("*"): + alias = gen_alias(domain) + break + if alias: + new_line = f"{new_line} > {alias}" + with open(domain_path) as fob: + old_dom_file = fob.readlines() + found_line = False + with open(domain_path, "w") as fob: + for line in old_dom_file: + if line.startswith("#"): + fob.write(line) + elif not found_line: + fob.write(new_line + "\n") + found_line = True + else: + fob.write(line) + + +def invalid_domains(domains: list[str], challenge: str) -> str | None: + """Validates well known limitations of domain-name specifications + doesn"t enforce when or if special characters are valid. Returns a + string if domains are invalid explaining why, otherwise returns None""" + if domains[0] == "": + return ( + f"Error: At least one domain must be provided in {domain_path}" + " (with no preceeding space)" + ) + for domain in domains: + if ">" in domain: + domain, alias = map(str.strip, domain.split(">", 1)) + if len(domain) != 0: + if len(domain) > 254: + return ( + f"Error in {domain}: Domain names must not exceed 254" + " characters" + ) + if domain.count(".") < 1: + return ( + f"Error in {domain}: Domain may not have less than 2" + " segments" + ) + for part in domain.split("."): + if not 0 < len(part) < 64: + return ( + f"Error in {domain}: Domain segments may not be" + " larger than 63 characters or less than 1" + ) + elif domain.startswith("*") and challenge.startswith("http"): + return ( + f"Error in {domain}: Wildcard domains are only valid with" + " DNS-01 challenge" + ) + return None + + +def run() -> None: + field_width = 60 + field_names = ["domain1", "domain2", "domain3", "domain4", "domain5"] + + canceled = False + + tos_url = None + msg = "" + try: + response = requests.get(LE_INFO_URL) + tos_url = response.json()["meta"]["termsOfService"] + except ConnectionError: + msg = "Connection error. Failed to connect to " + LE_INFO_URL + except JSONDecodeError: + msg = "Data error, no JSON data found" + except KeyError: + msg = "Data error, no value found for 'terms-of-service'" + if not tos_url: + console.msgbox("Error", msg, autosize=True) + return + + ret = console.yesno( + "Before getting a Let's Encrypt certificate, you must agree to the" + " current Terms of Service.\n\n" + "You can find the current Terms of Service here:\n\n" + f"{tos_url}\n\n" + "Do you agree to the Let's Encrypt Terms of Service?", + autosize=True, + ) + if ret != "ok": + return + + if not isdir(dehydrated_conf): + console.msgbox( + "Error", + f"Dehydrated not installed or {dehydrated_conf} not found," + " dehydrated can be installed via apt from the Debian repos.\n\n" + "More info: www.turnkeylinux.org/docs/letsencrypt", + autosize=True, + ) + return + + ret, challenge = console.menu( + "Challenge type", + "Select challenge type to use", + [ + ("http-01", "Requires public web access to this system"), + ("dns-01", "Requires your DNS provider to provide an API"), + ], + ) + if ret != "ok": + return + + if challenge == "http-01": + ret = console.yesno( + "DNS must be configured before obtaining certificates." + " Incorrectly configured DNS and excessive attempts could" + " lead to being temporarily blocked from requesting" + " certificates.\n\n" + "You can check for a valid 'A' DNS record for your domain via" + " Google:\n https://toolbox.googleapps.com/apps/dig/\n\n" + "Do you wish to continue?", + autosize=True, + ) + if ret != "ok": + return + d_conf = initial_load_conf() + write_conf(d_conf) + + elif challenge == "dns-01": + dns_01.initial_setup() + conf = "" + l_conf_possible = glob(join(dns_01.LEXICON_CONF_DIR, "lexicon_*.yml")) + if len(l_conf_possible) == 0: + conf = None + elif len(l_conf_possible) == 1: + conf = l_conf_possible[0] + elif len(l_conf_possible) >= 2: + console.msgbox( + "Error", + "Multiple lexicon_*.yml conf files found in" + f" {dns_01.LEXICON_CONF_DIR}, please ensure there is only one", + autosize=True, + ) + return + + if conf: + provider = basename(conf).split("_", 1)[1][:-4] + else: + providers, err = dns_01.get_providers() + if err: + console.msgbox("Error", err, autosize=True) + return + if not providers: + console.msgbox( + "Error", + "No providers found, please report to TurnKey", + autosize=True, + ) + return + ret, provider = console.menu( + "DNS providers list", + "Select DNS provider you'd like to use", + providers, + ) + if ret != "ok": + return + + if provider == "auto" and not which("nslookup"): + ret = console.yesno( + "nslookup tool is required to use dns-01 challenge with" + " auto provider.\n\n" + "Do you wish to install it now?", + autosize=True, + ) + if ret != "ok": + return + returncode, message = dns_01.apt_install(["dnsutils"]) + if returncode != 0: + console.msgbox("Error", message, autosize=True) + return + if not provider: + console.msgbox("Error", "No provider selected", autosize=True) + + d_conf = initial_load_conf(provider) + conf_file, config = dns_01.load_config(provider) + if len(config) > 12: + console.msgbox( + "Error", + "Config file too big - needs to be 12 lines or less", + autosize=True, + ) + return + elif len(config) < 12: + config.extend([""] * (12 - len(config))) + fields = [ + ("", 1, 0, config[0], 1, 10, field_width, 255), + ("", 2, 0, config[1], 2, 10, field_width, 255), + ("", 3, 0, config[2], 3, 10, field_width, 255), + ("", 4, 0, config[3], 4, 10, field_width, 255), + ("", 5, 0, config[4], 5, 10, field_width, 255), + ("", 6, 0, config[5], 6, 10, field_width, 255), + ("", 7, 0, config[6], 7, 10, field_width, 255), + ("", 8, 0, config[7], 8, 10, field_width, 255), + ("", 9, 0, config[8], 9, 10, field_width, 255), + ("", 10, 0, config[9], 10, 10, field_width, 255), + ("", 11, 0, config[10], 11, 10, field_width, 255), + ("", 12, 0, config[11], 12, 10, field_width, 255), + ] + ret, values = console.form( + "Lexicon configuration", + "Review and adjust current lexicon configuration as" + "necessary.\n\n Please see https://www.turnkeylinux.org/docs/" + "confconsole/letsencrypt#dns-01", + fields, + autosize=True, + ) + if ret != "ok": + return + + if config != values: + dns_01.save_config(conf_file, values) + + domains, alias = load_domains() + m = invalid_domains(domains, challenge) + + if m: + ret = console.yesno( + (str(m) + "\n\nWould you like to ignore and overwrite data?") + ) + if ret == "ok": + remove(domain_path) + domains, alias = load_domains() + else: + return + + values = domains + + while True: + while True: + fields = [ + ("Domain 1", 1, 0, values[0], 1, 10, field_width, 255), + ("Domain 2", 2, 0, values[1], 2, 10, field_width, 255), + ("Domain 3", 3, 0, values[2], 3, 10, field_width, 255), + ("Domain 4", 4, 0, values[3], 4, 10, field_width, 255), + ("Domain 5", 5, 0, values[4], 5, 10, field_width, 255), + ] + ret, values = console.form(TITLE, DESC, fields, autosize=True) + + if ret != "ok": + canceled = True + break + + msg = invalid_domains(values, challenge) + if msg: + console.msgbox("Error", msg) + continue + + if ret == "ok": + ret2 = console.yesno( + "This will overwrite any previous settings (saving a" + " backup) and check for certificate. Continue?" + ) + if ret2 == "ok": + save_domains(values) + break + + if canceled: + break + + # User has accepted ToS as part of this process, so pass "--register" + + # PLUGIN_PATH is inherited so is actually defined + dehyd_wrapper = join(dirname(PLUGIN_PATH), "dehydrated-wrapper") + dehydrated_bin = [ + "/bin/bash", + dehyd_wrapper, + "--register", + "--log-info", + "--challenge", + challenge, + ] + if challenge == "dns-01": + dehydrated_bin.append("--provider") + dehydrated_bin.append(provider) + proc = subprocess.run( + dehydrated_bin, + capture_output=True, + text=True, + ) + if proc.returncode == 0: + break + else: + console.msgbox("Error!", proc.stderr) diff --git a/debian/confconsole/usr/lib/confconsole/plugins.d/Mail_Relaying/description b/debian/confconsole/usr/lib/confconsole/plugins.d/Mail_Relaying/description new file mode 100644 index 0000000..0de9c6f --- /dev/null +++ b/debian/confconsole/usr/lib/confconsole/plugins.d/Mail_Relaying/description @@ -0,0 +1 @@ +Enable mail relaying to a remote server diff --git a/debian/confconsole/usr/lib/confconsole/plugins.d/Mail_Relaying/mail_relay.py b/debian/confconsole/usr/lib/confconsole/plugins.d/Mail_Relaying/mail_relay.py new file mode 100755 index 0000000..6543935 --- /dev/null +++ b/debian/confconsole/usr/lib/confconsole/plugins.d/Mail_Relaying/mail_relay.py @@ -0,0 +1,173 @@ +"""Setup relaying""" + +import ssl +import socket +import sys +from smtplib import SMTP, SMTP_SSL, SMTPException +import os +import subprocess + +TITLE = "Mail Relay" + +TEXT = ( + "By default, TurnKey servers send e-mail directly. An SMTP relay provides" + " more robust mail deliverability.\n\n" + "Send up to 9000 emails per month with a free Brevo account. To sign up," + " open the below URL in your web browser and follow the prompts:\n\n" + "https://hub.turnkeylinux.org/email" +) + +FORMNOTE = ( + "Please enter the settings below.\n\n" + "Note: The relay authentication procedure requires the user password to be" + " stored in plain text at /etc/postfix/sasl_passwd (readable only by" + " root). If this is not what you want, you should cancel this" + " configuration step." +) + + +def testsettings(host, port, login, password): + encoding = sys.stdin.encoding + + # login username and password must be string + def bytes2string(string): + if type(string) is bytes: + return string.decode(encoding) + return string + + host = host.encode(encoding) + port = int(port) + login = bytes2string(login) + password = bytes2string(password) + + try: # SSL + smtp = SMTP_SSL(host, port) + ret, msg = smtp.login(login, password) + smtp.quit() + + if ret == 235: # 2.7.0 Authentication successful + return True, None + except (ssl.SSLError, SMTPException): + pass + except socket.gaierror as e: + return None, e.args + + try: # STARTTLS or plaintext + smtp = SMTP(host, port) + smtp.starttls() + smtp.ehlo() + ret, msg = smtp.login(login, password) + smtp.quit() + + if ret == 235: + return True, None + except (ssl.SSLError, SMTPException) as e: + ret, msg = e.args[0], bytes2string(e.args[1]) + pass + + return False, (ret, msg) + + +def run(): + host = "localhost" + port = "25" + login = "" + password = "" + + cmd = os.path.join(os.path.dirname(__file__), "mail_relay.sh") + + # console is inherited so doesn"t need to be defined + retcode, choice = console.menu( + TITLE, + TEXT, + [ + ("SendinBlue", "TurnKey's preferred SMTP gateway"), + ("Custom", "Custom mail relay configuration"), + ("Deconfigure", "Erase current mail relay settings"), + ], + ) + + if choice: + if choice == "Deconfigure": + proc = subprocess.run( + [cmd, "deconfigure"], capture_output=True, text=True + ) + if proc.returncode != 0: + console.msgbox( + "Error", + proc.stderr, + ) + return + + console.msgbox( + TITLE, + "The mail relay settings were succesfully erased." + " No relaying will take place from now on.", + ) + return + + if choice == "Brevo": + host = "smtp-relay.brevo.com" + port = "587" + + field_width = field_limit = 100 + + while 1: + fields = [ + ("Host", 1, 0, host, 1, 10, field_width, field_limit), + ("Port", 2, 0, port, 2, 10, field_width, field_limit), + ("Login", 3, 0, login, 3, 10, field_width, field_limit), + ("Password", 4, 0, password, 4, 10, field_width, field_limit), + ] + + retcode, values = console.form(TITLE, FORMNOTE, fields) + + if retcode != "ok": + console.msgbox( + TITLE, + "You have cancelled the configuration process. No relaying" + " of mail will be performed.", + ) + return + + host, port, login, password = tuple(values) + + if not login: + ret = console.yesno( + "No login username provided. Unable to test SMTP" + " connection before configuring.\n\n" + "Are you sure you want to configure SMTP forwarding with" + " no login credentials?", + autosize=True, + ) + if ret != "ok": + return + else: + break + + else: + success, error_msg = testsettings(*values) + if success: + console.msgbox( + TITLE, + "SMTP connection test successful.\n\n" + "Ready to configure Postfix.", + ) + break + + else: + console.msgbox( + TITLE, + "Could not connect with supplied parameters.\n\n" + "Error code: {}\n\nMessage:\n\n {}\n\nPlease" + " check config and try again.".format(*error_msg), + ) + return + + proc = subprocess.run( + [cmd, host, port, login, password], + capture_output=True, + text=True, + ) + if proc.returncode != 0: + console.msgbox("Error", proc.stderr) diff --git a/debian/confconsole/usr/lib/confconsole/plugins.d/Mail_Relaying/mail_relay.sh b/debian/confconsole/usr/lib/confconsole/plugins.d/Mail_Relaying/mail_relay.sh new file mode 100755 index 0000000..61dc7bd --- /dev/null +++ b/debian/confconsole/usr/lib/confconsole/plugins.d/Mail_Relaying/mail_relay.sh @@ -0,0 +1,78 @@ +#!/bin/bash -ex + +fatal() { echo "fatal [$(basename $0)]: $@" 1>&2; exit 1; } +warning() { echo "warning [$(basename $0)]: $@" ; } +info() { echo "info [$(basename $0)]: $@"; } + +usage() { +cat<> $cfgfile + for (( i = 0; i < ${#options[@]}; i++ )); do + _value=${values[i]} + if [[ "$_value" == "NULL" ]]; then + _value= + fi + sed -i "/${options[$i]}/d; \$a${options[i]} = ${_value}" $cfgfile + done + + cat << EOF > $pwdfile +$hostport $username:$password +EOF + + chown root:root $pwdfile + chmod 600 $pwdfile + + postmap $pwdfile + postfix reload || true +} + +deconfigure_postfix() { + [[ -e $pwdfile ]] && shred -u $pwdfile + for var in "${options[@]}"; do + sed -i "/${var}/d" $cfgfile + done +} + +if [[ $# -lt 1 || $# -gt 4 ]]; then + usage +fi + +if [[ $# == 1 && $1 == 'deconfigure' ]]; then + deconfigure_postfix +else + configure_postfix "$@" +fi + +sleep 10 diff --git a/debian/confconsole/usr/lib/confconsole/plugins.d/Proxy_Settings/apt.py b/debian/confconsole/usr/lib/confconsole/plugins.d/Proxy_Settings/apt.py new file mode 100755 index 0000000..ddf14db --- /dev/null +++ b/debian/confconsole/usr/lib/confconsole/plugins.d/Proxy_Settings/apt.py @@ -0,0 +1,81 @@ +"""Set APT's HTTP Proxy""" + +from re import match, sub, MULTILINE, search +from os.path import isfile +from urllib.parse import urlparse + +CONF = "/etc/apt/apt.conf.d/80proxy" +PROXY_LINE = r'Acquire::http::Proxy "(.*)";' +PROXY_REPL = r'Acquire::http::Proxy "{}";' + + +def get_proxy() -> str: + proxy = "" + if not isfile(CONF): + return proxy + with open(CONF) as fob: + for line in fob: + lmatch = match(PROXY_LINE, line) + if lmatch: + proxy = lmatch.group(1) + return proxy + + +def set_proxy(prox: str) -> None: + if isfile(CONF): + with open(CONF) as fob: + data = fob.read() + else: + data = "" + + if search(PROXY_LINE, data): + data = sub(PROXY_LINE, PROXY_REPL.format(prox), data, 1, MULTILINE) + else: + data += "\n" + (PROXY_REPL.format(prox)) + "\n" + + with open(CONF, "w") as fob: + fob.write(data) + + +def validate_address(addr: str) -> bool: + parsed = urlparse(addr) + return bool(parsed.scheme) and len(parsed.netloc.split(".")) > 1 + + +def doOnce(): + pass + + +def run(): + original_proxy = get_proxy() + while True: + # console is inherited so doesn't need to be defined + code, prox = console.inputbox( + "Set proxy", + 'Set a HTTP Proxy. Must contain scheme "http://example.com"' + ' but not "example.com"', + init=original_proxy, + ) + if code == "ok": + if prox and not validate_address(prox): + console.msgbox( + "Invalid Proxy", + "A valid proxy address must at least have a net" + " location and scheme (http://example.com) but not" + " (example.com)", + ) + else: + if not prox and original_proxy: + # if no proxy chosen but there WAS a proxy set previously + if ( + console.yesno( + "Are you sure you want to disable apt proxy?" + ) + == "ok" + ): + set_proxy(prox) + else: + set_proxy(prox) + break + else: + break diff --git a/debian/confconsole/usr/lib/confconsole/plugins.d/Proxy_Settings/description b/debian/confconsole/usr/lib/confconsole/plugins.d/Proxy_Settings/description new file mode 100644 index 0000000..d5843f6 --- /dev/null +++ b/debian/confconsole/usr/lib/confconsole/plugins.d/Proxy_Settings/description @@ -0,0 +1 @@ +Configure Proxy Settings diff --git a/debian/confconsole/usr/lib/confconsole/plugins.d/Region_Config/description b/debian/confconsole/usr/lib/confconsole/plugins.d/Region_Config/description new file mode 100644 index 0000000..cb805f7 --- /dev/null +++ b/debian/confconsole/usr/lib/confconsole/plugins.d/Region_Config/description @@ -0,0 +1 @@ +Region & time settings diff --git a/debian/confconsole/usr/lib/confconsole/plugins.d/Region_Config/keyboard.py b/debian/confconsole/usr/lib/confconsole/plugins.d/Region_Config/keyboard.py new file mode 100755 index 0000000..128f18c --- /dev/null +++ b/debian/confconsole/usr/lib/confconsole/plugins.d/Region_Config/keyboard.py @@ -0,0 +1,56 @@ +"""Reconfigure Keyboard""" + +import subprocess +from subprocess import check_output, check_call + + +def is_installed(pkg: str) -> bool: + for line in check_output( + ["apt-cache", "policy", pkg], text=True + ).splitlines(): + if line.startswith(" Installed"): + _, val = line.split(":") + if val.strip() in ("(none)", ""): + return False + return True + + +def run(): + flag = [] + # interactive is inherited so doesn't need to be defined + if interactive: + to_install = [] + for package in ["console-setup", "keyboard-configuration"]: + if not is_installed(package): + to_install.append(package) + if to_install: + ret = console.yesno( + "The following package(s) is/are required for this" + " operation:\n\n" + f" {' '.join(to_install)}\n\n" + "Do you wish to install now?", + autosize=True, + ) + + if ret == "ok": + check_call(["apt-get", "-y", "install", *to_install]) + else: + return + + ret = console.yesno( + "Note: If new keyboard settings are not applied, you may need" + " to reboot your operating system. Continue with" + " configuration?", + autosize=True, + ) + + if ret != 0: + return + else: + flag = ["-f", "noninteractive"] + + subprocess.run(["dpkg-reconfigure", "keyboard-configuration", *flag]) + subprocess.run( + ["udevadm", "trigger", "--subsystem-match=input", "--action=change"] + ) + subprocess.run(["service", "keyboard-setup", "restart"]) diff --git a/debian/confconsole/usr/lib/confconsole/plugins.d/Region_Config/locales.py b/debian/confconsole/usr/lib/confconsole/plugins.d/Region_Config/locales.py new file mode 100755 index 0000000..1282b39 --- /dev/null +++ b/debian/confconsole/usr/lib/confconsole/plugins.d/Region_Config/locales.py @@ -0,0 +1,32 @@ +"""Reconfigure locales""" + +import subprocess +import os + + +def run(): + # interactive & console are inherited so doesn't need to be defined + if interactive: + console.msgbox( + "Locale", + 'We STRONGLY recommend you choose "None" as your default locale.', + autosize=True, + ) + + subprocess.run(["dpkg-reconfigure", "locales"]) + else: + locale = os.getenv("LOCALE") + + if locale: + subprocess.run(["locale-gen", locale]) + subprocess.run( + [ + "update-locale", + f"LANG={locale}", + f"LANGUAGE={locale}", + f"LC_ALL={locale}", + ] + ) + subprocess.run( + ["dpkg-reconfigure", "-f", "noninteractive", "locales"] + ) diff --git a/debian/confconsole/usr/lib/confconsole/plugins.d/Region_Config/tzdata.py b/debian/confconsole/usr/lib/confconsole/plugins.d/Region_Config/tzdata.py new file mode 100755 index 0000000..8a0a651 --- /dev/null +++ b/debian/confconsole/usr/lib/confconsole/plugins.d/Region_Config/tzdata.py @@ -0,0 +1,21 @@ +"""Reconfigure TZdata""" + +import subprocess +import os + + +def run(): + flag = [] + # interactive is inherited so doesn't need to be defined + if not interactive: + tz = os.getenv("TZ") + + if tz: + with open("/etc/timezone", "w") as f: + f.write(tz) + + flag = ["-f", "noninteractive"] + + subprocess.run( + ["dpkg-reconfigure", *flag, "tzdata"], stderr=subprocess.DEVNULL + ) diff --git a/debian/confconsole/usr/lib/confconsole/plugins.d/System_Settings/Confconsole_auto_start.py b/debian/confconsole/usr/lib/confconsole/plugins.d/System_Settings/Confconsole_auto_start.py new file mode 100755 index 0000000..8c481a3 --- /dev/null +++ b/debian/confconsole/usr/lib/confconsole/plugins.d/System_Settings/Confconsole_auto_start.py @@ -0,0 +1,57 @@ +"""Enable/Disable Confconsole autostart on login""" + +from os import chmod, stat, path + +CONFCONSOLE_AUTO = path.expanduser("~/.bashrc.d/confconsole-auto") + + +def enable_autostart() -> None: + st = stat(CONFCONSOLE_AUTO) + chmod(CONFCONSOLE_AUTO, st.st_mode | 0o111) + + +def disable_autostart() -> None: + st = stat(CONFCONSOLE_AUTO) + chmod(CONFCONSOLE_AUTO, st.st_mode ^ 0o111) + + +def check_autostart() -> str | bool: + if path.isfile(CONFCONSOLE_AUTO): + st = stat(CONFCONSOLE_AUTO) + return st.st_mode & 0o111 == 0o111 + else: + return "fail" + + +def run(): + enabled = check_autostart() + if enabled == "fail": + msg = "Auto-start file for Confconsole does not exist.\n" + # console is inherited so doesn't need to be defined + r = console.msgbox("Error", msg) + else: + status = "enabled" if enabled else "disabled" + msg = """Confconsole Auto start is currently {}""" + r = console._wrapper( + "yesno", + msg.format(status), + 10, + 30, + yes_label="Toggle", + no_label="Ok", + ) + while r == "ok": + if enabled: + disable_autostart() + else: + enable_autostart() + enabled = check_autostart() + status = "enabled" if enabled else "disabled" + r = console._wrapper( + "yesno", + msg.format(status), + 10, + 30, + yes_label="Toggle", + no_label="Ok", + ) diff --git a/debian/confconsole/usr/lib/confconsole/plugins.d/System_Settings/Secupdates_adv_conf.py b/debian/confconsole/usr/lib/confconsole/plugins.d/System_Settings/Secupdates_adv_conf.py new file mode 100755 index 0000000..7e13daa --- /dev/null +++ b/debian/confconsole/usr/lib/confconsole/plugins.d/System_Settings/Secupdates_adv_conf.py @@ -0,0 +1,120 @@ +"""Config SecUpdate behaviour""" + +import os +from os.path import exists, islink +from typing import Optional + +FILE_PATH = "/etc/cron-apt/action.d/5-install" +CONF_DEFAULT = "/etc/cron-apt/action-available.d/5-install.default" +CONF_ALT = "/etc/cron-apt/action-available.d/5-install.alt" + +doc_url = "www.turnkeylinux.org/secupdates#issue-res" + +info_default = """ +This is the historic and default TurnKey cronapt behaviour. Only packages \ +from the repos listed in security.sources.list will be installed. \ +Missing dependencies (extremely rare) will not be installed and will cause \ +package removal. This package removal may cause one or more services to fail.\ +""" + +info_alternate = """ +This is a new option which is similar to the default. However, it will not \ +allow removal of packages. This will maximise uptime of all services, but \ +conversely, may also allow services with unpatched security vulnerabilities \ +to continue running.""" + + +def new_link(link_path: str, target_path: str) -> None: + try: + os.unlink(link_path) + except FileNotFoundError: + pass + os.symlink(target_path, link_path) + + +def change_link(new_path: str) -> None: + new_link(FILE_PATH, new_path) + + +def check_paths() -> tuple[int, list[str]]: + errors: list[str] = [] + for _path in [FILE_PATH, CONF_DEFAULT, CONF_ALT]: + if not exists(_path): + errors.append(f"Path not found:\n{_path}") + if errors: + return 2, errors + if islink(FILE_PATH): + _target_path = os.readlink(FILE_PATH) + if _target_path.startswith("../action-available.d/5-install"): + _target_path = _target_path.replace("..", "/etc/cron-apt") + if _target_path == CONF_DEFAULT: + return 0, ["default"] + elif _target_path == CONF_ALT: + return 0, ["alternate"] + else: + return 1, [f"Unexpected link target:\n{_target_path}"] + else: + return 1, [f"{FILE_PATH}\nis not a symlink"] + + +def button_label(current: str) -> str: + options = ["default", "alternate"] + try: + options.remove(current) + except ValueError: + pass + + other = options[0] + msg = f"Enable '{other}'" + + return f"{msg:^20}" + + +def get_details(choice: str) -> Optional[str]: + if choice == "default": + return info_default + elif choice == "alternate": + return info_alternate + else: + return None + + +def run() -> None: + retcode, data = check_paths() + if retcode: + msg = "Error(s) encountered while checking status:" + for message in data: + msg = f"{msg}\n{message}" + msg = f"{msg}\nFor more info please see\n\n{doc_url}" + r = console.msgbox("Error", msg) + else: + # if retcode == 0, then data == [status] + status = data[0] + msg = ( + "Current SecUpate Issue resolution strategy is:\n\n\t{}" + "\n{}\n\nFor more info please see\n\n{}" + ) + r = console._wrapper( + "yesno", + msg.format(status, get_details(status), doc_url), + 20, + 60, + yes_label=button_label(status), + no_label="Back", + ) + while r == "ok": + # Toggle was clicked + if data == ["default"]: + change_link(CONF_ALT) + else: + change_link(CONF_DEFAULT) + retcode, data = check_paths() + status = data[0] + r = console._wrapper( + "yesno", + msg.format(status, get_details(status), doc_url), + 20, + 60, + yes_label=button_label(status), + no_label="Back", + ) diff --git a/debian/confconsole/usr/lib/confconsole/plugins.d/System_Settings/Security_Update.py b/debian/confconsole/usr/lib/confconsole/plugins.d/System_Settings/Security_Update.py new file mode 100755 index 0000000..a6365ff --- /dev/null +++ b/debian/confconsole/usr/lib/confconsole/plugins.d/System_Settings/Security_Update.py @@ -0,0 +1,10 @@ +"""Install Security Updates""" + +from subprocess import check_call, CalledProcessError + + +def run(): + try: + check_call(["turnkey-install-security-updates"]) + except CalledProcessError: + console.msgbox("An error occured while running security updates!") diff --git a/debian/confconsole/usr/lib/confconsole/plugins.d/System_Settings/description b/debian/confconsole/usr/lib/confconsole/plugins.d/System_Settings/description new file mode 100644 index 0000000..a0abc9a --- /dev/null +++ b/debian/confconsole/usr/lib/confconsole/plugins.d/System_Settings/description @@ -0,0 +1 @@ +Various global settings diff --git a/debian/confconsole/usr/lib/confconsole/plugins.d/System_Settings/hostname.py b/debian/confconsole/usr/lib/confconsole/plugins.d/System_Settings/hostname.py new file mode 100755 index 0000000..fea6855 --- /dev/null +++ b/debian/confconsole/usr/lib/confconsole/plugins.d/System_Settings/hostname.py @@ -0,0 +1,129 @@ +"""Update machine hostname.""" + +import re +import subprocess +from subprocess import Popen, PIPE + +TITLE = "Update Hostname" + + +def _validate_hostname(hostname): + pattern = r"^[-\w]*$" + hostname_parts = hostname.split(".") + match_parts = [] + fail_parts = [] + for part in hostname_parts: + match = re.match(pattern, part) + if match: + match_parts.append(part) + else: + fail_parts.append(part) + if len(hostname_parts) == len(match_parts): + return hostname + else: + return None + + +def _get_current_hostname(): + with open("/etc/hostname") as fob: + return fob.readline().strip() + + +def run(): + while True: + ret, new_hostname = console.inputbox( + TITLE, + "Please enter the new hostname for this machine:", + _get_current_hostname(), + ) + if ret == "ok": + valid_hostname = _validate_hostname(new_hostname) + if not valid_hostname: + console.msgbox( + TITLE, + f"Invalid hostname ({new_hostname}", + ) + continue + else: + proc = Popen(["hostname", new_hostname], stderr=PIPE) + _, out = proc.communicate() + returncode = proc.returncode + + if returncode: + console.msgbox( + TITLE, + f"{out} ({new_hostname})", + ) + continue + + new_localhost = new_hostname.split(".")[0] + + with open("/etc/hostname", "w") as fob: + fob.write(new_localhost + "\n") + + if new_localhost != new_hostname: + add_hosts = f"{new_localhost} {new_hostname}" + else: + add_hosts = new_hostname + with open("/etc/hosts", "r") as fob: + lines = fob.readlines() + with open("/etc/hosts", "w") as fob: + for line in lines: + fob.write( + re.sub( + r"^127\.0\.1\.1 .*", "127.0.1.1 " + add_hosts, line + ) + ) + + with open("/etc/postfix/main.cf", "r") as fob: + lines = fob.readlines() + with open("/etc/postfix/main.cf", "w") as fob: + for line in lines: + fob.write( + re.sub( + r"myhostname =.*", + f"myhostname = {new_hostname}", + line, + ) + ) + with open("/etc/network/interfaces", "r") as fob: + lines = fob.readlines() + with open("/etc/network/interfaces", "w") as fob: + for line in lines: + fob.write( + re.sub( + r"hostname .*", f"hostname {new_hostname}", line + ) + ) + should_restart = ( + console.yesno( + "Networking must be restarted to apply these changes. " + "However restarting networking may close your ssh " + "connection as hostname changes may effect the address " + "allocated to you by DHCP. Not restarting may have other " + "adverse effects in other software.\n\nDo you want to " + "restart networking?", + True, + ) + == "ok" + ) + + if should_restart: + proc = subprocess.run(["systemctl", "restart", "networking"]) + proc = subprocess.run( + ["postfix", "reload"], capture_output=True, text=True + ) + if proc.returncode != 0: + console.msgbox( + TITLE, + f"Error reloading postfix:\n{proc.stderr}", + ) + console.msgbox( + TITLE, + "Hostname updated successfully. Some applications" + " may require restart before the settings are" + " applied.", + ) + break + else: + break diff --git a/debian/confconsole/usr/lib/confconsole/plugins.d/example.py b/debian/confconsole/usr/lib/confconsole/plugins.d/example.py new file mode 100644 index 0000000..45aee7c --- /dev/null +++ b/debian/confconsole/usr/lib/confconsole/plugins.d/example.py @@ -0,0 +1,43 @@ +"""I will be the description""" + +# +""" +Note: plugins must be executable + + +Global Variables: + +eventManager - allows access to the event system + eventManager.add_event() adds an event of said name + eventManager.add_handler(, ) adds handler to event + eventManager.fire_event() call all handers for said event +console - allows python dialog access (see confconsole.py) + +impByName - a function, takes a name and returns all plugin modules + matching that name. +impByDir - a function, takes a path and returns all plugin modules within + that directory. +impByPath - a function, takes a path and returns the plugin module at + specified path or None. + + +Plugin Functions/Scope: + +main body - the main body is run at load time of the plugin, none of the + global variables are set at this point, neither are all the + plugins loaded. + +doOnce() - if defined is run once, after loading all plugins and before + running confconsole. + +run() - if defined is run whenever the plugin is selected, if not defined, no + menu entry is created for this plugin. +""" + + +def doOnce(): + eventManager.add_event("test_event") + + +def run(): + eventManager.fire_event("test_event") diff --git a/debian/confconsole/usr/share/confconsole/autostart/confconsole-auto b/debian/confconsole/usr/share/confconsole/autostart/confconsole-auto new file mode 100755 index 0000000..d9f1e1d --- /dev/null +++ b/debian/confconsole/usr/share/confconsole/autostart/confconsole-auto @@ -0,0 +1,45 @@ +#!/bin/bash -e +# Simple auto starter script for confconsole +# +# To enable every login: +# - set "autostart true" in /etc/confconsole/confconsole.conf +# +# To enable next login only: +# - set "autostart once" in /etc/confconsole/confconsole.conf; or +# +# To disable autostart: +# - set "autostart false" /etc/confconsole/confconsole.conf; or +# - comment out or remove "autostart" in /etc/confconsole/confconsole.conf +# - make this script non-executable +# +# Note that if this script is non-executable, or not located in ~/.bashrc.d/ +# of a root or a sudo user account, confconsole will NOT autostart, +# regardless of "autostart" value in /etc/confconsole/confconsole.conf. + + +# if "dumb" terminal (e.g. scp or others) exit straight away +if [ "$TERM" = "dumb" ]; then + return +fi + +conf=/etc/confconsole/confconsole.conf +autostart=$(grep "^autostart" $conf | tail -1 | cut -d' ' -f2) +while true; do + case "$autostart" in + once) + # disable autostart if set to "once" + sed -i "s|^autostart.*|autostart false|g" $conf + break;; + true) + break;; + *) + return;; + esac +done + +# if not root use sudo (support for sudoadmin) +if [ "$(whoami)" != "root" ]; then + SUDO=sudo +fi + +$SUDO confconsole diff --git a/debian/confconsole/usr/share/confconsole/letsencrypt/dehydrated-confconsole.config b/debian/confconsole/usr/share/confconsole/letsencrypt/dehydrated-confconsole.config new file mode 100644 index 0000000..1a7459c --- /dev/null +++ b/debian/confconsole/usr/share/confconsole/letsencrypt/dehydrated-confconsole.config @@ -0,0 +1,29 @@ +######################################################## +# This is the config file for dehydrated when launched # +# via confconsole on TurnKey GNU/Linux. # +# # +# It is loaded by the dehydrated-wrapper script. # +# # +# For more information about the confconsole Let's # +# Encrypt plugin and/or the dehydrated-wrapper please # +# see: # +# /usr/share/doc/confconsole/docs/Lets_Encrypt.rst # +# or: # +# https://www.turnkeylinux.org/docs/letsencrypt # +# # +# For more comprehensive example conf, see # +# /usr/share/doc/dehydrated/examples/config # +######################################################## + +BASEDIR=/var/lib/dehydrated +WELLKNOWN="${BASEDIR}/acme-challenges" +DOMAINS_TXT="/etc/dehydrated/confconsole.domains.txt" +HOOK="/etc/dehydrated/confconsole.hook.sh" +CHALLENGETYPE="http-01" + +# required for DNS-01 only - ignored by HTTP-01 challenge +PROVIDER='auto' +LEXICON_CONFIG_DIR='/etc/dehydrated' + +# staging server for testing - leave commented for production +#CA="https://acme-staging-v02.api.letsencrypt.org/directory" diff --git a/debian/confconsole/usr/share/confconsole/letsencrypt/dehydrated-confconsole.cron b/debian/confconsole/usr/share/confconsole/letsencrypt/dehydrated-confconsole.cron new file mode 100644 index 0000000..5417880 --- /dev/null +++ b/debian/confconsole/usr/share/confconsole/letsencrypt/dehydrated-confconsole.cron @@ -0,0 +1,34 @@ +#!/bin/bash + +export PATH="$PATH:/usr/sbin" + +CERT=/etc/ssl/private/cert.pem +RENEW=2592000 # seconds to cert expiry to try renew: 2592000 = 30 days +LOG=/var/log/confconsole/letsencrypt.log +DEHYDRATED=/usr/lib/confconsole/plugins.d/Lets_Encrypt/dehydrated-wrapper +ARG="--force" + +log() { + echo "[$(date "+%Y-%m-%d %H:%M:%S")] cron: $*" >> $LOG +} + +exit_code=0 + +cert_expire=$(/usr/bin/openssl x509 -checkend $RENEW -noout -in $CERT) \ + || exit_code=$? +log "${CERT}: ${cert_expire} within $(( RENEW / 60 / 60 / 24 )) days" + +if [[ "$exit_code" -eq 0 ]]; then + log "Nothing to do." +else + exit_code=0 + log "Attempting renewal." + dehydrated_output="$($DEHYDRATED $ARG 2>&1)" || exit_code=$? + log "$dehydrated_output" + if [[ $exit_code -ne 0 ]]; then + log "ERR: $(basename $DEHYDRATED) exited with a non-zero exit code." + exit 1 + else + log "certificate renewed" + fi +fi diff --git a/debian/confconsole/usr/share/confconsole/letsencrypt/dehydrated-confconsole.domains b/debian/confconsole/usr/share/confconsole/letsencrypt/dehydrated-confconsole.domains new file mode 100644 index 0000000..6902053 --- /dev/null +++ b/debian/confconsole/usr/share/confconsole/letsencrypt/dehydrated-confconsole.domains @@ -0,0 +1,5 @@ +# This is confconsole's domain.txt file - please use with confconsole +# If configuring/using Dehydrated directly, it is advised to use with +# appropriate configuration files. +example.com www.example.com ftp.example.com +# Add additional custom domains below here and confconsole will ignore diff --git a/debian/confconsole/usr/share/confconsole/letsencrypt/dehydrated-confconsole.hook-dns-01.sh b/debian/confconsole/usr/share/confconsole/letsencrypt/dehydrated-confconsole.hook-dns-01.sh new file mode 100644 index 0000000..288e6ed --- /dev/null +++ b/debian/confconsole/usr/share/confconsole/letsencrypt/dehydrated-confconsole.hook-dns-01.sh @@ -0,0 +1,99 @@ +#!/bin/bash -e + +# This dehydrated hook script is packaged with Confconsole. +# It is designed to be used in conjunction with the TurnKey dehydrated-wrapper +# and turnkey-lexicon wrapper, which in turn depends on lexicon installed to +# a venv (confconsole will install if needed). +# For more info, please see https://www.turnkeylinux.org/docs/letsencypt + +# DNS-01 Hook Script + +export PROVIDER_UPDATE_DELAY=${PROVIDER_UPDATE_DELAY:-"30"} +#provider 'auto' can be used since roughly v3.3.13 of lexicon. +export PROVIDER=${PROVIDER:-"auto"} + +function hook_log { + default="[$(date "+%F %T")] $(basename "$0"):" + case ${1} in + info) echo "$default INFO: ${2}";; + success) echo "$default SUCCESS: ${2}" >&2;; + fatal) echo "$default FATAL: ${2}" >&2; exit 1;; + esac +} + +for var in PROVIDER LEXICON_CONFIG_DIR TKL_KEYFILE TKL_CERTFILE TKL_COMBINED TKL_DHPARAM; do + eval "z=\$$var" + [[ -z "$z" ]] && hook_log fatal "$var is not set. Exiting..." +done + +function deploy_challenge { + local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}" + + hook_log info "Deploying challenge for $DOMAIN." + hook_log info "Creating a TXT challenge-record with $PROVIDER." + turnkey-lexicon --config-dir="$LEXICON_CONFIG_DIR" \ + "$PROVIDER" create "${DOMAIN}" TXT \ + --name="_acme-challenge.${DOMAIN}." \ + --content="${TOKEN_VALUE}" + + local DELAY_COUNTDOWN=$PROVIDER_UPDATE_DELAY + while [[ $DELAY_COUNTDOWN -gt 0 ]]; do + echo -ne "${DELAY_COUNTDOWN}\033[0K\r" + sleep 1 + : $((DELAY_COUNTDOWN--)) + done +} + +function invalid_challenge() { + local DOMAIN="${1}" RESPONSE="${2}" + + hook_log fatal "Challenge response for ${DOMAIN} failed: ${RESPONSE}." +} + +function clean_challenge { + local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}" + + hook_log info "Clean challenge for ${DOMAIN}." + + turnkey-lexicon --config-dir="$LEXICON_CONFIG_DIR" \ + "$PROVIDER" delete "${DOMAIN}" TXT \ + --name="_acme-challenge.${DOMAIN}." \ + --content="${TOKEN_VALUE}" +} + +function deploy_cert { + local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}" TIMESTAMP="${6}" + + hook_log success "Cert request successful. Writing relevant files for $DOMAIN." + hook_log info "fullchain: $FULLCHAINFILE" + hook_log info "keyfile: $KEYFILE" + cat "$KEYFILE" > "$TKL_KEYFILE" + cat "$FULLCHAINFILE" > "$TKL_CERTFILE" + cat "$TKL_CERTFILE" "$TKL_KEYFILE" "$TKL_DHPARAM" > "$TKL_COMBINED" + hook_log success "Files written/created for $DOMAIN: $TKL_CERTFILE - $TKL_KEYFILE - $TKL_COMBINED." +} + +function unchanged_cert { + local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}" + + hook_log info "cert for $DOMAIN is unchanged - nothing to do" +} + +[[ $(which turnkey-lexicon) ]] || hook_log fatal "turnkey-lexicon is not found." +if [[ "$PROVIDER" = "auto" ]]; then + [[ $(which nslookup) ]] || hook_log fatal "nslookup is not installed (provided by dnsutils package)." +fi + +HANDLER="$1"; shift +case "$HANDLER" in + deploy_challenge) + deploy_challenge "$@";; + invalid_challenge) + invalid_challenge "$@";; + clean_challenge) + clean_challenge "$@";; + deploy_cert) + deploy_cert "$@";; + unchanged_cert) + unchanged_cert "$@";; +esac diff --git a/debian/confconsole/usr/share/confconsole/letsencrypt/dehydrated-confconsole.hook-http-01.sh b/debian/confconsole/usr/share/confconsole/letsencrypt/dehydrated-confconsole.hook-http-01.sh new file mode 100644 index 0000000..5cbafd4 --- /dev/null +++ b/debian/confconsole/usr/share/confconsole/letsencrypt/dehydrated-confconsole.hook-http-01.sh @@ -0,0 +1,66 @@ +#!/bin/bash -e + +# This dehydrated hook script is packaged with Confconsole. +# It is designed to be used in conjunction with the TurnKey dehydrated-wrapper. +# For more info, please see https://www.turnkeylinux.org/docs/letsencypt + +# HTTP-01 Hook Script + +function hook_log { + default="[$(date "+%F %T")] $(basename "$0"):" + case ${1} in + info) echo "$default INFO: ${2}";; + success) echo "$default SUCCESS: ${2}" >&2;; + fatal) echo "$default FATAL: ${2}" >&2; exit 1;; + esac +} + +for var in HTTP HTTP_BIN HTTP_PID HTTP_LOG TKL_KEYFILE TKL_CERTFILE TKL_COMBINED TKL_DHPARAM; do + eval "z=\$$var" + [[ -z "$z" ]] && hook_log fatal "$var is not set. Exiting..." +done + +function deploy_challenge { + local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}" + + hook_log info "Deploying challenge for $DOMAIN" + hook_log info "Serving $WELLKNOWN/$TOKEN_FILENAME on http://$DOMAIN/.well-known/acme-challenge/$TOKEN_FILENAME" + $HTTP_BIN --deploy "$WELLKNOWN/$TOKEN_FILENAME" +} + +function clean_challenge { + local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}" + + hook_log info "Clean challenge for $DOMAIN" + $HTTP_BIN --clean "$WELLKNOWN/$TOKEN_FILENAME" +} + +function deploy_cert { + local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}" TIMESTAMP="${6}" + + hook_log success "Cert request successful. Writing relevant files for $DOMAIN." + hook_log info "fullchain: $FULLCHAINFILE" + hook_log info "keyfile: $KEYFILE" + cat "$KEYFILE" > "$TKL_KEYFILE" + cat "$FULLCHAINFILE" > "$TKL_CERTFILE" + cat "$TKL_CERTFILE" "$TKL_KEYFILE" "$TKL_DHPARAM" > "$TKL_COMBINED" + hook_log success "Files written/created for $DOMAIN: $TKL_CERTFILE - $TKL_KEYFILE - $TKL_COMBINED." +} + +function unchanged_cert { + local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}" + + hook_log info "cert for $DOMAIN is unchanged - nothing to do" +} + +HANDLER="$1"; shift +case "$HANDLER" in + deploy_challenge) + deploy_challenge "$@";; + clean_challenge) + clean_challenge "$@";; + deploy_cert) + deploy_cert "$@";; + unchanged_cert) + unchanged_cert "$@";; +esac diff --git a/debian/confconsole/usr/share/confconsole/letsencrypt/index.html b/debian/confconsole/usr/share/confconsole/letsencrypt/index.html new file mode 100644 index 0000000..4e15c83 --- /dev/null +++ b/debian/confconsole/usr/share/confconsole/letsencrypt/index.html @@ -0,0 +1,29 @@ + + + +Temporarily Down for Maintenance + + + +

Temporarily Down for Maintenance

+

The site is currently down for scheduled maintenance. +We should be back online in a few minutes.

+ +

For further information, please contact the webmaster or system administrator.

+ + diff --git a/debian/confconsole/usr/share/confconsole/letsencrypt/lexicon-confconsole-provider_cloudflare.yml b/debian/confconsole/usr/share/confconsole/letsencrypt/lexicon-confconsole-provider_cloudflare.yml new file mode 100644 index 0000000..71521c2 --- /dev/null +++ b/debian/confconsole/usr/share/confconsole/letsencrypt/lexicon-confconsole-provider_cloudflare.yml @@ -0,0 +1,22 @@ +# Cloudflare Lexicon conf template provided by TurnKey Linux's Confconsole +# +# Confconsole uses Lexicon to support the Let's Encrypt DNS-01 challenge type: +# https://dns-lexicon.github.io/dns-lexicon/ +# https://www.turnkeylinux.org/docs/confconsole/letsencrypt#dns-01 +# https://letsencrypt.org/docs/challenge-types/#dns-01-challenge +# +# Uncomment and update the example authentication lines below relevant to your +# desired Cloudflare authentication method. See Lexicon Cloudflare config docs +# for more info: +# https://dns-lexicon.github.io/dns-lexicon/configuration_reference.html#cloudflare + +# Cloudflare example 1 - using global api key +#auth_username: YOUR_CF_USERNAME +#auth_token: YOUR_CF_API_TOKEN + +# Cloudflare example 2 - using unscoped API token +#auth_token: YOUR_CF_UNSCOPED_API_TOKEN + +# Cloudflare example 3 - using scoped API token +#auth_token: YOUR_CF_SCOPED_API_TOKEN +#zone_id: YOUR_CF_ZONE_ID diff --git a/debian/confconsole/usr/share/confconsole/letsencrypt/lexicon-confconsole-provider_example.yml b/debian/confconsole/usr/share/confconsole/letsencrypt/lexicon-confconsole-provider_example.yml new file mode 100644 index 0000000..1740aeb --- /dev/null +++ b/debian/confconsole/usr/share/confconsole/letsencrypt/lexicon-confconsole-provider_example.yml @@ -0,0 +1,18 @@ +# Generic Lexicon conf template provided by TurnKey Linux's Confconsole +# +# Confconsole uses Lexicon to support the Let's Encrypt DNS-01 challenge type: +# https://dns-lexicon.github.io/dns-lexicon/ +# https://www.turnkeylinux.org/docs/confconsole/letsencrypt#dns-01 +# https://letsencrypt.org/docs/challenge-types/#dns-01-challenge +# +# AWS Route53 & Cloudflare specific Lexicon config are also provided by +# Confconsole. When using either of those DNS providers, rerunning Confconsole +# and selecting the relevant provider is recommended. +# +# Replace or add Lexicon config relevant to your DNS provider; see the Lexicon +# doc page for your specific provider: +# https://dns-lexicon.github.io/dns-lexicon/configuration_reference.html + +# Adjust/replace as per Lexicon docs for your specific DNS provider +#config_key_1: config_value_1 +#config_key_2: config_value_2 diff --git a/debian/confconsole/usr/share/confconsole/letsencrypt/lexicon-confconsole-provider_route53.yml b/debian/confconsole/usr/share/confconsole/letsencrypt/lexicon-confconsole-provider_route53.yml new file mode 100644 index 0000000..857b088 --- /dev/null +++ b/debian/confconsole/usr/share/confconsole/letsencrypt/lexicon-confconsole-provider_route53.yml @@ -0,0 +1,23 @@ +# Route53 Lexicon conf template provided by TurnKey Linux's Confconsole +# +# Confconsole uses Lexicon to support the Let's Encrypt DNS-01 challenge type: +# https://dns-lexicon.github.io/dns-lexicon/ +# https://www.turnkeylinux.org/docs/confconsole/letsencrypt#dns-01 +# https://letsencrypt.org/docs/challenge-types/#dns-01-challenge +# +# Uncomment and update the example authentication lines below relevant to your +# Route53 authentication method. See Lexicon Route53 config docs for more +# info: +# https://dns-lexicon.github.io/dns-lexicon/configuration_reference.html#route53 + +# AWS Route53 example 1 - AWS API key/secret auth: +#auth_access_key: YOUR_AWS_ACCESS_KEY +#auth_access_secret: YOUR_AWS_ACCESS_SECRET +#private_zone: False # generally you'll always want public zone +#zone_id: YOUR_ZONE_ID + +# AWS Route53 example 2 - AWS username/token auth: +#auth_username: YOUR_AWS_USERNAME +#auth_token: YOUR_AWS_TOKEN +#private_zone: False # generally you'll always want public zone +#zone_id: YOUR_ZONE_ID diff --git a/debian/confconsole/usr/share/doc/confconsole/Lets_encrypt#advanced.rst.gz b/debian/confconsole/usr/share/doc/confconsole/Lets_encrypt#advanced.rst.gz new file mode 120000 index 0000000..0aa8f0a --- /dev/null +++ b/debian/confconsole/usr/share/doc/confconsole/Lets_encrypt#advanced.rst.gz @@ -0,0 +1 @@ +Lets_encrypt.rst.gz \ No newline at end of file diff --git a/debian/confconsole/usr/share/doc/confconsole/Lets_encrypt.rst.gz b/debian/confconsole/usr/share/doc/confconsole/Lets_encrypt.rst.gz new file mode 100644 index 0000000000000000000000000000000000000000..c8ebe9d33f0ca01c3cdc5ddf06b7c0f5bb380b16 GIT binary patch literal 3773 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 literal 0 HcmV?d00001 diff --git a/debian/confconsole/usr/share/doc/confconsole/Proxy_settings.rst b/debian/confconsole/usr/share/doc/confconsole/Proxy_settings.rst new file mode 100644 index 0000000..48f4998 --- /dev/null +++ b/debian/confconsole/usr/share/doc/confconsole/Proxy_settings.rst @@ -0,0 +1,32 @@ +Confconsole - Proxy settings +============================ + +.. contents:: + +Overview +-------- + +The confconsole proxy setting plugin currently allows you to set a proxy +for apt. This will allow you to get system updates and install +packages, even if your server is hidden behind a proxy server. + +.. image:: ./images/05_confconsole_proxy_settings.png + +Apt proxy +--------- + +Selecting this option allows you to set the domain of a HTTP proxy for +use by the apt package management system. + +Currently only HTTP proxies are supported (i.e. not HTTPS). If you +require use of an HTTPS proxy, unfortunately, you'll need to manually +configure that. + +Alternate ports (other than 80) are also possible by appending the port +to the end. E.g. http://proxy.example.com:8080 + +**Note:** you must include the scheme for your proxy. As only HTTP is +supported, that means it should look like this: + + http://proxy.example.com + diff --git a/debian/confconsole/usr/share/doc/confconsole/README.gz b/debian/confconsole/usr/share/doc/confconsole/README.gz new file mode 100644 index 0000000000000000000000000000000000000000..b8cd1934e21ff98efcd1aaddd2376564ed5a2ca2 GIT binary patch literal 1881 zcmV-f2d4NRiwFP!000021C3bQa^p4>@Ww83IJn*}xm-&Oz;f;SXdZpIE3a^Z& zznp0$`}qHT7`x$}mEIrtPFhD2%!>O)Ic}w>w~}A%m2bGKtd!a{#yc)_&8t>b8&S5B zw@Po^CBp@=6RM4CMq^d-l{MQBj5XeO?!$b(-|q_#^EdL)lI4Z5>v?S|H?QDAl?`Xh zMsjC@tt796<|X8q0F^N9PWdmP9En`Rwon$8+0Mt3&je=3-UW(Z+TlxGYCs(+a zcB`~#c`2N%ulQE#z-p^@Qu8Vb;@+6H;P)N?nw_epgV_KK7gWWSUYUrLXj`*)JUGCM zD_ml`Jup#K(j_cIR|_xsR_FsnZ?`b6H@P}7*>v>F6^#9kVC|ILvswNm*i89fS$Wt< zK^*x+E0lLxwwYjM*&Mx!bcPm zMysGJ@>R6T9|qk2;ltSXM(Xo0I4`WHgaR-K`B4-0YUR3C9AsVa8_!E){MGn6)tJ&d zvjGMocte4zkX{EW4H8$&l?bipJI^n6%KCux?eV-U_!qO6$Q$72Rm%u<;rP@-&GbYM z1%x#5NvpOB#!mRwAZ-W>ZO(F1ITkYKd2X zRGU!|PbpQ%#k+4WzrB8XynVQT`uvI?FaO~?VHMpj_~%;rbjpTuScfNUE`2r6@XXh= zOU@7k(^P#kTAUWCjK*b2#luIRjV9I)CKO)~wYi%lL({e2T83^pr)4 z;Wyu(`J=EK>5=&r>4YyZ)gfOW1WK-(3K6dWmo7bS|w%2lFP|3qI9<4Z zT>SoRdGoaVkfsle8lfe=@f3*)4P*^8_Qa^U1Wt))5XC7sal9rz>8lP6HyisP{nYW# zy0V83H0dPRhei;ryH}Z(3ncO7(U;HB;CUvgNWJ{|`GHr``rN#Svv=^j-%~5lvrl8t zxnAm#UzW?o?B{@1K@!D_vSW_1#pSMBaH@PN0$u2IH8z<2PFg4_4al?Tupn(Q0wz794-^&%xUGge-z=qI5 zE^ff4#K5s6(8)9%Z!!B_4&jI`mhKFN+vC)r)N8ZX@9zE)&_*vxQEfe&r@fv8d+@^p%kJP7)neE zD$3mm-)3uhLd*S9#{RS`FWP{K!m+_nLS=m9bnZ_nkSD^ZnRiJ-uF@h7T&HXO6VQdO zGiVuP{;2<+Du7Vt)V4#^YHPYK)lL$a5MhdDb(z T&ZyISYrySqmGkVp*$@B#oo2cv literal 0 HcmV?d00001 diff --git a/debian/confconsole/usr/share/doc/confconsole/Region_config.rst b/debian/confconsole/usr/share/doc/confconsole/Region_config.rst new file mode 100644 index 0000000..9a65381 --- /dev/null +++ b/debian/confconsole/usr/share/doc/confconsole/Region_config.rst @@ -0,0 +1,83 @@ +Confconsole - Region config +=========================== + +.. contents:: + +Overview +-------- + +The Region config plug in allows users to customize common regional +settings on their TurnKey server. Configurable setting include +keyboard layout, locales (i.e. language and encoding) and timezone. + +.. image:: ./images/06_confconsole_region_config.png + +This is only useful if you wish to change from the defaults. The TurnKey +Linux system defaults are: + +- keyboard: English (US) - aka US International +- locale: + +Keyboard +-------- + +Select this option to reconfigure the default keyboard layout which your +server will use. Default is English (US). + +Please note that additional packages are needed to reconfigure this, so +your server will need to be connected to the internet to complete this. + +If the required package is not already installed, you will be given +the option to install it. + +Locale +------ + +A system "locale" refers to the regional conventions for things such as +date and time formatting, character display and currency display. This +essentially includes language, although not explicitly (not all programs +support alternate languages). + +When you first select this option, you will be greated with an extensive +list of avaialble locales. Simply scroll through them and use to +select/deselect locales. + +It is strongly suggested that you leave 'en_US.UTF-8' (the default) +enabled as some software requires it to function properly. It is +further suggested that you only enable the relevant UTF-8 character +set. UTF is generally the acceptd standard. + +**Important note:** You are strongly urged to set the default locale to +"None". This ensures that users logging in via SSH can use their local +PC locale, rather than being forced to use the system setting. + +**Note:** TurnKey includes a utility called locale-purge. This allows us +to keep the size of the instalation as small as possible. The downside +of that is that the system by default does not include documentation and +for languages other than English. If you do no use English as a first +language and would like to restore the non-English documentation, you +will need to manually re-install any/all packages for which the +alternate language documentation is missing. Please note, not all +packages include non-English docs. Also note that this only applies to +packages installed prior to setting your locale. All packages installed +after setting your locale, will keep everything related to configured +locales. + +Tzdata +------ + +Tzdata relates to the local timezone. On a server, as a general rule +it is best to use UTC time. And that is indeed the TurnKey default. +However, Linux can be easily configured to adopt a region specific +offset. + +This means, that whilst the underlaying system will still use UTC, +for any users (and software running on the system) it will display +the local timezone by default. + +Simply select your region. Then select the relevant city/area. + +**Note:** Some PHP applications may require you to also set the +timezone in your php.ini file. This confconsole plug in does NOT do +that! If you need to do that, you will need to manually adjust that +yourself. diff --git a/debian/confconsole/usr/share/doc/confconsole/RelNotes-0.9.1.txt b/debian/confconsole/usr/share/doc/confconsole/RelNotes-0.9.1.txt new file mode 100644 index 0000000..dd198fb --- /dev/null +++ b/debian/confconsole/usr/share/doc/confconsole/RelNotes-0.9.1.txt @@ -0,0 +1,16 @@ +==================== +v0.9.1 Release Notes +==================== + +* info dialog text is now created from templates/info.txt + + - simpler, flexible, generic + - templates are installed to /usr/share/confconsole/templates + - the following variables will be substitued: $ipaddr, $appname + +* ports are not verified to be open + + - unnecessary complexity + - each appliance can have their own template + +* bugfix: dialog is now configured not to "collapse" whitespaces and tabs diff --git a/debian/confconsole/usr/share/doc/confconsole/RelNotes-0.9.2.txt b/debian/confconsole/usr/share/doc/confconsole/RelNotes-0.9.2.txt new file mode 100644 index 0000000..28f3bc2 --- /dev/null +++ b/debian/confconsole/usr/share/doc/confconsole/RelNotes-0.9.2.txt @@ -0,0 +1,40 @@ +==================== +v0.9.2 Release Notes +==================== + +multiple nic support +==================== + +- auto configuration of default nic used in usage display + (previously eth0 was hardcoded) + +- option to set default nic for display in Usage + + $ cat /etc/confconsole.conf + default_nic eth0 + +- if no nics are configured, go directly to nic configuration + +- network configuration information displayed inline + +- basic customized manual configurations in /etc/network/interfaces + will be retained (this is a side-effect, not a feature!) + +- depends on resolvconf to support different nameservers for + different nics + + +many bugfixes and changes, these are just a few +=============================================== + +- save default route when configuration static ip (#LP:303498) + +- catch exceptions generated by 'route -n' (#LP:306928) + +- ip address validation + +- updated init script to execute confconsole more like a daemon + +- stop confconsole before shutdown/reboot (cosmetic) + + diff --git a/debian/confconsole/usr/share/doc/confconsole/RelNotes-0.9.3.txt b/debian/confconsole/usr/share/doc/confconsole/RelNotes-0.9.3.txt new file mode 100644 index 0000000..8fcb733 --- /dev/null +++ b/debian/confconsole/usr/share/doc/confconsole/RelNotes-0.9.3.txt @@ -0,0 +1,42 @@ +==================== +v0.9.3 Release Notes +==================== + +* new configuration variable: CONFCONSOLE_FGVT + + - when executing shutdown, change to vt CONFCONSOLE_FGVT if set + - fixes "unable to deallocate vt" error, and displays shutdown output + +* handle use cases as expected (user experience) + + - only display list of nics when more than 1 are available + - dont display "this is default nic" if only 1 nic + - if no nics are configured at all display error and go to advanced + - dont display cancel button on advanced menu when no nics at all + - provide user with more descriptive error (no ip or mask provided) + - dont display 'set as default nic' if interface is not configured yet + - allow user to remedy mistakes in the staticip configuration screen + - allow user to delete/unconfigure nameserver/gateway + - unconfigure the nic if all fields are empty when setting static ip + +* updated default button labels to make more sense + + - Globally: OK -> Select , Cancel -> Back + - Form (StaticIP): OK -> Apply , Cancel -> Cancel (due to global) + + - Back button now acts like a back button (rewrote the dialog + looping code) + +* fixed static nameserver configuration to persist across reboots + +* intercept exit signal and verify whether to quit (ESC, ALT+?) + +* validate all input prior to attempting to apply them (with better ip + validation) + +* template changes + + - added $hostname variable for usage substitution + - updated webmin default port to 12321 + + diff --git a/debian/confconsole/usr/share/doc/confconsole/RelNotes-0.9.4.txt b/debian/confconsole/usr/share/doc/confconsole/RelNotes-0.9.4.txt new file mode 100644 index 0000000..2c7662b --- /dev/null +++ b/debian/confconsole/usr/share/doc/confconsole/RelNotes-0.9.4.txt @@ -0,0 +1,99 @@ +==================== +v0.9.4 Release Notes +==================== + +* leverage debian networking tools (ifup/ifdown) + + - instead of reinventing (poorly) manipulation of network interface, + the correct way (and seperation of concerns) is to manipulate + various configuration files and rely on ifup/ifdown to configure + the interface. + + - not to mention the hooks that are relied on: + if-down.d if-post-down.d if-pre-up.d if-up.d + +* removed udhcpc dependency and demoted resolvconf to recommends + + - we now rely on ifup to start/stop whatever dhcp client happens to + be installed. + + - the confconsole works fine without resolvconf, but it is + recommended when using multiple nics. + + - none-the-less, when displaying an interface's nameserver: + + - check if one is set in /etc/network/interfaces (static) + - check if resolvconf via any dhcp client + - if not, fallback to /etc/resolv.conf + +* handle exceptions in a user friendly way (fault tolerant) + + - errors/bugs are inevitable, we should attempt to minimize their + impact as much as possible. + + - raising an exception terminates confconsole, and this is + unfriendly to new users (confconsole suddently crashes). + + - instead, we intercept the exception and provide a useful traceback + that can be submitted (so we an squash the bug), and finally + return the user to the previous dialog. + +* retain iface options which are already defined when updating + interfaces configuration + + - provides support for pre/post up and down configurations (flexibility) + + - bugfix: iface options were lost when updating configuration + + - this was originally by design as the user was prompted to + remove the header if manual changes were made. + + - but we need this functionality to allow other applications to + update the configuration (e.g., webmin firewall activate on + boot). + +* added support for multiple nameservers. noticable UI changes: + + - minimum of 2 nameserver fields + - atleast 1 blank nameserver field (which means an infinate amount of + nameservers can be added) + +* moved template to /etc/confconsole/usage.txt and set as conffile + + - it is a configuration file, and can be customized. + - it should not be automatically replaced when upgrading. + +* template changes + + - added web shell with port 12320 + - updated SSH line to display SFTP as well + + - generic dialog changes: increased default height (18 -> 20) + +* bugfixes + + - fixed severely broken static IP configuration screen (configuring + a static IP without a gateway would raise an exception). + + - refuse to run confconsole without root privileges. + + - changed resolvconf interface path + + - ubuntu implemented a workaround to store "run" info in /var/run + - its better to look in the path it is meant to be (ubuntu + created a symlink to /var/run) + + - install docs/ + +* misc + + - added sanity check to set_static. + + - refactored away indecipherable regexp from is_ipaddr. + + - use clean upaddr module to determine if dateway is in IP range, + and include a more helpful error message if not. + + - standardized enumeration of /etc/network/interfaces information + + diff --git a/debian/confconsole/usr/share/doc/confconsole/RelNotes-0.9.txt b/debian/confconsole/usr/share/doc/confconsole/RelNotes-0.9.txt new file mode 100644 index 0000000..eb819cf --- /dev/null +++ b/debian/confconsole/usr/share/doc/confconsole/RelNotes-0.9.txt @@ -0,0 +1,8 @@ +================== +v0.9 Release Notes +================== + +This is a beta release which is nonetheless feature complete as +originally designed. v1.0 will be released after more rigorous +testing (and potential bugfixes/tweaks) in a production setting. + diff --git a/debian/confconsole/usr/share/doc/confconsole/RelNotes-1.0.0.txt b/debian/confconsole/usr/share/doc/confconsole/RelNotes-1.0.0.txt new file mode 100644 index 0000000..0bf651b --- /dev/null +++ b/debian/confconsole/usr/share/doc/confconsole/RelNotes-1.0.0.txt @@ -0,0 +1,24 @@ +==================== +v1.0.0 Release Notes +==================== + +* update license to GPL v3 + +* update to work reliably with SystemD + +* numerous bugfixes and improvements, especially related to display + and networking + +* plugin system + + - refacted confconsole to support additional functionality + by way of a plugin system. + + - new plugins: + + - Let's Encrypt - free SSL certs + - Mail relaying - remote SMTP mail relay config + - Proxy settings - only apt proxy so far + - Region config - keyboard, locales & tzdata + - System settings - install secupdates & update hostname (so + far) diff --git a/debian/confconsole/usr/share/doc/confconsole/RelNotes-2.1.0.txt b/debian/confconsole/usr/share/doc/confconsole/RelNotes-2.1.0.txt new file mode 100644 index 0000000..8e96e98 --- /dev/null +++ b/debian/confconsole/usr/share/doc/confconsole/RelNotes-2.1.0.txt @@ -0,0 +1,5 @@ +==================== +v2.1.0 Release Notes +==================== + +* implemented support for dns-01 challenge in Let's Encrypt plugin. diff --git a/debian/confconsole/usr/share/doc/confconsole/System_settings.rst b/debian/confconsole/usr/share/doc/confconsole/System_settings.rst new file mode 100644 index 0000000..34e44e6 --- /dev/null +++ b/debian/confconsole/usr/share/doc/confconsole/System_settings.rst @@ -0,0 +1,52 @@ +System settings +=============== + +.. contents:: + +Overview +-------- + +Miscellaneous system settings. Currently a bit of a "catchall" for some +functionality we wanted to include. + +.. image:: ./images/07_confconsole_system_settings.png + +Security updates +---------------- + +This option manually checks for and installs Debian and TurnKey +security updates for package managed software (i.e. the base OS and +most; but not neccessarily all software pre-included). + +By default, all TurnKey servers automatically install security updates +daily. This option allows you to manually trigger the updates. + +This plugin leverages `turnkey-install-security-updates`, thus provides +exactly the same functionality. + +**Note:** As stated, this only installs software that is covered by the +Debian package management system. It does not apply to third party +package management software (such as pip for python, cpan for perl, +gem for ruby, composer for php, etc). More often than not, that also +means NOT webapps installed direct from third parties. Upgrading +third party software must be done manually. + +Hostname +-------- + +This option allows you to manually update the system's hostname. By +default TurnKey systems have a default hostname that matches the name +of the appliance. E.g. our LAMP server has a hostname of 'lamp', +WordPress server has a hostname of 'wordpress', etc. + +A hostname may consist of multiple segements/labels, separated by a +period/full-stop (i.e.: '.'). Each segment must contain only the +ASCII letters 'a' through 'z' (case-insensitive), the digits '0' +through '9', and the hyphen ('-'). No other symbols, punctuation +characters, or white space are permitted. Each segment must be no +more than 64 characters and the total hostname length must not exceed +255 characters. + +Some applications may need to be restarted to note the new hostname. +Rebooting is one easy way to ensure that the new hostname is being +used everywhere. diff --git a/debian/confconsole/usr/share/doc/confconsole/changelog.gz b/debian/confconsole/usr/share/doc/confconsole/changelog.gz new file mode 100644 index 0000000000000000000000000000000000000000..396918bef001c72401e71c4970f224db48e0fb3e GIT binary patch literal 167 zcmV;Y09gMYiwFP!000020}ahf4uUWgM&UWD_=mnAP>G57h*Jk9B)WmpUPxoXH4j$+p;c9AV%B{Go}Ys5h|mQEN$M^4R`p0pYJBmp*P&9_1z#ho)h!o8jj=qxwV zLTVi-M#zXsTBG4;_;7eHFilajf3h71v&?XgbCkL_?&jHNirHU**GM~v5)T<5a&bU$ Vtnz)8m)Pb@$sYr+Cfln30014`M|%JO literal 0 HcmV?d00001 diff --git a/debian/confconsole/usr/share/doc/confconsole/copyright b/debian/confconsole/usr/share/doc/confconsole/copyright new file mode 100644 index 0000000..a8ca672 --- /dev/null +++ b/debian/confconsole/usr/share/doc/confconsole/copyright @@ -0,0 +1,22 @@ +Author: Alon Swartz + +License: + + Copyright (C) 2008 Alon Swartz + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +On Debian and Ubuntu systems, the complete text of the GNU General Public +License can be found in /usr/share/common-licenses/GPL file. diff --git a/debian/confconsole/usr/share/doc/confconsole/images/00_confconsole_core_main.png b/debian/confconsole/usr/share/doc/confconsole/images/00_confconsole_core_main.png new file mode 100644 index 0000000000000000000000000000000000000000..28328b366f2fed456fb888c31c62044fdc53e547 GIT binary patch literal 36130 zcmcG0WmFzZ)9v8y?i$=(g9Zo?g1dWgm*ByJh2X&|d zTUB`Q@`g7J1OHCo@yO9jL1Ti-;?j^4RsJx$zy}iAO4~U7)!~Dku zvTy)*bPVtsf21Q5ot+JuKzeg00xNtfS+S7B@(A0}{vnmeby14)wST%LozaC6fqEApPQI(OCt(Nu4 zXlwg2Xc}S&0H4p!r|64{((Mh}ecxJbud+e;x2sJ{{q+d03it?Pf{dI($a_)XfSANu z)xb!f^y|jP+U^DvkQWF+&-INL5TbW zGqb8mlA(V09ncNVLxu@^x%Tvmd_6mtngKuq4Q*EA3ybQPBseo8ik6m^lM{c4S+7;m zJXc761qRoooZqh`!@(!wK#M77`2T=CnZV{Kob3 zEAI-lzmqc@Vnm?7`?4?5(b18I_3p%3%Ilo7C7&V%T8@W7Cn5hZQqwyl_1JK-u60N{ zCZ?dAN_49PMC~$M%2;^JaA+qYqFH_c!ZSu9j&udMH%1hQ=jYkV3>2}lYf3^&rIaZ} zMT+zZ0C4ONL`)YjF^C(9LHw3|Rin*JtxRuoc*rPZtwxZb9DYLcZmdcIj+oz=;EwMGmJI zOd9n?ZN3AnOe%q4I}mx3yNbz4aAi_ZaCk{hB%)G@dDmPrVsAnLcR2Gx*-1TtjWpA z70+$P@Nd;pty;vxg<2?}#%cj~`Dsf}MxjVCORq%T!)X}5#%c;NgOoHZBBHDJS8A2f zh7@0OllJeYhXWfS5o_Eg_x+GxI(4Gdaaxbh`Ngl1BqcBNFTzAm?K2~q1ru9t^Rsg+ z&|oiy)ZV<266*jLDrk7R?tvI8w$R(^ivlg?%n>dI##DRcZ`FsZ{R%#>J#1wxtf<5f z+=OAS0ydiUsK+dNpO;%diYciESVMc^h2cyhQM&jv&v&WnxvAJ>1)OpPFz z%wO9;6A5Z1fRNKyK?Al zrk^qA`k_{vkB=|QdZwJu>x#+A$(aII=efOb@MShAQR#tDrge3iXIrT<^735JP{lbp zh=IVPWdOgdav&e2s3eN$+(?QfWq{Y|+(FKIJYJMGN|hv}AROFm!rqB`jc%?w(NxOU zFMza##VkJ`IV~qvd%0F%BD3~t1r*%LPiZoW*RPiqrxzB8kn}xaxgguVJIyPz}fX2nIg`Dar!Pf?T!S0~=w#M(ZF+3z7B zFcfud@%Fso^tGLsGl94)Q))1M^G1`qbL zvomtc@YTaV!H-^7hGQK8nziOpYa-u&cT38AS8nj~OevL9(hL)Wf)%C2HCcIjtgNbi zBP;%Czz6#Gh($nH;p=euNO+*rzFDDwlaRpUINd#Ka(8uq-x?nBI}cAtb^adDrGSMo zhEibcx8yMEBvztUH&!0pE~!ZW#cA9?SmciQfw*yMYUoo01kLOHirH#gBI5M)*rYfU zr`tOeqR749&yS#?*6O2(*ll(cYwVT?3r5p^%AIeUtOp^y_$f3T48ihbv8~Q*|DUkP z$nKp!vcAN_1*7d%|A#bUp~VWae1(k2%1SoV?akG*KSMV=aqqu;OAL{wq$eqmG^t?7=BDY*-5n$)MhG%t(DjNB-m`v# zzx#?G859*&mFYO;PK~zBd=pMrC~BF*?WY<2F2?iiuE|L(Bz-Rwz=4wi(W;gZ3?{IE zcfAck@9T49{ipWlu68^Y!{g;vkFDK;HBE1xr|suQwAk3#o%veE>E|cG8UYeg0zP!I zmTOYGrKaGR=X-nQ0TMwm9+z&D)13CBT11e#K`pez1uT-LZHo5hhXHbcT}l{Mbe zd^6rgh7az(-+kG{A1YEGY%nl?Dkz|>JDi2^qNN2ll4Y%S5Hy@bk?Q*IA{7tkQ)6s; zY!AC$WNd6~at@9Obod>?p|-ZRF{@fdY;425&4DpXu4En)SSKVz@=p#HGEv%kAG|IC zM0XE=7fywF-xxu|!EHprWmxGHV z1i;taXw;n>BTgCH@pG)+q?ppxj%iLj7@C2#07sQX3Hxmpt@)?M-z)g ztJ^MoTQoo&8TpdX6lnSe8wSQ>?N8F$&Q92Lna;*pFp|_zg1Zs~ORyv!a=A%gT4b;Rkx6$}02Bmpe{~$H%Ob7h+h*jc=ZzZ{M)-At zum0eH{xE9g`TDmX;PY9yx9} zIwt0)*;!3d>SCwH3^1YO6!x=0mnhP${y9V;A|mSU>!V`g!UmVQCr>VkoW(a&hFVQtCw$+JD24!{sq={c16m4gyP zH=+VTZwjpigjU7FCzT2%Z^Z0aK#)j*84+us=$|a;9ytgN11rvuK)E$SLQO*}!4xZM zXRY(+4s;uKpLwp~R6#sig^cbNJcA&0@M(S0gY-1yJ~mn^DC6*MHd#Z$xY5@VYYHI z6{Y5wn@MVDYHaUGg{rHoPxkLFE9o;l5aB%!p-6r4(*@ju zWb-;p2{^~fNXZ5!|ktBUUoJP%m4@ocodZ2q-H@$02337-KHPi!^?B6 zzsf{LPEI0ox%ISrBW7c+%4BVO8xFW}?Shq0V}l@>ukTAN({X&K|L9e-NsnBG__>8#T4!ZYQ1jP>bY=owd1!e_U^zq>j_!U!6hj*J0j)+6w>$txUHvGXD;_o z!C7klaATH=#)sh5(6~5QB6=nkgoUFmV2IHGkg5D(M}si zZ``jc%E)o4;bD7$l9Xb0>bPlodfAh^ATam>CONbfzZE|CFS14^7H~!Kuu$c4yAcenwX47~CE_Zf>(pK7xSZ zLxZDcwFmpUu}RhDFWxGXYdbzZezm5>*E+6+j+yutzUdzN5oIB zUErB@>bm!la19S`P92Y6W$#k?>dzwh94^sS3nu z=N!8gSpTo&S-%IciF9=6K7U50g0ee0IypNNw=*)Da+S-YREn!))BC|=`^EleltEaS zm7J6;BFMmWg@gu~Rj_e$3M&;G5RxS!bp)H8EIu=8m#Y^keo1BAyoukqp65_hlbEUi&4Ev?#4=OAIMwvS<)fo9s3pQ*v`o?hK+1=1^keVvClH zi&A5XF~t2`9zXyS=){$3O}cgzER3foNJ7VUUA|yv-d3@5ZvS8tAq&ns+m~~8bgytw zr4SS*A&lyENcVL%%gZB3H)HtqYiNiKTW|bo%iPT9I+~<^Xs9c>AcNzZwNg@MaEN!G zC8ZVdcMgJe6JzwT?+9FZ3g5od(n0|s_>1!0mIbFMUZUPLG=$`Hchz&$>J4a z{?evRmmP&*S(usGb$L}J27`nA3JZf97Q`WMU|>M>M=;_2H)$k*Mh9bHoD4O3L{C_& z57F||ler#d69<@V=@Ag%5s?DbOSolumFcl%6l4iI*zG7O;Vonb6>?sIr3c2l=#Pe) z`0Jl(;b&B{!pXC+m!J^Va_6{8l%=-@FmGQnVaO?3a}*IllU<459@37!Cz5Bhvbv9sEmO1Y=4V7VF`$>%veYVj|rP1%W^)Ye$ti#{m$!`Wm#rFOzE{4%(G&KUZv#YD? z$D)L$Oi!oCfzkLUgX0WgE3;NJt^IRw_|)g)w&04Gn4Tf|5B-^*l$1Ge zQ3{k8YWFTnLlv#jLEVyF&QaR7H(m$d>H9}VE422lvUC@?2!E{TG#xJM>w7#-X`HMx zR%n;mhCcCsx@BzxXr-k$xBd|5+t20j>+0${Y~GwY9a>sGoj=&{%`R*lUcVMquw$98 zw}m2VY+0T^@6OGwv{>;qHM7uZJz`#{GnvU~1pq%05%0UTdwjtUpN%|?jIyU@KFCTu z34t~re=M305Y^Wwb#ii&XDQjaY*N-*C|17nacU&~)tRbYq6PTJ6^x4JDO2Tm@gCgo z`>$Q<3x8aF2u2FIoVk8oDj({0SZTT|*g${-1IWlKlyBoskp_!HfIk2SHz!C%k-mHi zKT>dQcAiuj5n2T6Z90b=&(ZP7419x?>1m?KLfcxse)jglT8yUmRg-B@^77X6d{d6r zrRkb{AN|w+=@I0WYIconhwsEcRh=ZmD@b_OHNL^(0ayhJ*%~pVA);W+ z0sjDBs()?W=+@oSr_nS@O2myR+~nHzsYedk_3`9zrosCUL)*iZAmE=`!l?dyL6tw8 z7X15n4Ph-A0K%!Uc6+kL&!3AOYVwLV9y-&u$IFH!99*2lWn~$BHE*#LGKG=z5 zKQ3|Tw)(dAE?7ku*G>Jtve9ox?R%dgJ`*Y+k*B;i>T|!gxfw*d)Fe)su%0w!UFrU8 z5aJsDB=j;64wY0q^y+YJcQ+tDUe){jkwdr1DR0n6CzI*%$o^#j+H+tA#u7!MjKlgS z{mTG4wPFD~esGzgWC}`rt-iEvDQ?tCP~ z^t{E0?B`uGH4h8Ce1{$+ypEqgA#QJP;q3`w!HPi|pV`M}wYpJ|X-0pDqv@QQ!qL*w zf=56NDT!bN*)n96CUTj21o+(@H@4oDKtlj0OJ`&!TRFiy(|;&z78;TKj+P>FD1+y0 zVf}w>KZB9{{!40Vgjg)p$z91g`_|kkNOdZ!s>HOVVD;0U86+t&0K=!}R-jQn8cpOD z`z<}aV#!npIRMnzt-Lr4#Vc-{QK4Jvt&xo2Z3B;Sh6SC3zg7gS){+ZTK~=Q0;V(72 z{RC(JVuF^_5wDl8GMvFq0&Y!X{3yC#pv%5NglW4w68!%C`+Cv%d&8rHlw^K)Qvbng zTU)w&pOUb=6(10G>$Z3kh&)_idAGElcBb*)jGq5E-A&pmivc$UgGDVrRVn;U1M{z7(>GPNUK+E(^GNbloheC8!>2#)nkd!@e*mYhw6{D z1JzpxKm4Le-;9RAzgS9`s%hg#MO%n2jf7;qU0B3>(FLBeGGAP^JY}O51GviPjuj89 z3#{blb)hr?XIR<1iBcYEm;I1}OkxxfGBQ4#?PYK!{QmAR2_Jk}?kHO|HA3LkkE9bk5Dqos5EU!{1@+;v_cL!)S!K#(EM~KO{$3#i6o3G%6};YTo0|9{1+fmNB^R_}td1<7M*(oWS+GxIkWP@I*S# z$PA|Hi1mg>FgF+;!HW<#U4hn^T=WGs{)Yb-G45@iWlKsXR$q~{ zg752x2OAxKji&ksU!p@nykN^5DXD_Vg!XLBH|`A};stNqC+V{Ae$;0{=kt2-NaqG4 z2zW8glsj8QtfXa0C5Y{q71Ly`MBn)XymE&-67q5OU}$7q9Y7V9o&&QR0&J+b;f-{6!Z=jWX%P1 znyr5t^VT9|IiwOTmGb{-d3pGE89gJT*IPDx4Cm|`+`&3XDV zYhw(y{MxbGws>U`F@IHL`2(O~7BaO0f<49m2lE(XAvOA#^`RCfyrx_``r7H3-v0E$ zm`ry^5UPKYaOI_d11FTj512dX8U0WP2bJuN#slOp+KPhvg=i?J&APF7d`H}BJPn|8 z7fVl}K@&EK8Hr|beu;-WXwVX|-PuF)5QHarC`4YX9TuNFbo>GJkmld5yI(5UjRWt9 zAq910u2vC!pUnvm=sf@;(iE$Wi8L*V>VQq7zSq7>9R_I$BIHq$k7ZFvrQW6E;)~M| zbIYJ`BsG3uzMC1L0p2Y^;1P*ZGx)(C%0rILBYlzc|Ltw4f&E1>|9r6*X$Kd;E)H0! zfV3(*J&SG=9VPFWg6&rZwy^@RPW=^VnKG9NPF$g%A|VyNwjV7Zow?-(I&b{=*Ueu%N~AgSVD$h)jD97*y#JITmJ1nhlX0a z61WZ2$4+RFyylV8J7Z!XLf^RbP`;|{u)XeZ+D}3do$WlV}8_mwvf_M z&=}+fL@8xSNxX8p8^@#ny`_7$w#O2te$PFB?$^gskt2l0j%`~1ZXrWer0fk{b+ZLE zx0RjDq$LBjLkS=0V>g9>+~67J?3K|y*(>LY z^$lMxTslRFKI7o+*1*A}vR${74GNKsE8_4n&-4TzcOE^cujdd^xDCmaEx3Ey=dOur zN%xH`F6+6+xSZ+gcxnj|%7?es81}j6&%0Lu- zU(b^BTjcyKVz3L-TajcQby)nlIo(lkqLgi|A?G+cqU=iu_BteAzieHfg8F~WMm6L_ z=R`)uP?XMrRp%z^hWD+^VYSOYQ|d<~)F;oX6-PbDR1y#M@D?=cJSy^_fFlD;djiSH zNN@x&ariT-p&?1UgTq_E03@s9=&3S+6lB=lBenCr)w$uUBv|(y&a_sdf<;rDsg=Q! zy$0gfFo^4Q)aVkuw_~Q^zaiHm%bLp^rtf{p%Y#>Qa*jt#nEeg$D9B$YQMa;G4$EFA* zLv0kVhLk@GA}_0@$GQ@{fCWJM$wV5Z{L(=t+re zacAm}rKsAl@KNihAW=qxS81mQW=4cCicUSrxCz5mGR&`!?l@2H`hI^zB)+hS68=2- zUg+KT9?kvcjcq!i34WbD`w~A`j?JzboWcRZtkN`X2%P@pXZJ)_)yE=#LP=ExMXF1 z(0O`%rb&3P-IX*F#Siaiz$~{uGDG7VWMs`u)|e1udgnJ95GG0izoR51cKR`;B#-O+ zIN%)JZ0*=*PyprIPU#}oG}L{CjA_U83I{2@uunTNo?qL0(F+!Vo;^g! zhH-OCBh**?V+DDKzPSKUJ?$>-SJ%=QT3P<|j)A2lQc1Nap_kj z{Wng5W_s^%wz%WnPCzUDl!j+ORO@Jz+(2!Al?#zS5y#Lxu4Lv+#9$1@bxf9nI30q{ zJbe2+r1U9@wT`=?tui=t<#2U9=sQ@X>lpkcSw8dUMMipOMyOd|l^ut6YzpE~TVDSi ztH=x0o-`j19;Ka4*9(}8V{ML)s^4nc1Q+apu5S>z-;go~N{VojmatywW}AE8pSjEP zSTyfBiOl?Q6mgJlu>Uz`-q|nTCY!vXd`IJ-Sh_z-XXtUhzA+*0@vc3S`8Em!Eyb^6 z`$fn_XDBEkwLXZ`Zro%`>~Q$J6Rs}_*dZmCM-@tfKWI@~DJxp+MinQo+%tYGfQkKc zVA}COD&91cM7!V2{Q{r-K`29dDBxaq;}fl*003{4FfcbZ`nXyKI#Gh^!{^)DtB`CMKSR*8T@~-V6mUY+AE*3S>zXdN7*SD zx^ML^XAch2bw=i|JJlTjR1^QJzNr5<)RsoH@t1s=>1Fp>LT=~MSl$2 z0Kc4ob~_-Z4o>$laz7Olf{wdmw^^KoFddyMo>@g@+c-cW;1%eSPB`LodjhU_mWN0e z$WW3!`@+PppydxAOAPF7Cyos=d}t_hVEv24BvGa=P057~c7F3X!ciM{^q}tG=__w( zV`fbukQk>xTmE4bYELw6QfP$U!KZuh5iE0?7p~!6-Pd((G^vO9fZ`hkI5jdyBiJRJ`>f8eC1ma$c2mYkl{sks)g^N7Jg$Ek_ zwV@gopbjB}R#lGG864Q#_=Mk~)|^1h-)Psx;8?A$@KGd`iN1oadmSyxC_>)RQ|-E2 zUzUf4p7bhyYFGAZGT$%wk)tx0P1}uC1)tuz4o1qo#zsWP4T(+icU@1s@Jcvaakwfpk2#kUVXoWZa}!6OA7p?Ise!f06W3}L)mXLqTInNZ*XfPrgeq9)g+&Tc86u=}^b8A} zL?%Hz#_wO>a6z=B2Q@7o1oY#AxV499X0lhq!(MbxUuQZ@J>&}wPLvee$j00okECLMN)aY(iUkEDwR$+Q;aGqi8Wdxvft6@ zF3ljw%Nhdr4#N<4p+YY$hU$M}29$SJiMZnN5h301Ay`bMdL}_vCYG1vjB^;!t_j9v zuzH71oCj}j4eQbW(!MV!w%xWDnUZ{#4oUE`J>2_qIfmJ^vIFG9a&w&ya5qFE3M=X! z2FHh_QLYVF&`0x#OG{?sD*IfJh{1W@R;y43dG9^)20fhd_~OjyEGR57H`){~kJ2)s zF{lz6d0|^+n@oZ2>Q7%@p-LlIL^eYsHG;XSaxXqO;Wv9pedE{kd=^qy!WA{X4Lv=d zuHfjKR{1q#Pw~1FR(=gl^>kM?u4{2h*ay8JSH1VRpLPv-hqkToe>*&@)*e1A6?~&x z*d^mNG8e85I+A{gZG|*P;^Ypjx$CIS^>OI%rg(?}{!a&{Z!BIz=><5vH*(r&)HLGA zdsXUamQd}wGy0I^(^~Ah>(a4OA7qPqi7uczwY!tIjTBSn6O3vq<=bO+DW;`x>dV7j zY@|StA_;HIH9x4*UOK)t=plfJHKgJk%ukfxFo$H(%ZU^;s^c2|rgL*S@pWfaa~wtF z=HgJzQxiZTyrWO!z+JW3m{@42i)L#LVSHBa1RS&30t!8%ey-=ok~u5GgLS|SPR^GXqoZ94NJ(v8F$ z)Rjli;N@f=?M&mi>tLa2y;{&s84>IB6?Bw_7vR>WsAPB~u+~Vka@{LO-=yOf-}ZP$ z8Jey=|2zhvUKq^9@;K*QuPjzxI}H;rk34NaQgR?^=c1U1}V=&4eD3gG9LJTa{tZ;R@=mJ5^-tus|Tk+xWI7hh- zO!=EuA_;mw^DnGbD4DFC&Aik6jckKPXWNK6shu^vHi)XZ9pOmrP<0i6I(_VFQy~)O z%=kw|bO&ZF%X(#I-KC-;i-AY7(nc0hnHc9Ec%LkI@6UF@YNdtsA(?YsIw*k*dw_y; zA)%!Fl0dVlxAPCe<1KN#xy|E|8in%yl*}mm8%V(#{;l32A!!=!h-IFb%FFiW2jk^$ z!eV9%^+@UFk#uI?%-pyyU4?WE;>O+&^8X+M4&!u%f4~IBIxz%g4nuO>QCB<&3Ytb8 zs@XtnF&-%I`NUZAo9dWrl;Kjb>*s&am()M#YolwR^96cIMq&K#P>m{g!{XrY}ql37}RZZLLuU@`FWV!Omm$emD_Vg;W%`XVK1dKmxVzc&2V|&w}m&7 zzgKr$h%r1sf%rWIJ$=t+_4iHeoBR7I$B4a=`Kck4XDj7=3Pz%S)p~iKxf)MFDG~OG zbq<~Sqs|{DeX2JZ>CIQG54RB>r#)IXMspS(cJt6S^DQLxCP##W-Y08#U>BiX#{#dNpYS)6l=B~mZya%GeQzK)`h@lf+(avJG4egmS%R1i*5txUt6!m-S=g#5IY4H}YhDlr-4;*mUCWObs!fk3XHQ zKhDY?J~4_h9N%ZneiLE-WylPCLs#T(T&4TU3a-7J+TmRNF*zjE1H0#hriYi;i`tEk z4xP1~t50#Odw?_fX;BiiwKz@O&(Tr1$^7WPa4d_`Wm4;@AK(k{`MufZ>psAMdPp~0 z9iy8{vDqKj{%_1mr%0WncZuZm#VRtE*0#0~i&Q`R10us|uMzH_1x%HMrx#f$b3($3 z#L&RbzCl-5$4?3As`Wm#WNs6VyNg{VAxcF|_%uO3j8qVP~G`+Oboxt0iv zxhAY#f)udH(~+ASLXan#?A0<@UQ+z1vILeI!1hz;=uF>EZ5Fv7*ly<$@^d@baXOA0 zG6Q8NxD^!)1qzuZXUMJalwsYy+Xj-x?BvxSolCcRqv()Hg*tX>Q4bf^_w6+_Rr71U zdAD0R$#WA%y}8tE(vy_D#$qRbS$J@%dVa271Qzb#^YP76F$lo=++Bzo8*^yaVULvS zHF&_Ru-=23|7eNOE8E!Q_{ifn9Rz!O)*9=DLB*A;LqFrI;Uahh1WC{p6CY2I($dqx zCb-~_g^M1Iq47o)!IsM{NU$SD*-lQzYbR;6UG5`AGBjoF*A?XWo`Q=D9|k5^v)zbO zy=>XP(K+Ks;jio5;yS&Rx5YNE1NhB=)fn4mcI&Npr@k7Admdm|L_e``z|x;zXmKev zE$wrMs9%@0qa#9pe}D0XkJt5M6e)%tv%Y~Es8WcHiwm65H~9ShJGRHiCMqXU17rf? z5B+jvY*}dPPc^U`z0m=o1%eZk9F=DgX|Fjcr>)G?+BxY=hGy( zFp$?rJ>af#1O+s(0G}X#wsn%i&U{{+Qe#nEU-pMd8UO%$*J_QU;T(8mENBV3rLQRr<%1Np;Q^v zf{*#Vjv$}-ogj}{d|I{lOXZD>FzSwH1Gn>|r5zl|zs9LlofUASiv>|gP(lDPB0R)o zz7MbTea*txe9Cm{U;I_ORd_l_m4PA-JY5AI?=h~z2lX(B!4o+gd0OZ9YIE$&!m`uU z)cu=uqGtc`IwDv|G~hj&yQ#TZc3~krLik(FlRcq>kxswwIIAF2S8SQ*) zgzcmKx%s=7Zy{9?%!|VLak=J0VNcvf2ntEin~0S+nv;rL2ne6*>uEu;68bXN??T-; z8ZoI+J>9g}xKwKoJG<0lX*Jts?i7K1&_zPFw@6u?r>-mk0GUVt-5=G!yAk=rF2Tv= zWiM7tRLAFpl6Q$B?njU>zERP72T4pqQd~PS2mHx?TGC`|w=+Ct7TYiAr}H5$Bke&zA=o%e?5CL zAC;;^L)(|h=Lp+gdWTcd#)0Y@X4E#N8VL1<^mya zODkI2YJS2(W{@7qynQEOC^}G=m!D6hz?Q7jJ3lO(uUv!;N|M+tuDF@xDzzGMi)ylz2DBy*dgr zXduUag?{EIduUP;ZOi9#YY1`*3WSIikCmKgGs{9xTiBp*UxtK1ws?aj4wNWK$`GmC zU`gfEH*LiXCVR=S(D(-G(}aCUg{kBIpAT02(3hHoO)utG{GJ23_j!!hKH;I`5*klk zCg3hJA-}i_2E>a}DRKVI;TOcJiK!K;aZ& z?o4rULHSEMEk8ZWMuYeB6VYw*M)~y(fu8)=H}0N4LmC*0d!&%yQW39sUsL7e9*Y*K z=453779&p)*ROAe3(KcUK%16X(qSV?O0WFfn<8Sf>PC;4KfjuYalFYik$FGrFO~a7 zU7ZIM2!*2~jrM+9FbCjKQ9IZCqBTBVqB{=);&zS_jXdq3g%mPhYkWv8d6{}NldC+l zuztc}W)nUN;6K`aL+W^n07`+t^CMl)>Sfa6;+U`~Oer4CK7FJQ5ACkW_YYiPUQ<&} z&7$dhj173KI@_-8$yCEJHrY6xd~ZEoVZbVpQm!Yk7K&4Xp7{knK|89u+q6`(k#MQW zt?mVxjIN5IncPQSVKr5IwGY|1liQxxwn5D?kA2+AC` zM})oGA1rbi%~Dii@<2r9l4)c$7D(B#f~H+I%|7D{i-& z4N6XK9mAST*@)H{FO%&$UmuOTR-K-Cp$-I3V*qqAPSq%)cgR*RJY7snM+b0bq=r40>Bnqt zs@~jrm`q0wNKwgd#aF3&4fE&cl92_F2woe{on>Z^Tdo}*hWq+>t;dQ%F%|y}chSTD z(@Oyv{LHx1H1*u zpaLPLXvNz)%3( z%`O7zVb1?yKrf|Xw1#^lQfQD>P4WLv!qt}&a2Vl#m5z_8PRNt_?_WJ!=hnYl_Td#2 zghRq@ivrDQrLI@&STxYo;{E3_hy5__KeQx4pSfQ(Wk{pks4IlIfy8-cvu(n%X0~nP zJ@IRAWP%i}mzW=CLfRt%p3$7DvKWQXZN~s*C{Q9I2FSjq{^L=3!)pH@NJ-OT>ziSE zlc=j?XLr{d}uXR>~Xxn9j!|*?boA?rnLyDWR?Uu3GlyilJhOz zYfk6#=S9V%Pd2iy$6PJ0`z#>+0ptF;5*l*X<`}q(g0@jGp#QXuwI_}ynBx<$Nps*73@L<`z-hCv4&Osal zkEwhBnS}TQ zM{C!jiPHZ)RT-}OzoRNi)_+kIwLJ>TkBV&Qe{F$vL&rNYRTpGp;sXpElEBwKAIaI- z37B;dEq=VPvE!u2$PX%g*%J44paQekeWfg@z8-JB*4C`h{{BjY-D^uVCbRB@vIw)E-K*;5L8BQEo_Ku`O%sGs7NRiV7_7G+RgX!(4&hU0$%p9tyZwFZl!N z;YpT1Izyxplf)W)i6P?6sdS!cv-;x+#Z83578=?t2%>BZ{2hA_W)>C*0s^gsHv8kY zb}-nN^z4_&dW%zndh?U{eL3))ALRS@?vkiG5fp(`D4x4euopiEGp7v~XW36qT%oe zFG|Ysz_9;!{(=Ziqu@;l9{GX+UKsF1kVU2EK5DLLmTOV84(M$F@ZrNQZoSXmrlaM2 z4Fb>s>VQY1NdjkZS-7>X#0U_^ewZ#;5B@`08r)fNC-Wl%OCmUdj=2E@y=E8j8J2YU zX^WAn!e8S6u(Q(6>wD+PoUcFt2eQxZo}QP5P%Ki#Y1=+%M7r7TM`&rjxU?$!!4Vw% zX12`)EhANc6ojub3JN`-r9`2@GrQr?fY8&dgZIs8lE-brtB~tjyCuk&XhN&YE6oVy zq9A!1C;&W971m?i|Hp|ATQ*`a-u&0GlUZNQ#oI6%3NadKh~c78@V^S^d4cBzp@JMg zsufj1zv`xD@^B?}J6=Wuh4!VTHXa@gP{7e*W2mRI1%7M2=Z}2N+er{3f+Z6IVf%Tm zn#J!TkA8P~&*}_}NB(Nx-^P*WZnLnA^bh>1ZOFJ+(z9zWG9koEGW2*20s6PV%#-v<+-yV1bUxY$%AGlb)oZFW{;h7}yJ)&ccI0Y~J+Nd7H)X z&wJ~W`4ZNS@$qbs;zi2SbbSvWC1)x{0LVcIp;8dtrD5=F2oF77`bYwDH!{Bq+?H0M z*sothUKskkE742TP`8ic1`SfE$^ib_@;Ge`0N}X=BBI7OW>ykveJ)@h0+fw|GT`n# zZlcPy`m$ucwGR>dFMF3A+`D0tlnu?zYv3w@(J`>31nJ?y$MNa_#-pK`U;+~`JZgcV zS+O4xumTUn6(xK;hP!M%1dk=3Tw}$=h-NNhG=SB+fq|k1r#}Kb4|Oo0lN&Amc(1MN z{xW}mO{U(kwZIn+(`C(TCzX;$?6=-c@KC*{(Q-E^1RmX0_exJVb#UjDRe^j_W9MEZ zgX-FiuIm;5)Ko?QpQftq>TUv-2;Spk1Jx^TlmLiV3yI&p+421}v?H+QyiS(QQwCXP zXWHCkrNg-3AIN~F8yoOMlcYd;G!-P;XTZ;ZSokC6uT+FxGEhg{6Kg`dMyBYrJ2o~( zPhK!Dl7z1A zCnHG!`1&<&ZB5_RqM;7b*4lEJk{FLwpL=if*=r3`yUx;FiNLyegneq)MX>~=(^FFx zfnkN&>whH3(4U@s8(eM)zj0$Ji+AZW1Il`l(lsdwFUbroVFKeD&?Cxk2ewYlG$YUSynk4szF_4nwF^ z=-ZSF3-7KrpJ{XVxRk+yC5R={RpK!a#j^Tkh`S>$PW$_i&39QHkq_NZFOZDawJ1HB zRQ$^Rfrl#CZyumePL)O&@H@AY6~BG^W+3s!7#Ozq9#u_zC8}baEgOxo~cRYy%kc%$jVYTR;Hm|!Aqij z=IA|C$zc=#g}fzaK8ng zJBsLyJKQz0)Om&|-dULSGK}<9xTv}Qs3LA|Z}%;UV>)xjHBay4f$yOjZxifyt_9x? znYVM)3&~iuE~c41pL84@)8=#?)XUWTjG_@Y#KFyX3pN~16Fm>7xaNSo!BR`S$|EzT z^H}Hg&5VPro}G}1SOiS+`!pt{Ilp;B$>C;pnYZ3bN>vrt!ZMT7=0MVy`Ui>{ zLxHDz_{&2s7%ZyU-pZP|=H@b6i{w6&CvLsz(zk`63prR>UC_}rnySSveE)t1;Vx>A zebU35?nq-=TYAHiSVwS>pH`TqAo6|UTvU46EuMv$Tfv-9B~3y}_r+TwQ4oB_?E}iz zrIl+-7t{O~S4S5yP1aJ&`AV)zv9kF2%LD~K;bXN#aX}5ZZ8)yQp+;|nG_9O$Q2#Wh z&_#Bk<;P;+3(xV#$H5mb^?!MBE{n_lfB62E-d^#PyJgr)Bt*llYmCk%mne+>H zgQd^&Yb*o!G*wi0T8g6L@f1yOAP3=0lRRzJJN&}JellS*Mu9^+<&QIyHT1>q^eD5l zicm(v0LG=SzpyDYHG+}ZIbcO_XL5N6i;=M$oq~cTgj^W=1Z=^$05F=6qU;k zbuF+T8>{7f=GxLqwBLj-IA^;b4Dro(CgPysowu4d#wY0WB_Vn?S6{f|@_2t!MK**I zkAVUG+w81iQ@h*Vx6e7Vq=e$Fz5PA%LByr3DkE-B-Q-$s&pw4n)CYJ6ts+b#Bcnc% zA>|O|D>hJKNXvaRP^sz^1tbABZ2@{_gUs5qrS{8%{xOZ=wM_}vR5hE%-O!&*)m|yz z|1LW?tUhxOruRTY&6h71R;}0&%xteT4;^Q+ZhQukE9lMWgZlb2U8e}S^n>{N#ySmbpfJD1U=#M7MfJ-G3q^28p9uH4#erc(u zy&a9cjs>HZE?p4Agg0Vcfp%lwybmq-l%EX);Jb64m*|_T{{&=U%?A(s` zIY_=Fc&lcWzst!%M|TmFO(5|F-6Wl=DBTg4=7cy5cPEyNhq}K%y0fp(_w$RS+J@x? z?l}%iv&+jXmWioqPqOdD38AFQC-gW9y-J_!oJzu=5<;vD7y8A=Qx6xJht_x>%uFWI zn?)%!CAX>R>+4^YpO3c_Z`WM>wm9t&5 zeWz(;HnTA9c`zD%pRGL%A}giR?5B(hO0lZkLI!Bj9;d_x1~X4Kx{2rKO$^lsFZuXv zokJ{-oN^<3^XTYI?CaOGmy5$)wXdic#ZwAwf2&HcIHh^iF*D(D(jaJE#YS_Fm_P(g zmsKOEE6MM?mAtz<%Cd#!h3FeMhGta7-ydzu%t)&`B~(^Yx3;yNvzGoS+8*Bof9q41 z!qV(?UVPBk-H-Na{o^MJK0*#XPXlYUCfpTZnKJsAlfr(?vD4+6dv@DR5pZ+B;%70D zN9weHF89-?>dxGoDt?_9HxgKuGFEM9zqyi#H#>c7FBxX#K+JXMN~Q{6S3v&pZLYWw z3GaB)G4ImJrBl|XfT={2o?(iEgM$L*BsQZ8l^HaHw`Nvb*(>MCj$7Jyy1BL z3LiU7ziKqX^OWuA3~YyvbEi6L{5s;TAL*Hx&Q<8TCJt`{*Vy!h{Ke$-bO3U;sTWW; zHnMi*NoXNz+xr+)c1!S|iVQ?Y{Kzfk$|@IrviZ$4v{6D*c_J3JUOM{5>Gh7L^`3t1 z+cI+gp;w8;>CYP53GBYWm(=b>~T08=&MJzZ(4yQe04|fhXcMD8=&P&u?OxaE6 zy?giWw_U53F=|_t3rkB9bW92vAp)OlHuWqmRaOp~oAcIpb`S`pUdC)dX}>(oAdlKB ztd?>3pqG?(PG8O_GmAB2#rHCuSvs<>@(T($FBS8lq3-%AYldkR5fYrv*nj&rOH1b( zGRft+iDhI&y4<&o)@!F#pg<0DLBzr$ljBk`-odVm0=?ZpQ7cw*s*$mAUEP?9eq2l{ z#p?K1)r=5)JnEyf+)7Fav8Sz;fH*QDTA1CVRGQvBJ4Q6Ge?3*b1Y-cXsc~9hsOE&K zXr6%fu1--w@Z)3+hm5@7zPXn8{K5h)^Ha)EniYE3OJw6I!>p^cuE?YEHXdWRxw*M+ z?EB{yeuAZ%&0wbRQZ@-67T*Q<`^)HPaa~=S)8og~o*{b!P02S07UMJe+}IYqBYxahiTnQbbz*UGaizo+y6(#&(vwafl9G@%hVqj| z+cyIH(phaE6}7F!+$?0GiU*?tg0%zf%&Y1iCAfM{o>U~nrAf%i$z2pgMRYckxmj9T zVv`FYblpnumy%+pKg)}F2BvXRE?Z{26m~nHRLu&k(X!F?{ruVRZBbSTL}RraR}i@c zc?x9IKMof@$MRk|gS%tj+0*0g?M-DY8(f<K#rjC&+0x#33t8?{9{l8f@}4!*j{D3`cdut>hAv-`b%O1Ci9N)*GU zt)jj&(9pJSI-9IYZl|uLafyW`b$Gjo@c9KDhO5bpvP$XgCCrj?bD!`3+!V}I?c2F+ zbO{L*u3SCS5okHGJ2|qZO3TATm26~mTc)kLRb`1_un!)oc-kUP=eNS^tU?BUn!7iz zN2AnK5D=A-V+OW_ue#k9m$eWQ_c}~hNqHU>b)`LCIA<%yLtdWmLFI!dUHn$dkLyp1 z&#GlVP!s(APncrYeEC0M3deK1E(GQS4N!#e+?6ZIpuhmLO6k14$i_}IxfZOFVHgqr zgcRA;0K6={?d|3n$6kIl-;WEw_X+18QfGvOM$L$^2o#b!`@d)2 z>bAkea{kbTC}U~l}6$=k|ZM; z`kKSrDnhHP*?^Ue)6)VC+NMXPS!6|wnZ-49!xA(Q|>#$)Y+05M=EOgsVIuuJkkAqY5 zX|}7`d=T1O(kcnPJk=*(+)p?2T%ze(8j(0bYIZf_+^!eU-3E_F; zyB+uAeav z@{R)P0|I}hM?_+9{<8EF(%|C!%yS^5zxwOU21!|2$s>cD5yn2!Uq0GvKUlu&6xZsZ zCg8M)y|XG9a&E2tW-!UusOU6PE$7nG-28m8`}awG{QSh^)iz!ptb8NCrb`eS^XOh9 z)>xTpT2oIC)#-7E;HJ^({yH~Q3C##e6!@R$hss79bI8Ag$ZjNK^@#_`?jwo%qlHHk z?F`xh^-D$>~#QB%Xg!Ev9Qo`45O z7B!wo-lGL;-|z{Xc6qcwHy*Xu*agtWjT=sTrb}!`>vgJxP&Z|2xPU_C zDMi@ zKU?RA(^z267gY(iezlXzp45(-i7KTm!xGzl=4;o!Os;jsyL|?wsdM9cCOBj33yVjl3b7*>oqa?@}p(;Tncs)jHs=14( zAZQB0AdKaU_fum#bc=n)%J42R?eo6k;r6jk#f`-m(M)XmHk#m}51<+u*#@RzN?}uk z=v0)dmAy4=lAi*w=XhMOz~T1NR%KUa>_uB0y|Lc+tw5mD**7<_^o)$2CF*;3&iIPe zSY8;;UJoF5^r+Dq-0Zn`nPI~3*)tIUm=NvqlTj4C9I!d6XIdV%L)<8F;^5i(GTwlzbCUQ>HTGcpn>A(slnZh*yCrPW{hOEsjcA@^T4ozgQ@RF>FS z6h09Vm0r8y5yd|B<<_Si{I+|H;2O}gvhs_V6xrlwUc!HRjd^zlJfK}*Jxd;O7RWx3 zy??N?;9GREb~c2PAbqRJ_ca8kBm(#KHAZ+v_#NkOS2TPGN{^*@6|p8O_oC&YdWo|z zNPA}}%rgWA20lyU8}K#q1=5;Jez-`45_#wD-I|!-7=!+{7y9Pr+H(i3t*sGJZ@s_1 zE~+U~a`ClO%Ng?t4=05veX^$&Yan}~8f%Q0-N4PY{PEh!StL2)>$}@I+*H<|clk>4 zyWL>+UZ(G7(cNo4M(Tt6&r_1FChYAYegzwDPK5xDSi?C(=z$cuKu-|lpsldi(@jo>n?_0g8#g9*R<370sL)yAMX3!dOV4YK zVb^T{!!4(q=7rJGH<7QRpQ&W6)h_>l!{jhGOj4CWc;xV}w4rngo1PmlEw zbV8F3qi$jW0(GbbrD*Mg{%ra-2PcnIQFYUu%ydJAw5TE|svx9oDTLF~*@*z4hJucJ z;srE?nnps5TI8NkOKmN-lhZjIVgurI11l5pN^YK&>Kfg5NlD(5C+ub)L|k07K0JD8 z_JNHq-9r{t(s*Ilyz{d(-|~^gI6 zA-2zOfaI+(iNhUBgz{0>YhIpDt%U%h){i$$K*2B=9V93)!$U|)g@Le)jsWtQN`YV| z*Kk0jfCxop<&0+wQQg$k)OiG~!_|QP^~cMwW|tgRZg_rd-dNwVMMr=m_7ax?W#!(c zR>4Se;7ld_7OvJ)y5#hKC7Z;xQME+^@?3bXHToj|HP2|LZGq=1dk=R^Rye=>$q) zVd0lA8FyNF@?ZVY-Avrv6lL~Q)wQSXN(V*nN_`4beza7J(KIwPTnvs3SS0pG3;dcH z!vMv>aDqiNyxb1)21*YLH}P(2Xpn#U^mM)Md)DCQ46bkRG)M$cf{f}WiGTgM*|8?U-F&xt;cGPD^pW``T5udi}>Aah{twzijI~B1|f%d)Xlo7$z#6$ zWf|h+>>a~JIoVCZtgL5sbX0cb?b2o9eO77Yn!kKeEyY^{psAj+sIF`_Uas+?be@aL z>{)g8$=+-;CoHRC`fq`Px+~N6F6VUbYIk?HA`okQwz22Uf=UuGkK21u!Hx{mLGN4h zeRU0{DveTJ%ny=^XCx%rFG_A@>d{@|imLkF9!u@Jd-{XPRnQKK9$IEC^#BhBh%7}m z-qYy^X)AlWjm?jU7q6jS;Bk)lJ}mU=>al8pcZjOgFJ8PjJyqw=!DXh95v!K@0G*zm zzMN-Q1u&4fygXh*1EFl(PU&!72o*Oaz_7i^x(>ZZkItU9r(1hPgXeHDy5*r+16{s$ zx^aA0fB&*?UP@Tm`^DYRw6wtP*;hiVujp(^-oc^((!r>1UIMSmMYj3UeC?##DJR0?_&|W!B z!B?o8&b&znd~6`lVg?QAc8u)oIW}&krKRUdKMrJ%Nyf0b1Nvn;&5PY>k@yV+>LEOO=Z^>vwOG{HIny!elLl>&-=!+Y0EiaonRNYgx$IZOPYDOjR#=*^ftE^Cj z|H-t2?<^F6E4J6(_~Eg=hyxzs>kl38s*u`9m^H6``SQgA3HP{DZkd+PE$wY6f!5h) z@>Wt={vj9Z)iZScMe_33t@|_Y^~~K#R(xnQcj>DA=i1OBwd^q{o+NVKXe8gt7p6?F zvsOB&rPGaHQKM$lgNOS1Fo6s9dJhF7iHWDeKhF_R6y2Jt(APhkM{CT@rdt^_ZGgFI zY)tE{{h(mvshwTp%NC;Cy(J^YatGbYa!U%DddmX>@K`f4G1Y`p6y>_t;i-(%P*8-! zen<=CRctKO!gch)#U%J7*lQMpub)U85E?3NV1O)uX*GTqeBI^4+a`C_O&aow-H)SP zPc07gGlsW6e)a%*8+6=#O@@xMUM}=k?YRjFUm#Bz5#bXHr$ilCjhTPVM`%WgtHLB? zAQg%i(4F5&OFLL}np^wnIZZREHcR3go^9L}#DukgPCM#Nuf1(1~hlA5Jkf2T?!(!|!@|_)6C*W~bW4}1obUQ|)V648`qQqh#6$`4 z$+?l6S;urAw8d=~2XS{-$C@^YyPDfue0_b%Au97C&gjoj3CEXD@NP|+T^>wih2SIv zIFpDMFVKO;K}s`is*)MN;qJ)q?I{Ed6`zxd$$BPxHDkjxtJPqYx|bLtj*YSIK1N1H z&yqNDufts_fG-6G*|v*Y8X*_SRWo+`8ML&_i}?koR>!XGb3u$N>u7o%>BI+E4n>Y> zpVq1u7xVYsS6X3uy;+cwXwqtsEhk(MDn0LzA~4a>XSoF96qpB3o-T@W14>Hd&;_tU z(E^-bV~@>vOoKtdqs=8I(>7jnv!9a#-?xNVe&^^XR?aw>6?;`=6*m1f7?=0n?T))y zDc95JqV8@qF7L;3yF|D82MJ@n>Q=HyAcmZay`y03jlLL_M}tn|m#?PiweYpAuka=C z213HQLeINpYyT8y)NMD4wQ(jzTdQr<{M{c)hi)L&#Y@vOu{Dn6=U)MY*?~hvMTL#M z5WKgy5=wQRCxX4|02etlp5&pzZW$05D%}x(Gn7izJK*$*AR5kc(`A6SW}y<^^Ces$ zcO;lF(YYP&0Mp?(TJicEJcg}h^2@JgL_NkW`g{R&rZ>%D;Pa?Zl6-gtvTDIv9OR9? zKmQxs{+9nFr6?xyOy~id$ad#xyZFL3+yACdS! zBWPYO^1q_%4gK(6NcwNrprfAw(g68+@*BwiZsY%uIRA>n$t`~aafUzc_5YV9=0*O$ zbmMg0KXzgW$Sc37hQL3X*lhCoS6$fKZ+`P3_S@j(-Fn|~S#26gB`NQ#Z%|AIZM_y2=D```57Ki^h!^Y174=eQ2x{pXPo z_g^@`&_dq7t}lO*fV^*hrL^9DV@n~3c%MQCK|{gPw1FD0-euxidSm6GRgfBywD&bf z2V5}vj!$kVclY%9CMTcs@+!Rq8?1Bbt7B$`e*3(PuOdur(Rznr!u&Sh%#1_6~DN{FW-4@ES4I;w6S+GlM{d5k|xOhgGg zIiOI@)b#&#$wzm9UwrxUB^{_7Rq&07PCgk;`6=(#*4FjX(7PSOCj{wC?i~aWKX+bw z{|Mfop|&n@#0xJlqTt}<1jzwl@LAZdWkW88_dIInvFHC#&n_KS*Hwd?B;{Ar+lvZ% z4H;QCo>Hphb$gSGpx2{6Vvq(x&%3OQ1vJ;;L0SR=VH+6xAN&^GZz>fE{gV!U3uwsl zs!3LheNzT78r1;+v>e&H1ZoUOUKH*O}<1wg2fgA5q>_0TNx;9sRdE(x#?Nm!Cgp-dG-YLx5wY zD>IH3P*Szede`|1vbmEZEN~%6XqpMg%F1yV4C-wy_Eik8Pzt)AZwgDOUCNR3Ur3U? z-JFLJ9{Vh=hr!@5tfIM%-7_lcoSz?#csS{q+1a%T56`R-`K?#+-g3aOmLB82G8tOT_ zh7W=4iC`QPv|omFc=$^o#G&?(Y3^p$+$v@9En~Q zs~i{3N9U;fGrgz-p<8hD>zCS3M&?Kbd1poQECSfDK;5u6_lq%ZxP7NQQ+6;=stq8m zxwXCK``3}lk+h21(*MFo`O5x(;-i9&H@+6JOwEl@)11svRZnVt`}|-UPp>jDd`?)!uw;eu2S^8^bO2Hte&BGEp;iEz04~ry5%gDa#Rq}aqiv0D86{}3BdvR@(?!$d*$94 zk;5Yjq=l8Eb3l6QfZvJk5z!OkL;dM=jC^Ks`fHj!=H^;PlCk5F`m@+PlEQb_pip`>PdRr!I36ACezkGN>i< z(c9w$Y~8PipNO>d)M-x@`1@0UYvo%GI?i(VxivRz2Ry#wQvMns*W%+> z&8*dKBZsXp>k6CDs5Wt{qf>en9ySgRZRE>$8r%{E6Nevw7D(OztdOND{K_nb3;2BT zZu)OFC?7@meDFVc__(7bln<3`PJk3_RwUExfJHz6TkXfy4~nJ*OR{ou>DsADNlBlq z%F%du0r1qF?eB7`O= zNCC>>hfQJd%rXcMm2nyD;cq$8;`3Qu1@RpnG;e93pyvmQNNaU3vhwRBTB_NN78Dei zJz#&CltiW2(>=8^n;3b}yS2Wr1rayG*b6JX{Ue`!a$g2;m(CkB=K!ZFiQ`4ac&y zT+Tw$Y`kwr)e#9O+>f6)9m%A28a`p6y_5LpDksx9T->0ldQAV?;TxZuk<0p^3gY=&HOL|DRJ#a+6`)AeifT}iKNxd8iud4&0`dryCV z>0Kdk66Ah%aqS3-FicFdXRD*W5>IH}^jg)S(+L zeByyO=H0w=2NQ~*KVI#q%AN1Ia(AMNmX}&eiWOKvrBroL>p(-qCo0pJGS*j9V^|hl z$AY7g$qV30leQQo@!j?F#i{p;{aWn0&m0tdrTBH6@4VfUKC8CAK60h2^3~HhF^TKr zZu?IYn~+&FeAZXsja86>b1pA$1A~f?PWN=eS9fwRPzhc>;tyt^+FBQW5KR4I$^t1t`kS!`WA5@u^mY02I z7~G|uoSdTU{xiiS8c!_Uj2vgoQPI(K;PU`|11u>wAD@bB+KG#jrnlqBtt8yFJH}4` z;Y140mGDtVyc(of6P^PQswn;N8-IPhMI*4|(8*_j^g$9JBAQz&-WDGJ&MQGC5e_RW z>k)nxaZ5{)f)1NEm_PlnsqfELjGZ~o6<|)i*!vgq39^E2E;l-s-*|)q zG}hGOVhBh!`cu-nt!+SBiIpe^!K)qVfu_ECm6g-L(Im=JxTVU5;i0jy;l}f|jT5gT z*aR5%)IoXH8`?DaCi;qw%l08W=oAbe9wqZCUf(KbVP|hLxBgP+^%sC7N`pHKT0k7y_BoIEsjN}Fj;xj&-*V&~U1g4at-jcl+07?>~ zCm)Kejh1@%(`!%11e&z7>& z@uM_>B3&EYoEJ`r4yeJO=?YGV__N|{nXfh*2Kqs5dn)-AkC;d?g~_D#utcQ3A^YVi zl!xyi0g|Nm3y1EQq=yE+NsG@#uaiCsT&fP*uWshxl~bgTkZ39DE{*@ZgwiF<98@YSCyfe_)3| zXY%-*b>HAlD%Jl8(;vxdKG z#Tb|i8McRgRP2a7aBnORmXLz=A(w-%t~oMMy-?`Y9VlJN4E-&q4lAOhPx!OfuepPQ zs10n6c)^1?)2Ehl#?h5iWvn5xlM4a4eC|k&n2jwAY+Ox25Qkcf5>g2&KR~$uSh>5m zU)bb->V(c38nSNPRaN|i9>rysO7#^6hQh!&Lgi5UBk1#!v*a%_^2b0zuCc=y>sUk< z=Kv^c&|M#MUsKhON^qxj0MoMU-*jJ0;jt#&LsE9_noZ&*4x^|10`?~UWpNtng%+%t zSy@nf-y9(}AtJod7D;qIuo@iK_+WWk*%2xUPfrRo6ySVHDRQvK@@9WV-gc#%9A1ba z;e4Q7UZt0~phN|U#77}DxK$u~2gWm$D z);Bi1z;6EKi|j`Co&qFpIagS)a6P>SE(tw7Jp?k7@m5G(_SoU<(%R@9WIV&c!|4|m z#v7tlh^zF_Zwx8^6f~ob*p@l!13$ABQZ6#iiyhKBI~BDKeXx(7cZzXnfb1E zP3BG6ZZO>#tYcFBDd?_-C_q=xh4vtAb&_&%85u8X&54K%M?Kcx=@icXn|st7ZKug= zSER8voW)O+l79~iVYyL?cWK8-nJWtm4t?3uJTgxvl)Uqpl+;hraOnhEZ~Jj(X19g^F_Nu*a1$~ z;N@%Yny{WhG9HSC#tmdL-gJ?LG?XmBKj}6pSn98ry;(3Ms+gvHzhsduo+m5C@n`tT z@L|QU3EMv_i$`Q{of{HPs;g{LnT>SZj^8r3#ap=~z&PFJYvY6jruH}cpH=I;e7e7J z%E^}EaC0kNzA_<|p{iqtBbWRtOihKW?-rB|A+jd9>MR1LeF-r8huzzL>zdv=diS>C z-YxDwLb!v!hH%4yViI33Jbc)DW_L~We(^Kj8?*PKp0*CJ&YS3FSoY^9_%w0K6!oV@CDYJPQ-7_#C0YnCp z!Rhm1_F1ii6`0?B`htk|mIwq9#E?T$*Vm_!WjQpgNzci2%e@K}0lu)h_IAxIXYj;7 zSyFvoTXEfPL5`lT`vUa1Gp@RZ#`N;g52R*l7dQBKH)nwBu?ML;@&zHZ?Cdm<<#Qfu zZR%SZ_v0t_%9^@zc(G<5=g{&kXS^Zl1geW5RjA@8GbH`idx>{%7+K=EykC)5+@8#Q zwez{t@%q=l#CDti7*DD!&X1%r@YXlhHGez@m9Jh}H4P16Hpw69%s8p5iSOuxgfhXd z8w}&@-L2s7v0P4PDs?MKK&MW&Q+1F`nWmze*-rb)wXOomG(5Cm<$NyqdoE)El7 zBZHmgn7*I_+5`%G@*B9EoUb{pPsD+LtA;ic>0AsACvi)@Z)Gwl1|~k%1d;34u6^j| zrtL!x1|=F04#i=Hkg)GludzE~9UM4A>OT*-dw`Nt1+$ZsQwcU;Q8F$^v8Y`KtgWWT zVpbpmz`%jT=EN-h`eO%jD#IsRzJ=*&k{R?5X|lCw{M+5%PA+J0vQ4!0P(hfMYju1T zu=?{!OFO*9pxWBQJ*x#XcuQ^d=ojpoV;6eHkHM1yodl43l#OAEAGwobHvmyuE-=u>)dtIrr*9l_Et;yt)V@n zV`KZ=k-VvlvT}UX>z<&{7Nj5beA?h)O0u>dXhl39cTvC0vVFkjp#+zNZWyJ1NeTH2 zPtAkPj5Usj>j-#b%w&z|>7}c@)WUtPCR7BGDM-%#{YANeH<&UqSmhhTTaCE0aeYOQ z=xq_w1PPY8`5Z`T4r!Cjs{^@d23FS(<7AeEH1RZcb0DQo#Z%C|Px1V1F#R2mZFK!r zZuwtPIQN=lpkNA4Tu282 z1e8YI&slh~MYl?bye4kE`xGyc;Qui*IG%Osd%oo|KHM71ZyDv^1i1O39sLh9Uc;I* zy-^mfWHvfR`#m(L7zhkkYg;$}{&!|Q*{!?FWs_dMT4j?}NgaA(O@d|v5)gG>h56UT zofR6)gm_mFe1~3ob9wU#zl%rsaG!pQHDd3Ev}Mqr&gNlYPKLaBy>ZhH89n z#8`=&GuuayNHshzbnN{2?4jS>1d5fxSZXAt%o7%11rJ9Ps;=F1}$ssSzaFZ4nxSh+8=hLvSDL2f*zlH$ocBpK)Y#i?5fP?du`?q4&J7AW#sRwL z>0EUD-!4=YYYm?oP?R~lzzA8_u?e)@&#X=0;&TGB^JQVD_7N!>u|5Nw%@Of|?* zEo7l6-P+<(8;N$bbfl1_=|11YZe(yebp7b<7oGDED%Aq_VfOS($QEZx48{A2)n^Ud zWdQPo3A@*3%2w2fTle|Zu(9s8xV~F2?H;VwBBPG*AsSfO=q{?s<#G>5eOqpdj+mc+ zp0R$a6dtX%I3z%II@C>EA$l=vZO1{ZYA4OnyxEs|JL+x$r$Ngl*UC1$EUNRldi6K< zOT*Yo)Ol7IM>DDQRvTC654$*A%UKcLuGVdBw$^r}YoYwX+?c!=``5Ak*=|e~M97fQ z*edB5%NC(Vttd)AE##~fV4%IyRNN{+yMbX^hCd@B;|k_47Dwf@4*@ zkk#3%Yx}ei3;mpea`#4$#$yFO5`R!6@24scZ0-ox-`V6IbUO$WFKInIOUT7c_>5Qn z%9*n?+ImftyEpYCQ4GQkm52J%D8dO*=E{RkMem~C8^14$N@B6}~nz81i1GTSjHQkuNRJp#SiQeBSd3*+S*?v=#ADcIJlF z9tRGWeR4^6ckCtHai9DLex}}<`aMVEi@d=B0m_r0@0vZ$XOVjIE6_v%Z)BC3i%fwZmm7~vj z_J{2wJlh|MoHdr>Bn_0xSp1tO`WuppCb?RT*2@H_*1J(88LW{?x8a(Oe z91CqTjxK#P{09Tt`A<0O-YBemGtv}P8D(_cwnu?g(~}}?mpNLLQ;0F?-8899lXR7i zpcZoiaq3gD&cSzDi1cxq&elqN#QKb>{E<>7x?0BY*K8Zstm(!f`+H7((RZ5DO=A4N zR+zOtypWH0SE~K$(q(E|X6tMHJ!yNsx*FDZk6bD^A1Mp&@xPv&*LB#~X%$7@)S&Bi z%zeDecEci2HLC$~B3@mK!!C}E4e#L9tt}1@H&_U3;=?L>l2trewPm6jCv%6m&T3d_ zL+IHtDRbkd^iDZ$&v!z}o$_p3!KhtKbuwa>^6Zxp8VYk*BIF#FY&9Y79s}vZEbdnR zTxq=DaZ5aR+MPNFqH6mVMMNmK{mW&SH&hXxd}um8Ne!YLT-SUPv~Vkf_T+SoCv>yD zhLbVHrwQoxyF#1YhLaQH+{xcNtAy^`?})0G&M-H!9}p*4R{8|r*^lURw*~b%4Y_`A z$?nbRGua=zqvzn7pPk%bKlNZxyNrl=zR6h{^IAN-I=UzRabacnQCefjyzRFn^?&Yo3>)#6lY{6#l4N`=W#_J1|a zSRbxw#9Yuw3%!7&(8FQc5_NHa=0FIwW{vW`MtJAbt!2jp!50DP=RtFX3_g`;Q?Gxk z4=|Uya~PtjD$7R8#3p5|sd?tFQNYF&L@mgVLH%prz>jwbxNwg;=V&ix!1{4J6TA^! z+`fFh@B)p#w3E<1 zmN@dl9h~4rlneNT4xCuFh0krIEo0uP37#nOL-#MkhB>&3x=xE6nglE*^vV3flLLk# z-<+$ZibynMu=*fl{Vt~aS(KJ|GNEk>;gA6nwJE8CsU9LZ6rt6>D>M@sX4kKFnuB^N z5+8x^6uNo_frz+`fk22|qD3GOPDkO@u_7MF8&@ax-Q3_`s6y!<2w$KQA`fiJK|?`^ z(IJm~<^%tH%e#n*Km`8$FDC(W zKYN=xm-Z3=)bP?KO2bbmS)30FY~t^3P(PxpO@j#AITP+ca=sq#wTlS zfIWUMhf*xtzJ6VqLDd_vn;qHS?LIc7>T>;K2!x*4&u7=sW7e7D@6SmC4a5E@AaY}&$sO*VBU3c0 XLX9;+!SWs458}?Pdy?7WdS3qrQBAU( literal 0 HcmV?d00001 diff --git a/debian/confconsole/usr/share/doc/confconsole/images/01_confconsole_core_advanced.png b/debian/confconsole/usr/share/doc/confconsole/images/01_confconsole_core_advanced.png new file mode 100644 index 0000000000000000000000000000000000000000..2fd6b5a94ace5964f8aa5584d5cc36dcf6612306 GIT binary patch literal 48044 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 VjKOZ8rZ5B|`&db;NaC6Q{{sa^?E3%! literal 0 HcmV?d00001 diff --git a/debian/confconsole/usr/share/doc/confconsole/images/02_confconsole_core_networking.png b/debian/confconsole/usr/share/doc/confconsole/images/02_confconsole_core_networking.png new file mode 100644 index 0000000000000000000000000000000000000000..77b55ce9f562d46c377ba2827329a10cd1269a44 GIT binary patch literal 36302 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 literal 0 HcmV?d00001 diff --git a/debian/confconsole/usr/share/doc/confconsole/images/04_confconsole_mail_relay.png b/debian/confconsole/usr/share/doc/confconsole/images/04_confconsole_mail_relay.png new file mode 100644 index 0000000000000000000000000000000000000000..df270b05ee47609a129f49eb9e66e1947f0d1fa5 GIT binary patch literal 21225 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 ?2b!(|Gp%KS&Cci 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 NPY?LlRbPc%|1X~Y>A3&^ literal 0 HcmV?d00001 diff --git a/debian/confconsole/usr/share/doc/confconsole/images/05_confconsole_proxy_settings.png b/debian/confconsole/usr/share/doc/confconsole/images/05_confconsole_proxy_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..c33d25f8f6d6481c00bd5e2851467ad699ed8753 GIT binary patch literal 21548 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%0D;%kG9EaIYj>IVYsU)PqF?E(u->GUux_BJobM^ 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