# SPDX-License-Identifier: AGPL-3.0-or-later """Configure Names App.""" import pathlib import augeas from plinth import action_utils from plinth.actions import privileged fallback_conf = pathlib.Path( '/etc/systemd/resolved.conf.d/freedombox-fallback.conf') override_conf = pathlib.Path('/etc/systemd/resolved.conf.d/freedombox.conf') source_fallback_conf = pathlib.Path( '/usr/share/freedombox' '/etc/systemd/resolved.conf.d/freedombox-fallback.conf') HOSTS_LOCAL_IP = '127.0.1.1' @privileged def set_hostname(hostname: str): """Set system hostname using hostnamectl.""" action_utils.run( ['hostnamectl', 'set-hostname', '--transient', '--static', hostname], check=True) action_utils.service_restart('avahi-daemon') def _load_augeas_hosts(): """Initialize Augeas for editing /etc/hosts.""" hosts_file = '/etc/hosts' aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD + augeas.Augeas.NO_MODL_AUTOLOAD) aug.transform('hosts', hosts_file) aug.set('/augeas/context', '/files' + hosts_file) aug.load() return aug def get_old_domains(aug=None) -> list[str]: """Return the list of domains store in old /etc/hosts format.""" if not aug: aug = _load_augeas_hosts() domains = {} # Maintain order of entries for match in aug.match('*'): if aug.get(match + '/ipaddr') != HOSTS_LOCAL_IP: continue domain = aug.get(match + '/canonical') aliases = [] for alias_match in aug.match(match + '/alias'): aliases.append(aug.get(alias_match)) # Read old style domains too. if aliases and '.' not in aliases[-1]: hostname = aliases[-1] if domain.startswith(hostname + '.'): domain = domain.partition('.')[2] aliases = aliases[:-1] for value in [domain] + aliases: if value: domains[domain] = True return list(domains.keys()) @privileged def domain_delete_all(): """Remove all static domain names from /etc/hosts.""" aug = _load_augeas_hosts() for match in aug.match('*'): if aug.get(match + '/ipaddr') == HOSTS_LOCAL_IP: aug.remove(match) aug.save() @privileged def install_resolved(): """Install systemd-resolved related packages.""" packages = ['systemd-resolved', 'libnss-resolve'] action_utils.run(['dpkg', '--configure', '-a'], check=False) with action_utils.apt_hold_freedombox(): action_utils.run_apt_command(['--fix-broken', 'install']) returncode = action_utils.run_apt_command(['install'] + packages) if returncode: raise RuntimeError( f'Apt command failed with return code: {returncode}') @privileged def set_resolved_configuration(dns_fallback: bool | None = None, dns_over_tls: str | None = None, dnssec: str | None = None): """Set systemd-resolved configuration options.""" if dns_fallback is not None: _set_enable_dns_fallback(dns_fallback) if dns_over_tls is not None or dnssec is not None: _set_resolved_configuration(dns_over_tls, dnssec) # Workaround buggy reload that does not apply DNS-over-TLS changes # properly. action_utils.service_try_restart('systemd-resolved') def get_resolved_configuration() -> dict[str, bool]: """Return systemd-resolved configuration.""" configuration = _get_resolved_configuration() configuration['dns_fallback'] = fallback_conf.exists() return configuration def _set_enable_dns_fallback(dns_fallback: bool): """Update whether to use DNS fallback servers.""" if dns_fallback: if not fallback_conf.exists(): fallback_conf.parent.mkdir(parents=True, exist_ok=True) fallback_conf.symlink_to(source_fallback_conf) else: fallback_conf.unlink(missing_ok=True) def _load_augeas(): """Initialize Augeas.""" aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD + augeas.Augeas.NO_MODL_AUTOLOAD) aug.transform('Systemd', str(override_conf)) aug.set('/augeas/context', '/files' + str(override_conf)) aug.load() return aug def _get_resolved_configuration(): """Return overridden configuration for systemd-resolved.""" aug = _load_augeas() # Default value for DNSSEC upstream is 'allow-downgrade', but in Debian it # is 'no'. return { 'dns_over_tls': aug.get('Resolve/DNSOverTLS/value') or 'no', 'dnssec': aug.get('Resolve/DNSSEC/value') or 'no' } def _set_resolved_configuration(dns_over_tls: str | None = None, dnssec: str | None = None): """Write configuration into a systemd-resolved override file.""" aug = _load_augeas() if dns_over_tls is not None: aug.set('Resolve/DNSOverTLS/value', dns_over_tls) if dnssec is not None: aug.set('Resolve/DNSSEC/value', dnssec) aug.save()