diff --git a/archinstall/applications/firewall.py b/archinstall/applications/firewall.py index dadaa05b16..c7be6f2afc 100644 --- a/archinstall/applications/firewall.py +++ b/archinstall/applications/firewall.py @@ -32,6 +32,24 @@ def fwd_services(self) -> list[str]: 'firewalld.service', ] + def _allow_ufw_ssh_on_first_boot(self, install_session: Installer) -> None: + service_content = """[Unit] + Description=Allow SSH in UFW on first boot + After=ufw.service + Wants=ufw.service + + [Service] + Type=oneshot + ExecStart=/usr/bin/ufw allow SSH + ExecStartPost=/usr/bin/systemctl disable ufw-allow-ssh.service + + [Install] + WantedBy=multi-user.target + """ + service_path = install_session.target / 'etc/systemd/system/ufw-allow-ssh.service' + service_path.write_text(service_content) + install_session.enable_service(['ufw-allow-ssh.service']) + def install( self, install_session: Installer, @@ -47,6 +65,9 @@ def install( ufw_conf = install_session.target / 'etc/ufw/ufw.conf' ufw_conf.write_text(ufw_conf.read_text().replace('ENABLED=no', 'ENABLED=yes')) + if firewall_config.allow_ssh: + self._allow_ufw_ssh_on_first_boot(install_session) + case Firewall.FWD: install_session.add_additional_packages(self.fwd_packages) install_session.enable_service(self.fwd_services) diff --git a/archinstall/lib/applications/application_menu.py b/archinstall/lib/applications/application_menu.py index 99e4872692..f1bf4a3ac3 100644 --- a/archinstall/lib/applications/application_menu.py +++ b/archinstall/lib/applications/application_menu.py @@ -76,6 +76,7 @@ def _define_menu_options(self) -> list[MenuItem]: MenuItem( text=tr('Firewall'), action=select_firewall, + value=self._app_config.firewall_config, preview_action=self._prev_firewall, key='firewall_config', ), @@ -121,7 +122,10 @@ def _prev_print_service(self, item: MenuItem) -> str | None: def _prev_firewall(self, item: MenuItem) -> str | None: if item.value is not None: config: FirewallConfiguration = item.value - return f'{tr("Firewall")}: {config.firewall.value}' + output = f'{tr("Firewall")}: {config.firewall.value}' + output += '\n' + output += f'{tr("Allow SSH")}: {config.allow_ssh}' + return output return None def _prev_fonts(self, item: MenuItem) -> str | None: @@ -230,7 +234,28 @@ async def select_firewall(preset: FirewallConfiguration | None = None) -> Firewa case ResultType.Skip: return preset case ResultType.Selection: - return FirewallConfiguration(firewall=result.get_value()) + selected_firewall = result.get_value() + header = tr('Would you like to allow incoming SSH connections through the firewall?') + '\n' + preset_ssh = preset.allow_ssh if preset else False + + ssh_result = await Confirmation( + header=header, + allow_skip=True, + preset=preset_ssh, + ).show() + + match ssh_result.type_: + case ResultType.Skip: + allow_ssh = preset_ssh + case ResultType.Selection: + allow_ssh = ssh_result.get_value() + case ResultType.Reset: + allow_ssh = False + + return FirewallConfiguration( + firewall=selected_firewall, + allow_ssh=allow_ssh, + ) case ResultType.Reset: return None diff --git a/archinstall/lib/global_menu.py b/archinstall/lib/global_menu.py index 72c31033af..3c582978fc 100644 --- a/archinstall/lib/global_menu.py +++ b/archinstall/lib/global_menu.py @@ -364,6 +364,8 @@ def _prev_applications(self, item: MenuItem) -> str | None: firewall_config = app_config.firewall_config output += f'{tr("Firewall")}: {firewall_config.firewall.value}' output += '\n' + output += f'{tr("Allow SSH")}: {firewall_config.allow_ssh}' + output += '\n' return output @@ -500,6 +502,13 @@ def _get_install_warnings(self) -> list[str]: if not isinstance(self._arch_config.network_config, NetworkConfiguration): warnings.append(tr('No network configuration selected. Network will need to be set up manually on the installed system.')) + firewall_config = self._arch_config.app_config.firewall_config + is_ufw = firewall_config and firewall_config.firewall and firewall_config.firewall.value == 'ufw' + has_openssh = 'openssh' in self._arch_config.packages + + if is_ufw and has_openssh and not firewall_config.allow_ssh: + warnings.append(tr('SSH not allowed through ufw. Rules will need to be set up manually on the installed system.')) + return warnings def _prev_install_invalid_config(self, item: MenuItem) -> PreviewResult | None: diff --git a/archinstall/lib/models/application.py b/archinstall/lib/models/application.py index 1e97a7846b..1b39c98d23 100644 --- a/archinstall/lib/models/application.py +++ b/archinstall/lib/models/application.py @@ -40,6 +40,7 @@ class Firewall(StrEnum): class FirewallConfigSerialization(TypedDict): firewall: str + allow_ssh: NotRequired[bool] class FontPackage(StrEnum): @@ -143,16 +144,16 @@ def parse_arg(cls, arg: PrintServiceConfigSerialization) -> Self: @dataclass class FirewallConfiguration: firewall: Firewall + allow_ssh: bool = False def json(self) -> FirewallConfigSerialization: - return { - 'firewall': self.firewall.value, - } + return {'firewall': self.firewall.value, 'allow_ssh': self.allow_ssh} @classmethod def parse_arg(cls, arg: dict[str, Any]) -> Self: return cls( Firewall(arg['firewall']), + allow_ssh=arg.get('allow_ssh', False), ) @@ -285,6 +286,10 @@ def summary(self) -> list[str]: if self.firewall_config: out.append(tr('Firewall "{}"').format(self.firewall_config.firewall)) + if self.firewall_config.allow_ssh: + out.append(tr('SSH allowed')) + else: + out.append(tr('SSH not allowed')) if self.fonts_config and self.fonts_config.fonts: fonts = ', '.join(f.value for f in self.fonts_config.fonts)