From cb11911665212724b4550949784b4894da5f743e Mon Sep 17 00:00:00 2001 From: "Mariya T." <235426764+sigreturn@users.noreply.github.com> Date: Sat, 20 Dec 2025 11:54:31 -0500 Subject: [PATCH 1/6] feat(applications): add CUPS installation support --- archinstall/applications/printer.py | 23 ++++++++++ .../lib/applications/application_handler.py | 4 ++ .../lib/applications/application_menu.py | 45 ++++++++++++++++++- archinstall/lib/global_menu.py | 5 +++ archinstall/lib/models/__init__.py | 8 +--- archinstall/lib/models/application.py | 24 ++++++++++ archinstall/locales/base.pot | 6 +++ tests/data/test_config.json | 3 ++ tests/test_args.py | 3 +- 9 files changed, 113 insertions(+), 8 deletions(-) create mode 100644 archinstall/applications/printer.py diff --git a/archinstall/applications/printer.py b/archinstall/applications/printer.py new file mode 100644 index 0000000000..2a8950eb45 --- /dev/null +++ b/archinstall/applications/printer.py @@ -0,0 +1,23 @@ +from typing import TYPE_CHECKING + +from archinstall.lib.output import debug + +if TYPE_CHECKING: + from archinstall.lib.installer import Installer + + +class PrinterApp: + @property + def packages(self) -> list[str]: + return ['cups', 'system-config-printer', 'cups-pk-helper'] + + @property + def services(self) -> list[str]: + return [ + 'cups.service', + ] + + def install(self, install_session: 'Installer') -> None: + debug('Installing print service') + install_session.add_additional_packages(self.packages) + install_session.enable_service(self.services) diff --git a/archinstall/lib/applications/application_handler.py b/archinstall/lib/applications/application_handler.py index 29e30f471a..e27b1c3c9c 100644 --- a/archinstall/lib/applications/application_handler.py +++ b/archinstall/lib/applications/application_handler.py @@ -2,6 +2,7 @@ from archinstall.applications.audio import AudioApp from archinstall.applications.bluetooth import BluetoothApp +from archinstall.applications.printer import PrinterApp from archinstall.lib.models import Audio from archinstall.lib.models.application import ApplicationConfiguration from archinstall.lib.models.users import User @@ -25,5 +26,8 @@ def install_applications(self, install_session: 'Installer', app_config: Applica users, ) + if app_config.printer_config and app_config.printer_config.enabled: + PrinterApp().install(install_session) + application_handler = ApplicationHandler() diff --git a/archinstall/lib/applications/application_menu.py b/archinstall/lib/applications/application_menu.py index ad20f6405e..e9b0fb352e 100644 --- a/archinstall/lib/applications/application_menu.py +++ b/archinstall/lib/applications/application_menu.py @@ -1,7 +1,7 @@ from typing import override from archinstall.lib.menu.abstract_menu import AbstractSubMenu -from archinstall.lib.models.application import ApplicationConfiguration, Audio, AudioConfiguration, BluetoothConfiguration +from archinstall.lib.models.application import ApplicationConfiguration, Audio, AudioConfiguration, BluetoothConfiguration, PrinterConfiguration from archinstall.lib.translationhandler import tr from archinstall.tui.curses_menu import SelectMenu from archinstall.tui.menu_item import MenuItem, MenuItemGroup @@ -48,6 +48,12 @@ def _define_menu_options(self) -> list[MenuItem]: preview_action=self._prev_audio, key='audio_config', ), + MenuItem( + text=tr('Print service'), + action=select_audio, + preview_action=self._prev_printer, + key='printer_config', + ), ] def _prev_bluetooth(self, item: MenuItem) -> str | None: @@ -65,6 +71,15 @@ def _prev_audio(self, item: MenuItem) -> str | None: return f'{tr("Audio")}: {config.audio.value}' return None + def _prev_printer(self, item: MenuItem) -> str | None: + if item.value is not None: + printer_config: PrinterConfiguration = item.value + + output = 'Print service: ' + output += tr('Enabled') if printer_config.enabled else tr('Disabled') + return output + return None + def select_bluetooth(preset: BluetoothConfiguration | None) -> BluetoothConfiguration | None: group = MenuItemGroup.yes_no() @@ -94,6 +109,34 @@ def select_bluetooth(preset: BluetoothConfiguration | None) -> BluetoothConfigur raise ValueError('Unhandled result type') +def select_printer(preset: PrinterConfiguration | None) -> PrinterConfiguration | None: + group = MenuItemGroup.yes_no() + group.focus_item = MenuItem.no() + + if preset is not None: + group.set_selected_by_value(preset.enabled) + + header = tr('Would you like to configure the print service?') + '\n' + + result = SelectMenu[bool]( + group, + header=header, + alignment=Alignment.CENTER, + columns=2, + orientation=Orientation.HORIZONTAL, + allow_skip=True, + ).run() + + match result.type_: + case ResultType.Selection: + enabled = result.item() == MenuItem.yes() + return PrinterConfiguration(enabled) + case ResultType.Skip: + return preset + case _: + raise ValueError('Unhandled result type') + + def select_audio(preset: AudioConfiguration | None = None) -> AudioConfiguration | None: items = [MenuItem(a.value, value=a) for a in Audio] group = MenuItemGroup(items) diff --git a/archinstall/lib/global_menu.py b/archinstall/lib/global_menu.py index 8c13daf402..1a8e5b38cd 100644 --- a/archinstall/lib/global_menu.py +++ b/archinstall/lib/global_menu.py @@ -324,6 +324,11 @@ def _prev_applications(self, item: MenuItem) -> str | None: output += f'{tr("Audio")}: {audio_config.audio.value}' output += '\n' + if app_config.printer_config: + output += f'{tr("Print service")}: ' + output += tr('Enabled') if app_config.printer_config.enabled else tr('Disabled') + output += '\n' + return output return None diff --git a/archinstall/lib/models/__init__.py b/archinstall/lib/models/__init__.py index 45d2b0259d..27d7654f73 100644 --- a/archinstall/lib/models/__init__.py +++ b/archinstall/lib/models/__init__.py @@ -1,9 +1,4 @@ -from .application import ( - ApplicationConfiguration, - Audio, - AudioConfiguration, - BluetoothConfiguration, -) +from .application import ApplicationConfiguration, Audio, AudioConfiguration, BluetoothConfiguration, PrinterConfiguration from .bootloader import Bootloader from .device import ( BDevice, @@ -76,6 +71,7 @@ 'PartitionTable', 'PartitionType', 'PasswordStrength', + 'PrinterConfiguration', 'ProfileConfiguration', 'Repository', 'SectorSize', diff --git a/archinstall/lib/models/application.py b/archinstall/lib/models/application.py index eeced89881..9684d82a4a 100644 --- a/archinstall/lib/models/application.py +++ b/archinstall/lib/models/application.py @@ -17,9 +17,14 @@ class AudioConfigSerialization(TypedDict): audio: str +class PrinterConfigSerialization(TypedDict): + enabled: bool + + class ApplicationSerialization(TypedDict): bluetooth_config: NotRequired[BluetoothConfigSerialization] audio_config: NotRequired[AudioConfigSerialization] + printer_config: NotRequired[PrinterConfigSerialization] @dataclass @@ -50,10 +55,23 @@ def parse_arg(arg: dict[str, Any]) -> 'BluetoothConfiguration': return BluetoothConfiguration(arg['enabled']) +@dataclass +class PrinterConfiguration: + enabled: bool + + def json(self) -> PrinterConfigSerialization: + return {'enabled': self.enabled} + + @staticmethod + def parse_arg(arg: dict[str, Any]) -> 'PrinterConfiguration': + return PrinterConfiguration(arg['enabled']) + + @dataclass class ApplicationConfiguration: bluetooth_config: BluetoothConfiguration | None = None audio_config: AudioConfiguration | None = None + printer_config: PrinterConfiguration | None = None @staticmethod def parse_arg( @@ -72,6 +90,9 @@ def parse_arg( if args and (audio_config := args.get('audio_config')) is not None: app_config.audio_config = AudioConfiguration.parse_arg(audio_config) + if args and (printer_config := args.get('printer_config')) is not None: + app_config.printer_config = PrinterConfiguration.parse_arg(printer_config) + return app_config def json(self) -> ApplicationSerialization: @@ -83,4 +104,7 @@ def json(self) -> ApplicationSerialization: if self.audio_config: config['audio_config'] = self.audio_config.json() + if self.printer_config: + config['printer_config'] = self.printer_config.json() + return config diff --git a/archinstall/locales/base.pot b/archinstall/locales/base.pot index 67da11b2ab..11dc6e5494 100644 --- a/archinstall/locales/base.pot +++ b/archinstall/locales/base.pot @@ -1779,6 +1779,12 @@ msgstr "" msgid "Would you like to configure Bluetooth?" msgstr "" +msgid "Print service" +msgstr "" + +msgid "Would you like to configure the print service?" +msgstr "" + msgid "Authentication" msgstr "" diff --git a/tests/data/test_config.json b/tests/data/test_config.json index a01c831b7f..2b6f4f472c 100644 --- a/tests/data/test_config.json +++ b/tests/data/test_config.json @@ -7,6 +7,9 @@ }, "audio_config": { "audio": "pipewire" + }, + "printer_config": { + "enabled": true } }, "auth_config": { diff --git a/tests/test_args.py b/tests/test_args.py index 87ab1a3f47..4669694a7a 100644 --- a/tests/test_args.py +++ b/tests/test_args.py @@ -7,7 +7,7 @@ from archinstall.default_profiles.profile import GreeterType from archinstall.lib.args import ArchConfig, ArchConfigHandler, Arguments from archinstall.lib.hardware import GfxDriver -from archinstall.lib.models.application import ApplicationConfiguration, Audio, AudioConfiguration, BluetoothConfiguration +from archinstall.lib.models.application import ApplicationConfiguration, Audio, AudioConfiguration, BluetoothConfiguration, PrinterConfiguration from archinstall.lib.models.authentication import AuthenticationConfiguration, U2FLoginConfiguration, U2FLoginMethod from archinstall.lib.models.bootloader import Bootloader, BootloaderConfiguration from archinstall.lib.models.device import DiskLayoutConfiguration, DiskLayoutType @@ -132,6 +132,7 @@ def test_config_file_parsing( app_config=ApplicationConfiguration( bluetooth_config=BluetoothConfiguration(enabled=True), audio_config=AudioConfiguration(audio=Audio.PIPEWIRE), + printer_config=PrinterConfiguration(enabled=True) ), auth_config=AuthenticationConfiguration( root_enc_password=Password(enc_password='password_hash'), From 667714001f44468d699a42b7919237a61b71511d Mon Sep 17 00:00:00 2001 From: "Mariya T." <235426764+sigreturn@users.noreply.github.com> Date: Sat, 20 Dec 2025 11:58:10 -0500 Subject: [PATCH 2/6] fix: use translation for print service preview_action --- archinstall/lib/applications/application_menu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/archinstall/lib/applications/application_menu.py b/archinstall/lib/applications/application_menu.py index e9b0fb352e..0633ab7aa1 100644 --- a/archinstall/lib/applications/application_menu.py +++ b/archinstall/lib/applications/application_menu.py @@ -75,7 +75,7 @@ def _prev_printer(self, item: MenuItem) -> str | None: if item.value is not None: printer_config: PrinterConfiguration = item.value - output = 'Print service: ' + output = f'{tr("Print service")}: ' output += tr('Enabled') if printer_config.enabled else tr('Disabled') return output return None From 413e531437993fde38bf0b12c59e7f8977bca417 Mon Sep 17 00:00:00 2001 From: "Mariya T." <235426764+sigreturn@users.noreply.github.com> Date: Sat, 20 Dec 2025 12:13:32 -0500 Subject: [PATCH 3/6] fix: incorrect action for print service menu item --- archinstall/lib/applications/application_menu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/archinstall/lib/applications/application_menu.py b/archinstall/lib/applications/application_menu.py index 0633ab7aa1..8297756b49 100644 --- a/archinstall/lib/applications/application_menu.py +++ b/archinstall/lib/applications/application_menu.py @@ -50,7 +50,7 @@ def _define_menu_options(self) -> list[MenuItem]: ), MenuItem( text=tr('Print service'), - action=select_audio, + action=select_printer, preview_action=self._prev_printer, key='printer_config', ), From 2fe0fd406e04bee9a3d50ee2cbcde2066e714743 Mon Sep 17 00:00:00 2001 From: "Mariya T." <235426764+sigreturn@users.noreply.github.com> Date: Sat, 20 Dec 2025 20:42:37 -0500 Subject: [PATCH 4/6] chore: refactor naming, printer -> print service --- .../{printer.py => print_service.py} | 2 +- .../lib/applications/application_handler.py | 6 ++--- .../lib/applications/application_menu.py | 18 +++++++-------- archinstall/lib/models/__init__.py | 4 ++-- archinstall/lib/models/application.py | 22 +++++++++---------- tests/data/test_config.json | 2 +- tests/test_args.py | 4 ++-- 7 files changed, 29 insertions(+), 29 deletions(-) rename archinstall/applications/{printer.py => print_service.py} (95%) diff --git a/archinstall/applications/printer.py b/archinstall/applications/print_service.py similarity index 95% rename from archinstall/applications/printer.py rename to archinstall/applications/print_service.py index 2a8950eb45..7660bad496 100644 --- a/archinstall/applications/printer.py +++ b/archinstall/applications/print_service.py @@ -6,7 +6,7 @@ from archinstall.lib.installer import Installer -class PrinterApp: +class PrintServiceApp: @property def packages(self) -> list[str]: return ['cups', 'system-config-printer', 'cups-pk-helper'] diff --git a/archinstall/lib/applications/application_handler.py b/archinstall/lib/applications/application_handler.py index e27b1c3c9c..990b49fb69 100644 --- a/archinstall/lib/applications/application_handler.py +++ b/archinstall/lib/applications/application_handler.py @@ -2,7 +2,7 @@ from archinstall.applications.audio import AudioApp from archinstall.applications.bluetooth import BluetoothApp -from archinstall.applications.printer import PrinterApp +from archinstall.applications.print_service import PrintServiceApp from archinstall.lib.models import Audio from archinstall.lib.models.application import ApplicationConfiguration from archinstall.lib.models.users import User @@ -26,8 +26,8 @@ def install_applications(self, install_session: 'Installer', app_config: Applica users, ) - if app_config.printer_config and app_config.printer_config.enabled: - PrinterApp().install(install_session) + if app_config.print_service_config and app_config.print_service_config.enabled: + PrintServiceApp().install(install_session) application_handler = ApplicationHandler() diff --git a/archinstall/lib/applications/application_menu.py b/archinstall/lib/applications/application_menu.py index 8297756b49..b60147c7b3 100644 --- a/archinstall/lib/applications/application_menu.py +++ b/archinstall/lib/applications/application_menu.py @@ -1,7 +1,7 @@ from typing import override from archinstall.lib.menu.abstract_menu import AbstractSubMenu -from archinstall.lib.models.application import ApplicationConfiguration, Audio, AudioConfiguration, BluetoothConfiguration, PrinterConfiguration +from archinstall.lib.models.application import ApplicationConfiguration, Audio, AudioConfiguration, BluetoothConfiguration, PrintServiceConfiguration from archinstall.lib.translationhandler import tr from archinstall.tui.curses_menu import SelectMenu from archinstall.tui.menu_item import MenuItem, MenuItemGroup @@ -50,9 +50,9 @@ def _define_menu_options(self) -> list[MenuItem]: ), MenuItem( text=tr('Print service'), - action=select_printer, - preview_action=self._prev_printer, - key='printer_config', + action=select_print_service, + preview_action=self._prev_print_service, + key='print_service_config', ), ] @@ -71,12 +71,12 @@ def _prev_audio(self, item: MenuItem) -> str | None: return f'{tr("Audio")}: {config.audio.value}' return None - def _prev_printer(self, item: MenuItem) -> str | None: + def _prev_print_service(self, item: MenuItem) -> str | None: if item.value is not None: - printer_config: PrinterConfiguration = item.value + print_service_config: PrintServiceConfiguration = item.value output = f'{tr("Print service")}: ' - output += tr('Enabled') if printer_config.enabled else tr('Disabled') + output += tr('Enabled') if print_service_config.enabled else tr('Disabled') return output return None @@ -109,7 +109,7 @@ def select_bluetooth(preset: BluetoothConfiguration | None) -> BluetoothConfigur raise ValueError('Unhandled result type') -def select_printer(preset: PrinterConfiguration | None) -> PrinterConfiguration | None: +def select_print_service(preset: PrintServiceConfiguration | None) -> PrintServiceConfiguration | None: group = MenuItemGroup.yes_no() group.focus_item = MenuItem.no() @@ -130,7 +130,7 @@ def select_printer(preset: PrinterConfiguration | None) -> PrinterConfiguration match result.type_: case ResultType.Selection: enabled = result.item() == MenuItem.yes() - return PrinterConfiguration(enabled) + return PrintServiceConfiguration(enabled) case ResultType.Skip: return preset case _: diff --git a/archinstall/lib/models/__init__.py b/archinstall/lib/models/__init__.py index 27d7654f73..d742bc9fe5 100644 --- a/archinstall/lib/models/__init__.py +++ b/archinstall/lib/models/__init__.py @@ -1,4 +1,4 @@ -from .application import ApplicationConfiguration, Audio, AudioConfiguration, BluetoothConfiguration, PrinterConfiguration +from .application import ApplicationConfiguration, Audio, AudioConfiguration, BluetoothConfiguration, PrintServiceConfiguration from .bootloader import Bootloader from .device import ( BDevice, @@ -71,7 +71,7 @@ 'PartitionTable', 'PartitionType', 'PasswordStrength', - 'PrinterConfiguration', + 'PrintServiceConfiguration', 'ProfileConfiguration', 'Repository', 'SectorSize', diff --git a/archinstall/lib/models/application.py b/archinstall/lib/models/application.py index 9684d82a4a..f5f9e99d8f 100644 --- a/archinstall/lib/models/application.py +++ b/archinstall/lib/models/application.py @@ -17,14 +17,14 @@ class AudioConfigSerialization(TypedDict): audio: str -class PrinterConfigSerialization(TypedDict): +class PrintServiceConfigSerialization(TypedDict): enabled: bool class ApplicationSerialization(TypedDict): bluetooth_config: NotRequired[BluetoothConfigSerialization] audio_config: NotRequired[AudioConfigSerialization] - printer_config: NotRequired[PrinterConfigSerialization] + print_service_config: NotRequired[PrintServiceConfigSerialization] @dataclass @@ -56,22 +56,22 @@ def parse_arg(arg: dict[str, Any]) -> 'BluetoothConfiguration': @dataclass -class PrinterConfiguration: +class PrintServiceConfiguration: enabled: bool - def json(self) -> PrinterConfigSerialization: + def json(self) -> PrintServiceConfigSerialization: return {'enabled': self.enabled} @staticmethod - def parse_arg(arg: dict[str, Any]) -> 'PrinterConfiguration': - return PrinterConfiguration(arg['enabled']) + def parse_arg(arg: dict[str, Any]) -> 'PrintServiceConfiguration': + return PrintServiceConfiguration(arg['enabled']) @dataclass class ApplicationConfiguration: bluetooth_config: BluetoothConfiguration | None = None audio_config: AudioConfiguration | None = None - printer_config: PrinterConfiguration | None = None + print_service_config: PrintServiceConfiguration | None = None @staticmethod def parse_arg( @@ -90,8 +90,8 @@ def parse_arg( if args and (audio_config := args.get('audio_config')) is not None: app_config.audio_config = AudioConfiguration.parse_arg(audio_config) - if args and (printer_config := args.get('printer_config')) is not None: - app_config.printer_config = PrinterConfiguration.parse_arg(printer_config) + if args and (print_service_config := args.get('print_service_config')) is not None: + app_config.print_service_config = PrintServiceConfiguration.parse_arg(print_service_config) return app_config @@ -104,7 +104,7 @@ def json(self) -> ApplicationSerialization: if self.audio_config: config['audio_config'] = self.audio_config.json() - if self.printer_config: - config['printer_config'] = self.printer_config.json() + if self.print_service_config: + config['print_service_config'] = self.print_service_config.json() return config diff --git a/tests/data/test_config.json b/tests/data/test_config.json index 2b6f4f472c..618bd8e9c7 100644 --- a/tests/data/test_config.json +++ b/tests/data/test_config.json @@ -8,7 +8,7 @@ "audio_config": { "audio": "pipewire" }, - "printer_config": { + "print_service_config": { "enabled": true } }, diff --git a/tests/test_args.py b/tests/test_args.py index 4669694a7a..a2edc28d6e 100644 --- a/tests/test_args.py +++ b/tests/test_args.py @@ -7,7 +7,7 @@ from archinstall.default_profiles.profile import GreeterType from archinstall.lib.args import ArchConfig, ArchConfigHandler, Arguments from archinstall.lib.hardware import GfxDriver -from archinstall.lib.models.application import ApplicationConfiguration, Audio, AudioConfiguration, BluetoothConfiguration, PrinterConfiguration +from archinstall.lib.models.application import ApplicationConfiguration, Audio, AudioConfiguration, BluetoothConfiguration, PrintServiceConfiguration from archinstall.lib.models.authentication import AuthenticationConfiguration, U2FLoginConfiguration, U2FLoginMethod from archinstall.lib.models.bootloader import Bootloader, BootloaderConfiguration from archinstall.lib.models.device import DiskLayoutConfiguration, DiskLayoutType @@ -132,7 +132,7 @@ def test_config_file_parsing( app_config=ApplicationConfiguration( bluetooth_config=BluetoothConfiguration(enabled=True), audio_config=AudioConfiguration(audio=Audio.PIPEWIRE), - printer_config=PrinterConfiguration(enabled=True) + print_service_config=PrintServiceConfiguration(enabled=True) ), auth_config=AuthenticationConfiguration( root_enc_password=Password(enc_password='password_hash'), From cea1b0d9c6166cc753ef3a27fe3f10d60b623ad4 Mon Sep 17 00:00:00 2001 From: "Mariya T." <235426764+sigreturn@users.noreply.github.com> Date: Sat, 20 Dec 2025 20:47:18 -0500 Subject: [PATCH 5/6] fix: commit untracked file --- archinstall/lib/global_menu.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/archinstall/lib/global_menu.py b/archinstall/lib/global_menu.py index 1a8e5b38cd..9f7ec10398 100644 --- a/archinstall/lib/global_menu.py +++ b/archinstall/lib/global_menu.py @@ -324,9 +324,9 @@ def _prev_applications(self, item: MenuItem) -> str | None: output += f'{tr("Audio")}: {audio_config.audio.value}' output += '\n' - if app_config.printer_config: + if app_config.print_service_config: output += f'{tr("Print service")}: ' - output += tr('Enabled') if app_config.printer_config.enabled else tr('Disabled') + output += tr('Enabled') if app_config.print_service_config.enabled else tr('Disabled') output += '\n' return output From 5ce0f09d0a963e1931938d92f48ff18a514ee3e9 Mon Sep 17 00:00:00 2001 From: "Mariya T." <235426764+sigreturn@users.noreply.github.com> Date: Sat, 20 Dec 2025 22:16:51 -0500 Subject: [PATCH 6/6] chore: fix formatting to make linter happy --- tests/test_args.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_args.py b/tests/test_args.py index a2edc28d6e..d3400c83d9 100644 --- a/tests/test_args.py +++ b/tests/test_args.py @@ -132,7 +132,7 @@ def test_config_file_parsing( app_config=ApplicationConfiguration( bluetooth_config=BluetoothConfiguration(enabled=True), audio_config=AudioConfiguration(audio=Audio.PIPEWIRE), - print_service_config=PrintServiceConfiguration(enabled=True) + print_service_config=PrintServiceConfiguration(enabled=True), ), auth_config=AuthenticationConfiguration( root_enc_password=Password(enc_password='password_hash'),